imwithye commited on
Commit
7cefe36
·
1 Parent(s): f3bc628

add scramble

Browse files
src/components/canvas.tsx CHANGED
@@ -1,14 +1,17 @@
1
  "use client";
2
 
3
- import { useControls, Leva } from "leva";
4
  import { Canvas as ThreeCanvas } from "@react-three/fiber";
5
 
6
  import { Env } from "../components/env";
7
- import { RubiksCube } from "./rubiks-cube";
8
- import { useState, useTransition } from "react";
9
  import { PresetsType } from "@react-three/drei/helpers/environment-assets";
 
10
 
11
  export const Canvas = () => {
 
 
12
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
13
  const [_, startTransition] = useTransition();
14
  const [background, setBackground] = useState<PresetsType>("sunset");
@@ -21,13 +24,24 @@ export const Canvas = () => {
21
  options: ["sunset", "dawn", "forest"],
22
  onChange: (value) => startTransition(() => setBackground(value)),
23
  },
 
 
 
 
 
 
 
24
  });
25
 
26
  return (
27
  <>
28
  <Leva />
29
  <ThreeCanvas shadows camera={{ position: [0, 0, 4.5], fov: 50 }}>
30
- <RubiksCube cubeRoughness={cubeRoughness} cubeSpeed={cubeSpeed} />
 
 
 
 
31
  <Env background={background} />
32
  </ThreeCanvas>
33
  </>
 
1
  "use client";
2
 
3
+ import { useControls, Leva, button } from "leva";
4
  import { Canvas as ThreeCanvas } from "@react-three/fiber";
5
 
6
  import { Env } from "../components/env";
7
+ import { RubiksCube, RubiksCubeRef } from "./rubiks-cube";
8
+ import { useRef, useState, useTransition } from "react";
9
  import { PresetsType } from "@react-three/drei/helpers/environment-assets";
10
+ import { Actions } from "./consts";
11
 
12
  export const Canvas = () => {
13
+ const rubiksCubeRef = useRef<RubiksCubeRef>(null);
14
+
15
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
16
  const [_, startTransition] = useTransition();
17
  const [background, setBackground] = useState<PresetsType>("sunset");
 
24
  options: ["sunset", "dawn", "forest"],
25
  onChange: (value) => startTransition(() => setBackground(value)),
26
  },
27
+ Scramble: button(() => {
28
+ const scrambleSteps = Array.from(
29
+ { length: 20 },
30
+ () => Actions[Math.floor(Math.random() * Actions.length)],
31
+ );
32
+ rubiksCubeRef.current?.scramble(scrambleSteps);
33
+ }),
34
  });
35
 
36
  return (
37
  <>
38
  <Leva />
39
  <ThreeCanvas shadows camera={{ position: [0, 0, 4.5], fov: 50 }}>
40
+ <RubiksCube
41
+ ref={rubiksCubeRef}
42
+ cubeRoughness={cubeRoughness}
43
+ cubeSpeed={cubeSpeed}
44
+ />
45
  <Env background={background} />
46
  </ThreeCanvas>
47
  </>
src/components/rotator.tsx CHANGED
@@ -2,7 +2,13 @@ import { useCubesContext } from "@/contexts/cubes-context";
2
  import { FacingDirection } from "./consts";
3
  import { RotationPanel } from "./rotation-panel";
4
  import { Group } from "three";
5
- import { Fragment, useRef } from "react";
 
 
 
 
 
 
6
  import { useFrame } from "@react-three/fiber";
7
 
8
  type RotateArgs = {
@@ -11,117 +17,174 @@ type RotateArgs = {
11
  rotatingGroup: Group;
12
  };
13
 
 
 
 
 
 
 
 
 
 
14
  type RotatorProps = {
15
  cubeSpeed: number;
16
  };
17
 
18
- export const Rotator = ({ cubeSpeed }: RotatorProps) => {
19
- const { getCubes, cubeGroupRef } = useCubesContext();
20
- const isRotating = useRef(false);
21
- const rotateArgs = useRef<RotateArgs>({
22
- rotatingFaceDirection: "front",
23
- rotatingDirection: "clockwise",
24
- rotatingGroup: new Group(),
25
- });
 
26
 
27
- useFrame((state, delta) => {
28
- const { rotatingFaceDirection, rotatingDirection, rotatingGroup } =
29
- rotateArgs.current;
30
- const cubeGroup = cubeGroupRef.current;
31
- if (!isRotating.current || !cubeGroup) return;
 
 
 
 
 
 
 
32
 
33
- let sign = 0;
34
- switch (rotatingFaceDirection) {
35
- case "front":
36
- sign = rotatingDirection === "clockwise" ? -1 : 1;
37
- rotatingGroup.rotation.z += sign * delta * cubeSpeed;
38
- if (Math.abs(rotatingGroup.rotation.z) > Math.PI / 2) {
39
- rotatingGroup.rotation.z = (Math.PI / 2) * sign;
40
- isRotating.current = false;
41
- }
42
- break;
43
- case "back":
44
- sign = rotatingDirection === "clockwise" ? 1 : -1;
45
- rotatingGroup.rotation.z += sign * delta * cubeSpeed;
46
- if (Math.abs(rotatingGroup.rotation.z) > Math.PI / 2) {
47
- rotatingGroup.rotation.z = (Math.PI / 2) * sign;
48
- isRotating.current = false;
49
- }
50
- break;
51
- case "left":
52
- sign = rotatingDirection === "clockwise" ? 1 : -1;
53
- rotatingGroup.rotation.x += sign * delta * cubeSpeed;
54
- if (Math.abs(rotatingGroup.rotation.x) > Math.PI / 2) {
55
- rotatingGroup.rotation.x = (Math.PI / 2) * sign;
56
- isRotating.current = false;
57
- }
58
- break;
59
- case "right":
60
- sign = rotatingDirection === "clockwise" ? -1 : 1;
61
- rotatingGroup.rotation.x += sign * delta * cubeSpeed;
62
- if (Math.abs(rotatingGroup.rotation.x) > Math.PI / 2) {
63
- rotatingGroup.rotation.x = (Math.PI / 2) * sign;
64
- isRotating.current = false;
65
- }
66
- break;
67
- case "top":
68
- sign = rotatingDirection === "clockwise" ? -1 : 1;
69
- rotatingGroup.rotation.y += sign * delta * cubeSpeed;
70
- if (Math.abs(rotatingGroup.rotation.y) > Math.PI / 2) {
71
- rotatingGroup.rotation.y = (Math.PI / 2) * sign;
72
- isRotating.current = false;
73
- }
74
- break;
75
- case "bottom":
76
- sign = rotatingDirection === "clockwise" ? 1 : -1;
77
- rotatingGroup.rotation.y += sign * delta * cubeSpeed;
78
- if (Math.abs(rotatingGroup.rotation.y) > Math.PI / 2) {
79
- rotatingGroup.rotation.y = (Math.PI / 2) * sign;
80
- isRotating.current = false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  }
82
- break;
83
- }
84
 
85
- if (isRotating.current) return;
86
- const children = [...rotatingGroup.children];
87
- children.forEach((child) => cubeGroup.attach(child));
88
- cubeGroup.remove(rotatingGroup);
89
- });
 
 
 
 
90
 
91
- const handleClick = (
92
- facingDirection: FacingDirection,
93
- direction: "clockwise" | "counter-clockwise",
94
- ) => {
95
- if (isRotating.current || !cubeGroupRef.current) return;
96
- const cubes = getCubes(facingDirection);
97
 
98
- rotateArgs.current.rotatingFaceDirection = facingDirection;
99
- rotateArgs.current.rotatingDirection = direction;
100
- rotateArgs.current.rotatingGroup = new Group();
101
- cubeGroupRef.current.add(rotateArgs.current.rotatingGroup);
102
- cubes.forEach((cube) => rotateArgs.current.rotatingGroup.attach(cube));
 
103
 
104
- isRotating.current = true;
105
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
 
107
- return (
108
- <>
109
- {["front", "back", "left", "right", "top", "bottom"].map(
110
- (facingDirection) => (
111
- <Fragment key={facingDirection}>
112
- <RotationPanel
113
- direction="clockwise"
114
- facingDirection={facingDirection as FacingDirection}
115
- onClick={handleClick}
116
- />
117
- <RotationPanel
118
- direction="counter-clockwise"
119
- facingDirection={facingDirection as FacingDirection}
120
- onClick={handleClick}
121
- />
122
- </Fragment>
123
- ),
124
- )}
125
- </>
126
- );
127
- };
 
2
  import { FacingDirection } from "./consts";
3
  import { RotationPanel } from "./rotation-panel";
4
  import { Group } from "three";
5
+ import {
6
+ forwardRef,
7
+ Fragment,
8
+ useImperativeHandle,
9
+ useMemo,
10
+ useRef,
11
+ } from "react";
12
  import { useFrame } from "@react-three/fiber";
13
 
14
  type RotateArgs = {
 
17
  rotatingGroup: Group;
18
  };
19
 
20
+ export type RotatorRef = {
21
+ rotate: (
22
+ steps: Array<{
23
+ faceDirection: FacingDirection;
24
+ direction: "clockwise" | "counter-clockwise";
25
+ }>,
26
+ ) => void;
27
+ };
28
+
29
  type RotatorProps = {
30
  cubeSpeed: number;
31
  };
32
 
33
+ export const Rotator = forwardRef<RotatorRef, RotatorProps>(
34
+ ({ cubeSpeed }: RotatorProps, ref) => {
35
+ const { getCubes, cubeGroupRef } = useCubesContext();
36
+ const rotationSteps = useRef<
37
+ Array<{
38
+ faceDirection: FacingDirection;
39
+ direction: "clockwise" | "counter-clockwise";
40
+ }>
41
+ >([]);
42
 
43
+ useImperativeHandle(ref, () => ({
44
+ rotate: (
45
+ steps: Array<{
46
+ faceDirection: FacingDirection;
47
+ direction: "clockwise" | "counter-clockwise";
48
+ }>,
49
+ ) => {
50
+ steps.forEach((step) => {
51
+ rotationSteps.current.push(step);
52
+ });
53
+ },
54
+ }));
55
 
56
+ const rotate = (
57
+ rotatingFaceDirection: FacingDirection,
58
+ rotatingDirection: "clockwise" | "counter-clockwise",
59
+ rotatingGroup: Group,
60
+ delta: number,
61
+ ) => {
62
+ let sign = 0;
63
+ switch (rotatingFaceDirection) {
64
+ case "front":
65
+ sign = rotatingDirection === "clockwise" ? -1 : 1;
66
+ rotatingGroup.rotation.z += sign * delta * cubeSpeed;
67
+ if (Math.abs(rotatingGroup.rotation.z) > Math.PI / 2) {
68
+ rotatingGroup.rotation.z = (Math.PI / 2) * sign;
69
+ return true;
70
+ }
71
+ return false;
72
+ case "back":
73
+ sign = rotatingDirection === "clockwise" ? 1 : -1;
74
+ rotatingGroup.rotation.z += sign * delta * cubeSpeed;
75
+ if (Math.abs(rotatingGroup.rotation.z) > Math.PI / 2) {
76
+ rotatingGroup.rotation.z = (Math.PI / 2) * sign;
77
+ return true;
78
+ }
79
+ return false;
80
+ case "left":
81
+ sign = rotatingDirection === "clockwise" ? 1 : -1;
82
+ rotatingGroup.rotation.x += sign * delta * cubeSpeed;
83
+ if (Math.abs(rotatingGroup.rotation.x) > Math.PI / 2) {
84
+ rotatingGroup.rotation.x = (Math.PI / 2) * sign;
85
+ return true;
86
+ }
87
+ return false;
88
+ case "right":
89
+ sign = rotatingDirection === "clockwise" ? -1 : 1;
90
+ rotatingGroup.rotation.x += sign * delta * cubeSpeed;
91
+ if (Math.abs(rotatingGroup.rotation.x) > Math.PI / 2) {
92
+ rotatingGroup.rotation.x = (Math.PI / 2) * sign;
93
+ return true;
94
+ }
95
+ return false;
96
+ case "top":
97
+ sign = rotatingDirection === "clockwise" ? -1 : 1;
98
+ rotatingGroup.rotation.y += sign * delta * cubeSpeed;
99
+ if (Math.abs(rotatingGroup.rotation.y) > Math.PI / 2) {
100
+ rotatingGroup.rotation.y = (Math.PI / 2) * sign;
101
+ return true;
102
+ }
103
+ return false;
104
+ case "bottom":
105
+ sign = rotatingDirection === "clockwise" ? 1 : -1;
106
+ rotatingGroup.rotation.y += sign * delta * cubeSpeed;
107
+ if (Math.abs(rotatingGroup.rotation.y) > Math.PI / 2) {
108
+ rotatingGroup.rotation.y = (Math.PI / 2) * sign;
109
+ return true;
110
+ }
111
+ return false;
112
+ }
113
+ };
114
+
115
+ const frameCallback = useMemo(() => {
116
+ let rotationStep: {
117
+ faceDirection: FacingDirection;
118
+ direction: "clockwise" | "counter-clockwise";
119
+ } | null = null;
120
+ let rotatingGroup = new Group();
121
+
122
+ const beforeRotationStep = (step: {
123
+ faceDirection: FacingDirection;
124
+ direction: "clockwise" | "counter-clockwise";
125
+ }) => {
126
+ const cubes = getCubes(step.faceDirection);
127
+ rotationStep = step;
128
+ rotatingGroup = new Group();
129
+ cubeGroupRef.current?.add(rotatingGroup);
130
+ cubes.forEach((cube) => rotatingGroup.attach(cube));
131
+ };
132
+ const afterRotationStep = () => {
133
+ rotationStep = null;
134
+ const children = [...rotatingGroup.children];
135
+ children.forEach((child) => cubeGroupRef.current?.attach(child));
136
+ cubeGroupRef.current?.remove(rotatingGroup);
137
+ };
138
+
139
+ return (state: unknown, delta: number) => {
140
+ if (!cubeGroupRef.current) return;
141
+ if (rotationSteps.current.length === 0 && !rotationStep) return;
142
+ if (!rotationStep) {
143
+ const step = rotationSteps.current.shift();
144
+ if (!step) return;
145
+ beforeRotationStep(step);
146
  }
 
 
147
 
148
+ const done = rotate(
149
+ rotationStep?.faceDirection!,
150
+ rotationStep?.direction!,
151
+ rotatingGroup,
152
+ delta,
153
+ );
154
+ if (done) afterRotationStep();
155
+ };
156
+ }, []);
157
 
158
+ useFrame(frameCallback);
 
 
 
 
 
159
 
160
+ const handleClick = (
161
+ facingDirection: FacingDirection,
162
+ direction: "clockwise" | "counter-clockwise",
163
+ ) => {
164
+ rotationSteps.current.push({ faceDirection: facingDirection, direction });
165
+ };
166
 
167
+ return (
168
+ <>
169
+ {["front", "back", "left", "right", "top", "bottom"].map(
170
+ (facingDirection) => (
171
+ <Fragment key={facingDirection}>
172
+ <RotationPanel
173
+ direction="clockwise"
174
+ facingDirection={facingDirection as FacingDirection}
175
+ onClick={handleClick}
176
+ />
177
+ <RotationPanel
178
+ direction="counter-clockwise"
179
+ facingDirection={facingDirection as FacingDirection}
180
+ onClick={handleClick}
181
+ />
182
+ </Fragment>
183
+ ),
184
+ )}
185
+ </>
186
+ );
187
+ },
188
+ );
189
 
190
+ Rotator.displayName = "Rotator";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/components/rubiks-cube.tsx CHANGED
@@ -1,8 +1,9 @@
1
- import { useRef } from "react";
2
  import { CubePiece } from "./cube-piece";
3
- import { Rotator } from "./rotator";
4
  import { CubesProvider } from "@/contexts/cubes-context";
5
  import { Group } from "three";
 
6
 
7
  const CUBE_POSITIONS: Array<[number, number, number]> = [];
8
  for (let x = -0.5; x <= 0.5; x += 1) {
@@ -13,25 +14,49 @@ for (let x = -0.5; x <= 0.5; x += 1) {
13
  }
14
  }
15
 
 
 
 
 
 
 
 
 
 
16
  type RubiksCubeProps = {
17
  cubeRoughness: number;
18
  cubeSpeed: number;
19
  };
20
 
21
- export const RubiksCube = ({ cubeRoughness, cubeSpeed }: RubiksCubeProps) => {
22
- const cubeGroupRef = useRef<Group | null>(null);
23
- return (
24
- <CubesProvider cubeGroupRef={cubeGroupRef}>
25
- <group ref={cubeGroupRef}>
26
- {CUBE_POSITIONS.map((position) => (
27
- <CubePiece
28
- key={position.join(",")}
29
- initialPosition={position}
30
- roughness={cubeRoughness}
31
- />
32
- ))}
33
- </group>
34
- <Rotator cubeSpeed={cubeSpeed} />
35
- </CubesProvider>
36
- );
37
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useRef, forwardRef, useImperativeHandle } from "react";
2
  import { CubePiece } from "./cube-piece";
3
+ import { Rotator, RotatorRef } from "./rotator";
4
  import { CubesProvider } from "@/contexts/cubes-context";
5
  import { Group } from "three";
6
+ import { FacingDirection } from "./consts";
7
 
8
  const CUBE_POSITIONS: Array<[number, number, number]> = [];
9
  for (let x = -0.5; x <= 0.5; x += 1) {
 
14
  }
15
  }
16
 
17
+ export type RubiksCubeRef = {
18
+ scramble: (
19
+ steps: Array<{
20
+ faceDirection: FacingDirection;
21
+ direction: "clockwise" | "counter-clockwise";
22
+ }>,
23
+ ) => void;
24
+ };
25
+
26
  type RubiksCubeProps = {
27
  cubeRoughness: number;
28
  cubeSpeed: number;
29
  };
30
 
31
+ export const RubiksCube = forwardRef<RubiksCubeRef, RubiksCubeProps>(
32
+ ({ cubeRoughness, cubeSpeed }, ref) => {
33
+ const cubeGroupRef = useRef<Group | null>(null);
34
+ const rotatorRef = useRef<RotatorRef | null>(null);
35
+
36
+ useImperativeHandle(ref, () => ({
37
+ scramble: (
38
+ steps: Array<{
39
+ faceDirection: FacingDirection;
40
+ direction: "clockwise" | "counter-clockwise";
41
+ }>,
42
+ ) => rotatorRef.current?.rotate(steps),
43
+ }));
44
+
45
+ return (
46
+ <CubesProvider cubeGroupRef={cubeGroupRef}>
47
+ <group ref={cubeGroupRef}>
48
+ {CUBE_POSITIONS.map((position) => (
49
+ <CubePiece
50
+ key={position.join(",")}
51
+ initialPosition={position}
52
+ roughness={cubeRoughness}
53
+ />
54
+ ))}
55
+ </group>
56
+ <Rotator ref={rotatorRef} cubeSpeed={cubeSpeed} />
57
+ </CubesProvider>
58
+ );
59
+ },
60
+ );
61
+
62
+ RubiksCube.displayName = "RubiksCube";