@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
@@ -7,6 +7,7 @@ import DIVEMediaCreator from "../mediacreator/MediaCreator.ts";
7
7
  import DIVEOrbitControls from "../controls/OrbitControls.ts";
8
8
  import { DIVESelectable } from "../interface/Selectable.ts";
9
9
  import DIVESelectTool from "../toolbox/select/SelectTool.ts";
10
+ import type DIVEModel from "../model/Model.ts";
10
11
 
11
12
  type EventListener<Action extends keyof Actions> = (payload: Actions[Action]['PAYLOAD']) => void;
12
13
 
@@ -103,6 +104,10 @@ export default class DIVECommunication {
103
104
  returnValue = this.setBackground(payload as Actions['SET_BACKGROUND']['PAYLOAD']);
104
105
  break;
105
106
  }
107
+ case 'DROP_IT': {
108
+ returnValue = this.dropIt(payload as Actions['DROP_IT']['PAYLOAD']);
109
+ break;
110
+ }
106
111
  case 'PLACE_ON_FLOOR': {
107
112
  returnValue = this.placeOnFloor(payload as Actions['PLACE_ON_FLOOR']['PAYLOAD']);
108
113
  break;
@@ -205,12 +210,15 @@ export default class DIVECommunication {
205
210
  }
206
211
 
207
212
  private getObjects(payload: Actions['GET_OBJECTS']['PAYLOAD']): Actions['GET_OBJECTS']['RETURN'] {
213
+ if (payload.ids.length === 0) return [];
214
+
215
+ const objects: COMEntity[] = [];
208
216
  this.registered.forEach((object) => {
209
- if (payload.ids && payload.ids.length > 0 && !payload.ids.includes(object.id)) return;
210
- payload.map.set(object.id, object);
217
+ if (!payload.ids.includes(object.id)) return;
218
+ objects.push(object);
211
219
  });
212
220
 
213
- return payload.map;
221
+ return objects;
214
222
  }
215
223
 
216
224
  private addObject(payload: Actions['ADD_OBJECT']['PAYLOAD']): Actions['ADD_OBJECT']['RETURN'] {
@@ -275,6 +283,16 @@ export default class DIVECommunication {
275
283
  return true;
276
284
  }
277
285
 
286
+ private dropIt(payload: Actions['DROP_IT']['PAYLOAD']): Actions['DROP_IT']['RETURN'] {
287
+ const object = this.registered.get(payload.id);
288
+ if (!object) return false;
289
+
290
+ const model = this.scene.GetSceneObject(object) as DIVEModel;
291
+ model.DropIt();
292
+
293
+ return true;
294
+ }
295
+
278
296
  private placeOnFloor(payload: Actions['PLACE_ON_FLOOR']['PAYLOAD']): Actions['PLACE_ON_FLOOR']['RETURN'] {
279
297
  if (!this.registered.get(payload.id)) return false;
280
298
 
@@ -289,6 +289,47 @@ describe('dive/communication/DIVECommunication', () => {
289
289
  expect(successSet).toBe(true);
290
290
  });
291
291
 
292
+ it('should perform action DROP_IT with existing model', () => {
293
+ const payload = {
294
+ entityType: "model",
295
+ id: "model",
296
+ position: { x: 0, y: 0, z: 0 },
297
+ rotation: { x: 0, y: 0, z: 0 },
298
+ scale: { x: 0.01, y: 0.01, z: 0.01 },
299
+
300
+ uri: "https://threejs.org/examples/models/gltf/LittlestTokyo.glb",
301
+ } as COMModel;
302
+
303
+ testCom.PerformAction('ADD_OBJECT', payload);
304
+
305
+ const placeSpy = jest.spyOn(mockScene, 'GetSceneObject').mockReturnValue({
306
+ DropIt: jest.fn(),
307
+ } as unknown as Object3D);
308
+
309
+ const successPlace = testCom.PerformAction('DROP_IT', payload);
310
+ expect(successPlace).toBe(true);
311
+ expect(placeSpy).toHaveBeenCalledTimes(1);
312
+ });
313
+
314
+ it('should perform action DROP_IT without existing model', () => {
315
+ const payload = {
316
+ entityType: "model",
317
+ id: "model",
318
+ position: { x: 0, y: 0, z: 0 },
319
+ rotation: { x: 0, y: 0, z: 0 },
320
+ scale: { x: 0.01, y: 0.01, z: 0.01 },
321
+
322
+ uri: "https://threejs.org/examples/models/gltf/LittlestTokyo.glb",
323
+ };
324
+ const placeSpy = jest.spyOn(mockScene, 'GetSceneObject').mockReturnValue({
325
+ DropIt: jest.fn(),
326
+ } as unknown as Object3D);
327
+
328
+ const successPlace = testCom.PerformAction('DROP_IT', payload);
329
+ expect(successPlace).toBe(false);
330
+ expect(placeSpy).toHaveBeenCalledTimes(0);
331
+ });
332
+
292
333
  it('should perform action PLACE_ON_FLOOR with existing model', () => {
293
334
  const payload = {
294
335
  entityType: "model",
@@ -469,13 +510,10 @@ describe('dive/communication/DIVECommunication', () => {
469
510
  } as COMPov;
470
511
  testCom.PerformAction('ADD_OBJECT', mock1);
471
512
 
472
- let map = new Map([['test0', mock0], ['test1', mock1]]);
473
-
474
- const successWithoutIds = testCom.PerformAction('GET_OBJECTS', { map });
475
- expect(successWithoutIds).toStrictEqual(map);
513
+ const successWithoutIds = testCom.PerformAction('GET_OBJECTS', { ids: [] });
514
+ expect(Array.from(successWithoutIds.values())).toStrictEqual([]);
476
515
 
477
- map = new Map();
478
- const successWithIds = testCom.PerformAction('GET_OBJECTS', { map, ids: ['test1'] });
516
+ const successWithIds = testCom.PerformAction('GET_OBJECTS', { ids: ['test1'] });
479
517
  expect(Array.from(successWithIds.values())).toStrictEqual([{ entityType: "pov", id: "test1", position: { x: 0, y: 0, z: 0 }, target: { x: 0, y: 0, z: 0 } }]);
480
518
  });
481
519
 
@@ -17,6 +17,7 @@ import GENERATE_MEDIA from "./media/generatemedia.ts";
17
17
  import GET_ALL_SCENE_DATA from "./scene/getallscenedata.ts";
18
18
  import SELECT_OBJECT from "./object/selectobject.ts";
19
19
  import GET_CAMERA_TRANSFORM from "./camera/getcameratransform.ts";
20
+ import DROP_IT from "./object/model/dropit.ts";
20
21
 
21
22
  export type Actions = {
22
23
  GET_ALL_SCENE_DATA: GET_ALL_SCENE_DATA,
@@ -27,6 +28,7 @@ export type Actions = {
27
28
  DELETE_OBJECT: DELETE_OBJECT,
28
29
  SELECT_OBJECT: SELECT_OBJECT,
29
30
  SET_BACKGROUND: SET_BACKGROUND,
31
+ DROP_IT: DROP_IT,
30
32
  PLACE_ON_FLOOR: PLACE_ON_FLOOR,
31
33
  SET_CAMERA_TRANSFORM: SET_CAMERA_TRANSFORM,
32
34
  GET_CAMERA_TRANSFORM: GET_CAMERA_TRANSFORM,
@@ -1,6 +1,6 @@
1
1
  import { COMEntity } from "../../types.ts";
2
2
 
3
3
  export default interface GET_OBJECTS {
4
- 'PAYLOAD': { map: Map<string, COMEntity>, ids?: string[] },
5
- 'RETURN': Map<string, COMEntity>,
4
+ 'PAYLOAD': { ids: string[] },
5
+ 'RETURN': COMEntity[],
6
6
  };
@@ -0,0 +1,4 @@
1
+ export default interface DROP_IT {
2
+ 'PAYLOAD': { id: string },
3
+ 'RETURN': boolean,
4
+ };
package/src/dive.ts CHANGED
@@ -163,6 +163,13 @@ export default class DIVE {
163
163
 
164
164
  // whene everything is done, start the renderer
165
165
  this.renderer.StartRenderer(this.scene, this.perspectiveCamera);
166
+
167
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
168
+ (window as any).DIVE = {
169
+ PrintScene: () => {
170
+ console.log(this.scene);
171
+ },
172
+ }
166
173
  }
167
174
 
168
175
  // methods
@@ -0,0 +1,130 @@
1
+ import { Euler, Object3D, Vector3 } from "three";
2
+ import { DIVERotateGizmo } from "./rotate/RotateGizmo";
3
+ import { DIVETranslateGizmo } from "./translate/TranslateGizmo";
4
+ import DIVEOrbitControls from "../controls/OrbitControls";
5
+ import { DIVEScaleGizmo } from "./scale/ScaleGizmo";
6
+ import { DIVEGizmoPlane as DIVEGizmoPlane } from "./plane/GizmoPlane";
7
+ import { DIVESelectable } from "../interface/Selectable";
8
+
9
+ export type DIVEGizmoMode = ('translate' | 'rotate' | 'scale');
10
+
11
+ export type DIVEGizmoAxis = 'x' | 'y' | 'z';
12
+
13
+ export class DIVEGizmo extends Object3D {
14
+ private _mode: DIVEGizmoMode;
15
+ public get mode(): DIVEGizmoMode {
16
+ return this._mode;
17
+ }
18
+ public set mode(value: DIVEGizmoMode) {
19
+ this._mode = value;
20
+ this.assemble();
21
+ }
22
+
23
+ private _gizmoNode: Object3D;
24
+ public get gizmoNode(): Object3D {
25
+ return this._gizmoNode;
26
+ }
27
+ private _translateGizmo: DIVETranslateGizmo;
28
+ private _rotateGizmo: DIVERotateGizmo;
29
+ private _scaleGizmo: DIVEScaleGizmo;
30
+
31
+ private _gizmoPlane: DIVEGizmoPlane;
32
+ public get gizmoPlane(): DIVEGizmoPlane {
33
+ return this._gizmoPlane;
34
+ }
35
+
36
+ // attachment stuff
37
+ private _object: (Object3D & DIVESelectable) | null;
38
+ public get object(): (Object3D & DIVESelectable) | null {
39
+ return this._object;
40
+ }
41
+
42
+ constructor(controller: DIVEOrbitControls) {
43
+ super();
44
+ this.name = "DIVEGizmo";
45
+
46
+ controller.addEventListener('change', () => {
47
+ const size = controller.getDistance() / 2.5;
48
+ this.scale.set(size, size, size);
49
+ });
50
+
51
+ this._mode = 'translate';
52
+
53
+ this._gizmoNode = new Object3D();
54
+ this.add(this._gizmoNode);
55
+
56
+ this._translateGizmo = new DIVETranslateGizmo(controller);
57
+ this._rotateGizmo = new DIVERotateGizmo(controller);
58
+ this._scaleGizmo = new DIVEScaleGizmo(controller);
59
+
60
+ this._gizmoPlane = new DIVEGizmoPlane();
61
+ this._gizmoPlane.visible = false;
62
+
63
+ this._object = null;
64
+ }
65
+
66
+ public attach(object: (Object3D & DIVESelectable)): this {
67
+ this._object = object;
68
+ this.assemble();
69
+ return this;
70
+ }
71
+
72
+ public detach(): this {
73
+ this._object = null;
74
+ this.assemble();
75
+ return this;
76
+ }
77
+
78
+ public onHover(mode: DIVEGizmoMode, axis: DIVEGizmoAxis, value: boolean): void {
79
+ if (!value) return;
80
+ this._gizmoPlane.assemble(mode, axis);
81
+ }
82
+
83
+ public onChange(position?: Vector3, rotation?: Euler, scale?: Vector3): void {
84
+ if (this.object === null) return;
85
+
86
+ if (position) {
87
+ this.position.copy(position);
88
+ this.object.position.copy(position);
89
+ }
90
+
91
+ if (rotation) {
92
+ this.object.rotation.copy(rotation);
93
+ }
94
+
95
+ if (scale) {
96
+ this.object.scale.copy(scale);
97
+ this._scaleGizmo.update(scale);
98
+ }
99
+ }
100
+
101
+ private assemble(): void {
102
+ // clear all children
103
+ this._gizmoNode.clear();
104
+ this._gizmoPlane.clear();
105
+
106
+ // reset all gizmos
107
+ this._translateGizmo.reset();
108
+ this._rotateGizmo.reset();
109
+ this._scaleGizmo.reset();
110
+
111
+ // check for object
112
+ if (this.object === null) return;
113
+
114
+ // add gizmos
115
+ if (this._mode === 'translate') {
116
+ this._gizmoNode.add(this._translateGizmo);
117
+ }
118
+
119
+ if (this._mode === 'rotate') {
120
+ this._gizmoNode.add(this._rotateGizmo);
121
+ }
122
+
123
+ if (this._mode === 'scale') {
124
+ this._gizmoNode.add(this._scaleGizmo);
125
+ }
126
+
127
+ // add plane for raycasting properly while dragging
128
+ this.add(this._gizmoPlane);
129
+ }
130
+ }
@@ -0,0 +1,124 @@
1
+ import { Color, ColorRepresentation, CylinderGeometry, Mesh, MeshBasicMaterial, Object3D, Vector3 } from "three";
2
+ import { UI_LAYER_MASK } from "../../constant/VisibilityLayerMask";
3
+ import { DIVEHoverable } from "../../interface/Hoverable";
4
+ import { DIVETranslateGizmo } from "../translate/TranslateGizmo";
5
+ import { DIVEDraggable } from "../../interface/Draggable";
6
+ import { DraggableEvent } from "../../toolbox/BaseTool";
7
+
8
+ export class DIVEAxisHandle extends Object3D implements DIVEHoverable, DIVEDraggable {
9
+ readonly isHoverable: true = true;
10
+ readonly isDraggable: true = true;
11
+
12
+ public parent: DIVETranslateGizmo | null = null;
13
+
14
+ public axis: 'x' | 'y' | 'z';
15
+
16
+ private _color: Color = new Color(0xff00ff);
17
+ private _colorHover: Color;
18
+ private _hovered: boolean;
19
+ private _highlight: boolean;
20
+ public get highlight(): boolean {
21
+ return this._highlight;
22
+ }
23
+ public set highlight(highlight: boolean) {
24
+ this._highlight = highlight;
25
+ this._lineMaterial.color = this._highlight || this._hovered ? this._colorHover : this._color;
26
+ }
27
+
28
+ private _lineMaterial: MeshBasicMaterial;
29
+
30
+ public get forwardVector(): Vector3 {
31
+ return new Vector3(0, 0, 1).applyQuaternion(this.quaternion).normalize();
32
+ }
33
+
34
+ public get rightVector(): Vector3 {
35
+ return new Vector3(1, 0, 0).applyQuaternion(this.quaternion).normalize();
36
+ }
37
+
38
+ public get upVector(): Vector3 {
39
+ return new Vector3(0, 1, 0).applyQuaternion(this.quaternion).normalize();
40
+ }
41
+
42
+ constructor(axis: 'x' | 'y' | 'z', length: number, direction: Vector3, color: ColorRepresentation) {
43
+ super();
44
+
45
+ this.name = "DIVEAxisHandle";
46
+ this.axis = axis;
47
+
48
+ this._color.set(color);
49
+ this._colorHover = this._color.clone().multiplyScalar(2);
50
+
51
+ this._highlight = false;
52
+ this._hovered = false;
53
+
54
+ // create line
55
+ const lineGeo = new CylinderGeometry(0.01, 0.01, length, 13);
56
+ this._lineMaterial = new MeshBasicMaterial({
57
+ color: color,
58
+ depthTest: false,
59
+ depthWrite: false,
60
+ });
61
+ const lineMesh = new Mesh(lineGeo, this._lineMaterial);
62
+ lineMesh.layers.mask = UI_LAYER_MASK;
63
+ lineMesh.renderOrder = Infinity;
64
+ lineMesh.rotateX(Math.PI / 2);
65
+ lineMesh.translateY(length / 2);
66
+ this.add(lineMesh);
67
+
68
+ // create collider
69
+ const collider = new CylinderGeometry(0.1, 0.1, length, 3);
70
+ const colliderMaterial = new MeshBasicMaterial({
71
+ color: 0xff00ff,
72
+ transparent: true,
73
+ opacity: 0.15,
74
+ depthTest: false,
75
+ depthWrite: false,
76
+ });
77
+ const colliderMesh = new Mesh(collider, colliderMaterial);
78
+ colliderMesh.visible = false;
79
+ colliderMesh.layers.mask = UI_LAYER_MASK;
80
+ colliderMesh.renderOrder = Infinity;
81
+ colliderMesh.rotateX(Math.PI / 2);
82
+ colliderMesh.translateY(length / 2);
83
+ this.add(colliderMesh);
84
+
85
+ this.rotateX(direction.y * -Math.PI / 2);
86
+ this.rotateY(direction.x * Math.PI / 2);
87
+ }
88
+
89
+ public reset(): void {
90
+ this._lineMaterial.color = this._color;
91
+ }
92
+
93
+ public onPointerEnter(): void {
94
+ this._hovered = true;
95
+ if (this.parent) {
96
+ this.parent.onHandleHover(this, true);
97
+ }
98
+ }
99
+
100
+ public onPointerLeave(): void {
101
+ this._hovered = false;
102
+ if (this.parent) {
103
+ this.parent.onHandleHover(this, false);
104
+ }
105
+ }
106
+
107
+ public onDragStart(): void {
108
+ if (this.parent) {
109
+ this.parent.onHandleDragStart(this);
110
+ }
111
+ }
112
+
113
+ public onDrag(e: DraggableEvent): void {
114
+ if (this.parent) {
115
+ this.parent.onHandleDrag(this, e);
116
+ }
117
+ }
118
+
119
+ public onDragEnd(): void {
120
+ if (this.parent) {
121
+ this.parent.onHandleDragEnd(this);
122
+ }
123
+ }
124
+ }
@@ -0,0 +1,119 @@
1
+ import { Color, ColorRepresentation, Mesh, MeshBasicMaterial, Object3D, TorusGeometry, Vector3 } from "three";
2
+ import { UI_LAYER_MASK } from "../../constant/VisibilityLayerMask";
3
+ import { DIVEHoverable } from "../../interface/Hoverable";
4
+ import { DraggableEvent } from "../../toolbox/BaseTool";
5
+ import { DIVERotateGizmo } from "../rotate/RotateGizmo";
6
+ import { DIVEDraggable } from "../../interface/Draggable";
7
+
8
+ export class DIVERadialHandle extends Object3D implements DIVEHoverable, DIVEDraggable {
9
+ readonly isHoverable: true = true;
10
+ readonly isDraggable: true = true;
11
+
12
+ public parent: DIVERotateGizmo | null = null;
13
+
14
+ public axis: 'x' | 'y' | 'z';
15
+
16
+ private _color: Color = new Color(0xff00ff);
17
+ private _colorHover: Color;
18
+ private _hovered: boolean;
19
+ private _highlight: boolean;
20
+ public get highlight(): boolean {
21
+ return this._highlight;
22
+ }
23
+ public set highlight(highlight: boolean) {
24
+ this._highlight = highlight;
25
+ this._lineMaterial.color = this._highlight || this._hovered ? this._colorHover : this._color;
26
+ }
27
+
28
+ private _lineMaterial: MeshBasicMaterial;
29
+
30
+ public get forwardVector(): Vector3 {
31
+ return new Vector3(0, 0, 1).applyQuaternion(this.quaternion).normalize();
32
+ }
33
+
34
+ public get rightVector(): Vector3 {
35
+ return new Vector3(1, 0, 0).applyQuaternion(this.quaternion).normalize();
36
+ }
37
+
38
+ public get upVector(): Vector3 {
39
+ return new Vector3(0, 1, 0).applyQuaternion(this.quaternion).normalize();
40
+ }
41
+
42
+ constructor(axis: 'x' | 'y' | 'z', radius: number, arc: number, direction: Vector3, color: ColorRepresentation) {
43
+ super();
44
+
45
+ this.name = "DIVERadialHandle";
46
+ this.axis = axis;
47
+
48
+ this._color.set(color);
49
+ this._colorHover = this._color.clone().multiplyScalar(2);
50
+ this._hovered = false;
51
+ this._highlight = false;
52
+
53
+ // create line
54
+ const lineGeo = new TorusGeometry(radius, 0.01, 13, 48, arc);
55
+ this._lineMaterial = new MeshBasicMaterial({
56
+ color: color,
57
+ depthTest: false,
58
+ depthWrite: false,
59
+ });
60
+ const lineMesh = new Mesh(lineGeo, this._lineMaterial);
61
+ lineMesh.layers.mask = UI_LAYER_MASK;
62
+ lineMesh.renderOrder = Infinity;
63
+ this.add(lineMesh);
64
+
65
+ // create collider
66
+ const collider = new TorusGeometry(radius, 0.1, 3, 48, arc);
67
+ const colliderMaterial = new MeshBasicMaterial({
68
+ color: 0xff00ff,
69
+ transparent: true,
70
+ opacity: 0.15,
71
+ depthTest: false,
72
+ depthWrite: false,
73
+ });
74
+ const colliderMesh = new Mesh(collider, colliderMaterial);
75
+ colliderMesh.visible = false;
76
+ colliderMesh.layers.mask = UI_LAYER_MASK;
77
+ colliderMesh.renderOrder = Infinity;
78
+
79
+ this.add(colliderMesh);
80
+
81
+ this.lookAt(direction);
82
+ }
83
+
84
+ public reset(): void {
85
+ this._lineMaterial.color = this._color;
86
+ }
87
+
88
+ public onPointerEnter(): void {
89
+ this._hovered = true;
90
+ if (this.parent) {
91
+ this.parent.onHandleHover(this, true);
92
+ }
93
+ }
94
+
95
+ public onPointerLeave(): void {
96
+ this._hovered = false;
97
+ if (this.parent) {
98
+ this.parent.onHandleHover(this, false);
99
+ }
100
+ }
101
+
102
+ public onDragStart(): void {
103
+ if (this.parent) {
104
+ this.parent.onHandleDragStart(this);
105
+ }
106
+ }
107
+
108
+ public onDrag(e: DraggableEvent): void {
109
+ if (this.parent) {
110
+ this.parent.onHandleDrag(this, e);
111
+ }
112
+ }
113
+
114
+ public onDragEnd(): void {
115
+ if (this.parent) {
116
+ this.parent.onHandleDragEnd(this);
117
+ }
118
+ }
119
+ }
@@ -0,0 +1,152 @@
1
+ import { BoxGeometry, Color, ColorRepresentation, CylinderGeometry, Mesh, MeshBasicMaterial, Object3D, Vector3 } from "three";
2
+ import { UI_LAYER_MASK } from "../../constant/VisibilityLayerMask";
3
+ import { DIVEHoverable } from "../../interface/Hoverable";
4
+ import { DIVEScaleGizmo } from "../scale/ScaleGizmo";
5
+ import { DIVEDraggable } from "../../interface/Draggable";
6
+ import { DraggableEvent } from "../../toolbox/BaseTool";
7
+
8
+ export class DIVEScaleHandle extends Object3D implements DIVEHoverable, DIVEDraggable {
9
+ readonly isHoverable: true = true;
10
+ readonly isDraggable: true = true;
11
+
12
+ public parent: DIVEScaleGizmo | null = null;
13
+
14
+ public axis: 'x' | 'y' | 'z';
15
+
16
+ private _color: Color = new Color(0xff00ff);
17
+ private _colorHover: Color;
18
+ private _hovered: boolean;
19
+ private _highlight: boolean;
20
+ public get highlight(): boolean {
21
+ return this._highlight;
22
+ }
23
+ public set highlight(highlight: boolean) {
24
+ this._highlight = highlight;
25
+ this._lineMaterial.color = this._highlight || this._hovered ? this._colorHover : this._color;
26
+ }
27
+
28
+ private _lineMaterial: MeshBasicMaterial;
29
+
30
+ private _box: Mesh;
31
+ private _boxSize: number;
32
+
33
+ public get forwardVector(): Vector3 {
34
+ return new Vector3(0, 0, 1).applyQuaternion(this.quaternion).normalize();
35
+ }
36
+
37
+ public get rightVector(): Vector3 {
38
+ return new Vector3(1, 0, 0).applyQuaternion(this.quaternion).normalize();
39
+ }
40
+
41
+ public get upVector(): Vector3 {
42
+ return new Vector3(0, 1, 0).applyQuaternion(this.quaternion).normalize();
43
+ }
44
+
45
+ constructor(axis: 'x' | 'y' | 'z', length: number, direction: Vector3, color: ColorRepresentation, boxSize: number = 0.05) {
46
+ super();
47
+
48
+ this.name = "DIVEScaleHandle";
49
+ this.axis = axis;
50
+
51
+ this._color.set(color);
52
+ this._colorHover = this._color.clone().multiplyScalar(2);
53
+ this._hovered = false;
54
+ this._highlight = false;
55
+
56
+ this._boxSize = boxSize;
57
+
58
+ // create line
59
+ const lineGeo = new CylinderGeometry(0.01, 0.01, length - boxSize / 2, 13);
60
+ this._lineMaterial = new MeshBasicMaterial({
61
+ color: color,
62
+ depthTest: false,
63
+ depthWrite: false,
64
+ });
65
+ const lineMesh = new Mesh(lineGeo, this._lineMaterial);
66
+ lineMesh.layers.mask = UI_LAYER_MASK;
67
+ lineMesh.renderOrder = Infinity;
68
+ lineMesh.rotateX(Math.PI / 2);
69
+ lineMesh.translateY(length / 2 - boxSize / 4);
70
+ this.add(lineMesh);
71
+
72
+ // create box
73
+ this._box = new Mesh(
74
+ new BoxGeometry(boxSize, boxSize, boxSize),
75
+ this._lineMaterial,
76
+ );
77
+ this._box.layers.mask = UI_LAYER_MASK;
78
+ this._box.renderOrder = Infinity;
79
+ this._box.rotateX(Math.PI / 2);
80
+ this._box.translateY(length - boxSize / 2);
81
+ this._box.rotateZ(direction.x * Math.PI / 2);
82
+ this._box.rotateX(direction.z * Math.PI / 2);
83
+ this.add(this._box);
84
+
85
+ // create collider
86
+ const collider = new CylinderGeometry(0.1, 0.1, length + boxSize / 2, 3);
87
+ const colliderMaterial = new MeshBasicMaterial({
88
+ color: 0xff00ff,
89
+ transparent: true,
90
+ opacity: 0.15,
91
+ depthTest: false,
92
+ depthWrite: false,
93
+ });
94
+ const colliderMesh = new Mesh(collider, colliderMaterial);
95
+ colliderMesh.visible = false;
96
+ colliderMesh.layers.mask = UI_LAYER_MASK;
97
+ colliderMesh.renderOrder = Infinity;
98
+ colliderMesh.rotateX(Math.PI / 2);
99
+ colliderMesh.translateY(length / 2);
100
+ this.add(colliderMesh);
101
+
102
+ this.rotateX(direction.y * -Math.PI / 2);
103
+ this.rotateY(direction.x * Math.PI / 2);
104
+ }
105
+
106
+ public reset(): void {
107
+ this._lineMaterial.color = this._color;
108
+ }
109
+
110
+ public update(scale: Vector3): void {
111
+ this._box.scale.copy(
112
+ new Vector3(1, 1, 1) // identity scale ...
113
+ .sub(this.forwardVector) // subtracted the forward vector ...
114
+ .add( // to then add ...
115
+ scale.clone() // the scale ...
116
+ .multiply(this.forwardVector) // that is scaled by the forward vector again to get the forward vector as the only direction
117
+ )
118
+ );
119
+ }
120
+
121
+ public onPointerEnter(): void {
122
+ this._hovered = true;
123
+ if (this.parent) {
124
+ this.parent.onHoverAxis(this, true);
125
+ }
126
+ }
127
+
128
+ public onPointerLeave(): void {
129
+ this._hovered = false;
130
+ if (this.parent) {
131
+ this.parent.onHoverAxis(this, false);
132
+ }
133
+ }
134
+
135
+ public onDragStart(): void {
136
+ if (this.parent) {
137
+ this.parent.onAxisDragStart(this);
138
+ }
139
+ }
140
+
141
+ public onDrag(e: DraggableEvent): void {
142
+ if (this.parent) {
143
+ this.parent.onAxisDrag(this, e);
144
+ }
145
+ }
146
+
147
+ public onDragEnd(): void {
148
+ if (this.parent) {
149
+ this.parent.onAxisDragEnd(this);
150
+ }
151
+ }
152
+ }