├── LICENSE ├── ThreeCSG.js ├── examples.html ├── texture.png └── threeCSG.es6 /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2012 Chandler Prall. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /ThreeCSG.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | window.ThreeBSP = (function() { 3 | 4 | var ThreeBSP, 5 | EPSILON = 1e-5, 6 | COPLANAR = 0, 7 | FRONT = 1, 8 | BACK = 2, 9 | SPANNING = 3; 10 | 11 | ThreeBSP = function( geometry ) { 12 | // Convert THREE.Geometry to ThreeBSP 13 | var i, _length_i, 14 | face, vertex, faceVertexUvs, uvs, 15 | polygon, 16 | polygons = [], 17 | tree; 18 | 19 | if ( geometry instanceof THREE.Geometry ) { 20 | this.matrix = new THREE.Matrix4; 21 | } else if ( geometry instanceof THREE.Mesh ) { 22 | // #todo: add hierarchy support 23 | geometry.updateMatrix(); 24 | this.matrix = geometry.matrix.clone(); 25 | geometry = geometry.geometry; 26 | } else if ( geometry instanceof ThreeBSP.Node ) { 27 | this.tree = geometry; 28 | this.matrix = new THREE.Matrix4; 29 | return this; 30 | } else { 31 | throw 'ThreeBSP: Given geometry is unsupported'; 32 | } 33 | 34 | for ( i = 0, _length_i = geometry.faces.length; i < _length_i; i++ ) { 35 | face = geometry.faces[i]; 36 | faceVertexUvs = geometry.faceVertexUvs[0][i]; 37 | polygon = new ThreeBSP.Polygon; 38 | 39 | if ( face instanceof THREE.Face3 ) { 40 | vertex = geometry.vertices[ face.a ]; 41 | uvs = faceVertexUvs ? new THREE.Vector2( faceVertexUvs[0].x, faceVertexUvs[0].y ) : null; 42 | vertex = new ThreeBSP.Vertex( vertex.x, vertex.y, vertex.z, face.vertexNormals[0], uvs ); 43 | vertex.applyMatrix4(this.matrix); 44 | polygon.vertices.push( vertex ); 45 | 46 | vertex = geometry.vertices[ face.b ]; 47 | uvs = faceVertexUvs ? new THREE.Vector2( faceVertexUvs[1].x, faceVertexUvs[1].y ) : null; 48 | vertex = new ThreeBSP.Vertex( vertex.x, vertex.y, vertex.z, face.vertexNormals[1], uvs ); 49 | vertex.applyMatrix4(this.matrix); 50 | polygon.vertices.push( vertex ); 51 | 52 | vertex = geometry.vertices[ face.c ]; 53 | uvs = faceVertexUvs ? new THREE.Vector2( faceVertexUvs[2].x, faceVertexUvs[2].y ) : null; 54 | vertex = new ThreeBSP.Vertex( vertex.x, vertex.y, vertex.z, face.vertexNormals[2], uvs ); 55 | vertex.applyMatrix4(this.matrix); 56 | polygon.vertices.push( vertex ); 57 | } else if ( typeof THREE.Face4 ) { 58 | vertex = geometry.vertices[ face.a ]; 59 | uvs = faceVertexUvs ? new THREE.Vector2( faceVertexUvs[0].x, faceVertexUvs[0].y ) : null; 60 | vertex = new ThreeBSP.Vertex( vertex.x, vertex.y, vertex.z, face.vertexNormals[0], uvs ); 61 | vertex.applyMatrix4(this.matrix); 62 | polygon.vertices.push( vertex ); 63 | 64 | vertex = geometry.vertices[ face.b ]; 65 | uvs = faceVertexUvs ? new THREE.Vector2( faceVertexUvs[1].x, faceVertexUvs[1].y ) : null; 66 | vertex = new ThreeBSP.Vertex( vertex.x, vertex.y, vertex.z, face.vertexNormals[1], uvs ); 67 | vertex.applyMatrix4(this.matrix); 68 | polygon.vertices.push( vertex ); 69 | 70 | vertex = geometry.vertices[ face.c ]; 71 | uvs = faceVertexUvs ? new THREE.Vector2( faceVertexUvs[2].x, faceVertexUvs[2].y ) : null; 72 | vertex = new ThreeBSP.Vertex( vertex.x, vertex.y, vertex.z, face.vertexNormals[2], uvs ); 73 | vertex.applyMatrix4(this.matrix); 74 | polygon.vertices.push( vertex ); 75 | 76 | vertex = geometry.vertices[ face.d ]; 77 | uvs = faceVertexUvs ? new THREE.Vector2( faceVertexUvs[3].x, faceVertexUvs[3].y ) : null; 78 | vertex = new ThreeBSP.Vertex( vertex.x, vertex.y, vertex.z, face.vertexNormals[3], uvs ); 79 | vertex.applyMatrix4(this.matrix); 80 | polygon.vertices.push( vertex ); 81 | } else { 82 | throw 'Invalid face type at index ' + i; 83 | } 84 | 85 | polygon.calculateProperties(); 86 | polygons.push( polygon ); 87 | }; 88 | 89 | this.tree = new ThreeBSP.Node( polygons ); 90 | }; 91 | ThreeBSP.prototype.subtract = function( other_tree ) { 92 | var a = this.tree.clone(), 93 | b = other_tree.tree.clone(); 94 | 95 | a.invert(); 96 | a.clipTo( b ); 97 | b.clipTo( a ); 98 | b.invert(); 99 | b.clipTo( a ); 100 | b.invert(); 101 | a.build( b.allPolygons() ); 102 | a.invert(); 103 | a = new ThreeBSP( a ); 104 | a.matrix = this.matrix; 105 | return a; 106 | }; 107 | ThreeBSP.prototype.union = function( other_tree ) { 108 | var a = this.tree.clone(), 109 | b = other_tree.tree.clone(); 110 | 111 | a.clipTo( b ); 112 | b.clipTo( a ); 113 | b.invert(); 114 | b.clipTo( a ); 115 | b.invert(); 116 | a.build( b.allPolygons() ); 117 | a = new ThreeBSP( a ); 118 | a.matrix = this.matrix; 119 | return a; 120 | }; 121 | ThreeBSP.prototype.intersect = function( other_tree ) { 122 | var a = this.tree.clone(), 123 | b = other_tree.tree.clone(); 124 | 125 | a.invert(); 126 | b.clipTo( a ); 127 | b.invert(); 128 | a.clipTo( b ); 129 | b.clipTo( a ); 130 | a.build( b.allPolygons() ); 131 | a.invert(); 132 | a = new ThreeBSP( a ); 133 | a.matrix = this.matrix; 134 | return a; 135 | }; 136 | ThreeBSP.prototype.toGeometry = function() { 137 | var i, j, 138 | matrix = new THREE.Matrix4().getInverse( this.matrix ), 139 | geometry = new THREE.Geometry(), 140 | polygons = this.tree.allPolygons(), 141 | polygon_count = polygons.length, 142 | polygon, polygon_vertice_count, 143 | vertice_dict = {}, 144 | vertex_idx_a, vertex_idx_b, vertex_idx_c, 145 | vertex, face, 146 | verticeUvs; 147 | 148 | for ( i = 0; i < polygon_count; i++ ) { 149 | polygon = polygons[i]; 150 | polygon_vertice_count = polygon.vertices.length; 151 | 152 | for ( j = 2; j < polygon_vertice_count; j++ ) { 153 | verticeUvs = []; 154 | 155 | vertex = polygon.vertices[0]; 156 | verticeUvs.push( new THREE.Vector2( vertex.uv.x, vertex.uv.y ) ); 157 | vertex = new THREE.Vector3( vertex.x, vertex.y, vertex.z ); 158 | vertex.applyMatrix4(matrix); 159 | 160 | if ( typeof vertice_dict[ vertex.x + ',' + vertex.y + ',' + vertex.z ] !== 'undefined' ) { 161 | vertex_idx_a = vertice_dict[ vertex.x + ',' + vertex.y + ',' + vertex.z ]; 162 | } else { 163 | geometry.vertices.push( vertex ); 164 | vertex_idx_a = vertice_dict[ vertex.x + ',' + vertex.y + ',' + vertex.z ] = geometry.vertices.length - 1; 165 | } 166 | 167 | vertex = polygon.vertices[j-1]; 168 | verticeUvs.push( new THREE.Vector2( vertex.uv.x, vertex.uv.y ) ); 169 | vertex = new THREE.Vector3( vertex.x, vertex.y, vertex.z ); 170 | vertex.applyMatrix4(matrix); 171 | if ( typeof vertice_dict[ vertex.x + ',' + vertex.y + ',' + vertex.z ] !== 'undefined' ) { 172 | vertex_idx_b = vertice_dict[ vertex.x + ',' + vertex.y + ',' + vertex.z ]; 173 | } else { 174 | geometry.vertices.push( vertex ); 175 | vertex_idx_b = vertice_dict[ vertex.x + ',' + vertex.y + ',' + vertex.z ] = geometry.vertices.length - 1; 176 | } 177 | 178 | vertex = polygon.vertices[j]; 179 | verticeUvs.push( new THREE.Vector2( vertex.uv.x, vertex.uv.y ) ); 180 | vertex = new THREE.Vector3( vertex.x, vertex.y, vertex.z ); 181 | vertex.applyMatrix4(matrix); 182 | if ( typeof vertice_dict[ vertex.x + ',' + vertex.y + ',' + vertex.z ] !== 'undefined' ) { 183 | vertex_idx_c = vertice_dict[ vertex.x + ',' + vertex.y + ',' + vertex.z ]; 184 | } else { 185 | geometry.vertices.push( vertex ); 186 | vertex_idx_c = vertice_dict[ vertex.x + ',' + vertex.y + ',' + vertex.z ] = geometry.vertices.length - 1; 187 | } 188 | 189 | face = new THREE.Face3( 190 | vertex_idx_a, 191 | vertex_idx_b, 192 | vertex_idx_c, 193 | new THREE.Vector3( polygon.normal.x, polygon.normal.y, polygon.normal.z ) 194 | ); 195 | 196 | geometry.faces.push( face ); 197 | geometry.faceVertexUvs[0].push( verticeUvs ); 198 | } 199 | 200 | } 201 | return geometry; 202 | }; 203 | ThreeBSP.prototype.toMesh = function( material ) { 204 | var geometry = this.toGeometry(), 205 | mesh = new THREE.Mesh( geometry, material ); 206 | 207 | mesh.position.setFromMatrixPosition( this.matrix ); 208 | mesh.rotation.setFromRotationMatrix( this.matrix ); 209 | 210 | return mesh; 211 | }; 212 | 213 | 214 | ThreeBSP.Polygon = function( vertices, normal, w ) { 215 | if ( !( vertices instanceof Array ) ) { 216 | vertices = []; 217 | } 218 | 219 | this.vertices = vertices; 220 | if ( vertices.length > 0 ) { 221 | this.calculateProperties(); 222 | } else { 223 | this.normal = this.w = undefined; 224 | } 225 | }; 226 | ThreeBSP.Polygon.prototype.calculateProperties = function() { 227 | var a = this.vertices[0], 228 | b = this.vertices[1], 229 | c = this.vertices[2]; 230 | 231 | this.normal = b.clone().subtract( a ).cross( 232 | c.clone().subtract( a ) 233 | ).normalize(); 234 | 235 | this.w = this.normal.clone().dot( a ); 236 | 237 | return this; 238 | }; 239 | ThreeBSP.Polygon.prototype.clone = function() { 240 | var i, vertice_count, 241 | polygon = new ThreeBSP.Polygon; 242 | 243 | for ( i = 0, vertice_count = this.vertices.length; i < vertice_count; i++ ) { 244 | polygon.vertices.push( this.vertices[i].clone() ); 245 | }; 246 | polygon.calculateProperties(); 247 | 248 | return polygon; 249 | }; 250 | 251 | ThreeBSP.Polygon.prototype.flip = function() { 252 | var i, vertices = []; 253 | 254 | this.normal.multiplyScalar( -1 ); 255 | this.w *= -1; 256 | 257 | for ( i = this.vertices.length - 1; i >= 0; i-- ) { 258 | vertices.push( this.vertices[i] ); 259 | }; 260 | this.vertices = vertices; 261 | 262 | return this; 263 | }; 264 | ThreeBSP.Polygon.prototype.classifyVertex = function( vertex ) { 265 | var side_value = this.normal.dot( vertex ) - this.w; 266 | 267 | if ( side_value < -EPSILON ) { 268 | return BACK; 269 | } else if ( side_value > EPSILON ) { 270 | return FRONT; 271 | } else { 272 | return COPLANAR; 273 | } 274 | }; 275 | ThreeBSP.Polygon.prototype.classifySide = function( polygon ) { 276 | var i, vertex, classification, 277 | num_positive = 0, 278 | num_negative = 0, 279 | vertice_count = polygon.vertices.length; 280 | 281 | for ( i = 0; i < vertice_count; i++ ) { 282 | vertex = polygon.vertices[i]; 283 | classification = this.classifyVertex( vertex ); 284 | if ( classification === FRONT ) { 285 | num_positive++; 286 | } else if ( classification === BACK ) { 287 | num_negative++; 288 | } 289 | } 290 | 291 | if ( num_positive === vertice_count && num_negative === 0 ) { 292 | return FRONT; 293 | } else if ( num_positive === 0 && num_negative === vertice_count ) { 294 | return BACK; 295 | } else if ( num_positive > 0 && num_negative > 0 ) { 296 | return SPANNING; 297 | } else { 298 | return COPLANAR; 299 | } 300 | }; 301 | ThreeBSP.Polygon.prototype.splitPolygon = function( polygon, coplanar_front, coplanar_back, front, back ) { 302 | var classification = this.classifySide( polygon ); 303 | 304 | if ( classification === COPLANAR ) { 305 | 306 | ( this.normal.dot( polygon.normal ) > 0 ? coplanar_front : coplanar_back ).push( polygon ); 307 | 308 | } else if ( classification === FRONT ) { 309 | 310 | front.push( polygon ); 311 | 312 | } else if ( classification === BACK ) { 313 | 314 | back.push( polygon ); 315 | 316 | } else { 317 | 318 | var vertice_count, 319 | i, j, ti, tj, vi, vj, 320 | t, v, 321 | f = [], 322 | b = []; 323 | 324 | for ( i = 0, vertice_count = polygon.vertices.length; i < vertice_count; i++ ) { 325 | 326 | j = (i + 1) % vertice_count; 327 | vi = polygon.vertices[i]; 328 | vj = polygon.vertices[j]; 329 | ti = this.classifyVertex( vi ); 330 | tj = this.classifyVertex( vj ); 331 | 332 | if ( ti != BACK ) f.push( vi ); 333 | if ( ti != FRONT ) b.push( vi ); 334 | if ( (ti | tj) === SPANNING ) { 335 | t = ( this.w - this.normal.dot( vi ) ) / this.normal.dot( vj.clone().subtract( vi ) ); 336 | v = vi.interpolate( vj, t ); 337 | f.push( v ); 338 | b.push( v ); 339 | } 340 | } 341 | 342 | 343 | if ( f.length >= 3 ) front.push( new ThreeBSP.Polygon( f ).calculateProperties() ); 344 | if ( b.length >= 3 ) back.push( new ThreeBSP.Polygon( b ).calculateProperties() ); 345 | } 346 | }; 347 | 348 | ThreeBSP.Vertex = function( x, y, z, normal, uv ) { 349 | this.x = x; 350 | this.y = y; 351 | this.z = z; 352 | this.normal = normal || new THREE.Vector3; 353 | this.uv = uv || new THREE.Vector2; 354 | }; 355 | ThreeBSP.Vertex.prototype.clone = function() { 356 | return new ThreeBSP.Vertex( this.x, this.y, this.z, this.normal.clone(), this.uv.clone() ); 357 | }; 358 | ThreeBSP.Vertex.prototype.add = function( vertex ) { 359 | this.x += vertex.x; 360 | this.y += vertex.y; 361 | this.z += vertex.z; 362 | return this; 363 | }; 364 | ThreeBSP.Vertex.prototype.subtract = function( vertex ) { 365 | this.x -= vertex.x; 366 | this.y -= vertex.y; 367 | this.z -= vertex.z; 368 | return this; 369 | }; 370 | ThreeBSP.Vertex.prototype.multiplyScalar = function( scalar ) { 371 | this.x *= scalar; 372 | this.y *= scalar; 373 | this.z *= scalar; 374 | return this; 375 | }; 376 | ThreeBSP.Vertex.prototype.cross = function( vertex ) { 377 | var x = this.x, 378 | y = this.y, 379 | z = this.z; 380 | 381 | this.x = y * vertex.z - z * vertex.y; 382 | this.y = z * vertex.x - x * vertex.z; 383 | this.z = x * vertex.y - y * vertex.x; 384 | 385 | return this; 386 | }; 387 | ThreeBSP.Vertex.prototype.normalize = function() { 388 | var length = Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z ); 389 | 390 | this.x /= length; 391 | this.y /= length; 392 | this.z /= length; 393 | 394 | return this; 395 | }; 396 | ThreeBSP.Vertex.prototype.dot = function( vertex ) { 397 | return this.x * vertex.x + this.y * vertex.y + this.z * vertex.z; 398 | }; 399 | ThreeBSP.Vertex.prototype.lerp = function( a, t ) { 400 | this.add( 401 | a.clone().subtract( this ).multiplyScalar( t ) 402 | ); 403 | 404 | this.normal.add( 405 | a.normal.clone().sub( this.normal ).multiplyScalar( t ) 406 | ); 407 | 408 | this.uv.add( 409 | a.uv.clone().sub( this.uv ).multiplyScalar( t ) 410 | ); 411 | 412 | return this; 413 | }; 414 | ThreeBSP.Vertex.prototype.interpolate = function( other, t ) { 415 | return this.clone().lerp( other, t ); 416 | }; 417 | ThreeBSP.Vertex.prototype.applyMatrix4 = function ( m ) { 418 | 419 | // input: THREE.Matrix4 affine matrix 420 | 421 | var x = this.x, y = this.y, z = this.z; 422 | 423 | var e = m.elements; 424 | 425 | this.x = e[0] * x + e[4] * y + e[8] * z + e[12]; 426 | this.y = e[1] * x + e[5] * y + e[9] * z + e[13]; 427 | this.z = e[2] * x + e[6] * y + e[10] * z + e[14]; 428 | 429 | return this; 430 | 431 | } 432 | 433 | 434 | ThreeBSP.Node = function( polygons ) { 435 | var i, polygon_count, 436 | front = [], 437 | back = []; 438 | 439 | this.polygons = []; 440 | this.front = this.back = undefined; 441 | 442 | if ( !(polygons instanceof Array) || polygons.length === 0 ) return; 443 | 444 | this.divider = polygons[0].clone(); 445 | 446 | for ( i = 0, polygon_count = polygons.length; i < polygon_count; i++ ) { 447 | this.divider.splitPolygon( polygons[i], this.polygons, this.polygons, front, back ); 448 | } 449 | 450 | if ( front.length > 0 ) { 451 | this.front = new ThreeBSP.Node( front ); 452 | } 453 | 454 | if ( back.length > 0 ) { 455 | this.back = new ThreeBSP.Node( back ); 456 | } 457 | }; 458 | ThreeBSP.Node.isConvex = function( polygons ) { 459 | var i, j; 460 | for ( i = 0; i < polygons.length; i++ ) { 461 | for ( j = 0; j < polygons.length; j++ ) { 462 | if ( i !== j && polygons[i].classifySide( polygons[j] ) !== BACK ) { 463 | return false; 464 | } 465 | } 466 | } 467 | return true; 468 | }; 469 | ThreeBSP.Node.prototype.build = function( polygons ) { 470 | var i, polygon_count, 471 | front = [], 472 | back = []; 473 | 474 | if ( !this.divider ) { 475 | this.divider = polygons[0].clone(); 476 | } 477 | 478 | for ( i = 0, polygon_count = polygons.length; i < polygon_count; i++ ) { 479 | this.divider.splitPolygon( polygons[i], this.polygons, this.polygons, front, back ); 480 | } 481 | 482 | if ( front.length > 0 ) { 483 | if ( !this.front ) this.front = new ThreeBSP.Node(); 484 | this.front.build( front ); 485 | } 486 | 487 | if ( back.length > 0 ) { 488 | if ( !this.back ) this.back = new ThreeBSP.Node(); 489 | this.back.build( back ); 490 | } 491 | }; 492 | ThreeBSP.Node.prototype.allPolygons = function() { 493 | var polygons = this.polygons.slice(); 494 | if ( this.front ) polygons = polygons.concat( this.front.allPolygons() ); 495 | if ( this.back ) polygons = polygons.concat( this.back.allPolygons() ); 496 | return polygons; 497 | }; 498 | ThreeBSP.Node.prototype.clone = function() { 499 | var node = new ThreeBSP.Node(); 500 | 501 | node.divider = this.divider.clone(); 502 | node.polygons = this.polygons.map( function( polygon ) { return polygon.clone(); } ); 503 | node.front = this.front && this.front.clone(); 504 | node.back = this.back && this.back.clone(); 505 | 506 | return node; 507 | }; 508 | ThreeBSP.Node.prototype.invert = function() { 509 | var i, polygon_count, temp; 510 | 511 | for ( i = 0, polygon_count = this.polygons.length; i < polygon_count; i++ ) { 512 | this.polygons[i].flip(); 513 | } 514 | 515 | this.divider.flip(); 516 | if ( this.front ) this.front.invert(); 517 | if ( this.back ) this.back.invert(); 518 | 519 | temp = this.front; 520 | this.front = this.back; 521 | this.back = temp; 522 | 523 | return this; 524 | }; 525 | ThreeBSP.Node.prototype.clipPolygons = function( polygons ) { 526 | var i, polygon_count, 527 | front, back; 528 | 529 | if ( !this.divider ) return polygons.slice(); 530 | 531 | front = [], back = []; 532 | 533 | for ( i = 0, polygon_count = polygons.length; i < polygon_count; i++ ) { 534 | this.divider.splitPolygon( polygons[i], front, back, front, back ); 535 | } 536 | 537 | if ( this.front ) front = this.front.clipPolygons( front ); 538 | if ( this.back ) back = this.back.clipPolygons( back ); 539 | else back = []; 540 | 541 | return front.concat( back ); 542 | }; 543 | 544 | ThreeBSP.Node.prototype.clipTo = function( node ) { 545 | this.polygons = node.clipPolygons( this.polygons ); 546 | if ( this.front ) this.front.clipTo( node ); 547 | if ( this.back ) this.back.clipTo( node ); 548 | }; 549 | 550 | 551 | return ThreeBSP; 552 | })(); 553 | -------------------------------------------------------------------------------- /examples.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 106 | 107 | 114 | 115 | 116 | 117 | 118 | 119 |
120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /texture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chandlerprall/ThreeCSG/e9e09bce93e466870003f0585e4a5f10fdaeba37/texture.png -------------------------------------------------------------------------------- /threeCSG.es6: -------------------------------------------------------------------------------- 1 | /*jshint esversion: 6 */ 2 | 3 | const EPSILON = 1e-5, 4 | COPLANAR = 0, 5 | FRONT = 1, 6 | BACK = 2, 7 | SPANNING = 3; 8 | 9 | export default class ThreeBSP { 10 | constructor(geometry) { 11 | // Convert THREE.Geometry to ThreeBSP 12 | var i, _length_i, 13 | face, vertex, faceVertexUvs, uvs, 14 | polygon, 15 | polygons = [], 16 | tree; 17 | 18 | this.Polygon = Polygon; 19 | this.Vertex = Vertex; 20 | this.Node = Node; 21 | if (geometry instanceof THREE.Geometry) { 22 | this.matrix = new THREE.Matrix4(); 23 | } else if (geometry instanceof THREE.Mesh) { 24 | // #todo: add hierarchy support 25 | geometry.updateMatrix(); 26 | this.matrix = geometry.matrix.clone(); 27 | geometry = geometry.geometry; 28 | } else if (geometry instanceof Node) { 29 | this.tree = geometry; 30 | this.matrix = new THREE.Matrix4(); 31 | return this; 32 | } else { 33 | throw 'ThreeBSP: Given geometry is unsupported'; 34 | } 35 | 36 | for (i = 0, _length_i = geometry.faces.length; i < _length_i; i++) { 37 | face = geometry.faces[i]; 38 | faceVertexUvs = geometry.faceVertexUvs[0][i]; 39 | polygon = new Polygon(); 40 | 41 | if (face instanceof THREE.Face3) { 42 | vertex = geometry.vertices[face.a]; 43 | uvs = faceVertexUvs ? new THREE.Vector2(faceVertexUvs[0].x, faceVertexUvs[0].y) : null; 44 | vertex = new Vertex(vertex.x, vertex.y, vertex.z, face.vertexNormals[0], uvs); 45 | vertex.applyMatrix4(this.matrix); 46 | polygon.vertices.push(vertex); 47 | 48 | vertex = geometry.vertices[face.b]; 49 | uvs = faceVertexUvs ? new THREE.Vector2(faceVertexUvs[1].x, faceVertexUvs[1].y) : null; 50 | vertex = new Vertex(vertex.x, vertex.y, vertex.z, face.vertexNormals[1], uvs); 51 | vertex.applyMatrix4(this.matrix); 52 | polygon.vertices.push(vertex); 53 | 54 | vertex = geometry.vertices[face.c]; 55 | uvs = faceVertexUvs ? new THREE.Vector2(faceVertexUvs[2].x, faceVertexUvs[2].y) : null; 56 | vertex = new Vertex(vertex.x, vertex.y, vertex.z, face.vertexNormals[2], uvs); 57 | vertex.applyMatrix4(this.matrix); 58 | polygon.vertices.push(vertex); 59 | } else if (typeof THREE.Face4) { 60 | vertex = geometry.vertices[face.a]; 61 | uvs = faceVertexUvs ? new THREE.Vector2(faceVertexUvs[0].x, faceVertexUvs[0].y) : null; 62 | vertex = new Vertex(vertex.x, vertex.y, vertex.z, face.vertexNormals[0], uvs); 63 | vertex.applyMatrix4(this.matrix); 64 | polygon.vertices.push(vertex); 65 | 66 | vertex = geometry.vertices[face.b]; 67 | uvs = faceVertexUvs ? new THREE.Vector2(faceVertexUvs[1].x, faceVertexUvs[1].y) : null; 68 | vertex = new Vertex(vertex.x, vertex.y, vertex.z, face.vertexNormals[1], uvs); 69 | vertex.applyMatrix4(this.matrix); 70 | polygon.vertices.push(vertex); 71 | 72 | vertex = geometry.vertices[face.c]; 73 | uvs = faceVertexUvs ? new THREE.Vector2(faceVertexUvs[2].x, faceVertexUvs[2].y) : null; 74 | vertex = new Vertex(vertex.x, vertex.y, vertex.z, face.vertexNormals[2], uvs); 75 | vertex.applyMatrix4(this.matrix); 76 | polygon.vertices.push(vertex); 77 | 78 | vertex = geometry.vertices[face.d]; 79 | uvs = faceVertexUvs ? new THREE.Vector2(faceVertexUvs[3].x, faceVertexUvs[3].y) : null; 80 | vertex = new Vertex(vertex.x, vertex.y, vertex.z, face.vertexNormals[3], uvs); 81 | vertex.applyMatrix4(this.matrix); 82 | polygon.vertices.push(vertex); 83 | } else { 84 | throw 'Invalid face type at index ' + i; 85 | } 86 | 87 | polygon.calculateProperties(); 88 | polygons.push(polygon); 89 | } 90 | 91 | this.tree = new Node(polygons); 92 | } 93 | 94 | subtract(other_tree) { 95 | var a = this.tree.clone(), 96 | b = other_tree.tree.clone(); 97 | 98 | a.invert(); 99 | a.clipTo(b); 100 | b.clipTo(a); 101 | b.invert(); 102 | b.clipTo(a); 103 | b.invert(); 104 | a.build(b.allPolygons()); 105 | a.invert(); 106 | a = new ThreeBSP(a); 107 | a.matrix = this.matrix; 108 | return a; 109 | } 110 | 111 | union(other_tree) { 112 | var a = this.tree.clone(), 113 | b = other_tree.tree.clone(); 114 | 115 | a.clipTo(b); 116 | b.clipTo(a); 117 | b.invert(); 118 | b.clipTo(a); 119 | b.invert(); 120 | a.build(b.allPolygons()); 121 | a = new ThreeBSP(a); 122 | a.matrix = this.matrix; 123 | return a; 124 | } 125 | 126 | intersect(other_tree) { 127 | var a = this.tree.clone(), 128 | b = other_tree.tree.clone(); 129 | 130 | a.invert(); 131 | b.clipTo(a); 132 | b.invert(); 133 | a.clipTo(b); 134 | b.clipTo(a); 135 | a.build(b.allPolygons()); 136 | a.invert(); 137 | a = new ThreeBSP(a); 138 | a.matrix = this.matrix; 139 | return a; 140 | } 141 | 142 | toGeometry() { 143 | var i, j, 144 | matrix = new THREE.Matrix4().getInverse(this.matrix), 145 | geometry = new THREE.Geometry(), 146 | polygons = this.tree.allPolygons(), 147 | polygon_count = polygons.length, 148 | polygon, polygon_vertice_count, 149 | vertice_dict = {}, 150 | vertex_idx_a, vertex_idx_b, vertex_idx_c, 151 | vertex, face, 152 | verticeUvs; 153 | 154 | for (i = 0; i < polygon_count; i++) { 155 | polygon = polygons[i]; 156 | polygon_vertice_count = polygon.vertices.length; 157 | 158 | for (j = 2; j < polygon_vertice_count; j++) { 159 | verticeUvs = []; 160 | 161 | vertex = polygon.vertices[0]; 162 | verticeUvs.push(new THREE.Vector2(vertex.uv.x, vertex.uv.y)); 163 | vertex = new THREE.Vector3(vertex.x, vertex.y, vertex.z); 164 | vertex.applyMatrix4(matrix); 165 | 166 | if (typeof vertice_dict[vertex.x + ',' + vertex.y + ',' + vertex.z] !== 'undefined') { 167 | vertex_idx_a = vertice_dict[vertex.x + ',' + vertex.y + ',' + vertex.z]; 168 | } else { 169 | geometry.vertices.push(vertex); 170 | vertex_idx_a = vertice_dict[vertex.x + ',' + vertex.y + ',' + vertex.z] = geometry.vertices.length - 1; 171 | } 172 | 173 | vertex = polygon.vertices[j - 1]; 174 | verticeUvs.push(new THREE.Vector2(vertex.uv.x, vertex.uv.y)); 175 | vertex = new THREE.Vector3(vertex.x, vertex.y, vertex.z); 176 | vertex.applyMatrix4(matrix); 177 | if (typeof vertice_dict[vertex.x + ',' + vertex.y + ',' + vertex.z] !== 'undefined') { 178 | vertex_idx_b = vertice_dict[vertex.x + ',' + vertex.y + ',' + vertex.z]; 179 | } else { 180 | geometry.vertices.push(vertex); 181 | vertex_idx_b = vertice_dict[vertex.x + ',' + vertex.y + ',' + vertex.z] = geometry.vertices.length - 1; 182 | } 183 | 184 | vertex = polygon.vertices[j]; 185 | verticeUvs.push(new THREE.Vector2(vertex.uv.x, vertex.uv.y)); 186 | vertex = new THREE.Vector3(vertex.x, vertex.y, vertex.z); 187 | vertex.applyMatrix4(matrix); 188 | if (typeof vertice_dict[vertex.x + ',' + vertex.y + ',' + vertex.z] !== 'undefined') { 189 | vertex_idx_c = vertice_dict[vertex.x + ',' + vertex.y + ',' + vertex.z]; 190 | } else { 191 | geometry.vertices.push(vertex); 192 | vertex_idx_c = vertice_dict[vertex.x + ',' + vertex.y + ',' + vertex.z] = geometry.vertices.length - 1; 193 | } 194 | 195 | face = new THREE.Face3( 196 | vertex_idx_a, 197 | vertex_idx_b, 198 | vertex_idx_c, 199 | new THREE.Vector3(polygon.normal.x, polygon.normal.y, polygon.normal.z) 200 | ); 201 | 202 | geometry.faces.push(face); 203 | geometry.faceVertexUvs[0].push(verticeUvs); 204 | } 205 | 206 | } 207 | return geometry; 208 | } 209 | 210 | toMesh (material) { 211 | var geometry = this.toGeometry(), 212 | mesh = new THREE.Mesh(geometry, material); 213 | 214 | mesh.position.setFromMatrixPosition(this.matrix); 215 | mesh.rotation.setFromRotationMatrix(this.matrix); 216 | 217 | return mesh; 218 | } 219 | } 220 | class Polygon { 221 | constructor(vertices, normal, w) { 222 | if (!( vertices instanceof Array )) { 223 | vertices = []; 224 | } 225 | 226 | this.vertices = vertices; 227 | if (vertices.length > 0) { 228 | this.calculateProperties(); 229 | } else { 230 | this.normal = this.w = undefined; 231 | } 232 | } 233 | 234 | calculateProperties() { 235 | var a = this.vertices[0], 236 | b = this.vertices[1], 237 | c = this.vertices[2]; 238 | 239 | this.normal = b.clone().subtract(a).cross( 240 | c.clone().subtract(a) 241 | ).normalize(); 242 | 243 | this.w = this.normal.clone().dot(a); 244 | 245 | return this; 246 | } 247 | 248 | clone() { 249 | var i, vertice_count, 250 | polygon = new Polygon(); 251 | 252 | for (i = 0, vertice_count = this.vertices.length; i < vertice_count; i++) { 253 | polygon.vertices.push(this.vertices[i].clone()); 254 | } 255 | polygon.calculateProperties(); 256 | 257 | return polygon; 258 | } 259 | 260 | flip() { 261 | var i, vertices = []; 262 | 263 | this.normal.multiplyScalar(-1); 264 | this.w *= -1; 265 | 266 | for (i = this.vertices.length - 1; i >= 0; i--) { 267 | vertices.push(this.vertices[i]); 268 | } 269 | this.vertices = vertices; 270 | 271 | return this; 272 | } 273 | 274 | classifyVertex(vertex) { 275 | var side_value = this.normal.dot(vertex) - this.w; 276 | 277 | if (side_value < -EPSILON) { 278 | return BACK; 279 | } else if (side_value > EPSILON) { 280 | return FRONT; 281 | } else { 282 | return COPLANAR; 283 | } 284 | } 285 | 286 | classifySide(polygon) { 287 | var i, vertex, classification, 288 | num_positive = 0, 289 | num_negative = 0, 290 | vertice_count = polygon.vertices.length; 291 | 292 | for (i = 0; i < vertice_count; i++) { 293 | vertex = polygon.vertices[i]; 294 | classification = this.classifyVertex(vertex); 295 | if (classification === FRONT) { 296 | num_positive++; 297 | } else if (classification === BACK) { 298 | num_negative++; 299 | } 300 | } 301 | 302 | if (num_positive === vertice_count && num_negative === 0) { 303 | return FRONT; 304 | } else if (num_positive === 0 && num_negative === vertice_count) { 305 | return BACK; 306 | } else if (num_positive > 0 && num_negative > 0) { 307 | return SPANNING; 308 | } else { 309 | return COPLANAR; 310 | } 311 | } 312 | 313 | splitPolygon(polygon, coplanar_front, coplanar_back, front, back) { 314 | var classification = this.classifySide(polygon); 315 | 316 | if (classification === COPLANAR) { 317 | 318 | ( this.normal.dot(polygon.normal) > 0 ? coplanar_front : coplanar_back ).push(polygon); 319 | 320 | } else if (classification === FRONT) { 321 | 322 | front.push(polygon); 323 | 324 | } else if (classification === BACK) { 325 | 326 | back.push(polygon); 327 | 328 | } else { 329 | 330 | var vertice_count, 331 | i, j, ti, tj, vi, vj, 332 | t, v, 333 | f = [], 334 | b = []; 335 | 336 | for (i = 0, vertice_count = polygon.vertices.length; i < vertice_count; i++) { 337 | 338 | j = (i + 1) % vertice_count; 339 | vi = polygon.vertices[i]; 340 | vj = polygon.vertices[j]; 341 | ti = this.classifyVertex(vi); 342 | tj = this.classifyVertex(vj); 343 | 344 | if (ti != BACK) f.push(vi); 345 | if (ti != FRONT) b.push(vi); 346 | if ((ti | tj) === SPANNING) { 347 | t = ( this.w - this.normal.dot(vi) ) / this.normal.dot(vj.clone().subtract(vi)); 348 | v = vi.interpolate(vj, t); 349 | f.push(v); 350 | b.push(v); 351 | } 352 | } 353 | 354 | 355 | if (f.length >= 3) front.push(new Polygon(f).calculateProperties()); 356 | if (b.length >= 3) back.push(new Polygon(b).calculateProperties()); 357 | } 358 | } 359 | } 360 | class Vertex { 361 | constructor(x, y, z, normal, uv) { 362 | this.x = x; 363 | this.y = y; 364 | this.z = z; 365 | this.normal = normal || new THREE.Vector3(); 366 | this.uv = uv || new THREE.Vector2(); 367 | } 368 | 369 | clone() { 370 | return new Vertex(this.x, this.y, this.z, this.normal.clone(), this.uv.clone()); 371 | } 372 | 373 | add(vertex) { 374 | this.x += vertex.x; 375 | this.y += vertex.y; 376 | this.z += vertex.z; 377 | return this; 378 | } 379 | 380 | subtract(vertex) { 381 | this.x -= vertex.x; 382 | this.y -= vertex.y; 383 | this.z -= vertex.z; 384 | return this; 385 | } 386 | 387 | multiplyScalar(scalar) { 388 | this.x *= scalar; 389 | this.y *= scalar; 390 | this.z *= scalar; 391 | return this; 392 | } 393 | 394 | cross(vertex) { 395 | var x = this.x, 396 | y = this.y, 397 | z = this.z; 398 | 399 | this.x = y * vertex.z - z * vertex.y; 400 | this.y = z * vertex.x - x * vertex.z; 401 | this.z = x * vertex.y - y * vertex.x; 402 | 403 | return this; 404 | } 405 | 406 | normalize() { 407 | var length = Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); 408 | 409 | this.x /= length; 410 | this.y /= length; 411 | this.z /= length; 412 | 413 | return this; 414 | } 415 | 416 | dot(vertex) { 417 | return this.x * vertex.x + this.y * vertex.y + this.z * vertex.z; 418 | } 419 | 420 | lerp(a, t) { 421 | this.add( 422 | a.clone().subtract(this).multiplyScalar(t) 423 | ); 424 | 425 | this.normal.add( 426 | a.normal.clone().sub(this.normal).multiplyScalar(t) 427 | ); 428 | 429 | this.uv.add( 430 | a.uv.clone().sub(this.uv).multiplyScalar(t) 431 | ); 432 | 433 | return this; 434 | } 435 | 436 | interpolate(other, t) { 437 | return this.clone().lerp(other, t); 438 | } 439 | 440 | applyMatrix4(m) { 441 | 442 | // input: THREE.Matrix4 affine matrix 443 | 444 | var x = this.x, y = this.y, z = this.z; 445 | 446 | var e = m.elements; 447 | 448 | this.x = e[0] * x + e[4] * y + e[8] * z + e[12]; 449 | this.y = e[1] * x + e[5] * y + e[9] * z + e[13]; 450 | this.z = e[2] * x + e[6] * y + e[10] * z + e[14]; 451 | 452 | return this; 453 | 454 | } 455 | } 456 | class Node { 457 | constructor(polygons) { 458 | var i, polygon_count, 459 | front = [], 460 | back = []; 461 | 462 | this.polygons = []; 463 | this.front = this.back = undefined; 464 | 465 | if (!(polygons instanceof Array) || polygons.length === 0) return; 466 | 467 | this.divider = polygons[0].clone(); 468 | 469 | for (i = 0, polygon_count = polygons.length; i < polygon_count; i++) { 470 | this.divider.splitPolygon(polygons[i], this.polygons, this.polygons, front, back); 471 | } 472 | 473 | if (front.length > 0) { 474 | this.front = new Node(front); 475 | } 476 | 477 | if (back.length > 0) { 478 | this.back = new Node(back); 479 | } 480 | } 481 | 482 | isConvex(polygons) { 483 | var i, j; 484 | for (i = 0; i < polygons.length; i++) { 485 | for (j = 0; j < polygons.length; j++) { 486 | if (i !== j && polygons[i].classifySide(polygons[j]) !== BACK) { 487 | return false; 488 | } 489 | } 490 | } 491 | return true; 492 | } 493 | 494 | build(polygons) { 495 | var i, polygon_count, 496 | front = [], 497 | back = []; 498 | 499 | if (!this.divider) { 500 | this.divider = polygons[0].clone(); 501 | } 502 | 503 | for (i = 0, polygon_count = polygons.length; i < polygon_count; i++) { 504 | this.divider.splitPolygon(polygons[i], this.polygons, this.polygons, front, back); 505 | } 506 | 507 | if (front.length > 0) { 508 | if (!this.front) this.front = new Node(); 509 | this.front.build(front); 510 | } 511 | 512 | if (back.length > 0) { 513 | if (!this.back) this.back = new Node(); 514 | this.back.build(back); 515 | } 516 | } 517 | 518 | allPolygons() { 519 | var polygons = this.polygons.slice(); 520 | if (this.front) polygons = polygons.concat(this.front.allPolygons()); 521 | if (this.back) polygons = polygons.concat(this.back.allPolygons()); 522 | return polygons; 523 | } 524 | 525 | clone() { 526 | var node = new Node(); 527 | 528 | node.divider = this.divider.clone(); 529 | node.polygons = this.polygons.map(function (polygon) { 530 | return polygon.clone(); 531 | }); 532 | node.front = this.front && this.front.clone(); 533 | node.back = this.back && this.back.clone(); 534 | 535 | return node; 536 | } 537 | 538 | invert() { 539 | var i, polygon_count, temp; 540 | 541 | for (i = 0, polygon_count = this.polygons.length; i < polygon_count; i++) { 542 | this.polygons[i].flip(); 543 | } 544 | 545 | this.divider.flip(); 546 | if (this.front) this.front.invert(); 547 | if (this.back) this.back.invert(); 548 | 549 | temp = this.front; 550 | this.front = this.back; 551 | this.back = temp; 552 | 553 | return this; 554 | } 555 | 556 | clipPolygons(polygons) { 557 | var i, polygon_count, 558 | front, back; 559 | 560 | if (!this.divider) return polygons.slice(); 561 | 562 | front = []; 563 | back = []; 564 | 565 | for (i = 0, polygon_count = polygons.length; i < polygon_count; i++) { 566 | this.divider.splitPolygon(polygons[i], front, back, front, back); 567 | } 568 | 569 | if (this.front) front = this.front.clipPolygons(front); 570 | if (this.back) back = this.back.clipPolygons(back); 571 | else back = []; 572 | 573 | return front.concat(back); 574 | } 575 | 576 | clipTo(node) { 577 | this.polygons = node.clipPolygons(this.polygons); 578 | if (this.front) this.front.clipTo(node); 579 | if (this.back) this.back.clipTo(node); 580 | } 581 | } 582 | 583 | window.ThreeBSP = ThreeBSP; 584 | --------------------------------------------------------------------------------