BSP.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. /*
  2. * BSP.js
  3. *
  4. * @author realor
  5. */
  6. import { GeometryUtils } from '../utils/GeometryUtils.js'
  7. import { SolidGeometry } from '../core/SolidGeometry.js'
  8. import * as THREE from '../lib/three.module.js'
  9. class BSP {
  10. constructor() {
  11. this.plane = null // divider plane
  12. this.coplanarPolygons = []
  13. this.frontBSP = null // front BSP
  14. this.backBSP = null // back BSP
  15. }
  16. fromSolidGeometry(geometry, matrix) {
  17. let vertices = geometry.vertices
  18. let faces = geometry.faces
  19. for (let face of faces) {
  20. if (face.holes.length === 0 && face.isConvex()) {
  21. // convex polygon without holes, direct copy
  22. let polygon = new Polygon()
  23. for (let v of face.indices) {
  24. let vertex = vertices[v].clone()
  25. if (matrix) {
  26. vertex.applyMatrix4(matrix)
  27. }
  28. polygon.vertices.push(vertex)
  29. }
  30. polygon.updateNormal()
  31. this.addPolygon(polygon)
  32. } else {
  33. // take triangulation for other cases
  34. let triangles = face.getTriangles()
  35. for (let triangle of triangles) {
  36. let polygon = new Polygon()
  37. for (let n = 0; n < 3; n++) {
  38. let vertex = vertices[triangle[n]].clone()
  39. if (matrix) {
  40. vertex.applyMatrix4(matrix)
  41. }
  42. polygon.vertices.push(vertex)
  43. }
  44. polygon.updateNormal()
  45. this.addPolygon(polygon)
  46. }
  47. }
  48. }
  49. }
  50. toSolidGeometry() {
  51. let polygons = this.getPolygons()
  52. let geometry = new SolidGeometry()
  53. for (let polygon of polygons) {
  54. let vertices = polygon.vertices.map(vertex => vertex.clone())
  55. let face = geometry.addFace(...vertices)
  56. if (polygon.normal) {
  57. face.normal = polygon.normal.clone()
  58. } else {
  59. face.updateNormal()
  60. }
  61. }
  62. return geometry
  63. }
  64. addPolygon(polygon) {
  65. if (this.plane === null) {
  66. let vertex0 = polygon.vertices[0]
  67. if (polygon.normal === null) polygon.updateNormal()
  68. this.plane = new THREE.Plane()
  69. this.plane.setFromNormalAndCoplanarPoint(polygon.normal, vertex0)
  70. this.coplanarPolygons.push(polygon)
  71. } else {
  72. let result = this.splitPolygon(polygon)
  73. if (result.frontPolygon) {
  74. if (this.frontBSP === null) this.frontBSP = new BSP()
  75. this.frontBSP.addPolygon(result.frontPolygon)
  76. }
  77. if (result.backPolygon) {
  78. if (this.backBSP === null) this.backBSP = new BSP()
  79. this.backBSP.addPolygon(result.backPolygon)
  80. }
  81. if (result.coplanarFrontPolygon) {
  82. this.coplanarPolygons.push(result.coplanarFrontPolygon)
  83. }
  84. if (result.coplanarBackPolygon) {
  85. this.coplanarPolygons.push(result.coplanarBackPolygon)
  86. }
  87. }
  88. return this
  89. }
  90. addPolygons(polygons) {
  91. for (let i = 0; i < polygons.length; i++) {
  92. this.addPolygon(polygons[i])
  93. }
  94. return this
  95. }
  96. /**
  97. * Removes the polygons from this bsp that are inside the given bsp
  98. * @param bsp the clipping bsp
  99. * @returns this bsp clipped by the given bsp
  100. */
  101. clip(bsp) {
  102. let insidePolygons = []
  103. let outsidePolygons = []
  104. bsp.classifyPolygons(this.coplanarPolygons, insidePolygons, outsidePolygons)
  105. this.coplanarPolygons = outsidePolygons // take only outside polygons
  106. if (this.frontBSP) {
  107. this.frontBSP.clip(bsp)
  108. }
  109. if (this.backBSP) {
  110. this.backBSP.clip(bsp)
  111. }
  112. return this
  113. }
  114. /**
  115. * Inverts this bsp tree
  116. * @returns this bsp inverted
  117. */
  118. invert() {
  119. if (this.plane === null) return this
  120. for (let i = 0; i < this.coplanarPolygons.length; i++) {
  121. this.coplanarPolygons[i].flip()
  122. }
  123. this.plane.negate()
  124. if (this.frontBSP) {
  125. this.frontBSP.invert()
  126. }
  127. if (this.backBSP) {
  128. this.backBSP.invert()
  129. }
  130. let tempBSP = this.frontBSP
  131. this.frontBSP = this.backBSP
  132. this.backBSP = tempBSP
  133. return this
  134. }
  135. /**
  136. * Performs a union between this bsp and the given bsp
  137. * @param bsp the other bsp
  138. * @returns this bsp
  139. */
  140. union(bsp) {
  141. // union = a | b
  142. let a = this
  143. let b = bsp
  144. // mutual clip
  145. a.clip(b)
  146. b.clip(a)
  147. // remove from b coplanar polygons
  148. b.invert()
  149. .clip(a)
  150. .invert()
  151. a.addPolygons(b.getPolygons())
  152. return a
  153. }
  154. /**
  155. * Performs a intersection between this bsp and the given bsp
  156. * @param bsp the other bsp
  157. * @returns this bsp
  158. */
  159. intersect(bsp) {
  160. // intersection = a & b = ~(~(a & b)) = ~(~a | ~b)
  161. let a = this
  162. let b = bsp
  163. a.invert()
  164. b.invert()
  165. a.clip(b)
  166. b.clip(a)
  167. b.invert()
  168. .clip(a)
  169. .invert()
  170. a.addPolygons(b.getPolygons()).invert()
  171. // Alternative method:
  172. //
  173. // a.invert();
  174. // b.clip(a);
  175. // b.invert();
  176. //
  177. // a.clip(b);
  178. // b.clip(a);
  179. //
  180. // a.addPolygons(b.getPolygons()).invert();
  181. return a
  182. }
  183. /**
  184. * Performs the subtraction between this bsp and the given bsp
  185. * @param bsp the other bsp
  186. * @returns this bsp
  187. */
  188. subtract(bsp) {
  189. // subtract = a - b = ~(~(a & ~b)) = ~(~a | b)
  190. let a = this
  191. let b = bsp
  192. a.invert()
  193. // mutual clip
  194. a.clip(b)
  195. b.clip(a)
  196. // remove from b coplanar polygons
  197. b.invert()
  198. .clip(a)
  199. .invert()
  200. a.addPolygons(b.getPolygons()).invert()
  201. return a
  202. }
  203. classifyPolygon(polygon, insidePolygons, outsidePolygons) {
  204. if (this.plane === null) return
  205. let result = this.splitPolygon(polygon)
  206. let frontPolygon = result.frontPolygon || result.coplanarFrontPolygon
  207. let backPolygon = result.backPolygon || result.coplanarBackPolygon
  208. let localInsidePolygons = []
  209. let localOutsidePolygons = []
  210. if (frontPolygon) {
  211. if (this.frontBSP) {
  212. this.frontBSP.classifyPolygon(frontPolygon, localInsidePolygons, localOutsidePolygons)
  213. } else {
  214. localOutsidePolygons.push(frontPolygon)
  215. }
  216. }
  217. if (backPolygon) {
  218. if (this.backBSP) {
  219. this.backBSP.classifyPolygon(backPolygon, localInsidePolygons, localOutsidePolygons)
  220. } else {
  221. localInsidePolygons.push(backPolygon)
  222. }
  223. }
  224. if (localInsidePolygons.length > 0 && localOutsidePolygons.length > 0) {
  225. insidePolygons.push(...localInsidePolygons)
  226. outsidePolygons.push(...localOutsidePolygons)
  227. } else if (localInsidePolygons.length > 0) {
  228. insidePolygons.push(polygon)
  229. } else if (localOutsidePolygons.length > 0) {
  230. outsidePolygons.push(polygon)
  231. }
  232. }
  233. classifyPolygons(polygons, insidePolygons, outsidePolygons) {
  234. for (let i = 0; i < polygons.length; i++) {
  235. polygons[i].id = i
  236. this.classifyPolygon(polygons[i], insidePolygons, outsidePolygons)
  237. }
  238. }
  239. splitPolygon(polygon, epsilon = 0.00001) {
  240. let result = {
  241. frontPolygon: null,
  242. backPolygon: null,
  243. coplanarFrontPolygon: null,
  244. coplanarBackPolygon: null
  245. }
  246. let plane = this.plane
  247. let frontCount = 0
  248. let backCount = 0
  249. const pos = []
  250. for (let i = 0; i < polygon.vertices.length; i++) {
  251. let vertex = polygon.vertices[i]
  252. let d = plane.distanceToPoint(vertex)
  253. if (Math.abs(d) < epsilon) d = 0
  254. if (d > 0) frontCount++
  255. else if (d < 0) backCount++
  256. pos.push(Math.sign(d))
  257. }
  258. if (frontCount === 0 && backCount === 0) {
  259. if (plane.normal.dot(polygon.normal) > 0) {
  260. result.coplanarFrontPolygon = polygon
  261. } else {
  262. result.coplanarBackPolygon = polygon
  263. }
  264. } else if (frontCount > 0 && backCount === 0) {
  265. result.frontPolygon = polygon
  266. } else if (backCount > 0 && frontCount === 0) {
  267. result.backPolygon = polygon
  268. } else {
  269. let frontPolygon = new Polygon()
  270. let backPolygon = new Polygon()
  271. for (let i = 0; i < polygon.vertices.length; i++) {
  272. let j = (i + 1) % polygon.vertices.length
  273. let vi = polygon.vertices[i]
  274. let vj = polygon.vertices[j]
  275. let pi = pos[i]
  276. let pj = pos[j]
  277. if (pi >= 0) {
  278. frontPolygon.addVertex(vi)
  279. }
  280. if (pi <= 0) {
  281. backPolygon.addVertex(vi)
  282. }
  283. if (pi !== pj && pi + pj === 0) {
  284. let vij = GeometryUtils.intersectLinePlane(vi, vj, plane)
  285. frontPolygon.addVertex(vij)
  286. backPolygon.addVertex(vij)
  287. }
  288. }
  289. if (frontPolygon.vertices.length >= 3) {
  290. // ??
  291. frontPolygon.updateNormal()
  292. result.frontPolygon = frontPolygon
  293. }
  294. if (backPolygon.vertices.length >= 3) {
  295. // ??
  296. backPolygon.updateNormal()
  297. result.backPolygon = backPolygon
  298. }
  299. }
  300. return result
  301. }
  302. getPolygons(polygons = []) {
  303. for (let i = 0; i < this.coplanarPolygons.length; i++) {
  304. polygons.push(this.coplanarPolygons[i])
  305. }
  306. if (this.frontBSP) {
  307. this.frontBSP.getPolygons(polygons)
  308. }
  309. if (this.backBSP) {
  310. this.backBSP.getPolygons(polygons)
  311. }
  312. return polygons
  313. }
  314. }
  315. class Polygon {
  316. constructor() {
  317. this.vertices = []
  318. this.normal = null
  319. }
  320. addVertex(vertex) {
  321. this.vertices.push(vertex)
  322. }
  323. updateNormal() {
  324. this.normal = GeometryUtils.calculateNormal(this.vertices)
  325. }
  326. flip() {
  327. this.normal.negate()
  328. this.vertices = this.vertices.reverse()
  329. }
  330. }
  331. export { BSP }