├── LICENSE ├── README.md ├── index.html ├── lib ├── camera_v1.05.js ├── fx.js ├── gl-matrix.js ├── node.js ├── octree.js └── utils.js └── screenshots ├── ccpedges.png ├── ccpfaces.png ├── childrenorder.png ├── contourEdgeProc.png ├── fpe.jpg ├── fpe2.png ├── fpf.jpg ├── gitscreen.png ├── octree.png ├── octree2.png ├── octree3.png ├── octree4.png ├── octree5.png └── vertorder.png /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Domenicobrz 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dual-Contouring-javascript-implementation 2 | 3 | 4 | 5 | 6 | This javascript implementation takes [Nick's excellent explanations](http://ngildea.blogspot.it/2014/11/implementing-dual-contouring.html) on the inner workings of the algorithm and expands it further in the topics he didn't discuss in depth, such as ContourCellProc's routines and the global variables of the original octree structure of Tao Ju. 7 | 8 | Since a blog post can't be a book, and considering the sheer amount of explanations required to fully grasp the algorithm, he omitted an in depth explanations on the inner isosurface extraction routines which will be discussed here along with a simple implementation of the algorithm in javascript togheter with a raw WebGL renderer to output the extraction results. 9 | 10 | We'll define the following order convention for the children of each internal node: 11 | 12 | 13 | 14 | Similiarly, the vertex order convention is as follow 15 | 16 | 17 | 18 | Starting from ContourCellProc(...), 19 | This function acts on internal nodes only, and starts by recursively calling itself on each of its eight children 20 | 21 | ```javascript 22 | ... 23 | for (i = 0; i < 8; i++) { 24 | ContourCellProc(node.children[i], indexBuffer); 25 | } 26 | ... 27 | ``` 28 | 29 | It laters calls ContourFaceProc(...); on the 12 faces adjacent to the children of the current node, highlighted in gray in the following picture 30 | 31 | 32 | 33 | And finishes off by calling ContourEdgeProc(...) on the 6 shared edges depicted in red in the next picture 34 | 35 | 36 | 37 | 38 | ContourFaceProc(...) is a bit more involved and it starts by checking if the provided nodes are actually internal, if they are it recursively 39 | calls itself on the four faces shared by the adjacent children of both nodes. Assuming this function is called with the direction argument set to 0 (horizontal direction), 40 | it would try to recursively call it self on the four faces visible in the next image 41 | 42 | The dark gray region represents the 4 shared faces, the light gray region encapsulate the children sharing those faces 43 | 44 | 45 | 46 | Afterwards this function will call ContourEdgeProc(...) on the four edges shared by the children of both internal nodes, highlighted here in red 47 | 48 | 49 | 50 | ContourEdgeProc(...) needs 4 nodes with a common edge, if we consider the first of the four edges of the above image the selected children to pass over ContourEdgeProc(...) will be 51 | those highlighted in gray in the next picture 52 | 53 | 54 | 55 | Lastly, we're going to analyze ContourEdgeProc(...) and ContourProcessEdge(...) 56 | 57 | ContourEdgeProc(...) is pretty straightforward 58 | 59 | If the 4 nodes passed to this function are all leaves, they're just sent to ContourProcessEdge along with the direction identifier 60 | 61 | If however, those 4 nodes are internal, we'll iterate on the 2 common edges shared by all of them, the next image shows one of those 2 edges 62 | and the associated 4 child nodes that would be passed to ContourProcessEdge(...) -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /lib/camera_v1.05.js: -------------------------------------------------------------------------------- 1 | //expects gl-matrix-min to be defined before the current script 2 | //^ potresti creare la tua lookAt function così elimini questa dipendenza 3 | //expects a variable called zoom (perspective focal length) to be connected with the projection/orthogonal matrix 4 | //you can't change the camera variable name (unless you'd like to find and replace) 5 | 6 | 7 | 8 | 9 | 10 | 11 | //se lo zoom è alto aumentiamo lo smoothness? stile minecraft! 12 | 13 | 14 | 15 | 16 | /* 17 | 18 | HOW TO USE: 19 | 20 | var camera = new createCamera(); 21 | 22 | INSIDE DRAW: 23 | 24 | var view = camera.getViewMatrix(deltatime, 0.3); 25 | 26 | */ 27 | 28 | 29 | 30 | //namespacia tutte le variabili 31 | //tutti i metodi devono essere interni a camera 32 | 33 | 34 | 35 | 36 | 37 | //you should really namespace all those functions 38 | zoom = 45; 39 | 40 | function createCamera() 41 | { 42 | /* functions */ 43 | this.getViewMatrix = getViewMatrix; 44 | this.params = camera_params; 45 | this.camera_keydown = camera_keydown; 46 | this.camera_keyup = camera_keyup; 47 | this.camera_mousemove = camera_mousemove; 48 | this.camera_mousedown = camera_mousedown; 49 | this.camera_mouseup = camera_mouseup; 50 | this.camera_zoom = camera_zoom; 51 | 52 | this.camera_touchdown = camera_touchdown; 53 | this.camera_touchup = camera_touchup; 54 | this.camera_touchmove = camera_touchmove; 55 | 56 | 57 | 58 | this.pos = vec3.fromValues(0, 0, 0); 59 | this.up = vec3.fromValues(0, 1, 0); 60 | this.front = vec3.fromValues(0, 0, -1); 61 | this.frontCross = vec3.fromValues(0, 0, 0); 62 | this.dir = vec3.fromValues(0, 0, 0); 63 | 64 | 65 | /* optional args */ 66 | this.speed = 5.0; 67 | this.zoom_smoothness = 0.1; 68 | 69 | 70 | 71 | //to use the autocentered view specify 72 | //autocentered as true 73 | //where to look with this.look 74 | //and a radius distance with this.radius 75 | this.autoCentered = false; 76 | this.autoRotate = false; 77 | this.autoRotateSpeed = 3.0; //degree/sec 78 | this.radius = 3.0; 79 | this.look = [0,0,0]; 80 | this.mousecontrols = true; 81 | this.depth_of_field_transform_count = 0; 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | this.lookAt = mat4.create(); 91 | this.yaw = -3.14/2; //-90 e sai perchè 92 | this.pitch = 0; 93 | this.deltaRot = [0.0, 0.0]; 94 | this.lastPos = [null, null]; 95 | this.deltazoom = 0; 96 | 97 | this.dragging = false; 98 | this.startingZoom = window.zoom; 99 | this.zoomdefined = false; 100 | //tries to make camera movement smoother by checking for frameskips 101 | this.smoothDeltaTimeCheck = true; 102 | this.rotationSensitivity = 0.002; 103 | 104 | 105 | this.pressedKeys = { w: false, 106 | s: false, 107 | a: false, 108 | d: false, 109 | space: false, 110 | shift: false, 111 | la: false, 112 | ua: false, 113 | ra: false, 114 | da: false }; 115 | 116 | 117 | 118 | 119 | if(this.mousecontrols) { 120 | window.addEventListener("keydown", this.camera_keydown.bind(this)); 121 | window.addEventListener("keyup", this.camera_keyup.bind(this)); 122 | document.getElementById('canvas').addEventListener("mousemove", this.camera_mousemove.bind(this)); 123 | document.getElementById('canvas').addEventListener("mousedown", this.camera_mousedown.bind(this)); 124 | window.addEventListener("mouseup", this.camera_mouseup.bind(this)); 125 | 126 | 127 | document.getElementById('canvas').addEventListener("touchstart", this.camera_touchdown.bind(this)); 128 | document.getElementById('canvas').addEventListener("touchmove", this.camera_touchmove.bind(this)); 129 | window.addEventListener("touchend", this.camera_touchup.bind(this)); 130 | //window.addEventListener('mousewheel', this.camera_zoom.bind(this)); 131 | // For Firefox 132 | window.addEventListener('DOMMouseScroll', this.camera_zoom.bind(this)); 133 | } 134 | } 135 | 136 | function camera_params(zoom_smoothness, 137 | speed, 138 | _zoom, 139 | smoothDeltatime, 140 | rotationSensitivity) 141 | { 142 | if(zoom_smoothness !== undefined) 143 | this.zoom_smoothness = zoom_smoothness; 144 | 145 | if(speed !== undefined) 146 | this.speed = speed; 147 | 148 | if(_zoom !== undefined) 149 | this.startingZoom = _zoom; 150 | 151 | if(smoothDeltatime !== undefined) 152 | this.smoothDeltaTimeCheck = smoothDeltatime; 153 | 154 | if(rotationSensitivity !== undefined) 155 | this.rotationSensitivity = rotationSensitivity; 156 | } 157 | 158 | function getViewMatrix(deltatime, smoothness) 159 | { 160 | //smooths agains frameskipping 161 | if(this.smoothDeltaTimeCheck && deltatime <= 0.03333) 162 | { 163 | if(deltatime < 0.010) 164 | { } 165 | else 166 | deltatime = 0.016; 167 | } 168 | 169 | if(!this.zoomdefined) 170 | { 171 | this.startingZoom = zoom; 172 | this.zoomdefined = true; 173 | } 174 | 175 | if(this.autoCentered) 176 | { 177 | return camera_getAutocenteredViewMatrix.call(this, deltatime, smoothness, this.radius); 178 | } 179 | 180 | 181 | 182 | if(this.pressedKeys.w || this.pressedKeys.ua) { 183 | this.pos[0] += deltatime * this.speed * this.front[0]; 184 | this.pos[1] += deltatime * this.speed * this.front[1]; 185 | this.pos[2] += deltatime * this.speed * this.front[2]; 186 | } 187 | if(this.pressedKeys.s || this.pressedKeys.da) { 188 | this.pos[0] -= deltatime * this.speed * this.front[0]; 189 | this.pos[1] -= deltatime * this.speed * this.front[1]; 190 | this.pos[2] -= deltatime * this.speed * this.front[2]; 191 | } 192 | if(this.pressedKeys.d || this.pressedKeys.ra) { 193 | this.frontCross = vec3.cross(this.frontCross, this.front, this.up); 194 | vec3.normalize(this.frontCross, this.frontCross); 195 | this.pos[0] += deltatime * this.speed * this.frontCross[0]; 196 | this.pos[1] += deltatime * this.speed * this.frontCross[1]; 197 | this.pos[2] += deltatime * this.speed * this.frontCross[2]; 198 | } 199 | if(this.pressedKeys.a || this.pressedKeys.la) { 200 | this.frontCross = vec3.cross(this.frontCross, this.front, this.up); 201 | vec3.normalize(this.frontCross, this.frontCross); 202 | this.pos[0] -= deltatime * this.speed * this.frontCross[0]; 203 | this.pos[1] -= deltatime * this.speed * this.frontCross[1]; 204 | this.pos[2] -= deltatime * this.speed * this.frontCross[2]; 205 | } 206 | if(this.pressedKeys.space) { 207 | this.pos[0] += deltatime * this.speed * this.up[0]; 208 | this.pos[1] += deltatime * this.speed * this.up[1]; 209 | this.pos[2] += deltatime * this.speed * this.up[2]; 210 | } 211 | if(this.pressedKeys.shift) { 212 | this.pos[0] -= deltatime * this.speed * this.up[0]; 213 | this.pos[1] -= deltatime * this.speed * this.up[1]; 214 | this.pos[2] -= deltatime * this.speed * this.up[2]; 215 | } 216 | 217 | 218 | 219 | 220 | 221 | this.yaw += this.deltaRot[0] * smoothness; 222 | this.pitch -= this.deltaRot[1] * smoothness; 223 | 224 | this.deltaRot[0] -= this.deltaRot[0] * smoothness; 225 | this.deltaRot[1] -= this.deltaRot[1] * smoothness; 226 | 227 | if(this.pitch > 3.13/2) this.pitch = 3.13/2; 228 | if(this.pitch < -3.13/2) this.pitch = -3.13/2; 229 | 230 | this.front[0] = Math.cos(this.yaw) * Math.cos(this.pitch); 231 | this.front[1] = Math.sin(this.pitch); 232 | this.front[2] = Math.sin(this.yaw) * Math.cos(this.pitch); 233 | vec3.normalize(this.front, this.front); 234 | 235 | 236 | 237 | 238 | 239 | 240 | if(Math.abs(this.deltazoom) > 0) 241 | { 242 | var increment = this.deltazoom * this.zoom_smoothness; 243 | if(this.deltazoom < 0) this.deltazoom = this.deltazoom < -0.01 ? 244 | this.deltazoom - increment : 0; 245 | 246 | if(this.deltazoom > 0) this.deltazoom = this.deltazoom > +0.01 ? 247 | this.deltazoom - increment : 0; 248 | 249 | zoom += increment; 250 | if(zoom < 44.5) zoom = 44.5; 251 | if(zoom > this.startingZoom) zoom = this.startingZoom; 252 | } 253 | 254 | 255 | 256 | 257 | 258 | this.dir[0] = this.pos[0] + this.front[0]; 259 | this.dir[1] = this.pos[1] + this.front[1]; 260 | this.dir[2] = this.pos[2] + this.front[2]; 261 | mat4.lookAt(this.lookAt, this.pos, this.dir, this.up); 262 | return this.lookAt; 263 | } 264 | 265 | function camera_keydown(e) 266 | { 267 | switch(e.keyCode) { 268 | case 87: 269 | this.pressedKeys.w = true; 270 | break; 271 | case 65: 272 | this.pressedKeys.a = true; 273 | break; 274 | case 83: 275 | this.pressedKeys.s = true; 276 | break; 277 | case 68: 278 | this.pressedKeys.d = true; 279 | break; 280 | case 32: 281 | this.pressedKeys.space = true; 282 | break; 283 | case 16: 284 | this.pressedKeys.shift = true; 285 | break; 286 | case 37: 287 | this.pressedKeys.la = true; 288 | break; 289 | case 38: 290 | this.pressedKeys.ua = true; 291 | break; 292 | case 39: 293 | this.pressedKeys.ra = true; 294 | break; 295 | case 40: 296 | this.pressedKeys.da = true; 297 | break; 298 | } 299 | } 300 | 301 | function camera_keyup(e) 302 | { 303 | switch(e.keyCode) { 304 | case 87: 305 | this.pressedKeys.w = false; 306 | break; 307 | case 65: 308 | this.pressedKeys.a = false; 309 | break; 310 | case 83: 311 | this.pressedKeys.s = false; 312 | break; 313 | case 68: 314 | this.pressedKeys.d = false; 315 | break; 316 | case 32: 317 | this.pressedKeys.space = false; 318 | break; 319 | case 16: 320 | this.pressedKeys.shift = false; 321 | break; 322 | case 37: 323 | this.pressedKeys.la = false; 324 | break; 325 | case 38: 326 | this.pressedKeys.ua = false; 327 | break; 328 | case 39: 329 | this.pressedKeys.ra = false; 330 | break; 331 | case 40: 332 | this.pressedKeys.da = false; 333 | break; 334 | } 335 | } 336 | 337 | function camera_mousemove(e) 338 | { 339 | if(!this.dragging) return; 340 | 341 | //sta telecamera va fatta col click, non è un fps 342 | this.deltaRot[0] += (e.clientX - this.lastPos[0]) * this.rotationSensitivity; 343 | this.deltaRot[1] += (e.clientY - this.lastPos[1]) * this.rotationSensitivity; 344 | 345 | 346 | this.lastPos[0] = e.clientX; 347 | this.lastPos[1] = e.clientY; 348 | } 349 | 350 | function camera_touchmove(e) 351 | { 352 | if(!this.dragging) return; 353 | 354 | //sta telecamera va fatta col click, non è un fps 355 | this.deltaRot[0] += (e.touches[0].clientX - this.lastPos[0]) * this.rotationSensitivity; 356 | this.deltaRot[1] += (e.touches[0].clientY - this.lastPos[1]) * this.rotationSensitivity; 357 | 358 | 359 | this.lastPos[0] = e.touches[0].clientX; 360 | this.lastPos[1] = e.touches[0].clientY; 361 | } 362 | 363 | 364 | function camera_mousedown(e) 365 | { 366 | if(e.which != 1) return; 367 | this.lastPos[0] = e.clientX; 368 | this.lastPos[1] = e.clientY; 369 | this.dragging = true; 370 | } 371 | 372 | function camera_mouseup(e) 373 | { 374 | this.dragging = false; 375 | } 376 | 377 | function camera_touchdown(e) 378 | { 379 | this.lastPos[0] = e.touches[0].clientX; 380 | this.lastPos[1] = e.touches[0].clientY; 381 | this.dragging = true; 382 | } 383 | 384 | function camera_touchup(e) 385 | { 386 | this.dragging = false; 387 | } 388 | 389 | 390 | function camera_zoom(e) 391 | { 392 | var delta = e.wheelDelta ? e.wheelDelta : -e.detail; 393 | 394 | if(delta > 0) 395 | this.deltazoom += -0.15; 396 | else 397 | this.deltazoom += +0.15; 398 | } 399 | 400 | 401 | function camera_getAutocenteredViewMatrix(deltatime, smoothness, radius) 402 | { 403 | this.yaw += this.deltaRot[0] * smoothness; 404 | this.pitch -= this.deltaRot[1] * smoothness; 405 | 406 | if(this.autoRotate) 407 | { 408 | this.yaw += (this.autoRotateSpeed / 180 * Math.PI) * deltatime; 409 | } 410 | 411 | 412 | this.deltaRot[0] -= this.deltaRot[0] * smoothness; 413 | this.deltaRot[1] -= this.deltaRot[1] * smoothness; 414 | 415 | if(this.pitch > 3.13/2) this.pitch = 3.13/2; 416 | if(this.pitch < -3.13/2) this.pitch = -3.13/2; 417 | 418 | 419 | if(Math.abs(this.deltazoom) > 0) 420 | { 421 | var increment = this.deltazoom * this.zoom_smoothness; 422 | if(this.deltazoom < 0) this.deltazoom = this.deltazoom < -0.01 ? 423 | this.deltazoom - increment : 0; 424 | 425 | if(this.deltazoom > 0) this.deltazoom = this.deltazoom > +0.01 ? 426 | this.deltazoom - increment : 0; 427 | 428 | zoom += increment; 429 | if(zoom < 44.5) zoom = 44.5; 430 | if(zoom > this.startingZoom) zoom = this.startingZoom; 431 | } 432 | 433 | 434 | 435 | var xpos = Math.cos(this.yaw) * Math.cos(this.pitch) * radius; 436 | var zpos = Math.sin(this.yaw) * Math.cos(this.pitch) * radius; 437 | var ypos = Math.sin(this.pitch) * radius; 438 | 439 | 440 | this.pos[0] = xpos + this.look[0]; 441 | this.pos[1] = ypos + this.look[1]; 442 | this.pos[2] = -zpos + this.look[2]; 443 | 444 | 445 | mat4.lookAt(this.lookAt, this.pos, this.look, this.up); 446 | 447 | 448 | 449 | 450 | if(this.depth_of_field_transform_count != 0) { 451 | 452 | 453 | var degree = 6.28 * this.depth_of_field_transform_count / 10; 454 | 455 | var yaw = this.yaw + Math.cos(degree) / 150; 456 | var pitch = this.pitch + Math.sin(degree) / 150; 457 | 458 | /*var yaw = this.yaw// + 459 | this.depth_of_field_transform_count / 1500 - 460 | 5 / 1500; 461 | 462 | var pitch = this.pitch + 463 | this.depth_of_field_transform_count / 1600 - 464 | 5 / 1600;*/ 465 | 466 | var xpos = Math.cos(yaw) * Math.cos(pitch) * radius; 467 | var zpos = Math.sin(yaw) * Math.cos(pitch) * radius; 468 | var ypos = Math.sin(pitch) * radius; 469 | 470 | 471 | this.pos[0] = xpos + this.look[0]; 472 | this.pos[1] = ypos + this.look[1]; 473 | this.pos[2] = -zpos + this.look[2]; 474 | 475 | mat4.lookAt(this.lookAt, this.pos, this.look, this.up); 476 | } 477 | 478 | 479 | return this.lookAt; 480 | } 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | //v1.00 - basic functions 489 | //v1.02 - autocentered Camera 490 | //v1.03 - basic autoRotate 491 | //v1.04 - getAutoCenteredViewMatrix has depth of field capabilities 492 | //v1.05 - works on cellphones -------------------------------------------------------------------------------- /lib/fx.js: -------------------------------------------------------------------------------- 1 | window.addEventListener("load", Start); 2 | 3 | function Start() { 4 | "use strict"; 5 | var canvas = document.getElementById("canvas"); 6 | canvas.width = window.innerWidth; 7 | canvas.height = window.innerHeight; 8 | 9 | window.gl = canvas.getContext('experimental-webgl'); 10 | var names = ["webgl", "experimental-webgl", "webkit-3d", "mozwebgl"]; 11 | 12 | for(var i in names) 13 | { 14 | try 15 | { 16 | gl = canvas.getContext(names[i], {stencil: true, premultipliedAlpha: false}); 17 | 18 | if (gl && typeof gl.getParameter == "function") 19 | { 20 | /* WebGL is enabled */ 21 | break; 22 | } 23 | } catch(e) { } 24 | } 25 | 26 | 27 | window.projection = mat4.create(); 28 | window.view = mat4.create(); 29 | projection = mat4.perspective(projection, 45, innerWidth / innerHeight, 0.1, 100); 30 | 31 | window.camera = new createCamera(); 32 | window.camera.pos = [0, 5, 20]; 33 | 34 | console.time("performance"); 35 | window.octree = new Octree(new Vec3(-5, -5, -5), 10, 5); 36 | octree.isosurface(function(x,y,z) { 37 | 38 | // var result = 7 * Math.cos(x) - 6 * Math.sin(x * y) * 3 + 10* Math.cos(z * z); /* - 5 * Math.tan(z * z * z * x);*/ 39 | var result = Math.sin(x) + Math.cos(z) - y; 40 | // var result = Math.sin(x) + Math.cos(z) - Math.tan(y); 41 | // var result = Math.exp(Math.sin(x) + Math.cos(y)) - Math.sin(Math.exp(x+y)) + Math.tan(z) * z * z; 42 | 43 | return result; 44 | }); 45 | 46 | window.octreeRenderer = new OctreeRenderer(gl, octree); 47 | octreeRenderer.createOctreeWireframeGeometry(); 48 | octreeRenderer.createOctreePointsGeometry(); 49 | octreeRenderer.createOctreeMeshGeometry(); 50 | octreeRenderer.projection = projection; 51 | octreeRenderer.view = view; 52 | 53 | console.timeEnd("performance"); 54 | 55 | 56 | requestAnimationFrame(draw); 57 | } 58 | 59 | var then = 0; 60 | function draw(now) { 61 | requestAnimationFrame(draw); 62 | now *= 0.001; 63 | var deltatime = now - then; 64 | then = now; 65 | 66 | gl.clearColor(0.0, 0.0, 0.0, 1.0); 67 | gl.clear(gl.COLOR_BUFFER_BIT); 68 | 69 | octreeRenderer.view = camera.getViewMatrix(deltatime, 0.3); 70 | octreeRenderer.drawOctreeWireFrame(); 71 | // octreeRenderer.drawOctreePoints(); 72 | octreeRenderer.drawOctreeMesh(); 73 | } -------------------------------------------------------------------------------- /lib/node.js: -------------------------------------------------------------------------------- 1 | var children_offsets/*[8][3]*/ = [ 2 | new Vec3(0, 0, 0), 3 | new Vec3(0, 0, 1), 4 | new Vec3(0, 1, 0), 5 | new Vec3(0, 1, 1), 6 | new Vec3(1, 0, 0), 7 | new Vec3(1, 0, 1), 8 | new Vec3(1, 1, 0), 9 | new Vec3(1, 1, 1) 10 | ]; 11 | 12 | var edgevmap/*[12][2]*/ = [ 13 | [0, 4], [1, 5], [2, 6], [3, 7], // x-axis 14 | [0, 2], [1, 3], [4, 6], [5, 7], // y-axis 15 | [0, 1], [2, 3], [4, 5], [6, 7] // z-axis 16 | ]; 17 | 18 | var cellProcFaceMask/*[12][3]*/ = [[0, 4, 0], [1, 5, 0], [2, 6, 0], [3, 7, 0], [0, 2, 1], [4, 6, 1], [1, 3, 1], [5, 7, 1], [0, 1, 2], [2, 3, 2], [4, 5, 2], [6, 7, 2]]; 19 | var cellProcEdgeMask/*[6][5]*/ = [[0, 1, 2, 3, 0], [4, 5, 6, 7, 0], [0, 4, 1, 5, 1], [2, 6, 3, 7, 1], [0, 2, 4, 6, 2], [1, 3, 5, 7, 2]]; 20 | 21 | var faceProcFaceMask/*[3][4][3]*/ = [ 22 | [[4, 0, 0], [5, 1, 0], [6, 2, 0], [7, 3, 0]], 23 | [[2, 0, 1], [6, 4, 1], [3, 1, 1], [7, 5, 1]], 24 | [[1, 0, 2], [3, 2, 2], [5, 4, 2], [7, 6, 2]] 25 | ]; 26 | 27 | var faceProcEdgeMask/*[3][4][6]*/ = [ 28 | [[1, 4, 0, 5, 1, 1], [1, 6, 2, 7, 3, 1], [0, 4, 6, 0, 2, 2], [0, 5, 7, 1, 3, 2]], 29 | [[0, 2, 3, 0, 1, 0], [0, 6, 7, 4, 5, 0], [1, 2, 0, 6, 4, 2], [1, 3, 1, 7, 5, 2]], 30 | [[1, 1, 0, 3, 2, 0], [1, 5, 4, 7, 6, 0], [0, 1, 5, 0, 4, 1], [0, 3, 7, 2, 6, 1]] 31 | ]; 32 | 33 | var processEdgeMask/*[3][4]*/ = [[3, 2, 1, 0], [7, 5, 6, 4], [11, 10, 9, 8]]; 34 | 35 | var edgeProcEdgeMask/*[3][2][5]*/ = [ 36 | [[3, 2, 1, 0, 0], [7, 6, 5, 4, 0]], 37 | [[5, 1, 4, 0, 1], [7, 3, 6, 2, 1]], 38 | [[6, 4, 2, 0, 2], [7, 5, 3, 1, 2]], 39 | ]; 40 | 41 | var MATERIAL_AIR = 0.0; 42 | var MATERIAL_INTERNAL = 1.0; 43 | var MAX_CROSSINGS = 6; 44 | /* 45 | @position dictates the 'bottom-left' origin of this node in 3D space 46 | @size length of each edge for all 3 axys 47 | @level how deep inside the octree this node resides. level 0 means the current node is the root 48 | */ 49 | function Node(position, size, level) { 50 | "use strict"; 51 | this.size = size; 52 | this.origin = position; 53 | this.level = level; 54 | this.type = "internal"; 55 | this.empty = false; 56 | // used to index this node vertex inside an index buffer 57 | this.index = -1; 58 | 59 | 60 | this.children = []; 61 | this.vertices = []; 62 | this.material = []; 63 | this.signs = []; 64 | 65 | // glmatrix expects arrays instead of Vec3s 66 | this.qefpositions = []; 67 | this.qefnormals = []; 68 | 69 | this.qefresult = new Vec3(0, 0, 0); 70 | } 71 | 72 | Node.prototype.subdivide = function (maxlevel) { 73 | "use strict"; 74 | var i; 75 | 76 | // assigning this node's vertices positions 77 | for (i = 0; i < 8; i++) { 78 | this.vertices.push(new Vec3( 79 | this.origin.x + children_offsets[i].x * this.size, 80 | this.origin.y + children_offsets[i].y * this.size, 81 | this.origin.z + children_offsets[i].z * this.size 82 | )); 83 | } 84 | 85 | /* displays a possible octree subdivision, used for debugging*/ 86 | /* if(Math.random() < 0.3) { 87 | this.type = "leaf"; 88 | return; 89 | } */ 90 | 91 | 92 | // we can further subdivide the octree 93 | if (this.level < maxlevel) { 94 | for (i = 0; i < 8; i++) { 95 | var ofs = children_offsets[i]; 96 | var hsize = this.size / 2; 97 | var childpos = new Vec3(this.origin.x + ofs.x * hsize, 98 | this.origin.y + ofs.y * hsize, 99 | this.origin.z + ofs.z * hsize); 100 | 101 | this.children[i] = new Node(childpos, hsize, this.level + 1); 102 | this.children[i].subdivide(maxlevel); 103 | } 104 | return; 105 | } 106 | 107 | // if we get here, we're on a leaf node 108 | this.type = "leaf"; 109 | 110 | // initialize the qef result as the center of this leaf node 111 | this.qefresult.x = this.origin.x + this.size / 2; 112 | this.qefresult.y = this.origin.y + this.size / 2; 113 | this.qefresult.z = this.origin.z + this.size / 2; 114 | }; 115 | 116 | /* 117 | Assigns the isovalues at every vertex of a leaf node 118 | by running the provided isofunction which will take the 119 | vertex position 120 | 121 | @isofunction: a function which will check the isovalue of 122 | the current vertex from it's position, whose parameters are 123 | x, y, z and should return either a positive or negative number 124 | */ 125 | Node.prototype.isosurface = function (isofunction) { 126 | "use strict"; 127 | var i = 0; 128 | 129 | // forward isosurface extraction to children 130 | if (this.type !== "leaf") { 131 | for (i = 0; i < 8; i++) { 132 | this.children[i].isosurface(isofunction); 133 | } 134 | return; 135 | } 136 | 137 | // if we get here, we're on a leaf node 138 | for (i = 0; i < 8; i++) { 139 | this.signs[i] = isofunction(this.vertices[i].x, 140 | this.vertices[i].y, 141 | this.vertices[i].z); 142 | 143 | this.material[i] = this.signs[i] >= 0 ? MATERIAL_AIR : MATERIAL_INTERNAL; 144 | } 145 | }; 146 | 147 | Node.prototype.setEmptyNodes = function () { 148 | "use strict"; 149 | 150 | var i = 0; 151 | if (this.type !== "leaf") { 152 | for (i = 0; i < 8; i++) 153 | this.children[i].setEmptyNodes(); 154 | } 155 | 156 | if (this.type === "leaf") { 157 | var material_air_count = 0; 158 | var material_internal_count = 0; 159 | for (i = 0; i < 8; i++) { 160 | if (this.material[i] === MATERIAL_AIR) { 161 | material_air_count++; 162 | } else { 163 | material_internal_count++; 164 | } 165 | } 166 | 167 | if (material_air_count === 8 || material_internal_count === 8) { 168 | this.empty = true; 169 | } 170 | 171 | // nothing else to do on a leaf node at this point 172 | return; 173 | } 174 | 175 | 176 | // if even one of our children is not empty, neither this cell is 177 | this.empty = true; 178 | for (i = 0; i < 8; i++) { 179 | if (!this.children[i].empty) { 180 | this.empty = false; 181 | break; 182 | } 183 | } 184 | }; 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | /* the following functions will be used for rendering only. 196 | they're not related to the structure of an octree's node. 197 | I didn't merge those with the OctreeRenderer class since 198 | it was easier to debug nodes this way 199 | */ 200 | 201 | 202 | 203 | /* 204 | Used for debugging. 205 | Will recursively fill the provided buffer with this node's line vertices 206 | and the vertices of each child. 207 | 208 | @buffer an array filled with lines vertices 209 | */ 210 | Node.prototype.getWireframeVertices = function (buffer) { 211 | "use strict"; 212 | var i = 0; 213 | 214 | if (this.empty) return; 215 | 216 | // construct the 12 edges lines from this node's vertices 217 | for (i = 0; i < 12; i++) { 218 | var v1_index = edgevmap[i][0]; 219 | var v2_index = edgevmap[i][1]; 220 | 221 | buffer.push(this.vertices[v1_index].x, 222 | this.vertices[v1_index].y, 223 | this.vertices[v1_index].z, 224 | 225 | this.vertices[v2_index].x, 226 | this.vertices[v2_index].y, 227 | this.vertices[v2_index].z); 228 | } 229 | 230 | // forward octreeVertices extraction to children 231 | if (this.type !== "leaf") { 232 | for (i = 0; i < 8; i++) { 233 | this.children[i].getWireframeVertices(buffer); 234 | } 235 | } 236 | }; 237 | 238 | /* 239 | Used for debugging 240 | */ 241 | Node.prototype.getPointVertices = function (buffer) { 242 | "use strict"; 243 | var i = 0; 244 | 245 | if (this.empty) return; 246 | 247 | // forward octreeVertices extraction to children 248 | if (this.type !== "leaf") { 249 | for (i = 0; i < 8; i++) { 250 | this.children[i].getPointVertices(buffer); 251 | } 252 | return; 253 | } 254 | 255 | // returns only the sign of the first vertex for now 256 | buffer.push(this.qefresult.x, 257 | this.qefresult.y, 258 | this.qefresult.z); 259 | }; 260 | 261 | /* 262 | will assign vertices values and indexes to bufferObject 263 | @bufferObject will hold a flat buffer to push vertices positions and 264 | a currentIndex property to increment after each leaf is 265 | evaluated 266 | 267 | it expects a bufferObject Object with the following properties: 268 | { 269 | vertexbuffer: @type array 270 | currentIndex: @type number 271 | } 272 | since the currentIndex state needs to persist between recursive calls 273 | */ 274 | Node.prototype.assignIndexes = function (bufferObject) { 275 | "use strict"; 276 | 277 | // forward assignment to leaf only 278 | var i = 0; 279 | if (this.type !== "leaf") { 280 | for (i = 0; i < 8; i++) { 281 | this.children[i].assignIndexes(bufferObject); 282 | } 283 | return; 284 | } 285 | 286 | 287 | 288 | // if we get here we're on a leaf node 289 | 290 | // we could decide to index and store only nodes with sign changes 291 | // if(this.empty) return; 292 | 293 | bufferObject.vertexbuffer.push(this.qefresult.x, 294 | this.qefresult.y, 295 | this.qefresult.z); 296 | 297 | this.index = bufferObject.currentIndex; 298 | bufferObject.currentIndex++; 299 | }; 300 | 301 | /* 302 | Will compute this node's crossing points linearly and it's normal 303 | with finite differences 304 | */ 305 | Node.prototype.crossingPoints = function(isofunction) { 306 | 307 | var i = 0; 308 | if(this.type !== "leaf") { 309 | for(i = 0; i < 8; i++) { 310 | this.children[i].crossingPoints(isofunction); 311 | } 312 | return; 313 | } 314 | 315 | var edgeCount = 0; 316 | for (i = 0; i < 12 && edgeCount < MAX_CROSSINGS; i++) { 317 | var v1 = edgevmap[i][0]; 318 | var v2 = edgevmap[i][1]; 319 | 320 | var s1 = this.material[v1]; 321 | var s2 = this.material[v2]; 322 | 323 | if ((s1 === MATERIAL_AIR && s2 === MATERIAL_AIR) || 324 | (s1 === MATERIAL_INTERNAL && s2 === MATERIAL_INTERNAL)) { 325 | // no zero crossing on this edge 326 | continue; 327 | } 328 | 329 | var p1 = this.vertices[v1]; 330 | var p2 = this.vertices[v2]; 331 | 332 | 333 | // compute zero crossing point Linearly 334 | var t = Math.abs(this.signs[v1] / (this.signs[v2] - this.signs[v1])); 335 | var p = [ 336 | p1.x + (p2.x - p1.x) * t, 337 | p1.y + (p2.y - p1.y) * t, 338 | p1.z + (p2.z - p1.z) * t 339 | ]; 340 | this.qefpositions[edgeCount] = p; 341 | 342 | 343 | // compute surface normal with finite differences and find the rate of change 344 | var h = 0.001; 345 | var dx = isofunction(p[0] + h, p[1], p[2]) - isofunction(p[0] - h, p[1], p[2]); 346 | var dy = isofunction(p[0], p[1] + h, p[2]) - isofunction(p[0], p[1] - h, p[2]); 347 | var dz = isofunction(p[0], p[1], p[2] + h) - isofunction(p[0], p[1], p[2] - h); 348 | this.qefnormals[edgeCount] = [dx, dy, dz]; 349 | vec3.normalize(this.qefnormals[edgeCount], this.qefnormals[edgeCount]); 350 | 351 | edgeCount++; 352 | } 353 | 354 | var averagePos = [0,0,0]; 355 | var averageNormal = [0,0,0]; 356 | for(var i = 0; i < this.qefpositions.length; i++) { 357 | averagePos[0] += this.qefpositions[i][0]; 358 | averagePos[1] += this.qefpositions[i][1]; 359 | averagePos[2] += this.qefpositions[i][2]; 360 | } 361 | 362 | averagePos[0] /= this.qefpositions.length; 363 | averagePos[1] /= this.qefpositions.length; 364 | averagePos[2] /= this.qefpositions.length; 365 | 366 | this.qefresult.x = averagePos[0]; 367 | this.qefresult.y = averagePos[1]; 368 | this.qefresult.z = averagePos[2]; 369 | }; 370 | 371 | 372 | /* 373 | Countour Cell Procs routines may receive more than one child, 374 | we can't therefore add them as Node.prototype's memebers 375 | */ 376 | 377 | function ContourCellProc(node, indexBuffer) { 378 | if (!node) { 379 | return; 380 | } 381 | 382 | var i = 0; 383 | var j = 0; 384 | if (node.type === "internal") { 385 | for (i = 0; i < 8; i++) { 386 | ContourCellProc(node.children[i], indexBuffer); 387 | } 388 | 389 | for (i = 0; i < 12; i++) { 390 | faceNodes = [-1, -1]; 391 | var c0 = cellProcFaceMask[i][0]; 392 | var c1 = cellProcFaceMask[i][1]; 393 | 394 | faceNodes[0] = node.children[c0]; 395 | faceNodes[1] = node.children[c1]; 396 | 397 | ContourFaceProc(faceNodes, cellProcFaceMask[i][2], indexBuffer); 398 | } 399 | 400 | for (i = 0; i < 6; i++) { 401 | edgeNodes = [-1, -1, -1, -1]; 402 | c = [ 403 | cellProcEdgeMask[i][0], 404 | cellProcEdgeMask[i][1], 405 | cellProcEdgeMask[i][2], 406 | cellProcEdgeMask[i][3], 407 | ]; 408 | 409 | for (j = 0; j < 4; j++) { 410 | edgeNodes[j] = node.children[c[j]]; 411 | } 412 | 413 | ContourEdgeProc(edgeNodes, cellProcEdgeMask[i][4], indexBuffer); 414 | } 415 | } 416 | } 417 | 418 | function ContourFaceProc(nodes, dir, indexBuffer) { 419 | if (!nodes[0] || !nodes[1]) { 420 | return; 421 | } 422 | 423 | // TODO: check if tao ju declared that no leaf node could access this statement and that 424 | // both nodes needs to be internal 425 | var i = 0; 426 | var j = 0; 427 | var c = []; 428 | if (nodes[0].type === "internal" || 429 | nodes[1].type === "internal") { 430 | for (i = 0; i < 4; i++) { 431 | var faceNodes = [-1, -1]; 432 | c = [ 433 | faceProcFaceMask[dir][i][0], 434 | faceProcFaceMask[dir][i][1] 435 | ]; 436 | 437 | for (j = 0; j < 2; j++) { 438 | if (nodes[j].type !== "internal") { 439 | faceNodes[j] = nodes[j]; 440 | } 441 | else { 442 | faceNodes[j] = nodes[j].children[c[j]]; 443 | } 444 | } 445 | 446 | ContourFaceProc(faceNodes, faceProcFaceMask[dir][i][2], indexBuffer); 447 | } 448 | 449 | var orders/*[2][4]*/ = 450 | [ 451 | [0, 0, 1, 1], 452 | [0, 1, 0, 1] 453 | ]; 454 | /* 455 | const int faceProcEdgeMask[3][4][6] = { 456 | {{1,4,0,5,1,1},{1,6,2,7,3,1},{0,4,6,0,2,2},{0,5,7,1,3,2}}, 457 | {{0,2,3,0,1,0},{0,6,7,4,5,0},{1,2,0,6,4,2},{1,3,1,7,5,2}}, 458 | {{1,1,0,3,2,0},{1,5,4,7,6,0},{0,1,5,0,4,1},{0,3,7,2,6,1}} 459 | };*/ 460 | for (i = 0; i < 4; i++) { 461 | var edgeNodes = [-1, -1, -1, -1]; 462 | c = [ 463 | faceProcEdgeMask[dir][i][1], 464 | faceProcEdgeMask[dir][i][2], 465 | faceProcEdgeMask[dir][i][3], 466 | faceProcEdgeMask[dir][i][4] 467 | ]; 468 | 469 | var order = orders[faceProcEdgeMask[dir][i][0]]; 470 | for (j = 0; j < 4; j++) { 471 | if (nodes[order[j]].type === "leaf") { 472 | edgeNodes[j] = nodes[order[j]]; 473 | } 474 | else { 475 | edgeNodes[j] = nodes[order[j]].children[c[j]]; 476 | } 477 | } 478 | 479 | ContourEdgeProc(edgeNodes, faceProcEdgeMask[dir][i][5], indexBuffer); 480 | } 481 | } 482 | } 483 | 484 | function ContourEdgeProc(nodes, dir, indexBuffer) { 485 | // se anche UNO dei nodi che ci sono arrivati non esiste, return. 486 | if (!nodes[0] || !nodes[1] || !nodes[2] || !nodes[3]) { 487 | return; 488 | } 489 | 490 | 491 | var i = 0; 492 | var j = 0; 493 | if (nodes[0].type !== "internal" && 494 | nodes[1].type !== "internal" && 495 | nodes[2].type !== "internal" && 496 | nodes[3].type !== "internal") { 497 | ContourProcessEdge(nodes, dir, indexBuffer); 498 | } 499 | else { 500 | for (i = 0; i < 2; i++) { 501 | edgeNodes = [-1, -1, -1, -1]; 502 | var c = [ 503 | // se uno dei nodi non è leaf, qual è il children di questo nodo 504 | // che puo' essere apparato agli altri leaf per formare 4 nodi da 505 | // passare di nuovo a contourEdgeProc(); ? 506 | edgeProcEdgeMask[dir][i][0], 507 | edgeProcEdgeMask[dir][i][1], 508 | edgeProcEdgeMask[dir][i][2], 509 | edgeProcEdgeMask[dir][i][3] 510 | ]; 511 | 512 | for (j = 0; j < 4; j++) { 513 | if (nodes[j].type === "leaf") { 514 | edgeNodes[j] = nodes[j]; 515 | } 516 | else { 517 | edgeNodes[j] = nodes[j].children[c[j]]; 518 | } 519 | } 520 | 521 | ContourEdgeProc(edgeNodes, edgeProcEdgeMask[dir][i][4], indexBuffer); 522 | } 523 | } 524 | } 525 | 526 | function ContourProcessEdge(nodes, dir, indexBuffer) { 527 | var minSize = 1000000; // arbitrary big number 528 | var minIndex = 0; 529 | var indices = [-1, -1, -1, -1]; 530 | var flip = false; 531 | var signChange = [false, false, false, false]; 532 | 533 | var i = 0; 534 | for (i = 0; i < 4; i++) { 535 | 536 | // edge in comune con i 4 nodi 537 | var edge = processEdgeMask[dir][i]; 538 | // vertici di quest'edge 539 | var c1 = edgevmap[edge][0]; 540 | var c2 = edgevmap[edge][1]; 541 | 542 | // signs di questi vertici 543 | var m1 = nodes[i].material[c1]; 544 | var m2 = nodes[i].material[c2]; 545 | 546 | var flip = m1 !== MATERIAL_AIR; 547 | 548 | indices[i] = nodes[i].index; 549 | 550 | signChange[i] = (m1 === MATERIAL_AIR && m2 === MATERIAL_INTERNAL) || 551 | (m1 === MATERIAL_INTERNAL && m2 === MATERIAL_AIR); 552 | } 553 | 554 | if (signChange[0]) { 555 | if (!flip) { 556 | indexBuffer.push(indices[0]); 557 | indexBuffer.push(indices[1]); 558 | indexBuffer.push(indices[3]); 559 | 560 | indexBuffer.push(indices[0]); 561 | indexBuffer.push(indices[3]); 562 | indexBuffer.push(indices[2]); 563 | } 564 | else { 565 | indexBuffer.push(indices[0]); 566 | indexBuffer.push(indices[3]); 567 | indexBuffer.push(indices[1]); 568 | 569 | indexBuffer.push(indices[0]); 570 | indexBuffer.push(indices[2]); 571 | indexBuffer.push(indices[3]); 572 | } 573 | } 574 | } -------------------------------------------------------------------------------- /lib/octree.js: -------------------------------------------------------------------------------- 1 | function Octree(origin, size, maxlevel) { 2 | "use strict"; 3 | this.origin = origin; 4 | this.size = size; 5 | this.maxlevel = maxlevel; 6 | 7 | this.rootNode = new Node(origin, size, 0); 8 | this.rootNode.subdivide(maxlevel); 9 | } 10 | 11 | Octree.prototype.isosurface = function(isofunction) { 12 | this.rootNode.isosurface(isofunction); 13 | this.rootNode.setEmptyNodes(); 14 | this.rootNode.crossingPoints(isofunction); 15 | }; 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | function OctreeRenderer(ctx, octree) { 24 | "use strict"; 25 | this.ctx = ctx; 26 | this.octree = octree; 27 | 28 | this.view = -1; 29 | this.projection = -1; 30 | 31 | this.vertex_buffer = []; 32 | this.index_buffer = []; 33 | 34 | this.wireframe_buffer = []; 35 | this.points_buffer = []; 36 | 37 | this.oct_wire_program = this.createOctreeWireframeProgram(); 38 | this.oct_points_program = this.createOctreePointsProgram(); 39 | this.oct_mesh_program = this.createOctreeMeshProgram(); 40 | 41 | this.oct_wireframe_num_verts = -1; 42 | this.oct_points_num_verts = -1; 43 | this.oct_num_verts = -1; 44 | this.oct_num_indexes = -1; 45 | } 46 | 47 | OctreeRenderer.prototype.createOctreeWireframeProgram = function() { 48 | "use strict"; 49 | 50 | var vertex_shader = 51 | "attribute vec3 pos;" + 52 | "uniform mat4 projection;" + 53 | "uniform mat4 view;" + 54 | "" + 55 | "varying float depth;" + 56 | "" + 57 | "void main() {" + 58 | " vec4 ndcpos = projection * view * vec4(pos, 1.0);" + 59 | " gl_Position = ndcpos;" + 60 | " depth = pow((pos.z + 5.0) / 10.0, 2.0) * 0.8 + 0.2;" + 61 | "}"; 62 | 63 | var fragment_shader = 64 | "precision mediump float;" + 65 | "" + 66 | "varying float depth;" + 67 | "" + 68 | "void main() {" + 69 | " gl_FragColor = vec4(vec3(depth * 0.3), 1.0);" + 70 | "}"; 71 | 72 | var Program = createProgramFromSource(vertex_shader, fragment_shader, this.ctx); 73 | Program.a1 = this.ctx.getAttribLocation(Program, "pos"); 74 | 75 | Program.projection = this.ctx.getUniformLocation(Program, "projection"); 76 | Program.view = this.ctx.getUniformLocation(Program, "view"); 77 | 78 | Program.buffer = this.ctx.createBuffer(); 79 | 80 | return Program; 81 | }; 82 | 83 | OctreeRenderer.prototype.createOctreePointsProgram = function() { 84 | "use strict"; 85 | 86 | var vertex_shader = 87 | "attribute vec3 pos;" + 88 | "uniform mat4 projection;" + 89 | "uniform mat4 view;" + 90 | "" + 91 | "varying float depth;" + 92 | "" + 93 | "void main() {" + 94 | " vec4 ndcpos = projection * view * vec4(pos, 1.0);" + 95 | " gl_Position = ndcpos;" + 96 | " gl_PointSize = 9.0;" + 97 | " depth = pow((pos.z + 5.0) / 10.0, 2.0) * 0.8 + 0.2;" + 98 | "}"; 99 | 100 | var fragment_shader = 101 | "precision mediump float;" + 102 | "" + 103 | "varying float depth;" + 104 | "" + 105 | "void main() {" + 106 | " gl_FragColor = vec4(vec3(1.0 * depth, 0.0, 0.0), 1.0);" + 107 | "}"; 108 | 109 | var Program = createProgramFromSource(vertex_shader, fragment_shader, this.ctx); 110 | Program.a1 = this.ctx.getAttribLocation(Program, "pos"); 111 | 112 | Program.projection = this.ctx.getUniformLocation(Program, "projection"); 113 | Program.view = this.ctx.getUniformLocation(Program, "view"); 114 | 115 | Program.buffer = this.ctx.createBuffer(); 116 | 117 | return Program; 118 | }; 119 | 120 | OctreeRenderer.prototype.createOctreeMeshProgram = function() { 121 | "use strict"; 122 | 123 | var vertex_shader = 124 | "attribute vec3 pos;" + 125 | "uniform mat4 projection;" + 126 | "uniform mat4 view;" + 127 | "" + 128 | "varying float depth;" + 129 | "" + 130 | "void main() {" + 131 | " vec4 ndcpos = projection * view * vec4(pos, 1.0);" + 132 | " gl_Position = ndcpos;" + 133 | " depth = pow((pos.z + 5.0) / 10.0, 2.0) * 0.8 + 0.2;" + 134 | "}"; 135 | 136 | var fragment_shader = 137 | "precision mediump float;" + 138 | "" + 139 | "varying float depth;" + 140 | "" + 141 | "void main() {" + 142 | " gl_FragColor = vec4(vec3(depth, depth, depth), 1.0);" + 143 | "}"; 144 | 145 | var Program = createProgramFromSource(vertex_shader, fragment_shader, this.ctx); 146 | Program.a1 = this.ctx.getAttribLocation(Program, "pos"); 147 | 148 | Program.projection = this.ctx.getUniformLocation(Program, "projection"); 149 | Program.view = this.ctx.getUniformLocation(Program, "view"); 150 | 151 | Program.vbuffer = this.ctx.createBuffer(); 152 | Program.ibuffer = this.ctx.createBuffer(); 153 | 154 | return Program; 155 | }; 156 | 157 | OctreeRenderer.prototype.createOctreeWireframeGeometry = function() { 158 | "use strict"; 159 | this.octree.rootNode.getWireframeVertices(this.wireframe_buffer); 160 | this.oct_wireframe_num_verts = this.wireframe_buffer.length / 3; 161 | this.ctx.bindBuffer(this.ctx.ARRAY_BUFFER, this.oct_wire_program.buffer); 162 | this.ctx.bufferData(this.ctx.ARRAY_BUFFER, new Float32Array(this.wireframe_buffer), this.ctx.STATIC_DRAW); 163 | }; 164 | 165 | OctreeRenderer.prototype.createOctreePointsGeometry = function() { 166 | "use strict"; 167 | this.octree.rootNode.getPointVertices(this.points_buffer); 168 | this.oct_points_num_verts = this.points_buffer.length / 3; 169 | this.ctx.bindBuffer(this.ctx.ARRAY_BUFFER, this.oct_points_program.buffer); 170 | this.ctx.bufferData(this.ctx.ARRAY_BUFFER, new Float32Array(this.points_buffer), this.ctx.STATIC_DRAW); 171 | }; 172 | 173 | OctreeRenderer.prototype.createOctreeMeshGeometry = function() { 174 | "use strict"; 175 | this.octree.rootNode.assignIndexes({ vertexbuffer: this.vertex_buffer, currentIndex: 0}); 176 | this.oct_num_verts = this.vertex_buffer.length / 3; 177 | this.ctx.bindBuffer(this.ctx.ARRAY_BUFFER, this.oct_mesh_program.vbuffer); 178 | this.ctx.bufferData(this.ctx.ARRAY_BUFFER, new Float32Array(this.vertex_buffer), this.ctx.STATIC_DRAW); 179 | 180 | 181 | ContourCellProc(this.octree.rootNode, this.index_buffer); 182 | this.oct_num_indexes = this.index_buffer.length; 183 | this.ctx.bindBuffer(this.ctx.ELEMENT_ARRAY_BUFFER, this.oct_mesh_program.ibuffer); 184 | this.ctx.bufferData(this.ctx.ELEMENT_ARRAY_BUFFER, new Uint16Array(this.index_buffer), this.ctx.STATIC_DRAW); 185 | }; 186 | 187 | OctreeRenderer.prototype.drawOctreeWireFrame = function() { 188 | "use strict"; 189 | this.ctx.useProgram(this.oct_wire_program); 190 | this.ctx.bindBuffer(this.ctx.ARRAY_BUFFER, this.oct_wire_program.buffer); 191 | 192 | this.ctx.enableVertexAttribArray(this.oct_wire_program.a1); 193 | this.ctx.vertexAttribPointer(this.oct_wire_program.a1, 3, this.ctx.FLOAT, false, 0, 0); 194 | 195 | this.ctx.uniformMatrix4fv(this.oct_wire_program.projection, false, this.projection); 196 | this.ctx.uniformMatrix4fv(this.oct_wire_program.view, false, this.view); 197 | 198 | this.ctx.enable(this.ctx.DEPTH_TEST); 199 | this.ctx.drawArrays(this.ctx.LINES, 0, this.oct_wireframe_num_verts); 200 | this.ctx.disable(this.ctx.DEPTH_TEST); 201 | }; 202 | 203 | OctreeRenderer.prototype.drawOctreePoints = function() { 204 | "use strict"; 205 | this.ctx.useProgram(this.oct_points_program); 206 | this.ctx.bindBuffer(this.ctx.ARRAY_BUFFER, this.oct_points_program.buffer); 207 | 208 | this.ctx.enableVertexAttribArray(this.oct_points_program.a1); 209 | this.ctx.vertexAttribPointer(this.oct_points_program.a1, 3, this.ctx.FLOAT, false, 0, 0); 210 | 211 | this.ctx.uniformMatrix4fv(this.oct_points_program.projection, false, this.projection); 212 | this.ctx.uniformMatrix4fv(this.oct_points_program.view, false, this.view); 213 | 214 | this.ctx.enable(this.ctx.DEPTH_TEST); 215 | this.ctx.drawArrays(this.ctx.POINTS, 0, this.oct_points_num_verts); 216 | this.ctx.disable(this.ctx.DEPTH_TEST); 217 | }; 218 | 219 | OctreeRenderer.prototype.drawOctreeMesh = function() { 220 | "use strict"; 221 | this.ctx.useProgram(this.oct_mesh_program); 222 | this.ctx.bindBuffer(this.ctx.ELEMENT_ARRAY_BUFFER, this.oct_mesh_program.ibuffer); 223 | this.ctx.bindBuffer(this.ctx.ARRAY_BUFFER, this.oct_mesh_program.vbuffer); 224 | 225 | this.ctx.enableVertexAttribArray(this.oct_mesh_program.a1); 226 | this.ctx.vertexAttribPointer(this.oct_mesh_program.a1, 3, this.ctx.FLOAT, false, 0, 0); 227 | 228 | this.ctx.uniformMatrix4fv(this.oct_mesh_program.projection, false, this.projection); 229 | this.ctx.uniformMatrix4fv(this.oct_mesh_program.view, false, this.view); 230 | 231 | this.ctx.enable(this.ctx.DEPTH_TEST); 232 | this.ctx.drawElements(this.ctx.TRIANGLES, this.oct_num_indexes, this.ctx.UNSIGNED_SHORT, 0); 233 | this.ctx.disable(this.ctx.DEPTH_TEST); 234 | }; -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | function Vec3(x, y, z) { 2 | "use strict"; 3 | this.x = x; 4 | this.y = y; 5 | this.z = z; 6 | } 7 | 8 | // creates a shader Program directly from source with the given context 9 | function createProgramFromSource(vertexSource, fragmentSource, ctx) { 10 | var vs = createShaderFromSource(vertexSource, "vert", ctx); 11 | var fs = createShaderFromSource(fragmentSource, "frag", ctx); 12 | 13 | var Program = ctx.createProgram(); 14 | 15 | ctx.attachShader(Program, vs); 16 | ctx.attachShader(Program, fs); 17 | ctx.linkProgram(Program); 18 | 19 | 20 | if (!ctx.getProgramParameter(Program, ctx.LINK_STATUS)) 21 | { 22 | alert("Could not initialise shaders"); 23 | return null; 24 | } 25 | 26 | return Program; 27 | } 28 | 29 | function createShaderFromSource(source, type, ctx) { 30 | var shader; 31 | if (type == "frag") { 32 | shader = ctx.createShader(ctx.FRAGMENT_SHADER); 33 | } else if (type == "vert") { 34 | shader = ctx.createShader(ctx.VERTEX_SHADER); 35 | } else { 36 | return null; 37 | } 38 | ctx.shaderSource(shader, source); 39 | ctx.compileShader(shader); 40 | 41 | if (!ctx.getShaderParameter(shader, ctx.COMPILE_STATUS)) { 42 | alert(ctx.getShaderInfoLog(shader) + " " + type); 43 | return null; 44 | } 45 | 46 | return shader; 47 | } 48 | -------------------------------------------------------------------------------- /screenshots/ccpedges.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Domenicobrz/Dual-Contouring-javascript-implementation/400427d30ec776cd9348c427c1b7f53dfa4491d9/screenshots/ccpedges.png -------------------------------------------------------------------------------- /screenshots/ccpfaces.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Domenicobrz/Dual-Contouring-javascript-implementation/400427d30ec776cd9348c427c1b7f53dfa4491d9/screenshots/ccpfaces.png -------------------------------------------------------------------------------- /screenshots/childrenorder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Domenicobrz/Dual-Contouring-javascript-implementation/400427d30ec776cd9348c427c1b7f53dfa4491d9/screenshots/childrenorder.png -------------------------------------------------------------------------------- /screenshots/contourEdgeProc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Domenicobrz/Dual-Contouring-javascript-implementation/400427d30ec776cd9348c427c1b7f53dfa4491d9/screenshots/contourEdgeProc.png -------------------------------------------------------------------------------- /screenshots/fpe.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Domenicobrz/Dual-Contouring-javascript-implementation/400427d30ec776cd9348c427c1b7f53dfa4491d9/screenshots/fpe.jpg -------------------------------------------------------------------------------- /screenshots/fpe2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Domenicobrz/Dual-Contouring-javascript-implementation/400427d30ec776cd9348c427c1b7f53dfa4491d9/screenshots/fpe2.png -------------------------------------------------------------------------------- /screenshots/fpf.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Domenicobrz/Dual-Contouring-javascript-implementation/400427d30ec776cd9348c427c1b7f53dfa4491d9/screenshots/fpf.jpg -------------------------------------------------------------------------------- /screenshots/gitscreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Domenicobrz/Dual-Contouring-javascript-implementation/400427d30ec776cd9348c427c1b7f53dfa4491d9/screenshots/gitscreen.png -------------------------------------------------------------------------------- /screenshots/octree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Domenicobrz/Dual-Contouring-javascript-implementation/400427d30ec776cd9348c427c1b7f53dfa4491d9/screenshots/octree.png -------------------------------------------------------------------------------- /screenshots/octree2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Domenicobrz/Dual-Contouring-javascript-implementation/400427d30ec776cd9348c427c1b7f53dfa4491d9/screenshots/octree2.png -------------------------------------------------------------------------------- /screenshots/octree3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Domenicobrz/Dual-Contouring-javascript-implementation/400427d30ec776cd9348c427c1b7f53dfa4491d9/screenshots/octree3.png -------------------------------------------------------------------------------- /screenshots/octree4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Domenicobrz/Dual-Contouring-javascript-implementation/400427d30ec776cd9348c427c1b7f53dfa4491d9/screenshots/octree4.png -------------------------------------------------------------------------------- /screenshots/octree5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Domenicobrz/Dual-Contouring-javascript-implementation/400427d30ec776cd9348c427c1b7f53dfa4491d9/screenshots/octree5.png -------------------------------------------------------------------------------- /screenshots/vertorder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Domenicobrz/Dual-Contouring-javascript-implementation/400427d30ec776cd9348c427c1b7f53dfa4491d9/screenshots/vertorder.png --------------------------------------------------------------------------------