UNPKG

@shopware-ag/dive

Version:

Shopware Spatial Framework

186 lines (152 loc) 6.24 kB
import { Mesh, MeshStandardMaterial, Raycaster, Vector3 } from 'three'; import { PRODUCT_LAYER_MASK } from '../constant/VisibilityLayerMask'; import type { GLTF } from 'three/examples/jsm/loaders/GLTFLoader'; import { findSceneRecursive } from '../helper/findSceneRecursive/findSceneRecursive'; import { type COMMaterial } from '../com/types'; import { DIVENode } from '../node/Node'; import { DIVECommunication } from '../com/Communication'; /** * A basic model class. * * It does calculate it's own bounding box which is used for positioning on the floor. * * Can be moved and selected. * * @module */ export class DIVEModel extends DIVENode { readonly isDIVEModel: true = true; private _mesh: Mesh | null = null; private _material: MeshStandardMaterial | null = null; public SetModel(gltf: GLTF): void { this.clear(); this._boundingBox.makeEmpty(); gltf.scene.traverse((child) => { child.castShadow = true; child.receiveShadow = true; child.layers.mask = this.layers.mask; this._boundingBox.expandByObject(child); // only search for first mesh for now if (!this._mesh && 'isMesh' in child) { this._mesh = child as Mesh; // if the material is already set, use it, otherwise set it from the model's material if (this._material) { this._mesh.material = this._material; } else { this._material = (child as Mesh) .material as MeshStandardMaterial; } } }); this.add(gltf.scene); } public SetMaterial(material: Partial<COMMaterial>): void { // if there is no material, create a new one if (!this._material) { this._material = new MeshStandardMaterial(); } if (material.vertexColors !== undefined) { this._material.vertexColors = material.vertexColors; } // apply color if supplied if (material.color !== undefined) { this._material.color.set(material.color); } // apply albedo map if supplied if (material.map !== undefined) { this._material.map = material.map; } // apply normal map if (material.normalMap !== undefined) { this._material.normalMap = material.normalMap; } // set roughness value // if supplied, apply roughness map // if we applied a roughness map, set roughness to 1.0 if (material.roughness !== undefined) { this._material.roughness = material.roughness; } if (material.roughnessMap !== undefined) { this._material.roughnessMap = material.roughnessMap; if (this._material.roughnessMap) { this._material.roughness = 1.0; } } // set metalness value // if supplied, apply metalness map // if we applied a metalness map, set metalness to 1.0 if (material.metalness !== undefined) { this._material.metalness = material.metalness; } if (material.metalnessMap !== undefined) { this._material.metalnessMap = material.metalnessMap; if (this._material.metalnessMap) { this._material.metalness = 1.0; } } // if the mesh is already set, update the material if (this._mesh) { this._mesh.material = this._material; } } public PlaceOnFloor(): void { // calculate and temporary save world position const worldPos = this.getWorldPosition(this._positionWorldBuffer); const oldWorldPos = worldPos.clone(); // compute the bounding box this._mesh?.geometry?.computeBoundingBox(); const meshBB = this._mesh?.geometry?.boundingBox; // subtract the bounding box min y axis value from the world position y value if (!meshBB || !this._mesh) return; worldPos.y = worldPos.y - this._mesh.localToWorld(meshBB.min.clone()).y; // skip any action when the position did not change if (worldPos.y === oldWorldPos.y) return; DIVECommunication.get(this.userData.id)?.PerformAction( 'UPDATE_OBJECT', { id: this.userData.id, position: worldPos, rotation: this.rotation, scale: this.scale, }, ); } public DropIt(): void { if (!this.parent) { console.warn( 'DIVEModel: DropIt() called on a model that is not in the scene.', this, ); return; } // calculate the bottom center of the bounding box const bottomY = this._boundingBox.min.y * this.scale.y; const bbBottomCenter = this.localToWorld( this._boundingBox.getCenter(new Vector3()).multiply(this.scale), ); bbBottomCenter.y = bottomY + this.position.y; // set up raycaster and raycast all scene objects (product layer) const raycaster = new Raycaster(bbBottomCenter, new Vector3(0, -1, 0)); raycaster.layers.mask = PRODUCT_LAYER_MASK; const intersections = raycaster.intersectObjects( findSceneRecursive(this).Root.children, true, ); // if we hit something, move the model to the top on the hit object's bounding box if (intersections.length > 0) { const mesh = intersections[0].object as Mesh; mesh.geometry.computeBoundingBox(); const meshBB = mesh.geometry.boundingBox!; const worldPos = mesh.localToWorld(meshBB.max.clone()); const oldPos = this.position.clone(); const newPos = this.position .clone() .setY(worldPos.y) .sub(new Vector3(0, bottomY, 0)); this.position.copy(newPos); // if the position changed, update the object in communication if (this.position.y === oldPos.y) return; this.onMove(); } } }