It’s all in the order of multiplication. This is the foundation of rigid animation. For example, if you have a car with hub caps that spin backwards regardless of how the wheel spins, then the car body would be a parent, the wheel would be a child of the parent, and the hub cap would be a child of the wheel. Each would have their own object/world matrix to contain their position and orientation. But you don’t use the wheel or the hub’s matrix directly. Instead you multiply the child by the parent and use the result to position and orient it. This allows the child to be attached to the parent and relative to the parent but move freely on it’s own.
I would challenge you to not build your model matrix every frame, but rather let the model matrix store the position and orientation of the model from one frame to another. Let it be the ultimate place to store the position and orientation of the object. You can decompose it when you need and get the position. (I think it’s the last column in the matrix, or is it last row, I can never remember). You can also get a forward, up, or right vector out of it as needed.
If you do it every frame, you have to move the model back to the origin to rotate or scale and then move it back to where it was within a single frame. No one will know it happened. Otherwise, scale will be skewed, and rotation will orbit the origin. You could do your translations last so that things would occur at the origin. When you’re at the origin, the object’s center and the origin match and so you rotate around the object’s center. If you translate first, they don’t match and rotations become orbits around the origin. Scaling is even uglier.
Here’s how I do a rotation:
Triangle.Transform(glm::rotate(glm::mat4(), glm::radians<float>(1), glm::vec3(0.0f, 1.0f, 0.0f)));
I pass an empty identity matrix to glm::rotate and a rotation giving me a rotation matrix. The Transform method is as follows:
void HandCodedObjectClass::Transform(glm::mat4 TransformationMatrix)
{
WorldMatrix *= TransformationMatrix;
}
So, I’m just combining a rotation with whatever is already in the object’s world matrix. The bad part of this is that the order of multiplication is hard coded. I think like this it’s a rotation, but you might want an orbit instead and then you would have to reverse the order of multiplication/combination.
For an example of how to rotate relative to another object I have this:
Cube2Pivot = Cube2Pivot * glm::rotate(glm::mat4(), glm::radians<float>(1), glm::vec3(0.0f, 1.0f, 0.0f));
Cube2.WorldMatrix = Cube.WorldMatrix * Cube2Pivot * Cube2World;
Here I have one cube orbiting another cube that you can move through the scene. Cube2Pivot is a matrix that determines the position and orientation of the second cube relative to the first (Cube).
If you are going to use the camera, realize that the camera is just like any other object’s world matrix except it is inversed. To turn it into a regular world matrix, you must invert it. Then you can treat it like any other object’s world matrix.
You might download my basic engine code on my website. The code in that project is pretty basic. There’s a video on that web page that shows what the code does. So you can see what it does without downloading the code and see whether it’s worth your time to download. The code above is from a slightly more complex version of the same code with these changes:
#include "Game.h"
using namespace OGLGameNameSpace;
Game::Game(void)
{
}
Game::~Game(void)
{
}
//=====================================================================================================================
// Game::Initialize()
//
// Purpose:
// To allow any code you want to run once at startup not associated with art assets.
//
// Input:
// None.
//
// Output:
// bool - The program will close if it returns false assuming a catastrophic error has occured.
//
// Notes:
//
//
//=====================================================================================================================
bool Game::Initialize()
{
bool GameObjectInitializedProperly = false; //Must be set to true to keep the program from closing.
CameraHeight = 1.68f; //Roughly the average eye height in meters of an average man. Our camera will stay at this level to make it feel like we are in the scene.
CameraTilt = 0.0f; //Will tilt the camera up and down.
GameObjectInitializedProperly = true; //This should probably be set by error checking, but we don't have any error checking here.
View = glm::lookAt(glm::vec3(0.0f, CameraHeight, 2.0f), glm::vec3(0.0f, CameraHeight, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f)); //1.63 meters is roughly the height of the average man's eyes.
//Projection = glm::perspective(0.96f, 1.770833f, 0.1f, 700.0f); //0.96 is 55 degrees and 1.7708333 is the width to height ratio on my computer.
Projection = glm::perspective(0.96f, OperatingSystem.AspectRatio(), 0.1f, 700.0f); //0.96 is 55 degrees and 1.7708333 is the width to height ratio on my computer.
glEnable(GL_CULL_FACE); //Turn back face culling on.
glCullFace(GL_BACK); //Set back face culling to remove the back face rather than the front face. Back is default. Included here for clarity.
glEnable(GL_DEPTH_TEST); //Enable the depth buffer so that things are drawn correctly.
glEnable(GL_FRAMEBUFFER_SRGB); //Needed to prepare frame buffer for sRGB color.
DiffuseLightDirection = glm::normalize(glm::vec3(1.0f, -1.0f, -1.0f)); //Direction that the primary light of the scene is "shining" in.
AmbientLightColor = glm::vec4(0.05f, 0.05f, 0.1f, 1.0f); //Light color in the "shadows".
DiffuseLightColor = glm::vec4(1.0f, 1.0f, 0.9f, 1.0f); //Direct light color.
return GameObjectInitializedProperly;
}
//=====================================================================================================================
//=====================================================================================================================
// Game::LoadContent()
//
// Purpose:
// To allow any code you want to run once at startup associated with art assets.
//
// Input:
// None.
//
// Output:
// bool - The program will close if it returns false assuming a catastrophic error has occured.
//
// Notes:
// Here we are creating all the objects in the scene. The switch statement makes it easy to manage this and add more
// objects of your own. You merely define all the objects vertices it will need to be drawn and then list the index order
// of every vertex to specify what order it will be drawn in. These examples merely count the number of vertices for the
// index which is largely pointless. However, the design means you "could" reuse vertices or specify them out of order and
// use the index buffer to specify what order to draw them in.
//
// You'll quickly notice that the number of vertices you have to specify for even the most simple of objects is nearly
// unacceptable. There are better ways, but we're learning the most straight forward way here which we will build on later.
//
// Textures were created with Paint.Net by saving as .DDS files with mip maps.
//
//=====================================================================================================================
bool Game::LoadContent()
{
bool NoCatastrophicFailuresOccured = false; //Close the program if even one mesh fails to initialize correctly.
float ScaleFactor = 200.0f;
float TerrainSize = 500.0f;
//GrassTexture.Load("Textures/Grass.dds", true);
GrassTexture.Load("Textures/FloorTiles.jpg", true);
//GrassTexture.Load("Textures/MudWall.jpg", true);
GrassTexture.Bind(0);
NoCatastrophicFailuresOccured = Shader.LoadShader("Shaders/BlinnPhong.vrt", "Shaders/BlinnPhong.frg");
glClearColor(0.392156862745098f, 0.5843137254901961f, 0.9294117647058824f, 1.0f); //XNA's "Cornflower Blue"
GLfloat TriangleVertexBuffer[] = {
0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f,
0.45f, -0.5, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, 1.0f,
-0.45f, -0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f
};
GLuint TriangleIndices[] = {
0,1,2
};
Triangle.DefineMesh(3, TriangleVertexBuffer, 3, TriangleIndices, nullptr);
GLfloat CubeVertexBuffer[] = {
//Front Quad.
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f,
//Right Quad.
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f,
//Bottom Quad.
0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f,
//Back Quad.
0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, 0.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, 0.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, 0.0f, 1.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, 0.0f, 1.0f,
//Left Quad.
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f,
//Top Quad.
-0.5f, 0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f,
0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f,
0.5f, 0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f,
};
GLuint CubeIndices[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35 };
Cube.DefineMesh(VERTICESINCUBE, CubeVertexBuffer, VERTICESINCUBE, CubeIndices, nullptr);
Cube2.DefineMesh(VERTICESINCUBE, CubeVertexBuffer, VERTICESINCUBE, CubeIndices, nullptr);
GLfloat GroundVertexBuffer[] = {
-TerrainSize, 0.0f, TerrainSize, 0.0f, ScaleFactor, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f,
TerrainSize, 0.0f, TerrainSize, ScaleFactor, ScaleFactor, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f,
TerrainSize, 0.0f, -TerrainSize, ScaleFactor, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f,
-TerrainSize, 0.0f, -TerrainSize, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f
};
GLuint GroundIndices[] = {
0,1,2,
0,2,3
};
Ground.DefineMesh(4, GroundVertexBuffer, 6, GroundIndices, &GrassTexture);
Cube2Pivot = glm::mat4();
Triangle.Transform(glm::translate(glm::mat4(), glm::vec3(0.0f, 1.63f, 0.0f)));
Cube.Transform(glm::translate(glm::mat4(), glm::vec3(-3.0f, 2.5f, -3.0f)));
//Cube2.Transform(glm::translate(glm::mat4(), glm::vec3(0.0f, 0.0f, 1.0f)));
Cube2World = glm::translate(glm::mat4(), glm::vec3(0.0f, 0.0f, 3.0f));
return NoCatastrophicFailuresOccured;
}
//=====================================================================================================================
//=====================================================================================================================
// Game::UnloadContent()
//
// Purpose:
// To allow any code you want to run as the program closes in order to cleanup.
//
// Input:
// None.
//
// Output:
// None.
//
// Notes:
// All the game objects you create need their Shutdown() methods called to release the associate vertex and index buffers.
// Since Shaders are owned by this class, it is also responsible for calling the Shutdown() method of any shaders created.
//
//=====================================================================================================================
void Game::UnloadContent()
{
//glDeleteVertexArrays(1, &vao);
//glDeleteBuffers(1, &vbo);
//glDeleteBuffers(1, &ibo);
}
//=====================================================================================================================
//=====================================================================================================================
// Game::Update()
//
// Purpose:
// To do everything that needs to be done in one frame except draw stuff to the screen.
//
// Input:
// float TimeDelta - Amount of time that has passed since the last frame occured in milliseconds.
//
// Output:
// None.
//
// Notes:
// This is where most of your game code will be. It gets called every frame to change anything that needs to be changed
// or done during that frame. It runs in a loop that needs to be called at least 30 times per second but there's nothing
// to control how often it gets called. Things would move at unpredictable rates if we did not use TimeDelta to take in to
// account the amount of time that has passed since the last frame.
//
// We start out by processing the keyboard and game controller input to change the camera's position and direction. You
// can also toggle full screen on and off.
//
// The camera movement this frame is stored as a 3D vector that we treat more like a 2D vector. The facing normal should
// point in the direction we want the camera to face. And as a normal should have a length of 1. Any movement during the
// frame is cumulative from the various controls. When you move it uses either the CameraFacingNormal or a normal rotated 90
// degrees away from the camera facing. It's basic vector addition to add the movement to the camera position.
//
// XMMatrixLookAtRH is used to create a view matrix to simulate the camera every frame. Generally, I would say it is a
// good idea to not continuously recreate the view matrix but it's easier then maintaining a view matrix between frames and
// this is really early in this tutorial series.
//
// Finally some very simple rigid animation is thrown in to show you not only how to do it, but that it can be done and how
// easy it is to do. Experiment by turning the rotations off and on and changing their directions and speed.
//
// The scene is lit with a simple Blinn-Phong shader that has "directional" lighting as opposed to point lights or
// spot lights. Directional lighting is nothing more than a direction that the light shines in. It is a normalized vector
// describing a direction and it has a color. That's all it is. Look at the shader for more detail. By rotating that direction
// the light source seems to orbit the scene similar to a day and night cycle except the light shines through solid objects.
//
//=====================================================================================================================
void Game::Update()
{
const float MaxTiltAngle = glm::radians(45.0);
const unsigned char* Buttons;
int JoyStick1Present = false;
int NumberOfJoyStickAxes = 0;
int NumberOfJoyStickButtons = 0;
const float* AxesArray = nullptr;
float LeftThumbStickY = 0.0f;
float LeftThumbStickX = 0.0f;
float Triggers = 0.0f; //XBox 360 controller triggers are a single axis for both triggers. Positive = Left. Negative = Right.
float RightThumbStickY = 0.0f;
float RightThumbStickX = 0.0f;
bool AButton = false;
bool BButton = false;
if (OperatingSystem.Keyboard.KeyPressed == GLFW_KEY_ESCAPE && OperatingSystem.Keyboard.ActionPressed == GLFW_PRESS) OperatingSystem.ShutDown();
JoyStick1Present = glfwJoystickPresent(GLFW_JOYSTICK_1);
if (JoyStick1Present)
{
AxesArray = glfwGetJoystickAxes(GLFW_JOYSTICK_1, &NumberOfJoyStickAxes);
Buttons = glfwGetJoystickButtons(GLFW_JOYSTICK_1, &NumberOfJoyStickButtons);
LeftThumbStickY = AxesArray[0];
LeftThumbStickX = AxesArray[1];
Triggers = AxesArray[2];
RightThumbStickY = AxesArray[3];
RightThumbStickX = AxesArray[4];
//Camera Controls with XBox 360 controller.
if (RightThumbStickX > 0.2 || RightThumbStickX < -0.2) View = glm::rotate(glm::mat4(), RightThumbStickX *0.06f, glm::vec3(0.0f, 1.0f, 0.0f)) * View;
if (LeftThumbStickX > 0.2 || LeftThumbStickX < -0.2) View = glm::translate(glm::mat4(), glm::vec3(0.0f, 0.0f, -LeftThumbStickX * 0.1f)) * View; //*0.1f to slow it down. Negative to flip the axis. -0.2 for deadzone.
if (RightThumbStickY > 0.2 || RightThumbStickY < -0.2) CameraTilt += 0.03 * RightThumbStickY;
if (LeftThumbStickY > 0.2 || LeftThumbStickY < -0.2) View = glm::translate(glm::mat4(), glm::vec3(-LeftThumbStickY * 0.1f, 0.0f, 0.0f)) * View;
if (Triggers > 0.2 || Triggers < -0.2) View = glm::translate(glm::mat4(), glm::vec3(0.0f, Triggers*0.1f, 0.0f)) * View;
if (Buttons[0] == '\x1') AButton = true;
if (Buttons[1] == '\x1') BButton = true;
if (Buttons[6] == '\x1') OperatingSystem.ShutDown();
}
//Camera Controls with keyboard.
if (OperatingSystem.Keyboard.KeyPressed == GLFW_KEY_W && OperatingSystem.Keyboard.ActionPressed != GLFW_RELEASE)
View = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, 0.05f)) * View;
if (OperatingSystem.Keyboard.KeyPressed == GLFW_KEY_S && OperatingSystem.Keyboard.ActionPressed != GLFW_RELEASE)
View = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, -0.05f)) * View;
if (OperatingSystem.Keyboard.KeyPressed == GLFW_KEY_E && OperatingSystem.Keyboard.ActionPressed != GLFW_RELEASE)
CameraTilt += 0.1;
if (OperatingSystem.Keyboard.KeyPressed == GLFW_KEY_Q && OperatingSystem.Keyboard.ActionPressed != GLFW_RELEASE)
CameraTilt -= 0.1;
if (OperatingSystem.Keyboard.ModePressed == GLFW_MOD_SHIFT)
{
//Keys while Shift keys are also held down.
if (OperatingSystem.Keyboard.KeyPressed == GLFW_KEY_A && OperatingSystem.Keyboard.ActionPressed != GLFW_RELEASE)
View = glm::translate(glm::mat4(), glm::vec3(0.1f, 0.0f, 0.0f)) * View;
if (OperatingSystem.Keyboard.KeyPressed == GLFW_KEY_D && OperatingSystem.Keyboard.ActionPressed != GLFW_RELEASE)
View = glm::translate(glm::mat4(), glm::vec3(-0.1f, 0.0f, 0.0f)) * View;
}
else
{
//Keys when shift keys are not being held down.
if (OperatingSystem.Keyboard.KeyPressed == GLFW_KEY_D && OperatingSystem.Keyboard.ActionPressed != GLFW_RELEASE)
View = glm::rotate(glm::mat4(1.0f), 0.05f, glm::vec3(0.0f, 1.0f, 0.0f)) * View;
if (OperatingSystem.Keyboard.KeyPressed == GLFW_KEY_A && OperatingSystem.Keyboard.ActionPressed != GLFW_RELEASE)
View = glm::rotate(glm::mat4(1.0f), -0.05f, glm::vec3(0.0f, 1.0f, 0.0f)) * View;
}
//Yellow cube controls.
if (OperatingSystem.Keyboard.KeyPressed == GLFW_KEY_I && OperatingSystem.Keyboard.ActionPressed != GLFW_RELEASE)
Cube.Transform(glm::translate(glm::mat4(), glm::vec3(0.0f, 0.0f, 0.05f)));
if (OperatingSystem.Keyboard.KeyPressed == GLFW_KEY_K && OperatingSystem.Keyboard.ActionPressed != GLFW_RELEASE)
Cube.Transform(glm::translate(glm::mat4(), glm::vec3(0.0f, 0.0f, -0.05f)));
if (OperatingSystem.Keyboard.KeyPressed == GLFW_KEY_L && OperatingSystem.Keyboard.ActionPressed != GLFW_RELEASE)
Cube.Transform(glm::rotate(glm::mat4(), glm::radians<float>(-1), glm::vec3(0.0f, 1.0f, 0.0f)));
if (OperatingSystem.Keyboard.KeyPressed == GLFW_KEY_J && OperatingSystem.Keyboard.ActionPressed != GLFW_RELEASE)
Cube.Transform(glm::rotate(glm::mat4(), glm::radians<float>(1), glm::vec3(0.0f, 1.0f, 0.0f)));
if (CameraTilt > MaxTiltAngle) CameraTilt = MaxTiltAngle;
if (CameraTilt < -MaxTiltAngle) CameraTilt = -MaxTiltAngle;
if (OperatingSystem.Keyboard.KeyPressed == GLFW_KEY_Y && OperatingSystem.Keyboard.ActionPressed != GLFW_RELEASE)
Cube2Pivot = glm::rotate(glm::mat4(), glm::radians<float>(1), glm::vec3(0.0f, 0.0f, 1.0f)) * Cube2Pivot;
if (OperatingSystem.Keyboard.KeyPressed == GLFW_KEY_H && OperatingSystem.Keyboard.ActionPressed != GLFW_RELEASE)
Cube2Pivot = glm::rotate(glm::mat4(), glm::radians<float>(-1), glm::vec3(0.0f, 0.0f, 1.0f)) * Cube2Pivot;
Triangle.Transform(glm::rotate(glm::mat4(), glm::radians<float>(1), glm::vec3(0.0f, 1.0f, 0.0f)));
Cube2Pivot = Cube2Pivot * glm::rotate(glm::mat4(), glm::radians<float>(1), glm::vec3(0.0f, 1.0f, 0.0f));
Cube2.WorldMatrix = Cube.WorldMatrix * Cube2Pivot * Cube2World;
}
//=====================================================================================================================
//=====================================================================================================================
// Game::Draw()
//
// Purpose:
// To do draw a single frame on the computer screen.
//
// Input:
// float TimeDelta - Amount of time that has passed since the last frame occured in milliseconds.
//
// Output:
// None.
//
// Notes:
// Since all the drawing code is tucked away neatly elsewhere, this method ends up being very short and sweet. There's
// basically nothing to it. The first two lines clear the backbuffer in corn flower blue which is a constant. And then
// it clears the depth stencil. You can clear the screen in any color you like.
//
// Then each game object's Draw() method needs to be called to tell it to draw it self to the back buffer. The parameters
// for the shader have to be sent here.
//
// Finaly, the swapchain is told to make the backbuffer the frontbuffer and draw it to the screen by presenting it and
// the image appears on the computer monitor.
//
//=====================================================================================================================
void Game::Draw()
{
glm::mat4 TiltedView = glm::rotate(glm::mat4(), CameraTilt, glm::vec3(1.0, 0.0, 0.0)) * View;
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
Triangle.Draw(TiltedView, Projection, &Shader, DiffuseLightDirection, AmbientLightColor, DiffuseLightColor);
Cube.Draw(TiltedView, Projection, &Shader, DiffuseLightDirection, AmbientLightColor, DiffuseLightColor);
Cube2.Draw(TiltedView, Projection, &Shader, DiffuseLightDirection, AmbientLightColor, DiffuseLightColor);
Ground.Draw(TiltedView, Projection, &Shader, DiffuseLightDirection, AmbientLightColor, DiffuseLightColor);
}
//=====================================================================================================================
The comments were from a previous version that doesn’t exactly match the current code.