UNPKG

@openhps/core

Version:

Open Hybrid Positioning System - Core component

1,073 lines (965 loc) 34.2 kB
import { Vector3 } from '../math/Vector3.js'; import { Vector2 } from '../math/Vector2.js'; import { Box3 } from '../math/Box3.js'; import { EventDispatcher } from './EventDispatcher.js'; import { BufferAttribute, Float32BufferAttribute, Uint16BufferAttribute, Uint32BufferAttribute } from './BufferAttribute.js'; import { Sphere } from '../math/Sphere.js'; import { Object3D } from './Object3D.js'; import { Matrix4 } from '../math/Matrix4.js'; import { Matrix3 } from '../math/Matrix3.js'; import { generateUUID } from '../math/MathUtils.js'; import { arrayNeedsUint32 } from '../utils.js'; let _id = 0; const _m1 = /*@__PURE__*/new Matrix4(); const _obj = /*@__PURE__*/new Object3D(); const _offset = /*@__PURE__*/new Vector3(); const _box = /*@__PURE__*/new Box3(); const _boxMorphTargets = /*@__PURE__*/new Box3(); const _vector = /*@__PURE__*/new Vector3(); /** * A representation of mesh, line, or point geometry. Includes vertex * positions, face indices, normals, colors, UVs, and custom attributes * within buffers, reducing the cost of passing all this data to the GPU. * * ```js * const geometry = new THREE.BufferGeometry(); * // create a simple square shape. We duplicate the top left and bottom right * // vertices because each vertex needs to appear once per triangle. * const vertices = new Float32Array( [ * -1.0, -1.0, 1.0, // v0 * 1.0, -1.0, 1.0, // v1 * 1.0, 1.0, 1.0, // v2 * * 1.0, 1.0, 1.0, // v3 * -1.0, 1.0, 1.0, // v4 * -1.0, -1.0, 1.0 // v5 * ] ); * // itemSize = 3 because there are 3 values (components) per vertex * geometry.setAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) ); * const material = new THREE.MeshBasicMaterial( { color: 0xff0000 } ); * const mesh = new THREE.Mesh( geometry, material ); * ``` * * @augments EventDispatcher */ class BufferGeometry extends EventDispatcher { /** * Constructs a new geometry. */ constructor() { super(); /** * This flag can be used for type testing. * * @type {boolean} * @readonly * @default true */ this.isBufferGeometry = true; /** * The ID of the geometry. * * @name BufferGeometry#id * @type {number} * @readonly */ Object.defineProperty(this, 'id', { value: _id++ }); /** * The UUID of the geometry. * * @type {string} * @readonly */ this.uuid = generateUUID(); /** * The name of the geometry. * * @type {string} */ this.name = ''; this.type = 'BufferGeometry'; /** * Allows for vertices to be re-used across multiple triangles; this is * called using "indexed triangles". Each triangle is associated with the * indices of three vertices. This attribute therefore stores the index of * each vertex for each triangular face. If this attribute is not set, the * renderer assumes that each three contiguous positions represent a single triangle. * * @type {?BufferAttribute} * @default null */ this.index = null; /** * A (storage) buffer attribute which was generated with a compute shader and * now defines indirect draw calls. * * Can only be used with {@link WebGPURenderer} and a WebGPU backend. * * @type {?BufferAttribute} * @default null */ this.indirect = null; /** * This dictionary has as id the name of the attribute to be set and as value * the buffer attribute to set it to. Rather than accessing this property directly, * use `setAttribute()` and `getAttribute()` to access attributes of this geometry. * * @type {Object<string,(BufferAttribute|InterleavedBufferAttribute)>} */ this.attributes = {}; /** * This dictionary holds the morph targets of the geometry. * * Note: Once the geometry has been rendered, the morph attribute data cannot * be changed. You will have to call `dispose()?, and create a new geometry instance. * * @type {Object} */ this.morphAttributes = {}; /** * Used to control the morph target behavior; when set to `true`, the morph * target data is treated as relative offsets, rather than as absolute * positions/normals. * * @type {boolean} * @default false */ this.morphTargetsRelative = false; /** * Split the geometry into groups, each of which will be rendered in a * separate draw call. This allows an array of materials to be used with the geometry. * * Use `addGroup()` and `clearGroups()` to edit groups, rather than modifying this array directly. * * Every vertex and index must belong to exactly one group — groups must not share vertices or * indices, and must not leave vertices or indices unused. * * @type {Array<Object>} */ this.groups = []; /** * Bounding box for the geometry which can be calculated with `computeBoundingBox()`. * * @type {Box3} * @default null */ this.boundingBox = null; /** * Bounding sphere for the geometry which can be calculated with `computeBoundingSphere()`. * * @type {Sphere} * @default null */ this.boundingSphere = null; /** * Determines the part of the geometry to render. This should not be set directly, * instead use `setDrawRange()`. * * @type {{start:number,count:number}} */ this.drawRange = { start: 0, count: Infinity }; /** * An object that can be used to store custom data about the geometry. * It should not hold references to functions as these will not be cloned. * * @type {Object} */ this.userData = {}; } /** * Returns the index of this geometry. * * @return {?BufferAttribute} The index. Returns `null` if no index is defined. */ getIndex() { return this.index; } /** * Sets the given index to this geometry. * * @param {Array<number>|BufferAttribute} index - The index to set. * @return {BufferGeometry} A reference to this instance. */ setIndex(index) { if (Array.isArray(index)) { this.index = new (arrayNeedsUint32(index) ? Uint32BufferAttribute : Uint16BufferAttribute)(index, 1); } else { this.index = index; } return this; } /** * Sets the given indirect attribute to this geometry. * * @param {BufferAttribute} indirect - The attribute holding indirect draw calls. * @return {BufferGeometry} A reference to this instance. */ setIndirect(indirect) { this.indirect = indirect; return this; } /** * Returns the indirect attribute of this geometry. * * @return {?BufferAttribute} The indirect attribute. Returns `null` if no indirect attribute is defined. */ getIndirect() { return this.indirect; } /** * Returns the buffer attribute for the given name. * * @param {string} name - The attribute name. * @return {BufferAttribute|InterleavedBufferAttribute|undefined} The buffer attribute. * Returns `undefined` if not attribute has been found. */ getAttribute(name) { return this.attributes[name]; } /** * Sets the given attribute for the given name. * * @param {string} name - The attribute name. * @param {BufferAttribute|InterleavedBufferAttribute} attribute - The attribute to set. * @return {BufferGeometry} A reference to this instance. */ setAttribute(name, attribute) { this.attributes[name] = attribute; return this; } /** * Deletes the attribute for the given name. * * @param {string} name - The attribute name to delete. * @return {BufferGeometry} A reference to this instance. */ deleteAttribute(name) { delete this.attributes[name]; return this; } /** * Returns `true` if this geometry has an attribute for the given name. * * @param {string} name - The attribute name. * @return {boolean} Whether this geometry has an attribute for the given name or not. */ hasAttribute(name) { return this.attributes[name] !== undefined; } /** * Adds a group to this geometry. * * @param {number} start - The first element in this draw call. That is the first * vertex for non-indexed geometry, otherwise the first triangle index. * @param {number} count - Specifies how many vertices (or indices) are part of this group. * @param {number} [materialIndex=0] - The material array index to use. */ addGroup(start, count, materialIndex = 0) { this.groups.push({ start: start, count: count, materialIndex: materialIndex }); } /** * Clears all groups. */ clearGroups() { this.groups = []; } /** * Sets the draw range for this geometry. * * @param {number} start - The first vertex for non-indexed geometry, otherwise the first triangle index. * @param {number} count - For non-indexed BufferGeometry, `count` is the number of vertices to render. * For indexed BufferGeometry, `count` is the number of indices to render. */ setDrawRange(start, count) { this.drawRange.start = start; this.drawRange.count = count; } /** * Applies the given 4x4 transformation matrix to the geometry. * * @param {Matrix4} matrix - The matrix to apply. * @return {BufferGeometry} A reference to this instance. */ applyMatrix4(matrix) { const position = this.attributes.position; if (position !== undefined) { position.applyMatrix4(matrix); position.needsUpdate = true; } const normal = this.attributes.normal; if (normal !== undefined) { const normalMatrix = new Matrix3().getNormalMatrix(matrix); normal.applyNormalMatrix(normalMatrix); normal.needsUpdate = true; } const tangent = this.attributes.tangent; if (tangent !== undefined) { tangent.transformDirection(matrix); tangent.needsUpdate = true; } if (this.boundingBox !== null) { this.computeBoundingBox(); } if (this.boundingSphere !== null) { this.computeBoundingSphere(); } return this; } /** * Applies the rotation represented by the Quaternion to the geometry. * * @param {Quaternion} q - The Quaternion to apply. * @return {BufferGeometry} A reference to this instance. */ applyQuaternion(q) { _m1.makeRotationFromQuaternion(q); this.applyMatrix4(_m1); return this; } /** * Rotates the geometry about the X axis. This is typically done as a one time * operation, and not during a loop. Use {@link Object3D#rotation} for typical * real-time mesh rotation. * * @param {number} angle - The angle in radians. * @return {BufferGeometry} A reference to this instance. */ rotateX(angle) { // rotate geometry around world x-axis _m1.makeRotationX(angle); this.applyMatrix4(_m1); return this; } /** * Rotates the geometry about the Y axis. This is typically done as a one time * operation, and not during a loop. Use {@link Object3D#rotation} for typical * real-time mesh rotation. * * @param {number} angle - The angle in radians. * @return {BufferGeometry} A reference to this instance. */ rotateY(angle) { // rotate geometry around world y-axis _m1.makeRotationY(angle); this.applyMatrix4(_m1); return this; } /** * Rotates the geometry about the Z axis. This is typically done as a one time * operation, and not during a loop. Use {@link Object3D#rotation} for typical * real-time mesh rotation. * * @param {number} angle - The angle in radians. * @return {BufferGeometry} A reference to this instance. */ rotateZ(angle) { // rotate geometry around world z-axis _m1.makeRotationZ(angle); this.applyMatrix4(_m1); return this; } /** * Translates the geometry. This is typically done as a one time * operation, and not during a loop. Use {@link Object3D#position} for typical * real-time mesh rotation. * * @param {number} x - The x offset. * @param {number} y - The y offset. * @param {number} z - The z offset. * @return {BufferGeometry} A reference to this instance. */ translate(x, y, z) { // translate geometry _m1.makeTranslation(x, y, z); this.applyMatrix4(_m1); return this; } /** * Scales the geometry. This is typically done as a one time * operation, and not during a loop. Use {@link Object3D#scale} for typical * real-time mesh rotation. * * @param {number} x - The x scale. * @param {number} y - The y scale. * @param {number} z - The z scale. * @return {BufferGeometry} A reference to this instance. */ scale(x, y, z) { // scale geometry _m1.makeScale(x, y, z); this.applyMatrix4(_m1); return this; } /** * Rotates the geometry to face a point in 3D space. This is typically done as a one time * operation, and not during a loop. Use {@link Object3D#lookAt} for typical * real-time mesh rotation. * * @param {Vector3} vector - The target point. * @return {BufferGeometry} A reference to this instance. */ lookAt(vector) { _obj.lookAt(vector); _obj.updateMatrix(); this.applyMatrix4(_obj.matrix); return this; } /** * Center the geometry based on its bounding box. * * @return {BufferGeometry} A reference to this instance. */ center() { this.computeBoundingBox(); this.boundingBox.getCenter(_offset).negate(); this.translate(_offset.x, _offset.y, _offset.z); return this; } /** * Defines a geometry by creating a `position` attribute based on the given array of points. The array * can hold 2D or 3D vectors. When using two-dimensional data, the `z` coordinate for all vertices is * set to `0`. * * If the method is used with an existing `position` attribute, the vertex data are overwritten with the * data from the array. The length of the array must match the vertex count. * * @param {Array<Vector2>|Array<Vector3>} points - The points. * @return {BufferGeometry} A reference to this instance. */ setFromPoints(points) { const positionAttribute = this.getAttribute('position'); if (positionAttribute === undefined) { const position = []; for (let i = 0, l = points.length; i < l; i++) { const point = points[i]; position.push(point.x, point.y, point.z || 0); } this.setAttribute('position', new Float32BufferAttribute(position, 3)); } else { const l = Math.min(points.length, positionAttribute.count); // make sure data do not exceed buffer size for (let i = 0; i < l; i++) { const point = points[i]; positionAttribute.setXYZ(i, point.x, point.y, point.z || 0); } if (points.length > positionAttribute.count) { console.warn('THREE.BufferGeometry: Buffer size too small for points data. Use .dispose() and create a new geometry.'); } positionAttribute.needsUpdate = true; } return this; } /** * Computes the bounding box of the geometry, and updates the `boundingBox` member. * The bounding box is not computed by the engine; it must be computed by your app. * You may need to recompute the bounding box if the geometry vertices are modified. */ computeBoundingBox() { if (this.boundingBox === null) { this.boundingBox = new Box3(); } const position = this.attributes.position; const morphAttributesPosition = this.morphAttributes.position; if (position && position.isGLBufferAttribute) { console.error('THREE.BufferGeometry.computeBoundingBox(): GLBufferAttribute requires a manual bounding box.', this); this.boundingBox.set(new Vector3(-Infinity, -Infinity, -Infinity), new Vector3(+Infinity, +Infinity, +Infinity)); return; } if (position !== undefined) { this.boundingBox.setFromBufferAttribute(position); // process morph attributes if present if (morphAttributesPosition) { for (let i = 0, il = morphAttributesPosition.length; i < il; i++) { const morphAttribute = morphAttributesPosition[i]; _box.setFromBufferAttribute(morphAttribute); if (this.morphTargetsRelative) { _vector.addVectors(this.boundingBox.min, _box.min); this.boundingBox.expandByPoint(_vector); _vector.addVectors(this.boundingBox.max, _box.max); this.boundingBox.expandByPoint(_vector); } else { this.boundingBox.expandByPoint(_box.min); this.boundingBox.expandByPoint(_box.max); } } } } else { this.boundingBox.makeEmpty(); } if (isNaN(this.boundingBox.min.x) || isNaN(this.boundingBox.min.y) || isNaN(this.boundingBox.min.z)) { console.error('THREE.BufferGeometry.computeBoundingBox(): Computed min/max have NaN values. The "position" attribute is likely to have NaN values.', this); } } /** * Computes the bounding sphere of the geometry, and updates the `boundingSphere` member. * The engine automatically computes the bounding sphere when it is needed, e.g., for ray casting or view frustum culling. * You may need to recompute the bounding sphere if the geometry vertices are modified. */ computeBoundingSphere() { if (this.boundingSphere === null) { this.boundingSphere = new Sphere(); } const position = this.attributes.position; const morphAttributesPosition = this.morphAttributes.position; if (position && position.isGLBufferAttribute) { console.error('THREE.BufferGeometry.computeBoundingSphere(): GLBufferAttribute requires a manual bounding sphere.', this); this.boundingSphere.set(new Vector3(), Infinity); return; } if (position) { // first, find the center of the bounding sphere const center = this.boundingSphere.center; _box.setFromBufferAttribute(position); // process morph attributes if present if (morphAttributesPosition) { for (let i = 0, il = morphAttributesPosition.length; i < il; i++) { const morphAttribute = morphAttributesPosition[i]; _boxMorphTargets.setFromBufferAttribute(morphAttribute); if (this.morphTargetsRelative) { _vector.addVectors(_box.min, _boxMorphTargets.min); _box.expandByPoint(_vector); _vector.addVectors(_box.max, _boxMorphTargets.max); _box.expandByPoint(_vector); } else { _box.expandByPoint(_boxMorphTargets.min); _box.expandByPoint(_boxMorphTargets.max); } } } _box.getCenter(center); // second, try to find a boundingSphere with a radius smaller than the // boundingSphere of the boundingBox: sqrt(3) smaller in the best case let maxRadiusSq = 0; for (let i = 0, il = position.count; i < il; i++) { _vector.fromBufferAttribute(position, i); maxRadiusSq = Math.max(maxRadiusSq, center.distanceToSquared(_vector)); } // process morph attributes if present if (morphAttributesPosition) { for (let i = 0, il = morphAttributesPosition.length; i < il; i++) { const morphAttribute = morphAttributesPosition[i]; const morphTargetsRelative = this.morphTargetsRelative; for (let j = 0, jl = morphAttribute.count; j < jl; j++) { _vector.fromBufferAttribute(morphAttribute, j); if (morphTargetsRelative) { _offset.fromBufferAttribute(position, j); _vector.add(_offset); } maxRadiusSq = Math.max(maxRadiusSq, center.distanceToSquared(_vector)); } } } this.boundingSphere.radius = Math.sqrt(maxRadiusSq); if (isNaN(this.boundingSphere.radius)) { console.error('THREE.BufferGeometry.computeBoundingSphere(): Computed radius is NaN. The "position" attribute is likely to have NaN values.', this); } } } /** * Calculates and adds a tangent attribute to this geometry. * * The computation is only supported for indexed geometries and if position, normal, and uv attributes * are defined. When using a tangent space normal map, prefer the MikkTSpace algorithm provided by * {@link BufferGeometryUtils#computeMikkTSpaceTangents} instead. */ computeTangents() { const index = this.index; const attributes = this.attributes; // based on http://www.terathon.com/code/tangent.html // (per vertex tangents) if (index === null || attributes.position === undefined || attributes.normal === undefined || attributes.uv === undefined) { console.error('THREE.BufferGeometry: .computeTangents() failed. Missing required attributes (index, position, normal or uv)'); return; } const positionAttribute = attributes.position; const normalAttribute = attributes.normal; const uvAttribute = attributes.uv; if (this.hasAttribute('tangent') === false) { this.setAttribute('tangent', new BufferAttribute(new Float32Array(4 * positionAttribute.count), 4)); } const tangentAttribute = this.getAttribute('tangent'); const tan1 = [], tan2 = []; for (let i = 0; i < positionAttribute.count; i++) { tan1[i] = new Vector3(); tan2[i] = new Vector3(); } const vA = new Vector3(), vB = new Vector3(), vC = new Vector3(), uvA = new Vector2(), uvB = new Vector2(), uvC = new Vector2(), sdir = new Vector3(), tdir = new Vector3(); function handleTriangle(a, b, c) { vA.fromBufferAttribute(positionAttribute, a); vB.fromBufferAttribute(positionAttribute, b); vC.fromBufferAttribute(positionAttribute, c); uvA.fromBufferAttribute(uvAttribute, a); uvB.fromBufferAttribute(uvAttribute, b); uvC.fromBufferAttribute(uvAttribute, c); vB.sub(vA); vC.sub(vA); uvB.sub(uvA); uvC.sub(uvA); const r = 1.0 / (uvB.x * uvC.y - uvC.x * uvB.y); // silently ignore degenerate uv triangles having coincident or colinear vertices if (!isFinite(r)) return; sdir.copy(vB).multiplyScalar(uvC.y).addScaledVector(vC, -uvB.y).multiplyScalar(r); tdir.copy(vC).multiplyScalar(uvB.x).addScaledVector(vB, -uvC.x).multiplyScalar(r); tan1[a].add(sdir); tan1[b].add(sdir); tan1[c].add(sdir); tan2[a].add(tdir); tan2[b].add(tdir); tan2[c].add(tdir); } let groups = this.groups; if (groups.length === 0) { groups = [{ start: 0, count: index.count }]; } for (let i = 0, il = groups.length; i < il; ++i) { const group = groups[i]; const start = group.start; const count = group.count; for (let j = start, jl = start + count; j < jl; j += 3) { handleTriangle(index.getX(j + 0), index.getX(j + 1), index.getX(j + 2)); } } const tmp = new Vector3(), tmp2 = new Vector3(); const n = new Vector3(), n2 = new Vector3(); function handleVertex(v) { n.fromBufferAttribute(normalAttribute, v); n2.copy(n); const t = tan1[v]; // Gram-Schmidt orthogonalize tmp.copy(t); tmp.sub(n.multiplyScalar(n.dot(t))).normalize(); // Calculate handedness tmp2.crossVectors(n2, t); const test = tmp2.dot(tan2[v]); const w = test < 0.0 ? -1.0 : 1.0; tangentAttribute.setXYZW(v, tmp.x, tmp.y, tmp.z, w); } for (let i = 0, il = groups.length; i < il; ++i) { const group = groups[i]; const start = group.start; const count = group.count; for (let j = start, jl = start + count; j < jl; j += 3) { handleVertex(index.getX(j + 0)); handleVertex(index.getX(j + 1)); handleVertex(index.getX(j + 2)); } } } /** * Computes vertex normals for the given vertex data. For indexed geometries, the method sets * each vertex normal to be the average of the face normals of the faces that share that vertex. * For non-indexed geometries, vertices are not shared, and the method sets each vertex normal * to be the same as the face normal. */ computeVertexNormals() { const index = this.index; const positionAttribute = this.getAttribute('position'); if (positionAttribute !== undefined) { let normalAttribute = this.getAttribute('normal'); if (normalAttribute === undefined) { normalAttribute = new BufferAttribute(new Float32Array(positionAttribute.count * 3), 3); this.setAttribute('normal', normalAttribute); } else { // reset existing normals to zero for (let i = 0, il = normalAttribute.count; i < il; i++) { normalAttribute.setXYZ(i, 0, 0, 0); } } const pA = new Vector3(), pB = new Vector3(), pC = new Vector3(); const nA = new Vector3(), nB = new Vector3(), nC = new Vector3(); const cb = new Vector3(), ab = new Vector3(); // indexed elements if (index) { for (let i = 0, il = index.count; i < il; i += 3) { const vA = index.getX(i + 0); const vB = index.getX(i + 1); const vC = index.getX(i + 2); pA.fromBufferAttribute(positionAttribute, vA); pB.fromBufferAttribute(positionAttribute, vB); pC.fromBufferAttribute(positionAttribute, vC); cb.subVectors(pC, pB); ab.subVectors(pA, pB); cb.cross(ab); nA.fromBufferAttribute(normalAttribute, vA); nB.fromBufferAttribute(normalAttribute, vB); nC.fromBufferAttribute(normalAttribute, vC); nA.add(cb); nB.add(cb); nC.add(cb); normalAttribute.setXYZ(vA, nA.x, nA.y, nA.z); normalAttribute.setXYZ(vB, nB.x, nB.y, nB.z); normalAttribute.setXYZ(vC, nC.x, nC.y, nC.z); } } else { // non-indexed elements (unconnected triangle soup) for (let i = 0, il = positionAttribute.count; i < il; i += 3) { pA.fromBufferAttribute(positionAttribute, i + 0); pB.fromBufferAttribute(positionAttribute, i + 1); pC.fromBufferAttribute(positionAttribute, i + 2); cb.subVectors(pC, pB); ab.subVectors(pA, pB); cb.cross(ab); normalAttribute.setXYZ(i + 0, cb.x, cb.y, cb.z); normalAttribute.setXYZ(i + 1, cb.x, cb.y, cb.z); normalAttribute.setXYZ(i + 2, cb.x, cb.y, cb.z); } } this.normalizeNormals(); normalAttribute.needsUpdate = true; } } /** * Ensures every normal vector in a geometry will have a magnitude of `1`. This will * correct lighting on the geometry surfaces. */ normalizeNormals() { const normals = this.attributes.normal; for (let i = 0, il = normals.count; i < il; i++) { _vector.fromBufferAttribute(normals, i); _vector.normalize(); normals.setXYZ(i, _vector.x, _vector.y, _vector.z); } } /** * Return a new non-index version of this indexed geometry. If the geometry * is already non-indexed, the method is a NOOP. * * @return {BufferGeometry} The non-indexed version of this indexed geometry. */ toNonIndexed() { function convertBufferAttribute(attribute, indices) { const array = attribute.array; const itemSize = attribute.itemSize; const normalized = attribute.normalized; const array2 = new array.constructor(indices.length * itemSize); let index = 0, index2 = 0; for (let i = 0, l = indices.length; i < l; i++) { if (attribute.isInterleavedBufferAttribute) { index = indices[i] * attribute.data.stride + attribute.offset; } else { index = indices[i] * itemSize; } for (let j = 0; j < itemSize; j++) { array2[index2++] = array[index++]; } } return new BufferAttribute(array2, itemSize, normalized); } // if (this.index === null) { console.warn('THREE.BufferGeometry.toNonIndexed(): BufferGeometry is already non-indexed.'); return this; } const geometry2 = new BufferGeometry(); const indices = this.index.array; const attributes = this.attributes; // attributes for (const name in attributes) { const attribute = attributes[name]; const newAttribute = convertBufferAttribute(attribute, indices); geometry2.setAttribute(name, newAttribute); } // morph attributes const morphAttributes = this.morphAttributes; for (const name in morphAttributes) { const morphArray = []; const morphAttribute = morphAttributes[name]; // morphAttribute: array of Float32BufferAttributes for (let i = 0, il = morphAttribute.length; i < il; i++) { const attribute = morphAttribute[i]; const newAttribute = convertBufferAttribute(attribute, indices); morphArray.push(newAttribute); } geometry2.morphAttributes[name] = morphArray; } geometry2.morphTargetsRelative = this.morphTargetsRelative; // groups const groups = this.groups; for (let i = 0, l = groups.length; i < l; i++) { const group = groups[i]; geometry2.addGroup(group.start, group.count, group.materialIndex); } return geometry2; } /** * Serializes the geometry into JSON. * * @return {Object} A JSON object representing the serialized geometry. */ toJSON() { const data = { metadata: { version: 4.6, type: 'BufferGeometry', generator: 'BufferGeometry.toJSON' } }; // standard BufferGeometry serialization data.uuid = this.uuid; data.type = this.type; if (this.name !== '') data.name = this.name; if (Object.keys(this.userData).length > 0) data.userData = this.userData; if (this.parameters !== undefined) { const parameters = this.parameters; for (const key in parameters) { if (parameters[key] !== undefined) data[key] = parameters[key]; } return data; } // for simplicity the code assumes attributes are not shared across geometries, see #15811 data.data = { attributes: {} }; const index = this.index; if (index !== null) { data.data.index = { type: index.array.constructor.name, array: Array.prototype.slice.call(index.array) }; } const attributes = this.attributes; for (const key in attributes) { const attribute = attributes[key]; data.data.attributes[key] = attribute.toJSON(data.data); } const morphAttributes = {}; let hasMorphAttributes = false; for (const key in this.morphAttributes) { const attributeArray = this.morphAttributes[key]; const array = []; for (let i = 0, il = attributeArray.length; i < il; i++) { const attribute = attributeArray[i]; array.push(attribute.toJSON(data.data)); } if (array.length > 0) { morphAttributes[key] = array; hasMorphAttributes = true; } } if (hasMorphAttributes) { data.data.morphAttributes = morphAttributes; data.data.morphTargetsRelative = this.morphTargetsRelative; } const groups = this.groups; if (groups.length > 0) { data.data.groups = JSON.parse(JSON.stringify(groups)); } const boundingSphere = this.boundingSphere; if (boundingSphere !== null) { data.data.boundingSphere = { center: boundingSphere.center.toArray(), radius: boundingSphere.radius }; } return data; } /** * Returns a new geometry with copied values from this instance. * * @return {BufferGeometry} A clone of this instance. */ clone() { return new this.constructor().copy(this); } /** * Copies the values of the given geometry to this instance. * * @param {BufferGeometry} source - The geometry to copy. * @return {BufferGeometry} A reference to this instance. */ copy(source) { // reset this.index = null; this.attributes = {}; this.morphAttributes = {}; this.groups = []; this.boundingBox = null; this.boundingSphere = null; // used for storing cloned, shared data const data = {}; // name this.name = source.name; // index const index = source.index; if (index !== null) { this.setIndex(index.clone()); } // attributes const attributes = source.attributes; for (const name in attributes) { const attribute = attributes[name]; this.setAttribute(name, attribute.clone(data)); } // morph attributes const morphAttributes = source.morphAttributes; for (const name in morphAttributes) { const array = []; const morphAttribute = morphAttributes[name]; // morphAttribute: array of Float32BufferAttributes for (let i = 0, l = morphAttribute.length; i < l; i++) { array.push(morphAttribute[i].clone(data)); } this.morphAttributes[name] = array; } this.morphTargetsRelative = source.morphTargetsRelative; // groups const groups = source.groups; for (let i = 0, l = groups.length; i < l; i++) { const group = groups[i]; this.addGroup(group.start, group.count, group.materialIndex); } // bounding box const boundingBox = source.boundingBox; if (boundingBox !== null) { this.boundingBox = boundingBox.clone(); } // bounding sphere const boundingSphere = source.boundingSphere; if (boundingSphere !== null) { this.boundingSphere = boundingSphere.clone(); } // draw range this.drawRange.start = source.drawRange.start; this.drawRange.count = source.drawRange.count; // user data this.userData = source.userData; return this; } /** * Frees the GPU-related resources allocated by this instance. Call this * method whenever this instance is no longer used in your app. * * @fires BufferGeometry#dispose */ dispose() { this.dispatchEvent({ type: 'dispose' }); } } export { BufferGeometry };