UNPKG

polygonjs-engine

Version:

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

359 lines (320 loc) 10.3 kB
import { ColorLike, NumericAttribValue, PolyDictionary, Vector2Like, Vector3Like, Vector4Like, } from '../../types/GlobalTypes'; import {Vector3} from 'three/src/math/Vector3'; import {Int32BufferAttribute} from 'three/src/core/BufferAttribute'; import {Float32BufferAttribute} from 'three/src/core/BufferAttribute'; import {BufferGeometry} from 'three/src/core/BufferGeometry'; import {Box3} from 'three/src/math/Box3'; import {CorePoint} from './Point'; import {CoreFace} from './Face'; import {ObjectType, AttribType, AttribSize} from './Constant'; import {CoreAttribute} from './Attribute'; import {CoreAttributeData} from './AttributeData'; import {CoreGeometryBuilderPoints} from './builders/Points'; import {CoreGeometryBuilderMerge} from './builders/Merge'; import {CoreGeometryBuilderMesh} from './builders/Mesh'; import {CoreGeometryBuilderLineSegments} from './builders/LineSegments'; import {TypeAssert} from '../../engine/poly/Assert'; import {CoreType} from '../Type'; import {ArrayUtils} from '../ArrayUtils'; import {ObjectUtils} from '../ObjectUtils'; export class CoreGeometry { _bounding_box: Box3 | undefined; private _points: CorePoint[] | undefined; constructor(private _geometry: BufferGeometry) {} geometry() { return this._geometry; } uuid() { return this._geometry.uuid; } boundingBox() { return (this._bounding_box = this._bounding_box || this._create_bounding_box()); } private _create_bounding_box() { this._geometry.computeBoundingBox(); if (this._geometry.boundingBox) { return this._geometry.boundingBox; } } markAsInstance() { this._geometry.userData['is_instance'] = true; } static markedAsInstance(geometry: BufferGeometry): boolean { return geometry.userData['is_instance'] === true; } markedAsInstance(): boolean { return CoreGeometry.markedAsInstance(this._geometry); } positionAttribName() { let name = 'position'; if (this.markedAsInstance()) { name = 'instancePosition'; } return name; } computeVertexNormals() { this._geometry.computeVertexNormals(); } userDataAttribs() { const key = 'indexed_attrib_values'; return (this._geometry.userData[key] = this._geometry.userData[key] || {}); } indexedAttributeNames() { return Object.keys(this.userDataAttribs() || {}); } userDataAttrib(name: string) { name = CoreAttribute.remap_name(name); return this.userDataAttribs()[name]; } isAttribIndexed(name: string): boolean { name = CoreAttribute.remap_name(name); return this.userDataAttrib(name) != null; } hasAttrib(name: string): boolean { if (name === 'ptnum') { return true; } name = CoreAttribute.remap_name(name); return this._geometry.attributes[name] != null; } attribType(name: string) { if (this.isAttribIndexed(name)) { return AttribType.STRING; } else { return AttribType.NUMERIC; } } attribNames(): string[] { return Object.keys(this._geometry.attributes); } attribSizes() { const h: PolyDictionary<AttribSize> = {}; for (let attrib_name of this.attribNames()) { h[attrib_name] = this._geometry.attributes[attrib_name].itemSize; } return h; } attribSize(name: string): number { let attrib; name = CoreAttribute.remap_name(name); if ((attrib = this._geometry.attributes[name]) != null) { return attrib.itemSize; } else { if (name === 'ptnum') { // to ensure attrib copy with ptnum as source works return 1; } else { return 0; } } } setIndexedAttributeValues(name: string, values: string[]) { this.userDataAttribs()[name] = values; } setIndexedAttribute(name: string, values: string[], indices: number[]) { this.setIndexedAttributeValues(name, values); this._geometry.setAttribute(name, new Int32BufferAttribute(indices, 1)); } addNumericAttrib(name: string, size: number = 1, default_value: NumericAttribValue = 0) { const values = []; let attribute_added = false; if (CoreType.isNumber(default_value)) { // adding number for (let i = 0; i < this.pointsCount(); i++) { for (let j = 0; j < size; j++) { values.push(default_value); } } attribute_added = true; } else { if (size > 1) { if (CoreType.isArray(default_value)) { // adding array for (let i = 0; i < this.pointsCount(); i++) { for (let j = 0; j < size; j++) { values.push(default_value[j]); } } attribute_added = true; } else { // adding Vector2 const vec2 = default_value as Vector2Like; if (size == 2 && vec2.x != null && vec2.y != null) { for (let i = 0; i < this.pointsCount(); i++) { values.push(vec2.x); values.push(vec2.y); } attribute_added = true; } // adding Vector3 const vec3 = default_value as Vector3Like; if (size == 3 && vec3.x != null && vec3.y != null && vec3.z != null) { for (let i = 0; i < this.pointsCount(); i++) { values.push(vec3.x); values.push(vec3.y); values.push(vec3.z); } attribute_added = true; } // adding Color const col = default_value as ColorLike; if (size == 3 && col.r != null && col.g != null && col.b != null) { for (let i = 0; i < this.pointsCount(); i++) { values.push(col.r); values.push(col.g); values.push(col.b); } attribute_added = true; } // adding Vector4 const vec4 = default_value as Vector4Like; if (size == 4 && vec4.x != null && vec4.y != null && vec4.z != null && vec4.w != null) { for (let i = 0; i < this.pointsCount(); i++) { values.push(vec4.x); values.push(vec4.y); values.push(vec4.z); values.push(vec4.w); } attribute_added = true; } } } } if (attribute_added) { this._geometry.setAttribute(name.trim(), new Float32BufferAttribute(values, size)); } else { console.warn(default_value); throw `CoreGeometry.add_numeric_attrib error: no other default value allowed for now in add_numeric_attrib (default given: ${default_value})`; } } initPositionAttribute(points_count: number, default_value?: Vector3) { const values = []; if (default_value == null) { default_value = new Vector3(); } for (let i = 0; i < points_count; i++) { values.push(default_value.x); values.push(default_value.y); values.push(default_value.z); } return this._geometry.setAttribute('position', new Float32BufferAttribute(values, 3)); } addAttribute(name: string, attrib_data: CoreAttributeData) { switch (attrib_data.type()) { case AttribType.STRING: return console.log('TODO: to implement'); case AttribType.NUMERIC: return this.addNumericAttrib(name, attrib_data.size()); } } renameAttrib(old_name: string, new_name: string) { if (this.isAttribIndexed(old_name)) { this.userDataAttribs()[new_name] = ObjectUtils.clone(this.userDataAttribs()[old_name]); delete this.userDataAttribs()[old_name]; } const old_attrib = this._geometry.getAttribute(old_name); this._geometry.setAttribute(new_name.trim(), new Float32BufferAttribute(old_attrib.array, old_attrib.itemSize)); return this._geometry.deleteAttribute(old_name); } deleteAttribute(name: string) { if (this.isAttribIndexed(name)) { delete this.userDataAttribs()[name]; } return this._geometry.deleteAttribute(name); } clone(): BufferGeometry { return CoreGeometry.clone(this._geometry); } static clone(src_geometry: BufferGeometry): BufferGeometry { let src_userData; // monkey path // for (let attribute_name of Object.keys(src_geometry.attributes)) { // const attribute = src_geometry.getAttribute(attribute_name); // if (attribute.constructor.name == InterleavedBufferAttribute.name) { // MonkeyPatcher.patch(attribute as InterleavedBufferAttribute); // } // } const new_geometry = src_geometry.clone(); if ((src_userData = src_geometry.userData) != null) { new_geometry.userData = ObjectUtils.cloneDeep(src_userData); } return new_geometry; } pointsCount(): number { return CoreGeometry.pointsCount(this._geometry); } static pointsCount(geometry: BufferGeometry): number { let position; let count = 0; const core_geometry = new this(geometry); let position_attrib_name = 'position'; if (core_geometry.markedAsInstance()) { position_attrib_name = 'instancePosition'; } if ((position = geometry.getAttribute(position_attrib_name)) != null) { let array; if ((array = position.array) != null) { count = array.length / 3; } } return count; } points(): CorePoint[] { return (this._points = this._points || this.pointsFromGeometry()); } resetPoints() { this._points = undefined; } pointsFromGeometry(): CorePoint[] { const points = []; const position_attrib = this._geometry.getAttribute(this.positionAttribName()); if (position_attrib != null) { const points_count = position_attrib.array.length / 3; for (let point_index = 0; point_index < points_count; point_index++) { const point = new CorePoint(this, point_index); points.push(point); } } return points; } private static _mesh_builder = new CoreGeometryBuilderMesh(); private static _points_builder = new CoreGeometryBuilderPoints(); private static _lines_segment_builder = new CoreGeometryBuilderLineSegments(); static geometryFromPoints(points: CorePoint[], object_type: ObjectType) { switch (object_type) { case ObjectType.MESH: return this._mesh_builder.from_points(points); case ObjectType.POINTS: return this._points_builder.from_points(points); case ObjectType.LINE_SEGMENTS: return this._lines_segment_builder.from_points(points); case ObjectType.OBJECT3D: return null; case ObjectType.LOD: return null; } TypeAssert.unreachable(object_type); } static merge_geometries(geometries: BufferGeometry[]) { return CoreGeometryBuilderMerge.merge(geometries); } segments() { const index: Array<number> = (this.geometry().index?.array || []) as Array<number>; return ArrayUtils.chunk(index, 2); } faces(): CoreFace[] { return this.facesFromGeometry(); } facesFromGeometry(): CoreFace[] { const index_array = this.geometry().index?.array || []; const faces_count = index_array.length / 3; return ArrayUtils.range(faces_count).map((i) => new CoreFace(this, i)); } }