├── .npmignore ├── package.json ├── .gitignore ├── README.md └── index.js /.npmignore: -------------------------------------------------------------------------------- 1 | bower_components 2 | node_modules 3 | *.log 4 | .DS_Store 5 | bundle.js 6 | test 7 | test.js 8 | demo/ 9 | bower.json -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jthreebsp", 3 | "version": "1.0.0", 4 | "description": "Three.js工具类库,可以进行模型的交集、并集、差集,开发测试使用Three.js版本使用的是r126,不保证其他版本可用(对旧版使用Geometry的已经不支持,支持使用BufferGeometry)", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/Jafeyyu/threebsp.git" 12 | }, 13 | "author": "Jafeyyu", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/Jafeyyu/threebsp/issues" 17 | }, 18 | "homepage": "https://github.com/Jafeyyu/threebsp#readme", 19 | "keywords": [ 20 | "threebsp", 21 | "ThreeBSP", 22 | "jthreebsp" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # build 9 | build/ 10 | dist/ 11 | 12 | # .DS_Store 13 | */.DS_Store 14 | .DS_Store 15 | 16 | # Runtime data 17 | pids 18 | *.pid 19 | *.seed 20 | *.pid.lock 21 | 22 | # Directory for instrumented libs generated by jscoverage/JSCover 23 | lib-cov 24 | 25 | # Coverage directory used by tools like istanbul 26 | coverage 27 | 28 | # nyc test coverage 29 | .nyc_output 30 | 31 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 32 | .grunt 33 | 34 | # Bower dependency directory (https://bower.io/) 35 | bower_components 36 | 37 | # node-waf configuration 38 | .lock-wscript 39 | 40 | # Compiled binary addons (https://nodejs.org/api/addons.html) 41 | build/Release 42 | 43 | # Dependency directories 44 | node_modules/ 45 | jspm_packages/ 46 | 47 | # TypeScript v1 declaration files 48 | typings/ 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Optional REPL history 57 | .node_repl_history 58 | 59 | # Output of 'npm pack' 60 | *.tgz 61 | 62 | # Yarn Integrity file 63 | .yarn-integrity 64 | 65 | # dotenv environment variables file 66 | .env 67 | .env.test 68 | 69 | # parcel-bundler cache (https://parceljs.org/) 70 | .cache 71 | 72 | # next.js build output 73 | .next 74 | 75 | # nuxt.js build output 76 | .nuxt 77 | 78 | # vuepress build output 79 | .vuepress/dist 80 | 81 | # Serverless directories 82 | .serverless/ 83 | 84 | # FuseBox cache 85 | .fusebox/ 86 | 87 | # DynamoDB Local files 88 | .dynamodb/ 89 | 90 | .history/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 说明 2 | 3 | 本项目基于 [fox19920726](https://github.com/fox19920726) 的[threebsp](https://github.com/fox19920726/threebsp) 进行修改,感谢原作者开源的代码。四年以前一直使用 Three.js 进行 web 3D 展示的开发工作,弄了两年,后来就没怎么用过了,最近正好又要用一下,发现变化还不小,以前的写的一些库已经不适用于新版的 Three.js 了,百度查资料才知道了 ThreeBSP 这个库,之前都是部门内自己开发各种库,都不知道还有这种开源神器,不过用的时候发现跑不起来,一看 github 有一段时间没更新了,自己看源码发现新版本的 Geometry 基本上废弃了,都是使用 BufferGeometry 了,但是 threebsp 还没进行相关代码的更新,只能自己改造一下,也是很久没研究了,相关知识忘得差不多了,简单修改了一下,基本上能运行了,肯定还有很多有不足的地方,欢迎各位大神进行优化修改。 4 | 5 | ## 模块化 ThreeBSP 包 6 | 7 | ### Vue 中的用法(react 暂时不做示例) 8 | 9 | 安装 Threebsp 包(因为发布到 npm 时包名不能和之前相同,所以发布时改了下名字,叫 jthreebsp) 10 | 11 | ```sh 12 | npm install jthreebsp 13 | ``` 14 | 15 | 必须在前面先引入 THREE 包 16 | 17 | ```sh 18 | import * as THREE from 'three' 19 | ``` 20 | 21 | 再把 THREE 当作参数传入方可使用 22 | 23 | ```sh 24 | const ThreeBSP = require('jthreebsp')(THREE) 25 | ``` 26 | 27 | ### 案例 28 | 29 | createMesh 方法 30 | 31 | ```sh 32 | createMesh(geom) { 33 | // 创建一个线框纹理 34 | const wireFrameMat = new THREE.MeshBasicMaterial({ 35 | opacity: 0.5, 36 | wireframeLinewidth: 0.5 37 | }); 38 | wireFrameMat.wireframe = true; 39 | 40 | // 创建模型 41 | const mesh = new THREE.Mesh(geom, wireFrameMat); 42 | 43 | return mesh; 44 | } 45 | ``` 46 | 47 | 创建球形几何体 48 | 49 | ```sh 50 | const sphereGeometry = new THREE.SphereGeometry(50, 20, 20) 51 | 52 | const sphere = this.createMesh(sphereGeometry) 53 | ``` 54 | 55 | 创建立方体几何体 56 | 57 | ```sh 58 | const cubeGeometry = new THREE.BoxGeometry(30, 30, 30) 59 | 60 | const cube = this.createMesh(cubeGeometry) 61 | 62 | cube.position.x = -50 63 | ``` 64 | 65 | 生成 ThreeBSP 对象 66 | 67 | ```sh 68 | const sphereBSP = new ThreeBSP(sphere) 69 | 70 | const cubeBSP = new ThreeBSP(cube) 71 | ``` 72 | 73 | 以下计算方式取其一即可 74 | 75 | 进行差集计算(使用该函数可以在第一个几何体中移除两个几何体重叠的部分来创建新的几何体。) 76 | 77 | ```sh 78 | const resultBSP = sphereBSP.subtract(cubeBSP) 79 | ``` 80 | 81 | 进行并集计算(使用该函数可以将两个几何体联合起来创建出一个新的几何体。) 82 | 83 | ```sh 84 | const resultBSP = sphereBSP.union(cubeBSP) 85 | ``` 86 | 87 | 进行交集计算(使用该函数可以基于两个现有几何体的重合的部分定义此几何体的形状。) 88 | 89 | ```sh 90 | const resultBSP = sphereBSP.intersect(cubeBSP) 91 | ``` 92 | 93 | 从 BSP 对象内获取到处理完后的 mesh 模型数据 94 | 95 | ```sh 96 | const result = resultBSP.toMesh() 97 | ``` 98 | 99 | 更新模型的面和顶点的数据 100 | 101 | ```sh 102 | result.geometry.computeFaceNormals() 103 | 104 | result.geometry.computeVertexNormals() 105 | ``` 106 | 107 | 重新赋值一个纹理 108 | 109 | ```sh 110 | const material = new THREE.MeshPhongMaterial({ color: 0x00ffff }) 111 | 112 | result.material = material 113 | ``` 114 | 115 | 将计算出来模型添加到场景当中 116 | 117 | ```sh 118 | this.scene.add(result) 119 | ``` 120 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = function(THREE) { 2 | 3 | // ε,表示最大的偏差量,如果偏差小于此值,则忽略偏差 4 | const EPSILON = 1e-5; 5 | 6 | // 共面 7 | const COPLANAR = 0; 8 | 9 | // 在前方 10 | const FRONT = 1; 11 | 12 | // 在后方 13 | const BACK = 2; 14 | 15 | // 存在交集 16 | const SPANNING = 3; 17 | 18 | var __slice = [].slice, 19 | __hasProp = {}.hasOwnProperty, 20 | __extends = function(child, parent) { 21 | for (var key in parent) { 22 | if (__hasProp.call(parent, key)) child[key] = parent[key]; 23 | } 24 | function ctor() { 25 | this.constructor = child; 26 | } 27 | ctor.prototype = parent.prototype; 28 | child.prototype = new ctor(); 29 | child.__super__ = parent.prototype; 30 | return child; 31 | }; 32 | 33 | function ThreeBSP(treeIsh, matrix) { 34 | this.matrix = matrix; 35 | if (this.matrix == null) { 36 | this.matrix = new THREE.Matrix4(); 37 | } 38 | this.tree = this.toTree(treeIsh); 39 | } 40 | 41 | ThreeBSP.prototype.toTree = function(treeIsh) { 42 | if (treeIsh instanceof ThreeBSP.Node) { 43 | return treeIsh; 44 | } 45 | // 看Three.js 0.126源码,各类Geometry基本都是继承或由BufferGeometry实现,基本上THREE.Geometry就废弃了,所以需要将获取点、法向量、uv信息要从以前的THREE.Geometry的faces中获取改为从THREE.BufferGeometry的attributes中获取 46 | var polygons = [], geometry = (treeIsh === THREE.BufferGeometry || (treeIsh.type || "").endsWith("Geometry")) ? treeIsh : treeIsh.constructor === THREE.Mesh ? (treeIsh.updateMatrix(), this.matrix = treeIsh.matrix.clone(), treeIsh.geometry) : void 0; 47 | if(geometry && geometry.attributes) { 48 | // TODO 暂时就不对geometry.attributes中的position、 normal和uv进行非空验证了,日后有时间在说吧,正常创建的BufferGeometry这些值通常都是有的 49 | var attributes = geometry.attributes, normal = attributes.normal, position = attributes.position, uv = attributes.uv; 50 | // 点的数量 51 | var pointsLength = attributes.position.array.length/attributes.position.itemSize; 52 | 53 | // 如果索引三角形index不为空,则根据index获取面的顶点、法向量、uv信息 54 | if(geometry.index) { 55 | var pointsArr = [], normalsArr = [], uvsArr = []; 56 | // 从geometry的attributes读取点、法向量、uv数据 57 | for(var i = 0, len = pointsLength; i < len;i++) { 58 | // 通常一个点和一个法向量的数据量(itemSize)是3,一个uv的数据量(itemSize)是2 59 | var startIndex = 3*i 60 | pointsArr.push(new THREE.Vector3(position.array[startIndex], position.array[startIndex + 1], position.array[startIndex + 2])); 61 | normalsArr.push(new THREE.Vector3(normal.array[startIndex], normal.array[startIndex + 1], normal.array[startIndex + 2])); 62 | uvsArr.push(new THREE.Vector2(uv.array[2*i], uv.array[2*i + 1])); 63 | } 64 | var index = geometry.index.array; 65 | for(var i = 0, len = index.length; i < len;) { 66 | var polygon = new ThreeBSP.Polygon(); 67 | // 将所有面都按照三角面进行处理,即三个顶点组成一个面 68 | for(var j = 0; j < 3; j++) { 69 | var pointIndex = index[i], point = pointsArr[pointIndex]; 70 | var vertex = new ThreeBSP.Vertex(point.x, point.y, point.z, normalsArr[pointIndex], uvsArr[pointIndex]); 71 | vertex.applyMatrix4(this.matrix); 72 | polygon.vertices.push(vertex); 73 | i++; 74 | } 75 | polygons.push(polygon.calculateProperties()); 76 | } 77 | } else { 78 | // 如果索引三角形index为空,则假定每三个相邻位置(即相邻的三个点)表示一个三角形 79 | for(var i = 0, len = pointsLength; i < len;) { 80 | var polygon = new ThreeBSP.Polygon(); 81 | // 将所有面都按照三角面进行处理,即三个顶点组成一个面 82 | for(var j = 0; j < 3; j++) { 83 | var startIndex = 3*i 84 | var vertex = new ThreeBSP.Vertex(position.array[startIndex], position.array[startIndex + 1], position.array[startIndex + 2], new THREE.Vector3(normal.array[startIndex], normal.array[startIndex + 1], normal.array[startIndex + 2]), new THREE.Vector2(uv.array[2*i], uv.array[2*i + 1])); 85 | vertex.applyMatrix4(this.matrix); 86 | polygon.vertices.push(vertex); 87 | i++; 88 | } 89 | polygons.push(polygon.calculateProperties()); 90 | } 91 | } 92 | } else { 93 | console.error("初始化ThreeBSP时为获取到几何数据信息,请检查初始化参数"); 94 | } 95 | return new ThreeBSP.Node(polygons); 96 | }; 97 | 98 | /** 99 | * 转换成mesh 100 | * @param {*} material 材质 101 | * @param {Boolean} groupByCoplanar 是否根据共面进行分组 102 | * @param {Boolean} uniqueMaterial 每个分组使用唯一的材质(此处将材质索引和组索引设置为一样的) 103 | * @returns 104 | */ 105 | ThreeBSP.prototype.toMesh = function(material, groupByCoplanar, uniqueMaterial) { 106 | var geometry = this.toGeometry(groupByCoplanar, uniqueMaterial); 107 | if (material == null) { 108 | material = new THREE.MeshNormalMaterial(); 109 | } 110 | var mesh = new THREE.Mesh(geometry, material); 111 | mesh.position.getPositionFromMatrix(this.matrix); 112 | mesh.rotation.setFromRotationMatrix(this.matrix); 113 | return mesh; 114 | }; 115 | 116 | /** 117 | * 转换成图形 118 | * @param {Boolean} groupByCoplanar 是否根据共面进行分组 119 | * @param {Boolean} uniqueMaterial 每个分组使用唯一的材质(此处将材质索引和组索引设置为一样的) 120 | * @returns 121 | */ 122 | ThreeBSP.prototype.toGeometry = function(groupByCoplanar, uniqueMaterial) { 123 | var geometry = new THREE.BufferGeometry(), matrix = new THREE.Matrix4().getInverse(this.matrix); 124 | // verticesArr用于记录点(去重),index依次记录面中各个点对应的索引 125 | var position = [], normal = [], uv = [], verticesArr = [], index = []; 126 | var resolvePolygon = (polygon) => { 127 | polygon.vertices.forEach(item => { 128 | var vertice = item.clone().applyMatrix4(matrix), verticeIndex = null; 129 | for(var i =0, len = verticesArr.length; i < len; i++) { 130 | if(vertice.equals(verticesArr[i])) { 131 | verticeIndex = i; 132 | break; 133 | } 134 | } 135 | // verticeIndex为空,表示数组中未记录当前点,进行点数据处理 136 | if(verticeIndex == null) { 137 | verticeIndex = verticesArr.length; 138 | verticesArr.push(vertice); 139 | position.push(vertice.x); 140 | position.push(vertice.y); 141 | position.push(vertice.z); 142 | normal.push(vertice.normal.x); 143 | normal.push(vertice.normal.y); 144 | normal.push(vertice.normal.z); 145 | uv.push(vertice.uv.x); 146 | uv.push(vertice.uv.y); 147 | } 148 | // 存储点的索引 149 | index.push(verticeIndex); 150 | }) 151 | }; 152 | if(groupByCoplanar) { 153 | // 将共面的面分到相同的组中 154 | var polygonGroups = [], groups = []; 155 | this.tree.allPolygons().forEach(polygon => { 156 | // 归入分组标志 157 | var flag = false; 158 | for(var i = 0, len = polygonGroups.length; i < len; i++) { 159 | if(COPLANAR === polygon.classifySide(polygonGroups[i][0])) { 160 | polygonGroups[i].push(polygon); 161 | flag = true; 162 | break; 163 | } 164 | } 165 | if(!flag) { 166 | // 为归入到已有分组中,建立新的分组 167 | polygonGroups.push([polygon]); 168 | } 169 | }); 170 | // 按照共面组进行数据的处理 171 | var start = 0; 172 | for(var i = 0, len = polygonGroups.length; i < len; i++) { 173 | var polygonGroup = polygonGroups[i], count = polygonGroup.length * 3, groupItem = { 174 | start: start, 175 | count: count 176 | }; 177 | if(uniqueMaterial) { 178 | groupItem.materialIndex = i; 179 | } 180 | polygonGroup.forEach(resolvePolygon); 181 | groups.push(groupItem); 182 | start = start + count; 183 | } 184 | geometry.groups = groups; 185 | } else { 186 | this.tree.allPolygons().forEach(resolvePolygon); 187 | } 188 | 189 | geometry.setAttribute('position', new THREE.BufferAttribute(Float32Array.from(position), 3, false)); 190 | geometry.setAttribute('normal', new THREE.BufferAttribute(Float32Array.from(normal), 3, false)); 191 | geometry.setAttribute('uv', new THREE.BufferAttribute(Float32Array.from(uv), 2, false)); 192 | geometry.index = new THREE.Uint16BufferAttribute(new Uint16Array(index), 1, false); 193 | return geometry; 194 | }; 195 | 196 | /** 197 | * 差集 198 | * @param {*} other 199 | * @returns 200 | */ 201 | ThreeBSP.prototype.subtract = function(other) { 202 | var us = this.tree.clone(), them = other.tree.clone(); 203 | us.invert().clipTo(them); 204 | them.clipTo(us).invert().clipTo(us).invert(); 205 | return new ThreeBSP(us.build(them.allPolygons()).invert(), this.matrix); 206 | }; 207 | 208 | /** 209 | * 并集 210 | * @param {*} other 211 | * @returns 212 | */ 213 | ThreeBSP.prototype.union = function(other) { 214 | var us = this.tree.clone(), them = other.tree.clone(); 215 | us.clipTo(them); 216 | them.clipTo(us).invert().clipTo(us).invert(); 217 | return new ThreeBSP(us.build(them.allPolygons()), this.matrix); 218 | }; 219 | 220 | /** 221 | * 交集 222 | * @param {*} other 223 | * @returns 224 | */ 225 | ThreeBSP.prototype.intersect = function(other) { 226 | var us = this.tree.clone(), them = other.tree.clone(); 227 | them.clipTo(us.invert()).invert().clipTo(us.clipTo(them)); 228 | return new ThreeBSP(us.build(them.allPolygons()).invert(), this.matrix); 229 | }; 230 | 231 | ThreeBSP.Vertex = (function(_super) { 232 | __extends(Vertex, _super); 233 | function Vertex(x, y, z, normal, uv) { 234 | this.x = x; 235 | this.y = y; 236 | this.z = z; 237 | this.normal = normal != null ? normal : new THREE.Vector3(); 238 | this.uv = uv != null ? uv : new THREE.Vector2(); 239 | // 此方法调用父级THREE.Vector3构造函数会报错TypeError: Class constructor Vector3 cannot be invoked without 'new',因此弃用此方式,改为显示初始化x,y,z 240 | // Vertex.__super__.constructor.call(this, x, y, z); 241 | } 242 | 243 | /** 244 | * 克隆点 245 | * @returns 246 | */ 247 | Vertex.prototype.clone = function() { 248 | return new ThreeBSP.Vertex(this.x, this.y, this.z, this.normal.clone(), this.uv.clone()); 249 | }; 250 | 251 | /** 252 | * 插点函数 253 | * @param {*} v 254 | * @param {*} alpha 255 | * @returns 256 | */ 257 | Vertex.prototype.lerp = function(v, alpha) { 258 | // 对uv进行插值 259 | this.uv.add(v.uv.clone().sub(this.uv).multiplyScalar(alpha)); 260 | // 对法向量进行插值 261 | this.normal.lerp(v, alpha); 262 | // 调用THREE.Vector3的lerp方法对点进行插值 263 | return Vertex.__super__.lerp.apply(this, arguments); 264 | }; 265 | 266 | /** 267 | * 在两个点之间插入新的点 268 | * @returns 269 | */ 270 | Vertex.prototype.interpolate = function() { 271 | var args = 1 <= arguments.length ? __slice.call(arguments, 0) : [], cloneVertex = this.clone(); 272 | return cloneVertex.lerp.apply(cloneVertex, args); 273 | }; 274 | 275 | /** 276 | * 判断两个点是否相同 277 | * @returns 278 | */ 279 | Vertex.prototype.equals = function(vertex) { 280 | if(vertex) { 281 | if(this.x === vertex.x && this.y === vertex.y && this.z === vertex.z) { 282 | var checkUv = function(uv1, uv2) { 283 | if(uv1 && uv2 && uv1.x === uv2.x && uv1.y === uv2.y) { 284 | return true; 285 | } else if(!uv1 && !uv2) { 286 | return true; 287 | } 288 | return false; 289 | } 290 | if(this.normal && vertex.normal && this.normal.x === vertex.normal.x && this.normal.y === vertex.normal.y && this.normal.z === vertex.normal.z) { 291 | return checkUv(this.uv, vertex.uv); 292 | } 293 | if(!this.normal && !vertex.normal) { 294 | return checkUv(this.uv, vertex.uv); 295 | } 296 | } 297 | } 298 | return false; 299 | }; 300 | 301 | return Vertex; 302 | 303 | })(THREE.Vector3); 304 | 305 | /** 306 | * 多边形(或者成为网格),目前默认只有三角形 307 | */ 308 | ThreeBSP.Polygon = (function() { 309 | function Polygon(vertices, normal, w) { 310 | this.vertices = vertices != null ? vertices : []; 311 | // 网格面的法向量 312 | this.normal = normal; 313 | this.w = w; 314 | if (this.vertices.length) { 315 | this.calculateProperties(); 316 | } 317 | } 318 | 319 | /** 320 | * 计算面的一些属性 321 | * @returns 322 | */ 323 | Polygon.prototype.calculateProperties = function() { 324 | var a = this.vertices[0], b = this.vertices[1], c = this.vertices[2]; 325 | // 计算面的法向量 326 | this.normal = b.clone().sub(a).cross(c.clone().sub(a)).normalize(); 327 | this.w = this.normal.clone().dot(a); 328 | return this; 329 | }; 330 | 331 | /** 332 | * 克隆网格面 333 | * @returns 334 | */ 335 | Polygon.prototype.clone = function() { 336 | return new ThreeBSP.Polygon(this.vertices.map(v => v.clone()), this.normal.clone(), this.w); 337 | }; 338 | 339 | /** 340 | * 对网格面进行翻转倒置 341 | * @returns 342 | */ 343 | Polygon.prototype.invert = function() { 344 | this.normal.multiplyScalar(-1); 345 | this.w *= -1; 346 | this.vertices.reverse(); 347 | return this; 348 | }; 349 | 350 | /** 351 | * 判断点与当前网格面的关系 352 | * @param {*} vertex 353 | * @returns 354 | */ 355 | Polygon.prototype.classifyVertex = function(vertex) { 356 | var side = this.normal.dot(vertex) - this.w; 357 | switch (false) { 358 | case !(side < -EPSILON): 359 | return BACK; 360 | case !(side > EPSILON): 361 | return FRONT; 362 | default: 363 | // 共面 364 | return COPLANAR; 365 | } 366 | }; 367 | 368 | /** 369 | * 判断指定网格面polygon与当前网格面的关系(FRONT:在当前面的前方, BACK:在当前面的后方, COPLANAR:与当前面共面, SPANNING:两个面交叉) 370 | * @param {*} polygon 371 | * @returns 372 | */ 373 | Polygon.prototype.classifySide = function(polygon) { 374 | var back = 0, front = 0; 375 | polygon.vertices.forEach(vertice => { 376 | switch (this.classifyVertex(vertice)) { 377 | case FRONT: 378 | front += 1; 379 | break; 380 | case BACK: 381 | back += 1; 382 | break; 383 | } 384 | }) 385 | 386 | if (front > 0 && back === 0) { 387 | return FRONT; 388 | } 389 | if (front === 0 && back > 0) { 390 | return BACK; 391 | } 392 | if ((front === back && back === 0)) { 393 | // 共面 394 | return COPLANAR; 395 | } 396 | return SPANNING; 397 | }; 398 | 399 | /** 400 | * 对两个网格面进行嵌合 401 | * @param {*} poly 402 | * @returns 403 | */ 404 | Polygon.prototype.tessellate = function(poly) { 405 | // 如果两个面是平行的或者时共面,则直接返回无需处理 406 | if (this.classifySide(poly) !== SPANNING) { 407 | return [poly]; 408 | } 409 | // 如果两个面是相交面,则进行插值处理 410 | var frontVertices = [], backVertices = [], count = poly.vertices.length, t, ti, tj, v, vi, vj; 411 | for (var i = 0, _len = poly.vertices.length; i < _len; i++) { 412 | vi = poly.vertices[i]; 413 | vj = poly.vertices[(j = (i + 1) % count)]; 414 | ti = this.classifyVertex(vi), 415 | tj = this.classifyVertex(vj); 416 | if (ti !== BACK) { 417 | frontVertices.push(vi); 418 | } 419 | if (ti !== FRONT) { 420 | backVertices.push(vi); 421 | } 422 | if ((ti | tj) === SPANNING) { 423 | t = (this.w - this.normal.dot(vi)) / this.normal.dot(vj.clone().sub(vi)); 424 | v = vi.interpolate(vj, t); 425 | frontVertices.push(v); 426 | backVertices.push(v); 427 | } 428 | } 429 | var polys = [], frontLength = frontVertices.length, backLength = backVertices.length; 430 | if (frontLength >= 3) { 431 | polys.push(new ThreeBSP.Polygon(frontVertices.slice(0, 3))); 432 | // 将多边形切割成多个三角面 433 | if (frontLength > 3) { 434 | var newVertices; 435 | for(var start = 2; start < frontLength; start++) { 436 | newVertices = [frontVertices[start], frontVertices[(start + 1)%frontLength], frontVertices[(start + 2)%frontLength]] 437 | polys.push(new ThreeBSP.Polygon(newVertices)); 438 | } 439 | } 440 | } 441 | if (backLength >= 3) { 442 | polys.push(new ThreeBSP.Polygon(backVertices.slice(0, 3))); 443 | // 将多边形切割成多个三角面 444 | if (backLength > 3) { 445 | var newVertices; 446 | for(var start = 2; start < backLength - 1; start++) { 447 | newVertices = [backVertices[start], backVertices[(start + 1)%backLength], backVertices[(start + 2)%backLength]] 448 | polys.push(new ThreeBSP.Polygon(newVertices)); 449 | } 450 | } 451 | } 452 | return polys; 453 | }; 454 | 455 | /** 456 | * 将指定面的点对于当前面的关系进行细分 457 | * @param {*} polygon 458 | * @param {*} coplanar_front 459 | * @param {*} coplanar_back 460 | * @param {*} front 461 | * @param {*} back 462 | * @returns 463 | */ 464 | Polygon.prototype.subdivide = function(polygon, coplanar_front, coplanar_back, front, back) { 465 | var poly, side, _ref = this.tessellate(polygon), _results = []; 466 | for (var _i = 0, _len = _ref.length; _i < _len; _i++) { 467 | poly = _ref[_i]; 468 | side = this.classifySide(poly); 469 | switch (side) { 470 | case FRONT: 471 | _results.push(front.push(poly)); 472 | break; 473 | case BACK: 474 | _results.push(back.push(poly)); 475 | break; 476 | case COPLANAR: 477 | if (this.normal.dot(poly.normal) > 0) { 478 | _results.push(coplanar_front.push(poly)); 479 | } else { 480 | _results.push(coplanar_back.push(poly)); 481 | } 482 | break; 483 | default: 484 | throw new Error("BUG: Polygon of classification " + side + " in subdivision"); 485 | } 486 | } 487 | return _results; 488 | }; 489 | 490 | return Polygon; 491 | })(); 492 | 493 | ThreeBSP.Node = (function() { 494 | 495 | function Node(polygons) { 496 | this.polygons = []; 497 | if (polygons != null && polygons.length) { 498 | this.build(polygons); 499 | } 500 | } 501 | 502 | /** 503 | * 504 | * @returns 克隆Node 505 | */ 506 | Node.prototype.clone = function() { 507 | var node = new ThreeBSP.Node(); 508 | node.divider = this.divider != null ? this.divider.clone() : void 0; 509 | node.polygons = this.polygons.map(item => item.clone()); 510 | node.front = this.front != null ? this.front.clone() : void 0; 511 | node.back = this.back != null ? this.back.clone() : void 0; 512 | return node; 513 | }; 514 | 515 | /** 516 | * 根据面构建Node 517 | * @param {*} polygons 518 | * @returns 519 | */ 520 | Node.prototype.build = function(polygons) { 521 | var polys, sides = { 522 | front: [], 523 | back: [] 524 | }; 525 | if (this.divider == null) { 526 | this.divider = polygons[0].clone(); 527 | } 528 | for (var _i = 0, _len = polygons.length; _i < _len; _i++) { 529 | this.divider.subdivide(polygons[_i], this.polygons, this.polygons, sides.front, sides.back); 530 | } 531 | for (var side in sides) { 532 | if (!__hasProp.call(sides, side)) continue; 533 | polys = sides[side]; 534 | if (polys.length) { 535 | if (this[side] == null) { 536 | this[side] = new ThreeBSP.Node(); 537 | } 538 | this[side].build(polys) 539 | } 540 | } 541 | return this; 542 | }; 543 | 544 | /** 545 | * 判断是否是凸面的 546 | * @param {*} polys 547 | * @returns 548 | */ 549 | Node.prototype.isConvex = function(polys) { 550 | var inner, outer; 551 | for (var _i = 0, _len = polys.length; _i < _len; _i++) { 552 | inner = polys[_i]; 553 | for (var _j = 0, _len1 = polys.length; _j < _len1; _j++) { 554 | outer = polys[_j]; 555 | if (inner !== outer && outer.classifySide(inner) !== BACK) { 556 | return false; 557 | } 558 | } 559 | } 560 | return true; 561 | }; 562 | 563 | /** 564 | * 获取Node的所有网格面 565 | * @returns 566 | */ 567 | Node.prototype.allPolygons = function() { 568 | return this.polygons.slice().concat(this.front != null ? this.front.allPolygons() : []).concat(this.back != null ? this.back.allPolygons() : []); 569 | }; 570 | 571 | /** 572 | * 将Node进行倒置反转 573 | * @returns 574 | */ 575 | Node.prototype.invert = function() { 576 | // 反转网格 577 | this.polygons.forEach(item => item.invert()); 578 | 579 | [this.divider, this.front, this.back].forEach(item => { 580 | if (item != null) { 581 | item.invert(); 582 | } 583 | }); 584 | 585 | var _ref2 = [this.back, this.front]; 586 | this.front = _ref2[0]; 587 | this.back = _ref2[1] 588 | return this; 589 | }; 590 | 591 | /** 592 | * 剪裁多边形 593 | * @param {*} polygons 594 | * @returns 595 | */ 596 | Node.prototype.clipPolygons = function(polygons) { 597 | var back = [], front = []; 598 | if (!this.divider) { 599 | return polygons.slice(); 600 | } 601 | polygons.forEach(poly => this.divider.subdivide(poly, front, back, front, back)) 602 | if (this.front) { 603 | front = this.front.clipPolygons(front); 604 | } 605 | if (this.back) { 606 | back = this.back.clipPolygons(back); 607 | } 608 | return front.concat(this.back ? back : []); 609 | }; 610 | 611 | /** 612 | * 用指定node对当前node进行剪裁 613 | * @param {*} node 614 | * @returns 615 | */ 616 | Node.prototype.clipTo = function(node) { 617 | this.polygons = node.clipPolygons(this.polygons); 618 | if (this.front != null) { 619 | this.front.clipTo(node); 620 | } 621 | if (this.back != null) { 622 | this.back.clipTo(node); 623 | } 624 | 625 | return this; 626 | }; 627 | 628 | return Node; 629 | })(); 630 | 631 | return ThreeBSP; 632 | 633 | } --------------------------------------------------------------------------------