#pragma once #include "../RubixSimulation.hpp" #include #include #include namespace OOPCube { struct VecTriangle { Vector3 pointOne; Vector3 pointTwo; Vector3 pointThree; }; struct Plane3D { VecTriangle one; VecTriangle two; Color color; }; struct CubeFace { Plane3D tiles[9]; // 3x3 tiles }; struct CubeRotation { bool rotating; float angle; Vector3 axis; CubeFace *face; }; struct RubixCubeFaces { CubeFace front; CubeFace right; CubeFace back; CubeFace left; CubeFace top; CubeFace bottom; }; class RubixCube { public: RubixCube() { float cubeSize = 3.0f; float faceOffset = cubeSize / 2.0f; faces.front = CreateCubeFace({0.0f, 0.0f, -faceOffset}, {0.0f, 0.0f, 1.0f}, cubeSize, GREEN); faces.back = CreateCubeFace({0.0f, 0.0f, faceOffset}, {0.0f, 0.0f, -1.0f}, cubeSize, BLUE); faces.left = CreateCubeFace({-faceOffset, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, cubeSize, ORANGE); faces.right = CreateCubeFace({faceOffset, 0.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}, cubeSize, RED); faces.top = CreateCubeFace({0.0f, faceOffset, 0.0f}, {0.0f, -1.0f, 0.0f}, cubeSize, YELLOW); faces.bottom = CreateCubeFace({0.0f, -faceOffset, 0.0f}, {0.0f, 1.0f, 0.0f}, cubeSize, WHITE); } void Draw(bool highlightFace = false, CubeFace *highlightedFace = nullptr) { if (highlightFace && highlightedFace != nullptr) { HighlightFace(highlightedFace); } DrawFace(&faces.front); DrawFace(&faces.back); DrawFace(&faces.left); DrawFace(&faces.right); DrawFace(&faces.top); DrawFace(&faces.bottom); } void RotateFace(CubeFace *face, float angle, Vector3 axis) { CubeFace *neighbors[4]; // Holds adjacent faces GetFaceNeighbors(face, neighbors); // Get adjacent faces RotateLayer(face, angle, axis, neighbors); // Rotate both face and neighbors UpdateCubeState(face, neighbors, angle > 0); // Update the state after rotation } CubeFace *GetFaceUnderMouse(Camera camera) { Ray mouseRay = GetMouseRay(GetMousePosition(), camera); CubeFace *face = nullptr; if (CheckRayIntersectionWithFace(mouseRay, faces.front)) { face = &faces.front; } else if (CheckRayIntersectionWithFace(mouseRay, faces.back)) { face = &faces.back; } else if (CheckRayIntersectionWithFace(mouseRay, faces.left)) { face = &faces.left; } else if (CheckRayIntersectionWithFace(mouseRay, faces.right)) { face = &faces.right; } else if (CheckRayIntersectionWithFace(mouseRay, faces.top)) { face = &faces.top; } else if (CheckRayIntersectionWithFace(mouseRay, faces.bottom)) { face = &faces.bottom; } return face; } private: RubixCubeFaces faces; void GetFaceNeighbors(CubeFace *face, CubeFace **neighbors) { // Map the neighbors of each face (order: top, right, bottom, left) if (face == &faces.front) { neighbors[0] = &faces.top; neighbors[1] = &faces.right; neighbors[2] = &faces.bottom; neighbors[3] = &faces.left; } else if (face == &faces.back) { neighbors[0] = &faces.top; neighbors[1] = &faces.left; neighbors[2] = &faces.bottom; neighbors[3] = &faces.right; } else if (face == &faces.left) { neighbors[0] = &faces.top; neighbors[1] = &faces.front; neighbors[2] = &faces.bottom; neighbors[3] = &faces.back; } else if (face == &faces.right) { neighbors[0] = &faces.top; neighbors[1] = &faces.back; neighbors[2] = &faces.bottom; neighbors[3] = &faces.front; } else if (face == &faces.top) { neighbors[0] = &faces.back; neighbors[1] = &faces.right; neighbors[2] = &faces.front; neighbors[3] = &faces.left; } else if (face == &faces.bottom) { neighbors[0] = &faces.front; neighbors[1] = &faces.right; neighbors[2] = &faces.back; neighbors[3] = &faces.left; } } void RotateLayer(CubeFace *face, float angle, Vector3 axis, CubeFace **neighbors) { // Rotate the face itself RotateCubeFace(face, MatrixRotate(axis, angle)); // Apply rotation to the neighboring rows/columns RotateNeighbors(face, angle, neighbors); } void RotateNeighbors(CubeFace *rotatingFace, float angle, CubeFace **neighbors) { // Clockwise or counterclockwise based on angle bool clockwise = angle > 0.0f; // Determine the layer based on the rotating face. // If front/back, rotate the middle horizontal row; otherwise, rotate the // vertical columns. if (rotatingFace == &faces.front || rotatingFace == &faces.back) { // Rotating the front or back face (affects top, right, bottom, and left // face) UpdateCubeState(rotatingFace, neighbors, clockwise); } else if (rotatingFace == &faces.left || rotatingFace == &faces.right) { // Rotating left or right face (affects top, front, bottom, and back face) UpdateCubeState(rotatingFace, neighbors, clockwise); } else if (rotatingFace == &faces.top || rotatingFace == &faces.bottom) { // Rotating top or bottom face (affects front, right, back, and left face) UpdateCubeState(rotatingFace, neighbors, clockwise); } } void UpdateCubeState(CubeFace *face, CubeFace **neighbors, bool clockwise) { // Swapping tiles based on clockwise or counterclockwise rotation if (face == &faces.front || face == &faces.back) { // Swap rows for front or back face rotation if (clockwise) { // Rotate clockwise: top->right, right->bottom, bottom->left, left->top SwapRows(neighbors[0], neighbors[1], neighbors[2], neighbors[3], 2, true); } else { // Rotate counter-clockwise: top->left, left->bottom, bottom->right, // right->top SwapRows(neighbors[0], neighbors[3], neighbors[2], neighbors[1], 2, false); } } else if (face == &faces.left || face == &faces.right) { // Swap columns for left or right face rotation if (clockwise) { // Rotate clockwise: top->front, front->bottom, bottom->back, back->top SwapColumns(neighbors[0], neighbors[1], neighbors[2], neighbors[3], 0, true); } else { // Rotate counter-clockwise: top->back, back->bottom, bottom->front, // front->top SwapColumns(neighbors[0], neighbors[3], neighbors[2], neighbors[1], 0, false); } } else if (face == &faces.top || face == &faces.bottom) { // Swap rows for top or bottom face rotation if (clockwise) { // Rotate clockwise: front->right, right->back, back->left, left->front SwapRows(neighbors[0], neighbors[1], neighbors[2], neighbors[3], 0, true); } else { // Rotate counter-clockwise: front->left, left->back, back->right, // right->front SwapRows(neighbors[0], neighbors[3], neighbors[2], neighbors[1], 0, false); } } } // Helper function to swap rows (used when rotating top/bottom or front/back) void SwapRows(CubeFace *top, CubeFace *right, CubeFace *bottom, CubeFace *left, int rowIndex, bool clockwise) { Plane3D tempRow[3]; // Store top row temporarily for (int i = 0; i < 3; i++) { tempRow[i] = top->tiles[rowIndex * 3 + i]; } // Swap rows based on direction if (clockwise) { // Top -> Right -> Bottom -> Left -> Top for (int i = 0; i < 3; i++) { top->tiles[rowIndex * 3 + i] = left->tiles[rowIndex * 3 + i]; left->tiles[rowIndex * 3 + i] = bottom->tiles[rowIndex * 3 + i]; bottom->tiles[rowIndex * 3 + i] = right->tiles[rowIndex * 3 + i]; right->tiles[rowIndex * 3 + i] = tempRow[i]; } } else { // Top -> Left -> Bottom -> Right -> Top for (int i = 0; i < 3; i++) { top->tiles[rowIndex * 3 + i] = right->tiles[rowIndex * 3 + i]; right->tiles[rowIndex * 3 + i] = bottom->tiles[rowIndex * 3 + i]; bottom->tiles[rowIndex * 3 + i] = left->tiles[rowIndex * 3 + i]; left->tiles[rowIndex * 3 + i] = tempRow[i]; } } } void SwapColumns(CubeFace *top, CubeFace *front, CubeFace *bottom, CubeFace *back, int colIndex, bool clockwise) { Plane3D tempCol[3]; // Store top column temporarily for (int i = 0; i < 3; i++) { tempCol[i] = top->tiles[i * 3 + colIndex]; } // Swap columns based on direction if (clockwise) { // Top -> Front -> Bottom -> Back -> Top for (int i = 0; i < 3; i++) { top->tiles[i * 3 + colIndex] = back->tiles[i * 3 + colIndex]; back->tiles[i * 3 + colIndex] = bottom->tiles[i * 3 + colIndex]; bottom->tiles[i * 3 + colIndex] = front->tiles[i * 3 + colIndex]; front->tiles[i * 3 + colIndex] = tempCol[i]; } } else { // Top -> Back -> Bottom -> Front -> Top for (int i = 0; i < 3; i++) { top->tiles[i * 3 + colIndex] = front->tiles[i * 3 + colIndex]; front->tiles[i * 3 + colIndex] = bottom->tiles[i * 3 + colIndex]; bottom->tiles[i * 3 + colIndex] = back->tiles[i * 3 + colIndex]; back->tiles[i * 3 + colIndex] = tempCol[i]; } } } void RotateCubeFace(CubeFace *face, Matrix rotationMatrix) { for (int i = 0; i < 9; i++) { RotatePlane3D(&face->tiles[i], rotationMatrix); } } void RotatePlane3D(Plane3D *plane, Matrix rotationMatrix) { plane->one.pointOne = Vector3Transform(plane->one.pointOne, rotationMatrix); plane->one.pointTwo = Vector3Transform(plane->one.pointTwo, rotationMatrix); plane->one.pointThree = Vector3Transform(plane->one.pointThree, rotationMatrix); plane->two.pointOne = Vector3Transform(plane->two.pointOne, rotationMatrix); plane->two.pointTwo = Vector3Transform(plane->two.pointTwo, rotationMatrix); plane->two.pointThree = Vector3Transform(plane->two.pointThree, rotationMatrix); } void HighlightFace(CubeFace *face) { DrawFace(face, true); } void DrawFace(CubeFace *face, bool highlight = false) { Color faceColor = highlight ? LIGHTGRAY : face->tiles[0].color; for (int i = 0; i < 9; i++) { DrawPlane3D(&face->tiles[i]); } } bool CheckRayIntersectionWithFace(Ray ray, CubeFace face) { for (int i = 0; i < 9; i++) { if (CheckRayCollision(ray, face.tiles[i])) { return true; } } return false; } bool CheckRayCollision(Ray ray, Plane3D plane) { RayCollision collisionOne = GetRayCollisionTriangle( ray, plane.one.pointOne, plane.one.pointTwo, plane.one.pointThree); RayCollision collisionTwo = GetRayCollisionTriangle( ray, plane.two.pointOne, plane.two.pointTwo, plane.two.pointThree); return collisionOne.hit || collisionTwo.hit; } Plane3D CreatePlane3D(float width, float height, Vector3 position, Color color, Vector3 normal, float scale) { Plane3D plane3D; plane3D.color = color; Vector3 right, up; if (Vector3Equals(normal, {0.0f, 1.0f, 0.0f}) || Vector3Equals(normal, {0.0f, -1.0f, 0.0f})) { right = {1.0f, 0.0f, 0.0f}; up = {0.0f, 0.0f, (normal.y > 0.0f) ? -1.0f : 1.0f}; } else { right = {normal.z, 0.0f, -normal.x}; up = {0.0f, 1.0f, 0.0f}; } float scaledWidth = width * scale; float scaledHeight = height * scale; plane3D.one.pointOne = Vector3Add(position, Vector3Add(Vector3Scale(right, -scaledWidth / 2), Vector3Scale(up, -scaledHeight / 2))); plane3D.one.pointTwo = Vector3Add(position, Vector3Add(Vector3Scale(right, scaledWidth / 2), Vector3Scale(up, -scaledHeight / 2))); plane3D.one.pointThree = Vector3Add(position, Vector3Add(Vector3Scale(right, -scaledWidth / 2), Vector3Scale(up, scaledHeight / 2))); plane3D.two.pointOne = Vector3Add(position, Vector3Add(Vector3Scale(right, scaledWidth / 2), Vector3Scale(up, -scaledHeight / 2))); plane3D.two.pointTwo = Vector3Add(position, Vector3Add(Vector3Scale(right, scaledWidth / 2), Vector3Scale(up, scaledHeight / 2))); plane3D.two.pointThree = Vector3Add(position, Vector3Add(Vector3Scale(right, -scaledWidth / 2), Vector3Scale(up, scaledHeight / 2))); return plane3D; } CubeFace CreateCubeFace(Vector3 center, Vector3 normal, float cubeSize, Color faceColor) { CubeFace face; float tileSize = cubeSize / 3.0f; float halfSize = cubeSize / 2.0f; Vector3 right = Vector3CrossProduct(normal, {0.0f, 1.0f, 0.0f}); if (Vector3Length(right) == 0) { right = {1.0f, 0.0f, 0.0f}; } Vector3 up = Vector3CrossProduct(right, normal); Vector3 tileCenters[3][3]; for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { tileCenters[i][j] = Vector3Add( center, Vector3Add(Vector3Scale(right, (i - 1) * tileSize), Vector3Scale(up, (j - 1) * tileSize))); } } for (int i = 0; i < 9; i++) { face.tiles[i] = CreatePlane3D(tileSize, tileSize, tileCenters[i / 3][i % 3], faceColor, normal, 1.0f); } return face; } void DrawPlane3D(Plane3D *plane) { DrawTriangle3D(plane->one.pointOne, plane->one.pointTwo, plane->one.pointThree, plane->color); DrawTriangle3D(plane->two.pointOne, plane->two.pointTwo, plane->two.pointThree, plane->color); } }; class OOPCube : public RubixSimulation { public: bool Init() override { camera = {0}; camera.position = {4.0f, 4.0f, 4.0f}; camera.target = {0.0f, 0.0f, 0.0f}; camera.up = {0.0f, 1.0f, 0.0f}; camera.fovy = 45.0f; camera.projection = CAMERA_PERSPECTIVE; return true; } void Run() override { SetConfigFlags(FLAG_VSYNC_HINT); InitWindow(1920, 1080, "OOP Cube Simulation"); DisableCursor(); rlDisableBackfaceCulling(); bool isControllingCamera = false; while (!WindowShouldClose()) { if (IsKeyPressed(KEY_SPACE)) { isControllingCamera = !isControllingCamera; } if (isControllingCamera) { HideCursor(); UpdateCamera(&camera, CAMERA_FREE); SetMousePosition(GetScreenWidth() / 2, GetScreenHeight() / 2); } BeginDrawing(); ClearBackground(GRAY); ShowCursor(); BeginMode3D(camera); DrawGrid(10, 1.0f); CubeFace *faceUnderMouse = cube.GetFaceUnderMouse(camera); if (faceUnderMouse != nullptr) { if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) { cube.RotateFace(faceUnderMouse, PI / 2.0f, {0.0f, 1.0f, 0.0f}); } else if (IsMouseButtonPressed(MOUSE_RIGHT_BUTTON)) { cube.RotateFace(faceUnderMouse, -PI / 2.0f, {0.0f, 1.0f, 0.0f}); } cube.Draw(true, faceUnderMouse); } else { cube.Draw(); } EndMode3D(); EndDrawing(); } } private: Camera camera; RubixCube cube; }; } // namespace OOPCube