@tomorrowevening/hermes 0.0.35 → 0.0.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/hermes.cjs.js +144 -0
- package/dist/hermes.esm.js +3849 -0
- package/dist/hermes.umd.js +144 -0
- package/dist/style.css +1 -1
- package/package.json +20 -16
- package/src/core/Application.ts +111 -0
- package/src/core/RemoteController.ts +60 -0
- package/src/core/remote/BaseRemote.ts +16 -0
- package/src/core/remote/RemoteComponents.ts +45 -0
- package/src/core/remote/RemoteTheatre.ts +300 -0
- package/src/core/remote/RemoteThree.ts +143 -0
- package/src/core/remote/RemoteTweakpane.ts +194 -0
- package/src/core/types.ts +56 -0
- package/src/editor/Editor.tsx +20 -0
- package/src/editor/components/Draggable.tsx +40 -0
- package/src/editor/components/DraggableItem.tsx +22 -0
- package/src/editor/components/Dropdown.tsx +38 -0
- package/src/editor/components/DropdownItem.tsx +64 -0
- package/src/editor/components/NavButton.tsx +11 -0
- package/src/editor/components/content.ts +2 -0
- package/src/editor/components/icons/CloseIcon.tsx +7 -0
- package/src/editor/components/icons/DragIcon.tsx +9 -0
- package/src/editor/components/types.ts +41 -0
- package/src/editor/global.ts +20 -0
- package/src/editor/multiView/CameraWindow.tsx +74 -0
- package/src/editor/multiView/InfiniteGridHelper.ts +24 -0
- package/src/editor/multiView/InfiniteGridMaterial.ts +127 -0
- package/src/editor/multiView/MultiView.scss +101 -0
- package/src/editor/multiView/MultiView.tsx +636 -0
- package/src/editor/multiView/MultiViewData.ts +59 -0
- package/src/editor/multiView/UVMaterial.ts +55 -0
- package/src/editor/scss/_debug.scss +58 -0
- package/src/editor/scss/_draggable.scss +43 -0
- package/src/editor/scss/_dropdown.scss +84 -0
- package/src/editor/scss/_sidePanel.scss +278 -0
- package/src/editor/scss/_theme.scss +9 -0
- package/src/editor/scss/index.scss +67 -0
- package/src/editor/sidePanel/Accordion.tsx +41 -0
- package/src/editor/sidePanel/ChildObject.tsx +57 -0
- package/src/editor/sidePanel/ContainerObject.tsx +11 -0
- package/src/editor/sidePanel/SidePanel.tsx +64 -0
- package/src/editor/sidePanel/ToggleBtn.tsx +27 -0
- package/src/editor/sidePanel/inspector/Inspector.tsx +119 -0
- package/src/editor/sidePanel/inspector/InspectorField.tsx +198 -0
- package/src/editor/sidePanel/inspector/InspectorGroup.tsx +50 -0
- package/src/editor/sidePanel/inspector/SceneInspector.tsx +84 -0
- package/src/editor/sidePanel/inspector/inspector.scss +161 -0
- package/src/editor/sidePanel/inspector/utils/InspectAnimation.tsx +102 -0
- package/src/editor/sidePanel/inspector/utils/InspectCamera.tsx +75 -0
- package/src/editor/sidePanel/inspector/utils/InspectLight.tsx +62 -0
- package/src/editor/sidePanel/inspector/utils/InspectMaterial.tsx +710 -0
- package/src/editor/sidePanel/inspector/utils/InspectTransform.tsx +113 -0
- package/src/editor/sidePanel/types.ts +130 -0
- package/src/editor/sidePanel/utils.ts +278 -0
- package/src/editor/utils.ts +117 -0
- package/src/example/CustomEditor.tsx +78 -0
- package/src/example/components/App.css +6 -0
- package/src/example/components/App.tsx +246 -0
- package/src/example/constants.ts +52 -0
- package/src/example/index.scss +45 -0
- package/src/example/main.tsx +37 -0
- package/src/example/three/BaseScene.ts +42 -0
- package/src/example/three/CustomMaterial.ts +72 -0
- package/src/example/three/FBXAnimation.ts +26 -0
- package/src/example/three/Scene1.ts +225 -0
- package/src/example/three/Scene2.ts +138 -0
- package/src/example/three/loader.ts +110 -0
- package/src/index.ts +27 -0
- package/src/vite-env.d.ts +1 -0
- package/dist/hermes.js +0 -3901
- package/dist/hermes.umd.cjs +0 -144
@@ -0,0 +1,636 @@
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
2
|
+
import { AxesHelper, Camera, CameraHelper, Group, OrthographicCamera, PerspectiveCamera, Raycaster, Scene, Vector2, WebGLRenderer } from 'three';
|
3
|
+
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
|
4
|
+
import CameraWindow, { Dropdown } from './CameraWindow';
|
5
|
+
import InfiniteGridHelper from './InfiniteGridHelper';
|
6
|
+
import { cameras, controls, depthMaterial, helpers, ModeOptions, MultiViewMode, normalsMaterial, RenderMode, renderOptions, uvMaterial, wireframeMaterial } from './MultiViewData';
|
7
|
+
import './MultiView.scss';
|
8
|
+
import RemoteThree from '@/core/remote/RemoteThree';
|
9
|
+
import { ToolEvents, debugDispatcher } from '../global';
|
10
|
+
import { dispose } from '../utils';
|
11
|
+
import { mapLinear } from 'three/src/math/MathUtils';
|
12
|
+
|
13
|
+
let currentRenderMode: RenderMode = 'Renderer';
|
14
|
+
|
15
|
+
// Scene
|
16
|
+
|
17
|
+
const scene = new Scene();
|
18
|
+
scene.name = 'Debug Scene';
|
19
|
+
|
20
|
+
let currentScene = new Scene();
|
21
|
+
scene.add(currentScene);
|
22
|
+
|
23
|
+
const helpersContainer = new Group();
|
24
|
+
helpersContainer.name = 'helpers';
|
25
|
+
scene.add(helpersContainer);
|
26
|
+
|
27
|
+
const grid = new InfiniteGridHelper();
|
28
|
+
helpersContainer.add(grid);
|
29
|
+
|
30
|
+
const axisHelper = new AxesHelper(500);
|
31
|
+
axisHelper.name = 'axisHelper';
|
32
|
+
helpersContainer.add(axisHelper);
|
33
|
+
|
34
|
+
const interactionHelper = new AxesHelper(100);
|
35
|
+
interactionHelper.name = 'interactionHelper';
|
36
|
+
helpersContainer.add(interactionHelper);
|
37
|
+
interactionHelper.visible = false;
|
38
|
+
|
39
|
+
let useRaycaster = false;
|
40
|
+
|
41
|
+
// Cameras
|
42
|
+
|
43
|
+
let tlCam = cameras.get('Debug')!;
|
44
|
+
let trCam = cameras.get('Orthographic')!;
|
45
|
+
let blCam = cameras.get('Front')!;
|
46
|
+
let brCam = cameras.get('Top')!;
|
47
|
+
let sceneSet = false;
|
48
|
+
|
49
|
+
interface MultiViewProps {
|
50
|
+
three: RemoteThree;
|
51
|
+
mode?: MultiViewMode;
|
52
|
+
scenes: Map<string, any>;
|
53
|
+
onSceneSet?: (scene: Scene) => void;
|
54
|
+
onSceneUpdate?: (scene: Scene) => void;
|
55
|
+
}
|
56
|
+
|
57
|
+
export default function MultiView(props: MultiViewProps) {
|
58
|
+
// States
|
59
|
+
const [mode, setMode] = useState<MultiViewMode>(props.mode !== undefined ? props.mode : 'Single');
|
60
|
+
const [renderer, setRenderer] = useState<WebGLRenderer | null>(null);
|
61
|
+
const [modeOpen, setModeOpen] = useState(false);
|
62
|
+
const [renderModeOpen, setRenderModeOpen] = useState(false);
|
63
|
+
const [interactionModeOpen, setInteractionModeOpen] = useState(false);
|
64
|
+
const [, setLastUpdate] = useState(Date.now());
|
65
|
+
|
66
|
+
// References
|
67
|
+
const canvasRef = useRef<HTMLCanvasElement>(null);
|
68
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
69
|
+
const tlWindow = useRef<HTMLDivElement>(null);
|
70
|
+
const trWindow = useRef<HTMLDivElement>(null);
|
71
|
+
const blWindow = useRef<HTMLDivElement>(null);
|
72
|
+
const brWindow = useRef<HTMLDivElement>(null);
|
73
|
+
|
74
|
+
const createControls = (camera: Camera, element: HTMLDivElement) => {
|
75
|
+
// Previous items
|
76
|
+
const prevControls = controls.get(camera.name);
|
77
|
+
if (prevControls !== undefined) prevControls.dispose();
|
78
|
+
controls.delete(camera.name);
|
79
|
+
|
80
|
+
const prevHelper = helpers.get(camera.name);
|
81
|
+
if (prevHelper !== undefined) {
|
82
|
+
scene.remove(prevHelper);
|
83
|
+
prevHelper.dispose();
|
84
|
+
}
|
85
|
+
helpers.delete(camera.name);
|
86
|
+
|
87
|
+
// New items
|
88
|
+
const control = new OrbitControls(camera, element);
|
89
|
+
control.enableDamping = true; // an animation loop is required when either damping or auto-rotation are enabled
|
90
|
+
control.dampingFactor = 0.05;
|
91
|
+
switch (camera.name) {
|
92
|
+
case 'Top':
|
93
|
+
case 'Bottom':
|
94
|
+
case 'Left':
|
95
|
+
case 'Right':
|
96
|
+
case 'Front':
|
97
|
+
case 'Back':
|
98
|
+
control.enableRotate = false;
|
99
|
+
break;
|
100
|
+
}
|
101
|
+
controls.set(camera.name, control);
|
102
|
+
|
103
|
+
if (camera instanceof PerspectiveCamera) {
|
104
|
+
const helper = new CameraHelper(camera);
|
105
|
+
helpers.set(camera.name, helper);
|
106
|
+
scene.add(helper);
|
107
|
+
}
|
108
|
+
};
|
109
|
+
|
110
|
+
const clearCamera = (camera: Camera) => {
|
111
|
+
const helper = helpers.get(camera.name);
|
112
|
+
if (helper !== undefined) {
|
113
|
+
scene.remove(helper);
|
114
|
+
helper.dispose();
|
115
|
+
helpers.delete(camera.name);
|
116
|
+
}
|
117
|
+
const control = controls.get(camera.name);
|
118
|
+
if (control !== undefined) {
|
119
|
+
control.dispose();
|
120
|
+
controls.delete(camera.name);
|
121
|
+
}
|
122
|
+
};
|
123
|
+
|
124
|
+
const killControls = () => {
|
125
|
+
controls.forEach((value: OrbitControls, key: string) => {
|
126
|
+
value.dispose();
|
127
|
+
const helper = helpers.get(key);
|
128
|
+
if (helper !== undefined) {
|
129
|
+
scene.remove(helper);
|
130
|
+
helper.dispose();
|
131
|
+
}
|
132
|
+
helpers.delete(key);
|
133
|
+
controls.delete(key);
|
134
|
+
});
|
135
|
+
controls.clear();
|
136
|
+
helpers.clear();
|
137
|
+
};
|
138
|
+
|
139
|
+
const assignControls = () => {
|
140
|
+
switch (mode) {
|
141
|
+
case 'Single':
|
142
|
+
createControls(tlCam, tlWindow.current!);
|
143
|
+
break;
|
144
|
+
case 'Side by Side':
|
145
|
+
case 'Stacked':
|
146
|
+
createControls(tlCam, tlWindow.current!);
|
147
|
+
createControls(trCam, trWindow.current!);
|
148
|
+
break;
|
149
|
+
case 'Quad':
|
150
|
+
createControls(tlCam, tlWindow.current!);
|
151
|
+
createControls(trCam, trWindow.current!);
|
152
|
+
createControls(blCam, blWindow.current!);
|
153
|
+
createControls(brCam, brWindow.current!);
|
154
|
+
break;
|
155
|
+
}
|
156
|
+
};
|
157
|
+
|
158
|
+
// Renderer
|
159
|
+
useEffect(() => {
|
160
|
+
const instance = new WebGLRenderer({
|
161
|
+
canvas: canvasRef.current!,
|
162
|
+
stencil: false
|
163
|
+
});
|
164
|
+
instance.autoClear = false;
|
165
|
+
instance.shadowMap.enabled = true;
|
166
|
+
instance.setPixelRatio(devicePixelRatio);
|
167
|
+
instance.setClearColor(0x000000);
|
168
|
+
setRenderer(instance);
|
169
|
+
}, []);
|
170
|
+
|
171
|
+
// Event handling
|
172
|
+
useEffect(() => {
|
173
|
+
const sceneUpdate = (evt: any) => {
|
174
|
+
dispose(currentScene);
|
175
|
+
scene.remove(currentScene);
|
176
|
+
|
177
|
+
const sceneClass = props.scenes.get(evt.value.name);
|
178
|
+
if (sceneClass !== undefined) {
|
179
|
+
const sceneInstance = new sceneClass();
|
180
|
+
if (props.onSceneSet !== undefined) props.onSceneSet(sceneInstance);
|
181
|
+
currentScene = sceneInstance;
|
182
|
+
props.three.scene = currentScene;
|
183
|
+
scene.add(currentScene);
|
184
|
+
sceneSet = true;
|
185
|
+
}
|
186
|
+
};
|
187
|
+
|
188
|
+
const addCamera = (evt: any) => {
|
189
|
+
const data = evt.value;
|
190
|
+
const child = props.three.scene?.getObjectByProperty('uuid', data.uuid);
|
191
|
+
if (child !== undefined) cameras.set(data.name, child as Camera);
|
192
|
+
setLastUpdate(Date.now());
|
193
|
+
};
|
194
|
+
|
195
|
+
const removeCamera = (evt: any) => {
|
196
|
+
cameras.delete(evt.value.name);
|
197
|
+
setLastUpdate(Date.now());
|
198
|
+
};
|
199
|
+
|
200
|
+
debugDispatcher.addEventListener(ToolEvents.SET_SCENE, sceneUpdate);
|
201
|
+
debugDispatcher.addEventListener(ToolEvents.ADD_CAMERA, addCamera);
|
202
|
+
debugDispatcher.addEventListener(ToolEvents.REMOVE_CAMERA, removeCamera);
|
203
|
+
return () => {
|
204
|
+
debugDispatcher.removeEventListener(ToolEvents.SET_SCENE, sceneUpdate);
|
205
|
+
debugDispatcher.removeEventListener(ToolEvents.ADD_CAMERA, addCamera);
|
206
|
+
debugDispatcher.removeEventListener(ToolEvents.REMOVE_CAMERA, removeCamera);
|
207
|
+
};
|
208
|
+
}, []);
|
209
|
+
|
210
|
+
// Resize handling + drawing
|
211
|
+
useEffect(() => {
|
212
|
+
if (renderer === null) return;
|
213
|
+
let width = window.innerWidth;
|
214
|
+
let height = window.innerHeight;
|
215
|
+
let bw = Math.floor(width / 2);
|
216
|
+
let bh = Math.floor(height / 2);
|
217
|
+
let raf = -1;
|
218
|
+
|
219
|
+
const onResize = () => {
|
220
|
+
width = window.innerWidth - 300;
|
221
|
+
height = window.innerHeight;
|
222
|
+
bw = Math.floor(width / 2);
|
223
|
+
bh = Math.floor(height / 2);
|
224
|
+
renderer.setSize(width, height);
|
225
|
+
|
226
|
+
let cw = width;
|
227
|
+
let ch = height;
|
228
|
+
switch (mode) {
|
229
|
+
case 'Side by Side':
|
230
|
+
cw = bw;
|
231
|
+
ch = height;
|
232
|
+
break;
|
233
|
+
case 'Stacked':
|
234
|
+
cw = width;
|
235
|
+
ch = bh;
|
236
|
+
break;
|
237
|
+
case 'Quad':
|
238
|
+
cw = bw;
|
239
|
+
ch = bh;
|
240
|
+
break;
|
241
|
+
}
|
242
|
+
|
243
|
+
cameras.forEach((camera) => {
|
244
|
+
if (camera instanceof OrthographicCamera) {
|
245
|
+
camera.left = cw / -2;
|
246
|
+
camera.right = cw / 2;
|
247
|
+
camera.top = ch / 2;
|
248
|
+
camera.bottom = ch / -2;
|
249
|
+
camera.updateProjectionMatrix();
|
250
|
+
} else if (camera instanceof PerspectiveCamera) {
|
251
|
+
camera.aspect = cw / ch;
|
252
|
+
camera.updateProjectionMatrix();
|
253
|
+
helpers.get(camera.name)?.update();
|
254
|
+
}
|
255
|
+
});
|
256
|
+
};
|
257
|
+
|
258
|
+
const drawSingle = () => {
|
259
|
+
renderer.setViewport(0, 0, width, height);
|
260
|
+
renderer.setScissor(0, 0, width, height);
|
261
|
+
renderer.render(scene, tlCam);
|
262
|
+
};
|
263
|
+
|
264
|
+
const drawDouble = () => {
|
265
|
+
if (mode === 'Side by Side') {
|
266
|
+
renderer.setViewport(0, 0, bw, height);
|
267
|
+
renderer.setScissor(0, 0, bw, height);
|
268
|
+
renderer.render(scene, tlCam);
|
269
|
+
|
270
|
+
renderer.setViewport(bw, 0, bw, height);
|
271
|
+
renderer.setScissor(bw, 0, bw, height);
|
272
|
+
renderer.render(scene, trCam);
|
273
|
+
} else {
|
274
|
+
const y = height - bh;
|
275
|
+
renderer.setViewport(0, y, width, bh);
|
276
|
+
renderer.setScissor(0, y, width, bh);
|
277
|
+
renderer.render(scene, tlCam);
|
278
|
+
|
279
|
+
renderer.setViewport(0, 0, width, bh);
|
280
|
+
renderer.setScissor(0, 0, width, bh);
|
281
|
+
renderer.render(scene, trCam);
|
282
|
+
}
|
283
|
+
};
|
284
|
+
|
285
|
+
const drawQuad = () => {
|
286
|
+
let x = 0;
|
287
|
+
let y = 0;
|
288
|
+
y = height - bh;
|
289
|
+
|
290
|
+
// TL
|
291
|
+
x = 0;
|
292
|
+
renderer.setViewport(x, y, bw, bh);
|
293
|
+
renderer.setScissor(x, y, bw, bh);
|
294
|
+
renderer.render(scene, tlCam);
|
295
|
+
|
296
|
+
// TR
|
297
|
+
x = bw;
|
298
|
+
renderer.setViewport(x, y, bw, bh);
|
299
|
+
renderer.setScissor(x, y, bw, bh);
|
300
|
+
renderer.render(scene, trCam);
|
301
|
+
|
302
|
+
y = 0;
|
303
|
+
|
304
|
+
// BL
|
305
|
+
x = 0;
|
306
|
+
renderer.setViewport(x, y, bw, bh);
|
307
|
+
renderer.setScissor(x, y, bw, bh);
|
308
|
+
renderer.render(scene, blCam);
|
309
|
+
|
310
|
+
// BR
|
311
|
+
x = bw;
|
312
|
+
renderer.setViewport(x, y, bw, bh);
|
313
|
+
renderer.setScissor(x, y, bw, bh);
|
314
|
+
renderer.render(scene, brCam);
|
315
|
+
};
|
316
|
+
|
317
|
+
const onUpdate = () => {
|
318
|
+
// Updates
|
319
|
+
controls.forEach((control: OrbitControls) => {
|
320
|
+
control.update();
|
321
|
+
});
|
322
|
+
|
323
|
+
if (props.onSceneUpdate !== undefined && sceneSet) props.onSceneUpdate(currentScene);
|
324
|
+
|
325
|
+
// Drawing
|
326
|
+
renderer.clear();
|
327
|
+
switch (mode) {
|
328
|
+
case 'Single':
|
329
|
+
drawSingle();
|
330
|
+
break;
|
331
|
+
case 'Side by Side':
|
332
|
+
case 'Stacked':
|
333
|
+
drawDouble();
|
334
|
+
break;
|
335
|
+
case 'Quad':
|
336
|
+
drawQuad();
|
337
|
+
break;
|
338
|
+
}
|
339
|
+
|
340
|
+
raf = requestAnimationFrame(onUpdate);
|
341
|
+
};
|
342
|
+
|
343
|
+
// Start rendering
|
344
|
+
assignControls();
|
345
|
+
window.addEventListener('resize', onResize);
|
346
|
+
onResize();
|
347
|
+
onUpdate();
|
348
|
+
|
349
|
+
return () => {
|
350
|
+
window.removeEventListener('resize', onResize);
|
351
|
+
cancelAnimationFrame(raf);
|
352
|
+
raf = -1;
|
353
|
+
};
|
354
|
+
}, [mode, renderer]);
|
355
|
+
|
356
|
+
// Raycaster
|
357
|
+
useEffect(() => {
|
358
|
+
if (renderer !== null) {
|
359
|
+
const raycaster = new Raycaster();
|
360
|
+
const pointer = new Vector2();
|
361
|
+
|
362
|
+
const updateCamera = (mouseX: number, mouseY: number, hw: number, hh: number) => {
|
363
|
+
switch (mode) {
|
364
|
+
case 'Quad':
|
365
|
+
if (mouseX < hw) {
|
366
|
+
if (mouseY < hh) {
|
367
|
+
raycaster.setFromCamera(pointer, tlCam);
|
368
|
+
} else {
|
369
|
+
raycaster.setFromCamera(pointer, blCam);
|
370
|
+
}
|
371
|
+
} else {
|
372
|
+
if (mouseY < hh) {
|
373
|
+
raycaster.setFromCamera(pointer, trCam);
|
374
|
+
} else {
|
375
|
+
raycaster.setFromCamera(pointer, brCam);
|
376
|
+
}
|
377
|
+
}
|
378
|
+
break;
|
379
|
+
case 'Side by Side':
|
380
|
+
if (mouseX < hw) {
|
381
|
+
raycaster.setFromCamera(pointer, tlCam);
|
382
|
+
} else {
|
383
|
+
raycaster.setFromCamera(pointer, trCam);
|
384
|
+
}
|
385
|
+
break;
|
386
|
+
case 'Single':
|
387
|
+
raycaster.setFromCamera(pointer, tlCam);
|
388
|
+
break;
|
389
|
+
case 'Stacked':
|
390
|
+
if (mouseY < hh) {
|
391
|
+
raycaster.setFromCamera(pointer, tlCam);
|
392
|
+
} else {
|
393
|
+
raycaster.setFromCamera(pointer, trCam);
|
394
|
+
}
|
395
|
+
break;
|
396
|
+
}
|
397
|
+
};
|
398
|
+
|
399
|
+
const onMouseMove = (event: MouseEvent) => {
|
400
|
+
if (!useRaycaster) return;
|
401
|
+
const size = new Vector2();
|
402
|
+
renderer!.getSize(size);
|
403
|
+
|
404
|
+
const mouseX = Math.min(event.clientX, size.x);
|
405
|
+
const mouseY = Math.min(event.clientY, size.y);
|
406
|
+
pointer.x = mapLinear(mouseX, 0, size.x, -1, 1);
|
407
|
+
pointer.y = mapLinear(mouseY, 0, size.y, 1, -1);
|
408
|
+
|
409
|
+
const hw = size.x / 2;
|
410
|
+
const hh = size.y / 2;
|
411
|
+
|
412
|
+
const sideBySide = () => {
|
413
|
+
if (mouseX < hw) {
|
414
|
+
pointer.x = mapLinear(mouseX, 0, hw, -1, 1);
|
415
|
+
} else {
|
416
|
+
pointer.x = mapLinear(mouseX, hw, size.x, -1, 1);
|
417
|
+
}
|
418
|
+
};
|
419
|
+
|
420
|
+
const stacked = () => {
|
421
|
+
if (mouseY < hh) {
|
422
|
+
pointer.y = mapLinear(mouseY, 0, hh, 1, -1);
|
423
|
+
} else {
|
424
|
+
pointer.y = mapLinear(mouseY, hh, size.y, 1, -1);
|
425
|
+
}
|
426
|
+
};
|
427
|
+
|
428
|
+
// mapLinear
|
429
|
+
switch (mode) {
|
430
|
+
case 'Quad':
|
431
|
+
sideBySide();
|
432
|
+
stacked();
|
433
|
+
break;
|
434
|
+
case 'Side by Side':
|
435
|
+
sideBySide();
|
436
|
+
break;
|
437
|
+
case 'Stacked':
|
438
|
+
stacked();
|
439
|
+
stacked();
|
440
|
+
break;
|
441
|
+
}
|
442
|
+
|
443
|
+
updateCamera(mouseX, mouseY, hw, hh);
|
444
|
+
const intersects = raycaster.intersectObjects(currentScene.children);
|
445
|
+
if (intersects.length > 0) interactionHelper.position.copy(intersects[0].point);
|
446
|
+
};
|
447
|
+
|
448
|
+
const onClick = (event: MouseEvent) => {
|
449
|
+
if (!useRaycaster) return;
|
450
|
+
|
451
|
+
const size = new Vector2();
|
452
|
+
renderer!.getSize(size);
|
453
|
+
if (event.clientX >= size.x) return;
|
454
|
+
|
455
|
+
onMouseMove(event);
|
456
|
+
|
457
|
+
const intersects = raycaster.intersectObjects(currentScene.children);
|
458
|
+
if (intersects.length > 0) {
|
459
|
+
props.three.getObject(intersects[0].object.uuid);
|
460
|
+
}
|
461
|
+
};
|
462
|
+
|
463
|
+
const element = containerRef.current!;
|
464
|
+
element.addEventListener('mousemove', onMouseMove, false);
|
465
|
+
element.addEventListener('click', onClick, false);
|
466
|
+
return () => {
|
467
|
+
element.removeEventListener('mousemove', onMouseMove);
|
468
|
+
element.removeEventListener('click', onClick);
|
469
|
+
};
|
470
|
+
}
|
471
|
+
}, [mode, renderer]);
|
472
|
+
|
473
|
+
// Camera names
|
474
|
+
const cameraOptions: string[] = [];
|
475
|
+
cameras.forEach((_: Camera, key: string) => {
|
476
|
+
cameraOptions.push(key);
|
477
|
+
});
|
478
|
+
|
479
|
+
return (
|
480
|
+
<div className='multiview'>
|
481
|
+
<canvas ref={canvasRef} />
|
482
|
+
|
483
|
+
<div className={`cameras ${mode === 'Single' || mode === 'Stacked' ? 'single' : ''}`} ref={containerRef}>
|
484
|
+
{mode === 'Single' && (
|
485
|
+
<>
|
486
|
+
<CameraWindow camera={tlCam} options={cameraOptions} ref={tlWindow} onSelect={(value: string) => {
|
487
|
+
controls.get(tlCam.name)?.dispose();
|
488
|
+
const camera = cameras.get(value);
|
489
|
+
if (camera !== undefined) {
|
490
|
+
clearCamera(tlCam);
|
491
|
+
tlCam = camera;
|
492
|
+
createControls(camera, tlWindow.current!);
|
493
|
+
}
|
494
|
+
}} />
|
495
|
+
</>
|
496
|
+
)}
|
497
|
+
|
498
|
+
{(mode === 'Side by Side' || mode === 'Stacked') && (
|
499
|
+
<>
|
500
|
+
<CameraWindow camera={tlCam} options={cameraOptions} ref={tlWindow} onSelect={(value: string) => {
|
501
|
+
controls.get(tlCam.name)?.dispose();
|
502
|
+
const camera = cameras.get(value);
|
503
|
+
if (camera !== undefined) {
|
504
|
+
clearCamera(tlCam);
|
505
|
+
tlCam = camera;
|
506
|
+
createControls(camera, tlWindow.current!);
|
507
|
+
}
|
508
|
+
}} />
|
509
|
+
<CameraWindow camera={trCam} options={cameraOptions} ref={trWindow} onSelect={(value: string) => {
|
510
|
+
controls.get(trCam.name)?.dispose();
|
511
|
+
const camera = cameras.get(value);
|
512
|
+
if (camera !== undefined) {
|
513
|
+
clearCamera(trCam);
|
514
|
+
trCam = camera;
|
515
|
+
createControls(camera, trWindow.current!);
|
516
|
+
}
|
517
|
+
}} />
|
518
|
+
</>
|
519
|
+
)}
|
520
|
+
|
521
|
+
{mode === 'Quad' && (
|
522
|
+
<>
|
523
|
+
<CameraWindow camera={tlCam} options={cameraOptions} ref={tlWindow} onSelect={(value: string) => {
|
524
|
+
controls.get(tlCam.name)?.dispose();
|
525
|
+
const camera = cameras.get(value);
|
526
|
+
if (camera !== undefined) {
|
527
|
+
clearCamera(tlCam);
|
528
|
+
tlCam = camera;
|
529
|
+
createControls(camera, tlWindow.current!);
|
530
|
+
}
|
531
|
+
}} />
|
532
|
+
<CameraWindow camera={trCam} options={cameraOptions} ref={trWindow} onSelect={(value: string) => {
|
533
|
+
controls.get(trCam.name)?.dispose();
|
534
|
+
const camera = cameras.get(value);
|
535
|
+
if (camera !== undefined) {
|
536
|
+
clearCamera(trCam);
|
537
|
+
trCam = camera;
|
538
|
+
createControls(camera, trWindow.current!);
|
539
|
+
}
|
540
|
+
}} />
|
541
|
+
<CameraWindow camera={blCam} options={cameraOptions} ref={blWindow} onSelect={(value: string) => {
|
542
|
+
controls.get(blCam.name)?.dispose();
|
543
|
+
const camera = cameras.get(value);
|
544
|
+
if (camera !== undefined) {
|
545
|
+
clearCamera(blCam);
|
546
|
+
blCam = camera;
|
547
|
+
createControls(camera, blWindow.current!);
|
548
|
+
}
|
549
|
+
}} />
|
550
|
+
<CameraWindow camera={brCam} options={cameraOptions} ref={brWindow} onSelect={(value: string) => {
|
551
|
+
controls.get(brCam.name)?.dispose();
|
552
|
+
const camera = cameras.get(value);
|
553
|
+
if (camera !== undefined) {
|
554
|
+
clearCamera(brCam);
|
555
|
+
brCam = camera;
|
556
|
+
createControls(camera, brWindow.current!);
|
557
|
+
}
|
558
|
+
}} />
|
559
|
+
</>
|
560
|
+
)}
|
561
|
+
</div>
|
562
|
+
|
563
|
+
<div className='settings'>
|
564
|
+
{/* Mode */}
|
565
|
+
<Dropdown
|
566
|
+
index={ModeOptions.indexOf(mode)}
|
567
|
+
options={ModeOptions}
|
568
|
+
onSelect={(value: string) => {
|
569
|
+
if (value === mode) return;
|
570
|
+
killControls();
|
571
|
+
setMode(value as MultiViewMode);
|
572
|
+
}}
|
573
|
+
open={modeOpen}
|
574
|
+
onToggle={(value: boolean) => {
|
575
|
+
setModeOpen(value);
|
576
|
+
if (renderModeOpen) setRenderModeOpen(false);
|
577
|
+
if (interactionModeOpen) setInteractionModeOpen(false);
|
578
|
+
}}
|
579
|
+
/>
|
580
|
+
|
581
|
+
{/* Render Mode */}
|
582
|
+
<Dropdown
|
583
|
+
index={renderOptions.indexOf(currentRenderMode)}
|
584
|
+
options={renderOptions}
|
585
|
+
onSelect={(value: string) => {
|
586
|
+
if (value === currentRenderMode) return;
|
587
|
+
currentRenderMode = value as RenderMode;
|
588
|
+
switch (currentRenderMode) {
|
589
|
+
case 'Depth':
|
590
|
+
scene.overrideMaterial = depthMaterial;
|
591
|
+
break;
|
592
|
+
case 'Normals':
|
593
|
+
scene.overrideMaterial = normalsMaterial;
|
594
|
+
break;
|
595
|
+
default:
|
596
|
+
case 'Renderer':
|
597
|
+
scene.overrideMaterial = null;
|
598
|
+
break;
|
599
|
+
case 'Wireframe':
|
600
|
+
scene.overrideMaterial = wireframeMaterial;
|
601
|
+
break;
|
602
|
+
case 'UVs':
|
603
|
+
scene.overrideMaterial = uvMaterial;
|
604
|
+
break;
|
605
|
+
}
|
606
|
+
}}
|
607
|
+
open={renderModeOpen}
|
608
|
+
onToggle={(value: boolean) => {
|
609
|
+
if (modeOpen) setModeOpen(false);
|
610
|
+
setRenderModeOpen(value);
|
611
|
+
if (interactionModeOpen) setInteractionModeOpen(false);
|
612
|
+
}}
|
613
|
+
/>
|
614
|
+
|
615
|
+
{/* Interaction Mode */}
|
616
|
+
<Dropdown
|
617
|
+
index={0}
|
618
|
+
options={[
|
619
|
+
'Orbit Mode',
|
620
|
+
'Selection Mode',
|
621
|
+
]}
|
622
|
+
onSelect={(value: string) => {
|
623
|
+
useRaycaster = value === 'Selection Mode';
|
624
|
+
interactionHelper.visible = useRaycaster;
|
625
|
+
}}
|
626
|
+
open={interactionModeOpen}
|
627
|
+
onToggle={(value: boolean) => {
|
628
|
+
if (modeOpen) setModeOpen(false);
|
629
|
+
if (renderModeOpen) setRenderModeOpen(false);
|
630
|
+
setInteractionModeOpen(value);
|
631
|
+
}}
|
632
|
+
/>
|
633
|
+
</div>
|
634
|
+
</div>
|
635
|
+
);
|
636
|
+
}
|
@@ -0,0 +1,59 @@
|
|
1
|
+
import { Camera, CameraHelper, MeshBasicMaterial, MeshDepthMaterial, MeshNormalMaterial, OrthographicCamera, PerspectiveCamera, Vector3 } from 'three';
|
2
|
+
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
|
3
|
+
import UVMaterial from './UVMaterial';
|
4
|
+
|
5
|
+
export type MultiViewMode = 'Single' | 'Side by Side' | 'Stacked' |'Quad';
|
6
|
+
export const ModeOptions: MultiViewMode[] = [
|
7
|
+
'Single',
|
8
|
+
'Side by Side',
|
9
|
+
'Stacked',
|
10
|
+
'Quad'
|
11
|
+
];
|
12
|
+
|
13
|
+
// Cameras
|
14
|
+
|
15
|
+
export const cameras: Map<string, Camera> = new Map();
|
16
|
+
export const controls: Map<string, OrbitControls> = new Map();
|
17
|
+
export const helpers: Map<string, CameraHelper> = new Map();
|
18
|
+
|
19
|
+
export function createOrtho(name: string, position: Vector3) {
|
20
|
+
const camera = new OrthographicCamera(-100, 100, 100, -100, 50, 3000);
|
21
|
+
camera.name = name;
|
22
|
+
camera.position.copy(position);
|
23
|
+
camera.lookAt(0, 0, 0);
|
24
|
+
cameras.set(name, camera);
|
25
|
+
return camera;
|
26
|
+
}
|
27
|
+
|
28
|
+
createOrtho('Top', new Vector3(0, 1000, 0));
|
29
|
+
createOrtho('Bottom', new Vector3(0, -1000, 0));
|
30
|
+
createOrtho('Left', new Vector3(-1000, 0, 0));
|
31
|
+
createOrtho('Right', new Vector3(1000, 0, 0));
|
32
|
+
createOrtho('Front', new Vector3(0, 0, 1000));
|
33
|
+
createOrtho('Back', new Vector3(0, 0, -1000));
|
34
|
+
createOrtho('Orthographic', new Vector3(1000, 1000, 1000));
|
35
|
+
|
36
|
+
export const debugCamera = new PerspectiveCamera(60, 1, 50, 3000);
|
37
|
+
debugCamera.name = 'Debug';
|
38
|
+
debugCamera.position.set(500, 500, 500);
|
39
|
+
debugCamera.lookAt(0, 0, 0);
|
40
|
+
cameras.set('Debug', debugCamera);
|
41
|
+
|
42
|
+
// Rendering
|
43
|
+
|
44
|
+
export type RenderMode = 'Depth' | 'Normals' | 'Renderer' | 'UVs' | 'Wireframe';
|
45
|
+
export const renderOptions: RenderMode[] = [
|
46
|
+
'Renderer',
|
47
|
+
'Depth',
|
48
|
+
'Normals',
|
49
|
+
'UVs',
|
50
|
+
'Wireframe',
|
51
|
+
];
|
52
|
+
export const depthMaterial = new MeshDepthMaterial();
|
53
|
+
export const normalsMaterial = new MeshNormalMaterial();
|
54
|
+
export const uvMaterial = new UVMaterial();
|
55
|
+
export const wireframeMaterial = new MeshBasicMaterial({
|
56
|
+
opacity: 0.33,
|
57
|
+
transparent: true,
|
58
|
+
wireframe: true
|
59
|
+
});
|