Skip to content

Commit 6c78c60

Browse files
committed
Update: modularised the three.js code
1 parent 3f9cd3c commit 6c78c60

12 files changed

Lines changed: 758 additions & 12 deletions

File tree

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,48 @@
1-
import { useEffect, useRef } from "react";
2-
import { main } from "./solarsystem.js";
31
import styles from "./Home.module.css";
2+
import {useState, useEffect, useRef} from "react";
3+
import LevelComponent from "../../components/LevelComponent";
4+
// import { main } from "./solarsystem.js";
5+
import { main } from "./js/main.js";
46

5-
function Home() {
6-
7+
export default function Home() {
8+
const lvlNum = 4;
79
const canvasRef = useRef<HTMLCanvasElement | null>(null);
810

911
useEffect(() => {
1012

1113
if (!canvasRef.current) return;
1214

1315
main(canvasRef.current);
16+
}, []);
1417

15-
}, []);
18+
const [isVisible, setIsVisible] = useState(false);
19+
const [mountKey, setMountKey] = useState(0);
20+
21+
const handleOpen = () => {
22+
setMountKey(k => k + 1);
23+
setIsVisible(true);
24+
};
1625

1726
return (
18-
<>
27+
<div>
28+
<button className="ml-100 mt-80 absolute border-4 rounded-3xl p-5" onClick={handleOpen} disabled={isVisible}>
29+
Glorp
30+
</button>
31+
{isVisible && (
32+
<LevelComponent
33+
key={mountKey}
34+
onClose={() => setIsVisible(false)}
35+
lvl={lvlNum}
36+
/>
37+
)}
38+
1939
<header>
2040
<div className={styles["header-brand"]}>
21-
<h1>Glorpython</h1>
41+
<h1>glorpython</h1>
2242
</div>
2343
</header>
44+
2445
<canvas ref={canvasRef} className={styles.solarsystem}></canvas>
25-
</>
46+
</div>
2647
);
27-
}
28-
29-
export default Home;
48+
}

src/pages/home/.ipynb_checkpoints/solarsystem-checkpoint.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ export function main(canvas) {
5454
];
5555

5656
const planets = planetData.flatMap(d => {
57+
58+
5759
const pts = [];
5860
for (let i = 0; i <= 128; i++) {
5961
const a = (i / 128) * Math.PI * 2;
@@ -101,6 +103,10 @@ export function main(canvas) {
101103
scene.add(new THREE.Points(starGeo, starMat));
102104
}
103105

106+
107+
108+
109+
104110
// Mouse parallax
105111
let targetOffset = { x: 0, y: 0 };
106112
let currentOffset = { x: 0, y: 0 };

src/pages/home/Home.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import styles from "./Home.module.css";
22
import {useState, useEffect, useRef} from "react";
33
import LevelComponent from "../../components/LevelComponent";
4-
import { main } from "./solarsystem.js";
4+
// import { main } from "./solarsystem.js";
5+
import { main } from "./js/main.js";
56

67
export default function Home() {
78
const lvlNum = 4;
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import * as THREE from "three";
2+
3+
export function createCamera( canvas ) {
4+
5+
const fov = 45;
6+
const aspect = canvas.clientWidth / canvas.clientHeight;
7+
const near = 0.1;
8+
const far = 500;
9+
const basePosition = new THREE.Vector3(0, 40, 70);
10+
11+
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
12+
13+
camera.position.copy(basePosition);
14+
15+
return { camera, basePosition };
16+
17+
}
18+
19+
export function setupParallax( canvas ) {
20+
21+
// Mouse parallax
22+
const targetOffset = { x: 0, y: 0 };
23+
const currentOffset = { x: 0, y: 0 };
24+
25+
window.addEventListener('mousemove', e => {
26+
const rect = canvas.getBoundingClientRect();
27+
const nx = (e.clientX - rect.left) / rect.width - 0.5;
28+
const ny = (e.clientY - rect.top) / rect.height - 0.5;
29+
targetOffset.x = nx * 20;
30+
targetOffset.y = -ny * 20;
31+
});
32+
33+
window.addEventListener('mouseleave', () => {
34+
targetOffset.x = 0;
35+
targetOffset.y = 0;
36+
});
37+
38+
return { targetOffset, currentOffset };
39+
40+
}
41+
42+
export function setupFocus( canvas, camera, planets, basePosition ) {
43+
44+
const state = {
45+
raycaster: new THREE.Raycaster(),
46+
pointer: new THREE.Vector2(),
47+
paused: false,
48+
focusedPlanet: null,
49+
cameraTarget: null,
50+
lookAtTarget: null,
51+
tweening: false,
52+
};
53+
54+
canvas.addEventListener('click', e => {
55+
const rect = canvas.getBoundingClientRect();
56+
state.pointer.x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
57+
state.pointer.y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
58+
state.raycaster.setFromCamera(state.pointer, camera);
59+
60+
const meshes = planets.map(p => p.mesh);
61+
const hits = state.raycaster.intersectObjects(meshes);
62+
63+
if (hits.length > 0) {
64+
state.focusedPlanet = planets.find(p => p.mesh === hits[0].object);
65+
state.paused = true;
66+
state.tweening = true;
67+
}
68+
else {
69+
state.focusedPlanet = null;
70+
state.paused = false;
71+
state.tweening = true;
72+
state.cameraTarget = basePosition.clone();
73+
state.lookAtTarget = new THREE.Vector3(0, 0, 0);
74+
}
75+
});
76+
77+
return state;
78+
79+
}
80+
81+
82+
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import * as THREE from "three";
2+
import { createRenderer } from "./renderer.js";
3+
import { createCamera, setupParallax, setupFocus } from "./camera.js";
4+
import { createScene } from "./scene.js";
5+
6+
const ZOOM_DIST = 5;
7+
const TWEEN_SPEED = 0.07;
8+
9+
export function main( canvas ) {
10+
11+
// --- Initialisation ---
12+
13+
const renderer = createRenderer( canvas );
14+
15+
const { camera, basePosition } = createCamera( canvas );
16+
17+
const { scene, sun, planets } = createScene();
18+
19+
const { targetOffset, currentOffset } = setupParallax( canvas );
20+
21+
const focus = setupFocus( canvas, camera, planets, basePosition );
22+
23+
const currentLookAt = new THREE.Vector3(0, 0, 0);
24+
const origin = new THREE.Vector3(0, 0, 0);
25+
26+
27+
28+
// --- Render loop ---
29+
30+
requestAnimationFrame(render);
31+
32+
function render() {
33+
34+
updateScene();
35+
updateCamera();
36+
handleResize();
37+
38+
renderer.render(scene, camera);
39+
requestAnimationFrame(render);
40+
41+
42+
43+
// --- Done! (the rest is defining these functions) ---
44+
45+
46+
47+
// Update the sun and planet motions, if not paused
48+
function updateScene() {
49+
50+
rotateSun()
51+
advancePlanetOrbits()
52+
53+
function rotateSun() {
54+
if (!focus.paused) sun.rotation.y += 0.003;
55+
}
56+
57+
function advancePlanetOrbits() {
58+
planets.forEach(p => {
59+
if (!focus.paused) p.angle += p.speed * 0.008;
60+
61+
p.mesh.position.set(
62+
Math.cos(p.angle) * p.orbitR,
63+
Math.sin(p.angle) * p.tilt * p.orbitR * 0.18,
64+
Math.sin(p.angle) * p.orbitR
65+
);
66+
p.mesh.rotation.y += 0.01;
67+
});
68+
}
69+
70+
}
71+
72+
// Update the camera by tweening if needed, otherwise (unless paused, which is part of tweeening) do the parallax thing
73+
function updateCamera() {
74+
75+
updateFocusTarget();
76+
77+
if (focus.tweening && focus.cameraTarget && focus.lookAtTarget) {
78+
updateCameraTween();
79+
}
80+
else if (!focus.paused) {
81+
updateCameraParallax();
82+
}
83+
84+
function updateFocusTarget() {
85+
86+
if (!focus.focusedPlanet) return;
87+
88+
const planetPosition = focus.focusedPlanet.mesh.position;
89+
const planetDirection = planetPosition.clone().normalize();
90+
91+
focus.cameraTarget = planetPosition.clone().add(planetDirection.multiplyScalar(focus.focusedPlanet.radius + ZOOM_DIST));
92+
focus.cameraTarget.y += focus.focusedPlanet.radius * 0.8;
93+
focus.lookAtTarget = planetPosition.clone();
94+
95+
}
96+
97+
function updateCameraTween () {
98+
99+
camera.position.lerp(focus.cameraTarget, TWEEN_SPEED);
100+
currentLookAt.lerp(focus.lookAtTarget, TWEEN_SPEED);
101+
camera.lookAt(currentLookAt);
102+
103+
if (camera.position.distanceTo(focus.cameraTarget) < 0.05) {
104+
focus.tweening = false;
105+
};
106+
107+
}
108+
109+
function updateCameraParallax() {
110+
111+
currentOffset.x += (targetOffset.x - currentOffset.x) * 0.13;
112+
currentOffset.y += (targetOffset.y - currentOffset.y) * 0.13;
113+
114+
camera.position.set(
115+
basePosition.x + currentOffset.x,
116+
basePosition.y + currentOffset.y,
117+
basePosition.z
118+
);
119+
120+
currentLookAt.lerp(origin, 0.1);
121+
camera.lookAt(currentLookAt);
122+
123+
}
124+
}
125+
126+
// Resize the canvas dimensions change, then update the camera so things don't get squashed
127+
function handleResize() {
128+
129+
if (resizeRendererToDisplaySize( canvas, renderer )) {
130+
camera.aspect = canvas.clientWidth / canvas.clientHeight;
131+
camera.updateProjectionMatrix();
132+
}
133+
134+
function resizeRendererToDisplaySize( canvas, renderer ) {
135+
136+
const width = canvas.clientWidth;
137+
const height = canvas.clientHeight;
138+
const needResize = canvas.width !== width || canvas.height !== height;
139+
140+
if (needResize) {
141+
renderer.setSize(width, height, false);
142+
}
143+
144+
return needResize;
145+
146+
}
147+
148+
}
149+
150+
}
151+
152+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import * as THREE from "three";
2+
3+
export function createRenderer( canvas ) {
4+
5+
const renderer = new THREE.WebGLRenderer({
6+
antialias: true,
7+
canvas
8+
});
9+
renderer.setPixelRatio(window.devicePixelRatio);
10+
renderer.toneMapping = THREE.ACESFilmicToneMapping;
11+
renderer.toneMappingExposure = 2.0;
12+
13+
return renderer
14+
15+
}

0 commit comments

Comments
 (0)