UNPKG

@polygonjs/polygonjs

Version:

node-based WebGL 3D engine https://polygonjs.com

293 lines (278 loc) 9.71 kB
import { Bone, SkinnedMesh, Material, AnimationClip, BufferGeometry, Color, Mesh, Box3, Sphere, Vector3, Object3D, Matrix4, } from 'three'; import {cloneBufferGeometry} from '../../Geometry'; import {Object3DWithGeometry} from '../../Group'; import {AttribClass, dataFromObject, ObjectData, ObjectType} from '../../Constant'; import {objectData} from '../../entities/object/BaseCoreObjectUtils'; import {MaterialWithCustomMaterials, applyCustomMaterials} from '../../Material'; import {objectCloneDeep} from '../../../ObjectUtils'; import {ThreeMeshBVHHelper} from '../../bvh/ThreeMeshBVHHelper'; import {CoreGeometryBuilderMerge} from './builders/Merge'; import {CoreObjectType, MergeCompactOptions, ObjectContent, objectContentCopyProperties} from '../../ObjectContent'; import {BaseCoreObject} from '../../entities/object/BaseCoreObject'; import {TransformTargetType} from '../../../Transform'; import {TypeAssert} from '../../../../engine/poly/Assert'; import {applyTransformWithSpaceToObject, ObjectTransformMode, ObjectTransformSpace} from '../../../TransformSpace'; import {BaseSopOperation} from '../../../../engine/operations/sop/_Base'; import {primitiveClassFactoryNonAbstract} from './ThreeModule'; import {ThreejsVertex} from './ThreejsVertex'; import {ThreejsPoint} from './ThreejsPoint'; import {TraversedRelatedEntityData} from '../../entities/utils/TraversedRelatedEntities'; import {CoreEntityWithObject} from '../../CoreEntity'; import {arrayCopy} from '../../../ArrayUtils'; interface Object3DWithAnimations extends Object3D { animations: AnimationClip[]; } interface MaterialWithColor extends Material { color: Color; } const COMPUTE_PRECISE_BOUNDS = true; const SPHERE_EMPTY = new Sphere(new Vector3(0, 0, 0), 0); export class ThreejsCoreObject extends BaseCoreObject<CoreObjectType.THREEJS> { protected override _object: Object3D; constructor(_object: Object3D, index: number) { super(_object, index); this._object = _object; } override humanType(): string { return dataFromObject(this._object).humanName; } override object() { return this._object; } override geometry(): BufferGeometry | null { return (this._object as Mesh).geometry as BufferGeometry | null; } // object():Object3D{ // return this._object // } // coreGeometry(): CoreGeometry | null { // const geo = this.geometry(); // if (geo) { // return new CoreGeometry(geo); // } else { // return null; // } // // const geo = this.geometry() // // if (geo) { // // return new CoreGeometry(geo) // // } else { // // return null // // } // } static override objectData(object: Object3D): ObjectData { const data = objectData(object); data.verticesCount = ThreejsVertex.entitiesCount(object); data.pointsCount = ThreejsPoint.entitiesCount(object); const primitiveClass = primitiveClassFactoryNonAbstract(object); data.primitivesCount = primitiveClass?.entitiesCount(object) || 0; data.primitiveName = primitiveClass?.primitiveName() || ''; return data; } static override position(object: Object3D, target: Vector3) { target.copy(object.position); } static override boundingBox(object: Object3D, target: Box3) { target.setFromObject(object, COMPUTE_PRECISE_BOUNDS); } static override geometryBoundingBox(object: Object3D, target: Box3) { const geometry = (object as Mesh).geometry; if (geometry) { if (!geometry.boundingBox) { geometry.computeBoundingBox(); } if (geometry.boundingBox) { target.copy(geometry.boundingBox); } } else { target.makeEmpty(); } } static override boundingSphere(object: Object3D, target: Sphere) { const geometry = (object as Mesh).geometry; if (!geometry) { target.copy(SPHERE_EMPTY); return; } geometry.computeBoundingSphere(); const computedSphere = geometry.boundingSphere; if (!computedSphere) { target.copy(SPHERE_EMPTY); return; } target.copy(computedSphere); } static override geometryBoundingSphere(object: Object3D, target: Sphere): void { this.boundingSphere(object, target); } // computeVertexNormals() { // this.coreGeometry()?.computeVertexNormals(); // } static override clone(srcObject: Object3D) { const clonedObject = srcObject.clone(); var sourceLookup = new Map<Object3D, Object3D>(); var cloneLookup = new Map<Object3D, Object3D>(); ThreejsCoreObject.parallelTraverse( srcObject, clonedObject, function (sourceNode: Object3D, clonedNode: Object3D) { sourceLookup.set(clonedNode, sourceNode); cloneLookup.set(sourceNode, clonedNode); } ); clonedObject.traverse(function (node) { const srcNode = sourceLookup.get(node) as SkinnedMesh | undefined; const meshNode = node as Mesh; if (meshNode.geometry && srcNode && srcNode.geometry) { const srcNodeGeometry = srcNode.geometry as BufferGeometry; meshNode.geometry = cloneBufferGeometry(srcNodeGeometry); ThreeMeshBVHHelper.copyBVH(meshNode, srcNode); // const mesh_node_geometry = meshNode.geometry as BufferGeometry; // if (mesh_node_geometry.userData) { // mesh_node_geometry.userData = objectCloneDeep(srcNodeGeometry.userData); // } } if (meshNode.material) { // no need to assign the material here // as this should already be done in the .clone() method. // Otherwise, when this is assigned here, some objects that rely on their own mat // such as sop/Reflector stop working when cloned // mesh_node.material = src_node.material; applyCustomMaterials(node, meshNode.material as MaterialWithCustomMaterials); // prevents crashes for linesegments with shader material such as the line dashed instance // TODO: test const material_with_color = meshNode.material as MaterialWithColor; if (material_with_color.color == null) { material_with_color.color = new Color(1, 1, 1); } } if (srcNode) { if (srcNode.userData) { node.userData = objectCloneDeep(srcNode.userData); } const src_node_with_animations = (<unknown>srcNode) as Object3DWithAnimations; if (src_node_with_animations.animations) { (node as Object3DWithAnimations).animations = src_node_with_animations.animations.map((animation) => animation.clone() ); } const skinned_node = node as SkinnedMesh; if (skinned_node.isSkinnedMesh) { var clonedMesh = skinned_node; var sourceMesh = srcNode; var sourceBones = sourceMesh.skeleton.bones; clonedMesh.skeleton = sourceMesh.skeleton.clone(); clonedMesh.bindMatrix.copy(sourceMesh.bindMatrix); const new_bones = sourceBones.map(function (bone) { return cloneLookup.get(bone); }) as Bone[]; clonedMesh.skeleton.bones = new_bones; clonedMesh.bind(clonedMesh.skeleton, clonedMesh.bindMatrix); } } }); return clonedObject; } static parallelTraverse(a: Object3D, b: Object3D, callback: (a: Object3D, b: Object3D) => void) { callback(a, b); for (var i = 0; i < a.children.length; i++) { const childA = a.children[i]; const childB = b.children[i]; if (childA && childB) { this.parallelTraverse(childA, childB, callback); } } } static override applyMatrix( object: Object3D, matrix: Matrix4, transformTargetType: TransformTargetType, transformSpace: ObjectTransformSpace, transformMode: ObjectTransformMode ) { switch (transformTargetType) { case TransformTargetType.OBJECT: { applyTransformWithSpaceToObject(object, matrix, transformSpace, transformMode); // this._applyMatrixToObject(object, matrix); return; } case TransformTargetType.GEOMETRY: { const geometry = (object as Object3DWithGeometry).geometry; if (geometry) { geometry.applyMatrix4(matrix); } return; } } TypeAssert.unreachable(transformTargetType); } static override mergeCompact(options: MergeCompactOptions) { const {objects, material, objectType, mergedObjects, onError} = options; const firstObject = objects[0]; if (!firstObject) { return; } const geometries: BufferGeometry[] = []; for (const object of objects) { const geometry = (object as Mesh).geometry; if (geometry) { geometry.applyMatrix4((object as Mesh).matrix); geometries.push(geometry); } } try { const mergedGeometry = CoreGeometryBuilderMerge.merge(geometries); if (mergedGeometry) { const newObject = BaseSopOperation.createObject(mergedGeometry, objectType as ObjectType, material); if (newObject) { objectContentCopyProperties(firstObject, newObject); mergedObjects.push(newObject as Object3DWithGeometry); } } else { onError('merge failed, check that input geometries have the same attributes'); } } catch (e) { onError((e as Error).message || 'unknown error'); } } // // // RELATED ENTITIES // // static override relatedPrimitiveIds( object: ObjectContent<CoreObjectType>, index: number, target: number[], traversedRelatedEntityData?: TraversedRelatedEntityData ): void { const _primitiveClassFactory = primitiveClassFactoryNonAbstract(object as any as Object3D); if (!_primitiveClassFactory) { target.length = 0; return; } const count = _primitiveClassFactory?.entitiesCount(object as any as Object3D); target.length = count; for (let i = 0; i < count; i++) { target[i] = i; } if (traversedRelatedEntityData && traversedRelatedEntityData[AttribClass.PRIMITIVE].ids != target) { arrayCopy(target, traversedRelatedEntityData[AttribClass.PRIMITIVE].ids); } } static override relatedPrimitiveClass<T extends CoreObjectType>(object: ObjectContent<T>) { return primitiveClassFactoryNonAbstract(object as any as Mesh) as any as typeof CoreEntityWithObject<T>; } }