TkAstral3D/packages/sdk/lib/core/expansion/Object3D.ts
2025-10-04 23:36:07 +08:00

273 lines
9.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import * as THREE from "three";
/**
* 在对象以及后代中执行的回调函数,仅对满足条件的对象执行
* @param callback - 以一个object3D对象作为第一个参数的函数。
* @param condition - 需要满足该条件才继续后续回调的条件函数
*/
THREE.Object3D.prototype.traverseByCondition = function(callback, condition){
if (!condition(this)) return;
callback(this);
const children = this.children;
for (let i = 0, l = children.length; i < l; i++) {
children[i].traverseByCondition(callback, condition);
}
}
/**
* 判断 parentObj 是否是 当前对象 的任意层级祖先(包括祖父、曾祖父等)
* @param parentObj - 可能是祖先的对象
*/
THREE.Object3D.prototype.isAncestor = function(parentObj) {
let current:THREE.Object3D | null = this;
while (current) {
if (current === parentObj) return true;
current = current.parent;
}
return false;
}
/**
* 重写toJSON方法
*/
THREE.Object3D.prototype.toJSON = function(meta:any) {
// 当从JSON.stringify调用时meta是一个字符串
const isRootObject = (meta === undefined || typeof meta === 'string');
// @ts-ignore
const output: any = {};
// meta是一个散列用于收集几何图形材料。不提供它意味着这是被序列化的根对象。
if (isRootObject) {
meta = {
geometries: {},
materials: {},
textures: {},
images: {},
shapes: {},
skeletons: {},
animations: {},
nodes: {}
};
output.metadata = {
version: 4.6,
type: 'Object',
generator: 'Astral.Object3D.toJSON'
};
}
// 标准Object3D序列化
const object:any = {
uuid: this.uuid,
type: this.type
};
if (this.name !== '') object.name = this.name;
if (this.castShadow === true) object.castShadow = true;
if (this.receiveShadow === true) object.receiveShadow = true;
if (this.visible === false) object.visible = false;
if (this.frustumCulled === false) object.frustumCulled = false;
if (this.renderOrder !== 0) object.renderOrder = this.renderOrder;
if (Object.keys(this.userData).length > 0) object.userData = this.userData;
object.layers = this.layers.mask;
object.matrix = this.matrix.toArray();
object.up = this.up.toArray();
if (this.matrixAutoUpdate === false) object.matrixAutoUpdate = false;
// 对象特定属性
if (this.isInstancedMesh) {
object.type = 'InstancedMesh';
object.count = this.count;
object.instanceMatrix = this.instanceMatrix?.toJSON();
if (this.instanceColor !== null) object.instanceColor = this.instanceColor?.toJSON();
}
if (this.isBatchedMesh) {
object.type = 'BatchedMesh';
object.perObjectFrustumCulled = this.perObjectFrustumCulled;
object.sortObjects = this.sortObjects;
object.drawRanges = this._drawRanges;
object.reservedRanges = this._reservedRanges;
object.visibility = this._visibility;
object.active = this._active;
object.bounds = this._bounds.map(bound => ({
boxInitialized: bound.boxInitialized,
boxMin: bound.box.min.toArray(),
boxMax: bound.box.max.toArray(),
sphereInitialized: bound.sphereInitialized,
sphereRadius: bound.sphere.radius,
sphereCenter: bound.sphere.center.toArray()
}));
object.maxInstanceCount = this._maxInstanceCount;
object.maxVertexCount = this._maxVertexCount;
object.maxIndexCount = this._maxIndexCount;
object.geometryInitialized = this._geometryInitialized;
object.geometryCount = this._geometryCount;
object.matricesTexture = this._matricesTexture?.toJSON(meta);
if (this._colorsTexture !== null) object.colorsTexture = this._colorsTexture?.toJSON(meta);
if (this.boundingSphere !== null) {
object.boundingSphere = {
center: object.boundingSphere.center.toArray(),
radius: object.boundingSphere.radius
};
}
if (this.boundingBox !== null) {
object.boundingBox = {
min: object.boundingBox.min.toArray(),
max: object.boundingBox.max.toArray()
};
}
}
function serialize(library, element) {
if (library[element.uuid] === undefined) {
library[element.uuid] = element.toJSON(meta);
}
return element.uuid;
}
if (this.isScene) {
if (this.background) {
if (this.background.isColor) {
object.background = this.background.toJSON();
} else if (this.background.isTexture) {
object.background = this.background.toJSON(meta).uuid;
}
}
if (this.environment && this.environment.isTexture && this.environment.isRenderTargetTexture !== true) {
object.environment = this.environment.toJSON(meta).uuid;
}
} else if (this.isMesh || this.isLine || this.isPoints) {
object.geometry = serialize(meta.geometries, this.geometry);
const parameters = this.geometry.parameters;
if (parameters !== undefined && parameters.shapes !== undefined) {
const shapes = parameters.shapes;
if (Array.isArray(shapes)) {
for (let i = 0, l = shapes.length; i < l; i++) {
const shape = shapes[i];
serialize(meta.shapes, shape);
}
} else {
serialize(meta.shapes, shapes);
}
}
}
if (this.isSkinnedMesh) {
object.bindMode = this.bindMode;
object.bindMatrix = this.bindMatrix.toArray();
if (this.skeleton !== undefined) {
serialize(meta.skeletons, this.skeleton);
object.skeleton = this.skeleton.uuid;
}
}
if (this.material !== undefined) {
// 判断元数据是否含有材质
// 创建新变量替代不然正在使用的材质被还原回this.metaData.material会造成播放异常
let _material = this.material;
if(this.metaData?.material){
if (this.metaData.material instanceof THREE.Material){
_material = this.metaData.material;
}
}
if (Array.isArray(_material)) {
const uuids:string[] = [];
for (let i = 0, l = _material.length; i < l; i++) {
uuids.push(serialize(meta.materials, _material[i]));
}
object.material = uuids;
} else {
object.material = serialize(meta.materials, _material);
}
}
if (this.children.length > 0) {
object.children = [];
for (let i = 0; i < this.children.length; i++) {
object.children.push(this.children[i].toJSON(meta).object);
}
}
if (this.animations.length > 0) {
object.animations = [];
for (let i = 0; i < this.animations.length; i++) {
let animation = this.animations[i];
// 20250306 修复动画导出问题(代码中处理了object3D.animations,此属性下是AnimationAction数组)
if(animation instanceof THREE.AnimationAction){
animation = animation.getClip();
}
if(!animation) continue;
object.animations.push(serialize(meta.animations, animation));
}
}
if (isRootObject) {
const geometries = extractFromCache(meta.geometries);
const materials = extractFromCache(meta.materials);
const textures = extractFromCache(meta.textures);
const images = extractFromCache(meta.images);
const shapes = extractFromCache(meta.shapes);
const skeletons = extractFromCache(meta.skeletons);
const animations = extractFromCache(meta.animations);
const nodes = extractFromCache(meta.nodes);
if (geometries.length > 0) output.geometries = geometries;
if (materials.length > 0) output.materials = materials;
if (textures.length > 0) output.textures = textures;
if (images.length > 0) output.images = images;
if (shapes.length > 0) output.shapes = shapes;
if (skeletons.length > 0) output.skeletons = skeletons;
if (animations.length > 0) output.animations = animations.map(animation => {
animation.tracks = animation.tracks.map(track => {
if(!track.type){
track.type = 'vector';
}
return track;
});
return animation;
});
if (nodes.length > 0) output.nodes = nodes;
}
output.object = object;
return output;
// 从缓存哈希中提取数据,删除每个项目上的元数据并作为数组返回
function extractFromCache(cache) {
const values:any = [];
for (const key in cache) {
const data = cache[key];
delete data.metadata;
values.push(data);
}
return values;
}
}