459 lines
15 KiB
C++
459 lines
15 KiB
C++
|
#pragma once
|
||
|
|
||
|
#include "../RubixSimulation.hpp"
|
||
|
#include <raylib.h>
|
||
|
#include <raymath.h>
|
||
|
#include <rlgl.h>
|
||
|
|
||
|
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
|