123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401 |
- /**
- * GeometryUtils.js
- *
- * @author realor
- */
- import * as THREE from '../lib/three.module.js'
- class GeometryUtils {
- static PRECISION = 0.00001
- static _vector1 = new THREE.Vector3()
- static _vector2 = new THREE.Vector3()
- static _vector3 = new THREE.Vector3()
- static _vector4 = new THREE.Vector3()
- static _vector5 = new THREE.Vector3()
- static _plane1 = new THREE.Plane()
- static isPointOnSegment(point, pointA, pointB, distance = 0.0001) {
- const projectedPoint = this._vector1
- if (this.projectPointOnSegment(point, pointA, pointB, projectedPoint)) {
- return projectedPoint.distanceToSquared(point) < distance * distance
- }
- return false
- }
- static projectPointOnSegment(point, pointA, pointB, projectedPoint) {
- const vAB = this._vector2
- const vAP = this._vector3
- const vProjAB = this._vector4
- vAB.subVectors(pointB, pointA)
- vAP.subVectors(point, pointA)
- const denominator = vAB.lengthSq()
- if (denominator === 0) return null
- const scalar = vAB.dot(vAP) / denominator
- if (scalar >= 0 && scalar <= 1) {
- vProjAB.copy(vAB).multiplyScalar(scalar)
- if (!(projectedPoint instanceof THREE.Vector3)) {
- projectedPoint = new THREE.Vector3()
- }
- projectedPoint.copy(pointA).add(vProjAB)
- return projectedPoint
- }
- return null
- }
- static intersectLines(line1, line2, position1, position2) {
- const vector1 = GeometryUtils._vector1
- const vector2 = GeometryUtils._vector2
- const normal = GeometryUtils._vector5
- const plane = GeometryUtils._plane1
- position1 = position1 || GeometryUtils._vector3
- position2 = position2 || GeometryUtils._vector4
- vector1.subVectors(line1.end, line1.start).normalize()
- vector2.subVectors(line2.end, line2.start).normalize()
- if (Math.abs(vector1.dot(vector2)) < 0.9999) {
- // are not parallel
- normal
- .copy(vector1)
- .cross(vector2)
- .normalize()
- vector1.cross(normal).normalize()
- plane.setFromNormalAndCoplanarPoint(vector1, line1.start)
- if (plane.intersectLine(line2, position2)) {
- vector2.cross(normal).normalize()
- plane.setFromNormalAndCoplanarPoint(vector2, line2.start)
- if (plane.intersectLine(line1, position1)) {
- return position1.distanceTo(position2)
- }
- }
- }
- return -1
- }
- static centroid(vertexPositions, accessFn, centroid) {
- if (!(centroid instanceof THREE.Vector3)) centroid = new THREE.Vector3()
- else centroid.set(0, 0, 0)
- const count = vertexPositions.length
- let point
- for (let i = 0; i < count; i++) {
- if (accessFn) {
- point = accessFn(vertexPositions[i])
- } else {
- point = vertexPositions[i]
- }
- centroid.x += point.x
- centroid.y += point.y
- centroid.z += point.z
- }
- centroid.x /= count
- centroid.y /= count
- centroid.z /= count
- return centroid
- }
- static calculateNormal(vertexPositions, accessFn, normal) {
- if (!(normal instanceof THREE.Vector3)) normal = new THREE.Vector3()
- else normal.set(0, 0, 0)
- // Newell's method
- const count = vertexPositions.length
- let pi, pj
- for (let i = 0; i < count; i++) {
- let j = (i + 1) % count
- if (accessFn) {
- pi = accessFn(vertexPositions[i])
- pj = accessFn(vertexPositions[j])
- } else {
- pi = vertexPositions[i]
- pj = vertexPositions[j]
- }
- normal.x += (pi.y - pj.y) * (pi.z + pj.z)
- normal.y += (pi.z - pj.z) * (pi.x + pj.x)
- normal.z += (pi.x - pj.x) * (pi.y + pj.y)
- }
- normal.normalize()
- return normal
- }
- /* triangulate a 3D face */
- static triangulateFace(vertices, holes, normal) {
- const vx = GeometryUtils._vector1
- const vy = GeometryUtils._vector2
- const vz = GeometryUtils._vector3
- if (normal instanceof THREE.Vector3) {
- vz.copy(normal)
- } else {
- GeometryUtils.calculateNormal(vertices, undefined, vz)
- }
- const v0 = vertices[0]
- const v1 = vertices[1]
- vx.subVectors(v1, v0).normalize()
- vy.crossVectors(vz, vx)
- const matrix = new THREE.Matrix4()
- matrix.set(vx.x, vy.x, vz.x, v0.x, vx.y, vy.y, vz.y, v0.y, vx.z, vy.z, vz.z, v0.z, 0, 0, 0, 1).invert()
- const projectVertices = vertices => {
- let projectedVertices = []
- for (let vertex of vertices) {
- let point = new THREE.Vector3()
- point.copy(vertex)
- projectedVertices.push(point.applyMatrix4(matrix))
- }
- return projectedVertices
- }
- let projectedVertices = projectVertices(vertices)
- let projectedHoles = []
- for (let hole of holes) {
- projectedHoles.push(projectVertices(hole))
- }
- return THREE.ShapeUtils.triangulateShape(projectedVertices, projectedHoles)
- }
- static intersectLinePlane(v1, v2, plane) {
- let v21 = v2.clone().sub(v1)
- let t = -(plane.normal.dot(v1) + plane.constant) / plane.normal.dot(v21)
- return v21.multiplyScalar(t).add(v1)
- }
- static orthogonalVector(vector, orthoVector) {
- if (!(orthoVector instanceof THREE.Vector3)) {
- orthoVector = new THREE.Vector3()
- }
- if (Math.abs(vector.x) > 0.1) {
- orthoVector.set(vector.y, -vector.x, vector.z)
- } else if (Math.abs(vector.y) > 0.1) {
- orthoVector.set(-vector.y, vector.x, vector.z)
- } // (~0, ~0, z)
- else {
- orthoVector.set(-vector.z, vector.y, vector.x)
- }
- return orthoVector.cross(vector)
- }
- static traverseBufferGeometryVertices(geometry, callback) {
- const position = this._vector1
- const positions = geometry.attributes.position.array
- for (let i = 0; i < positions.length; i += 3) {
- position.x = positions[i]
- position.y = positions[i + 1]
- position.z = positions[i + 2]
- callback(position)
- }
- }
- static getBufferGeometryVertices(geometry) {
- const positions = geometry.attributes.position.array
- const vertices = []
- for (let i = 0; i < positions.length; i += 3) {
- let x = positions[i]
- let y = positions[i + 1]
- let z = positions[i + 2]
- vertices.push(new THREE.Vector3(x, y, z))
- }
- return vertices
- }
- static getBufferGeometryFaces(geometry, addFace) {
- const positions = geometry.attributes.position.array
- if (geometry.index) {
- // indexed geometry
- let indices = geometry.index.array
- for (let i = 0; i < indices.length; i += 3) {
- let va = indices[i]
- let vb = indices[i + 1]
- let vc = indices[i + 2]
- addFace(va, vb, vc)
- }
- } // non indexed geometry
- else {
- var vertexCount = positions.length / 3
- for (let i = 0; i < vertexCount; i += 3) {
- let va = i
- let vb = i + 1
- let vc = i + 2
- addFace(va, vb, vc)
- }
- }
- }
- /**
- * Simplified version of BufferGeometryUtils.mergeBufferGeometries
- *
- * @param {Array<BufferGeometry>} geometries
- * @param {Boolean} useGroups
- * @return {BufferAttribute}
- */
- static mergeBufferGeometries(geometries, useGroups = false) {
- const isIndexed = geometries[0].index !== null
- const attributesUsed = new Set(Object.keys(geometries[0].attributes))
- const attributes = {}
- const mergedGeometry = new THREE.BufferGeometry()
- let offset = 0
- for (let i = 0; i < geometries.length; ++i) {
- const geometry = geometries[i]
- let attributesCount = 0
- // ensure that all geometries are indexed, or none
- if (isIndexed !== (geometry.index !== null)) {
- console.error('Not common attributes')
- return null
- }
- // gather attributes, exit early if they're different
- for (const name in geometry.attributes) {
- if (!attributesUsed.has(name)) {
- console.error('Not common attributes')
- return null
- }
- if (attributes[name] === undefined) {
- attributes[name] = []
- }
- attributes[name].push(geometry.attributes[name])
- attributesCount++
- }
- // ensure geometries have the same number of attributes
- if (attributesCount !== attributesUsed.size) {
- console.error('Not all geometries have the same number of attributes.')
- return null
- }
- // gather .userData
- mergedGeometry.userData.mergedUserData = mergedGeometry.userData.mergedUserData || []
- mergedGeometry.userData.mergedUserData.push(geometry.userData)
- if (useGroups) {
- let count
- if (isIndexed) {
- count = geometry.index.count
- } else if (geometry.attributes.position !== undefined) {
- count = geometry.attributes.position.count
- } else {
- console.error('Geometry has not an index or a position attribute')
- return null
- }
- mergedGeometry.addGroup(offset, count, i)
- offset += count
- }
- }
- // merge indices
- if (isIndexed) {
- let indexOffset = 0
- const mergedIndex = []
- for (let i = 0; i < geometries.length; ++i) {
- const index = geometries[i].index
- for (let j = 0; j < index.count; ++j) {
- mergedIndex.push(index.getX(j) + indexOffset)
- }
- indexOffset += geometries[i].attributes.position.count
- }
- mergedGeometry.setIndex(mergedIndex)
- }
- // merge attributes
- for (const name in attributes) {
- const mergedAttribute = this.mergeBufferAttributes(attributes[name])
- if (!mergedAttribute) {
- console.error('Failed while merging the ' + name + ' attribute.')
- return null
- }
- mergedGeometry.setAttribute(name, mergedAttribute)
- }
- return mergedGeometry
- }
- /**
- * @param {Array<BufferAttribute>} attributes
- * @return {BufferAttribute}
- */
- static mergeBufferAttributes(attributes) {
- let TypedArray
- let itemSize
- let normalized
- let arrayLength = 0
- for (let i = 0; i < attributes.length; ++i) {
- const attribute = attributes[i]
- if (attribute.isInterleavedBufferAttribute) {
- console.error('InterleavedBufferAttributes are not supported.')
- return null
- }
- if (TypedArray === undefined) {
- TypedArray = attribute.array.constructor
- }
- if (TypedArray !== attribute.array.constructor) {
- console.error('BufferAttribute.array is not consistent.')
- return null
- }
- if (itemSize === undefined) {
- itemSize = attribute.itemSize
- }
- if (itemSize !== attribute.itemSize) {
- console.error('BufferAttribute.itemSize is not consistent.')
- return null
- }
- if (normalized === undefined) {
- normalized = attribute.normalized
- }
- if (normalized !== attribute.normalized) {
- console.error('BufferAttribute.normalized is not consistent.')
- return null
- }
- arrayLength += attribute.array.length
- }
- const array = new TypedArray(arrayLength)
- let offset = 0
- for (let i = 0; i < attributes.length; ++i) {
- array.set(attributes[i].array, offset)
- offset += attributes[i].array.length
- }
- return new THREE.BufferAttribute(array, itemSize, normalized)
- }
- }
- export { GeometryUtils }
|