import * as THREE from 'three'; import { ConvexHull } from 'three/addons/math/ConvexHull.js'; const {BufferGeometry, Float32BufferAttribute, Vector3} = THREE; export class ConvexGeometry extends BufferGeometry { constructor( points = [] ) { super(); this.convexHull = new ConvexHull(); this.setFromPoints(points); } setFromPoints(points) { const {convexHull} = this; convexHull.setFromPoints(points); // buffers const vertices = []; const normals = []; // generate vertices and normals const faces = convexHull.faces; for ( let i = 0; i < faces.length; i ++ ) { const face = faces[ i ]; let edge = face.edge; // we move along a doubly-connected edge list to access all face points (see HalfEdge docs) do { const point = edge.head().point; vertices.push( point.x, point.y, point.z ); normals.push( face.normal.x, face.normal.y, face.normal.z ); edge = edge.next; } while ( edge !== face.edge ); } // build geometry this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); } } export class WireframeGeometry extends BufferGeometry { constructor( geometry = null ) { super(); this.type = 'WireframeGeometry'; this.parameters = { geometry: geometry }; this.update(); } update() { const {geometry} = this.parameters; if ( geometry !== null ) { // buffer const vertices = []; const edges = new Set(); // helper variables const start = new Vector3(); const end = new Vector3(); if ( geometry.index !== null ) { // indexed BufferGeometry const position = geometry.attributes.position; const indices = geometry.index; let groups = geometry.groups; if ( groups.length === 0 ) { groups = [ { start: 0, count: indices.count, materialIndex: 0 } ]; } // create a data structure that contains all edges without duplicates for ( let o = 0, ol = groups.length; o < ol; ++ o ) { const group = groups[ o ]; const groupStart = group.start; const groupCount = group.count; for ( let i = groupStart, l = ( groupStart + groupCount ); i < l; i += 3 ) { for ( let j = 0; j < 3; j ++ ) { const index1 = indices.getX( i + j ); const index2 = indices.getX( i + ( j + 1 ) % 3 ); start.fromBufferAttribute( position, index1 ); end.fromBufferAttribute( position, index2 ); if ( isUniqueEdge( start, end, edges ) === true ) { vertices.push( start.x, start.y, start.z ); vertices.push( end.x, end.y, end.z ); } } } } } else { // non-indexed BufferGeometry const position = geometry.attributes.position; for ( let i = 0, l = ( position.count / 3 ); i < l; i ++ ) { for ( let j = 0; j < 3; j ++ ) { // three edges per triangle, an edge is represented as (index1, index2) // e.g. the first triangle has the following edges: (0,1),(1,2),(2,0) const index1 = 3 * i + j; const index2 = 3 * i + ( ( j + 1 ) % 3 ); start.fromBufferAttribute( position, index1 ); end.fromBufferAttribute( position, index2 ); if ( isUniqueEdge( start, end, edges ) === true ) { vertices.push( start.x, start.y, start.z ); vertices.push( end.x, end.y, end.z ); } } } } // build geometry this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); } } copy( source ) { super.copy( source ); this.parameters = Object.assign( {}, source.parameters ); return this; } } export function isUniqueEdge( start, end, edges ) { const hash1 = `${start.x},${start.y},${start.z}-${end.x},${end.y},${end.z}`; const hash2 = `${end.x},${end.y},${end.z}-${start.x},${start.y},${start.z}`; // coincident edge if ( edges.has( hash1 ) === true || edges.has( hash2 ) === true ) { return false; } else { edges.add( hash1 ); edges.add( hash2 ); return true; } }