@shopware-ag/dive 1.9.0 → 1.10.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shopware-ag/dive",
3
- "version": "1.9.0",
3
+ "version": "1.10.0",
4
4
  "description": "Shopware Spatial Framework",
5
5
  "type": "module",
6
6
  "main": "./build/dive.cjs",
package/src/com/types.ts CHANGED
@@ -26,6 +26,7 @@ export type COMModel = COMBaseEntity & {
26
26
  rotation: Vector3Like;
27
27
  scale: Vector3Like;
28
28
  loaded: boolean;
29
+ material?: COMMaterial;
29
30
  };
30
31
 
31
32
  export type COMGeometry = {
@@ -38,9 +39,9 @@ export type COMGeometry = {
38
39
  export type COMMaterial = {
39
40
  color: string | number;
40
41
  roughness: number;
41
- roughnessMap: Texture | null;
42
+ roughnessMap?: Texture;
42
43
  metalness: number;
43
- metalnessMap: Texture | null;
44
+ metalnessMap?: Texture;
44
45
  }
45
46
 
46
47
  export type COMPrimitive = COMBaseEntity & {
@@ -1,10 +1,11 @@
1
- import { Box3, type Mesh, Object3D, Raycaster, Vector3, Vector3Like } from 'three';
1
+ import { Box3, Color, Mesh, MeshStandardMaterial, Object3D, Raycaster, Vector3, Vector3Like } from 'three';
2
2
  import { DIVESelectable } from '../interface/Selectable';
3
3
  import { PRODUCT_LAYER_MASK } from '../constant/VisibilityLayerMask';
4
4
  import { DIVEMoveable } from '../interface/Moveable';
5
5
  import DIVECommunication from '../com/Communication';
6
6
  import type { GLTF, TransformControls } from 'three/examples/jsm/Addons.js';
7
7
  import { findSceneRecursive } from '../helper/findSceneRecursive/findSceneRecursive';
8
+ import { type COMMaterial } from '../com/types';
8
9
 
9
10
  /**
10
11
  * A basic model class.
@@ -23,6 +24,9 @@ export default class DIVEModel extends Object3D implements DIVESelectable, DIVEM
23
24
 
24
25
  private boundingBox: Box3;
25
26
 
27
+ private _mesh: Mesh | null = null;
28
+ private _material: MeshStandardMaterial | null = null;
29
+
26
30
  constructor() {
27
31
  super();
28
32
 
@@ -40,6 +44,18 @@ export default class DIVEModel extends Object3D implements DIVESelectable, DIVEM
40
44
 
41
45
  child.layers.mask = this.layers.mask;
42
46
  this.boundingBox.expandByObject(child);
47
+
48
+ // only search for first mesh for now
49
+ if (!this._mesh && 'isMesh' in child) {
50
+ this._mesh = child as Mesh;
51
+
52
+ // if the material is already set, use it, otherwise set it from the model's material
53
+ if (this._material) {
54
+ this._mesh.material = this._material;
55
+ } else {
56
+ this._material = (child as Mesh).material as MeshStandardMaterial;
57
+ }
58
+ }
43
59
  });
44
60
 
45
61
  this.add(gltf.scene);
@@ -63,6 +79,39 @@ export default class DIVEModel extends Object3D implements DIVESelectable, DIVEM
63
79
  });
64
80
  }
65
81
 
82
+ public SetMaterial(material: COMMaterial): void {
83
+ console.error('HERE', this._mesh);
84
+
85
+ // if there is no material, create a new one
86
+ if (!this._material) {
87
+ this._material = new MeshStandardMaterial();
88
+ }
89
+
90
+ this._material.color = new Color(material.color);
91
+
92
+ // if there is a roughness map, use it, otherwise use the roughness value
93
+ if (material.roughnessMap) {
94
+ this._material.roughnessMap = material.roughnessMap;
95
+ this._material.roughness = 1.0;
96
+ } else {
97
+ this._material.roughness = material.roughness;
98
+ }
99
+
100
+ // if there is a metalness map, use it, otherwise use the metalness value
101
+ if (material.metalnessMap) {
102
+ this._material.metalnessMap = material.metalnessMap;
103
+ this._material.metalness = 0.0;
104
+ } else {
105
+ this._material.metalness = material.metalness;
106
+ }
107
+
108
+ // if the mesh is already set, update the material
109
+
110
+ if (this._mesh) {
111
+ this._mesh.material = this._material;
112
+ }
113
+ }
114
+
66
115
  public SetToWorldOrigin(): void {
67
116
  this.position.set(0, 0, 0);
68
117
  DIVECommunication.get(this.userData.id)?.PerformAction('UPDATE_OBJECT', { id: this.userData.id, position: this.position, rotation: this.rotation, scale: this.scale });
@@ -2,7 +2,8 @@ import Model from '../Model';
2
2
  import DIVECommunication from '../../com/Communication';
3
3
  import { GLTF } from 'three/examples/jsm/Addons';
4
4
  import DIVEScene from '../../scene/Scene';
5
- import { Vector3, Box3, Mesh } from 'three';
5
+ import { Vector3, Box3, Mesh, MeshStandardMaterial, type Texture, Color } from 'three';
6
+ import { type COMMaterial } from '../../com/types';
6
7
 
7
8
  const intersectObjectsMock = jest.fn();
8
9
 
@@ -135,6 +136,17 @@ jest.mock('three', () => {
135
136
  };
136
137
  return this;
137
138
  }),
139
+ MeshStandardMaterial: jest.fn(function () {
140
+ this.color = new Color();
141
+ this.roughness = 1;
142
+ this.roughnessMap = undefined;
143
+ this.metalness = 0;
144
+ this.metalnessMap = undefined;
145
+ return this;
146
+ }),
147
+ Color: jest.fn(function () {
148
+ return this;
149
+ }),
138
150
  }
139
151
  });
140
152
 
@@ -150,12 +162,14 @@ jest.mock('../../com/Communication.ts', () => {
150
162
 
151
163
  const gltf = {
152
164
  scene: {
165
+ isMesh: true,
153
166
  isObject3D: true,
154
167
  parent: null,
155
168
  dispatchEvent: jest.fn(),
156
169
  layers: {
157
170
  mask: 0,
158
171
  },
172
+ material: {},
159
173
  updateWorldMatrix: jest.fn(),
160
174
  children: [
161
175
  {
@@ -166,6 +180,7 @@ const gltf = {
166
180
  },
167
181
  children: [],
168
182
  updateWorldMatrix: jest.fn(),
183
+ isMesh: true,
169
184
  },
170
185
  ],
171
186
  traverse: function (callback: (object: object) => void) {
@@ -177,43 +192,42 @@ const gltf = {
177
192
 
178
193
  jest.spyOn(DIVECommunication, 'get').mockReturnValue({ PerformAction: jest.fn() } as unknown as DIVECommunication);
179
194
 
195
+ let model: Model;
196
+
180
197
  describe('dive/model/DIVEModel', () => {
198
+ beforeEach(() => {
199
+ model = new Model();
200
+ });
201
+
181
202
  afterEach(() => {
182
203
  jest.clearAllMocks();
183
204
  });
184
205
 
185
206
  it('should instantiate', () => {
186
- const model = new Model();
187
207
  expect(model).toBeDefined();
188
208
  });
189
209
 
190
210
  it('should set model', () => {
191
- const model = new Model();
192
211
  expect(() => model.SetModel(gltf)).not.toThrow();
193
212
  });
194
213
 
195
214
  it('should set position', () => {
196
- const model = new Model();
197
215
  expect(() => model.SetPosition({ x: 0, y: 0, z: 0 })).not.toThrow();
198
216
  });
199
217
 
200
218
  it('should set rotation', () => {
201
- const model = new Model();
202
219
  expect(() => model.SetRotation({ x: 0, y: 0, z: 0 })).not.toThrow();
203
220
  });
204
221
 
205
222
  it('should set scale', () => {
206
- const model = new Model();
207
223
  expect(() => model.SetScale({ x: 1, y: 1, z: 1 })).not.toThrow();
208
224
  });
209
225
 
210
226
  it('should set visibility', () => {
211
- const model = new Model();
212
227
  expect(() => model.SetVisibility(true)).not.toThrow();
213
228
  });
214
229
 
215
230
  it('should set to world origin', () => {
216
- const model = new Model();
217
231
  model.userData.id = 'something';
218
232
 
219
233
  expect(() => model.SetToWorldOrigin()).not.toThrow();
@@ -226,7 +240,6 @@ describe('dive/model/DIVEModel', () => {
226
240
  });
227
241
 
228
242
  it('should place on floor', () => {
229
- const model = new Model();
230
243
  model.userData.id = 'something';
231
244
 
232
245
  expect(() => model.PlaceOnFloor()).not.toThrow();
@@ -247,7 +260,6 @@ describe('dive/model/DIVEModel', () => {
247
260
  z: 1,
248
261
  };
249
262
 
250
- const model = new Model();
251
263
  model.userData.id = 'something';
252
264
  model.position.set(0, 4, 0);
253
265
  model['boundingBox'] = {
@@ -301,7 +313,6 @@ describe('dive/model/DIVEModel', () => {
301
313
  });
302
314
 
303
315
  it('should onMove', () => {
304
- const model = new Model();
305
316
  model.userData.id = 'something';
306
317
 
307
318
  expect(() => model.onMove()).not.toThrow();
@@ -311,22 +322,60 @@ describe('dive/model/DIVEModel', () => {
311
322
  });
312
323
 
313
324
  it('should onSelect', () => {
314
- const testLight = new Model();
315
- testLight.userData.id = 'something';
325
+ model.userData.id = 'something';
316
326
 
317
- expect(() => testLight.onSelect()).not.toThrow();
327
+ expect(() => model.onSelect()).not.toThrow();
318
328
 
319
329
  jest.spyOn(DIVECommunication, 'get').mockReturnValueOnce(undefined);
320
- expect(() => testLight.onSelect()).not.toThrow();
330
+ expect(() => model.onSelect()).not.toThrow();
321
331
  });
322
332
 
323
333
  it('should onDeselect', () => {
324
- const testLight = new Model();
325
- testLight.userData.id = 'something';
334
+ model.userData.id = 'something';
326
335
 
327
- expect(() => testLight.onDeselect()).not.toThrow();
336
+ expect(() => model.onDeselect()).not.toThrow();
328
337
 
329
338
  jest.spyOn(DIVECommunication, 'get').mockReturnValueOnce(undefined);
330
- expect(() => testLight.onDeselect()).not.toThrow();
339
+ expect(() => model.onDeselect()).not.toThrow();
340
+ });
341
+
342
+ it('should set material', () => {
343
+ // apply invalid material should not crash
344
+ expect(() => model.SetMaterial({} as COMMaterial)).not.toThrow();
345
+ expect(model['_material']).not.toBeNull();
346
+
347
+ expect(() => model.SetMaterial({
348
+ color: 0xffffff,
349
+ roughness: 0,
350
+ metalness: 1,
351
+ } as COMMaterial)).not.toThrow();
352
+ expect((model['_material'] as MeshStandardMaterial).roughness).toBe(0);
353
+ expect((model['_material'] as MeshStandardMaterial).roughnessMap).toBeUndefined();
354
+ expect((model['_material'] as MeshStandardMaterial).metalness).toBe(1);
355
+ expect((model['_material'] as MeshStandardMaterial).metalnessMap).toBeUndefined();
356
+
357
+ expect(() => model.SetMaterial({
358
+ color: 0xff00ff,
359
+ roughness: 0,
360
+ roughnessMap: 'this is a Texture' as unknown as Texture,
361
+ metalness: 1,
362
+ metalnessMap: 'this is a Texture' as unknown as Texture,
363
+ } as COMMaterial)).not.toThrow();
364
+ expect((model['_material'] as MeshStandardMaterial).roughness).toBe(1);
365
+ expect((model['_material'] as MeshStandardMaterial).roughnessMap).toBeDefined();
366
+ expect((model['_material'] as MeshStandardMaterial).metalness).toBe(0);
367
+ expect((model['_material'] as MeshStandardMaterial).metalnessMap).toBeDefined();
368
+ });
369
+
370
+ it('should set model material when material already set before', () => {
371
+ model.SetMaterial({ roughness: 0.5 } as COMMaterial);
372
+ expect(() => model.SetModel(gltf)).not.toThrow();
373
+ expect((model['_mesh']?.material as MeshStandardMaterial).roughness).toBe(0.5);
374
+ });
375
+
376
+ it('should set material to model when model already set before', () => {
377
+ model.SetModel(gltf);
378
+ expect(() => model.SetMaterial({ roughness: 0.5 } as COMMaterial)).not.toThrow();
379
+ expect((model['_mesh']?.material as MeshStandardMaterial).roughness).toBe(0.5);
331
380
  });
332
381
  });
@@ -162,9 +162,9 @@ jest.mock('three', () => {
162
162
  MeshStandardMaterial: jest.fn(function () {
163
163
  this.color = {};
164
164
  this.roughness = 0;
165
- this.roughnessMap = null;
165
+ this.roughnessMap = undefined;
166
166
  this.metalness = 0;
167
- this.metalnessMap = null;
167
+ this.metalnessMap = undefined;
168
168
  return this;
169
169
  }),
170
170
  Color: jest.fn(function () {
@@ -410,9 +410,7 @@ describe('dive/primitive/DIVEPrimitive', () => {
410
410
  expect(() => primitive.SetMaterial({
411
411
  color: 0xffffff,
412
412
  roughness: 0,
413
- roughnessMap: null,
414
413
  metalness: 1,
415
- metalnessMap: null,
416
414
  } as COMMaterial)).not.toThrow();
417
415
  expect(material.roughness).toBe(0);
418
416
  expect(material.roughnessMap).toBeUndefined();
@@ -52,6 +52,7 @@ export default class DIVEModelRoot extends Object3D {
52
52
  if (object.rotation !== undefined) (sceneObject as Model).SetRotation(object.rotation);
53
53
  if (object.scale !== undefined) (sceneObject as Model).SetScale(object.scale);
54
54
  if (object.visible !== undefined) (sceneObject as Model).SetVisibility(object.visible);
55
+ if (object.material !== undefined) (sceneObject as Model).SetMaterial(object.material);
55
56
  }
56
57
 
57
58
  public DeleteModel(object: Partial<COMModel>): void {
@@ -2,6 +2,7 @@ import DIVEModelRoot from '../ModelRoot';
2
2
  import DIVECommunication from '../../../../com/Communication';
3
3
  import { DIVEMoveable } from '../../../../interface/Moveable';
4
4
  import type DIVEScene from '../../../Scene';
5
+ import { type COMMaterial } from '../../../../com/types';
5
6
 
6
7
  const mock_LoadGLTF = jest.fn().mockResolvedValue({});
7
8
  const mock_SetPosition = jest.fn();
@@ -46,6 +47,7 @@ jest.mock('../../../../model/Model.ts', () => {
46
47
  this.SetRotation = mock_SetRotation;
47
48
  this.SetScale = mock_SetScale;
48
49
  this.SetVisibility = jest.fn();
50
+ this.SetMaterial = jest.fn();
49
51
  this.PlaceOnFloor = mock_PlaceOnFloor;
50
52
  this.removeFromParent = jest.fn();
51
53
  return this;
@@ -95,6 +97,7 @@ describe('dive/scene/root/modelroot/DIVEModelRoot', () => {
95
97
  position: { x: 1, y: 2, z: 3 },
96
98
  rotation: { x: 1, y: 2, z: 3 },
97
99
  scale: { x: 1, y: 2, z: 3 },
100
+ material: {} as COMMaterial,
98
101
  })).not.toThrow();
99
102
 
100
103
  jest.spyOn(DIVECommunication, 'get').mockReturnValueOnce(undefined);