123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395 |
- /*
- * BSP.js
- *
- * @author realor
- */
- import { GeometryUtils } from '../utils/GeometryUtils.js'
- import { SolidGeometry } from '../core/SolidGeometry.js'
- import * as THREE from '../lib/three.module.js'
- class BSP {
- constructor() {
- this.plane = null // divider plane
- this.coplanarPolygons = []
- this.frontBSP = null // front BSP
- this.backBSP = null // back BSP
- }
- fromSolidGeometry(geometry, matrix) {
- let vertices = geometry.vertices
- let faces = geometry.faces
- for (let face of faces) {
- if (face.holes.length === 0 && face.isConvex()) {
- // convex polygon without holes, direct copy
- let polygon = new Polygon()
- for (let v of face.indices) {
- let vertex = vertices[v].clone()
- if (matrix) {
- vertex.applyMatrix4(matrix)
- }
- polygon.vertices.push(vertex)
- }
- polygon.updateNormal()
- this.addPolygon(polygon)
- } else {
- // take triangulation for other cases
- let triangles = face.getTriangles()
- for (let triangle of triangles) {
- let polygon = new Polygon()
- for (let n = 0; n < 3; n++) {
- let vertex = vertices[triangle[n]].clone()
- if (matrix) {
- vertex.applyMatrix4(matrix)
- }
- polygon.vertices.push(vertex)
- }
- polygon.updateNormal()
- this.addPolygon(polygon)
- }
- }
- }
- }
- toSolidGeometry() {
- let polygons = this.getPolygons()
- let geometry = new SolidGeometry()
- for (let polygon of polygons) {
- let vertices = polygon.vertices.map(vertex => vertex.clone())
- let face = geometry.addFace(...vertices)
- if (polygon.normal) {
- face.normal = polygon.normal.clone()
- } else {
- face.updateNormal()
- }
- }
- return geometry
- }
- addPolygon(polygon) {
- if (this.plane === null) {
- let vertex0 = polygon.vertices[0]
- if (polygon.normal === null) polygon.updateNormal()
- this.plane = new THREE.Plane()
- this.plane.setFromNormalAndCoplanarPoint(polygon.normal, vertex0)
- this.coplanarPolygons.push(polygon)
- } else {
- let result = this.splitPolygon(polygon)
- if (result.frontPolygon) {
- if (this.frontBSP === null) this.frontBSP = new BSP()
- this.frontBSP.addPolygon(result.frontPolygon)
- }
- if (result.backPolygon) {
- if (this.backBSP === null) this.backBSP = new BSP()
- this.backBSP.addPolygon(result.backPolygon)
- }
- if (result.coplanarFrontPolygon) {
- this.coplanarPolygons.push(result.coplanarFrontPolygon)
- }
- if (result.coplanarBackPolygon) {
- this.coplanarPolygons.push(result.coplanarBackPolygon)
- }
- }
- return this
- }
- addPolygons(polygons) {
- for (let i = 0; i < polygons.length; i++) {
- this.addPolygon(polygons[i])
- }
- return this
- }
- /**
- * Removes the polygons from this bsp that are inside the given bsp
- * @param bsp the clipping bsp
- * @returns this bsp clipped by the given bsp
- */
- clip(bsp) {
- let insidePolygons = []
- let outsidePolygons = []
- bsp.classifyPolygons(this.coplanarPolygons, insidePolygons, outsidePolygons)
- this.coplanarPolygons = outsidePolygons // take only outside polygons
- if (this.frontBSP) {
- this.frontBSP.clip(bsp)
- }
- if (this.backBSP) {
- this.backBSP.clip(bsp)
- }
- return this
- }
- /**
- * Inverts this bsp tree
- * @returns this bsp inverted
- */
- invert() {
- if (this.plane === null) return this
- for (let i = 0; i < this.coplanarPolygons.length; i++) {
- this.coplanarPolygons[i].flip()
- }
- this.plane.negate()
- if (this.frontBSP) {
- this.frontBSP.invert()
- }
- if (this.backBSP) {
- this.backBSP.invert()
- }
- let tempBSP = this.frontBSP
- this.frontBSP = this.backBSP
- this.backBSP = tempBSP
- return this
- }
- /**
- * Performs a union between this bsp and the given bsp
- * @param bsp the other bsp
- * @returns this bsp
- */
- union(bsp) {
- // union = a | b
- let a = this
- let b = bsp
- // mutual clip
- a.clip(b)
- b.clip(a)
- // remove from b coplanar polygons
- b.invert()
- .clip(a)
- .invert()
- a.addPolygons(b.getPolygons())
- return a
- }
- /**
- * Performs a intersection between this bsp and the given bsp
- * @param bsp the other bsp
- * @returns this bsp
- */
- intersect(bsp) {
- // intersection = a & b = ~(~(a & b)) = ~(~a | ~b)
- let a = this
- let b = bsp
- a.invert()
- b.invert()
- a.clip(b)
- b.clip(a)
- b.invert()
- .clip(a)
- .invert()
- a.addPolygons(b.getPolygons()).invert()
- // Alternative method:
- //
- // a.invert();
- // b.clip(a);
- // b.invert();
- //
- // a.clip(b);
- // b.clip(a);
- //
- // a.addPolygons(b.getPolygons()).invert();
- return a
- }
- /**
- * Performs the subtraction between this bsp and the given bsp
- * @param bsp the other bsp
- * @returns this bsp
- */
- subtract(bsp) {
- // subtract = a - b = ~(~(a & ~b)) = ~(~a | b)
- let a = this
- let b = bsp
- a.invert()
- // mutual clip
- a.clip(b)
- b.clip(a)
- // remove from b coplanar polygons
- b.invert()
- .clip(a)
- .invert()
- a.addPolygons(b.getPolygons()).invert()
- return a
- }
- classifyPolygon(polygon, insidePolygons, outsidePolygons) {
- if (this.plane === null) return
- let result = this.splitPolygon(polygon)
- let frontPolygon = result.frontPolygon || result.coplanarFrontPolygon
- let backPolygon = result.backPolygon || result.coplanarBackPolygon
- let localInsidePolygons = []
- let localOutsidePolygons = []
- if (frontPolygon) {
- if (this.frontBSP) {
- this.frontBSP.classifyPolygon(frontPolygon, localInsidePolygons, localOutsidePolygons)
- } else {
- localOutsidePolygons.push(frontPolygon)
- }
- }
- if (backPolygon) {
- if (this.backBSP) {
- this.backBSP.classifyPolygon(backPolygon, localInsidePolygons, localOutsidePolygons)
- } else {
- localInsidePolygons.push(backPolygon)
- }
- }
- if (localInsidePolygons.length > 0 && localOutsidePolygons.length > 0) {
- insidePolygons.push(...localInsidePolygons)
- outsidePolygons.push(...localOutsidePolygons)
- } else if (localInsidePolygons.length > 0) {
- insidePolygons.push(polygon)
- } else if (localOutsidePolygons.length > 0) {
- outsidePolygons.push(polygon)
- }
- }
- classifyPolygons(polygons, insidePolygons, outsidePolygons) {
- for (let i = 0; i < polygons.length; i++) {
- polygons[i].id = i
- this.classifyPolygon(polygons[i], insidePolygons, outsidePolygons)
- }
- }
- splitPolygon(polygon, epsilon = 0.00001) {
- let result = {
- frontPolygon: null,
- backPolygon: null,
- coplanarFrontPolygon: null,
- coplanarBackPolygon: null
- }
- let plane = this.plane
- let frontCount = 0
- let backCount = 0
- const pos = []
- for (let i = 0; i < polygon.vertices.length; i++) {
- let vertex = polygon.vertices[i]
- let d = plane.distanceToPoint(vertex)
- if (Math.abs(d) < epsilon) d = 0
- if (d > 0) frontCount++
- else if (d < 0) backCount++
- pos.push(Math.sign(d))
- }
- if (frontCount === 0 && backCount === 0) {
- if (plane.normal.dot(polygon.normal) > 0) {
- result.coplanarFrontPolygon = polygon
- } else {
- result.coplanarBackPolygon = polygon
- }
- } else if (frontCount > 0 && backCount === 0) {
- result.frontPolygon = polygon
- } else if (backCount > 0 && frontCount === 0) {
- result.backPolygon = polygon
- } else {
- let frontPolygon = new Polygon()
- let backPolygon = new Polygon()
- for (let i = 0; i < polygon.vertices.length; i++) {
- let j = (i + 1) % polygon.vertices.length
- let vi = polygon.vertices[i]
- let vj = polygon.vertices[j]
- let pi = pos[i]
- let pj = pos[j]
- if (pi >= 0) {
- frontPolygon.addVertex(vi)
- }
- if (pi <= 0) {
- backPolygon.addVertex(vi)
- }
- if (pi !== pj && pi + pj === 0) {
- let vij = GeometryUtils.intersectLinePlane(vi, vj, plane)
- frontPolygon.addVertex(vij)
- backPolygon.addVertex(vij)
- }
- }
- if (frontPolygon.vertices.length >= 3) {
- // ??
- frontPolygon.updateNormal()
- result.frontPolygon = frontPolygon
- }
- if (backPolygon.vertices.length >= 3) {
- // ??
- backPolygon.updateNormal()
- result.backPolygon = backPolygon
- }
- }
- return result
- }
- getPolygons(polygons = []) {
- for (let i = 0; i < this.coplanarPolygons.length; i++) {
- polygons.push(this.coplanarPolygons[i])
- }
- if (this.frontBSP) {
- this.frontBSP.getPolygons(polygons)
- }
- if (this.backBSP) {
- this.backBSP.getPolygons(polygons)
- }
- return polygons
- }
- }
- class Polygon {
- constructor() {
- this.vertices = []
- this.normal = null
- }
- addVertex(vertex) {
- this.vertices.push(vertex)
- }
- updateNormal() {
- this.normal = GeometryUtils.calculateNormal(this.vertices)
- }
- flip() {
- this.normal.negate()
- this.vertices = this.vertices.reverse()
- }
- }
- export { BSP }
|