@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/build/dive.cjs +33 -0
- package/build/dive.cjs.map +1 -1
- package/build/dive.d.cts +3 -2
- package/build/dive.d.ts +3 -2
- package/build/dive.js +48 -15
- package/build/dive.js.map +1 -1
- package/package.json +1 -1
- package/src/com/types.ts +3 -2
- package/src/model/Model.ts +50 -1
- package/src/model/__test__/Model.test.ts +68 -19
- package/src/primitive/__test__/Primitive.test.ts +2 -4
- package/src/scene/root/modelroot/ModelRoot.ts +1 -0
- package/src/scene/root/modelroot/__test__/ModelRoot.test.ts +3 -0
package/package.json
CHANGED
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
|
|
42
|
+
roughnessMap?: Texture;
|
|
42
43
|
metalness: number;
|
|
43
|
-
metalnessMap
|
|
44
|
+
metalnessMap?: Texture;
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
export type COMPrimitive = COMBaseEntity & {
|
package/src/model/Model.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { Box3,
|
|
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
|
-
|
|
315
|
-
testLight.userData.id = 'something';
|
|
325
|
+
model.userData.id = 'something';
|
|
316
326
|
|
|
317
|
-
expect(() =>
|
|
327
|
+
expect(() => model.onSelect()).not.toThrow();
|
|
318
328
|
|
|
319
329
|
jest.spyOn(DIVECommunication, 'get').mockReturnValueOnce(undefined);
|
|
320
|
-
expect(() =>
|
|
330
|
+
expect(() => model.onSelect()).not.toThrow();
|
|
321
331
|
});
|
|
322
332
|
|
|
323
333
|
it('should onDeselect', () => {
|
|
324
|
-
|
|
325
|
-
testLight.userData.id = 'something';
|
|
334
|
+
model.userData.id = 'something';
|
|
326
335
|
|
|
327
|
-
expect(() =>
|
|
336
|
+
expect(() => model.onDeselect()).not.toThrow();
|
|
328
337
|
|
|
329
338
|
jest.spyOn(DIVECommunication, 'get').mockReturnValueOnce(undefined);
|
|
330
|
-
expect(() =>
|
|
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 =
|
|
165
|
+
this.roughnessMap = undefined;
|
|
166
166
|
this.metalness = 0;
|
|
167
|
-
this.metalnessMap =
|
|
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);
|