├── 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
--------------------------------------------------------------------------------