@polygonjs/polygonjs
Version:
node-based WebGL 3D engine https://polygonjs.com
293 lines (278 loc) • 9.71 kB
text/typescript
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>;
}
}