From f86fcad89f4ef1d98b7f584d4edf6c81543d7b2b Mon Sep 17 00:00:00 2001
From: Robert <robert.trololo@gmail.com>
Date: Fri, 22 Jan 2021 01:13:01 +0100
Subject: [PATCH] Added transformables

---
 CMakeLists.txt                           |   4 +-
 examples/debug/main.cpp                  |  22 +-
 examples/debug/shaders/vertexShader.vert |   6 +-
 include/transformable.hpp                | 248 +++++++++++++++++++++
 src/transformable.cpp                    | 267 +++++++++++++++++++++++
 5 files changed, 544 insertions(+), 3 deletions(-)
 create mode 100644 include/transformable.hpp
 create mode 100644 src/transformable.cpp

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7f9b8e5..a80f56a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2,6 +2,8 @@ cmake_minimum_required(VERSION 3.10)
 
 project(OpenGLUtility)
 
+# set(CMAKE_CXX_STANDARD 17)
+
 set(build_examples ON CACHE BOOL "Build examples")
 set(build_documentation ON CACHE BOOL "Generate documentation")
 
@@ -26,7 +28,7 @@ add_library(openglu SHARED
 	${include_files}
 	${source_files}
 	"cpp.hint"
-)
+ "src/transformable.cpp")
 
 target_compile_definitions(openglu PRIVATE OGLU_BUILD_DLL)
 
diff --git a/examples/debug/main.cpp b/examples/debug/main.cpp
index 411924a..94c3625 100644
--- a/examples/debug/main.cpp
+++ b/examples/debug/main.cpp
@@ -1,8 +1,12 @@
 #include <iostream>
 
 #include "openglu.hpp"
+#include "transformable.hpp"
 #include <glad/glad.h>
 #include <GLFW/glfw3.h>
+#include <glm/glm.hpp>
+#include <glm/gtc/matrix_transform.hpp>
+#include <glm/gtc/type_ptr.hpp>
 
 void framebuffer_size_callback(GLFWwindow* window, int width, int height)
 {
@@ -79,16 +83,32 @@ int main(int argc, char** argv)
 	oglu::Texture crate = oglu::MakeTexture("assets/crate.jpg");
 	oglu::Texture opengl = oglu::MakeTexture("assets/opengl.png");
 
+	oglu::Transformable model;
+	//model.SetRotation(-55.0f, 0.0f, 0.0f);
+
+	glm::mat4 view = glm::mat4(1.0f);
+	view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f));
+
+	glm::mat4 projection;
+	projection = glm::perspective(glm::radians(45.f), 1.0f, 0.1f, 100.0f);
+
+	glm::mat4 test = glm::make_mat4(model.GetMatrix());
+
 	// Window loop
 	while (!glfwWindowShouldClose(window))
 	{
 		processInput(window);
 
-		oglu::ClearScreen(GL_COLOR_BUFFER_BIT, oglu::Color(0.29f, 0.13f, 0.23f));
+		oglu::ClearScreen(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, oglu::Color(0.29f, 0.13f, 0.23f));
+
+		model.Rotate(6.0f, 0.0f, 0.0f);
 
 		shader->Use();
 		shader->SetUniform("texture1", crate, 0);
 		shader->SetUniform("texture2", opengl, 1);
+		shader->SetUniformMatrix4fv("model", 1, GL_FALSE, model.GetMatrix());
+		shader->SetUniformMatrix4fv("view", 1, GL_FALSE, glm::value_ptr(view));
+		shader->SetUniformMatrix4fv("projection", 1, GL_FALSE, glm::value_ptr(projection));
 
 		square->BindAndDraw();
 
diff --git a/examples/debug/shaders/vertexShader.vert b/examples/debug/shaders/vertexShader.vert
index 208257b..8b2b3d3 100644
--- a/examples/debug/shaders/vertexShader.vert
+++ b/examples/debug/shaders/vertexShader.vert
@@ -6,9 +6,13 @@ layout (location = 2) in vec2 aUV;
 out vec3 oCol;
 out vec2 oUV;
 
+uniform mat4 model;
+uniform mat4 view;
+uniform mat4 projection;
+
 void main()
 {
 	oCol = aCol;
 	oUV = aUV;
-	gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
+	gl_Position = projection * view * model * vec4(aPos, 1.0);
 }
\ No newline at end of file
diff --git a/include/transformable.hpp b/include/transformable.hpp
new file mode 100644
index 0000000..c02f4ac
--- /dev/null
+++ b/include/transformable.hpp
@@ -0,0 +1,248 @@
+/*****************************************************************//**
+ * \file   transformable.hpp
+ * \brief  Contains utility for anything that has a geometry
+ * 
+ * \author Lauchmelder
+ * \date   January 2021
+ *********************************************************************/
+
+#ifndef TRANSFORMABLE_HPP
+#define TRANSFORMABLE_HPP
+
+#include <core.hpp>
+
+namespace oglu
+{
+	/**
+	 * @brief Defines position, rotation and scale.
+	 * 
+	 * This class wraps a 4x4 transformation matrix and provides useful
+	 * helper functions to perform translations, rotations and scalings.
+	 */
+	class OGLU_API Transformable
+	{
+	public:
+		/**
+		 * @brief Create new identity transformation.
+		 * 
+		 * An identity transformation is a matrix with all 1's on the diagonal.
+		 * When applied to a vector it leaves it unchanged.
+		 */
+		Transformable();
+
+		/**
+		 * @brief Copies another transformation.
+		 * 
+		 * The transformation matrix of @p other is copied.
+		 */
+		Transformable(const Transformable& other);
+		~Transformable();
+
+		/**
+		 * @brief Sets the position.
+		 * 
+		 * This sets an absolute position, means that it resets any previous
+		 * translations.
+		 * 
+		 * @param[in] x New x position
+		 * @param[in] y New y position
+		 * @param[in] z New z position
+		 */
+		void SetPosition(float x, float y, float z);
+
+		/**
+		 * @brief Sets the position.
+		 *
+		 * This sets an absolute position, means that it resets any previous
+		 * translations.
+		 *
+		 * @param[in] position An array of floats containing three scalars
+		 */
+		void SetPosition(const float* translation);
+
+		/**
+		 * @brief Sets the rotation.
+		 *
+		 * This sets an absolute rotation, means that it resets any previous
+		 * rotations.
+		 *
+		 * @param[in] rotX New rotation around x axis
+		 * @param[in] rotY New rotation around y axis
+		 * @param[in] rotZ New rotation around z axis
+		 */
+		void SetRotation(float rotX, float rotY, float rotZ);
+
+		/**
+		 * @brief Sets the rotation.
+		 *
+		 * This sets an absolute rotation, means that it resets any previous
+		 * rotations.
+		 *
+		 * @param[in] rotation An array of floats containing three scalars
+		 */
+		void SetRotation(const float* rotation);
+
+		/**
+		 * @brief Sets the rotation.
+		 *
+		 * This sets an absolute rotation, means that it resets any previous
+		 * rotations.
+		 *
+		 * @param[in] angle The angle to rotate by
+		 * @param[in] xAxis The x component of the rotation axis
+		 * @param[in] yAxis The y component of the rotation axis
+		 * @param[in] zAxis The z component of the rotation axis
+		 */
+		void SetRotation(float angle, float xAxis, float yAxis, float zAxis);
+
+		/**
+		 * @brief Sets the rotation.
+		 *
+		 * This sets an absolute rotation, means that it resets any previous
+		 * rotations.
+		 *
+		 * @param[in] angle The angle to rotate by
+		 * @param[in] axis An array of floats containing the three rotation axis components
+		 */
+		void SetRotation(float angle, const float* axis);
+
+		/**
+		 * @brief Sets the scaling.
+		 *
+		 * This sets an absolute scaling, means that it resets any previous
+		 * scaling.
+		 *
+		 * @param[in] scaleX The scaling in x direction
+		 * @param[in] scaleY The scaling in y direction
+		 * @param[in] scaleZ The scaling in z direction
+		 */
+		void SetScale(float scaleX, float scaleY, float scaleZ);
+
+		/**
+		 * @brief Sets the scaling.
+		 *
+		 * This sets an absolute scaling, means that it resets any previous
+		 * scaling.
+		 *
+		 * @param[in] scale An array of floats containing three scalars
+		 */
+		void SetScale(const float* scale);
+
+		/**
+		 * @brief Performs a translation.
+		 *
+		 * This function applies a translation to the object, it operates
+		 * operates on the current position.
+		 *
+		 * @param[in] x Offset along the x axis
+		 * @param[in] y Offset along the y axis
+		 * @param[in] z Offset along the z axis
+		 */
+		void Move(float x, float y, float z);
+
+		/**
+		 * @brief Performs a translation.
+		 *
+		 * This function applies a translation to the object, it operates
+		 * operates on the current position.
+		 *
+		 * @param[in] position An array of floats containing the offset values
+		 */
+		void Move(const float* translation);
+
+		/**
+		 * @brief Performs a rotation.
+		 *
+		 * This function applies a rotation to the object, it operates
+		 * operates on the current rotation.
+		 *
+		 * @param[in] rotX Rotation around the x axis
+		 * @param[in] rotY Rotation around the y axis
+		 * @param[in] rotZ Rotation around the z axis
+		 */
+		void Rotate(float rotX, float rotY, float rotZ);
+
+		/**
+		 * @brief Performs a rotation.
+		 *
+		 * This function applies a rotation to the object, it operates
+		 * operates on the current rotation.
+		 *
+		 * @param[in] rotation An array of floats containing the rotation values
+		 */
+		void Rotate(const float* rotation);
+
+		/**
+		 * @brief Performs a rotation.
+		 *
+		 * This function applies a rotation to the object, it operates
+		 * operates on the current rotation.
+		 *
+		 * @param[in] angle The angle to rotate by
+		 * @param[in] xAxis x component of the rotation axis
+		 * @param[in] yAxis y component of the rotation axis
+		 * @param[in] zAxis z component of the rotation axis
+		 */
+		void Rotate(float angle, float xAxis, float yAxis, float zAxis);
+
+		/**
+		 * @brief Performs a rotation.
+		 *
+		 * This function applies a rotation to the object, it operates
+		 * operates on the current rotation.
+		 *
+		 * @param[in] angle The angle to rotate by
+		 * @param[in] An array of floats containing the components of the rotation axis
+		 */
+		void Rotate(float angle, const float* axis);
+
+		/**
+		 * @brief Performs scaling.
+		 *
+		 * This function applies scaling to the object, it operates
+		 * operates on the current scaling.
+		 *
+		 * @param[in] scaleX Scaling in x direction
+		 * @param[in] scaleX Scaling in y direction
+		 * @param[in] scaleX Scaling in z direction
+		 */
+		void Scale(float scaleX, float scaleY, float scaleZ);
+
+		/**
+		 * @brief Performs scaling.
+		 *
+		 * This function applies scaling to the object, it operates
+		 * operates on the current scaling.
+		 *
+		 * @param[in] scale An array of floats containing three scaling values
+		 */
+		void Scale(const float* scale);
+
+		/**
+		 * @brief Returns a transformation matrix.
+		 *
+		 * Internally, this function multiplies the translation, rotation,
+		 * and scaling matrices into one transformation matrix which is
+		 * then returned. 
+		 * 
+		 * This multiplication is only performed when a change occured to one
+		 * of those matrices (so when there was a translation, rotation or scaling).
+		 * So it is okay to call this function multiple times without huge performance
+		 * loss, as long as no transformations occur inbetween.
+		 *
+		 * @return An array of 16 floats representing the transformation matrix
+		 */
+		const float* GetMatrix();
+
+	private:
+		// TODO: Separate translation, rotation and scaling matrices.
+		// Combine them only when the user wants the transformation matrix
+		float* translation;	///< Translation matrix
+		float* rotation;	///< Rotation matrix
+		float* scaling;		///< Scaling matrix
+
+		bool calculateMatrix;	///< Wether GetMatrix() needs to re-calculate the transformation matrix
+	};
+}
+
+#endif
\ No newline at end of file
diff --git a/src/transformable.cpp b/src/transformable.cpp
new file mode 100644
index 0000000..6025583
--- /dev/null
+++ b/src/transformable.cpp
@@ -0,0 +1,267 @@
+#include "transformable.hpp"
+
+#include <algorithm>
+
+#include <glm/glm.hpp>
+#include <glm/gtc/matrix_transform.hpp>
+#include <glm/gtc/type_ptr.hpp>
+
+namespace oglu
+{
+	oglu::Transformable::Transformable() :
+		translation(new float[16]), rotation(new float[16]), scaling(new float[16]), calculateMatrix(false)
+	{
+		glm::mat4 identity(1.0f);
+		memcpy(
+			translation,
+			glm::value_ptr(identity),
+			16 * sizeof(float)
+		);
+		memcpy(
+			rotation,
+			glm::value_ptr(identity),
+			16 * sizeof(float)
+		);
+		memcpy(
+			scaling,
+			glm::value_ptr(identity),
+			16 * sizeof(float)
+		);
+	}
+
+	Transformable::Transformable(const Transformable& other) :
+		translation(new float[16]), rotation(new float[16]), scaling(new float[16]), calculateMatrix(true)
+	{
+		memcpy(
+			this->translation,
+			other.translation,
+			16 * sizeof(float)
+		);
+
+		memcpy(
+			this->rotation,
+			other.rotation,
+			16 * sizeof(float)
+		);
+
+		memcpy(
+			this->scaling,
+			other.scaling,
+			16 * sizeof(float)
+		);
+	}
+
+	Transformable::~Transformable()
+	{
+		delete[] scaling;
+		delete[] rotation;
+		delete[] translation;
+	}
+
+	void Transformable::SetPosition(float x, float y, float z)
+	{
+		memcpy(
+			this->translation,
+			glm::value_ptr(glm::translate(glm::mat4(1.0f), glm::vec3(x, y, z))),
+			16 * sizeof(float)
+		);
+		calculateMatrix = true;
+	}
+
+	void Transformable::SetPosition(const float* translation)
+	{
+		memcpy(
+			this->translation,
+			glm::value_ptr(glm::translate(glm::mat4(1.0f), glm::make_vec3(translation))),
+			16 * sizeof(float)
+		);
+		calculateMatrix = true;
+	}
+
+	void Transformable::SetRotation(float rotX, float rotY, float rotZ)
+	{
+		// TODO: Using rotation matrices is stupid. Eventually this could (should) be done with quaternions
+		// For now we'll just risk gimbal locking the model
+		memcpy(
+			this->rotation,
+			glm::value_ptr(
+				glm::rotate(
+					glm::rotate(
+						glm::rotate(
+							glm::mat4(1.0f), glm::radians(rotX), glm::vec3(1.0f, 0.0f, 0.0f)
+						), glm::radians(rotY), glm::vec3(0.0f, 1.0f, 0.0f)
+					), glm::radians(rotZ), glm::vec3(0.0f, 0.0f, 1.0f)
+				)
+			),
+			16 * sizeof(float)
+		);
+		calculateMatrix = true;
+	}
+
+	void Transformable::SetRotation(const float* rotation)
+	{
+		memcpy(
+			this->rotation,
+			glm::value_ptr(
+				glm::rotate(
+					glm::rotate(
+						glm::rotate(
+							glm::mat4(1.0f), glm::radians(rotation[0]), glm::vec3(1.0f, 0.0f, 0.0f)
+						), glm::radians(rotation[1]), glm::vec3(0.0f, 1.0f, 0.0f)
+					), glm::radians(rotation[2]), glm::vec3(0.0f, 0.0f, 1.0f)
+				)
+			),
+			16 * sizeof(float)
+		);
+		calculateMatrix = true;
+	}
+
+	void Transformable::SetRotation(float angle, float xAxis, float yAxis, float zAxis)
+	{
+		memcpy(
+			this->rotation,
+			glm::value_ptr(glm::rotate(glm::mat4(1.0f), glm::radians(angle), glm::vec3(xAxis, yAxis, zAxis))),
+			16 * sizeof(float)
+		);
+		calculateMatrix = true;
+	}
+
+	void Transformable::SetRotation(float angle, const float* axis)
+	{
+		memcpy(
+			this->rotation,
+			glm::value_ptr(glm::rotate(glm::mat4(1.0f), glm::radians(angle), glm::make_vec3(axis))),
+			16 * sizeof(float)
+		);
+		calculateMatrix = true;
+	}
+
+	void Transformable::SetScale(float scaleX, float scaleY, float scaleZ)
+	{
+		memcpy(
+			this->scaling,
+			glm::value_ptr(glm::scale(glm::mat4(1.0f), glm::vec3(scaleX, scaleY, scaleZ))),
+			16 * sizeof(float)
+		);
+		calculateMatrix = true;
+	}
+
+	void Transformable::SetScale(const float* scale)
+	{
+		memcpy(
+			this->scaling,
+			glm::value_ptr(glm::scale(glm::mat4(1.0f), glm::make_vec3(scale))),
+			16 * sizeof(float)
+		);
+		calculateMatrix = true;
+	}
+
+	void Transformable::Move(float x, float y, float z)
+	{
+		memcpy(
+			this->translation,
+			glm::value_ptr(glm::translate(glm::make_mat4(this->translation), glm::vec3(x, y, z))),
+			16 * sizeof(float)
+		);
+		calculateMatrix = true;
+	}
+
+	void Transformable::Move(const float* translation)
+	{
+		memcpy(
+			this->translation,
+			glm::value_ptr(glm::translate(glm::make_mat4(this->translation), glm::make_vec3(translation))),
+			16 * sizeof(float)
+		);
+		calculateMatrix = true;
+	}
+
+	void Transformable::Rotate(float rotX, float rotY, float rotZ)
+	{
+		memcpy(
+			this->rotation,
+			glm::value_ptr(
+				glm::rotate(
+					glm::rotate(
+						glm::rotate(
+							glm::make_mat4(this->rotation), glm::radians(rotX), glm::vec3(1.0f, 0.0f, 0.0f)
+						), glm::radians(rotY), glm::vec3(0.0f, 1.0f, 0.0f)
+					), glm::radians(rotZ), glm::vec3(0.0f, 0.0f, 1.0f)
+				)
+			),
+			16 * sizeof(float)
+		);
+		calculateMatrix = true;
+	}
+
+	void Transformable::Rotate(const float* rotation)
+	{
+		memcpy(
+			this->rotation,
+			glm::value_ptr(
+				glm::rotate(
+					glm::rotate(
+						glm::rotate(
+							glm::make_mat4(this->rotation), glm::radians(rotation[0]), glm::vec3(1.0f, 0.0f, 0.0f)
+						), glm::radians(rotation[1]), glm::vec3(0.0f, 1.0f, 0.0f)
+					), glm::radians(rotation[2]), glm::vec3(0.0f, 0.0f, 1.0f)
+				)
+			),
+			16 * sizeof(float)
+		);
+		calculateMatrix = true;
+	}
+
+	void Transformable::Rotate(float angle, float xAxis, float yAxis, float zAxis)
+	{
+		memcpy(
+			this->rotation,
+			glm::value_ptr(glm::rotate(glm::make_mat4(this->rotation), glm::radians(angle), glm::vec3(xAxis, yAxis, zAxis))),
+			16 * sizeof(float)
+		);
+		calculateMatrix = true;
+	}
+
+	void Transformable::Rotate(float angle, const float* axis)
+	{
+		memcpy(
+			this->rotation,
+			glm::value_ptr(glm::rotate(glm::make_mat4(this->rotation), glm::radians(angle), glm::make_vec3(axis))),
+			16 * sizeof(float)
+		);
+		calculateMatrix = true;
+	}
+
+	void Transformable::Scale(float scaleX, float scaleY, float scaleZ)
+	{
+		memcpy(
+			this->scaling,
+			glm::value_ptr(glm::scale(glm::make_mat4(this->scaling), glm::vec3(scaleX, scaleY, scaleZ))),
+			16 * sizeof(float)
+		);
+		calculateMatrix = true;
+	}
+
+	void Transformable::Scale(const float* scale)
+	{
+		memcpy(
+			this->scaling,
+			glm::value_ptr(glm::translate(glm::make_mat4(this->scaling), glm::make_vec3(scale))),
+			16 * sizeof(float)
+		);
+		calculateMatrix = true;
+	}
+
+	const float* Transformable::GetMatrix()
+	{
+		static glm::mat4 transformation(1.0f);
+
+		if (calculateMatrix)
+		{
+			transformation = glm::make_mat4(translation) * glm::make_mat4(rotation) * glm::make_mat4(scaling);
+			calculateMatrix = false;
+		}
+
+		return glm::value_ptr(transformation);
+	}
+}