Cuber/Sims/OOPCube.hpp

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