@tomorrowevening/hermes 0.0.11 → 0.0.13

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.
Files changed (64) hide show
  1. package/dist/hermes.js +1541 -1446
  2. package/dist/hermes.umd.cjs +16 -16
  3. package/dist/style.css +1 -1
  4. package/package.json +3 -2
  5. package/src/core/Application.ts +1 -1
  6. package/src/core/RemoteController.ts +4 -151
  7. package/src/core/remote/RemoteComponents.ts +15 -1
  8. package/src/core/remote/RemoteTheatre.ts +114 -3
  9. package/src/core/remote/RemoteThree.ts +57 -2
  10. package/src/core/remote/RemoteTweakpane.ts +28 -2
  11. package/src/core/types.ts +2 -0
  12. package/src/editor/global.ts +3 -0
  13. package/src/editor/multiView/CameraWindow.tsx +20 -7
  14. package/src/editor/multiView/MultiView.scss +2 -0
  15. package/src/editor/multiView/MultiView.tsx +203 -9
  16. package/src/editor/scss/{_sceneHierarchy.scss → _sidePanel.scss} +1 -1
  17. package/src/editor/scss/index.scss +1 -1
  18. package/src/editor/{sceneHierarchy/SceneHierarchy.tsx → sidePanel/SidePanel.tsx} +8 -10
  19. package/src/editor/sidePanel/inspector/Inspector.tsx +111 -0
  20. package/src/editor/sidePanel/inspector/utils/InspectAnimation.tsx +51 -0
  21. package/src/editor/{sceneHierarchy → sidePanel}/types.ts +10 -1
  22. package/src/editor/{sceneHierarchy → sidePanel}/utils.ts +11 -2
  23. package/src/index.ts +6 -6
  24. package/types/core/Application.d.ts +1 -1
  25. package/types/core/RemoteController.d.ts +1 -1
  26. package/types/core/remote/RemoteComponents.d.ts +3 -0
  27. package/types/core/remote/RemoteTheatre.d.ts +3 -0
  28. package/types/core/remote/RemoteThree.d.ts +7 -1
  29. package/types/core/remote/RemoteTweakpane.d.ts +2 -0
  30. package/types/core/types.d.ts +1 -1
  31. package/types/editor/global.d.ts +2 -0
  32. package/types/editor/multiView/CameraWindow.d.ts +2 -0
  33. package/types/editor/multiView/MultiView.d.ts +4 -0
  34. package/types/editor/sidePanel/SidePanel.d.ts +11 -0
  35. package/types/editor/sidePanel/inspector/utils/InspectAnimation.d.ts +3 -0
  36. package/types/editor/{sceneHierarchy → sidePanel}/types.d.ts +7 -1
  37. package/types/index.d.ts +6 -6
  38. package/src/editor/sceneHierarchy/inspector/Inspector.tsx +0 -97
  39. package/types/editor/sceneHierarchy/SceneHierarchy.d.ts +0 -11
  40. /package/src/editor/{sceneHierarchy → sidePanel}/Accordion.tsx +0 -0
  41. /package/src/editor/{sceneHierarchy → sidePanel}/ChildObject.tsx +0 -0
  42. /package/src/editor/{sceneHierarchy → sidePanel}/ContainerObject.tsx +0 -0
  43. /package/src/editor/{sceneHierarchy → sidePanel}/ToggleBtn.tsx +0 -0
  44. /package/src/editor/{sceneHierarchy → sidePanel}/inspector/InspectorField.tsx +0 -0
  45. /package/src/editor/{sceneHierarchy → sidePanel}/inspector/InspectorGroup.tsx +0 -0
  46. /package/src/editor/{sceneHierarchy → sidePanel}/inspector/SceneInspector.tsx +0 -0
  47. /package/src/editor/{sceneHierarchy → sidePanel}/inspector/inspector.scss +0 -0
  48. /package/src/editor/{sceneHierarchy → sidePanel}/inspector/utils/InspectCamera.tsx +0 -0
  49. /package/src/editor/{sceneHierarchy → sidePanel}/inspector/utils/InspectLight.tsx +0 -0
  50. /package/src/editor/{sceneHierarchy → sidePanel}/inspector/utils/InspectMaterial.tsx +0 -0
  51. /package/src/editor/{sceneHierarchy → sidePanel}/inspector/utils/InspectTransform.tsx +0 -0
  52. /package/types/editor/{sceneHierarchy → sidePanel}/Accordion.d.ts +0 -0
  53. /package/types/editor/{sceneHierarchy → sidePanel}/ChildObject.d.ts +0 -0
  54. /package/types/editor/{sceneHierarchy → sidePanel}/ContainerObject.d.ts +0 -0
  55. /package/types/editor/{sceneHierarchy → sidePanel}/ToggleBtn.d.ts +0 -0
  56. /package/types/editor/{sceneHierarchy → sidePanel}/inspector/Inspector.d.ts +0 -0
  57. /package/types/editor/{sceneHierarchy → sidePanel}/inspector/InspectorField.d.ts +0 -0
  58. /package/types/editor/{sceneHierarchy → sidePanel}/inspector/InspectorGroup.d.ts +0 -0
  59. /package/types/editor/{sceneHierarchy → sidePanel}/inspector/SceneInspector.d.ts +0 -0
  60. /package/types/editor/{sceneHierarchy → sidePanel}/inspector/utils/InspectCamera.d.ts +0 -0
  61. /package/types/editor/{sceneHierarchy → sidePanel}/inspector/utils/InspectLight.d.ts +0 -0
  62. /package/types/editor/{sceneHierarchy → sidePanel}/inspector/utils/InspectMaterial.d.ts +0 -0
  63. /package/types/editor/{sceneHierarchy → sidePanel}/inspector/utils/InspectTransform.d.ts +0 -0
  64. /package/types/editor/{sceneHierarchy → sidePanel}/utils.d.ts +0 -0
package/src/core/types.ts CHANGED
@@ -28,6 +28,8 @@ export type EditorEvent =
28
28
  | 'setScene'
29
29
  | 'createTexture'
30
30
  | 'requestMethod'
31
+ | 'addCamera'
32
+ | 'removeCamera'
31
33
  // GUI
32
34
  | 'addFolder'
33
35
  | 'bindObject'
@@ -14,4 +14,7 @@ export const ToolEvents = {
14
14
  UPDATE_OBJECT: 'ToolEvents::updateObject',
15
15
  CREATE_TEXTURE: 'ToolEvents::createTexture',
16
16
  REQUEST_METHOD: 'ToolEvents::requestMethod',
17
+ // MultiView
18
+ ADD_CAMERA: 'ToolEvents::addCamera',
19
+ REMOVE_CAMERA: 'ToolEvents::removeCamera',
17
20
  };
@@ -2,18 +2,19 @@ import { ForwardedRef, forwardRef, useState } from 'react';
2
2
  import { Camera } from 'three';
3
3
 
4
4
  interface DropdownProps {
5
- index: number
5
+ index: number;
6
+ open: boolean;
7
+ onToggle: (value: boolean) => void;
6
8
  onSelect: (value: string) => void;
7
9
  options: string[];
8
10
  up?: boolean;
9
11
  }
10
12
 
11
13
  export const Dropdown = (props: DropdownProps) => {
12
- const [isOpen, setIsOpen] = useState(false);
13
14
  const [selectedOption, setSelectedOption] = useState(props.options[props.index]);
14
15
 
15
16
  const handleToggle = () => {
16
- setIsOpen(!isOpen);
17
+ props.onToggle(!props.open);
17
18
  };
18
19
 
19
20
  const handleSelect = (option: any) => {
@@ -21,7 +22,7 @@ export const Dropdown = (props: DropdownProps) => {
21
22
  props.onSelect(option);
22
23
  setSelectedOption(option);
23
24
  }
24
- setIsOpen(false);
25
+ props.onToggle(false);
25
26
  };
26
27
 
27
28
  return (
@@ -29,7 +30,7 @@ export const Dropdown = (props: DropdownProps) => {
29
30
  <div className="dropdown-toggle" onClick={handleToggle}>
30
31
  {selectedOption}
31
32
  </div>
32
- {isOpen && (
33
+ {props.open && (
33
34
  <ul className="dropdown-menu">
34
35
  {props.options.map((option) => (
35
36
  <li key={option} onClick={() => handleSelect(option)}>
@@ -49,11 +50,23 @@ interface CameraWindowProps {
49
50
  }
50
51
 
51
52
  const CameraWindow = forwardRef(function CameraWindow(props: CameraWindowProps, ref: ForwardedRef<HTMLDivElement>) {
53
+ const [open, setOpen] = useState(false);
52
54
  const index = props.options.indexOf(props.camera.name);
53
55
  return (
54
56
  <div className='CameraWindow'>
55
- <div ref={ref} className='clickable'></div>
56
- <Dropdown index={index} options={props.options} onSelect={props.onSelect} up={true} />
57
+ <div ref={ref} className='clickable' onClick={() => {
58
+ if (open) setOpen(false);
59
+ }} />
60
+ <Dropdown
61
+ index={index}
62
+ open={open}
63
+ options={props.options}
64
+ onSelect={props.onSelect}
65
+ onToggle={(value: boolean) => {
66
+ setOpen(value);
67
+ }}
68
+ up={true}
69
+ />
57
70
  </div>
58
71
  );
59
72
  });
@@ -54,6 +54,7 @@ $padding: 2px;
54
54
  .cameras {
55
55
  display: grid;
56
56
  grid-template-columns: repeat(2, 1fr);
57
+ pointer-events: visible;
57
58
  position: absolute;
58
59
  width: 100%;
59
60
  height: 100%;
@@ -92,6 +93,7 @@ $padding: 2px;
92
93
  }
93
94
 
94
95
  .settings {
96
+ pointer-events: visible;
95
97
  position: absolute;
96
98
  left: 50%;
97
99
  transform: translateX(-50%);
@@ -1,5 +1,5 @@
1
1
  import { useEffect, useRef, useState } from 'react';
2
- import { AxesHelper, Camera, CameraHelper, OrthographicCamera, PerspectiveCamera, Scene, WebGLRenderer } from 'three';
2
+ import { AxesHelper, Camera, CameraHelper, Group, OrthographicCamera, PerspectiveCamera, Raycaster, Scene, Vector2, WebGLRenderer } from 'three';
3
3
  import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
4
4
  import CameraWindow, { Dropdown } from './CameraWindow';
5
5
  import InfiniteGridHelper from './InfiniteGridHelper';
@@ -8,6 +8,7 @@ import './MultiView.scss';
8
8
  import RemoteThree from '@/core/remote/RemoteThree';
9
9
  import { ToolEvents, debugDispatcher } from '../global';
10
10
  import { dispose } from '../utils';
11
+ import { mapLinear } from 'three/src/math/MathUtils';
11
12
 
12
13
  let currentRenderMode: RenderMode = 'Renderer';
13
14
 
@@ -19,12 +20,23 @@ scene.name = 'Debug Scene';
19
20
  let currentScene = new Scene();
20
21
  scene.add(currentScene);
21
22
 
23
+ const helpersContainer = new Group();
24
+ helpersContainer.name = 'helpers';
25
+ scene.add(helpersContainer);
26
+
22
27
  const grid = new InfiniteGridHelper();
23
- scene.add(grid);
28
+ helpersContainer.add(grid);
24
29
 
25
30
  const axisHelper = new AxesHelper(500);
26
31
  axisHelper.name = 'axisHelper';
27
- scene.add(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;
28
40
 
29
41
  // Cameras
30
42
 
@@ -32,20 +44,28 @@ let tlCam = cameras.get('Debug')!;
32
44
  let trCam = cameras.get('Orthographic')!;
33
45
  let blCam = cameras.get('Front')!;
34
46
  let brCam = cameras.get('Top')!;
47
+ let sceneSet = false;
35
48
 
36
49
  interface MultiViewProps {
37
50
  three: RemoteThree;
38
51
  mode?: MultiViewMode;
52
+ scenes: Map<string, any>;
53
+ onSceneSet?: (scene: Scene) => void;
54
+ onSceneUpdate?: (scene: Scene) => void;
39
55
  }
40
56
 
41
57
  export default function MultiView(props: MultiViewProps) {
42
58
  // States
43
- const [mode, setMode] = useState<MultiViewMode>(props.mode !== undefined ? props.mode : 'Quad');
59
+ const [mode, setMode] = useState<MultiViewMode>(props.mode !== undefined ? props.mode : 'Single');
44
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());
45
65
 
46
66
  // References
47
67
  const canvasRef = useRef<HTMLCanvasElement>(null);
48
-
68
+ const containerRef = useRef<HTMLDivElement>(null);
49
69
  const tlWindow = useRef<HTMLDivElement>(null);
50
70
  const trWindow = useRef<HTMLDivElement>(null);
51
71
  const blWindow = useRef<HTMLDivElement>(null);
@@ -150,17 +170,40 @@ export default function MultiView(props: MultiViewProps) {
150
170
 
151
171
  // Event handling
152
172
  useEffect(() => {
153
- const sceneUpdate = () => {
173
+ const sceneUpdate = (evt: any) => {
154
174
  dispose(currentScene);
155
175
  scene.remove(currentScene);
156
- if (props.three.scene !== undefined) {
157
- currentScene = props.three.scene;
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;
158
183
  scene.add(currentScene);
184
+ sceneSet = true;
159
185
  }
160
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
+
161
200
  debugDispatcher.addEventListener(ToolEvents.SET_SCENE, sceneUpdate);
201
+ debugDispatcher.addEventListener(ToolEvents.ADD_CAMERA, addCamera);
202
+ debugDispatcher.addEventListener(ToolEvents.REMOVE_CAMERA, removeCamera);
162
203
  return () => {
163
204
  debugDispatcher.removeEventListener(ToolEvents.SET_SCENE, sceneUpdate);
205
+ debugDispatcher.removeEventListener(ToolEvents.ADD_CAMERA, addCamera);
206
+ debugDispatcher.removeEventListener(ToolEvents.REMOVE_CAMERA, removeCamera);
164
207
  };
165
208
  }, []);
166
209
 
@@ -277,6 +320,8 @@ export default function MultiView(props: MultiViewProps) {
277
320
  control.update();
278
321
  });
279
322
 
323
+ if (props.onSceneUpdate !== undefined && sceneSet) props.onSceneUpdate(currentScene);
324
+
280
325
  // Drawing
281
326
  renderer.clear();
282
327
  switch (mode) {
@@ -308,6 +353,123 @@ export default function MultiView(props: MultiViewProps) {
308
353
  };
309
354
  }, [mode, renderer]);
310
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
+
311
473
  // Camera names
312
474
  const cameraOptions: string[] = [];
313
475
  cameras.forEach((_: Camera, key: string) => {
@@ -318,7 +480,7 @@ export default function MultiView(props: MultiViewProps) {
318
480
  <div className='multiview'>
319
481
  <canvas ref={canvasRef} />
320
482
 
321
- <div className={`cameras ${mode === 'Single' || mode === 'Stacked' ? 'single' : ''}`}>
483
+ <div className={`cameras ${mode === 'Single' || mode === 'Stacked' ? 'single' : ''}`} ref={containerRef}>
322
484
  {mode === 'Single' && (
323
485
  <>
324
486
  <CameraWindow camera={tlCam} options={cameraOptions} ref={tlWindow} onSelect={(value: string) => {
@@ -408,7 +570,14 @@ export default function MultiView(props: MultiViewProps) {
408
570
  killControls();
409
571
  setMode(value as MultiViewMode);
410
572
  }}
573
+ open={modeOpen}
574
+ onToggle={(value: boolean) => {
575
+ setModeOpen(value);
576
+ if (renderModeOpen) setRenderModeOpen(false);
577
+ if (interactionModeOpen) setInteractionModeOpen(false);
578
+ }}
411
579
  />
580
+
412
581
  {/* Render Mode */}
413
582
  <Dropdown
414
583
  index={renderOptions.indexOf(currentRenderMode)}
@@ -435,6 +604,31 @@ export default function MultiView(props: MultiViewProps) {
435
604
  break;
436
605
  }
437
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
+ }}
438
632
  />
439
633
  </div>
440
634
  </div>
@@ -9,7 +9,7 @@ $icon_ui: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rh
9
9
  $icon_utils: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAACXBIWXMAAAsTAAALEwEAmpwYAAADFElEQVRYhe2YT0hVQRSHf7YwsUVoakFtKmhdUfhUeLSrrFVQEES72rip1EoscFlLK2jRpl21tT8QhFkLq13gIotqlaRPQ8SkgvRrcefiOO/eufN8LzPwwMV375w5893fnHPm+aoArWZb968BsmwNsFz7rwHzkgYltVdgnRpJZyU9lTQm6ZukUUl9GQwSkHS1AgUimwduAHUpvlnXLmCYYpsFDmXNT3p4AJhICDgC7C0RbgvwISHWGNASEiPpYdLbxvYJ2FQC4F1rbj/w1nx+ERoj6WE78NsDeSIw+E7gh5kzYJ5dM/dzwFbH/yDQ58ZJStAnkm570rY+sDDaTHEgqd88GzJ/ayWdk7Td3Ock3Zd0OrRINgKjKQq+ApoCFOw0/j8tteqAaSvWd6LtjgtyKkRBSZqRdFLS+4SxnKQBSU0ZCsaHfLXlOy3plqRxc79BUTtrNPe/QhWU9cbHgQ7gTYKSm1Pm5YAvlm+bM15PlOvXWSwcgGdurFJaRpOBsu010Oj4tVhbBtAFVHni3rR8z5QDKKNYEmSsZI6lPbTbmZ8HdgPbgMNE1R3bMFBbLqCABgNl20vgmAN30ZnXRXQqzbPYfmIbAXYkrbccwLTtts1VrjvFbxa4Q3GalA0Yb/fzALgua2zcjHcDp0hRrVKAAo4CCx64XmtsksDzt1KAbitx4a5YY1+BPctZZ7lwzfgLwlZuxQFb8LcSW7k5FlNgRbY4R5ToIXATREp3OM9a/xZgjqUnhG9bZ4D91linNTZpYlUU0IVzlbMBIGrE5z0+hVAlQ3POt63uwnZ+XvL4ThCQk5VUbhLYR1StNmSPM+eCM8erZBacr1rtE8JVw21Dlz0vVsCTk2lwDcBnE2CB4oKwz9YCyVvltqOkLw9xC/pIyj9jaYBHrMC9Hris3pYzPmmQPdZYeymAeWviPaAmAS4oyT1KrjexY8uXAlgNPLImP2BpnwtuExakXWxXHbjHZs2SiqQeGKTYpgKVy9ru2IbMWiVXcQxpfy1/t0y4+Go2MWJ7SMYvFVWw9hNwWbYGWK6tesA/ap/6uHJWeiAAAAAASUVORK5CYII=');
10
10
  $icon_world: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAACXBIWXMAAAsTAAALEwEAmpwYAAAEZklEQVRYhe2YW4iVVRTHf4465oSaUNlUjo7SmCBEPVipTWYXoV7qoejykhRCEVQP9pAFSWVBPdRTMNRLWGRaFr2FSShmEQ1KzQymUhpeMky6ONbcfj3sdZpvvvnO8Zy5lA/+YcP51jpr7bVv/7X2nqByNqPu/w7gTDgX4GgxaYR2DcDlwC3AUmAh0ARMDf1fwGGgE9gFfAb8AJyquSe1ljZFXa6+o/Y4FD3qvmh5neomdYXaUEuftQQ3T31d7Y8O96tdal98P69eos5SXwhZn7pb7cgE2qa2jHWAy9Svo4Nf1VfUG9VPQvauWp/5f726MXSb1WvVF9XjIftWvXWsArxdPRSOt5uWCfV+dUDtVJsL7K5Q98aM3xuyVnVr+Dqi3jHaAJepx8Lhe2pTyJvVn6PzVRXsHw7bw+rckDWqb4b8mHrbSAOcp7aHozZ1Wka3NuQfqHUVfExUt8R/n8rI69XXQt6lLijnoxzNTAEeA64GeoDzgbVBI5OBu+J/FwPrg3aK0A1cGL8fB+YAfUFDF4T+SmAN8ATwZ97BBC3MxdeTuGtqOBSYkGklgu/NyIpgtMnxPRDfpd9kdCuBT/MOimawHlgdwW0DPs918AAwH9gK7MjoyqEHWA6sAPYBGzMD6gWWRHCPhL/TQ4dYfPpOxSFYXKDvVE+YaKZamrpZPWmil7xuoXrQxAiL8vqiXHwTaU99BbTndItJe+Zoga4S2oFjpJR4TU7XBXwZs7oyb1gU4DKgH/iCtP+yaAlH+4E/agjwJCkXTwwfeewgLXdrNQEuAn6neIYaSfvwQA3BlXCAdEAaC3TtpEFclVcUHZIm0sauC2f1Ie8lLZGkmbiI8vSSR3fYDYSPyzJ990YfvQxS0r8oopkTJI76jbTUpRMnMI3Ekd2k0qnaenKAxKUNwN+k7ZH1Wxd9nswHWWvBWo7vxg1FSyzwC/AgsIfBJe4DngNWAW1UziB5dAPPAo8CbwPrGLrELcAGYEY1AR4C5pKW5WhOt5c0iwMxiFpQykBdpGo7i7kkwj+eNypa4g5gOsP5ChKX1ZG4sFbMjyDzgyb6mgl8V02AO0mndAnDZ/h70hZoJg2iWswMm37SKuRxA2kGtw/TFKSelipTXesIUt3uWlNd0R78EXifdEheYnixMDlm5BlqKxZmkChqHcOLhdnAxxQkgErl1jbgPM7CcgtS6nkDeDIcbQZ+IpVgk4A7gUtJBcVOKhesS0n5/QjwEYMF6yzg7gjwLVLuLxhi+X3TrH4TZXmbOj2jezrkZyr5JzlY8q/JyKsu+UdzaToaG/uhCvarw/agOscxvjSVWvbaucPBa+d9plvdXlORm7dbELo+9Z6Qjfm1MzuT2Yv7q9HZlpBtdOjFfar6Yeg2qdep6x2ni3upFT19dDj49PGy6emjMQZg6PaY9lkJ4/L0UWqlx6MNDn8g6o2g98fvPEb0eFSOB8+Eoue32Rm6OR200hn0sY0RPr+NNMD/DGf9C+u5AEeLfwBkrdN1844nCgAAAABJRU5ErkJggg==');
11
11
 
12
- #SceneHierarchy {
12
+ #SidePanel {
13
13
  background-color: #0d0d0d;
14
14
  border: 1px solid #111;
15
15
  max-height: 100%;
@@ -1,6 +1,6 @@
1
1
  @import 'theme';
2
2
  @import 'debug';
3
- @import 'sceneHierarchy';
3
+ @import 'sidePanel';
4
4
 
5
5
  .editor {
6
6
  font-family: Roboto Mono, Source Code Pro, Menlo, Courier, monospace;
@@ -3,17 +3,17 @@ import { Component, ReactNode } from 'react';
3
3
  // Models
4
4
  import { debugDispatcher, ToolEvents } from '../global';
5
5
  // Components
6
- import '../scss/_sceneHierarchy.scss';
6
+ import '../scss/_sidePanel.scss';
7
7
  import Accordion from './Accordion';
8
8
  import ContainerObject from './ContainerObject';
9
9
  import Inspector from './inspector/Inspector';
10
- import { SceneHierarchyState } from './types';
10
+ import { SidePanelState } from './types';
11
11
  import RemoteThree from '@/core/remote/RemoteThree';
12
12
 
13
- export default class SceneHierarchy extends Component<SceneHierarchyState> {
13
+ export default class SidePanel extends Component<SidePanelState> {
14
14
  private three: RemoteThree;
15
15
 
16
- constructor(props: SceneHierarchyState) {
16
+ constructor(props: SidePanelState) {
17
17
  super(props);
18
18
  this.state = {
19
19
  scene: props.scene !== undefined ? props.scene : null,
@@ -30,7 +30,7 @@ export default class SceneHierarchy extends Component<SceneHierarchyState> {
30
30
  const hasScene = this.componentState.scene !== null;
31
31
  const HierarchyName = 'Hierarchy - ' + (hasScene ? `${this.componentState.scene?.name}` : 'No Scene');
32
32
  return (
33
- <div id="SceneHierarchy" key="SceneHierarchy">
33
+ <div id="SidePanel" key="SidePanel">
34
34
  {(
35
35
  <>
36
36
  <Accordion label={HierarchyName} open={true}>
@@ -41,9 +41,7 @@ export default class SceneHierarchy extends Component<SceneHierarchyState> {
41
41
  </>
42
42
  </Accordion>
43
43
 
44
- <Accordion label='Inspector'>
45
- <Inspector key="Inspector" three={this.three} />
46
- </Accordion>
44
+ <Inspector three={this.three} />
47
45
  </>
48
46
  )}
49
47
  </div>
@@ -60,7 +58,7 @@ export default class SceneHierarchy extends Component<SceneHierarchyState> {
60
58
 
61
59
  // Getters / Setters
62
60
 
63
- get componentState(): SceneHierarchyState {
64
- return this.state as SceneHierarchyState;
61
+ get componentState(): SidePanelState {
62
+ return this.state as SidePanelState;
65
63
  }
66
64
  }
@@ -0,0 +1,111 @@
1
+ import { useEffect, useState } from "react";
2
+ import { CoreComponentProps, RemoteObject } from "../types";
3
+ import { ToolEvents, debugDispatcher } from "../../global";
4
+ // Components
5
+ import './inspector.scss';
6
+ import Accordion from "../Accordion";
7
+ import InspectorField from './InspectorField';
8
+ // Utils
9
+ import { InspectCamera } from "./utils/InspectCamera";
10
+ import { InspectMaterial } from "./utils/InspectMaterial";
11
+ import { InspectTransform } from "./utils/InspectTransform";
12
+ import { InspectLight } from "./utils/InspectLight";
13
+ import { setItemProps } from "../utils";
14
+ import InspectAnimation from "./utils/InspectAnimation";
15
+
16
+ const defaultObject: RemoteObject = {
17
+ name: '',
18
+ uuid: '',
19
+ type: '',
20
+ visible: false,
21
+ matrix: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
22
+ animations: [],
23
+ material: undefined,
24
+ perspectiveCameraInfo: undefined,
25
+ orthographicCameraInfo: undefined,
26
+ lightInfo: undefined,
27
+ };
28
+
29
+ export default function Inspector(props: CoreComponentProps) {
30
+ const [currentObject, setCurrentObject] = useState<RemoteObject>(defaultObject);
31
+
32
+ useEffect(() => {
33
+ function onSelectItem(evt: any) {
34
+ const obj = evt.value as RemoteObject;
35
+ setCurrentObject(obj);
36
+ }
37
+
38
+ function setScene() {
39
+ setCurrentObject(defaultObject);
40
+ }
41
+
42
+ debugDispatcher.addEventListener(ToolEvents.SET_SCENE, setScene);
43
+ debugDispatcher.addEventListener(ToolEvents.SET_OBJECT, onSelectItem);
44
+ return () => {
45
+ debugDispatcher.removeEventListener(ToolEvents.SET_SCENE, setScene);
46
+ debugDispatcher.removeEventListener(ToolEvents.SET_OBJECT, onSelectItem);
47
+ };
48
+ }, []);
49
+
50
+ const objType = currentObject.type.toLowerCase();
51
+
52
+ return (
53
+ <Accordion label='Inspector' key='Inspector'>
54
+ <div id="Inspector" className={props.class}>
55
+ {currentObject.uuid.length > 0 && (
56
+ <>
57
+ {/* Core */}
58
+ <>
59
+ <InspectorField
60
+ type="string"
61
+ title="Name"
62
+ prop="name"
63
+ value={currentObject.name}
64
+ disabled={true}
65
+ />
66
+ <InspectorField
67
+ type="string"
68
+ title="Type"
69
+ prop="type"
70
+ value={currentObject.type}
71
+ disabled={true}
72
+ />
73
+ <InspectorField
74
+ type="string"
75
+ title="UUID"
76
+ prop="uuid"
77
+ value={currentObject.uuid}
78
+ disabled={true}
79
+ />
80
+ <InspectorField
81
+ type="boolean"
82
+ title="Visible"
83
+ prop="visible"
84
+ value={currentObject.visible}
85
+ onChange={(key: string, value: any) => {
86
+ props.three.updateObject(currentObject.uuid, key, value);
87
+ const child = props.three.scene?.getObjectByProperty('uuid', currentObject.uuid);
88
+ if (child !== undefined) setItemProps(child, key, value);
89
+ }}
90
+ />
91
+ </>
92
+
93
+ {/* Data */}
94
+ <>
95
+ {/* Transform */}
96
+ {InspectTransform(currentObject, props.three)}
97
+ {/* Animations */}
98
+ {currentObject.animations.length > 0 ? InspectAnimation(currentObject, props.three) : null}
99
+ {/* Cameras */}
100
+ {objType.search('camera') > -1 ? InspectCamera(currentObject, props.three) : null}
101
+ {/* Lights */}
102
+ {objType.search('light') > -1 ? InspectLight(currentObject, props.three) : null}
103
+ {/* Material */}
104
+ {objType.search('mesh') > -1 ? InspectMaterial(currentObject, props.three) : null}
105
+ </>
106
+ </>
107
+ )}
108
+ </div>
109
+ </Accordion>
110
+ );
111
+ }
@@ -0,0 +1,51 @@
1
+ import RemoteThree from "@/core/remote/RemoteThree";
2
+ import InspectorGroup from "../InspectorGroup";
3
+ import { InspectorFieldProps } from '../InspectorField';
4
+ import { AnimationClipInfo, RemoteObject } from '../../types';
5
+ import { setItemProps } from "../../utils";
6
+
7
+ export default function InspectAnimation(obj: RemoteObject, three: RemoteThree) {
8
+ const items: InspectorFieldProps[] = [];
9
+ obj.animations.forEach((value: AnimationClipInfo) => {
10
+ // Add animation
11
+ items.push({
12
+ title: 'Name',
13
+ type: 'string',
14
+ prop: 'name',
15
+ value: value.name,
16
+ disabled: true,
17
+ onChange: (prop: string, value: any) => {
18
+ three.updateObject(obj.uuid, prop, value);
19
+ const child = three.scene?.getObjectByProperty('uuid', obj.uuid);
20
+ if (child !== undefined) setItemProps(child, prop, value);
21
+ },
22
+ });
23
+ items.push({
24
+ title: 'Duration',
25
+ type: 'number',
26
+ prop: 'duration',
27
+ value: value.duration,
28
+ disabled: true,
29
+ onChange: (prop: string, value: any) => {
30
+ three.updateObject(obj.uuid, prop, value);
31
+ const child = three.scene?.getObjectByProperty('uuid', obj.uuid);
32
+ if (child !== undefined) setItemProps(child, prop, value);
33
+ },
34
+ });
35
+ items.push({
36
+ title: 'Blend Mode',
37
+ type: 'number',
38
+ prop: 'blendMode',
39
+ value: value.blendMode,
40
+ disabled: true,
41
+ onChange: (prop: string, value: any) => {
42
+ three.updateObject(obj.uuid, prop, value);
43
+ const child = three.scene?.getObjectByProperty('uuid', obj.uuid);
44
+ if (child !== undefined) setItemProps(child, prop, value);
45
+ },
46
+ });
47
+ });
48
+ return (
49
+ <InspectorGroup title="Animations" items={items} />
50
+ );
51
+ }
@@ -11,7 +11,7 @@ export interface ChildObjectProps extends CoreComponentProps {
11
11
  three: RemoteThree
12
12
  }
13
13
 
14
- export interface SceneHierarchyState {
14
+ export interface SidePanelState {
15
15
  scene?: Object3D
16
16
  three: RemoteThree
17
17
  }
@@ -71,6 +71,14 @@ export interface RemoteMaterial {
71
71
  specularColor?: Color
72
72
  }
73
73
 
74
+ // Animation Info
75
+
76
+ export interface AnimationClipInfo {
77
+ name: string;
78
+ duration: number;
79
+ blendMode: number;
80
+ }
81
+
74
82
  // Camera Info
75
83
 
76
84
  export interface PerspectiveCameraInfo {
@@ -114,6 +122,7 @@ export interface RemoteObject {
114
122
  type: string
115
123
  visible: boolean
116
124
  matrix: number[] // based on Matrix4.elements
125
+ animations: AnimationClipInfo[]
117
126
  material?: RemoteMaterial | RemoteMaterial[]
118
127
  perspectiveCameraInfo?: PerspectiveCameraInfo
119
128
  orthographicCameraInfo?: OrthographicCameraInfo