@shopware-ag/dive 1.5.0 → 1.6.0

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 (41) hide show
  1. package/README.md +38 -25
  2. package/build/dive.cjs +717 -500
  3. package/build/dive.cjs.map +1 -1
  4. package/build/dive.d.cts +174 -113
  5. package/build/dive.d.ts +174 -113
  6. package/build/dive.js +716 -486
  7. package/build/dive.js.map +1 -1
  8. package/package.json +1 -1
  9. package/src/__test__/DIVE.test.ts +66 -22
  10. package/src/animation/AnimationSystem.ts +16 -0
  11. package/src/animation/__test__/AnimationSystem.test.ts +23 -2
  12. package/src/axiscamera/AxisCamera.ts +40 -2
  13. package/src/axiscamera/__test__/AxisCamera.test.ts +178 -5
  14. package/src/com/Communication.ts +50 -16
  15. package/src/com/__test__/Communication.test.ts +73 -24
  16. package/src/com/actions/camera/computeencompassingview.ts +9 -0
  17. package/src/com/actions/index.ts +2 -0
  18. package/src/com/actions/scene/updatescene.ts +1 -0
  19. package/src/controls/OrbitControls.ts +14 -2
  20. package/src/controls/__test__/OrbitControls.test.ts +31 -4
  21. package/src/dive.ts +93 -33
  22. package/src/grid/Grid.ts +4 -0
  23. package/src/grid/__test__/Grid.test.ts +7 -0
  24. package/src/interface/Selectable.ts +17 -0
  25. package/src/interface/__test__/Interfaces.test.ts +18 -0
  26. package/src/mediacreator/MediaCreator.ts +2 -2
  27. package/src/mediacreator/__test__/MediaCreator.test.ts +12 -10
  28. package/src/renderer/Renderer.ts +7 -1
  29. package/src/renderer/__test__/Renderer.test.ts +14 -5
  30. package/src/scene/Scene.ts +8 -2
  31. package/src/scene/__test__/Scene.test.ts +6 -0
  32. package/src/scene/root/Root.ts +11 -1
  33. package/src/scene/root/__test__/Root.test.ts +68 -2
  34. package/src/toolbox/BaseTool.ts +1 -1
  35. package/src/toolbox/Toolbox.ts +53 -37
  36. package/src/toolbox/__test__/BaseTool.test.ts +43 -7
  37. package/src/toolbox/__test__/Toolbox.test.ts +39 -44
  38. package/src/toolbox/select/SelectTool.ts +17 -28
  39. package/src/toolbox/select/__test__/SelectTool.test.ts +21 -12
  40. package/src/toolbox/transform/TransformTool.ts +7 -1
  41. package/src/toolbox/transform/__test__/TransformTool.test.ts +22 -5
@@ -1,6 +1,6 @@
1
- import DIVEPerspectiveCamera from '../../camera/PerspectiveCamera';
2
- import DIVEScene from '../../scene/Scene';
3
- import DIVERenderer, { DIVERendererDefaultSettings } from '../Renderer';
1
+ import type DIVEPerspectiveCamera from '../../camera/PerspectiveCamera';
2
+ import type DIVEScene from '../../scene/Scene';
3
+ import { DIVERenderer, DIVERendererDefaultSettings } from '../Renderer';
4
4
 
5
5
  /**
6
6
  * @jest-environment jsdom
@@ -20,6 +20,7 @@ jest.mock('three', () => {
20
20
  position: 'absolute',
21
21
  },
22
22
  };
23
+ this.dispose = jest.fn();
23
24
  this.debug = {
24
25
  checkShaderErrors: true,
25
26
  };
@@ -43,12 +44,20 @@ let renderer: DIVERenderer;
43
44
  describe('dive/renderer/DIVERenderer', () => {
44
45
  beforeEach(() => {
45
46
  jest.clearAllMocks();
46
- renderer = new DIVERenderer(DIVERendererDefaultSettings);
47
+ renderer = new DIVERenderer();
47
48
  });
48
49
 
49
50
  it('should instantiate', () => {
50
51
  expect(renderer).toBeDefined();
51
- renderer = new DIVERenderer();
52
+ });
53
+
54
+ it('should instantiate with settings parameter', () => {
55
+ renderer = new DIVERenderer(DIVERendererDefaultSettings);
56
+ expect(renderer).toBeDefined();
57
+ });
58
+
59
+ it('should dispose', () => {
60
+ renderer.Dispose();
52
61
  });
53
62
 
54
63
  it('should start render', () => {
@@ -1,5 +1,5 @@
1
- import { Color, ColorRepresentation, Object3D, Scene } from 'three';
2
- import { COMModel, COMEntity } from '../com/types';
1
+ import { Color, Scene, type Box3, type ColorRepresentation, type Object3D } from 'three';
2
+ import { type COMModel, type COMEntity } from '../com/types';
3
3
  import DIVERoot from './root/Root';
4
4
 
5
5
  /**
@@ -19,6 +19,8 @@ export default class DIVEScene extends Scene {
19
19
  constructor() {
20
20
  super();
21
21
 
22
+ this.background = new Color(0xffffff);
23
+
22
24
  this.root = new DIVERoot();
23
25
  this.add(this.root);
24
26
  }
@@ -27,6 +29,10 @@ export default class DIVEScene extends Scene {
27
29
  this.background = new Color(color);
28
30
  }
29
31
 
32
+ public ComputeSceneBB(): Box3 {
33
+ return this.Root.ComputeSceneBB();
34
+ }
35
+
30
36
  public GetSceneObject(object: Partial<COMEntity>): Object3D | undefined {
31
37
  return this.Root.GetSceneObject(object);
32
38
  }
@@ -17,6 +17,7 @@ jest.mock('../root/Root', () => {
17
17
  this.PlaceOnFloor = mock_PlaceOnFloor;
18
18
  this.GetSceneObject = mock_GetSceneObject;
19
19
  this.removeFromParent = jest.fn();
20
+ this.ComputeSceneBB = jest.fn();
20
21
  return this;
21
22
  });
22
23
  });
@@ -38,6 +39,11 @@ describe('dive/scene/DIVEScene', () => {
38
39
  expect((scene.background as Color).getHex()).toBe(0x123456);
39
40
  });
40
41
 
42
+ it('should ComputeSceneBB', () => {
43
+ const scene = new DIVEScene();
44
+ expect(() => scene.ComputeSceneBB()).not.toThrow();
45
+ });
46
+
41
47
  it('should add object', () => {
42
48
  const scene = new DIVEScene();
43
49
  scene.AddSceneObject({} as COMEntity);
@@ -1,4 +1,4 @@
1
- import { Object3D } from "three";
1
+ import { Box3, Object3D } from "three";
2
2
  import DIVELightRoot from "./lightroot/LightRoot.ts";
3
3
  import DIVEModelRoot from "./modelroot/ModelRoot.ts";
4
4
  import { COMLight, COMModel, COMEntity } from "../../com/types.ts";
@@ -39,6 +39,16 @@ export default class DIVERoot extends Object3D {
39
39
  this.add(this.grid);
40
40
  }
41
41
 
42
+ public ComputeSceneBB(): Box3 {
43
+ const bb = new Box3();
44
+ this.modelRoot.traverse((object: Object3D) => {
45
+ if ('isObject3D' in object) {
46
+ bb.expandByObject(object);
47
+ }
48
+ });
49
+ return bb;
50
+ }
51
+
42
52
  public GetSceneObject(object: Partial<COMEntity>): Object3D | undefined {
43
53
  switch (object.entityType) {
44
54
  case "pov": {
@@ -1,5 +1,6 @@
1
- import { COMLight, COMModel, COMPov } from '../../../com';
2
1
  import DIVERoot from '../Root';
2
+ import { type Vector3, type Object3D } from 'three';
3
+ import { type COMLight, type COMModel, type COMPov } from '../../../com';
3
4
 
4
5
  const mock_UpdateLight = jest.fn();
5
6
  const mock_UpdateModel = jest.fn();
@@ -9,6 +10,62 @@ const mock_DeleteLight = jest.fn();
9
10
  const mock_DeleteModel = jest.fn();
10
11
  const mock_PlaceOnFloor = jest.fn();
11
12
 
13
+ jest.mock('three', () => {
14
+ return {
15
+ Box3: jest.fn(() => {
16
+ return {
17
+ expandByObject: jest.fn(),
18
+ setFromObject: jest.fn(),
19
+ applyMatrix4: jest.fn(),
20
+ union: jest.fn(),
21
+ isEmpty: jest.fn(),
22
+ getCenter: jest.fn(),
23
+ getSize: jest.fn(),
24
+ getBoundingSphere: jest.fn(),
25
+ };
26
+ }),
27
+ Object3D: jest.fn(function () {
28
+ this.clear = jest.fn();
29
+ this.color = {};
30
+ this.intensity = 0;
31
+ this.layers = {
32
+ mask: 0,
33
+ };
34
+ this.shadow = {
35
+ radius: 0,
36
+ mapSize: { width: 0, height: 0 },
37
+ bias: 0,
38
+ camera: {
39
+ near: 0,
40
+ far: 0,
41
+ fov: 0,
42
+ },
43
+ }
44
+ this.add = jest.fn();
45
+ this.userData = {};
46
+ this.rotation = {
47
+ x: 0,
48
+ y: 0,
49
+ z: 0,
50
+ setFromVector3: jest.fn(),
51
+ };
52
+ this.scale = {
53
+ x: 1,
54
+ y: 1,
55
+ z: 1,
56
+ set: jest.fn(),
57
+ };
58
+ this.localToWorld = (vec3: Vector3) => {
59
+ return vec3;
60
+ };
61
+ this.traverse = jest.fn((callback) => {
62
+ callback(this.children[0])
63
+ });
64
+ return this;
65
+ }),
66
+ }
67
+ });
68
+
12
69
  jest.mock('../../../primitive/floor/Floor', () => {
13
70
  return jest.fn(function () {
14
71
  this.isObject3D = true;
@@ -52,6 +109,9 @@ jest.mock('../modelroot/ModelRoot', () => {
52
109
  this.PlaceOnFloor = mock_PlaceOnFloor;
53
110
  this.GetModel = mock_GetModel;
54
111
  this.removeFromParent = jest.fn();
112
+ this.traverse = jest.fn((callback: (object: Object3D) => void) => {
113
+ callback(this);
114
+ });
55
115
  return this;
56
116
  });
57
117
  });
@@ -64,7 +124,7 @@ describe('DIVE/scene/root/DIVERoot', () => {
64
124
  it('should instantiate', () => {
65
125
  const root = new DIVERoot();
66
126
  expect(root).toBeDefined();
67
- expect(root.children).toHaveLength(4);
127
+ expect(root.add).toHaveBeenCalledTimes(4);
68
128
  });
69
129
 
70
130
  it('should have Floor', () => {
@@ -77,6 +137,12 @@ describe('DIVE/scene/root/DIVERoot', () => {
77
137
  expect(root.Grid).toBeDefined();
78
138
  });
79
139
 
140
+ it('should ComputeSceneBB', () => {
141
+ const root = new DIVERoot();
142
+ const bb = root.ComputeSceneBB();
143
+ expect(bb).toBeDefined();
144
+ });
145
+
80
146
  it('should add object', () => {
81
147
  const root = new DIVERoot();
82
148
  root.AddSceneObject({ entityType: 'light' } as COMLight);
@@ -13,7 +13,7 @@ export type DraggableEvent = {
13
13
  }
14
14
 
15
15
  /* eslint-disable @typescript-eslint/no-unused-vars */
16
- export default abstract class DIVEBaseTool {
16
+ export abstract class DIVEBaseTool {
17
17
  readonly POINTER_DRAG_THRESHOLD: number = 0.001;
18
18
 
19
19
  public name: string;
@@ -1,7 +1,9 @@
1
- import DIVEOrbitControls from "../controls/OrbitControls.ts";
2
- import DIVEScene from "../scene/Scene.ts";
3
- import DIVEBaseTool from "./BaseTool.ts";
4
- import DIVESelectTool from "./select/SelectTool.ts";
1
+ import type DIVEOrbitControls from "../controls/OrbitControls.ts";
2
+ import type DIVEScene from "../scene/Scene.ts";
3
+ import { type DIVEBaseTool } from "./BaseTool.ts";
4
+ import { type DIVESelectTool } from "./select/SelectTool.ts";
5
+
6
+ export type ToolType = 'select' | 'none';
5
7
 
6
8
  /**
7
9
  * A Toolbox to activate and deactivate tools to use with the pointer.
@@ -12,51 +14,51 @@ import DIVESelectTool from "./select/SelectTool.ts";
12
14
  export default class DIVEToolbox {
13
15
  public static readonly DefaultTool = 'select';
14
16
 
15
- private activeTool: DIVEBaseTool;
17
+ private _scene: DIVEScene;
18
+ private _controller: DIVEOrbitControls;
16
19
 
17
- private selectTool: DIVESelectTool;
20
+ private _activeTool: DIVEBaseTool | null;
18
21
 
19
- private removeListenersCallback: () => void = () => { };
22
+ private _selectTool: DIVESelectTool | null;
23
+ public get selectTool(): DIVESelectTool {
24
+ if (!this._selectTool) {
25
+ const DIVESelectTool = require('./select/SelectTool.ts').DIVESelectTool as typeof import('./select/SelectTool.ts').DIVESelectTool;
26
+ this._selectTool = new DIVESelectTool(this._scene, this._controller);
27
+ }
28
+ return this._selectTool;
29
+ }
20
30
 
21
31
  constructor(scene: DIVEScene, controller: DIVEOrbitControls) {
22
- this.selectTool = new DIVESelectTool(scene, controller);
32
+ this._scene = scene;
33
+ this._controller = controller;
23
34
 
24
- const pointerMove = this.onPointerMove.bind(this);
25
- const pointerDown = this.onPointerDown.bind(this);
26
- const pointerUp = this.onPointerUp.bind(this);
27
- const wheel = this.onWheel.bind(this);
28
-
29
- controller.domElement.addEventListener('pointermove', pointerMove);
30
- controller.domElement.addEventListener('pointerdown', pointerDown);
31
- controller.domElement.addEventListener('pointerup', pointerUp);
32
- controller.domElement.addEventListener('wheel', wheel);
33
-
34
- this.removeListenersCallback = () => {
35
- controller.domElement.removeEventListener('pointermove', pointerMove);
36
- controller.domElement.removeEventListener('pointerdown', pointerDown);
37
- controller.domElement.removeEventListener('pointerup', pointerUp);
38
- controller.domElement.removeEventListener('wheel', wheel);
39
- };
35
+ // toolset
36
+ this._selectTool = null;
40
37
 
41
38
  // default tool
42
- this.activeTool = this.selectTool;
43
- this.activeTool.Activate();
39
+ this._activeTool = null;
44
40
  }
45
41
 
46
- public dispose(): void {
47
- this.removeListenersCallback();
42
+ public Dispose(): void {
43
+ this.removeEventListeners();
48
44
  }
49
45
 
50
- public GetActiveTool(): DIVEBaseTool {
51
- return this.activeTool;
46
+ public GetActiveTool(): DIVEBaseTool | null {
47
+ return this._activeTool;
52
48
  }
53
49
 
54
- public UseTool(tool: string): void {
55
- this.activeTool.Deactivate();
50
+ public UseTool(tool: ToolType): void {
51
+ this._activeTool?.Deactivate();
56
52
  switch (tool) {
57
53
  case "select": {
54
+ this.addEventListeners();
58
55
  this.selectTool.Activate();
59
- this.activeTool = this.selectTool;
56
+ this._activeTool = this.selectTool;
57
+ break;
58
+ }
59
+ case "none": {
60
+ this.removeEventListeners();
61
+ this._activeTool = null;
60
62
  break;
61
63
  }
62
64
  default: {
@@ -74,18 +76,32 @@ export default class DIVEToolbox {
74
76
  }
75
77
 
76
78
  public onPointerMove(e: PointerEvent): void {
77
- this.activeTool.onPointerMove(e);
79
+ this._activeTool?.onPointerMove(e);
78
80
  }
79
81
 
80
82
  public onPointerDown(e: PointerEvent): void {
81
- this.activeTool.onPointerDown(e);
83
+ this._activeTool?.onPointerDown(e);
82
84
  }
83
85
 
84
86
  public onPointerUp(e: PointerEvent): void {
85
- this.activeTool.onPointerUp(e);
87
+ this._activeTool?.onPointerUp(e);
86
88
  }
87
89
 
88
90
  public onWheel(e: WheelEvent): void {
89
- this.activeTool.onWheel(e);
91
+ this._activeTool?.onWheel(e);
92
+ }
93
+
94
+ private addEventListeners(): void {
95
+ this._controller.domElement.addEventListener('pointermove', (e) => this.onPointerMove(e));
96
+ this._controller.domElement.addEventListener('pointerdown', (e) => this.onPointerDown(e));
97
+ this._controller.domElement.addEventListener('pointerup', (e) => this.onPointerUp(e));
98
+ this._controller.domElement.addEventListener('wheel', (e) => this.onWheel(e));
99
+ }
100
+
101
+ private removeEventListeners(): void {
102
+ this._controller.domElement.removeEventListener('pointermove', (e) => this.onPointerMove(e));
103
+ this._controller.domElement.removeEventListener('pointerdown', (e) => this.onPointerDown(e));
104
+ this._controller.domElement.removeEventListener('pointerup', (e) => this.onPointerUp(e));
105
+ this._controller.domElement.removeEventListener('wheel', (e) => this.onWheel(e));
90
106
  }
91
107
  }
@@ -1,11 +1,10 @@
1
1
 
2
+ import { DIVEBaseTool } from '../BaseTool';
3
+ import type DIVEOrbitControls from '../../controls/OrbitControls';
4
+ import type DIVEScene from '../../scene/Scene';
2
5
  import { type Object3D, type Vector3 } from 'three';
3
- import DIVEOrbitControls from '../../controls/OrbitControls';
4
- import DIVEScene from '../../scene/Scene';
5
- import DIVEBaseTool from '../BaseTool';
6
- import DIVEToolbox from '../Toolbox';
7
6
  import { type DIVEHoverable } from '../../interface/Hoverable';
8
- import { DIVEDraggable } from '../../interface/Draggable';
7
+ import { type DIVEDraggable } from '../../interface/Draggable';
9
8
 
10
9
  /**
11
10
  * @jest-environment jsdom
@@ -47,8 +46,18 @@ describe('dive/toolbox/DIVEBaseTool', () => {
47
46
  });
48
47
 
49
48
  it('should instantiate', () => {
50
- const toolBox = new abstractWrapper(mockScene, mockController);
51
- expect(toolBox).toBeDefined();
49
+ const baseTool = new abstractWrapper(mockScene, mockController);
50
+ expect(baseTool).toBeDefined();
51
+ });
52
+
53
+ it('should Activate', () => {
54
+ const baseTool = new abstractWrapper(mockScene, mockController);
55
+ expect(() => baseTool.Activate()).not.toThrow();
56
+ });
57
+
58
+ it('should Deactivate', () => {
59
+ const baseTool = new abstractWrapper(mockScene, mockController);
60
+ expect(() => baseTool.Deactivate()).not.toThrow();
52
61
  });
53
62
 
54
63
  it('should raycast', () => {
@@ -63,7 +72,24 @@ describe('dive/toolbox/DIVEBaseTool', () => {
63
72
  expect(toolBox['_pointerAnyDown']).toBeDefined();
64
73
  expect(toolBox['_pointerAnyDown']).toBe(false);
65
74
 
75
+ toolBox['_pointerPrimaryDown'] = false;
76
+ toolBox['_pointerMiddleDown'] = false;
77
+ toolBox['_pointerSecondaryDown'] = false;
78
+ expect(toolBox['_pointerAnyDown']).toBe(false);
79
+
66
80
  toolBox['_pointerPrimaryDown'] = true;
81
+ toolBox['_pointerMiddleDown'] = false;
82
+ toolBox['_pointerSecondaryDown'] = false;
83
+ expect(toolBox['_pointerAnyDown']).toBe(true);
84
+
85
+ toolBox['_pointerPrimaryDown'] = false;
86
+ toolBox['_pointerMiddleDown'] = true;
87
+ toolBox['_pointerSecondaryDown'] = false;
88
+ expect(toolBox['_pointerAnyDown']).toBe(true);
89
+
90
+ toolBox['_pointerPrimaryDown'] = false;
91
+ toolBox['_pointerMiddleDown'] = false;
92
+ toolBox['_pointerSecondaryDown'] = true;
67
93
  expect(toolBox['_pointerAnyDown']).toBe(true);
68
94
  });
69
95
 
@@ -388,8 +414,18 @@ describe('dive/toolbox/DIVEBaseTool', () => {
388
414
  expect(() => toolBox.onDrag({} as PointerEvent)).not.toThrow();
389
415
  });
390
416
 
417
+ it('should execute onCLick correctly', () => {
418
+ const toolBox = new abstractWrapper(mockScene, mockController);
419
+ expect(() => toolBox.onClick({} as PointerEvent)).not.toThrow();
420
+ });
421
+
391
422
  it('should execute onDragEnd correctly', () => {
392
423
  const toolBox = new abstractWrapper(mockScene, mockController);
393
424
  expect(() => toolBox.onDragEnd({} as PointerEvent)).not.toThrow();
394
425
  });
426
+
427
+ it('should execute onWheel correctly', () => {
428
+ const toolBox = new abstractWrapper(mockScene, mockController);
429
+ expect(() => toolBox.onWheel({} as WheelEvent)).not.toThrow();
430
+ });
395
431
  });
@@ -1,6 +1,6 @@
1
- import DIVEOrbitControls from '../../controls/OrbitControls';
2
- import DIVEScene from '../../scene/Scene';
3
- import DIVEToolbox from '../Toolbox';
1
+ import DIVEToolbox, { type ToolType } from '../Toolbox';
2
+ import type DIVEOrbitControls from '../../controls/OrbitControls';
3
+ import type DIVEScene from '../../scene/Scene';
4
4
 
5
5
  /**
6
6
  * @jest-environment jsdom
@@ -21,27 +21,20 @@ const mock_Canvas = {
21
21
  offsetTop: 0,
22
22
  };
23
23
 
24
- const mock_Activate = jest.fn();
25
- const mock_Deactivate = jest.fn();
26
- const mock_onPointerDown = jest.fn();
27
- const mock_onPointerMove = jest.fn();
28
- const mock_onPointerUp = jest.fn();
29
- const mock_onWheel = jest.fn();
30
- const mock_SetGizmoMode = jest.fn();
31
- const mock_SetGizmoVisibility = jest.fn();
32
-
33
24
  jest.mock('../select/SelectTool.ts', () => {
34
- return jest.fn(function () {
35
- this.Activate = mock_Activate;
36
- this.Deactivate = mock_Deactivate;
37
- this.onPointerDown = mock_onPointerDown;
38
- this.onPointerMove = mock_onPointerMove;
39
- this.onPointerUp = mock_onPointerUp;
40
- this.onWheel = mock_onWheel;
41
- this.SetGizmoMode = mock_SetGizmoMode;
42
- this.SetGizmoVisibility = mock_SetGizmoVisibility;
43
- return this;
44
- });
25
+ return {
26
+ DIVESelectTool: jest.fn(function () {
27
+ this.Activate = jest.fn();
28
+ this.Deactivate = jest.fn();
29
+ this.onPointerDown = jest.fn();
30
+ this.onPointerMove = jest.fn();
31
+ this.onPointerUp = jest.fn();
32
+ this.onWheel = jest.fn();
33
+ this.SetGizmoMode = jest.fn();
34
+ this.SetGizmoVisibility = jest.fn();
35
+ return this;
36
+ })
37
+ }
45
38
  });
46
39
 
47
40
  const mockController = {
@@ -58,52 +51,56 @@ describe('dive/toolbox/DIVEToolBox', () => {
58
51
  it('should instantiate', () => {
59
52
  const toolBox = new DIVEToolbox({} as DIVEScene, mockController);
60
53
  expect(toolBox).toBeDefined();
61
- expect(mock_Activate).toHaveBeenCalledTimes(1);
62
- expect(mock_addEventListener).toHaveBeenCalled();
63
54
  });
64
55
 
65
56
  it('should dispose', () => {
66
57
  const toolBox = new DIVEToolbox({} as DIVEScene, mockController);
67
- toolBox.dispose();
58
+ toolBox.Dispose();
68
59
  expect(mock_removeEventListener).toHaveBeenCalled();
69
60
  });
70
61
 
71
62
  it('should throw with incorrect tool', () => {
72
63
  const toolBox = new DIVEToolbox({} as DIVEScene, mockController);
73
- expect(() => toolBox.UseTool('not a real tool')).toThrow();
74
- expect(mock_Deactivate).toHaveBeenCalledTimes(1);
64
+ expect(() => toolBox.UseTool('not a real tool' as unknown as ToolType)).toThrow();
65
+ });
66
+
67
+ it('should use no tool', () => {
68
+ const toolBox = new DIVEToolbox({} as DIVEScene, mockController);
69
+ expect(() => toolBox.UseTool('select')).not.toThrow();
70
+ expect(() => toolBox.UseTool('none')).not.toThrow();
75
71
  });
76
72
 
77
73
  it('should use select tool', () => {
78
74
  const toolBox = new DIVEToolbox({} as DIVEScene, mockController);
79
- expect(mock_Activate).toHaveBeenCalledTimes(1);
80
- toolBox.UseTool(DIVEToolbox.DefaultTool);
81
- expect(mock_Deactivate).toHaveBeenCalledTimes(1);
82
- expect(mock_Activate).toHaveBeenCalledTimes(2);
75
+ expect(() => toolBox.UseTool(DIVEToolbox.DefaultTool)).not.toThrow();
83
76
  });
84
77
 
85
78
  it('should execute pointer down event on tool', () => {
86
79
  const toolBox = new DIVEToolbox({} as DIVEScene, mockController);
87
- toolBox.onPointerDown({ type: 'pointerdown' } as PointerEvent);
88
- expect(mock_onPointerDown).toHaveBeenCalledTimes(1);
80
+ expect(() => toolBox.onPointerDown({ type: 'pointerdown' } as PointerEvent)).not.toThrow();
81
+ expect(() => toolBox.UseTool('select')).not.toThrow();
82
+ expect(() => toolBox.onPointerDown({ type: 'pointerdown' } as PointerEvent)).not.toThrow();
89
83
  });
90
84
 
91
85
  it('should execute pointer move event on tool', () => {
92
86
  const toolBox = new DIVEToolbox({} as DIVEScene, mockController);
93
- toolBox.onPointerMove({ type: 'pointermove' } as PointerEvent);
94
- expect(mock_onPointerMove).toHaveBeenCalledTimes(1);
87
+ expect(() => toolBox.onPointerMove({ type: 'pointermove' } as PointerEvent)).not.toThrow();
88
+ expect(() => toolBox.UseTool('select')).not.toThrow();
89
+ expect(() => toolBox.onPointerMove({ type: 'pointermove' } as PointerEvent)).not.toThrow();
95
90
  });
96
91
 
97
92
  it('should execute pointer up event on tool', () => {
98
93
  const toolBox = new DIVEToolbox({} as DIVEScene, mockController);
99
- toolBox.onPointerUp({ type: 'pointerup' } as PointerEvent);
100
- expect(mock_onPointerUp).toHaveBeenCalledTimes(1);
94
+ expect(() => toolBox.onPointerUp({ type: 'pointerup' } as PointerEvent)).not.toThrow();
95
+ expect(() => toolBox.UseTool('select')).not.toThrow();
96
+ expect(() => toolBox.onPointerUp({ type: 'pointerup' } as PointerEvent)).not.toThrow();
101
97
  });
102
98
 
103
99
  it('should execute wheel event on tool', () => {
104
100
  const toolBox = new DIVEToolbox({} as DIVEScene, mockController);
105
- toolBox.onWheel({ type: 'wheel' } as WheelEvent);
106
- expect(mock_onWheel).toHaveBeenCalledTimes(1);
101
+ expect(() => toolBox.onWheel({ type: 'wheel' } as WheelEvent)).not.toThrow();
102
+ expect(() => toolBox.UseTool('select')).not.toThrow();
103
+ expect(() => toolBox.onWheel({ type: 'wheel' } as WheelEvent)).not.toThrow();
107
104
  });
108
105
 
109
106
  it('should get active tool', () => {
@@ -113,13 +110,11 @@ describe('dive/toolbox/DIVEToolBox', () => {
113
110
 
114
111
  it('should set gizmo mode', () => {
115
112
  const toolBox = new DIVEToolbox({} as DIVEScene, mockController);
116
- toolBox.SetGizmoMode('translate');
117
- expect(mock_SetGizmoMode).toHaveBeenCalledTimes(1);
113
+ expect(() => toolBox.SetGizmoMode('translate')).not.toThrow();
118
114
  });
119
115
 
120
116
  it('should set gizmo active', () => {
121
117
  const toolBox = new DIVEToolbox({} as DIVEScene, mockController);
122
- toolBox.SetGizmoVisibility(true);
123
- expect(mock_SetGizmoVisibility).toHaveBeenCalledTimes(1);
118
+ expect(() => toolBox.SetGizmoVisibility(true)).not.toThrow();
124
119
  });
125
120
  });
@@ -1,9 +1,14 @@
1
- import { Object3D } from "three";
2
- import { DIVESelectable, isSelectable } from "../../interface/Selectable.ts";
1
+ import { type Object3D } from "three";
3
2
  import DIVEScene from "../../scene/Scene.ts";
4
- import { DIVEMoveable } from "../../interface/Moveable.ts";
5
- import DIVEOrbitControls from "../../controls/OrbitControls.ts";
6
3
  import DIVETransformTool from "../transform/TransformTool.ts";
4
+ import type DIVEOrbitControls from "../../controls/OrbitControls.ts";
5
+ import { type DIVESelectable, findSelectableInterface } from "../../interface/Selectable.ts";
6
+ import { type DIVEMoveable } from "../../interface/Moveable.ts";
7
+ import { type DIVEBaseTool } from "../BaseTool.ts";
8
+
9
+ export const isSelectTool = (tool: DIVEBaseTool): tool is DIVESelectTool => {
10
+ return (tool as DIVESelectTool).isSelectTool !== undefined;
11
+ }
7
12
 
8
13
  export interface DIVEObjectEventMap {
9
14
  select: object
@@ -17,7 +22,8 @@ export interface DIVEObjectEventMap {
17
22
  * @module
18
23
  */
19
24
 
20
- export default class DIVESelectTool extends DIVETransformTool {
25
+ export class DIVESelectTool extends DIVETransformTool {
26
+ readonly isSelectTool: boolean = true;
21
27
 
22
28
  constructor(scene: DIVEScene, controller: DIVEOrbitControls) {
23
29
  super(scene, controller);
@@ -39,10 +45,6 @@ export default class DIVESelectTool extends DIVETransformTool {
39
45
  this.DetachGizmo();
40
46
  }
41
47
 
42
- public DetachGizmo(): void {
43
- this._gizmo.detach();
44
- }
45
-
46
48
  public AttachGizmo(selectable: DIVESelectable): void {
47
49
  if ('isMoveable' in selectable) {
48
50
  const movable = selectable as (Object3D & DIVESelectable & DIVEMoveable);
@@ -51,11 +53,15 @@ export default class DIVESelectTool extends DIVETransformTool {
51
53
  }
52
54
  }
53
55
 
56
+ public DetachGizmo(): void {
57
+ this._gizmo.detach();
58
+ }
59
+
54
60
  public onClick(e: PointerEvent): void {
55
61
  super.onClick(e);
56
62
 
57
- const first = this._raycaster.intersectObjects(this._scene.Root.children, true).filter((intersect) => intersect.object.visible )[0];
58
- const selectable = this.findSelectableInterface(first?.object);
63
+ const first = this._raycaster.intersectObjects(this._scene.Root.children, true).filter((intersect) => intersect.object.visible)[0];
64
+ const selectable = findSelectableInterface(first?.object);
59
65
 
60
66
  // if nothing is hit
61
67
  if (!first || !selectable) {
@@ -77,21 +83,4 @@ export default class DIVESelectTool extends DIVETransformTool {
77
83
  // select clicked object
78
84
  this.Select(selectable);
79
85
  }
80
-
81
- private findSelectableInterface(child: Object3D): (Object3D & DIVESelectable) | undefined {
82
- if (child === undefined) return undefined;
83
-
84
- if (child.parent === null) {
85
- // in this case it is the scene itself
86
- return undefined;
87
- }
88
-
89
- if (isSelectable(child)) {
90
- // in this case it is the Selectable
91
- return child;
92
- }
93
-
94
- // search recursively in parent
95
- return this.findSelectableInterface(child.parent);
96
- }
97
86
  }