@shopware-ag/dive 1.0.7 → 1.0.8
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.
- package/package.json +6 -3
- package/src/__test__/DIVE.test.ts +244 -0
- package/src/animation/AnimationSystem.ts +14 -0
- package/src/animation/__test__/AnimationSystem.test.ts +19 -0
- package/src/axiscamera/AxisCamera.ts +50 -0
- package/src/axiscamera/__test__/AxisCamera.test.ts +41 -0
- package/src/camera/PerspectiveCamera.ts +43 -0
- package/src/camera/__test__/PerspectiveCamera.test.ts +27 -0
- package/src/com/Communication.ts +382 -0
- package/src/com/__test__/Communication.test.ts +612 -0
- package/src/com/actions/camera/getcameratransform.ts +9 -0
- package/src/com/actions/camera/movecamera.ts +15 -0
- package/src/com/actions/camera/resetcamera.ts +4 -0
- package/src/com/actions/camera/setcameralayer.ts +4 -0
- package/src/com/actions/camera/setcameratransform.ts +9 -0
- package/src/com/actions/camera/zoomcamera.ts +4 -0
- package/src/com/actions/index.ts +41 -0
- package/src/com/actions/media/generatemedia.ts +15 -0
- package/src/com/actions/object/addobject.ts +6 -0
- package/src/com/actions/object/deleteobject.ts +6 -0
- package/src/com/actions/object/getallobjects.ts +6 -0
- package/src/com/actions/object/getobjects.ts +6 -0
- package/src/com/actions/object/model/modelloaded.ts +4 -0
- package/src/com/actions/object/model/placeonfloor.ts +4 -0
- package/src/com/actions/object/selectobject.ts +6 -0
- package/src/com/actions/object/updateobject.ts +6 -0
- package/src/com/actions/scene/getallscenedata.ts +23 -0
- package/src/com/actions/scene/setbackground.ts +4 -0
- package/src/com/actions/scene/updatescene.ts +9 -0
- package/src/com/actions/toolbox/select/setgizmomode.ts +4 -0
- package/src/com/index.ts +4 -0
- package/src/com/types.ts +30 -0
- package/src/constant/AxisHelperColors.ts +7 -0
- package/src/constant/GridColors.ts +2 -0
- package/src/constant/VisibilityLayerMask.ts +5 -0
- package/src/controls/OrbitControls.ts +145 -0
- package/src/controls/__test__/OrbitControls.test.ts +181 -0
- package/src/grid/Grid.ts +22 -0
- package/src/grid/__test__/Grid.test.ts +19 -0
- package/src/helper/applyMixins/__test__/applyMixins.test.ts +27 -0
- package/src/helper/applyMixins/applyMixins.ts +15 -0
- package/src/helper/getObjectDelta/__test__/getObjectDelta.spec.ts +152 -0
- package/src/helper/getObjectDelta/getObjectDelta.ts +101 -0
- package/src/interface/Moveable.ts +13 -0
- package/src/interface/Rotatable.ts +10 -0
- package/src/interface/Scalable.ts +10 -0
- package/src/interface/Selectable.ts +11 -0
- package/src/interface/__test__/Interfaces.test.ts +13 -0
- package/src/light/AmbientLight.ts +29 -0
- package/src/light/PointLight.ts +63 -0
- package/src/light/SceneLight.ts +60 -0
- package/src/light/__test__/AmbientLight.test.ts +44 -0
- package/src/light/__test__/PointLight.test.ts +98 -0
- package/src/light/__test__/SceneLight.test.ts +122 -0
- package/src/loadingmanager/LoadingManager.ts +44 -0
- package/src/loadingmanager/__test__/LoadingManager.test.ts +52 -0
- package/src/math/__test__/DIVEMath.test.ts +12 -0
- package/src/math/ceil/__test__/ceilExp.test.ts +12 -0
- package/src/math/ceil/ceilExp.ts +6 -0
- package/src/math/floor/__test__/floorExp.test.ts +14 -0
- package/src/math/floor/floorExp.ts +6 -0
- package/src/math/helper/__test__/shift.test.ts +12 -0
- package/src/math/helper/shift.ts +4 -0
- package/src/math/index.ts +19 -0
- package/src/math/round/__test__/roundExp.test.ts +14 -0
- package/src/math/round/roundExp.ts +7 -0
- package/src/math/toFixed/__test__/toFixedExp.test.ts +14 -0
- package/src/math/toFixed/toFixedExp.ts +6 -0
- package/src/math/truncate/__test__/truncateExp.test.ts +14 -0
- package/src/math/truncate/truncateExp.ts +6 -0
- package/src/mediacreator/MediaCreator.ts +65 -0
- package/src/mediacreator/__test__/MediaCreator.test.ts +113 -0
- package/src/model/Model.ts +72 -0
- package/src/model/__test__/Model.test.ts +163 -0
- package/src/primitive/floor/Floor.ts +34 -0
- package/src/primitive/floor/__test__/Floor.test.ts +21 -0
- package/src/renderer/Renderer.ts +165 -0
- package/src/renderer/__test__/Renderer.test.ts +169 -0
- package/src/scene/Scene.ts +49 -0
- package/src/scene/__test__/Scene.test.ts +70 -0
- package/src/scene/root/Root.ts +107 -0
- package/src/scene/root/__test__/Root.test.ts +129 -0
- package/src/scene/root/lightroot/LightRoot.ts +84 -0
- package/src/scene/root/lightroot/__test__/LightRoot.test.ts +137 -0
- package/src/scene/root/modelroot/ModelRoot.ts +82 -0
- package/src/scene/root/modelroot/__test__/ModelRoot.test.ts +185 -0
- package/src/toolbox/BaseTool.ts +18 -0
- package/src/toolbox/Toolbox.ts +76 -0
- package/src/toolbox/__test__/Toolbox.test.ts +109 -0
- package/src/toolbox/select/SelectTool.ts +123 -0
- package/src/toolbox/select/__test__/SelectTool.test.ts +190 -0
- package/build/dive.cjs +0 -1551
- package/build/dive.cjs.map +0 -1
- package/build/dive.d.cts +0 -558
- package/build/dive.d.ts +0 -558
- package/build/dive.js +0 -1516
- package/build/dive.js.map +0 -1
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import DIVEModelRoot from '../ModelRoot';
|
|
2
|
+
import DIVECommunication from '../../../../com/Communication';
|
|
3
|
+
import { TransformControls } from 'three/examples/jsm/Addons';
|
|
4
|
+
import { DIVEMoveable } from '../../../../interface/Moveable';
|
|
5
|
+
|
|
6
|
+
const mock_LoadGLTF = jest.fn().mockResolvedValue({});
|
|
7
|
+
const mock_SetPosition = jest.fn();
|
|
8
|
+
const mock_SetRotation = jest.fn();
|
|
9
|
+
const mock_SetScale = jest.fn();
|
|
10
|
+
const mock_PlaceOnFloor = jest.fn();
|
|
11
|
+
const mock_SetModel = jest.fn();
|
|
12
|
+
|
|
13
|
+
jest.mock('../../../../com/types.ts', () => {
|
|
14
|
+
return {
|
|
15
|
+
COMModel: {},
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
jest.mock('../../../../loadingmanager/LoadingManager.ts', () => {
|
|
20
|
+
return jest.fn(function () {
|
|
21
|
+
this.LoadGLTF = mock_LoadGLTF;
|
|
22
|
+
return this;
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
jest.mock('../../../../com/Communication.ts', () => {
|
|
27
|
+
return {
|
|
28
|
+
get: jest.fn(() => {
|
|
29
|
+
return {
|
|
30
|
+
PerformAction: jest.fn(),
|
|
31
|
+
}
|
|
32
|
+
}),
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
jest.mock('../../../../model/Model.ts', () => {
|
|
37
|
+
return jest.fn(function () {
|
|
38
|
+
this.isObject3D = true;
|
|
39
|
+
this.parent = null;
|
|
40
|
+
this.dispatchEvent = jest.fn();
|
|
41
|
+
this.userData = {
|
|
42
|
+
id: undefined,
|
|
43
|
+
};
|
|
44
|
+
this.SetModel = mock_SetModel;
|
|
45
|
+
this.SetPosition = mock_SetPosition;
|
|
46
|
+
this.SetRotation = mock_SetRotation;
|
|
47
|
+
this.SetScale = mock_SetScale;
|
|
48
|
+
this.PlaceOnFloor = mock_PlaceOnFloor;
|
|
49
|
+
this.removeFromParent = jest.fn();
|
|
50
|
+
return this;
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
jest.spyOn(DIVECommunication, 'get').mockReturnValue({ PerformAction: jest.fn() } as unknown as DIVECommunication);
|
|
55
|
+
|
|
56
|
+
const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => { });
|
|
57
|
+
|
|
58
|
+
describe('dive/scene/root/modelroot/DIVEModelRoot', () => {
|
|
59
|
+
beforeEach(() => {
|
|
60
|
+
consoleWarnSpy.mockClear();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should instantiate', () => {
|
|
64
|
+
const modelRoot = new DIVEModelRoot();
|
|
65
|
+
expect(modelRoot).toBeDefined();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should not add incorrect model', async () => {
|
|
69
|
+
const modelRoot = new DIVEModelRoot();
|
|
70
|
+
await expect(() => modelRoot.UpdateModel({ id: undefined })).not.toThrow();
|
|
71
|
+
expect(consoleWarnSpy).toHaveBeenCalled();
|
|
72
|
+
expect(modelRoot.children).toHaveLength(0);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should add basic model', async () => {
|
|
76
|
+
const modelRoot = new DIVEModelRoot();
|
|
77
|
+
await expect(() => modelRoot.UpdateModel({
|
|
78
|
+
id: 'test_id',
|
|
79
|
+
uri: 'not a real uri',
|
|
80
|
+
})).not.toThrow();
|
|
81
|
+
expect(mock_LoadGLTF).toHaveBeenCalledTimes(1);
|
|
82
|
+
expect(mock_SetModel).toHaveBeenCalledTimes(1);
|
|
83
|
+
expect(modelRoot.children).toHaveLength(1);
|
|
84
|
+
expect(modelRoot.children[0].userData.id).toBe('test_id');
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should add configured model', async () => {
|
|
88
|
+
const modelRoot = new DIVEModelRoot();
|
|
89
|
+
modelRoot.userData.id = 'something';
|
|
90
|
+
await expect(() => modelRoot.UpdateModel({
|
|
91
|
+
id: 'test_id',
|
|
92
|
+
uri: 'not a real uri',
|
|
93
|
+
position: { x: 1, y: 2, z: 3 },
|
|
94
|
+
rotation: { x: 1, y: 2, z: 3 },
|
|
95
|
+
scale: { x: 1, y: 2, z: 3 },
|
|
96
|
+
})).not.toThrow();
|
|
97
|
+
|
|
98
|
+
jest.spyOn(DIVECommunication, 'get').mockReturnValueOnce(undefined);
|
|
99
|
+
await expect(() => modelRoot.UpdateModel({
|
|
100
|
+
id: 'test_id',
|
|
101
|
+
uri: 'not a real uri',
|
|
102
|
+
position: { x: 1, y: 2, z: 3 },
|
|
103
|
+
rotation: { x: 1, y: 2, z: 3 },
|
|
104
|
+
scale: { x: 1, y: 2, z: 3 },
|
|
105
|
+
})).not.toThrow();
|
|
106
|
+
|
|
107
|
+
expect(modelRoot.children).toHaveLength(1);
|
|
108
|
+
expect(mock_SetPosition).toHaveBeenCalledTimes(2);
|
|
109
|
+
expect(mock_SetRotation).toHaveBeenCalledTimes(2);
|
|
110
|
+
expect(mock_SetScale).toHaveBeenCalledTimes(2);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should not place incorrect model on floor', async () => {
|
|
114
|
+
const modelRoot = new DIVEModelRoot();
|
|
115
|
+
expect(() => modelRoot.PlaceOnFloor({ id: undefined })).not.toThrow();
|
|
116
|
+
expect(consoleWarnSpy).toHaveBeenCalled();
|
|
117
|
+
expect(mock_PlaceOnFloor).toHaveBeenCalledTimes(0);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should not place non-existing model on floor', async () => {
|
|
121
|
+
const modelRoot = new DIVEModelRoot();
|
|
122
|
+
modelRoot.PlaceOnFloor({ id: 'test_id' });
|
|
123
|
+
expect(mock_PlaceOnFloor).toHaveBeenCalledTimes(0);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should place model on floor', async () => {
|
|
127
|
+
const modelRoot = new DIVEModelRoot();
|
|
128
|
+
await modelRoot.UpdateModel({
|
|
129
|
+
id: 'test_id',
|
|
130
|
+
uri: 'not a real uri',
|
|
131
|
+
position: { x: 1, y: 2, z: 3 },
|
|
132
|
+
rotation: { x: 1, y: 2, z: 3 },
|
|
133
|
+
scale: { x: 1, y: 2, z: 3 },
|
|
134
|
+
});
|
|
135
|
+
modelRoot.PlaceOnFloor({ id: 'test_id' });
|
|
136
|
+
expect(mock_PlaceOnFloor).toHaveBeenCalledTimes(1);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should get model', async () => {
|
|
140
|
+
const modelRoot = new DIVEModelRoot();
|
|
141
|
+
expect(() => modelRoot.GetModel({ id: undefined })).not.toThrow();
|
|
142
|
+
expect(consoleWarnSpy).toHaveBeenCalled();
|
|
143
|
+
expect(modelRoot.GetModel({ id: 'test_id' })).toBeUndefined();
|
|
144
|
+
await expect(() => modelRoot.UpdateModel({
|
|
145
|
+
id: 'test_id',
|
|
146
|
+
uri: 'not a real uri',
|
|
147
|
+
position: { x: 1, y: 2, z: 3 },
|
|
148
|
+
rotation: { x: 1, y: 2, z: 3 },
|
|
149
|
+
scale: { x: 1, y: 2, z: 3 },
|
|
150
|
+
})).not.toThrow();
|
|
151
|
+
expect(modelRoot.GetModel({ id: 'test_id' })).toBeDefined();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('should delete model', async () => {
|
|
155
|
+
const modelRoot = new DIVEModelRoot();
|
|
156
|
+
expect(() => modelRoot.DeleteModel({ id: undefined })).not.toThrow();
|
|
157
|
+
expect(consoleWarnSpy).toHaveBeenCalled();
|
|
158
|
+
consoleWarnSpy.mockClear();
|
|
159
|
+
expect(() => modelRoot.DeleteModel({ id: 'test_id' })).not.toThrow();
|
|
160
|
+
expect(consoleWarnSpy).toHaveBeenCalled();
|
|
161
|
+
await expect(() => modelRoot.UpdateModel({
|
|
162
|
+
id: 'test_id',
|
|
163
|
+
uri: 'not a real uri',
|
|
164
|
+
position: { x: 1, y: 2, z: 3 },
|
|
165
|
+
rotation: { x: 1, y: 2, z: 3 },
|
|
166
|
+
scale: { x: 1, y: 2, z: 3 },
|
|
167
|
+
})).not.toThrow();
|
|
168
|
+
(modelRoot.children[0] as unknown as DIVEMoveable).isMoveable = true;
|
|
169
|
+
expect(() => modelRoot.DeleteModel({ id: 'test_id' })).not.toThrow();
|
|
170
|
+
|
|
171
|
+
await expect(() => modelRoot.UpdateModel({
|
|
172
|
+
id: 'test_id',
|
|
173
|
+
uri: 'not a real uri',
|
|
174
|
+
position: { x: 1, y: 2, z: 3 },
|
|
175
|
+
rotation: { x: 1, y: 2, z: 3 },
|
|
176
|
+
scale: { x: 1, y: 2, z: 3 },
|
|
177
|
+
})).not.toThrow();
|
|
178
|
+
(modelRoot.children[0] as unknown as DIVEMoveable).isMoveable = true;
|
|
179
|
+
(modelRoot.children[0] as unknown as DIVEMoveable).gizmo = {
|
|
180
|
+
detach: jest.fn(),
|
|
181
|
+
} as unknown as TransformControls;
|
|
182
|
+
expect(() => modelRoot.DeleteModel({ id: 'test_id' })).not.toThrow();
|
|
183
|
+
expect(modelRoot.children).toHaveLength(0);
|
|
184
|
+
});
|
|
185
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
2
|
+
export default abstract class DIVEBaseTool {
|
|
3
|
+
protected name: string;
|
|
4
|
+
|
|
5
|
+
protected constructor() {
|
|
6
|
+
this.name = "BaseTool";
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
public Activate(): void { }
|
|
10
|
+
|
|
11
|
+
public Deactivate(): void { }
|
|
12
|
+
|
|
13
|
+
public onPointerDown(e: PointerEvent): void { }
|
|
14
|
+
|
|
15
|
+
public onPointerUp(e: PointerEvent): void { }
|
|
16
|
+
|
|
17
|
+
public onWheel(e: WheelEvent): void { }
|
|
18
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
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";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A Toolbox to activate and deactivate tools to use with the pointer.
|
|
8
|
+
*
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export default class DIVEToolbox {
|
|
13
|
+
public static readonly DefaultTool = 'select';
|
|
14
|
+
|
|
15
|
+
private activeTool: DIVEBaseTool;
|
|
16
|
+
|
|
17
|
+
private selectTool: DIVESelectTool;
|
|
18
|
+
|
|
19
|
+
private removeListenersCallback: () => void = () => { };
|
|
20
|
+
|
|
21
|
+
constructor(scene: DIVEScene, controller: DIVEOrbitControls) {
|
|
22
|
+
this.selectTool = new DIVESelectTool(scene, controller);
|
|
23
|
+
|
|
24
|
+
controller.domElement.addEventListener('pointerdown', this.onPointerDown.bind(this));
|
|
25
|
+
controller.domElement.addEventListener('pointerup', this.onPointerUp.bind(this));
|
|
26
|
+
controller.domElement.addEventListener('wheel', this.onWheel.bind(this));
|
|
27
|
+
|
|
28
|
+
this.removeListenersCallback = () => {
|
|
29
|
+
controller.domElement.removeEventListener('pointerdown', this.onPointerDown.bind(this));
|
|
30
|
+
controller.domElement.removeEventListener('pointerup', this.onPointerUp.bind(this));
|
|
31
|
+
controller.domElement.removeEventListener('wheel', this.onWheel.bind(this));
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// default tool
|
|
35
|
+
this.activeTool = this.selectTool;
|
|
36
|
+
this.activeTool.Activate();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public dispose(): void {
|
|
40
|
+
this.removeListenersCallback();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
public GetActiveTool(): DIVEBaseTool {
|
|
44
|
+
return this.activeTool;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public UseTool(tool: string): void {
|
|
48
|
+
this.activeTool.Deactivate();
|
|
49
|
+
switch (tool) {
|
|
50
|
+
case "select": {
|
|
51
|
+
this.selectTool.Activate();
|
|
52
|
+
this.activeTool = this.selectTool;
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
default: {
|
|
56
|
+
throw new Error(`ToolBox.UseTool: Unknown tool: ${tool}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public SetGizmoMode(mode: 'translate' | 'rotate' | 'scale'): void {
|
|
62
|
+
this.selectTool.SetGizmoMode(mode);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public onPointerDown(e: PointerEvent): void {
|
|
66
|
+
this.activeTool.onPointerDown(e);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public onPointerUp(e: PointerEvent): void {
|
|
70
|
+
this.activeTool.onPointerUp(e);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
public onWheel(e: WheelEvent): void {
|
|
74
|
+
this.activeTool.onWheel(e);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import DIVEOrbitControls from '../../controls/OrbitControls';
|
|
2
|
+
import DIVEScene from '../../scene/Scene';
|
|
3
|
+
import DIVEToolbox from '../Toolbox';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @jest-environment jsdom
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const mock_addEventListener = jest.fn();
|
|
10
|
+
const mock_removeEventListener = jest.fn();
|
|
11
|
+
|
|
12
|
+
const mock_Canvas = {
|
|
13
|
+
width: 0,
|
|
14
|
+
height: 0,
|
|
15
|
+
addEventListener: mock_addEventListener,
|
|
16
|
+
getContext: jest.fn(),
|
|
17
|
+
removeEventListener: mock_removeEventListener,
|
|
18
|
+
clientWidth: 0,
|
|
19
|
+
clientHeight: 0,
|
|
20
|
+
offsetLeft: 0,
|
|
21
|
+
offsetTop: 0,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const mock_Activate = jest.fn();
|
|
25
|
+
const mock_Deactivate = jest.fn();
|
|
26
|
+
const mock_onPointerDown = jest.fn();
|
|
27
|
+
const mock_onPointerUp = jest.fn();
|
|
28
|
+
const mock_onWheel = jest.fn();
|
|
29
|
+
const mock_SetGizmoMode = jest.fn();
|
|
30
|
+
|
|
31
|
+
jest.mock('../select/SelectTool.ts', () => {
|
|
32
|
+
return jest.fn(function () {
|
|
33
|
+
this.Activate = mock_Activate;
|
|
34
|
+
this.Deactivate = mock_Deactivate;
|
|
35
|
+
this.onPointerDown = mock_onPointerDown;
|
|
36
|
+
this.onPointerUp = mock_onPointerUp;
|
|
37
|
+
this.onWheel = mock_onWheel;
|
|
38
|
+
this.SetGizmoMode = mock_SetGizmoMode;
|
|
39
|
+
return this;
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const mockController = {
|
|
44
|
+
domElement: mock_Canvas,
|
|
45
|
+
object: {}
|
|
46
|
+
} as unknown as DIVEOrbitControls;
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
describe('dive/toolbox/DIVEToolBox', () => {
|
|
50
|
+
afterEach(() => {
|
|
51
|
+
jest.clearAllMocks();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should instantiate', () => {
|
|
55
|
+
const toolBox = new DIVEToolbox({} as DIVEScene, mockController);
|
|
56
|
+
expect(toolBox).toBeDefined();
|
|
57
|
+
expect(mock_Activate).toHaveBeenCalledTimes(1);
|
|
58
|
+
expect(mock_addEventListener).toHaveBeenCalled();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should dispose', () => {
|
|
62
|
+
const toolBox = new DIVEToolbox({} as DIVEScene, mockController);
|
|
63
|
+
toolBox.dispose();
|
|
64
|
+
expect(mock_removeEventListener).toHaveBeenCalled();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should throw with incorrect tool', () => {
|
|
68
|
+
const toolBox = new DIVEToolbox({} as DIVEScene, mockController);
|
|
69
|
+
expect(() => toolBox.UseTool('not a real tool')).toThrow();
|
|
70
|
+
expect(mock_Deactivate).toHaveBeenCalledTimes(1);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('should use select tool', () => {
|
|
74
|
+
const toolBox = new DIVEToolbox({} as DIVEScene, mockController);
|
|
75
|
+
expect(mock_Activate).toHaveBeenCalledTimes(1);
|
|
76
|
+
toolBox.UseTool(DIVEToolbox.DefaultTool);
|
|
77
|
+
expect(mock_Deactivate).toHaveBeenCalledTimes(1);
|
|
78
|
+
expect(mock_Activate).toHaveBeenCalledTimes(2);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should execute pointer down event on tool', () => {
|
|
82
|
+
const toolBox = new DIVEToolbox({} as DIVEScene, mockController);
|
|
83
|
+
toolBox.onPointerDown({ type: 'pointerdown' } as PointerEvent);
|
|
84
|
+
expect(mock_onPointerDown).toHaveBeenCalledTimes(1);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should execute pointer up event on tool', () => {
|
|
88
|
+
const toolBox = new DIVEToolbox({} as DIVEScene, mockController);
|
|
89
|
+
toolBox.onPointerUp({ type: 'pointerup' } as PointerEvent);
|
|
90
|
+
expect(mock_onPointerUp).toHaveBeenCalledTimes(1);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should execute wheel event on tool', () => {
|
|
94
|
+
const toolBox = new DIVEToolbox({} as DIVEScene, mockController);
|
|
95
|
+
toolBox.onWheel({ type: 'wheel' } as WheelEvent);
|
|
96
|
+
expect(mock_onWheel).toHaveBeenCalledTimes(1);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should get active tool', () => {
|
|
100
|
+
const toolBox = new DIVEToolbox({} as DIVEScene, mockController);
|
|
101
|
+
expect(toolBox.GetActiveTool()).toBeDefined();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should set gizmo mode', () => {
|
|
105
|
+
const toolBox = new DIVEToolbox({} as DIVEScene, mockController);
|
|
106
|
+
toolBox.SetGizmoMode('translate');
|
|
107
|
+
expect(mock_SetGizmoMode).toHaveBeenCalledTimes(1);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
@@ -0,0 +1,123 @@
|
|
|
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";
|
|
5
|
+
import DIVEScene from "../../scene/Scene.ts";
|
|
6
|
+
import { HELPER_LAYER_MASK, PRODUCT_LAYER_MASK, UI_LAYER_MASK } from "../../constant/VisibilityLayerMask.ts";
|
|
7
|
+
import { DIVEMoveable } from "../../interface/Moveable.ts";
|
|
8
|
+
import DIVEOrbitControls from "../../controls/OrbitControls.ts";
|
|
9
|
+
|
|
10
|
+
export interface DIVEObjectEventMap {
|
|
11
|
+
select: object
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A Tool to select and move objects in the scene.
|
|
16
|
+
*
|
|
17
|
+
* Objects have to implement the DIVESelectable interface to be selectable and DIVEMoveable to be moveable.
|
|
18
|
+
*
|
|
19
|
+
* @module
|
|
20
|
+
*/
|
|
21
|
+
|
|
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
|
+
|
|
29
|
+
|
|
30
|
+
constructor(scene: DIVEScene, controller: DIVEOrbitControls) {
|
|
31
|
+
super();
|
|
32
|
+
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
|
+
}
|
|
64
|
+
|
|
65
|
+
public Activate(): void { }
|
|
66
|
+
|
|
67
|
+
public SetGizmoMode(mode: 'translate' | 'rotate' | 'scale'): void {
|
|
68
|
+
this.gizmo.setMode(mode);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
public Select(selectable: DIVESelectable): void {
|
|
72
|
+
if (selectable.onSelect) selectable.onSelect();
|
|
73
|
+
|
|
74
|
+
if ('isMoveable' in selectable) {
|
|
75
|
+
const movable = selectable as (Object3D & DIVESelectable & DIVEMoveable);
|
|
76
|
+
movable.gizmo = this.gizmo;
|
|
77
|
+
this.gizmo.attach(movable);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
public Deselect(selectable: DIVESelectable): void {
|
|
82
|
+
if (selectable.onDeselect) selectable.onDeselect();
|
|
83
|
+
if (('isMoveable' in selectable)) (selectable as unknown as DIVEMoveable).gizmo = null;
|
|
84
|
+
this.gizmo.detach();
|
|
85
|
+
}
|
|
86
|
+
|
|
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);
|
|
90
|
+
|
|
91
|
+
const first = this.raycastFirst();
|
|
92
|
+
const selectable = this.findSelectableInterface(first?.object);
|
|
93
|
+
if (!first || !selectable) {
|
|
94
|
+
if (this.gizmo.object) this.Deselect(this.gizmo.object as (Object3D & DIVESelectable));
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
this.Select(selectable);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private findSelectableInterface(child: Object3D): (Object3D & DIVESelectable) | undefined {
|
|
102
|
+
if (child === undefined) return undefined;
|
|
103
|
+
|
|
104
|
+
if (child.parent === null) {
|
|
105
|
+
// in this case it is the scene itself
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if ('isSelectable' in child) {
|
|
110
|
+
return child as (Object3D & DIVESelectable);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return this.findSelectableInterface(child.parent);
|
|
114
|
+
}
|
|
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
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import DIVESelectTool from '../SelectTool';
|
|
2
|
+
import DIVEScene from '../../../scene/Scene';
|
|
3
|
+
import DIVEOrbitControls from '../../../controls/OrbitControls';
|
|
4
|
+
import DIVEPerspectiveCamera from '../../../camera/PerspectiveCamera';
|
|
5
|
+
import DIVERenderer, { DIVERendererDefaultSettings } from '../../../renderer/Renderer';
|
|
6
|
+
import { DIVESelectable } from '../../../interface/Selectable';
|
|
7
|
+
|
|
8
|
+
jest.mock('../../../renderer/Renderer', () => {
|
|
9
|
+
return jest.fn(function () {
|
|
10
|
+
return this;
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
|
|
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
|
+
jest.mock('../../../camera/PerspectiveCamera', () => {
|
|
23
|
+
return jest.fn(function () {
|
|
24
|
+
this.isPerspectiveCamera = true;
|
|
25
|
+
this.layers = {
|
|
26
|
+
mask: 0,
|
|
27
|
+
};
|
|
28
|
+
return this;
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
jest.mock('../../../controls/OrbitControls', () => {
|
|
33
|
+
return jest.fn(function () {
|
|
34
|
+
this.enabled = true;
|
|
35
|
+
this.domElement = {
|
|
36
|
+
clientWIdth: 0,
|
|
37
|
+
clientHeight: 0,
|
|
38
|
+
};
|
|
39
|
+
this.object = {
|
|
40
|
+
layers: {
|
|
41
|
+
mask: 0,
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
return this;
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
jest.mock('../../../scene/Scene', () => {
|
|
49
|
+
return jest.fn(function () {
|
|
50
|
+
this.add = jest.fn();
|
|
51
|
+
this.Root = {
|
|
52
|
+
children: [],
|
|
53
|
+
}
|
|
54
|
+
return this;
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const mock_intersectObjects = jest.fn().mockReturnValue([]);
|
|
59
|
+
|
|
60
|
+
jest.mock('three', () => {
|
|
61
|
+
return {
|
|
62
|
+
Vector2: jest.fn(function () {
|
|
63
|
+
return this;
|
|
64
|
+
}),
|
|
65
|
+
Raycaster: jest.fn(function () {
|
|
66
|
+
this.setFromCamera = jest.fn();
|
|
67
|
+
this.intersectObjects = mock_intersectObjects;
|
|
68
|
+
this.layers = {
|
|
69
|
+
mask: 0,
|
|
70
|
+
};
|
|
71
|
+
return this;
|
|
72
|
+
}),
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const mock_attach = jest.fn();
|
|
77
|
+
const mock_detach = jest.fn();
|
|
78
|
+
|
|
79
|
+
jest.mock('three/examples/jsm/Addons.js', () => {
|
|
80
|
+
return {
|
|
81
|
+
TransformControls: jest.fn(function () {
|
|
82
|
+
this.addEventListener = (type: string, callback: (e: object) => void) => {
|
|
83
|
+
callback({ value: false });
|
|
84
|
+
this.object = {};
|
|
85
|
+
callback({ value: false });
|
|
86
|
+
this.object = {
|
|
87
|
+
onMove: 'hello',
|
|
88
|
+
};
|
|
89
|
+
callback({ value: false });
|
|
90
|
+
this.object = {
|
|
91
|
+
onMove: jest.fn(),
|
|
92
|
+
};
|
|
93
|
+
callback({ value: false });
|
|
94
|
+
},
|
|
95
|
+
this.attach = mock_attach,
|
|
96
|
+
this.detach = mock_detach,
|
|
97
|
+
this.traverse = function (callback: (obj: object) => void) {
|
|
98
|
+
callback(this);
|
|
99
|
+
};
|
|
100
|
+
this.setMode = jest.fn();
|
|
101
|
+
this.getRaycaster = jest.fn().mockReturnValue({
|
|
102
|
+
layers: {
|
|
103
|
+
mask: 0,
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
this.layers = {
|
|
107
|
+
mask: 0,
|
|
108
|
+
};
|
|
109
|
+
return this;
|
|
110
|
+
}),
|
|
111
|
+
};
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const mockCamera: DIVEPerspectiveCamera = {} as DIVEPerspectiveCamera;
|
|
115
|
+
const mockRenderer: DIVERenderer = new DIVERenderer(DIVERendererDefaultSettings);
|
|
116
|
+
const mockScene: DIVEScene = new DIVEScene();
|
|
117
|
+
const mockController: DIVEOrbitControls = new DIVEOrbitControls(mockCamera, mockRenderer);
|
|
118
|
+
|
|
119
|
+
describe('dive/toolbox/select/DIVESelectTool', () => {
|
|
120
|
+
it('should instantiate', () => {
|
|
121
|
+
const selectTool = new DIVESelectTool(mockScene, mockController);
|
|
122
|
+
expect(selectTool).toBeDefined();
|
|
123
|
+
expect(mockController.object.onSetCameraLayer).toBeDefined();
|
|
124
|
+
expect(() => mockController.object.onSetCameraLayer(0)).not.toThrow();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('should activate', () => {
|
|
128
|
+
const selectTool = new DIVESelectTool(mockScene, mockController);
|
|
129
|
+
expect(() => selectTool.Activate()).not.toThrow();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('should execute onPointerUp without hit', () => {
|
|
133
|
+
const selectTool = new DIVESelectTool(mockScene, mockController);
|
|
134
|
+
expect(() => selectTool.onPointerUp({ offsetX: 0, offsetY: 0 } as PointerEvent)).not.toThrow();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should execute onPointerUp with hit', () => {
|
|
138
|
+
mock_intersectObjects.mockReturnValueOnce([{ object: { parent: { name: 'this is the test scene root!!!', parent: null } } }]);
|
|
139
|
+
const selectTool = new DIVESelectTool(mockScene, mockController);
|
|
140
|
+
expect(() => selectTool.onPointerUp({ offsetX: 0, offsetY: 0 } as PointerEvent)).not.toThrow();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should execute onPointerUp with ISelectable hit', () => {
|
|
144
|
+
const mock_onSelect = jest.fn();
|
|
145
|
+
|
|
146
|
+
mock_intersectObjects.mockReturnValueOnce([{
|
|
147
|
+
object: {
|
|
148
|
+
isSelectable: true,
|
|
149
|
+
onSelect: mock_onSelect,
|
|
150
|
+
parent: {
|
|
151
|
+
name: 'this is the test scene root!!!',
|
|
152
|
+
parent: null,
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
}]);
|
|
156
|
+
const selectTool = new DIVESelectTool(mockScene, mockController);
|
|
157
|
+
expect(() => selectTool.onPointerUp({ offsetX: 0, offsetY: 0 } as PointerEvent)).not.toThrow();
|
|
158
|
+
expect(mock_onSelect).toHaveBeenCalledTimes(1);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('should execute onPointerUp with IMovable hit', () => {
|
|
162
|
+
const mock_onSelect = jest.fn();
|
|
163
|
+
|
|
164
|
+
mock_intersectObjects.mockReturnValueOnce([{
|
|
165
|
+
object: {
|
|
166
|
+
isSelectable: true,
|
|
167
|
+
isMoveable: true,
|
|
168
|
+
onSelect: mock_onSelect,
|
|
169
|
+
parent: {
|
|
170
|
+
name: 'this is the test scene root!!!',
|
|
171
|
+
parent: null,
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
}]);
|
|
175
|
+
const selectTool = new DIVESelectTool(mockScene, mockController);
|
|
176
|
+
expect(() => selectTool.onPointerUp({ offsetX: 0, offsetY: 0 } as PointerEvent)).not.toThrow();
|
|
177
|
+
expect(mock_attach).toHaveBeenCalledTimes(1);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('should deselect', () => {
|
|
181
|
+
const selectTool = new DIVESelectTool(mockScene, mockController);
|
|
182
|
+
expect(() => selectTool.Deselect({ isSelectable: true })).not.toThrow();
|
|
183
|
+
expect(() => selectTool.Deselect({ isMoveable: true, onDeselect: jest.fn() } as unknown as DIVESelectable)).not.toThrow();
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
it('should set gizmo mode', () => {
|
|
187
|
+
const selectTool = new DIVESelectTool(mockScene, mockController);
|
|
188
|
+
expect(() => selectTool.SetGizmoMode('translate')).not.toThrow();
|
|
189
|
+
});
|
|
190
|
+
});
|