@openhps/core
Version:
Open Hybrid Positioning System - Core component
1,073 lines (965 loc) • 34.2 kB
JavaScript
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 };