|
|
@@ -1,25 +1,20 @@
|
|
|
import {
|
|
|
- BufferGeometry,
|
|
|
- Color,
|
|
|
- Float32BufferAttribute,
|
|
|
- Vector2,
|
|
|
- Vector3,
|
|
|
- Vector4
|
|
|
+ BufferAttribute,
|
|
|
+ BufferGeometry
|
|
|
} from 'three';
|
|
|
-import * as BufferGeometryUtils from '../utils/BufferGeometryUtils.js';
|
|
|
-
|
|
|
-const _cb = new Vector3(), _ab = new Vector3();
|
|
|
+import { MeshoptSimplifier } from '../libs/meshopt_simplifier.module.js';
|
|
|
+import { mergeVertices } from '../utils/BufferGeometryUtils.js';
|
|
|
|
|
|
/**
|
|
|
* This class can be used to modify a geometry by simplifying it. A typical use
|
|
|
* case for such a modifier is automatic LOD generation.
|
|
|
*
|
|
|
- * The implementation is based on [Progressive Mesh type Polygon Reduction Algorithm](https://web.archive.org/web/20230610044040/http://www.melax.com/polychop/)
|
|
|
- * by Stan Melax in 1998.
|
|
|
+ * The implementation is based on [meshoptimizer]{@link https://github.com/zeux/meshoptimizer}.
|
|
|
+ * If you only need a simplified index buffer, use {@link MeshoptSimplifier} directly.
|
|
|
*
|
|
|
* ```js
|
|
|
* const modifier = new SimplifyModifier();
|
|
|
- * geometry = modifier.modify( geometry );
|
|
|
+ * geometry = await modifier.modify( geometry, count );
|
|
|
* ```
|
|
|
*
|
|
|
* @three_import import { SimplifyModifier } from 'three/addons/modifiers/SimplifyModifier.js';
|
|
|
@@ -27,206 +22,113 @@ const _cb = new Vector3(), _ab = new Vector3();
|
|
|
class SimplifyModifier {
|
|
|
|
|
|
/**
|
|
|
- * Returns a new, modified version of the given geometry by applying a simplification.
|
|
|
- * Please note that the resulting geometry is always non-indexed.
|
|
|
+ * Returns a new, simplified version of the given geometry. The vertex buffers
|
|
|
+ * of the result only contain vertices referenced by the simplified index.
|
|
|
*
|
|
|
+ * @async
|
|
|
* @param {BufferGeometry} geometry - The geometry to modify.
|
|
|
- * @param {number} count - The number of vertices to remove.
|
|
|
- * @return {BufferGeometry} A new, modified geometry.
|
|
|
+ * @param {number} count - The approximate number of vertices to remove.
|
|
|
+ * @return {Promise<BufferGeometry>} A promise that resolves with the new, modified geometry.
|
|
|
*/
|
|
|
- modify( geometry, count ) {
|
|
|
-
|
|
|
- geometry = geometry.clone();
|
|
|
-
|
|
|
- // currently morphAttributes are not supported
|
|
|
- delete geometry.morphAttributes.position;
|
|
|
- delete geometry.morphAttributes.normal;
|
|
|
- const attributes = geometry.attributes;
|
|
|
-
|
|
|
- // this modifier can only process indexed and non-indexed geometries with at least a position attribute
|
|
|
-
|
|
|
- for ( const name in attributes ) {
|
|
|
-
|
|
|
- if ( name !== 'position' && name !== 'uv' && name !== 'normal' && name !== 'tangent' && name !== 'color' ) geometry.deleteAttribute( name );
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- geometry = BufferGeometryUtils.mergeVertices( geometry );
|
|
|
+ async modify( geometry, count ) {
|
|
|
|
|
|
- //
|
|
|
- // put data of original geometry in different data structures
|
|
|
- //
|
|
|
+ await MeshoptSimplifier.ready;
|
|
|
|
|
|
- const vertices = [];
|
|
|
- const faces = [];
|
|
|
+ // non-indexed geometries must be welded so the simplifier can collapse edges
|
|
|
|
|
|
- // add vertices
|
|
|
+ if ( geometry.getIndex() === null ) geometry = mergeVertices( geometry );
|
|
|
|
|
|
+ const index = geometry.getIndex();
|
|
|
const positionAttribute = geometry.getAttribute( 'position' );
|
|
|
- const uvAttribute = geometry.getAttribute( 'uv' );
|
|
|
- const normalAttribute = geometry.getAttribute( 'normal' );
|
|
|
- const tangentAttribute = geometry.getAttribute( 'tangent' );
|
|
|
- const colorAttribute = geometry.getAttribute( 'color' );
|
|
|
-
|
|
|
- let t = null;
|
|
|
- let v2 = null;
|
|
|
- let nor = null;
|
|
|
- let col = null;
|
|
|
-
|
|
|
- for ( let i = 0; i < positionAttribute.count; i ++ ) {
|
|
|
-
|
|
|
- const v = new Vector3().fromBufferAttribute( positionAttribute, i );
|
|
|
- if ( uvAttribute ) {
|
|
|
-
|
|
|
- v2 = new Vector2().fromBufferAttribute( uvAttribute, i );
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- if ( normalAttribute ) {
|
|
|
-
|
|
|
- nor = new Vector3().fromBufferAttribute( normalAttribute, i );
|
|
|
-
|
|
|
- }
|
|
|
|
|
|
- if ( tangentAttribute ) {
|
|
|
+ // the simplifier requires tightly packed positions
|
|
|
|
|
|
- t = new Vector4().fromBufferAttribute( tangentAttribute, i );
|
|
|
+ const positions = new Float32Array( positionAttribute.count * 3 );
|
|
|
|
|
|
- }
|
|
|
+ for ( let i = 0; i < positionAttribute.count; i ++ ) {
|
|
|
|
|
|
- if ( colorAttribute ) {
|
|
|
+ positions[ i * 3 + 0 ] = positionAttribute.getX( i );
|
|
|
+ positions[ i * 3 + 1 ] = positionAttribute.getY( i );
|
|
|
+ positions[ i * 3 + 2 ] = positionAttribute.getZ( i );
|
|
|
|
|
|
- col = new Color().fromBufferAttribute( colorAttribute, i );
|
|
|
+ }
|
|
|
|
|
|
- }
|
|
|
+ const ratio = Math.min( 1, Math.max( 0, 1 - count / positionAttribute.count ) );
|
|
|
+ const targetIndexCount = Math.max( 3, Math.floor( index.count * ratio / 3 ) * 3 );
|
|
|
|
|
|
- const vertex = new Vertex( v, v2, nor, t, col );
|
|
|
- vertices.push( vertex );
|
|
|
+ // normals and uvs, when present, contribute to the error metric so collapses
|
|
|
+ // that would distort shading or texture seams are penalized
|
|
|
|
|
|
- }
|
|
|
+ const normalAttribute = geometry.getAttribute( 'normal' );
|
|
|
+ const uvAttribute = geometry.getAttribute( 'uv' );
|
|
|
+ const attributeStride = ( normalAttribute ? 3 : 0 ) + ( uvAttribute ? 2 : 0 );
|
|
|
|
|
|
- // add faces
|
|
|
+ let indices;
|
|
|
|
|
|
- let index = geometry.getIndex();
|
|
|
+ if ( attributeStride > 0 ) {
|
|
|
|
|
|
- if ( index !== null ) {
|
|
|
+ const vertexAttributes = new Float32Array( positionAttribute.count * attributeStride );
|
|
|
+ const attributeWeights = [];
|
|
|
|
|
|
- for ( let i = 0; i < index.count; i += 3 ) {
|
|
|
+ if ( normalAttribute ) attributeWeights.push( 0.25, 0.25, 0.25 );
|
|
|
+ if ( uvAttribute ) attributeWeights.push( 0.5, 0.5 );
|
|
|
|
|
|
- const a = index.getX( i );
|
|
|
- const b = index.getX( i + 1 );
|
|
|
- const c = index.getX( i + 2 );
|
|
|
+ for ( let i = 0, o = 0; i < positionAttribute.count; i ++ ) {
|
|
|
|
|
|
- const triangle = new Triangle( vertices[ a ], vertices[ b ], vertices[ c ], a, b, c );
|
|
|
- faces.push( triangle );
|
|
|
+ if ( normalAttribute ) {
|
|
|
|
|
|
- }
|
|
|
+ vertexAttributes[ o ++ ] = normalAttribute.getX( i );
|
|
|
+ vertexAttributes[ o ++ ] = normalAttribute.getY( i );
|
|
|
+ vertexAttributes[ o ++ ] = normalAttribute.getZ( i );
|
|
|
|
|
|
- } else {
|
|
|
+ }
|
|
|
|
|
|
- for ( let i = 0; i < positionAttribute.count; i += 3 ) {
|
|
|
+ if ( uvAttribute ) {
|
|
|
|
|
|
- const a = i;
|
|
|
- const b = i + 1;
|
|
|
- const c = i + 2;
|
|
|
+ vertexAttributes[ o ++ ] = uvAttribute.getX( i );
|
|
|
+ vertexAttributes[ o ++ ] = uvAttribute.getY( i );
|
|
|
|
|
|
- const triangle = new Triangle( vertices[ a ], vertices[ b ], vertices[ c ], a, b, c );
|
|
|
- faces.push( triangle );
|
|
|
+ }
|
|
|
|
|
|
}
|
|
|
|
|
|
- }
|
|
|
-
|
|
|
- // compute all edge collapse costs
|
|
|
+ indices = MeshoptSimplifier.simplifyWithAttributes( index.array, positions, 3, vertexAttributes, attributeStride, attributeWeights, null, targetIndexCount, 1 )[ 0 ];
|
|
|
|
|
|
- for ( let i = 0, il = vertices.length; i < il; i ++ ) {
|
|
|
+ } else {
|
|
|
|
|
|
- computeEdgeCostAtVertex( vertices[ i ] );
|
|
|
+ indices = MeshoptSimplifier.simplify( index.array, positions, 3, targetIndexCount, 1 )[ 0 ];
|
|
|
|
|
|
}
|
|
|
|
|
|
- let nextVertex;
|
|
|
+ // drop vertices that are no longer referenced and rebuild the attributes
|
|
|
|
|
|
- let z = count;
|
|
|
-
|
|
|
- while ( z -- ) {
|
|
|
-
|
|
|
- nextVertex = minimumCostEdge( vertices );
|
|
|
-
|
|
|
- if ( ! nextVertex ) {
|
|
|
-
|
|
|
- console.log( 'THREE.SimplifyModifier: No next vertex' );
|
|
|
- break;
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- collapse( vertices, faces, nextVertex, nextVertex.collapseNeighbor );
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- //
|
|
|
+ const [ remap, unique ] = MeshoptSimplifier.compactMesh( indices ); // rewrites indices in place
|
|
|
|
|
|
const simplifiedGeometry = new BufferGeometry();
|
|
|
- const position = [];
|
|
|
- const uv = [];
|
|
|
- const normal = [];
|
|
|
- const tangent = [];
|
|
|
- const color = [];
|
|
|
-
|
|
|
- index = [];
|
|
|
-
|
|
|
- //
|
|
|
-
|
|
|
- for ( let i = 0; i < vertices.length; i ++ ) {
|
|
|
|
|
|
- const vertex = vertices[ i ];
|
|
|
- position.push( vertex.position.x, vertex.position.y, vertex.position.z );
|
|
|
- if ( vertex.uv ) {
|
|
|
+ for ( const name in geometry.attributes ) {
|
|
|
|
|
|
- uv.push( vertex.uv.x, vertex.uv.y );
|
|
|
+ const attribute = geometry.getAttribute( name );
|
|
|
+ const itemSize = attribute.itemSize;
|
|
|
+ const newAttribute = new BufferAttribute( new attribute.array.constructor( unique * itemSize ), itemSize, attribute.normalized );
|
|
|
|
|
|
- }
|
|
|
-
|
|
|
- if ( vertex.normal ) {
|
|
|
+ // scatter each vertex to its new slot; 0xffffffff marks unreferenced vertices
|
|
|
|
|
|
- normal.push( vertex.normal.x, vertex.normal.y, vertex.normal.z );
|
|
|
+ for ( let i = 0; i < remap.length; i ++ ) {
|
|
|
|
|
|
- }
|
|
|
+ const target = remap[ i ];
|
|
|
|
|
|
- if ( vertex.tangent ) {
|
|
|
+ if ( target === 0xffffffff ) continue;
|
|
|
|
|
|
- tangent.push( vertex.tangent.x, vertex.tangent.y, vertex.tangent.z, vertex.tangent.w );
|
|
|
+ for ( let k = 0; k < itemSize; k ++ ) newAttribute.setComponent( target, k, attribute.getComponent( i, k ) );
|
|
|
|
|
|
}
|
|
|
|
|
|
- if ( vertex.color ) {
|
|
|
-
|
|
|
- color.push( vertex.color.r, vertex.color.g, vertex.color.b );
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- // cache final index to GREATLY speed up faces reconstruction
|
|
|
- vertex.id = i;
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- //
|
|
|
-
|
|
|
- for ( let i = 0; i < faces.length; i ++ ) {
|
|
|
-
|
|
|
- const face = faces[ i ];
|
|
|
- index.push( face.v1.id, face.v2.id, face.v3.id );
|
|
|
+ simplifiedGeometry.setAttribute( name, newAttribute );
|
|
|
|
|
|
}
|
|
|
|
|
|
- simplifiedGeometry.setAttribute( 'position', new Float32BufferAttribute( position, 3 ) );
|
|
|
- if ( uv.length > 0 ) simplifiedGeometry.setAttribute( 'uv', new Float32BufferAttribute( uv, 2 ) );
|
|
|
- if ( normal.length > 0 ) simplifiedGeometry.setAttribute( 'normal', new Float32BufferAttribute( normal, 3 ) );
|
|
|
- if ( tangent.length > 0 ) simplifiedGeometry.setAttribute( 'tangent', new Float32BufferAttribute( tangent, 4 ) );
|
|
|
- if ( color.length > 0 ) simplifiedGeometry.setAttribute( 'color', new Float32BufferAttribute( color, 3 ) );
|
|
|
-
|
|
|
- simplifiedGeometry.setIndex( index );
|
|
|
+ simplifiedGeometry.setIndex( new BufferAttribute( indices, 1 ) );
|
|
|
|
|
|
return simplifiedGeometry;
|
|
|
|
|
|
@@ -234,399 +136,4 @@ class SimplifyModifier {
|
|
|
|
|
|
}
|
|
|
|
|
|
-function pushIfUnique( array, object ) {
|
|
|
-
|
|
|
- if ( array.indexOf( object ) === - 1 ) array.push( object );
|
|
|
-
|
|
|
-}
|
|
|
-
|
|
|
-function removeFromArray( array, object ) {
|
|
|
-
|
|
|
- const k = array.indexOf( object );
|
|
|
- if ( k > - 1 ) array.splice( k, 1 );
|
|
|
-
|
|
|
-}
|
|
|
-
|
|
|
-function computeEdgeCollapseCost( u, v ) {
|
|
|
-
|
|
|
- // if we collapse edge uv by moving u to v then how
|
|
|
- // much different will the model change, i.e. the "error".
|
|
|
-
|
|
|
- const edgelength = v.position.distanceTo( u.position );
|
|
|
- let curvature = 0;
|
|
|
-
|
|
|
- const sideFaces = [];
|
|
|
-
|
|
|
- // find the "sides" triangles that are on the edge uv
|
|
|
- for ( let i = 0, il = u.faces.length; i < il; i ++ ) {
|
|
|
-
|
|
|
- const face = u.faces[ i ];
|
|
|
-
|
|
|
- if ( face.hasVertex( v ) ) {
|
|
|
-
|
|
|
- sideFaces.push( face );
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- // use the triangle facing most away from the sides
|
|
|
- // to determine our curvature term
|
|
|
- for ( let i = 0, il = u.faces.length; i < il; i ++ ) {
|
|
|
-
|
|
|
- let minCurvature = 1;
|
|
|
- const face = u.faces[ i ];
|
|
|
-
|
|
|
- for ( let j = 0; j < sideFaces.length; j ++ ) {
|
|
|
-
|
|
|
- const sideFace = sideFaces[ j ];
|
|
|
- // use dot product of face normals.
|
|
|
- const dotProd = face.normal.dot( sideFace.normal );
|
|
|
- minCurvature = Math.min( minCurvature, ( 1.001 - dotProd ) / 2 );
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- curvature = Math.max( curvature, minCurvature );
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- // crude approach in attempt to preserve borders
|
|
|
- // though it seems not to be totally correct
|
|
|
- const borders = 0;
|
|
|
-
|
|
|
- if ( sideFaces.length < 2 ) {
|
|
|
-
|
|
|
- // we add some arbitrary cost for borders,
|
|
|
- // borders += 10;
|
|
|
- curvature = 1;
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- const amt = edgelength * curvature + borders;
|
|
|
-
|
|
|
- return amt;
|
|
|
-
|
|
|
-}
|
|
|
-
|
|
|
-function computeEdgeCostAtVertex( v ) {
|
|
|
-
|
|
|
- // compute the edge collapse cost for all edges that start
|
|
|
- // from vertex v. Since we are only interested in reducing
|
|
|
- // the object by selecting the min cost edge at each step, we
|
|
|
- // only cache the cost of the least cost edge at this vertex
|
|
|
- // (in member variable collapse) as well as the value of the
|
|
|
- // cost (in member variable collapseCost).
|
|
|
-
|
|
|
- if ( v.neighbors.length === 0 ) {
|
|
|
-
|
|
|
- // collapse if no neighbors.
|
|
|
- v.collapseNeighbor = null;
|
|
|
- v.collapseCost = - 0.01;
|
|
|
-
|
|
|
- return;
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- v.collapseCost = 100000;
|
|
|
- v.collapseNeighbor = null;
|
|
|
-
|
|
|
- // search all neighboring edges for "least cost" edge
|
|
|
- for ( let i = 0; i < v.neighbors.length; i ++ ) {
|
|
|
-
|
|
|
- const collapseCost = computeEdgeCollapseCost( v, v.neighbors[ i ] );
|
|
|
-
|
|
|
- if ( ! v.collapseNeighbor ) {
|
|
|
-
|
|
|
- v.collapseNeighbor = v.neighbors[ i ];
|
|
|
- v.collapseCost = collapseCost;
|
|
|
- v.minCost = collapseCost;
|
|
|
- v.totalCost = 0;
|
|
|
- v.costCount = 0;
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- v.costCount ++;
|
|
|
- v.totalCost += collapseCost;
|
|
|
-
|
|
|
- if ( collapseCost < v.minCost ) {
|
|
|
-
|
|
|
- v.collapseNeighbor = v.neighbors[ i ];
|
|
|
- v.minCost = collapseCost;
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- // we average the cost of collapsing at this vertex
|
|
|
- v.collapseCost = v.totalCost / v.costCount;
|
|
|
- // v.collapseCost = v.minCost;
|
|
|
-
|
|
|
-}
|
|
|
-
|
|
|
-function removeVertex( v, vertices ) {
|
|
|
-
|
|
|
- console.assert( v.faces.length === 0 );
|
|
|
-
|
|
|
- while ( v.neighbors.length ) {
|
|
|
-
|
|
|
- const n = v.neighbors.pop();
|
|
|
- removeFromArray( n.neighbors, v );
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- removeFromArray( vertices, v );
|
|
|
-
|
|
|
-}
|
|
|
-
|
|
|
-function removeFace( f, faces ) {
|
|
|
-
|
|
|
- removeFromArray( faces, f );
|
|
|
-
|
|
|
- if ( f.v1 ) removeFromArray( f.v1.faces, f );
|
|
|
- if ( f.v2 ) removeFromArray( f.v2.faces, f );
|
|
|
- if ( f.v3 ) removeFromArray( f.v3.faces, f );
|
|
|
-
|
|
|
- // TODO optimize this!
|
|
|
- const vs = [ f.v1, f.v2, f.v3 ];
|
|
|
-
|
|
|
- for ( let i = 0; i < 3; i ++ ) {
|
|
|
-
|
|
|
- const v1 = vs[ i ];
|
|
|
- const v2 = vs[ ( i + 1 ) % 3 ];
|
|
|
-
|
|
|
- if ( ! v1 || ! v2 ) continue;
|
|
|
-
|
|
|
- v1.removeIfNonNeighbor( v2 );
|
|
|
- v2.removeIfNonNeighbor( v1 );
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
-}
|
|
|
-
|
|
|
-function collapse( vertices, faces, u, v ) {
|
|
|
-
|
|
|
- // Collapse the edge uv by moving vertex u onto v
|
|
|
-
|
|
|
- if ( ! v ) {
|
|
|
-
|
|
|
- // u is a vertex all by itself so just delete it..
|
|
|
- removeVertex( u, vertices );
|
|
|
- return;
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- if ( v.uv ) {
|
|
|
-
|
|
|
- u.uv.copy( v.uv );
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- if ( v.normal ) {
|
|
|
-
|
|
|
- v.normal.add( u.normal ).normalize();
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- if ( v.tangent ) {
|
|
|
-
|
|
|
- v.tangent.add( u.tangent ).normalize();
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- const tmpVertices = [];
|
|
|
-
|
|
|
- for ( let i = 0; i < u.neighbors.length; i ++ ) {
|
|
|
-
|
|
|
- tmpVertices.push( u.neighbors[ i ] );
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- // delete triangles on edge uv:
|
|
|
- for ( let i = u.faces.length - 1; i >= 0; i -- ) {
|
|
|
-
|
|
|
- if ( u.faces[ i ] && u.faces[ i ].hasVertex( v ) ) {
|
|
|
-
|
|
|
- removeFace( u.faces[ i ], faces );
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- // update remaining triangles to have v instead of u
|
|
|
- for ( let i = u.faces.length - 1; i >= 0; i -- ) {
|
|
|
-
|
|
|
- u.faces[ i ].replaceVertex( u, v );
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- removeVertex( u, vertices );
|
|
|
-
|
|
|
- // recompute the edge collapse costs in neighborhood
|
|
|
- for ( let i = 0; i < tmpVertices.length; i ++ ) {
|
|
|
-
|
|
|
- computeEdgeCostAtVertex( tmpVertices[ i ] );
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
-}
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-function minimumCostEdge( vertices ) {
|
|
|
-
|
|
|
- // O(n * n) approach. TODO optimize this
|
|
|
-
|
|
|
- let least = vertices[ 0 ];
|
|
|
-
|
|
|
- for ( let i = 0; i < vertices.length; i ++ ) {
|
|
|
-
|
|
|
- if ( vertices[ i ].collapseCost < least.collapseCost ) {
|
|
|
-
|
|
|
- least = vertices[ i ];
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- return least;
|
|
|
-
|
|
|
-}
|
|
|
-
|
|
|
-// we use a triangle class to represent structure of face slightly differently
|
|
|
-
|
|
|
-class Triangle {
|
|
|
-
|
|
|
- constructor( v1, v2, v3, a, b, c ) {
|
|
|
-
|
|
|
- this.a = a;
|
|
|
- this.b = b;
|
|
|
- this.c = c;
|
|
|
-
|
|
|
- this.v1 = v1;
|
|
|
- this.v2 = v2;
|
|
|
- this.v3 = v3;
|
|
|
-
|
|
|
- this.normal = new Vector3();
|
|
|
-
|
|
|
- this.computeNormal();
|
|
|
-
|
|
|
- v1.faces.push( this );
|
|
|
- v1.addUniqueNeighbor( v2 );
|
|
|
- v1.addUniqueNeighbor( v3 );
|
|
|
-
|
|
|
- v2.faces.push( this );
|
|
|
- v2.addUniqueNeighbor( v1 );
|
|
|
- v2.addUniqueNeighbor( v3 );
|
|
|
-
|
|
|
-
|
|
|
- v3.faces.push( this );
|
|
|
- v3.addUniqueNeighbor( v1 );
|
|
|
- v3.addUniqueNeighbor( v2 );
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- computeNormal() {
|
|
|
-
|
|
|
- const vA = this.v1.position;
|
|
|
- const vB = this.v2.position;
|
|
|
- const vC = this.v3.position;
|
|
|
-
|
|
|
- _cb.subVectors( vC, vB );
|
|
|
- _ab.subVectors( vA, vB );
|
|
|
- _cb.cross( _ab ).normalize();
|
|
|
-
|
|
|
- this.normal.copy( _cb );
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- hasVertex( v ) {
|
|
|
-
|
|
|
- return v === this.v1 || v === this.v2 || v === this.v3;
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- replaceVertex( oldv, newv ) {
|
|
|
-
|
|
|
- if ( oldv === this.v1 ) this.v1 = newv;
|
|
|
- else if ( oldv === this.v2 ) this.v2 = newv;
|
|
|
- else if ( oldv === this.v3 ) this.v3 = newv;
|
|
|
-
|
|
|
- removeFromArray( oldv.faces, this );
|
|
|
- newv.faces.push( this );
|
|
|
-
|
|
|
-
|
|
|
- oldv.removeIfNonNeighbor( this.v1 );
|
|
|
- this.v1.removeIfNonNeighbor( oldv );
|
|
|
-
|
|
|
- oldv.removeIfNonNeighbor( this.v2 );
|
|
|
- this.v2.removeIfNonNeighbor( oldv );
|
|
|
-
|
|
|
- oldv.removeIfNonNeighbor( this.v3 );
|
|
|
- this.v3.removeIfNonNeighbor( oldv );
|
|
|
-
|
|
|
- this.v1.addUniqueNeighbor( this.v2 );
|
|
|
- this.v1.addUniqueNeighbor( this.v3 );
|
|
|
-
|
|
|
- this.v2.addUniqueNeighbor( this.v1 );
|
|
|
- this.v2.addUniqueNeighbor( this.v3 );
|
|
|
-
|
|
|
- this.v3.addUniqueNeighbor( this.v1 );
|
|
|
- this.v3.addUniqueNeighbor( this.v2 );
|
|
|
-
|
|
|
- this.computeNormal();
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
-}
|
|
|
-
|
|
|
-class Vertex {
|
|
|
-
|
|
|
- constructor( v, uv, normal, tangent, color ) {
|
|
|
-
|
|
|
- this.position = v;
|
|
|
- this.uv = uv;
|
|
|
- this.normal = normal;
|
|
|
- this.tangent = tangent;
|
|
|
- this.color = color;
|
|
|
-
|
|
|
- this.id = - 1; // external use position in vertices list (for e.g. face generation)
|
|
|
-
|
|
|
- this.faces = []; // faces vertex is connected
|
|
|
- this.neighbors = []; // neighbouring vertices aka "adjacentVertices"
|
|
|
-
|
|
|
- // these will be computed in computeEdgeCostAtVertex()
|
|
|
- this.collapseCost = 0; // cost of collapsing this vertex, the less the better. aka objdist
|
|
|
- this.collapseNeighbor = null; // best candidate for collapsing
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- addUniqueNeighbor( vertex ) {
|
|
|
-
|
|
|
- pushIfUnique( this.neighbors, vertex );
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- removeIfNonNeighbor( n ) {
|
|
|
-
|
|
|
- const neighbors = this.neighbors;
|
|
|
- const faces = this.faces;
|
|
|
-
|
|
|
- const offset = neighbors.indexOf( n );
|
|
|
-
|
|
|
- if ( offset === - 1 ) return;
|
|
|
-
|
|
|
- for ( let i = 0; i < faces.length; i ++ ) {
|
|
|
-
|
|
|
- if ( faces[ i ].hasVertex( n ) ) return;
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- neighbors.splice( offset, 1 );
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
-}
|
|
|
-
|
|
|
export { SimplifyModifier };
|