forked from pachoclo/vite-threejs-ts-template
-
Notifications
You must be signed in to change notification settings - Fork 0
/
scene.ts
268 lines (229 loc) · 7.7 KB
/
scene.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
import GUI from 'lil-gui'
import {
AmbientLight,
AxesHelper,
BoxGeometry,
Clock,
GridHelper,
LoadingManager,
Mesh,
MeshLambertMaterial,
MeshStandardMaterial,
PCFSoftShadowMap,
PerspectiveCamera,
PlaneGeometry,
PointLight,
PointLightHelper,
Scene,
WebGLRenderer,
} from 'three'
import { DragControls } from 'three/examples/jsm/controls/DragControls'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import Stats from 'three/examples/jsm/libs/stats.module'
import * as animations from './helpers/animations'
import { toggleFullScreen } from './helpers/fullscreen'
import { resizeRendererToDisplaySize } from './helpers/responsiveness'
import './style.css'
const CANVAS_ID = 'scene'
let canvas: HTMLElement
let renderer: WebGLRenderer
let scene: Scene
let loadingManager: LoadingManager
let ambientLight: AmbientLight
let pointLight: PointLight
let cube: Mesh
let camera: PerspectiveCamera
let cameraControls: OrbitControls
let dragControls: DragControls
let axesHelper: AxesHelper
let pointLightHelper: PointLightHelper
let clock: Clock
let stats: Stats
let gui: GUI
const animation = { enabled: false, play: true }
init()
animate()
function init() {
// ===== 🖼️ CANVAS, RENDERER, & SCENE =====
{
canvas = document.querySelector(`canvas#${CANVAS_ID}`)!
renderer = new WebGLRenderer({ canvas, antialias: true, alpha: true })
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
renderer.shadowMap.enabled = true
renderer.shadowMap.type = PCFSoftShadowMap
scene = new Scene()
}
// ===== 👨🏻💼 LOADING MANAGER =====
{
loadingManager = new LoadingManager()
loadingManager.onStart = () => {
console.log('loading started')
}
loadingManager.onProgress = (url, loaded, total) => {
console.log('loading in progress:')
console.log(`${url} -> ${loaded} / ${total}`)
}
loadingManager.onLoad = () => {
console.log('loaded!')
}
loadingManager.onError = () => {
console.log('❌ error while loading')
}
}
// ===== 💡 LIGHTS =====
{
ambientLight = new AmbientLight('white', 0.4)
pointLight = new PointLight('#ffdca8', 1.2, 100)
pointLight.position.set(-2, 3, 3)
pointLight.castShadow = true
pointLight.shadow.radius = 4
pointLight.shadow.camera.near = 0.5
pointLight.shadow.camera.far = 4000
pointLight.shadow.mapSize.width = 2048
pointLight.shadow.mapSize.height = 2048
scene.add(ambientLight)
scene.add(pointLight)
}
// ===== 📦 OBJECTS =====
{
const sideLength = 1
const cubeGeometry = new BoxGeometry(sideLength, sideLength, sideLength)
const cubeMaterial = new MeshStandardMaterial({
color: '#f69f1f',
metalness: 0.5,
roughness: 0.7,
})
cube = new Mesh(cubeGeometry, cubeMaterial)
cube.castShadow = true
cube.position.y = 0.5
const planeGeometry = new PlaneGeometry(3, 3)
const planeMaterial = new MeshLambertMaterial({
color: 'gray',
emissive: 'teal',
emissiveIntensity: 0.2,
side: 2,
transparent: true,
opacity: 0.4,
})
const plane = new Mesh(planeGeometry, planeMaterial)
plane.rotateX(Math.PI / 2)
plane.receiveShadow = true
scene.add(cube)
scene.add(plane)
}
// ===== 🎥 CAMERA =====
{
camera = new PerspectiveCamera(50, canvas.clientWidth / canvas.clientHeight, 0.1, 100)
camera.position.set(2, 2, 5)
}
// ===== 🕹️ CONTROLS =====
{
cameraControls = new OrbitControls(camera, canvas)
cameraControls.target = cube.position.clone()
cameraControls.enableDamping = true
cameraControls.autoRotate = false
cameraControls.update()
dragControls = new DragControls([cube], camera, renderer.domElement)
dragControls.addEventListener('hoveron', (event) => {
event.object.material.emissive.set('orange')
})
dragControls.addEventListener('hoveroff', (event) => {
event.object.material.emissive.set('black')
})
dragControls.addEventListener('dragstart', (event) => {
cameraControls.enabled = false
animation.play = false
event.object.material.emissive.set('black')
event.object.material.opacity = 0.7
event.object.material.needsUpdate = true
})
dragControls.addEventListener('dragend', (event) => {
cameraControls.enabled = true
animation.play = true
event.object.material.emissive.set('black')
event.object.material.opacity = 1
event.object.material.needsUpdate = true
})
dragControls.enabled = false
// Full screen
window.addEventListener('dblclick', (event) => {
if (event.target === canvas) {
toggleFullScreen(canvas)
}
})
}
// ===== 🪄 HELPERS =====
{
axesHelper = new AxesHelper(4)
axesHelper.visible = false
scene.add(axesHelper)
pointLightHelper = new PointLightHelper(pointLight, undefined, 'orange')
pointLightHelper.visible = false
scene.add(pointLightHelper)
const gridHelper = new GridHelper(20, 20, 'teal', 'darkgray')
gridHelper.position.y = -0.01
scene.add(gridHelper)
}
// ===== 📈 STATS & CLOCK =====
{
clock = new Clock()
stats = new Stats()
document.body.appendChild(stats.dom)
}
// ==== 🐞 DEBUG GUI ====
{
gui = new GUI({ title: '🐞 Debug GUI', width: 300 })
const cubeOneFolder = gui.addFolder('Cube one')
cubeOneFolder.add(cube.position, 'x').min(-5).max(5).step(0.5).name('pos x')
cubeOneFolder.add(cube.position, 'y').min(-5).max(5).step(0.5).name('pos y')
cubeOneFolder.add(cube.position, 'z').min(-5).max(5).step(0.5).name('pos z')
cubeOneFolder.add(cube.material, 'wireframe')
cubeOneFolder.addColor(cube.material, 'color')
cubeOneFolder.add(cube.material, 'metalness', 0, 1, 0.1)
cubeOneFolder.add(cube.material, 'roughness', 0, 1, 0.1)
cubeOneFolder.add(cube.rotation, 'x', -Math.PI * 2, Math.PI * 2, Math.PI / 4).name('rotate x')
cubeOneFolder.add(cube.rotation, 'y', -Math.PI * 2, Math.PI * 2, Math.PI / 4).name('rotate y')
cubeOneFolder.add(cube.rotation, 'z', -Math.PI * 2, Math.PI * 2, Math.PI / 4).name('rotate z')
cubeOneFolder.add(animation, 'enabled').name('animated')
const controlsFolder = gui.addFolder('Controls')
controlsFolder.add(dragControls, 'enabled').name('drag controls')
const lightsFolder = gui.addFolder('Lights')
lightsFolder.add(pointLight, 'visible').name('point light')
lightsFolder.add(ambientLight, 'visible').name('ambient light')
const helpersFolder = gui.addFolder('Helpers')
helpersFolder.add(axesHelper, 'visible').name('axes')
helpersFolder.add(pointLightHelper, 'visible').name('pointLight')
const cameraFolder = gui.addFolder('Camera')
cameraFolder.add(cameraControls, 'autoRotate')
// persist GUI state in local storage on changes
gui.onFinishChange(() => {
const guiState = gui.save()
localStorage.setItem('guiState', JSON.stringify(guiState))
})
// load GUI state if available in local storage
const guiState = localStorage.getItem('guiState')
if (guiState) gui.load(JSON.parse(guiState))
// reset GUI state button
const resetGui = () => {
localStorage.removeItem('guiState')
gui.reset()
}
gui.add({ resetGui }, 'resetGui').name('RESET')
gui.close()
}
}
function animate() {
requestAnimationFrame(animate)
stats.update()
if (animation.enabled && animation.play) {
animations.rotate(cube, clock, Math.PI / 3)
animations.bounce(cube, clock, 1, 0.5, 0.5)
}
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement
camera.aspect = canvas.clientWidth / canvas.clientHeight
camera.updateProjectionMatrix()
}
cameraControls.update()
renderer.render(scene, camera)
}