From 3887733e678fa38b5c91f997914a092aee474c86 Mon Sep 17 00:00:00 2001 From: fred Date: Sun, 19 Jan 2025 14:17:36 +0100 Subject: [PATCH] Feature Complete Private --- .gitignore | 8 +- RubiksCube/Camera.h | 112 ++++++++++ RubiksCube/Cube.cpp | 59 ++--- RubiksCube/Cube.h | 23 +- RubiksCube/Debug.cpp | 68 ++++++ RubiksCube/Debug.h | 146 +++++++++++++ RubiksCube/Entity.cpp | 81 +++++++ RubiksCube/Entity.h | 40 ++++ RubiksCube/InputSystem.cpp | 4 +- RubiksCube/InputSystem.h | 2 +- RubiksCube/Mesh.cpp | 1 + RubiksCube/Mesh.h | 97 +++++++++ RubiksCube/MeshData.h | 1 - RubiksCube/PartialCube.cpp | 0 RubiksCube/PartialCube.h | 0 RubiksCube/PartitionedCube.cpp | 133 +++++++++--- RubiksCube/PartitionedCube.h | 155 +++++++++---- RubiksCube/Plane.h | 2 +- RubiksCube/RubiksCube.cpp | 2 + RubiksCube/RubiksCube.vcxproj | 52 ++++- RubiksCube/RubiksCube.vcxproj.filters | 60 ++++- RubiksCube/SceneInterface.cpp | 301 ++++++++++++++------------ RubiksCube/SceneInterface.h | 82 +++++-- RubiksCube/Settings.h | 12 + RubiksCube/Shader.cpp | 1 - RubiksCube/Shader.h | 46 +++- RubiksCube/Texture.h | 97 +++++++++ RubiksCube/VertexData.h | 1 - RubiksCube/cube.frag | 38 +++- RubiksCube/cube.vert | 27 ++- RubiksCube/diffuse.png | Bin 0 -> 35815 bytes RubiksCube/diffuse_test.png | Bin 0 -> 31917 bytes RubiksCube/line.frag | 19 ++ RubiksCube/line.vert | 69 ++++++ RubiksCube/normal.png | Bin 0 -> 12736 bytes RubiksCube/roughness.png | Bin 0 -> 22675 bytes readme.txt | 39 ++++ 37 files changed, 1481 insertions(+), 297 deletions(-) create mode 100644 RubiksCube/Camera.h create mode 100644 RubiksCube/Debug.cpp create mode 100644 RubiksCube/Debug.h create mode 100644 RubiksCube/Entity.cpp create mode 100644 RubiksCube/Entity.h create mode 100644 RubiksCube/Mesh.cpp create mode 100644 RubiksCube/Mesh.h delete mode 100644 RubiksCube/MeshData.h delete mode 100644 RubiksCube/PartialCube.cpp delete mode 100644 RubiksCube/PartialCube.h create mode 100644 RubiksCube/Settings.h delete mode 100644 RubiksCube/Shader.cpp create mode 100644 RubiksCube/Texture.h delete mode 100644 RubiksCube/VertexData.h create mode 100644 RubiksCube/diffuse.png create mode 100644 RubiksCube/diffuse_test.png create mode 100644 RubiksCube/line.frag create mode 100644 RubiksCube/line.vert create mode 100644 RubiksCube/normal.png create mode 100644 RubiksCube/roughness.png create mode 100644 readme.txt diff --git a/.gitignore b/.gitignore index 6d58447..a88a25e 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,7 @@ -ExternalResources \ No newline at end of file +ExternalResources + +Abgabe + +963830_rohmenf.zip + +bugs.md \ No newline at end of file diff --git a/RubiksCube/Camera.h b/RubiksCube/Camera.h new file mode 100644 index 0000000..3dbe723 --- /dev/null +++ b/RubiksCube/Camera.h @@ -0,0 +1,112 @@ +#pragma once + +#include + +#include + + + +#include "Entity.h" + +#include + +class Camera : public Entity { +private: + GLFWwindow* _window; + InputSystem* _inputSystem; + + glm::mat4 _view; + glm::mat4 _projection; + + glm::quat _orientation; + + glm::vec2 _dragStart; + bool _wasMouseClicked; + + float _scrollPositionPrevious; + float _scrollPositionDelta; + + static inline float cameraDistance = 8.15f; + + static inline Camera* _instance; + +public: + const glm::mat4& View() const { return _view * glm::mat4_cast(_orientation); } + const glm::mat4& Projection() const { return _projection; } + +public: + Camera(GLFWwindow* window, InputSystem* inputSystem) : Entity(nullptr) { + _instance = this; + _window = window; + _inputSystem = inputSystem; + + _wasMouseClicked = false; + + _view = glm::lookAt(glm::vec3(0.0f, 0.0f, cameraDistance), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f)); + _projection = glm::mat4(1.0f); + } + + void Update(double deltaTime) override { + int screenWidth; + int screenHeight; + + glfwGetFramebufferSize(_window, &screenWidth, &screenHeight); + + float aspec = (float)screenWidth / (float)screenHeight; + + _projection = glm::perspective(glm::radians(45.0f), aspec, 0.1f, 100.0f); + + if (_inputSystem->WasKeyPressed(GLFW_KEY_SPACE)) + _orientation = glm::quat(1.0f, glm::vec3(0.0f, 0.0f, 0.0f)); + + glm::fvec2 velocity(0.0f, 0.0f); + if (_inputSystem->IsKeyPressed(GLFW_KEY_UP)) + velocity.x = glm::radians(90.0f); + if (_inputSystem->IsKeyPressed(GLFW_KEY_DOWN)) + velocity.x = glm::radians(-90.0f); + + if (_inputSystem->IsKeyPressed(GLFW_KEY_RIGHT)) + velocity.y = glm::radians(90.0f); + if (_inputSystem->IsKeyPressed(GLFW_KEY_LEFT)) + velocity.y = glm::radians(-90.0f); + + if (!_wasMouseClicked && _inputSystem->IsRightMouseDown()) { + _inputSystem->GetMousePos(_dragStart); + } + + if (_wasMouseClicked && _inputSystem->IsRightMouseDown()) { + glm::vec2 currentMousePositions; + _inputSystem->GetMousePos(currentMousePositions); + glm::vec2 dragDiff = currentMousePositions - _dragStart; + _dragStart = currentMousePositions; + + _orientation = glm::quat(1.0f, glm::vec3(dragDiff.y, dragDiff.x, 0.0f) * 0.008f) * _orientation; + _orientation = glm::normalize(_orientation); + } + + _wasMouseClicked = _inputSystem->IsRightMouseDown(); + + glm::quat velocityQuaternion = glm::quat(0.0f, glm::vec3(velocity.x, velocity.y, 0.0f)); + + _orientation += 0.5f * (float)deltaTime * velocityQuaternion * _orientation; + _orientation = glm::normalize(_orientation); + + _view = glm::translate(_view, glm::vec3(0.0f, 0.0f, _scrollPositionDelta)); + _scrollPositionDelta = 0.0f; + + // since callbacks need to be static use a singleton + glfwSetScrollCallback(_window, Camera::scrollCallbackGlobal); + } + + static void scrollCallbackGlobal(GLFWwindow* window, double xOffset, double yOffset) { + _instance->scrollCallback(window, xOffset, yOffset); + } + + void scrollCallback(GLFWwindow* window, double xOffset, double yOffset) { + _scrollPositionDelta = yOffset; + } + + void SetAspectRatio(float aspec) { + _projection = glm::perspective(glm::radians(45.0f), aspec, 0.1f, 100.0f); + } +}; diff --git a/RubiksCube/Cube.cpp b/RubiksCube/Cube.cpp index d5d3e33..12b498e 100644 --- a/RubiksCube/Cube.cpp +++ b/RubiksCube/Cube.cpp @@ -1,9 +1,7 @@ #include "Cube.h" -Cube::Cube() +Cube::Cube() : Entity(nullptr) { - this->_transform = glm::mat4(1.0f); - for(int x=-1;x<=1;x++) { for(int y=-1;y<=1;y++) { for(int z=-1;z<=1;z++) { @@ -35,7 +33,7 @@ Cube::Cube() sideColors[Side::Forward] = SideColor[Side::Forward]; } - PartitionedCube* cubeAtIndex = new PartitionedCube(glm::vec3(x, y, z), sideColors); + PartitionedCube* cubeAtIndex = new PartitionedCube(this, glm::vec3(x, y, z), sideColors); this->_children[x + 1][y + 1][z + 1] = cubeAtIndex; } @@ -53,20 +51,6 @@ Cube::~Cube() { } } -void Cube::Update(double deltaTime) { - for(int x=0;x<3;x++) { - for(int y=0;y<3;y++) { - for(int z=0;z<3;z++) { - _children[x][y][z]->Update(deltaTime); - } - } - } -} - -void Cube::Transform(glm::mat4 transform) { - this->_transform *= transform; -} - void Cube::_FindAxisChildren(const glm::ivec3& axis, int index, std::vector& result) const { glm::ivec3 orientationBuffer[3] = { axis, @@ -83,12 +67,20 @@ void Cube::_FindAxisChildren(const glm::ivec3& axis, int index, std::vector_children, sizeof(this->_children)); std::vector result; + glm::mat3 transform = glm::rotate(glm::mat4(1.0f), glm::radians(90.0f) * turns, glm::vec3(axis)); + + for(int x=0;x<3;x++) { + for(int y=0;y<3;y++) { + transform[x][y] = lroundf(transform[x][y]); + } + } + _FindAxisChildren(axis, index, result); for(auto& position : result) { @@ -98,20 +90,22 @@ void Cube::_TransformData(const glm::ivec3& axis, int index, const glm::mat3& tr newPosition += glm::ivec3(1); _children[newPosition.x][newPosition.y][newPosition.z] = previousCubeList[position.x][position.y][position.z]; + + _children[newPosition.x][newPosition.y][newPosition.z]->TransformData(axis, turns); } } void Cube::Transform(const glm::ivec3& axis, int index, const glm::mat3& transform) { - _TransformData(axis, index, transform); + // _TransformData(axis, index, transform); std::vector result; - _FindAxisChildren(transform * axis, index, result); + _FindAxisChildren(axis, index, result); for(auto& position : result) { PartitionedCube* cube = _children[position.x + 1][position.y + 1][position.z + 1]; - cube->Transform(glm::mat4(transform)); + cube->TransformLocal(glm::mat4(transform)); } } @@ -123,16 +117,29 @@ void Cube::TransformTemp(const glm::ivec3& axis, int index, const glm::mat3& tra for(auto& position : result) { PartitionedCube* cube = _children[position.x + 1][position.y + 1][position.z + 1]; - cube->Transform(glm::mat4(transform)); + cube->TransformTemp(glm::mat4(transform)); } } -void Cube::TransformAnimation(const glm::ivec3& axis, int index, const glm::mat3& transform, float duration) { - _TransformData(axis, index, transform); +void Cube::UndoTransformTemp() +{ + for(int x=0;x<3;x++) { + for(int y=0;y<3;y++) { + for(int z=0;z<3;z++) { + _children[x][y][z]->UndoTransformTemp(); + } + } + } +} + +void Cube::TransformAnimation(const glm::ivec3& axis, int index, float angle, float duration) { + // _TransformData(axis, index, transform); std::vector result; - _FindAxisChildren(transform * axis, index, result); + _FindAxisChildren(axis, index, result); + + glm::quat transform = glm::angleAxis(angle, glm::vec3(axis)); for(auto& position : result) { PartitionedCube* cube = _children[position.x + 1][position.y + 1][position.z + 1]; diff --git a/RubiksCube/Cube.h b/RubiksCube/Cube.h index 0774247..4c615fd 100644 --- a/RubiksCube/Cube.h +++ b/RubiksCube/Cube.h @@ -1,36 +1,33 @@ #pragma once -#include +#include + +#include + +#include "Entity.h" #include "PartitionedCube.h" // Colors according to https://ruwix.com/the-rubiks-cube/japanese-western-color-schemes/ -class Cube +class Cube : public Entity { private: - glm::mat4 _transform; - PartitionedCube* _children[3][3][3]; - void _TransformData(const glm::ivec3& axis, int index, const glm::mat3& transform); - void _FindAxisChildren(const glm::ivec3& axis, int index, std::vector& result) const; public: Cube(); - ~Cube(); - - void Update(double deltaTime); + virtual ~Cube(); const int ChildPartitionsCount = 27; PartitionedCube** Children() { return &(_children[0][0][0]); } - const glm::mat4& Transform() const { return _transform; } - - void TransformAnimation(const glm::ivec3& axis, int index, const glm::mat3& transform, float duration); + void TransformData(const glm::ivec3& axis, int index, int turns); + void TransformAnimation(const glm::ivec3& axis, int index, float angle, float duration); void Transform(const glm::ivec3& axis, int index, const glm::mat3& transform); void TransformTemp(const glm::ivec3& axis, int index, const glm::mat3& transform); - void Transform(glm::mat4 transform); + void UndoTransformTemp(); static constexpr Color SideColor[6] = { Color::ORANGE(), diff --git a/RubiksCube/Debug.cpp b/RubiksCube/Debug.cpp new file mode 100644 index 0000000..4005cb1 --- /dev/null +++ b/RubiksCube/Debug.cpp @@ -0,0 +1,68 @@ +#include "Debug.h" + +#include + +#include + +void Debug::Render(DefaultUniform& uniform) +{ + RenderLines(uniform); + + _lines.clear(); +} + +void Debug::RenderLines(DefaultUniform& uniform) const +{ + glUseProgram(_shader); + glBindVertexArray(_vba); + glBindBuffer(GL_ARRAY_BUFFER, _vbo); + + int vertexCount = _lines.size() * 6; + + int viewportWidth, viewportHeight; + glfwGetFramebufferSize(_window, &viewportWidth, &viewportHeight); + + glm::vec2 viewport = glm::vec2(viewportWidth, viewportHeight); + + + + glUniform2fv(_uniformViewportRes, 1, glm::value_ptr(viewport)); + glUniformMatrix4fv(_uniformModel, 1, GL_FALSE, glm::value_ptr(uniform.model)); + glUniformMatrix4fv(_uniformView, 1, GL_FALSE, glm::value_ptr(uniform.view)); + glUniformMatrix4fv(_uniformProjection, 1, GL_FALSE, glm::value_ptr(uniform.projection)); + + + + DebugVertexData* data = new DebugVertexData[vertexCount]; + int index = 0; + for(const auto& line : _lines) { + int vertexIndex = index * 6; + + glm::vec3 quad[4] = { + line.positionFirst, + line.positionFirst, + line.positionLast, + line.positionLast + }; + + glm::vec3 direction = line.positionLast - line.positionFirst; + + data[ vertexIndex + 0 ] = DebugVertexData { quad[0], direction, line.color, line.lineWidth }; + data[ vertexIndex + 1 ] = DebugVertexData { quad[1], direction, line.color, line.lineWidth }; + data[ vertexIndex + 2 ] = DebugVertexData { quad[2], direction, line.color, line.lineWidth }; + data[ vertexIndex + 3 ] = DebugVertexData { quad[1], direction, line.color, line.lineWidth }; + data[ vertexIndex + 4 ] = DebugVertexData { quad[2], direction, line.color, line.lineWidth }; + data[ vertexIndex + 5 ] = DebugVertexData { quad[3], direction, line.color, line.lineWidth }; + + index++; + } + + glBufferData(GL_ARRAY_BUFFER, sizeof(DebugVertexData) * vertexCount, data, GL_STATIC_DRAW); + glDrawArrays(GL_TRIANGLES, 0, vertexCount); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); + glUseProgram(0); + + delete[] data; +} diff --git a/RubiksCube/Debug.h b/RubiksCube/Debug.h new file mode 100644 index 0000000..8d33598 --- /dev/null +++ b/RubiksCube/Debug.h @@ -0,0 +1,146 @@ +#pragma once + +#include "Color.h" + +#include "ShaderUtil.h" + +#include "Mesh.h" + +#include "Entity.h" + +#include + +#include +#include + +#include + +struct GLFWwindow; + +struct DebugLine { + glm::vec3 positionFirst; + glm::vec3 positionLast; + float lineWidth; + Color color; +}; + +struct DebugPoint { + glm::vec3 position; + Color color; +}; + +struct DebugDirection { + glm::vec3 origin; + glm::vec3 direction; + float lineWidth; + Color color; +}; + +struct DebugVertexData { + glm::vec3 v; + glm::vec3 tangent; + Color color; + float lineWidth; +}; + +class Debug : public Entity +{ +private: + std::vector _lines; + std::vector _points; + + GLFWwindow* _window; + + GLuint _vba; + GLuint _vbo; + GLuint _shader; + + GLuint _uniformViewportRes; + GLuint _uniformModel; + GLuint _uniformView; + GLuint _uniformProjection; + +public: + Debug() : Entity(nullptr) { } + Debug(GLFWwindow* window) : Entity(nullptr) { + _window = window; + + glGenBuffers(1, &_vbo); + glGenVertexArrays(1, &_vba); + glBindVertexArray(_vba); + glBindBuffer(GL_ARRAY_BUFFER, _vbo); + + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(DebugVertexData), (void*)offsetof(DebugVertexData, v)); + glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(DebugVertexData), (void*)offsetof(DebugVertexData, tangent)); + glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, sizeof(DebugVertexData), (void*)offsetof(DebugVertexData, color)); + glVertexAttribPointer(3, 1, GL_FLOAT, GL_FALSE, sizeof(DebugVertexData), (void*)offsetof(DebugVertexData, lineWidth)); + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + glEnableVertexAttribArray(2); + glEnableVertexAttribArray(3); + + _shader = ShaderUtil::CreateShaderProgram("line.vert", "line.frag"); + + _uniformViewportRes = glGetUniformLocation(_shader, "uViewportRes"); + _uniformModel = glGetUniformLocation(_shader, "model"); + _uniformView = glGetUniformLocation(_shader, "view"); + _uniformProjection = glGetUniformLocation(_shader, "projection"); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); + + Instance = this; + } + + ~Debug() { + glDeleteVertexArrays(1, &_vba); + glDeleteBuffers(1, &_vbo); + glDeleteProgram(_shader); + } + + void Render(DefaultUniform& uniform) override; + void RenderLines(DefaultUniform& uniform) const; + + static inline Debug* Instance; + + void Line(const glm::vec3& positionFirst, const glm::vec3& positionLast, const Color& color) { + _lines.push_back(DebugLine { positionFirst, positionLast, 1.0f, color }); + } + + void Direction(const glm::vec3& origin, const glm::vec3& direction, const Color& color) { + glm::vec4 capWorldPos = glm::vec4(origin + direction * 1.0f, 1.0f); + + _lines.push_back(DebugLine { origin, capWorldPos, 8.0f, color }); + + glm::vec3 up = glm::vec3(direction[1], direction[2], direction[0]); + + glm::vec3 capOffsets[4] = { + glm::vec3( 1.0f, 1.0f, -1.0f), + glm::vec3(-1.0f, 1.0f, -1.0f), + glm::vec3( 1.0f, -1.0f, -1.0f), + glm::vec3(-1.0f, -1.0f, -1.0f) + }; + + glm::mat4 capLocalTf = glm::lookAt(glm::vec3(capWorldPos), origin, up); + + capLocalTf = glm::inverse(capLocalTf); + + float extends = 0.05f; + for(const glm::vec3& offset : capOffsets) { + Line(capWorldPos, capLocalTf * glm::vec4(extends * offset, 1.0f), color); + } + } + + void Point(const glm::vec3& position, const Color& color) { + for(int axis=0;axis<3;axis++) { + glm::vec3 forward = glm::vec3(0.0f); + forward[axis] = 1.0f; + + glm::vec3 lineOrigin = position - forward; + glm::vec3 lineDestinatio = position + forward; + + Line(lineOrigin, lineDestinatio, color); + } + } +}; + diff --git a/RubiksCube/Entity.cpp b/RubiksCube/Entity.cpp new file mode 100644 index 0000000..931d64a --- /dev/null +++ b/RubiksCube/Entity.cpp @@ -0,0 +1,81 @@ +#include "Entity.h" + +void Entity::_UpdateChildTransform() const { + for(auto& child : _children) { + child->_localToWorld = _localToWorld * child->_transform; + child->_worldToLocal = glm::inverse(child->_transform); + + child->_UpdateChildTransform(); + } +} + +Entity::Entity(Entity* parent) : _localToWorld(1.0f), _worldToLocal(1.0f) +{ + _parent = parent; + _transform = glm::mat4(1.0f); + + if (parent == nullptr) + return; + + parent->AddChild(this); + _localToWorld = _parent->_localToWorld; + _worldToLocal = glm::inverse(_localToWorld); +} + +Entity::~Entity() { + _parent->RemoveChild(this); +} + +void Entity::AddChild(Entity* entity) { + _children.insert(entity); +} + +void Entity::RemoveChild(Entity* entity) { + _children.erase(entity); +} + +void Entity::Transform(const glm::mat4& transform) { + glm::mat4 transformLocalBasis = _worldToLocal * transform; + + _transform = transformLocalBasis * _transform; + + if (_parent == nullptr) { + _localToWorld = _transform; + } + else { + _localToWorld = _parent->LocalToWorld() * _transform; + } + + _worldToLocal = glm::inverse(_localToWorld); + _UpdateChildTransform(); +} + +void Entity::TransformLocal(const glm::mat4& transform) +{ + _transform = transform * _transform; + + if (_parent == nullptr) { + _localToWorld = _transform; + } + else { + _localToWorld = _parent->LocalToWorld() * _transform; + } + + _worldToLocal = glm::inverse(_localToWorld); + _UpdateChildTransform(); +} + +void Entity::SetTransform(const glm::mat4& transform) { + _transform = transform; + + if (_parent != nullptr) { + _localToWorld = transform * _parent->_localToWorld; + _worldToLocal = glm::inverse(transform) * _parent->_worldToLocal; + } + else { + _localToWorld = transform; + _worldToLocal = glm::inverse(transform); + } + + _UpdateChildTransform(); +} diff --git a/RubiksCube/Entity.h b/RubiksCube/Entity.h new file mode 100644 index 0000000..52c6d4d --- /dev/null +++ b/RubiksCube/Entity.h @@ -0,0 +1,40 @@ +#pragma once + +#include + +#include + +#include "Mesh.h" + +class Entity { +private: + glm::mat4 _transform; + + glm::mat4 _localToWorld; + glm::mat4 _worldToLocal; + + Entity* _parent; + std::unordered_set _children; + + void _UpdateChildTransform() const; + +public: + Entity(Entity* parent); + virtual ~Entity(); + + void AddChild(Entity* entity); + void RemoveChild(Entity* entity); + + Entity* Parent() const { return _parent; } + + void Transform(const glm::mat4& transform); + void TransformLocal(const glm::mat4& transform); + void SetTransform(const glm::mat4& transform); + const glm::mat4& LocalToWorld() const { return _localToWorld; } + const glm::mat4& WorldToLocal() const { return _worldToLocal; } + const glm::mat4& LocalObjectTransform() const { return _transform; } + + virtual void Render(DefaultUniform& uniform) { }; + virtual void Update(double deltaTime) { }; + +}; diff --git a/RubiksCube/InputSystem.cpp b/RubiksCube/InputSystem.cpp index 513c1a0..c898f64 100644 --- a/RubiksCube/InputSystem.cpp +++ b/RubiksCube/InputSystem.cpp @@ -4,9 +4,9 @@ #include -#include +#include -#include +#include void InputSystem::ObserveKey(int key) { _keyCodeDictionaryObserver.emplace(key, KeyObserver(_window, key)); diff --git a/RubiksCube/InputSystem.h b/RubiksCube/InputSystem.h index 6f61370..51b498c 100644 --- a/RubiksCube/InputSystem.h +++ b/RubiksCube/InputSystem.h @@ -2,7 +2,7 @@ #include -#include +#include #include "KeyObserver.h" diff --git a/RubiksCube/Mesh.cpp b/RubiksCube/Mesh.cpp new file mode 100644 index 0000000..49582af --- /dev/null +++ b/RubiksCube/Mesh.cpp @@ -0,0 +1 @@ +#include "Mesh.h" diff --git a/RubiksCube/Mesh.h b/RubiksCube/Mesh.h new file mode 100644 index 0000000..6df299d --- /dev/null +++ b/RubiksCube/Mesh.h @@ -0,0 +1,97 @@ +#pragma once + +#include + +#include + +#include +#include + +#include "Shader.h" + +class DefaultUniform { +private: + +public: + glm::mat4 model; + glm::mat4 view; + glm::mat4 projection; +}; + +template +class Mesh +{ +private: + TUniform _uniform; + + GLuint _uniformModel; + GLuint _uniformView; + GLuint _uniformProjection; + +protected: + std::vector _vertexData; + + GLuint _vba; + GLuint _vbo; + Shader* shader; + + bool _isVertexDirty; + +public: + + TUniform& Uniform() { return _uniform; } + +public: + Mesh(Shader* shader) : shader(shader), _isVertexDirty(true) { + shader->Initialize(); + + glCreateVertexArrays(1, &_vba); + glCreateBuffers(1, &_vbo); + + _uniformModel = glGetUniformLocation(shader->Reference(), "model"); + _uniformView = glGetUniformLocation(shader->Reference(), "view"); + _uniformProjection = glGetUniformLocation(shader->Reference(), "projection"); + } + + ~Mesh() { + glDeleteVertexArrays(1, &_vba); + glDeleteBuffers(1, &_vbo); + } + + void AddTriangle(const TVertexData& p0, const TVertexData& p1, const TVertexData& p2) { + _vertexData.push_back(p0); + _vertexData.push_back(p1); + _vertexData.push_back(p2); + + _isVertexDirty = true; + } + + void AddQuad(const TVertexData& p0, const TVertexData& p1, const TVertexData& p2, const TVertexData& p3) { + AddTriangle(p0, p1, p2); + AddTriangle(p1, p2, p3); + } + + virtual void Render(const TUniform& uniform) { + shader->Use(); + + glUniformMatrix4fv(_uniformModel, 1, GL_FALSE, glm::value_ptr(uniform.model)); + glUniformMatrix4fv(_uniformView, 1, GL_FALSE, glm::value_ptr(uniform.view)); + glUniformMatrix4fv(_uniformProjection, 1, GL_FALSE, glm::value_ptr(uniform.projection)); + + glBindVertexArray(_vba); + glBindBuffer(GL_ARRAY_BUFFER, _vbo); + + if (_isVertexDirty) { + glBufferData(GL_ARRAY_BUFFER, sizeof(TVertexData) * _vertexData.size(), _vertexData.data(), GL_STATIC_DRAW); + + _isVertexDirty = false; + } + + glDrawArrays(GL_TRIANGLES, 0, _vertexData.size()); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); + + shader->Disable(); + } +}; diff --git a/RubiksCube/MeshData.h b/RubiksCube/MeshData.h deleted file mode 100644 index 6f70f09..0000000 --- a/RubiksCube/MeshData.h +++ /dev/null @@ -1 +0,0 @@ -#pragma once diff --git a/RubiksCube/PartialCube.cpp b/RubiksCube/PartialCube.cpp deleted file mode 100644 index e69de29..0000000 diff --git a/RubiksCube/PartialCube.h b/RubiksCube/PartialCube.h deleted file mode 100644 index e69de29..0000000 diff --git a/RubiksCube/PartitionedCube.cpp b/RubiksCube/PartitionedCube.cpp index 68c8ae4..bf015d6 100644 --- a/RubiksCube/PartitionedCube.cpp +++ b/RubiksCube/PartitionedCube.cpp @@ -1,23 +1,30 @@ #include "PartitionedCube.h" +#include "Debug.h" + #include "Cube.h" -#include -#include +#include +#include -PartitionedCube::PartitionedCube(const glm::vec3& position, Color* sideColors) : _faces { - sideColors[0], - sideColors[1], - sideColors[2], - sideColors[3], - sideColors[4], - sideColors[5], - } +Shader PartitionedCubeMesh::_shader("cube.vert", "cube.frag"); + +Texture PartitionedCubeMesh::_diffuse; +Texture PartitionedCubeMesh::_roughness; +Texture PartitionedCubeMesh::_normal; + +PartitionedCube::PartitionedCube(Cube* parent, const glm::vec3& position, Color* sideColors) : Entity(parent) { - this->_transform = glm::translate(glm::mat4(1.0f), position); + this->TransformLocal(glm::translate(glm::mat4(1.0f), position)); + + this->_transformTem = glm::mat4(1.0f); + + for(int i=0;i<6;i++) { + _faces[i] = sideColors[i]; + } + _animationTime = 0.0f; _animationTimeExtend = 0.0f; - _animationTransformInvoke = _transform; _animationTransform = glm::quat(1.0f, 0.0f, 0.0f, 0.0f); _animationFinished = true; @@ -33,7 +40,7 @@ PartitionedCube::PartitionedCube(const glm::vec3& position, Color* sideColors) : z[axis] = factor; x[(axis+1)%3] = 1.0f; - y[(axis+2)%3] = 1.0f; + y = glm::cross(x, z); glm::vec3 v0 = x * 0.5f + y * 0.5f + 0.5f * z; glm::vec3 v1 = x * -0.5f + y * 0.5f + 0.5f * z; @@ -42,37 +49,109 @@ PartitionedCube::PartitionedCube(const glm::vec3& position, Color* sideColors) : int s = 2 * axis + reverse; // (-x, +x, -y, +y, -z, +z) - _meshData.addFace(v0, v1, v2, v3, sideColors[s]); + glm::vec2 uv0 = glm::vec2(1.0f, 1.0f); + glm::vec2 uv1 = glm::vec2(0.0f, 1.0f); + glm::vec2 uv2 = glm::vec2(1.0f, 0.0f); + glm::vec2 uv3 = glm::vec2(0.0f, 0.0f); + + VertexData d0 = VertexData(v0, z, uv0, sideColors[s]); + VertexData d1 = VertexData(v1, z, uv1, sideColors[s]); + VertexData d2 = VertexData(v2, z, uv2, sideColors[s]); + VertexData d3 = VertexData(v3, z, uv3, sideColors[s]); + + _mesh.AddQuad(d0, d1, d2, d3); factor = -factor; } } + + _mesh.CalculateTangents(); } void PartitionedCube::Update(double deltaTime) { this->_animationTime -= deltaTime; - + if (!_animationFinished && _animationTime <= 0.0f) { - _animationFinished = true; + _animationTime = 0.0f; - _transform = glm::mat4_cast(_animationTransform) * _animationTransformInvoke; + const glm::mat4 finishedAnimationTransform = TransformCustom(); + SetTransform(finishedAnimationTransform); + _animationFinished = true; } } -void PartitionedCube::Transform(glm::mat4 transformation) -{ - this->_transform = transformation * this->_transform; - _animationTransformInvoke = transformation * _animationTransformInvoke; - _animationTransform = glm::quat_cast(transformation * glm::mat4_cast(_animationTransform)); +void PartitionedCube::Render(DefaultUniform& uniform) { + uniform.model = TransformCustom(); + + _mesh.Render(uniform); } -void PartitionedCube::TransformAnimation(const glm::mat4& transform, float duration) { +int toIndex(const glm::ivec3& direction) { + int axis = glm::abs(direction.x * 1 + direction.y * 2 + direction.z * 3) - 1; + int allAxis = direction.x + direction.y + direction.z; + int x = glm::sign(allAxis); + + int reverse = (x + 1) / 2; + + return axis * 2 + reverse; +} + +glm::ivec3 toDirection(int index) { + int axis = index / 2; + int reverse = index % 2; + + glm::ivec3 direction = glm::ivec3(0); + direction[axis] = 1; + + if (reverse == 0) + direction = -direction; + + return direction; +} + +void PartitionedCube::TransformData(const glm::ivec3& axis, int turns) { + Color previousListColors[6]; + for(int i=0;i<6;i++) { + previousListColors[i] = _faces[i]; + } + + glm::mat3 rotations = glm::rotate(glm::mat4(1.0f), glm::radians(90.0f) * turns, glm::vec3(axis)); + + for(int axis=0;axis<3;axis++) { + for(int direction=-1;direction<=1;direction+=2) { + glm::ivec3 position = glm::ivec3(0); + position[axis] = direction; + + glm::vec3 newPosition = rotations * position; + + glm::ivec3 newPositionStable; + newPositionStable.x = std::round(newPosition.x); + newPositionStable.y = std::round(newPosition.y); + newPositionStable.z = std::round(newPosition.z); + + int newIndex = toIndex(newPositionStable); + int index = toIndex(position); + _faces[newIndex] = previousListColors[index]; + } + } +} + +void PartitionedCube::TransformAnimation(const glm::quat& transform, float duration) { if (!_animationFinished) - _transform = glm::mat4_cast(_animationTransform) * _animationTransformInvoke; - - this->_animationTransformInvoke = this->_transform; - this->_animationTransform = glm::quat_cast(transform); + SetTransform(TransformCustom()); + + this->_animationTransform = transform; this->_animationTime = duration; this->_animationTimeExtend = duration; _animationFinished = false; } + +void PartitionedCube::TransformTemp(const glm::mat4& transform) +{ + _transformTem = transform; +} + +void PartitionedCube::UndoTransformTemp() +{ + _transformTem = glm::mat4(1.0f); +} diff --git a/RubiksCube/PartitionedCube.h b/RubiksCube/PartitionedCube.h index 2069dbb..e855da8 100644 --- a/RubiksCube/PartitionedCube.h +++ b/RubiksCube/PartitionedCube.h @@ -4,47 +4,121 @@ #include -#include -#include -#include +#include +#include +#include #include #include +#include "Entity.h" + +#include "Texture.h" +#include "Shader.h" +#include "Mesh.h" + struct VertexData { glm::vec3 position; + glm::vec3 normal; + glm::vec2 uv0; + glm::vec3 tangent; + glm::vec3 bitangent; Color color; - VertexData() : position(glm::vec3(0.0f, 0.0f, 0.0f)), color(Color::WHITE()) { - - } - - VertexData(const glm::vec3& position, Color color) : position(position), color(color) { + VertexData(const glm::vec3& position, const glm::vec3& normal, const glm::vec2 uv0, Color color) : position(position), color(color), normal(normal), uv0(uv0), tangent(0.0f), bitangent(0.0f) { } }; -struct MeshData { - int vertexIndex; - std::vector data; - int triangleIndex; +class PartitionedCubeMesh : public Mesh { +private: + static Shader _shader; + static Texture _diffuse; + static Texture _roughness; + static Texture _normal; - MeshData() { - vertexIndex = 0; - triangleIndex = 0; + GLuint _uniformDiffuse; + GLuint _uniformRoughness; + GLuint _uniformNormal; + +public: + PartitionedCubeMesh() : Mesh(&_shader) { + _shader.Use(); + + glBindVertexArray(_vba); + glBindBuffer(GL_ARRAY_BUFFER, _vbo); + + int shd = shader->Reference(); + + _uniformDiffuse = glGetUniformLocation(shd, "diffuse"); + _uniformRoughness = glGetUniformLocation(shd, "roughness"); + _uniformNormal = glGetUniformLocation(shd, "normal"); + + glUniform1i(_uniformDiffuse, 0); + glUniform1i(_uniformRoughness, 1); + glUniform1i(_uniformNormal, 2); + + glVertexAttribPointer(glGetAttribLocation(shd, "position"), 3, GL_FLOAT, GL_FALSE, sizeof(VertexData), (void*)offsetof(VertexData, position)); + glVertexAttribPointer(glGetAttribLocation(shd, "normal"), 3, GL_FLOAT, GL_FALSE, sizeof(VertexData), (void*)offsetof(VertexData, normal)); + glVertexAttribPointer(glGetAttribLocation(shd, "uv0"), 2, GL_FLOAT, GL_FALSE, sizeof(VertexData), (void*)offsetof(VertexData, uv0)); + glVertexAttribPointer(glGetAttribLocation(shd, "tangent"), 3, GL_FLOAT, GL_FALSE, sizeof(VertexData), (void*)offsetof(VertexData, tangent)); + glVertexAttribPointer(glGetAttribLocation(shd, "bitangent"), 3, GL_FLOAT, GL_FALSE, sizeof(VertexData), (void*)offsetof(VertexData, bitangent)); + glVertexAttribPointer(glGetAttribLocation(shd, "color"), 4, GL_FLOAT, GL_FALSE, sizeof(VertexData), (void*)offsetof(VertexData, color)); + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + glEnableVertexAttribArray(2); + glEnableVertexAttribArray(3); + glEnableVertexAttribArray(4); + glEnableVertexAttribArray(5); + + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); + + _shader.Disable(); } - void addTriangle(glm::vec3 v0, glm::vec3 v1, glm::vec3 v2, Color color) { - data.push_back(VertexData(v0, color)); - data.push_back(VertexData(v1, color)); - data.push_back(VertexData(v2, color)); - vertexIndex += 3; + virtual void Render(const DefaultUniform& uniform) override { + _shader.Use(); - triangleIndex ++; + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, _diffuse.Reference()); + + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, _roughness.Reference()); + + glActiveTexture(GL_TEXTURE2); + glBindTexture(GL_TEXTURE_2D, _normal.Reference()); + + _shader.Disable(); + + Mesh::Render(uniform); } - void addFace(glm::vec3 v0, glm::vec3 v1, glm::vec3 v2, glm::vec3 v3, Color color) { - addTriangle(v0, v1, v2, color); - addTriangle(v3, v2, v1, color); + static void LoadResources() { + _diffuse = Texture("diffuse.png"); + _roughness = Texture("roughness.png"); + _normal = Texture("normal.png"); + } + + void CalculateTangents() { + for(int i=0;i<_vertexData.size();i += 3) { + VertexData& d0 = _vertexData.at(i + 0); + VertexData& d1 = _vertexData.at(i + 1); + VertexData& d2 = _vertexData.at(i + 2); + + glm::vec3 deltaPos1 = d1.position - d0.position; + glm::vec3 deltaPos2 = d2.position - d0.position; + + glm::vec2 deltaUV1 = d1.uv0 - d0.uv0; + glm::vec2 deltaUV2 = d2.uv0 - d0.uv0; + + float r = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV1.y * deltaUV2.x); + d0.tangent = (deltaPos1 * deltaUV2.y - deltaPos2 * deltaUV1.y) * r; + d0.bitangent = (deltaPos2 * deltaUV1.x - deltaPos1 * deltaUV2.x) * r; + d1.tangent = (deltaPos1 * deltaUV2.y - deltaPos2 * deltaUV1.y) * r; + d1.bitangent = (deltaPos2 * deltaUV1.x - deltaPos1 * deltaUV2.x) * r; + d2.tangent = (deltaPos1 * deltaUV2.y - deltaPos2 * deltaUV1.y) * r; + d2.bitangent = (deltaPos2 * deltaUV1.x - deltaPos1 * deltaUV2.x) * r; + } } }; @@ -61,40 +135,45 @@ constexpr glm::vec3 SideToDirection[6] = { glm::vec3(0.0f, 0.0f, 1.0f), }; -class PartitionedCube +class Cube; + +class PartitionedCube : public Entity { private: - glm::mat4 _transform; + glm::mat4 _transformTem; - glm::mat4 _animationTransformInvoke; glm::quat _animationTransform; float _animationTime; float _animationTimeExtend; bool _animationFinished; Color _faces[6]; - MeshData _meshData; + PartitionedCubeMesh _mesh; public: - PartitionedCube(const glm::vec3& position, Color sideColors[6]); + PartitionedCube(Cube* parent, const glm::vec3& position, Color* sideColors); - void Update(double deltaTime); + void Update(double deltaTime) override; + void Render(DefaultUniform& uniform) override; - const glm::mat4 Transform() const { + const glm::mat4 TransformCustom() const { // Need to use slerp since rotations are non-linear and would not interpolate this way + // + // However a lerp can be used and then normalized, with not that much of an + // error but being much more performant thus preferred function if (false == _animationFinished) { float lerpFactor = 1.0f - ( _animationTime / _animationTimeExtend); - glm::mat4 animationView = glm::mat4_cast(glm::slerp(glm::quat(1.0f, 0.0f, 0.0f, 0.0f), _animationTransform, lerpFactor)); + glm::mat4 animationView = glm::mat4_cast(glm::normalize(glm::lerp(glm::quat(1.0f, 0.0f, 0.0f, 0.0f), _animationTransform, lerpFactor))); - return animationView * _animationTransformInvoke; + return Parent()->LocalToWorld() * animationView * LocalObjectTransform(); } - return _transform; + return Parent()->LocalToWorld() * _transformTem * LocalObjectTransform(); } - const MeshData& MeshData() const { return _meshData; }; - - void Transform(glm::mat4 transformation); - void TransformAnimation(const glm::mat4& transform, float duration); + void TransformData(const glm::ivec3& axis, int turns); + void TransformAnimation(const glm::quat& transform, float duration); + void TransformTemp(const glm::mat4& transform); + void UndoTransformTemp(); }; diff --git a/RubiksCube/Plane.h b/RubiksCube/Plane.h index 877efcf..cd73d8b 100644 --- a/RubiksCube/Plane.h +++ b/RubiksCube/Plane.h @@ -1,6 +1,6 @@ #pragma once -#include +#include class Plane { diff --git a/RubiksCube/RubiksCube.cpp b/RubiksCube/RubiksCube.cpp index 0b01b4e..0ea8d3f 100644 --- a/RubiksCube/RubiksCube.cpp +++ b/RubiksCube/RubiksCube.cpp @@ -8,6 +8,8 @@ #include "GameInterface.h" #include "SceneInterface.h" +#define STB_IMAGE_IMPLEMENTATION +#include diff --git a/RubiksCube/RubiksCube.vcxproj b/RubiksCube/RubiksCube.vcxproj index 93c882f..43beaec 100644 --- a/RubiksCube/RubiksCube.vcxproj +++ b/RubiksCube/RubiksCube.vcxproj @@ -71,10 +71,10 @@ - $(SolutionDir)/../ExternalResources/glew/include;$(SolutionDir)/../ExternalResources/glfw/include;$(SolutionDir)/../ExternalResources/glm/glm;$(SolutionDir)/../ExternalResources/stb;$(ExternalIncludePath) + $(SolutionDir)/../ExternalResources/glew/include;$(SolutionDir)/../ExternalResources/glfw/include;$(SolutionDir)/../ExternalResources/glm;$(SolutionDir)/../ExternalResources/stb;$(ExternalIncludePath) - $(SolutionDir)/../ExternalResources/glew/include;$(SolutionDir)/../ExternalResources/glfw/include;$(SolutionDir)/../ExternalResources/glm/glm;$(SolutionDir)/../ExternalResources/stb;$(ExternalIncludePath) + $(SolutionDir)/../ExternalResources/glew/include;$(SolutionDir)/../ExternalResources/glfw/include;$(SolutionDir)/../ExternalResources/glm;$(SolutionDir)/../ExternalResources/stb;$(ExternalIncludePath) @@ -110,6 +110,7 @@ true _DEBUG;_CONSOLE;%(PreprocessorDefinitions) true + stdcpp17 Console @@ -126,6 +127,7 @@ true NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true + stdcpp17 Console @@ -139,30 +141,36 @@ + + + - + + + - + + - + @@ -176,6 +184,40 @@ false + + + false + Document + false + + + false + Document + false + + + + + false + false + + + + + false + false + + + false + false + + + + + false + false + + diff --git a/RubiksCube/RubiksCube.vcxproj.filters b/RubiksCube/RubiksCube.vcxproj.filters index 43eaf9b..b0bb364 100644 --- a/RubiksCube/RubiksCube.vcxproj.filters +++ b/RubiksCube/RubiksCube.vcxproj.filters @@ -36,9 +36,6 @@ Quelldateien - - Quelldateien - Quelldateien @@ -51,6 +48,15 @@ Quelldateien + + Quelldateien + + + Quelldateien + + + Quelldateien + @@ -68,15 +74,6 @@ Headerdateien - - Headerdateien - - - Headerdateien - - - Headerdateien - Headerdateien @@ -92,6 +89,27 @@ Headerdateien + + Headerdateien + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + Headerdateien + + + Headerdateien + @@ -100,5 +118,23 @@ Shaders + + Shaders + + + Shaders + + + Ressourcendateien + + + Ressourcendateien + + + Ressourcendateien + + + Ressourcendateien + \ No newline at end of file diff --git a/RubiksCube/SceneInterface.cpp b/RubiksCube/SceneInterface.cpp index da5886f..13d96af 100644 --- a/RubiksCube/SceneInterface.cpp +++ b/RubiksCube/SceneInterface.cpp @@ -6,9 +6,10 @@ #include "Plane.h" -#include -#include -#include +#include +#include +#include + #include @@ -16,6 +17,20 @@ #include +SceneInterface::SceneInterface() +{ + +} + +SceneInterface::~SceneInterface() +{ + for(Entity* entity : _entities) { + delete entity; + } + + delete currentAction; +} + void SceneInterface::Initialize(GLFWwindow* window) { _inputSystem.SetWindow(window); @@ -39,23 +54,31 @@ void SceneInterface::Initialize(GLFWwindow* window) _inputSystem.ObserveKey(GLFW_KEY_KP_9); _wasMouseDown = false; - - this->_shaderProgram = ShaderUtil::CreateShaderProgram("cube.vert", "cube.frag"); - this->_transformLocation = glGetUniformLocation(this->_shaderProgram, "transformation"); - - glGenVertexArrays(1, &this->_arrayBufferObject); - glGenBuffers(1, &this->_vertexBufferObject); - - glBindVertexArray(_arrayBufferObject); - glBindBuffer(GL_ARRAY_BUFFER, this->_vertexBufferObject); - - glVertexAttribPointer(glGetAttribLocation(_shaderProgram, "position"), 3, GL_FLOAT, GL_FALSE, sizeof(VertexData), (void*)offsetof(VertexData, position)); - glEnableVertexAttribArray(0); - glVertexAttribPointer(glGetAttribLocation(_shaderProgram, "color"), 4, GL_FLOAT, GL_FALSE, sizeof(VertexData), (void*)offsetof(VertexData, color)); - glEnableVertexAttribArray(1); - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindVertexArray(0); + _debug = new Debug(window); + + PartitionedCubeMesh::LoadResources(); + + _camera = new Camera(window, &_inputSystem); + Cube* cube = new Cube(); + _cube = cube; + + _entities.push_back(_debug); + _entities.push_back(_camera); + _entities.push_back(_cube); + + for(PartitionedCube** cube = _cube->Children();cube != _cube->Children() + _cube->ChildPartitionsCount;cube++) { + _entities.push_back(*cube); + } + + std::cout << "Controls: " << std::endl; + std::cout << "Numpad 1, 2, 3, 7, 8, 9 + Shift Rotate X Axis Down Up View" << std::endl; + std::cout << "Numpad 1, 4, 7, 3, 6, 9 Rotate Y Axis Left Right View" << std::endl; + std::cout << "Arrows Up, Down, Right, Left Rotate Camera relative lookat" << std::endl; + std::cout << "Space Reset Look Changes" << std::endl; + std::cout << "Mouse Scroll Vertically Zoom" << std::endl; + std::cout << "Left Mouse Rotate Cube Planes" << std::endl; + std::cout << "Right Mouse Rotate Camera" << std::endl; } glm::vec3 basisCoordinateVectors[] = { @@ -113,33 +136,19 @@ void IntersectLinePlane(const glm::vec3& lineStart, const glm::vec3& lineDirecti } } -void SceneInterface::EnqueueAction(const Action& action) { +void SceneInterface::EnqueueAction(Action* action) { _actions.push(action); } void SceneInterface::Update(float deltaTime) { - _cube.Update(deltaTime); _inputSystem.Update(); - if (_inputSystem.WasKeyPressed(GLFW_KEY_SPACE)) - _orientation = glm::quat(1.0f, glm::vec3(0.0f, 0.0f, 0.0f)); + for(Entity* entity : _entities) { + entity->Update(deltaTime); + } - glm::fvec2 velocity(0.0f, 0.0f); - if (_inputSystem.IsKeyPressed(GLFW_KEY_UP)) - velocity.x = glm::radians(90.0f); - if (_inputSystem.IsKeyPressed(GLFW_KEY_DOWN)) - velocity.x = glm::radians(-90.0f); - - if (_inputSystem.IsKeyPressed(GLFW_KEY_RIGHT)) - velocity.y = glm::radians(90.0f); - if (_inputSystem.IsKeyPressed(GLFW_KEY_LEFT)) - velocity.y = glm::radians(-90.0f); - - glm::quat velocityQuaternion = glm::quat(0.0f, glm::vec3(velocity.x, velocity.y, 0.0f)); - - _orientation += 0.5f * (float)deltaTime * velocityQuaternion * _orientation; - _orientation = glm::normalize(_orientation); + // int spinIndex = 0; @@ -170,49 +179,98 @@ void SceneInterface::Update(float deltaTime) int axisIndex = spinAxis.x == 0; // x => 0, y => 1 int spinIndex = keyToIndex[axisIndex][rotationKey - 1]; - if (reverse) - spinAxis *= -1; + int reverseSign = reverse ? -1 : 1; + + spinAxis *= reverseSign; + spinIndex *= reverseSign; + + glm::mat3 cubeMat = glm::mat3(_cube->LocalToWorld()) * glm::mat3(_camera->View()); + + glm::mat3 orthogonalized = cubeMat; + for(int column=0;column<3;column++) { + int nearestCardinalisedAxis = 0; + for(int i=0;i<3;i++) { + if (glm::abs(orthogonalized[column][i]) > glm::abs(orthogonalized[column][nearestCardinalisedAxis])) { + nearestCardinalisedAxis = i; + } + } + + for(int columnToPlace=0;columnToPlace<3;columnToPlace++) { + for(int i=0;i<3;i++) { + if (columnToPlace == column) { + if (nearestCardinalisedAxis == i) { + orthogonalized[columnToPlace][i] = glm::sign(orthogonalized[columnToPlace][i]); + } + else { + orthogonalized[columnToPlace][i] = 0.0f; + } + } + else { + if (nearestCardinalisedAxis == i) { + orthogonalized[columnToPlace][i] = 0.0f; + } + } + } + } + } + + glm::mat3 screenToCube = glm::inverse(orthogonalized); + + spinAxis = screenToCube * spinAxis; + int direction = spinAxis.x + spinAxis.y + spinAxis.z; + + EnqueueAction(new ActionSpinDefault(_cube, spinAxis, spinIndex, 1.0f)); // _cube.TransformAnimation(cubeSpinAxis, spinIndex, rotation, 1.0f); - EnqueueAction(Action(spinAxis, spinIndex, 1.0f)); - std::cout << glm::to_string(spinAxis) << " " << spinIndex << std::endl; } - if (!_wasMouseDown && _inputSystem.IsLeftMouseDown()) { - OnDragStart(); + if (currentAction == nullptr) { + if (!_wasMouseDown && _inputSystem.IsLeftMouseDown()) { + OnDragStart(); + } + else if (_wasMouseDown && !_inputSystem.IsLeftMouseDown()) { + OnDragStop(); + } + else if (_wasMouseDown && _inputSystem.IsLeftMouseDown()) { + OnDrag(deltaTime); + } + + _wasMouseDown = _inputSystem.IsLeftMouseDown(); } - else if (_wasMouseDown && !_inputSystem.IsLeftMouseDown()) { - OnDragStop(); - } - else if (_wasMouseDown && _inputSystem.IsLeftMouseDown()) { - OnDrag(deltaTime); + + if (currentAction != nullptr) { + currentAction->duration -= deltaTime; } - _wasMouseDown = _inputSystem.IsLeftMouseDown(); + if (currentAction != nullptr && currentAction->duration > 0.0f) { + return; + } - currentAction.duration -= deltaTime; - if (currentAction.duration <= 0.0f && _actions.size() > 0) { - Action& action = _actions.front(); + if (_actions.size() > 0) { + delete currentAction; + + Action* action = _actions.front(); _actions.pop(); ApplyAction(action); } + else { + currentAction = nullptr; + } } void SceneInterface::OnDragStart() { _inputSystem.GetMousePos(_initialMouseLocation); - Plane planes[6]; - glm::vec3 lineStart; glm::vec3 lineDirection; - glm::mat4 viewPerspective = _perspective * _view; + glm::mat4 viewPerspective = _camera->Projection() * _camera->View(); _inputSystem.GetPickingRay(viewPerspective, lineStart, lineDirection); - glm::mat4 transform = glm::mat4(1.0f); + glm::mat4 transform = _cube->LocalToWorld(); for(int axis=0;axis<3;axis++) { for(int direction=-1;direction<=1;direction+=2) { @@ -252,7 +310,7 @@ void SceneInterface::OnDrag(double deltaTime) { glm::vec3 lineStart; glm::vec3 lineDirection; - glm::mat4 transform = _perspective * _view; + glm::mat4 transform = _camera->Projection() * _camera->View(); _inputSystem.GetPickingRay(transform, lineStart, lineDirection); float intersectionDistance = -1.0f; @@ -269,7 +327,7 @@ void SceneInterface::OnDrag(double deltaTime) { float dragAbsDistance = abs(glm::distance(glm::vec2(0.0f), directionPlane)); if (dragAbsDistance >= MIN_DRAG_MAGNITUDE) { - glm::vec3 mousePosition = glm::inverse(planeTransform) * glm::vec4(_mousePositionPlane, 0.0f, 1.0f); + glm::vec3 mousePosition = planeTransform * glm::vec4(_mousePositionPlane, 0.0f, 1.0f); glm::vec3 direction = glm::inverse(planeTransform) * glm::vec4(directionPlane, 0.0f, 0.0f); @@ -279,113 +337,76 @@ void SceneInterface::OnDrag(double deltaTime) { axisSingle = orthogonalise(axisSingle); float projection = glm::dot(mousePosition, axisSingle); - + int index = floor((projection + 0.5f)); glm::ivec3 axis = orthogonalise(axisSingle); - std::cout << glm::to_string(axisSingle) << std::endl; - std::cout << glm::to_string(direction) << std::endl; - std::cout << glm::to_string(axis) << std::endl; + _cube->UndoTransformTemp(); - _cube.TransformTemp(axis, index, glm::rotate(glm::mat4(1.0f), glm::radians(abs(glm::distance(glm::vec2(0.0f), directionPlane))), axisSingle)); + float angle = glm::radians(90.0f * ( 1.0f / 3.0f ) * abs(glm::distance(glm::vec2(0.0f), directionPlane))); + + _cube->TransformTemp(axis, index, glm::rotate(glm::mat4(1.0f), angle, axisSingle)); + _spinIndex = index; + _spinAxis = axis; + _spinDelta = angle; } } +const float rotations = 2 * glm::pi(); + +const float quarterRotation = rotations * 0.25f; + void SceneInterface::OnDragStop() { + // Undo any temporarilies + _cube->UndoTransformTemp(); + + // Transform Instantly to current angle + _cube->Transform(_spinAxis, _spinIndex, glm::rotate(glm::mat4(1.0f), _spinDelta, glm::vec3(_spinAxis))); + + float angleNormalized = std::fmodf(_spinDelta, quarterRotation); + + float remainingAngle = 0.5f - glm::abs(angleNormalized - quarterRotation * 0.5f); + float remainingFactor = remainingAngle / quarterRotation; + + int rotations = round(_spinDelta / quarterRotation); + + // remaining rotation animating + if (_spinDelta > 0.0f && rotations > 0) { + EnqueueAction(new ActionSpinAfterDragging(_cube, _spinAxis, _spinIndex, ( quarterRotation * rotations ) - _spinDelta, rotations, remainingFactor * 1.0f)); + } + else { + EnqueueAction(new ActionSpinAfterDragging(_cube, _spinAxis, _spinIndex, -( _spinDelta ), rotations, remainingFactor * 1.0f)); + } } -void SceneInterface::ApplyAction(const Action& action) { +void SceneInterface::ApplyAction(Action* action) { currentAction = action; - glm::mat3 cubeMat = _cube.Transform() * glm::mat4_cast(_orientation); - - glm::mat3 orthogonalized = cubeMat; - for(int column=0;column<3;column++) { - int nearestCardinalisedAxis = 0; - for(int i=0;i<3;i++) { - if (glm::abs(orthogonalized[column][i]) > glm::abs(orthogonalized[column][nearestCardinalisedAxis])) { - nearestCardinalisedAxis = i; - } - } - - for(int columnToPlace=0;columnToPlace<3;columnToPlace++) { - for(int i=0;i<3;i++) { - if (columnToPlace == column) { - if (nearestCardinalisedAxis == i) { - orthogonalized[columnToPlace][i] = glm::sign(orthogonalized[columnToPlace][i]); - } - else { - orthogonalized[columnToPlace][i] = 0.0f; - } - } - else { - if (nearestCardinalisedAxis == i) { - orthogonalized[columnToPlace][i] = 0.0f; - } - } - } - } - } - - glm::vec3 axis = action.axis; - - int direction = axis.x + axis.y + axis.z; - - axis = glm::inverse(orthogonalized) * axis; - - int index = action.index; - index *= direction; - - glm::mat4 rotationMat = glm::rotate(glm::mat4(1.0f), glm::radians(90.0f), axis); - for(int i=0;i<3;i++) { - for(int g=0;g<3;g++) { - rotationMat[i][g] = round(rotationMat[i][g]); - } - } - glm::imat3x3 rotation = glm::imat3x3(rotationMat); - - _cube.TransformAnimation(axis, index, rotation, action.duration); + action->Invoke(); } void SceneInterface::Render(float aspectRatio) { - glUseProgram(_shaderProgram); - glBindVertexArray(this->_arrayBufferObject); + if (_camera == nullptr) + return; - glBindBuffer(GL_ARRAY_BUFFER, this->_vertexBufferObject); + DefaultUniform uniform; + uniform.view = _camera->View(); + uniform.projection = _camera->Projection(); - _view = glm::lookAt(glm::vec3(0.0f, 0.0f, 4.75f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f)) * glm::mat4_cast(_orientation); - _perspective = glm::perspective(glm::radians(45.0f), aspectRatio, 0.1f, 100.0f); + for(Entity* entity : _entities) { + uniform.model = entity->LocalToWorld(); - auto transformation = _perspective * _view; + entity->Render(uniform); + } // std::cout << "perspective: " << glm::to_string(glm::perspective(glm::radians(45.0f), aspectRatio, 0.1f, 100.0f)) << std::endl; // std::cout << "view : " << glm::to_string(glm::lookAt(glm::vec3(0.0f, 0.0f, -3.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f))) << std::endl; // std::cout << "model : " << glm::to_string(_cube.Transform()) << std::endl; - - for(int i=0;i<_cube.ChildPartitionsCount;i++) { - const PartitionedCube* cube = _cube.Children()[i]; - - const MeshData& meshData = cube->MeshData(); - - const glm::vec3& position = meshData.data[0].position; - - auto localTransform = transformation * cube->Transform(); - - glUniformMatrix4fv(_transformLocation, 1, GL_FALSE, glm::value_ptr(localTransform)); - glBufferData(GL_ARRAY_BUFFER, sizeof(VertexData) * meshData.vertexIndex, meshData.data.data(), GL_STATIC_DRAW); - glDrawArrays(GL_TRIANGLES, 0, meshData.vertexIndex); - } - - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindVertexArray(0); - glUseProgram(0); } void SceneInterface::ClearResources() { - glDeleteBuffers(1, &_vertexBufferObject); - glDeleteVertexArrays(1, &_arrayBufferObject); - glDeleteProgram(_shaderProgram); + } diff --git a/RubiksCube/SceneInterface.h b/RubiksCube/SceneInterface.h index 193de17..f0c71e8 100644 --- a/RubiksCube/SceneInterface.h +++ b/RubiksCube/SceneInterface.h @@ -2,69 +2,109 @@ #include +#include "Debug.h" #include "Plane.h" #include "InputSystem.h" #include "GameInterface.h" +#include "Camera.h" #include "Shader.h" #include "Cube.h" -#include +#include #include struct Action { - glm::ivec3 axis; - int index; +protected: + Cube* _cube; + +public: double duration; - Action() : axis(glm::ivec3(0)), index(0), duration(0.0f) { } + Action(Cube* cube, double duration) : _cube(cube), duration(duration) { } + virtual void Invoke() = 0; +}; - Action(const glm::ivec3& axis, int index, double duration) - : axis(axis), index(index), duration(duration) { +struct ActionSpinDefault : public Action { + glm::ivec3 axis; + int index; + + ActionSpinDefault() : Action(nullptr, 0.0f), axis(glm::ivec3(0)), index(0) { } + + ActionSpinDefault(Cube* cube, const glm::ivec3& axis, int index, double duration) + : Action(cube, duration), axis(axis), index(index) { } + + void Invoke() override { + _cube->TransformAnimation(axis, index, glm::radians(90.0f), duration); + + _cube->TransformData(axis, index, 1); + } +}; + +struct ActionSpinAfterDragging : public Action { + glm::ivec3 axis; + int index; + float angle; + int turns; + + ActionSpinAfterDragging() : Action(nullptr, 0.0f), axis(glm::ivec3(0)), index(0), angle(0.0f), turns(0) { } + + ActionSpinAfterDragging(Cube* cube, const glm::ivec3& axis, int index, float angle, int turns, double duration) + : Action(cube, duration), axis(axis), index(index), angle(angle), turns(turns) { + + } + + void Invoke() override { + _cube->TransformAnimation(axis, index, angle, duration); + + _cube->TransformData(axis, index, turns); + } }; class SceneInterface : public GameInterface { private: - Cube _cube; - std::queue _actions; - Action currentAction; + Cube* _cube; + Camera* _camera; + + std::vector _entities; + + std::queue _actions; + Action* currentAction; + + Debug* _debug; InputSystem _inputSystem; - glm::quat _orientation; - bool _wasMouseDown; glm::vec2 _initialMouseLocation; Plane _plane; glm::vec2 _mousePositionPlane; - glm::mat4 _view; - glm::mat4 _perspective; - - GLuint _shaderProgram; - GLuint _transformLocation; - GLuint _arrayBufferObject; - GLuint _vertexBufferObject; + int _spinIndex; + glm::ivec3 _spinAxis; + float _spinDelta; const float MIN_DRAG_MAGNITUDE = 0.16f; - void ApplyAction(const Action& action); + void ApplyAction(Action* action); void OnDragStart(); void OnDrag(double deltaTime); void OnDragStop(); public: + SceneInterface(); + ~SceneInterface(); + void Initialize(GLFWwindow* window) override; void Update(float deltaTime) override; void Render(float aspectRatio) override; - void EnqueueAction(const Action& action); + void EnqueueAction(Action* action); void ClearResources() override; }; - diff --git a/RubiksCube/Settings.h b/RubiksCube/Settings.h new file mode 100644 index 0000000..a708389 --- /dev/null +++ b/RubiksCube/Settings.h @@ -0,0 +1,12 @@ +#pragma once + +const bool ENABLE_DEBUG = true; + +namespace Settings { + const float TURN_ANIMATION_DURATION = 1.25f; + const float MIN_TURN_DISTANCE = 3.0f; + + const float MIN_CAMERA_DISTANCE = 4.2f; + const float MAX_CAMERA_DISTANCE = 10.0f; + const float CAMERA_DISTANCE_START = 8.0f; +} diff --git a/RubiksCube/Shader.cpp b/RubiksCube/Shader.cpp deleted file mode 100644 index 43a1b8e..0000000 --- a/RubiksCube/Shader.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "Shader.h" diff --git a/RubiksCube/Shader.h b/RubiksCube/Shader.h index 9eac2f8..0639e09 100644 --- a/RubiksCube/Shader.h +++ b/RubiksCube/Shader.h @@ -1,5 +1,45 @@ #pragma once -class Shader -{ -}; +#include + +#include "ShaderUtil.h" + +class Shader { +private: + const char* _vert; + const char* _frag; + + GLuint _id; + +public: + Shader(const char* vert, const char* frag) : _vert(vert), _frag(frag), _id(0) { + + } + + ~Shader() { + if (_id != 0) { + glDeleteProgram(_id); + } + } + + GLuint Reference() const { return _id; } + + void Initialize() { + if (_id != 0) + return; + + _id = ShaderUtil::CreateShaderProgram(_vert, _frag); + } + + void Use() { + Initialize(); + + glUseProgram(_id); + } + + void Disable() { + Initialize(); + + glUseProgram(0); + } +}; diff --git a/RubiksCube/Texture.h b/RubiksCube/Texture.h new file mode 100644 index 0000000..2cd8c6c --- /dev/null +++ b/RubiksCube/Texture.h @@ -0,0 +1,97 @@ +#pragma once + +#include + +#include + +#include + +class Texture { +private: + GLuint _id; + +protected: + const char* _texture; + +public: + GLuint Reference() const { + return _id; + } + +public: + Texture() : _id(0) { } + + Texture(const char* file) { + int width, height, len; + + stbi_set_flip_vertically_on_load(1); + unsigned char* data = stbi_load(file, &width, &height, &len, STBI_rgb_alpha); + + glGenTextures(1, &_id); + glBindTexture(GL_TEXTURE_2D, _id); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); + glGenerateMipmap(GL_TEXTURE_2D); + + glBindTexture(GL_TEXTURE_2D, 0); + + stbi_image_free(data); + } + + Texture(const Texture& other) : Texture(other._texture) { + + } + + Texture& operator=(const Texture& other) { + if (this == &other) + return *this; + + Texture copy(other._texture); + this->_id = other._id; + this->_texture = other._texture; + + copy._id = 0; + copy._texture = nullptr; + + return *this; + } + + Texture(Texture&& other) noexcept { + _id = other._id; + _texture = other._texture; + + other._id = 0; + other._texture = nullptr; + } + + Texture& operator= (Texture&& other) noexcept { + if (this == &other) + return *this; + + this->_id = other._id; + this->_texture = other._texture; + + other._id = 0; + other._texture = nullptr; + } + + ~Texture() { + if (_id == 0) + return; + + glDeleteTextures(1, &_id); + } + + void Use() const { + glBindTexture(GL_TEXTURE_2D, _id); + } + + void Disable() const { + glBindTexture(GL_TEXTURE_2D, 0); + } +}; diff --git a/RubiksCube/VertexData.h b/RubiksCube/VertexData.h deleted file mode 100644 index 6f70f09..0000000 --- a/RubiksCube/VertexData.h +++ /dev/null @@ -1 +0,0 @@ -#pragma once diff --git a/RubiksCube/cube.frag b/RubiksCube/cube.frag index 94d6252..0a77914 100644 --- a/RubiksCube/cube.frag +++ b/RubiksCube/cube.frag @@ -1,9 +1,45 @@ #version 330 +uniform mat4 view; + out vec4 color; in vec4 vertexColor; +in mat3 TBN; + +in vec3 fPos; +in vec2 TCordinate; + +uniform sampler2D diffuse; +uniform sampler2D roughness; +uniform sampler2D normal; + +const vec3 lightColor = vec3(1.0f, 1.0f, 1.0f); +const vec3 lightDir = vec3(-1.0f, -1.0f, 1.0f); +const float ambient = 0.24f; +const float specularValue = 1.0f; + void main() { - color = vertexColor; + vec4 diffuseBase = texture(diffuse, TCordinate); + + vec3 normal = texture(normal, TCordinate).rgb; + normal = normal * 2.0f - 1.0f; + normal = normalize(mat3(view) * TBN * normal); + + vec3 lightNormal = normalize(vec3(view * vec4(lightDir, 0.0f))); + + float diffValue = max(dot(normal, lightNormal), 0.0f); + float diffuse = diffValue; + + vec3 viewDir = normalize(-fPos); + + vec3 reflectDir = reflect(-lightNormal, normal); + + float spec = pow(max(dot(viewDir, reflectDir), 0.0f), 16); + float specular = specularValue * spec; + + float roughness = texture(roughness, TCordinate).g; + + color = vertexColor * diffuseBase * vec4((ambient + diffuse) * lightColor, 1.0f) + vec4(roughness * specular * lightColor, 1.0f); } diff --git a/RubiksCube/cube.vert b/RubiksCube/cube.vert index 2c8acd7..d60562b 100644 --- a/RubiksCube/cube.vert +++ b/RubiksCube/cube.vert @@ -1,13 +1,34 @@ #version 330 -uniform mat4 transformation; +uniform mat4 model; +uniform mat4 view; +uniform mat4 projection; layout (location=0) in vec3 position; -layout (location=1) in vec4 color; +layout (location=1) in vec3 normal; +layout (location=2) in vec2 uv0; +layout (location=3) in vec3 tangent; +layout (location=4) in vec3 bitangent; +layout (location=5) in vec4 color; out vec4 vertexColor; +out mat3 TBN; + +out vec3 fPos; +out vec2 TCordinate; + void main() { - gl_Position = transformation * vec4(position, 1.0f); + gl_Position = projection * view * model * vec4(position, 1.0f); + + fPos = vec3(view * model * vec4(position, 1.0f)); + vertexColor = color; + + vec3 T = normalize(vec3(model * vec4(tangent, 0.0f))); + vec3 B = normalize(vec3(model * vec4(bitangent, 0.0f))); + vec3 N = normalize(vec3(model * vec4(normal, 0.0f))); + TBN = mat3(T, B, N); + + TCordinate = uv0; } diff --git a/RubiksCube/diffuse.png b/RubiksCube/diffuse.png new file mode 100644 index 0000000000000000000000000000000000000000..d5169fd25d7b46ba931c6330d538ca3cbdb4a7fa GIT binary patch literal 35815 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 JoH&2uzX7PId~pB( literal 0 HcmV?d00001 diff --git a/RubiksCube/line.frag b/RubiksCube/line.frag new file mode 100644 index 0000000..e072dd6 --- /dev/null +++ b/RubiksCube/line.frag @@ -0,0 +1,19 @@ +#version 330 + +const float blend = 1.5f; + +in float lineStrength; + +in vec4 color; +in float lineWidth; + +out vec4 fragColor; + +void main(void) +{ + vec4 col = color; + float d = length(vec2(lineStrength) - gl_FragCoord.xy); + float w = lineWidth; + + fragColor = col; +} diff --git a/RubiksCube/line.vert b/RubiksCube/line.vert new file mode 100644 index 0000000..45aa850 --- /dev/null +++ b/RubiksCube/line.vert @@ -0,0 +1,69 @@ +#version 330 + +uniform vec2 uViewportRes; +uniform mat4 model; +uniform mat4 view; +uniform mat4 projection; + +layout (location = 0) in vec3 position; +layout (location = 1) in vec3 tangent; +layout (location = 2) in vec4 iColor; +layout (location = 3) in float iLineWidth; + +out vec4 color; +out float lineWidth; +out float lineStrength; + +void main(void) { + int index = gl_VertexID % 6; + float orientation = 1.0f + ( (index % 2) * -2 ); + + vec4 positionScreen = projection * view * model * vec4(position, 1.0f); + vec4 directionScreen = projection * view * model * vec4(position + tangent, 0.0f); + + vec4 normal = positionScreen - directionScreen; + + positionScreen.xyz /= positionScreen.w; + positionScreen.xy *= (uViewportRes * 0.5f); + + float aspectRatio = projection[1][1] / projection[0][0]; + + normal.xyz /= normal.w; + positionScreen += vec4(normalize(normal.yx * vec2(-1.0f, 1.0f) * orientation), 0.0f, 0.0f) * 0.5f * iLineWidth; + + positionScreen.xy /= (uViewportRes * 0.5f); + positionScreen.xyz *= positionScreen.w; + + + gl_Position = positionScreen; + + lineWidth = iLineWidth; + lineStrength = orientation; + color = iColor; +} + +void mainBackup(void) +{ + float aspectRatio = 0.0f; + + int index = gl_VertexID % 6; + float orientation = 1 + ( (index % 2) * -2 ); + + vec4 positionCamera = view * model * vec4(position, 1.0f); + + float distance = -position.z; + + vec4 tv = projection * view * model * vec4(position + tangent, 0.0f); + + vec4 v = vec4(normalize(tv.yx) * orientation * vec2(-1.0f, 1.0f), 0.0f, 0.0f) * distance; + + vec4 direction = positionCamera - tv; + + direction.xy = direction.yx * vec2(-1.0f, 1.0f) * orientation; + + gl_Position = (projection * positionCamera) + vec4(direction.xy, 0.0f, 0.0f); + + lineWidth = iLineWidth; + lineStrength = orientation; + color = vec4(iColor.xyz, 1.0f); +} diff --git a/RubiksCube/normal.png b/RubiksCube/normal.png new file mode 100644 index 0000000000000000000000000000000000000000..c7919427e5416dbca881124d933ee004dcf39547 GIT binary patch literal 12736 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 literal 0 HcmV?d00001 diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..e6993ac --- /dev/null +++ b/readme.txt @@ -0,0 +1,39 @@ +Rubiks Cube Projekt + + + +Steuerung: + +Linke Maustaste Halten und Ziehen Cube angeklickte Scheibe drehen +Rechte Maustaste Halten und Ziehen Kamera drehen +Scrollrad Zoom + +Shift + Numpad 1, 2, 3 ( Numlock ausschalten! ) Cube Horizontal nach unten +Shift + Numpad 7, 8, 9 ( Numlock ausschalten! ) Cube Horizontal nach oben +Numpad 1, 4, 7 Cube vertikal links drehen +Numpad 9, 6, 3 Cube vertikal rechts drehen + +Space Ansicht ( Drehung ) zurücksetzen + +Kurze Übersicht der Welt: + Die Anwendung wird gestartet, mit der Kamera von vorne auf den Cube gerichtet. + Der Cube bleibt im World-Space ohne Veränderungen. + Die Kamera dreht sich um den Cube herum. + Die Lichtquelle leuchtet von unten links und von vorne. + +Features: + - Logische Repräsentation + - Tastatursteuerung + - + Animations + - Kamerasteuerung + - Maussteuerung Kamerarelativ + - + Animations + + + +Rendering: + - Directional Lighting + - Blinn-Shading + - Diffuse Texturierung + - Normal Map mit Textur als Normal + - Specular Map