@shopware-ag/dive 1.1.2 → 1.3.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 (47) hide show
  1. package/README.md +1 -0
  2. package/build/dive.cjs +333 -60
  3. package/build/dive.cjs.map +1 -1
  4. package/build/dive.d.cts +73 -6
  5. package/build/dive.d.ts +73 -6
  6. package/build/dive.js +335 -62
  7. package/build/dive.js.map +1 -1
  8. package/package.json +1 -1
  9. package/src/__test__/DIVE.test.ts +2 -0
  10. package/src/com/Communication.ts +21 -3
  11. package/src/com/__test__/Communication.test.ts +44 -6
  12. package/src/com/actions/index.ts +2 -0
  13. package/src/com/actions/object/getobjects.ts +2 -2
  14. package/src/com/actions/object/model/dropit.ts +4 -0
  15. package/src/dive.ts +7 -0
  16. package/src/gizmo/Gizmo.ts +130 -0
  17. package/src/gizmo/handles/AxisHandle.ts +124 -0
  18. package/src/gizmo/handles/RadialHandle.ts +119 -0
  19. package/src/gizmo/handles/ScaleHandle.ts +152 -0
  20. package/src/gizmo/plane/GizmoPlane.ts +85 -0
  21. package/src/gizmo/rotate/RotateGizmo.ts +95 -0
  22. package/src/gizmo/scale/ScaleGizmo.ts +97 -0
  23. package/src/gizmo/translate/TranslateGizmo.ts +88 -0
  24. package/src/helper/findSceneRecursive/__test__/findSceneRecursive.test.ts +40 -0
  25. package/src/helper/findSceneRecursive/findSceneRecursive.ts +16 -0
  26. package/src/interface/Draggable.ts +34 -0
  27. package/src/interface/Hoverable.ts +33 -0
  28. package/src/interface/Moveable.ts +0 -2
  29. package/src/interface/Selectable.ts +6 -0
  30. package/src/interface/__test__/Interfaces.test.ts +56 -0
  31. package/src/math/index.ts +3 -0
  32. package/src/math/signedAngleTo/__test__/signedAngleTo.test.ts +14 -0
  33. package/src/math/signedAngleTo/signedAngleTo.ts +13 -0
  34. package/src/model/Model.ts +35 -1
  35. package/src/model/__test__/Model.test.ts +141 -8
  36. package/src/scene/root/lightroot/LightRoot.ts +17 -3
  37. package/src/scene/root/lightroot/__test__/LightRoot.test.ts +12 -3
  38. package/src/scene/root/modelroot/ModelRoot.ts +17 -3
  39. package/src/scene/root/modelroot/__test__/ModelRoot.test.ts +13 -14
  40. package/src/toolbox/BaseTool.ts +254 -4
  41. package/src/toolbox/Toolbox.ts +6 -0
  42. package/src/toolbox/__test__/BaseTool.test.ts +389 -0
  43. package/src/toolbox/__test__/Toolbox.test.ts +8 -0
  44. package/src/toolbox/select/SelectTool.ts +29 -65
  45. package/src/toolbox/select/__test__/SelectTool.test.ts +57 -25
  46. package/src/toolbox/transform/TransformTool.ts +48 -0
  47. /package/src/helper/getObjectDelta/__test__/{getObjectDelta.spec.ts → getObjectDelta.test.ts} +0 -0
@@ -0,0 +1,389 @@
1
+
2
+ 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
+ import { type DIVEHoverable } from '../../interface/Hoverable';
8
+ import { DIVEDraggable } from '../../interface/Draggable';
9
+
10
+ /**
11
+ * @jest-environment jsdom
12
+ */
13
+
14
+
15
+ const mock_Canvas = {
16
+ width: 0,
17
+ height: 0,
18
+ getContext: jest.fn(),
19
+ clientWidth: 1000,
20
+ clientHeight: 1000,
21
+ offsetLeft: 0,
22
+ offsetTop: 0,
23
+ };
24
+
25
+ const mockController = {
26
+ domElement: mock_Canvas,
27
+ object: {
28
+ isPerspectiveCamera: true,
29
+ type: 'cameraP'
30
+ }
31
+ } as unknown as DIVEOrbitControls;
32
+
33
+ const mockScene = {
34
+ children: [],
35
+ } as unknown as DIVEScene;
36
+
37
+ const abstractWrapper = class Wrapper extends DIVEBaseTool {
38
+ constructor(scene: DIVEScene, controller: DIVEOrbitControls) {
39
+ super(scene, controller);
40
+ this.name = "DIVEBaseTool";
41
+ }
42
+ };
43
+
44
+ describe('dive/toolbox/DIVEBaseTool', () => {
45
+ afterEach(() => {
46
+ jest.clearAllMocks();
47
+ });
48
+
49
+ it('should instantiate', () => {
50
+ const toolBox = new abstractWrapper(mockScene, mockController);
51
+ expect(toolBox).toBeDefined();
52
+ });
53
+
54
+ it('should raycast', () => {
55
+ const toolBox = new abstractWrapper(mockScene, mockController);
56
+ expect(() => toolBox['raycast']()).not.toThrow();
57
+ expect(() => toolBox['raycast']([])).not.toThrow();
58
+ });
59
+
60
+ it('should return correct pointerAnyDown', () => {
61
+ const toolBox = new abstractWrapper(mockScene, mockController);
62
+ expect(toolBox).toBeDefined();
63
+ expect(toolBox['_pointerAnyDown']).toBeDefined();
64
+ expect(toolBox['_pointerAnyDown']).toBe(false);
65
+
66
+ toolBox['_pointerPrimaryDown'] = true;
67
+ expect(toolBox['_pointerAnyDown']).toBe(true);
68
+ });
69
+
70
+ it('should execute onPointerDown correctly', () => {
71
+ const toolBox = new abstractWrapper(mockScene, mockController);
72
+ expect(() => toolBox.onPointerDown({ button: 0 } as PointerEvent)).not.toThrow();
73
+ expect(() => toolBox.onPointerDown({ button: 1 } as PointerEvent)).not.toThrow();
74
+ expect(() => toolBox.onPointerDown({ button: 2 } as PointerEvent)).not.toThrow();
75
+
76
+ toolBox['_intersects'] = [
77
+ {
78
+ distance: 1,
79
+ point: {
80
+ clone() {
81
+ return {
82
+ x: 1,
83
+ y: 1,
84
+ z: 1
85
+ } as unknown as Vector3;
86
+ },
87
+ x: 1,
88
+ y: 1,
89
+ z: 1
90
+ } as unknown as Vector3,
91
+ object: {
92
+ uuid: 'uuid2',
93
+ isHoverable: true,
94
+ onPointerEnter() {
95
+ return;
96
+ },
97
+ } as unknown as Object3D & DIVEHoverable
98
+ }
99
+ ];
100
+
101
+ expect(() => toolBox.onPointerDown({ button: 0 } as PointerEvent)).not.toThrow();
102
+ expect(() => toolBox.onPointerDown({ button: 1 } as PointerEvent)).not.toThrow();
103
+ expect(() => toolBox.onPointerDown({ button: 2 } as PointerEvent)).not.toThrow();
104
+ });
105
+
106
+ it('should execute onPointerMove correctly', () => {
107
+ const toolBox = new abstractWrapper(mockScene, mockController);
108
+ jest.spyOn(toolBox['_raycaster'], 'setFromCamera').mockImplementation();
109
+
110
+ // test with no hit with hovered object before
111
+ jest.spyOn(toolBox['_raycaster'], 'intersectObjects').mockReturnValueOnce([]);
112
+
113
+ toolBox['_hovered'] = {
114
+ uuid: 'uuid',
115
+ onPointerLeave() {
116
+ return;
117
+ },
118
+ } as Object3D & DIVEHoverable;
119
+
120
+ expect(() => toolBox.onPointerMove({ button: 0, offsetX: 100, offsetY: 100 } as PointerEvent)).not.toThrow();
121
+ expect(() => toolBox.onPointerMove({ button: 1, offsetX: 100, offsetY: 100 } as PointerEvent)).not.toThrow();
122
+ expect(() => toolBox.onPointerMove({ button: 2, offsetX: 100, offsetY: 100 } as PointerEvent)).not.toThrow();
123
+
124
+ // test with no hovered object
125
+ jest.spyOn(toolBox['_raycaster'], 'intersectObjects').mockReturnValueOnce(
126
+ [
127
+ {
128
+ distance: 1,
129
+ point: {
130
+ x: 1,
131
+ y: 1,
132
+ z: 1
133
+ } as unknown as Vector3,
134
+ object: {
135
+ uuid: 'uuid',
136
+ isHoverable: true,
137
+ } as Object3D & DIVEHoverable
138
+ }
139
+ ]
140
+ );
141
+
142
+ toolBox['_hovered'] = null;
143
+
144
+ expect(() => toolBox.onPointerMove({ button: 0, offsetX: 100, offsetY: 100 } as PointerEvent)).not.toThrow();
145
+ expect(() => toolBox.onPointerMove({ button: 1, offsetX: 100, offsetY: 100 } as PointerEvent)).not.toThrow();
146
+ expect(() => toolBox.onPointerMove({ button: 2, offsetX: 100, offsetY: 100 } as PointerEvent)).not.toThrow();
147
+
148
+ // test with no hovered object with onPointerEnter
149
+ jest.spyOn(toolBox['_raycaster'], 'intersectObjects').mockReturnValueOnce(
150
+ [
151
+ {
152
+ distance: 1,
153
+ point: {
154
+ x: 1,
155
+ y: 1,
156
+ z: 1
157
+ } as unknown as Vector3,
158
+ object: {
159
+ uuid: 'uuid',
160
+ isHoverable: true,
161
+ onPointerEnter() {
162
+ return;
163
+ },
164
+ } as unknown as Object3D & DIVEHoverable
165
+ }
166
+ ]
167
+ );
168
+
169
+ expect(() => toolBox.onPointerMove({ button: 0, offsetX: 100, offsetY: 100 } as PointerEvent)).not.toThrow();
170
+ expect(() => toolBox.onPointerMove({ button: 1, offsetX: 100, offsetY: 100 } as PointerEvent)).not.toThrow();
171
+ expect(() => toolBox.onPointerMove({ button: 2, offsetX: 100, offsetY: 100 } as PointerEvent)).not.toThrow();
172
+
173
+ // test with same hovered object
174
+ jest.spyOn(toolBox['_raycaster'], 'intersectObjects').mockReturnValueOnce(
175
+ [
176
+ {
177
+ distance: 1,
178
+ point: {
179
+ x: 1,
180
+ y: 1,
181
+ z: 1
182
+ } as unknown as Vector3,
183
+ object: {
184
+ uuid: 'uuid',
185
+ isHoverable: true,
186
+ onPointerOver() {
187
+ return;
188
+ }
189
+ } as unknown as Object3D & DIVEHoverable
190
+ }
191
+ ]
192
+ );
193
+
194
+ toolBox['_hovered'] = {
195
+ uuid: 'uuid',
196
+ onPointerLeave() {
197
+ return;
198
+ },
199
+ } as Object3D & DIVEHoverable;
200
+
201
+ expect(() => toolBox.onPointerMove({ button: 0, offsetX: 100, offsetY: 100 } as PointerEvent)).not.toThrow();
202
+ expect(() => toolBox.onPointerMove({ button: 1, offsetX: 100, offsetY: 100 } as PointerEvent)).not.toThrow();
203
+ expect(() => toolBox.onPointerMove({ button: 2, offsetX: 100, offsetY: 100 } as PointerEvent)).not.toThrow();
204
+
205
+ // test with different hovered object
206
+ jest.spyOn(toolBox['_raycaster'], 'intersectObjects').mockReturnValueOnce(
207
+ [
208
+ {
209
+ distance: 1,
210
+ point: {
211
+ x: 1,
212
+ y: 1,
213
+ z: 1
214
+ } as unknown as Vector3,
215
+ object: {
216
+ uuid: 'uuid2',
217
+ isHoverable: true,
218
+ onPointerEnter() {
219
+ return;
220
+ },
221
+ } as unknown as Object3D & DIVEHoverable
222
+ }
223
+ ]
224
+ );
225
+
226
+ toolBox['_hovered'] = {
227
+ uuid: 'uuid',
228
+ onPointerLeave() {
229
+ return;
230
+ },
231
+ } as Object3D & DIVEHoverable;
232
+
233
+ expect(() => toolBox.onPointerMove({ button: 0, offsetX: 100, offsetY: 100 } as PointerEvent)).not.toThrow();
234
+ expect(() => toolBox.onPointerMove({ button: 1, offsetX: 100, offsetY: 100 } as PointerEvent)).not.toThrow();
235
+ expect(() => toolBox.onPointerMove({ button: 2, offsetX: 100, offsetY: 100 } as PointerEvent)).not.toThrow();
236
+
237
+ // test with pointer down
238
+ toolBox['_pointerPrimaryDown'] = true;
239
+ expect(() => toolBox.onPointerMove({ button: 0, offsetX: 100, offsetY: 100 } as PointerEvent)).not.toThrow();
240
+ expect(() => toolBox.onPointerMove({ button: 1, offsetX: 100, offsetY: 100 } as PointerEvent)).not.toThrow();
241
+ expect(() => toolBox.onPointerMove({ button: 2, offsetX: 100, offsetY: 100 } as PointerEvent)).not.toThrow();
242
+
243
+ // test with pointer down while already dragging
244
+ toolBox['_pointerPrimaryDown'] = true;
245
+ toolBox['_dragging'] = true;
246
+ expect(() => toolBox.onPointerMove({ button: 0, offsetX: 100, offsetY: 100 } as PointerEvent)).not.toThrow();
247
+ expect(() => toolBox.onPointerMove({ button: 1, offsetX: 100, offsetY: 100 } as PointerEvent)).not.toThrow();
248
+ expect(() => toolBox.onPointerMove({ button: 2, offsetX: 100, offsetY: 100 } as PointerEvent)).not.toThrow();
249
+ });
250
+
251
+ it('should execute onPointerUp correctly', () => {
252
+ const toolBox = new abstractWrapper(mockScene, mockController);
253
+ expect(() => toolBox.onPointerUp({ button: 0 } as PointerEvent)).not.toThrow();
254
+ expect(() => toolBox.onPointerUp({ button: 1 } as PointerEvent)).not.toThrow();
255
+ expect(() => toolBox.onPointerUp({ button: 2 } as PointerEvent)).not.toThrow();
256
+
257
+ toolBox['pointerWasDragged'] = () => { return true; };
258
+ toolBox['_dragging'] = true;
259
+ toolBox['_intersects'] = [
260
+ {
261
+ distance: 1,
262
+ point: {
263
+ clone() {
264
+ return {
265
+ x: 1,
266
+ y: 1,
267
+ z: 1
268
+ } as unknown as Vector3;
269
+ },
270
+ x: 1,
271
+ y: 1,
272
+ z: 1
273
+ } as unknown as Vector3,
274
+ object: {
275
+ uuid: 'uuid2',
276
+ isHoverable: true,
277
+ onPointerEnter() {
278
+ return;
279
+ },
280
+ } as unknown as Object3D & DIVEHoverable
281
+ }
282
+ ];
283
+ toolBox['_draggable'] = {
284
+ onDragEnd() {
285
+ return;
286
+ },
287
+ } as unknown as Object3D & DIVEDraggable;
288
+ expect(() => toolBox.onPointerUp({ button: 0 } as PointerEvent)).not.toThrow();
289
+ expect(() => toolBox.onPointerUp({ button: 1 } as PointerEvent)).not.toThrow();
290
+ expect(() => toolBox.onPointerUp({ button: 2 } as PointerEvent)).not.toThrow();
291
+ });
292
+
293
+ it('should execute onDragStart correctly', () => {
294
+ const toolBox = new abstractWrapper(mockScene, mockController);
295
+ expect(() => toolBox.onDragStart({} as PointerEvent)).not.toThrow();
296
+
297
+ toolBox['_draggable'] = {
298
+ onDragStart() {
299
+ return;
300
+ },
301
+ } as unknown as Object3D & DIVEDraggable;
302
+ expect(() => toolBox.onDragStart({} as PointerEvent)).not.toThrow();
303
+
304
+ toolBox['_dragRaycastOnObjects'] = [];
305
+ expect(() => toolBox.onDragStart({} as PointerEvent)).not.toThrow();
306
+
307
+ jest.spyOn(toolBox['_raycaster'], 'intersectObjects').mockReturnValueOnce([]);
308
+ expect(() => toolBox.onDragStart({} as PointerEvent)).not.toThrow();
309
+
310
+ jest.spyOn(toolBox['_raycaster'], 'intersectObjects').mockReturnValueOnce([
311
+ {
312
+ distance: 1,
313
+ point: {
314
+ clone() {
315
+ return {
316
+ x: 1,
317
+ y: 1,
318
+ z: 1
319
+ } as unknown as Vector3;
320
+ },
321
+ x: 1,
322
+ y: 1,
323
+ z: 1
324
+ } as unknown as Vector3,
325
+ object: {
326
+ uuid: 'uuid2',
327
+ isHoverable: true,
328
+ onPointerEnter() {
329
+ return;
330
+ },
331
+ } as unknown as Object3D & DIVEHoverable
332
+ }
333
+ ]);
334
+ expect(() => toolBox.onDragStart({} as PointerEvent)).not.toThrow();
335
+
336
+ toolBox['_draggable'] = {
337
+ onDragStart() {
338
+ return;
339
+ },
340
+ } as unknown as Object3D & DIVEDraggable;
341
+ expect(() => toolBox.onDragStart({} as PointerEvent)).not.toThrow();
342
+ });
343
+
344
+ it('should execute onDrag correctly', () => {
345
+ const toolBox = new abstractWrapper(mockScene, mockController);
346
+ expect(() => toolBox.onDrag({} as PointerEvent)).not.toThrow();
347
+
348
+ toolBox['_dragRaycastOnObjects'] = [];
349
+ expect(() => toolBox.onDrag({} as PointerEvent)).not.toThrow();
350
+
351
+ toolBox['_draggable'] = {
352
+ onDrag() {
353
+ return;
354
+ },
355
+ } as unknown as Object3D & DIVEDraggable;
356
+ jest.spyOn(toolBox['_raycaster'], 'intersectObjects').mockReturnValueOnce(
357
+ [
358
+ {
359
+ distance: 1,
360
+ point: {
361
+ clone() {
362
+ return {
363
+ x: 1,
364
+ y: 1,
365
+ z: 1
366
+ } as unknown as Vector3;
367
+ },
368
+ x: 1,
369
+ y: 1,
370
+ z: 1
371
+ } as unknown as Vector3,
372
+ object: {
373
+ uuid: 'uuid2',
374
+ isHoverable: true,
375
+ onPointerEnter() {
376
+ return;
377
+ },
378
+ } as unknown as Object3D & DIVEHoverable
379
+ }
380
+ ]
381
+ );
382
+ expect(() => toolBox.onDrag({} as PointerEvent)).not.toThrow();
383
+ });
384
+
385
+ it('should execute onDragEnd correctly', () => {
386
+ const toolBox = new abstractWrapper(mockScene, mockController);
387
+ expect(() => toolBox.onDragEnd({} as PointerEvent)).not.toThrow();
388
+ });
389
+ });
@@ -24,6 +24,7 @@ const mock_Canvas = {
24
24
  const mock_Activate = jest.fn();
25
25
  const mock_Deactivate = jest.fn();
26
26
  const mock_onPointerDown = jest.fn();
27
+ const mock_onPointerMove = jest.fn();
27
28
  const mock_onPointerUp = jest.fn();
28
29
  const mock_onWheel = jest.fn();
29
30
  const mock_SetGizmoMode = jest.fn();
@@ -33,6 +34,7 @@ jest.mock('../select/SelectTool.ts', () => {
33
34
  this.Activate = mock_Activate;
34
35
  this.Deactivate = mock_Deactivate;
35
36
  this.onPointerDown = mock_onPointerDown;
37
+ this.onPointerMove = mock_onPointerMove;
36
38
  this.onPointerUp = mock_onPointerUp;
37
39
  this.onWheel = mock_onWheel;
38
40
  this.SetGizmoMode = mock_SetGizmoMode;
@@ -84,6 +86,12 @@ describe('dive/toolbox/DIVEToolBox', () => {
84
86
  expect(mock_onPointerDown).toHaveBeenCalledTimes(1);
85
87
  });
86
88
 
89
+ it('should execute pointer move event on tool', () => {
90
+ const toolBox = new DIVEToolbox({} as DIVEScene, mockController);
91
+ toolBox.onPointerMove({ type: 'pointermove' } as PointerEvent);
92
+ expect(mock_onPointerMove).toHaveBeenCalledTimes(1);
93
+ });
94
+
87
95
  it('should execute pointer up event on tool', () => {
88
96
  const toolBox = new DIVEToolbox({} as DIVEScene, mockController);
89
97
  toolBox.onPointerUp({ type: 'pointerup' } as PointerEvent);
@@ -1,11 +1,9 @@
1
- import { Intersection, Object3D, Raycaster, Vector2 } from "three";
2
- import { TransformControls } from "three/examples/jsm/Addons.js";
3
- import DIVEBaseTool from "../BaseTool.ts";
4
- import { DIVESelectable } from "../../interface/Selectable.ts";
1
+ import { Object3D } from "three";
2
+ import { DIVESelectable, isSelectable } from "../../interface/Selectable.ts";
5
3
  import DIVEScene from "../../scene/Scene.ts";
6
- import { HELPER_LAYER_MASK, PRODUCT_LAYER_MASK, UI_LAYER_MASK } from "../../constant/VisibilityLayerMask.ts";
7
4
  import { DIVEMoveable } from "../../interface/Moveable.ts";
8
5
  import DIVEOrbitControls from "../../controls/OrbitControls.ts";
6
+ import DIVETransformTool from "../transform/TransformTool.ts";
9
7
 
10
8
  export interface DIVEObjectEventMap {
11
9
  select: object
@@ -19,82 +17,54 @@ export interface DIVEObjectEventMap {
19
17
  * @module
20
18
  */
21
19
 
22
- export default class DIVESelectTool extends DIVEBaseTool {
23
- private canvas: HTMLElement;
24
- private scene: DIVEScene;
25
- private controller: DIVEOrbitControls;
26
- private raycaster: Raycaster;
27
- private gizmo: TransformControls;
28
-
20
+ export default class DIVESelectTool extends DIVETransformTool {
29
21
 
30
22
  constructor(scene: DIVEScene, controller: DIVEOrbitControls) {
31
- super();
23
+ super(scene, controller);
32
24
  this.name = "SelectTool";
33
-
34
- this.canvas = controller.domElement;
35
- this.scene = scene;
36
- this.controller = controller;
37
- this.raycaster = new Raycaster();
38
- this.raycaster.layers.mask = PRODUCT_LAYER_MASK | HELPER_LAYER_MASK;
39
-
40
- this.gizmo = new TransformControls(this.controller.object, this.canvas);
41
-
42
- this.gizmo.layers.mask = UI_LAYER_MASK;
43
- this.gizmo.getRaycaster().layers.mask = UI_LAYER_MASK & this.controller.object.layers.mask;
44
- this.gizmo.traverse((child) => {
45
- child.layers.mask = UI_LAYER_MASK;
46
- });
47
- this.gizmo.addEventListener('objectChange', () => {
48
- if (!this.gizmo.object) return;
49
- if (!('onMove' in this.gizmo.object)) return;
50
- if (typeof this.gizmo.object.onMove !== 'function') return;
51
- this.gizmo.object.onMove();
52
- });
53
-
54
- this.controller.object.onSetCameraLayer = (mask: number) => {
55
- this.gizmo.getRaycaster().layers.mask = UI_LAYER_MASK & mask;
56
- };
57
-
58
- this.gizmo.addEventListener('dragging-changed', function (event) {
59
- controller.enabled = !event.value;
60
- });
61
-
62
- this.scene.add(this.gizmo);
63
25
  }
64
26
 
65
27
  public Activate(): void { }
66
28
 
67
- public SetGizmoMode(mode: 'translate' | 'rotate' | 'scale'): void {
68
- this.gizmo.setMode(mode);
69
- }
70
29
 
71
30
  public Select(selectable: DIVESelectable): void {
72
31
  if (selectable.onSelect) selectable.onSelect();
73
32
 
74
33
  if ('isMoveable' in selectable) {
75
34
  const movable = selectable as (Object3D & DIVESelectable & DIVEMoveable);
76
- movable.gizmo = this.gizmo;
77
- this.gizmo.attach(movable);
35
+ this._gizmo.attach(movable);
78
36
  }
79
37
  }
80
38
 
81
39
  public Deselect(selectable: DIVESelectable): void {
82
40
  if (selectable.onDeselect) selectable.onDeselect();
83
- if (('isMoveable' in selectable)) (selectable as unknown as DIVEMoveable).gizmo = null;
84
- this.gizmo.detach();
41
+ this._gizmo.detach();
85
42
  }
86
43
 
87
- public onPointerUp(e: PointerEvent): void {
88
- const pointerPos: Vector2 = new Vector2(e.offsetX / this.canvas.clientWidth * 2 - 1, e.offsetY / this.canvas.clientHeight * -2 + 1);
89
- this.raycaster.setFromCamera(pointerPos, this.controller.object);
44
+ public onClick(e: PointerEvent): void {
45
+ super.onClick(e);
90
46
 
91
- const first = this.raycastFirst();
47
+ const first = this._raycaster.intersectObjects(this._scene.Root.children, true)[0];
92
48
  const selectable = this.findSelectableInterface(first?.object);
49
+
50
+ // if nothing is hit
93
51
  if (!first || !selectable) {
94
- if (this.gizmo.object) this.Deselect(this.gizmo.object as (Object3D & DIVESelectable));
52
+ if (this._gizmo.object) {
53
+ this.Deselect(this._gizmo.object as Object3D & DIVESelectable);
54
+ }
95
55
  return;
96
56
  }
97
57
 
58
+ if (this._gizmo.object) {
59
+ // do not reselect if the same object was clicked
60
+ if (this._gizmo.object.uuid === selectable.uuid) return;
61
+
62
+ // deselect previous object
63
+ this.Deselect(this._gizmo.object as (Object3D & DIVESelectable));
64
+ }
65
+
66
+
67
+ // select clicked object
98
68
  this.Select(selectable);
99
69
  }
100
70
 
@@ -106,18 +76,12 @@ export default class DIVESelectTool extends DIVEBaseTool {
106
76
  return undefined;
107
77
  }
108
78
 
109
- if ('isSelectable' in child) {
110
- return child as (Object3D & DIVESelectable);
79
+ if (isSelectable(child)) {
80
+ // in this case it is the Selectable
81
+ return child;
111
82
  }
112
83
 
84
+ // search recursively in parent
113
85
  return this.findSelectableInterface(child.parent);
114
86
  }
115
-
116
- private raycastFirst(): Intersection {
117
- return this.raycastAll()[0];
118
- }
119
-
120
- private raycastAll(): Intersection[] {
121
- return this.raycaster.intersectObjects(this.scene.Root.children, true);
122
- }
123
87
  }