@shopware-ag/dive 1.7.0 → 1.9.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.
@@ -0,0 +1,434 @@
1
+ import { DIVEPrimitive } from '../Primitive';
2
+ import DIVECommunication from '../../com/Communication';
3
+ import { Vector3, Box3, Mesh, type Texture, type MeshStandardMaterial } from 'three';
4
+ import type DIVEScene from '../../scene/Scene';
5
+ import { type COMMaterial, type COMGeometry } from '../../com/types';
6
+
7
+ const intersectObjectsMock = jest.fn();
8
+
9
+ jest.mock('three', () => {
10
+ return {
11
+ Vector3: jest.fn(function (x: number, y: number, z: number) {
12
+ this.x = x;
13
+ this.y = y;
14
+ this.z = z;
15
+ this.copy = (vec3: Vector3) => {
16
+ this.x = vec3.x;
17
+ this.y = vec3.y;
18
+ this.z = vec3.z;
19
+ return this;
20
+ };
21
+ this.set = (x: number, y: number, z: number) => {
22
+ this.x = x;
23
+ this.y = y;
24
+ this.z = z;
25
+ return this;
26
+ };
27
+ this.multiply = (vec3: Vector3) => {
28
+ this.x *= vec3.x;
29
+ this.y *= vec3.y;
30
+ this.z *= vec3.z;
31
+ return this;
32
+ };
33
+ this.clone = () => {
34
+ return new Vector3(this.x, this.y, this.z);
35
+ };
36
+ this.setY = (y: number) => {
37
+ this.y = y;
38
+ return this;
39
+ }
40
+ this.add = (vec3: Vector3) => {
41
+ this.x += vec3.x;
42
+ this.y += vec3.y;
43
+ this.z += vec3.z;
44
+ return this;
45
+ };
46
+ this.sub = (vec3: Vector3) => {
47
+ this.x -= vec3.x;
48
+ this.y -= vec3.y;
49
+ this.z -= vec3.z;
50
+ return this;
51
+ };
52
+ return this;
53
+ }),
54
+ Object3D: jest.fn(function () {
55
+ this.clear = jest.fn();
56
+ this.color = {};
57
+ this.intensity = 0;
58
+ this.layers = {
59
+ mask: 0,
60
+ };
61
+ this.shadow = {
62
+ radius: 0,
63
+ mapSize: { width: 0, height: 0 },
64
+ bias: 0,
65
+ camera: {
66
+ near: 0,
67
+ far: 0,
68
+ fov: 0,
69
+ },
70
+ }
71
+ this.add = jest.fn();
72
+ this.sub = jest.fn();
73
+ this.children = [{
74
+ visible: true,
75
+ material: {
76
+ color: {},
77
+ },
78
+ }];
79
+ this.userData = {};
80
+ this.position = new Vector3();
81
+ this.rotation = {
82
+ x: 0,
83
+ y: 0,
84
+ z: 0,
85
+ setFromVector3: jest.fn(),
86
+ };
87
+ this.scale = {
88
+ x: 1,
89
+ y: 1,
90
+ z: 1,
91
+ set: jest.fn(),
92
+ };
93
+ this.localToWorld = (vec3: Vector3) => {
94
+ return vec3;
95
+ };
96
+ this.mesh = new Mesh();
97
+ this.traverse = jest.fn((callback) => {
98
+ callback(this.children[0])
99
+ });
100
+ return this;
101
+ }),
102
+ Box3: jest.fn(function () {
103
+ this.min = new Vector3(Infinity, Infinity, Infinity);
104
+ this.max = new Vector3(-Infinity, -Infinity, -Infinity);
105
+ this.getCenter = jest.fn(() => {
106
+ return new Vector3(0, 0, 0);
107
+ });
108
+ this.expandByObject = jest.fn();
109
+ this.setFromObject = jest.fn();
110
+
111
+ return this;
112
+ }),
113
+ Raycaster: jest.fn(function () {
114
+ this.intersectObjects = intersectObjectsMock;
115
+ this.layers = {
116
+ mask: 0,
117
+ };
118
+ return this;
119
+ }),
120
+ Mesh: jest.fn(function () {
121
+ this.geometry = {
122
+ computeBoundingBox: jest.fn(),
123
+ boundingBox: new Box3(),
124
+ };
125
+ this.material = {};
126
+ this.castShadow = true;
127
+ this.receiveShadow = true;
128
+ this.layers = {
129
+ mask: 0,
130
+ };
131
+ this.updateWorldMatrix = jest.fn();
132
+ this.traverse = jest.fn();
133
+ this.removeFromParent = jest.fn();
134
+ this.localToWorld = (vec3: Vector3) => {
135
+ return vec3;
136
+ };
137
+ return this;
138
+ }),
139
+ BufferGeometry: jest.fn(function () {
140
+ this.setAttribute = jest.fn();
141
+ this.setIndex = jest.fn();
142
+ return this;
143
+ }),
144
+ CylinderGeometry: jest.fn(function () {
145
+ return {};
146
+ }),
147
+ SphereGeometry: jest.fn(function () {
148
+ return {};
149
+ }),
150
+ BoxGeometry: jest.fn(function () {
151
+ return {};
152
+ }),
153
+ ConeGeometry: jest.fn(function () {
154
+ return {};
155
+ }),
156
+ Float32BufferAttribute: jest.fn(function () {
157
+ return {};
158
+ }),
159
+ Uint32BufferAttribute: jest.fn(function () {
160
+ return {};
161
+ }),
162
+ MeshStandardMaterial: jest.fn(function () {
163
+ this.color = {};
164
+ this.roughness = 0;
165
+ this.roughnessMap = null;
166
+ this.metalness = 0;
167
+ this.metalnessMap = null;
168
+ return this;
169
+ }),
170
+ Color: jest.fn(function () {
171
+ return this;
172
+ }),
173
+ }
174
+ });
175
+
176
+ jest.mock('../../com/Communication.ts', () => {
177
+ return {
178
+ get: jest.fn(() => {
179
+ return {
180
+ PerformAction: jest.fn(),
181
+ }
182
+ }),
183
+ }
184
+ });
185
+
186
+ jest.spyOn(DIVECommunication, 'get').mockReturnValue({ PerformAction: jest.fn() } as unknown as DIVECommunication);
187
+
188
+ let primitive: DIVEPrimitive;
189
+
190
+ describe('dive/primitive/DIVEPrimitive', () => {
191
+ beforeEach(() => {
192
+ primitive = new DIVEPrimitive();
193
+ });
194
+
195
+ afterEach(() => {
196
+ jest.clearAllMocks();
197
+ });
198
+
199
+ it('should instantiate', () => {
200
+ expect(primitive).toBeDefined();
201
+ });
202
+
203
+ it('should set geometry', () => {
204
+ const bufferGeometry = {} as COMGeometry;
205
+ expect(() => primitive.SetGeometry(bufferGeometry)).not.toThrow();
206
+ });
207
+
208
+ it('should set position', () => {
209
+ expect(() => primitive.SetPosition({ x: 0, y: 0, z: 0 })).not.toThrow();
210
+ });
211
+
212
+ it('should set rotation', () => {
213
+ expect(() => primitive.SetRotation({ x: 0, y: 0, z: 0 })).not.toThrow();
214
+ });
215
+
216
+ it('should set scale', () => {
217
+ expect(() => primitive.SetScale({ x: 1, y: 1, z: 1 })).not.toThrow();
218
+ });
219
+
220
+ it('should set visibility', () => {
221
+ expect(() => primitive.SetVisibility(true)).not.toThrow();
222
+ });
223
+
224
+ it('should set to world origin', () => {
225
+ primitive.userData.id = 'something';
226
+
227
+ expect(() => primitive.SetToWorldOrigin()).not.toThrow();
228
+ expect(primitive.position.x).toBe(0);
229
+ expect(primitive.position.y).toBe(0);
230
+ expect(primitive.position.z).toBe(0);
231
+
232
+ jest.spyOn(DIVECommunication, 'get').mockReturnValueOnce(undefined);
233
+ expect(() => primitive.SetToWorldOrigin()).not.toThrow();
234
+ });
235
+
236
+ it('should place on floor', () => {
237
+ primitive.userData.id = 'something';
238
+
239
+ expect(() => primitive.PlaceOnFloor()).not.toThrow();
240
+
241
+ jest.spyOn(DIVECommunication, 'get').mockReturnValueOnce(undefined);
242
+ expect(() => primitive.PlaceOnFloor()).not.toThrow();
243
+ });
244
+
245
+ it('should drop it', () => {
246
+ const comMock = {
247
+ PerformAction: jest.fn(),
248
+ } as unknown as DIVECommunication;
249
+ jest.spyOn(DIVECommunication, 'get').mockReturnValue(comMock);
250
+
251
+ const size = {
252
+ x: 1,
253
+ y: 1,
254
+ z: 1,
255
+ };
256
+
257
+ primitive.userData.id = 'something';
258
+ primitive.position.set(0, 4, 0);
259
+ primitive['_boundingBox'] = {
260
+ min: new Vector3(-size.x / 2, -size.y / 2, -size.z / 2),
261
+ max: new Vector3(size.x / 2, size.y / 2, size.z / 2),
262
+ getCenter: jest.fn(() => {
263
+ return new Vector3(0, 0, 0);
264
+ }),
265
+ } as unknown as Box3;
266
+
267
+
268
+ const hitObject = new Mesh();
269
+ hitObject.geometry.boundingBox = new Box3();
270
+ hitObject.geometry.boundingBox.max = new Vector3(0, 2, 0);
271
+ intersectObjectsMock.mockReturnValue([{
272
+ object: hitObject,
273
+
274
+ }]);
275
+
276
+ const scene = {
277
+ parent: null,
278
+ Root: {
279
+ children: [
280
+ primitive,
281
+ ],
282
+ },
283
+ } as unknown as DIVEScene;
284
+ scene.Root.parent = scene;
285
+
286
+ // test when parent is not set
287
+ console.warn = jest.fn();
288
+ expect(() => primitive.DropIt()).not.toThrow();
289
+ expect(console.warn).toHaveBeenCalledTimes(1);
290
+
291
+ primitive.parent = scene.Root;
292
+
293
+ expect(() => primitive.DropIt()).not.toThrow();
294
+ expect(primitive.position.y).toBe(2.5);
295
+ expect(comMock.PerformAction).toHaveBeenCalledTimes(1);
296
+
297
+ expect(() => primitive.DropIt()).not.toThrow();
298
+ expect(comMock.PerformAction).toHaveBeenCalledTimes(1);
299
+
300
+ // reset for PerformAction to be called again
301
+ primitive.position.y = 2;
302
+ jest.spyOn(DIVECommunication, 'get').mockReturnValueOnce(undefined);
303
+ expect(() => primitive.DropIt()).not.toThrow();
304
+ expect(comMock.PerformAction).toHaveBeenCalledTimes(1);
305
+
306
+
307
+ });
308
+
309
+ it('should onMove', () => {
310
+ primitive.userData.id = 'something';
311
+
312
+ expect(() => primitive.onMove()).not.toThrow();
313
+
314
+ jest.spyOn(DIVECommunication, 'get').mockReturnValueOnce(undefined);
315
+ expect(() => primitive.onMove()).not.toThrow();
316
+ });
317
+
318
+ it('should onSelect', () => {
319
+ primitive.userData.id = 'something';
320
+
321
+ expect(() => primitive.onSelect()).not.toThrow();
322
+
323
+ jest.spyOn(DIVECommunication, 'get').mockReturnValueOnce(undefined);
324
+ expect(() => primitive.onSelect()).not.toThrow();
325
+ });
326
+
327
+ it('should onDeselect', () => {
328
+ primitive.userData.id = 'something';
329
+
330
+ expect(() => primitive.onDeselect()).not.toThrow();
331
+
332
+ jest.spyOn(DIVECommunication, 'get').mockReturnValueOnce(undefined);
333
+ expect(() => primitive.onDeselect()).not.toThrow();
334
+ });
335
+
336
+ it('should set geometry', () => {
337
+ primitive.userData.id = 'something';
338
+
339
+ // cylinder
340
+ const cylinder = {
341
+ name: 'cylinder',
342
+ width: 1,
343
+ height: 1.5,
344
+ depth: 1,
345
+ } as COMGeometry;
346
+ expect(() => primitive.SetGeometry(cylinder)).not.toThrow();
347
+
348
+ // sphere
349
+ const sphere = {
350
+ name: 'sphere',
351
+ width: 1,
352
+ height: 1,
353
+ depth: 1,
354
+ } as COMGeometry;
355
+ expect(() => primitive.SetGeometry(sphere)).not.toThrow();
356
+
357
+ // pyramid
358
+ const pyramid = {
359
+ name: 'pyramid',
360
+ width: 1,
361
+ height: 1.5,
362
+ depth: 1,
363
+ } as COMGeometry;
364
+ expect(() => primitive.SetGeometry(pyramid)).not.toThrow();
365
+
366
+ // box
367
+ const box = {
368
+ name: 'box',
369
+ width: 1,
370
+ height: 1,
371
+ depth: 1,
372
+ } as COMGeometry;
373
+ expect(() => primitive.SetGeometry(box)).not.toThrow();
374
+
375
+ // cone
376
+ const cone = {
377
+ name: 'cone',
378
+ width: 1,
379
+ height: 1.5,
380
+ depth: 1,
381
+ } as COMGeometry;
382
+ expect(() => primitive.SetGeometry(cone)).not.toThrow();
383
+
384
+ // wall
385
+ const wall = {
386
+ name: 'wall',
387
+ width: 1,
388
+ height: 1.5,
389
+ depth: 0.1,
390
+ } as COMGeometry;
391
+ expect(() => primitive.SetGeometry(wall)).not.toThrow();
392
+
393
+ // plane
394
+ const plane = {
395
+ name: 'plane',
396
+ width: 1,
397
+ height: 0.1,
398
+ depth: 1,
399
+ } as COMGeometry;
400
+ expect(() => primitive.SetGeometry(plane)).not.toThrow();
401
+ });
402
+
403
+ it('should set material', () => {
404
+ const material = (primitive['_mesh'].material as MeshStandardMaterial);
405
+
406
+ // apply invalid material should not crash
407
+ expect(() => primitive.SetMaterial({} as COMMaterial)).not.toThrow();
408
+ expect(material).toBeDefined();
409
+
410
+ expect(() => primitive.SetMaterial({
411
+ color: 0xffffff,
412
+ roughness: 0,
413
+ roughnessMap: null,
414
+ metalness: 1,
415
+ metalnessMap: null,
416
+ } as COMMaterial)).not.toThrow();
417
+ expect(material.roughness).toBe(0);
418
+ expect(material.roughnessMap).toBeUndefined();
419
+ expect(material.metalness).toBe(1);
420
+ expect(material.metalnessMap).toBeUndefined();
421
+
422
+ expect(() => primitive.SetMaterial({
423
+ color: 0xff00ff,
424
+ roughness: 0,
425
+ roughnessMap: 'this is a Texture' as unknown as Texture,
426
+ metalness: 1,
427
+ metalnessMap: 'this is a Texture' as unknown as Texture,
428
+ } as COMMaterial)).not.toThrow();
429
+ expect(material.roughness).toBe(1);
430
+ expect(material.roughnessMap).toBeDefined();
431
+ expect(material.metalness).toBe(0);
432
+ expect(material.metalnessMap).toBeDefined();
433
+ });
434
+ });
@@ -7,6 +7,7 @@ export type DIVERendererSettings = {
7
7
  shadowMapEnabled: boolean;
8
8
  shadowMapType: ShadowMapType;
9
9
  toneMapping: ToneMapping;
10
+ canvas?: HTMLCanvasElement;
10
11
  }
11
12
 
12
13
  export const DIVERendererDefaultSettings: DIVERendererSettings = {
@@ -16,6 +17,7 @@ export const DIVERendererDefaultSettings: DIVERendererSettings = {
16
17
  shadowMapEnabled: true,
17
18
  shadowMapType: PCFSoftShadowMap,
18
19
  toneMapping: NoToneMapping,
20
+ canvas: undefined,
19
21
  }
20
22
 
21
23
  /**
@@ -36,18 +38,19 @@ export class DIVERenderer extends WebGLRenderer {
36
38
  private preRenderCallbacks: Map<string, () => void> = new Map<string, () => void>();
37
39
  private postRenderCallbacks: Map<string, () => void> = new Map<string, () => void>();
38
40
 
39
- constructor(rendererSettings: DIVERendererSettings = DIVERendererDefaultSettings) {
41
+ constructor(rendererSettings: Partial<DIVERendererSettings> = DIVERendererDefaultSettings) {
40
42
  super({
41
- antialias: rendererSettings.antialias,
42
- alpha: rendererSettings.alpha,
43
- preserveDrawingBuffer: true
43
+ antialias: rendererSettings.antialias || DIVERendererDefaultSettings.antialias,
44
+ alpha: rendererSettings.alpha || DIVERendererDefaultSettings.alpha,
45
+ preserveDrawingBuffer: true,
46
+ canvas: rendererSettings.canvas,
44
47
  });
45
48
  this.setPixelRatio(window.devicePixelRatio);
46
49
 
47
- this.shadowMap.enabled = rendererSettings.shadowMapEnabled;
48
- this.shadowMap.type = rendererSettings.shadowMapType;
50
+ this.shadowMap.enabled = rendererSettings.shadowMapEnabled || DIVERendererDefaultSettings.shadowMapEnabled;
51
+ this.shadowMap.type = rendererSettings.shadowMapType || DIVERendererDefaultSettings.shadowMapType;
49
52
 
50
- this.toneMapping = rendererSettings.toneMapping;
53
+ this.toneMapping = rendererSettings.toneMapping || DIVERendererDefaultSettings.toneMapping;
51
54
 
52
55
  this.debug.checkShaderErrors = false;
53
56
  }
@@ -48,9 +48,20 @@ describe('dive/renderer/DIVERenderer', () => {
48
48
  });
49
49
 
50
50
  it('should instantiate', () => {
51
+ renderer = new DIVERenderer({});
51
52
  expect(renderer).toBeDefined();
52
53
  });
53
54
 
55
+ it('should instantiate', () => {
56
+ renderer = new DIVERenderer({
57
+ alpha: true,
58
+ shadowMapEnabled: true,
59
+ shadowMapType: 1,
60
+ toneMapping: 1,
61
+ antialias: true,
62
+ });
63
+ });
64
+
54
65
  it('should instantiate with settings parameter', () => {
55
66
  renderer = new DIVERenderer(DIVERendererDefaultSettings);
56
67
  expect(renderer).toBeDefined();
@@ -1,6 +1,6 @@
1
- import { Color } from 'three';
2
1
  import DIVEScene from '../Scene';
3
- import { COMEntity } from '../../com';
2
+ import { type Color } from 'three';
3
+ import { type COMEntity } from '../../com/types';
4
4
 
5
5
  const mock_UpdateSceneObject = jest.fn();
6
6
  const mock_DeleteSceneObject = jest.fn();
@@ -1,7 +1,8 @@
1
1
  import { Box3, Object3D } from "three";
2
2
  import DIVELightRoot from "./lightroot/LightRoot.ts";
3
3
  import DIVEModelRoot from "./modelroot/ModelRoot.ts";
4
- import { COMLight, COMModel, COMEntity } from "../../com/types.ts";
4
+ import { DIVEPrimitiveRoot } from "./primitiveroot/PrimitiveRoot.ts";
5
+ import { type COMLight, type COMModel, type COMEntity, type COMPrimitive } from "../../com/types.ts";
5
6
  import DIVEFloor from "../../primitive/floor/Floor.ts";
6
7
  import DIVEGrid from "../../grid/Grid.ts";
7
8
 
@@ -14,6 +15,7 @@ import DIVEGrid from "../../grid/Grid.ts";
14
15
  export default class DIVERoot extends Object3D {
15
16
  private lightRoot: DIVELightRoot;
16
17
  private modelRoot: DIVEModelRoot;
18
+ private primitiveRoot: DIVEPrimitiveRoot;
17
19
  private floor: DIVEFloor;
18
20
  private grid: DIVEGrid;
19
21
 
@@ -31,10 +33,16 @@ export default class DIVERoot extends Object3D {
31
33
 
32
34
  this.lightRoot = new DIVELightRoot();
33
35
  this.add(this.lightRoot);
36
+
34
37
  this.modelRoot = new DIVEModelRoot();
35
38
  this.add(this.modelRoot);
39
+
40
+ this.primitiveRoot = new DIVEPrimitiveRoot();
41
+ this.add(this.primitiveRoot);
42
+
36
43
  this.floor = new DIVEFloor();
37
44
  this.add(this.floor);
45
+
38
46
  this.grid = new DIVEGrid();
39
47
  this.add(this.grid);
40
48
  }
@@ -46,6 +54,11 @@ export default class DIVERoot extends Object3D {
46
54
  bb.expandByObject(object);
47
55
  }
48
56
  });
57
+ this.primitiveRoot.traverse((object: Object3D) => {
58
+ if ('isObject3D' in object) {
59
+ bb.expandByObject(object);
60
+ }
61
+ });
49
62
  return bb;
50
63
  }
51
64
 
@@ -60,6 +73,9 @@ export default class DIVERoot extends Object3D {
60
73
  case "model": {
61
74
  return this.modelRoot.GetModel(object as COMModel);
62
75
  }
76
+ case "primitive": {
77
+ return this.primitiveRoot.GetPrimitive(object as COMPrimitive);
78
+ }
63
79
  }
64
80
  }
65
81
 
@@ -76,6 +92,10 @@ export default class DIVERoot extends Object3D {
76
92
  this.modelRoot.UpdateModel(object as COMModel);
77
93
  break;
78
94
  }
95
+ case "primitive": {
96
+ this.primitiveRoot.UpdatePrimitive(object as COMPrimitive);
97
+ break;
98
+ }
79
99
  }
80
100
  }
81
101
 
@@ -92,6 +112,10 @@ export default class DIVERoot extends Object3D {
92
112
  this.modelRoot.UpdateModel(object as COMModel);
93
113
  break;
94
114
  }
115
+ case "primitive": {
116
+ this.primitiveRoot.UpdatePrimitive(object as COMPrimitive);
117
+ break;
118
+ }
95
119
  }
96
120
  }
97
121
 
@@ -108,10 +132,27 @@ export default class DIVERoot extends Object3D {
108
132
  this.modelRoot.DeleteModel(object as COMModel);
109
133
  break;
110
134
  }
135
+ case "primitive": {
136
+ this.primitiveRoot.DeletePrimitive(object as COMPrimitive);
137
+ break;
138
+ }
111
139
  }
112
140
  }
113
141
 
114
- public PlaceOnFloor(object: Partial<COMModel>): void {
115
- this.modelRoot.PlaceOnFloor(object);
142
+ public PlaceOnFloor(object: Partial<COMEntity>): void {
143
+ switch (object.entityType) {
144
+ case "pov":
145
+ case "light": {
146
+ break;
147
+ }
148
+ case "model": {
149
+ this.modelRoot.PlaceOnFloor(object as COMModel);
150
+ break;
151
+ }
152
+ case "primitive": {
153
+ this.primitiveRoot.PlaceOnFloor(object as COMPrimitive);
154
+ break;
155
+ }
156
+ }
116
157
  }
117
158
  }