@shopware-ag/dive 1.2.0 → 1.3.1

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 (39) hide show
  1. package/build/dive.cjs +288 -60
  2. package/build/dive.cjs.map +1 -1
  3. package/build/dive.d.cts +64 -6
  4. package/build/dive.d.ts +64 -6
  5. package/build/dive.js +289 -61
  6. package/build/dive.js.map +1 -1
  7. package/package.json +1 -1
  8. package/src/__test__/DIVE.test.ts +2 -0
  9. package/src/com/Communication.ts +6 -3
  10. package/src/com/__test__/Communication.test.ts +3 -6
  11. package/src/com/actions/object/getobjects.ts +2 -2
  12. package/src/dive.ts +7 -0
  13. package/src/gizmo/Gizmo.ts +130 -0
  14. package/src/gizmo/handles/AxisHandle.ts +124 -0
  15. package/src/gizmo/handles/RadialHandle.ts +119 -0
  16. package/src/gizmo/handles/ScaleHandle.ts +152 -0
  17. package/src/gizmo/plane/GizmoPlane.ts +85 -0
  18. package/src/gizmo/rotate/RotateGizmo.ts +95 -0
  19. package/src/gizmo/scale/ScaleGizmo.ts +97 -0
  20. package/src/gizmo/translate/TranslateGizmo.ts +88 -0
  21. package/src/interface/Draggable.ts +34 -0
  22. package/src/interface/Hoverable.ts +33 -0
  23. package/src/interface/Moveable.ts +0 -2
  24. package/src/interface/Selectable.ts +6 -0
  25. package/src/interface/__test__/Interfaces.test.ts +56 -0
  26. package/src/math/index.ts +3 -0
  27. package/src/math/signedAngleTo/__test__/signedAngleTo.test.ts +14 -0
  28. package/src/math/signedAngleTo/signedAngleTo.ts +13 -0
  29. package/src/scene/root/lightroot/LightRoot.ts +17 -3
  30. package/src/scene/root/lightroot/__test__/LightRoot.test.ts +12 -3
  31. package/src/scene/root/modelroot/ModelRoot.ts +17 -3
  32. package/src/scene/root/modelroot/__test__/ModelRoot.test.ts +13 -14
  33. package/src/toolbox/BaseTool.ts +254 -4
  34. package/src/toolbox/Toolbox.ts +6 -0
  35. package/src/toolbox/__test__/BaseTool.test.ts +389 -0
  36. package/src/toolbox/__test__/Toolbox.test.ts +8 -0
  37. package/src/toolbox/select/SelectTool.ts +29 -65
  38. package/src/toolbox/select/__test__/SelectTool.test.ts +57 -25
  39. package/src/toolbox/transform/TransformTool.ts +48 -0
@@ -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
  }
@@ -3,7 +3,8 @@ import DIVEScene from '../../../scene/Scene';
3
3
  import DIVEOrbitControls from '../../../controls/OrbitControls';
4
4
  import DIVEPerspectiveCamera from '../../../camera/PerspectiveCamera';
5
5
  import DIVERenderer, { DIVERendererDefaultSettings } from '../../../renderer/Renderer';
6
- import { DIVESelectable } from '../../../interface/Selectable';
6
+ import { DIVESelectable, isSelectable } from '../../../interface/Selectable';
7
+ import { type Object3D } from 'three';
7
8
 
8
9
  jest.mock('../../../renderer/Renderer', () => {
9
10
  return jest.fn(function () {
@@ -11,14 +12,6 @@ jest.mock('../../../renderer/Renderer', () => {
11
12
  });
12
13
  });
13
14
 
14
- jest.mock('../../../toolbox/BaseTool', () => {
15
- return jest.fn(function (cam) {
16
- this.cam = cam;
17
- this.add = jest.fn();
18
- return this;
19
- });
20
- });
21
-
22
15
  jest.mock('../../../camera/PerspectiveCamera', () => {
23
16
  return jest.fn(function () {
24
17
  this.isPerspectiveCamera = true;
@@ -62,6 +55,9 @@ jest.mock('three', () => {
62
55
  Vector2: jest.fn(function () {
63
56
  return this;
64
57
  }),
58
+ Vector3: jest.fn(function () {
59
+ return this;
60
+ }),
65
61
  Raycaster: jest.fn(function () {
66
62
  this.setFromCamera = jest.fn();
67
63
  this.intersectObjects = mock_intersectObjects;
@@ -120,8 +116,6 @@ describe('dive/toolbox/select/DIVESelectTool', () => {
120
116
  it('should instantiate', () => {
121
117
  const selectTool = new DIVESelectTool(mockScene, mockController);
122
118
  expect(selectTool).toBeDefined();
123
- expect(mockController.object.onSetCameraLayer).toBeDefined();
124
- expect(() => mockController.object.onSetCameraLayer(0)).not.toThrow();
125
119
  });
126
120
 
127
121
  it('should activate', () => {
@@ -129,21 +123,23 @@ describe('dive/toolbox/select/DIVESelectTool', () => {
129
123
  expect(() => selectTool.Activate()).not.toThrow();
130
124
  });
131
125
 
132
- it('should execute onPointerUp without hit', () => {
126
+ it('should execute onClick without hit', () => {
133
127
  const selectTool = new DIVESelectTool(mockScene, mockController);
134
- expect(() => selectTool.onPointerUp({ offsetX: 0, offsetY: 0 } as PointerEvent)).not.toThrow();
128
+ selectTool['_gizmo'].object = {} as unknown as Object3D & DIVESelectable;
129
+ expect(() => selectTool.onClick({ offsetX: 0, offsetY: 0 } as PointerEvent)).not.toThrow();
135
130
  });
136
131
 
137
- it('should execute onPointerUp with hit', () => {
138
- mock_intersectObjects.mockReturnValueOnce([{ object: { parent: { name: 'this is the test scene root!!!', parent: null } } }]);
132
+ it('should execute onClick with hit', () => {
133
+ mock_intersectObjects.mockReturnValueOnce([{ object: { uuid: 'test', parent: { name: 'this is the test scene root!!!', parent: null } } }]);
139
134
  const selectTool = new DIVESelectTool(mockScene, mockController);
140
- expect(() => selectTool.onPointerUp({ offsetX: 0, offsetY: 0 } as PointerEvent)).not.toThrow();
135
+ expect(() => selectTool.onClick({ offsetX: 0, offsetY: 0 } as PointerEvent)).not.toThrow();
141
136
  });
142
137
 
143
- it('should execute onPointerUp with ISelectable hit', () => {
138
+ it('should execute onClick with same ISelectable hit', () => {
144
139
  const mock_onSelect = jest.fn();
145
140
 
146
141
  mock_intersectObjects.mockReturnValueOnce([{
142
+
147
143
  object: {
148
144
  isSelectable: true,
149
145
  onSelect: mock_onSelect,
@@ -151,14 +147,41 @@ describe('dive/toolbox/select/DIVESelectTool', () => {
151
147
  name: 'this is the test scene root!!!',
152
148
  parent: null,
153
149
  },
150
+ uuid: 'test0',
154
151
  },
155
152
  }]);
156
153
  const selectTool = new DIVESelectTool(mockScene, mockController);
157
- expect(() => selectTool.onPointerUp({ offsetX: 0, offsetY: 0 } as PointerEvent)).not.toThrow();
158
- expect(mock_onSelect).toHaveBeenCalledTimes(1);
154
+ selectTool['_gizmo'].object = {
155
+ isSelectable: true,
156
+ uuid: 'test0',
157
+ } as unknown as Object3D & DIVESelectable;
158
+ expect(() => selectTool.onClick({ offsetX: 0, offsetY: 0 } as PointerEvent)).not.toThrow();
159
159
  });
160
160
 
161
- it('should execute onPointerUp with IMovable hit', () => {
161
+ it('should execute onClick with ISelectable hit', () => {
162
+ const mock_onSelect = jest.fn();
163
+
164
+ mock_intersectObjects.mockReturnValueOnce([{
165
+
166
+ object: {
167
+ isSelectable: true,
168
+ onSelect: mock_onSelect,
169
+ parent: {
170
+ name: 'this is the test scene root!!!',
171
+ parent: null,
172
+ },
173
+ uuid: 'test0',
174
+ },
175
+ }]);
176
+ const selectTool = new DIVESelectTool(mockScene, mockController);
177
+ selectTool['_gizmo'].object = {
178
+ isSelectable: true,
179
+ uuid: 'test1',
180
+ } as unknown as Object3D & DIVESelectable;
181
+ expect(() => selectTool.onClick({ offsetX: 0, offsetY: 0 } as PointerEvent)).not.toThrow();
182
+ });
183
+
184
+ it('should execute onClick with IMovable hit', () => {
162
185
  const mock_onSelect = jest.fn();
163
186
 
164
187
  mock_intersectObjects.mockReturnValueOnce([{
@@ -173,15 +196,24 @@ describe('dive/toolbox/select/DIVESelectTool', () => {
173
196
  },
174
197
  }]);
175
198
  const selectTool = new DIVESelectTool(mockScene, mockController);
176
- expect(() => selectTool.onPointerUp({ offsetX: 0, offsetY: 0 } as PointerEvent)).not.toThrow();
177
- expect(mock_attach).toHaveBeenCalledTimes(1);
199
+ expect(() => selectTool.onClick({ offsetX: 0, offsetY: 0 } as PointerEvent)).not.toThrow();
178
200
  });
179
201
 
180
- it('should deselect', () => {
202
+ it('should Select', () => {
181
203
  const selectTool = new DIVESelectTool(mockScene, mockController);
204
+ const mock_onSelect = jest.fn();
205
+ expect(() => selectTool.Select({ isSelectable: true })).not.toThrow();
206
+ expect(() => selectTool.Select({ isMoveable: true, onSelect: mock_onSelect } as unknown as DIVESelectable)).not.toThrow();
207
+ expect(mock_onSelect).toHaveBeenCalledTimes(1);
208
+ });
209
+
210
+ it('should Deselect', () => {
211
+ const selectTool = new DIVESelectTool(mockScene, mockController);
212
+ const mock_onDeselect = jest.fn();
182
213
  expect(() => selectTool.Deselect({ isSelectable: true })).not.toThrow();
183
- expect(() => selectTool.Deselect({ isMoveable: true, onDeselect: jest.fn() } as unknown as DIVESelectable)).not.toThrow();
184
- })
214
+ expect(() => selectTool.Deselect({ isMoveable: true, onDeselect: mock_onDeselect } as unknown as DIVESelectable)).not.toThrow();
215
+ expect(mock_onDeselect).toHaveBeenCalledTimes(1);
216
+ });
185
217
 
186
218
  it('should set gizmo mode', () => {
187
219
  const selectTool = new DIVESelectTool(mockScene, mockController);
@@ -0,0 +1,48 @@
1
+ import DIVEBaseTool from "../BaseTool.ts";
2
+ import DIVEScene from "../../scene/Scene.ts";
3
+ import DIVEOrbitControls from "../../controls/OrbitControls.ts";
4
+ import { TransformControls } from "three/examples/jsm/Addons";
5
+
6
+ export interface DIVEObjectEventMap {
7
+ select: object
8
+ }
9
+
10
+ /**
11
+ * A Tool to select and move objects in the scene.
12
+ *
13
+ * Objects have to implement the DIVESelectable interface to be selectable and DIVEMoveable to be moveable.
14
+ *
15
+ * @module
16
+ */
17
+
18
+ export default class DIVETransformTool extends DIVEBaseTool {
19
+ protected _gizmo: TransformControls;
20
+
21
+ constructor(scene: DIVEScene, controller: DIVEOrbitControls) {
22
+ super(scene, controller);
23
+ this.name = "DIVETransformTool";
24
+
25
+ this._gizmo = new TransformControls(this._controller.object, this._controller.domElement);
26
+ this._gizmo.mode = 'translate';
27
+
28
+ scene.add(this._gizmo);
29
+ }
30
+
31
+ public Activate(): void { }
32
+
33
+ public SetGizmoMode(mode: 'translate' | 'rotate' | 'scale'): void {
34
+ this._gizmo.mode = mode;
35
+ }
36
+
37
+ // public onPointerDown(e: PointerEvent): void {
38
+ // super.onPointerDown(e);
39
+
40
+ // // if (this._hovered) {
41
+ // // this._dragRaycastOnObjects = this._gizmo.gizmoPlane.children;
42
+ // // }
43
+ // }
44
+
45
+ // protected raycast(): Intersection[] {
46
+ // return super.raycast(this._gizmo.gizmoNode.children);
47
+ // }
48
+ }