UNPKG

polygonjs-engine

Version:

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

319 lines (295 loc) 9.52 kB
import {Number3, AttribValue, NumericAttribValue, PolyDictionary} from '../../types/GlobalTypes'; import {Vector2} from 'three/src/math/Vector2'; import {Vector3} from 'three/src/math/Vector3'; import {Vector4} from 'three/src/math/Vector4'; import {Object3D} from 'three/src/core/Object3D'; import {Mesh} from 'three/src/objects/Mesh'; import {Color} from 'three/src/math/Color'; import {BufferGeometry} from 'three/src/core/BufferGeometry'; import {AnimationClip} from 'three/src/animation/AnimationClip'; import {Material} from 'three/src/materials/Material'; import {SkinnedMesh} from 'three/src/objects/SkinnedMesh'; import {Bone} from 'three/src/objects/Bone'; import {CoreGeometry} from './Geometry'; import {GroupString} from './Group'; import {CoreAttribute} from './Attribute'; import {CoreConstant, AttribType, AttribSize} from './Constant'; import {CorePoint} from './Point'; import {CoreMaterial, ShaderMaterialWithCustomMaterials} from './Material'; import {CoreString} from '../String'; import {CoreEntity} from './Entity'; import {ParamInitValueSerialized} from '../../engine/params/types/ParamInitValueSerialized'; import {CoreType} from '../Type'; import {ObjectUtils} from '../ObjectUtils'; const PTNUM = 'ptnum'; const NAME_ATTR = 'name'; const ATTRIBUTES = 'attributes'; interface Object3DWithAnimations extends Object3D { animations: AnimationClip[]; } interface MaterialWithColor extends Material { color: Color; } // interface SkinnedMeshWithisSkinnedMesh extends SkinnedMesh { // readonly isSkinnedMesh: boolean; // } export class CoreObject extends CoreEntity { constructor(private _object: Object3D, index: number) { super(index); if (this._object.userData[ATTRIBUTES] == null) { this._object.userData[ATTRIBUTES] = {}; } } // set_index(i: number) { // this._index = i; // } object() { return this._object; } geometry(): BufferGeometry | null { return (this._object as Mesh).geometry as BufferGeometry | null; } 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 // } } points() { return this.coreGeometry()?.points() || []; } pointsFromGroup(group: GroupString): CorePoint[] { if (group) { const indices = CoreString.indices(group); if (indices) { const points = this.points(); return indices.map((i) => points[i]); } else { return []; } } else { return this.points(); } } static isInGroup(groupString: string, object: Object3D) { const group = groupString.trim(); if (group.length == 0) { return true; } const elements = group.split('='); const attribNameWithPrefix = elements[0]; if (attribNameWithPrefix[0] == '@') { const attribName = attribNameWithPrefix.substr(1); const expectedAttribValue = elements[1]; const currentAttribValue = this.attribValue(object, attribName); return expectedAttribValue == currentAttribValue; } return false; } computeVertexNormals() { this.coreGeometry()?.computeVertexNormals(); } static addAttribute(object: Object3D, attrib_name: string, value: AttribValue) { let data: ParamInitValueSerialized; if (value != null && !CoreType.isNumber(value) && !CoreType.isArray(value) && !CoreType.isString(value)) { data = (value as Vector3).toArray() as Number3; } else { data = value; } const user_data = object.userData; user_data[ATTRIBUTES] = user_data[ATTRIBUTES] || {}; user_data[ATTRIBUTES][attrib_name] = data; } addAttribute(name: string, value: AttribValue) { CoreObject.addAttribute(this._object, name, value); } addNumericAttrib(name: string, value: NumericAttribValue) { this.addAttribute(name, value); } setAttribValue(name: string, value: AttribValue) { this.addAttribute(name, value); } addNumericVertexAttrib(name: string, size: number, default_value: NumericAttribValue) { if (default_value == null) { default_value = CoreAttribute.default_value(size); } this.coreGeometry()?.addNumericAttrib(name, size, default_value); } attributeNames(): string[] { // TODO: to remove return Object.keys(this._object.userData[ATTRIBUTES]); } attribNames(): string[] { return this.attributeNames(); } hasAttrib(name: string): boolean { return this.attributeNames().includes(name); } renameAttrib(old_name: string, new_name: string) { const current_value = this.attribValue(old_name); if (current_value != null) { this.addAttribute(new_name, current_value); this.deleteAttribute(old_name); } else { console.warn(`attribute ${old_name} not found`); } } deleteAttribute(name: string) { delete this._object.userData[ATTRIBUTES][name]; } static attribValue( object: Object3D, name: string, index: number = 0, target?: Vector2 | Vector3 | Vector4 ): AttribValue | undefined { if (name === PTNUM) { return index; } if (object.userData && object.userData[ATTRIBUTES]) { const val = object.userData[ATTRIBUTES][name] as AttribValue; if (val == null) { if (name == NAME_ATTR) { return object.name; } } else { if (CoreType.isArray(val) && target) { target.fromArray(val); return target; } } return val; } if (name == NAME_ATTR) { return object.name; } } static stringAttribValue(object: Object3D, name: string, index: number = 0): string | undefined { const str = this.attribValue(object, name, index); if (str != null) { if (CoreType.isString(str)) { return str; } else { return `${str}`; } } } attribValue(name: string, target?: Vector2 | Vector3 | Vector4): AttribValue | undefined { return CoreObject.attribValue(this._object, name, this._index, target); } stringAttribValue(name: string) { return CoreObject.stringAttribValue(this._object, name, this._index); } name(): string { return this.attribValue(NAME_ATTR) as string; } humanType(): string { return CoreConstant.CONSTRUCTOR_NAMES_BY_CONSTRUCTOR_NAME[this._object.constructor.name]; } attribTypes() { const h: PolyDictionary<AttribType> = {}; for (let attrib_name of this.attribNames()) { const type = this.attribType(attrib_name); if (type != null) { h[attrib_name] = type; } } return h; } attribType(name: string) { const val = this.attribValue(name); if (CoreType.isString(val)) { return AttribType.STRING; } else { return AttribType.NUMERIC; } } attribSizes() { const h: PolyDictionary<AttribSize> = {}; for (let attrib_name of this.attribNames()) { const size = this.attribSize(attrib_name); if (size != null) { h[attrib_name] = size; } } return h; } attribSize(name: string): AttribSize | null { const val = this.attribValue(name); if (val == null) { return null; } return CoreAttribute.attribSizeFromValue(val); } clone() { return CoreObject.clone(this._object); } static clone(src_object: Object3D) { const new_object = src_object.clone(); var sourceLookup = new Map<Object3D, Object3D>(); var cloneLookup = new Map<Object3D, Object3D>(); CoreObject.parallelTraverse(src_object, new_object, function (sourceNode: Object3D, clonedNode: Object3D) { sourceLookup.set(clonedNode, sourceNode); cloneLookup.set(sourceNode, clonedNode); }); new_object.traverse(function (node) { const src_node = sourceLookup.get(node) as SkinnedMesh; const mesh_node = node as Mesh; if (mesh_node.geometry) { const src_node_geometry = src_node.geometry as BufferGeometry; mesh_node.geometry = CoreGeometry.clone(src_node_geometry); const mesh_node_geometry = mesh_node.geometry as BufferGeometry; if (mesh_node_geometry.userData) { mesh_node_geometry.userData = ObjectUtils.cloneDeep(src_node_geometry.userData); } } if (mesh_node.material) { mesh_node.material = src_node.material; CoreMaterial.apply_custom_materials(node, mesh_node.material as ShaderMaterialWithCustomMaterials); // prevents crashes for linesegments with shader material such as the line dashed instance // TODO: test const material_with_color = mesh_node.material as MaterialWithColor; if (material_with_color.color == null) { material_with_color.color = new Color(1, 1, 1); } } if (src_object.userData) { node.userData = ObjectUtils.cloneDeep(src_node.userData); } const src_node_with_animations = (<unknown>src_node) 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 = src_node; 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 new_object; } static parallelTraverse(a: Object3D, b: Object3D, callback: (a: Object3D, b: Object3D) => void) { callback(a, b); for (var i = 0; i < a.children.length; i++) { this.parallelTraverse(a.children[i], b.children[i], callback); } } }