diff --git a/hidden.js b/hidden.js index 2ac57bd..3058a8b 100644 --- a/hidden.js +++ b/hidden.js @@ -264,22 +264,19 @@ function intercept_lines(p0,p1,p2,p3) // returns a list of segments that are visible // TODO: best if triangles is sorted by z depth // TODO: figure out a better representation for the screen map -function hidden_wire(s, screen_map, work_queue) +function hidden_wire(s, interval, work_queue) { let segments = []; - let min_key_x = Math.trunc(Math.min(s.p0.x, s.p1.x) / stl_key2d_scale); - let min_key_y = Math.trunc(Math.min(s.p0.y, s.p1.y) / stl_key2d_scale); - let max_key_x = Math.trunc(Math.max(s.p0.x, s.p1.x) / stl_key2d_scale); - let max_key_y = Math.trunc(Math.max(s.p0.y, s.p1.y) / stl_key2d_scale); + if (!p_max) p_max = createVector(); + if (!p_min) p_min = createVector(); - for(let x = min_key_x ; x <= max_key_x ; x++) - { - for(let y = min_key_y ; y <= max_key_y ; y++) - { - let triangles = screen_map[x + "," + y]; - if (!triangles) - continue; + v3max(p_max, s.p0, s.p1); + v3min(p_min, s.p0, s.p1); + + let triangles = interval.query(p_min.x,p_min.y,p_max.x,p_max.y); + //console.log("occlude ", triangles.length); + //triangles.sort((a,b) => a.min.z - b.min.z); for(let t of triangles) { @@ -300,7 +297,10 @@ function hidden_wire(s, screen_map, work_queue) // this triangle, which means that no other // triangles on the sorted list can occlude // the segment, so we're done. - break; + //break; + + // but the interval tree isn't sorted (yet) + continue; } if (rc == tri_clipped @@ -317,8 +317,6 @@ function hidden_wire(s, screen_map, work_queue) // huh? console.log("occlude() returned? ", rc) } - } - } // if we have made it all the way here, the remaining part // of this segment is visible and should be added to the draw list diff --git a/index.html b/index.html index 3e3dd4e..a6fb02b 100644 --- a/index.html +++ b/index.html @@ -7,6 +7,7 @@ + diff --git a/interval.js b/interval.js new file mode 100644 index 0000000..c0c12a8 --- /dev/null +++ b/interval.js @@ -0,0 +1,92 @@ +/* + * 2D interval tree for sorting triangles. + */ + +function Interval(x,y,scale) +{ + this.x = x; + this.y = y; + this.scale = scale; + + console.log("Interval("+x+","+y+","+scale+")"); + + this.quads = [ + null, + null, + null, + null, + ]; + + this.span = []; + + this.quad_mask = function (min_x,min_y,max_x,max_y) + { + let q0 = (this.x < max_x && this.y < max_y) ? 0x01: 0x00; + let q1 = (min_x < this.x && this.y < max_y) ? 0x02: 0x00; + let q2 = (min_x < this.x && min_y < this.y) ? 0x04: 0x00; + let q3 = (this.x < max_x && min_y < this.y) ? 0x08: 0x00; + let span = ((min_x < this.x && this.x < max_x) + || (min_y < this.y && this.y < max_y)) ? 0x10 : 0x00;; + + return span | q0 | q1 | q2 | q3; + }; + + this.insert = function (t,min_x,min_y,max_x,max_y) { + if (min_x > max_x || min_y > max_y) + console.log(t); + + if (this.scale > 8) + { + let quads = this.quad_mask(min_x,min_y,max_x,max_y); + //console.log(t, quads); + + for (let quad = 0 ; quad < 4 ; quad++) + { + if (quads != (1 << quad)) + continue; + return this.quad(quad).insert(t,min_x,min_y,max_x,max_y); + } + } + + // multiple quadrants, put this in the span list + this.span.push(t); + return this; + }; + + this.quad = function (q) { + if (this.quads[q]) + return this.quads[q]; + + let ns = this.scale / 2; + let nx = this.x + (q == 0 || q == 3 ? ns : -ns); + let ny = this.y + (q == 0 || q == 1 ? ns : -ns); + this.quads[q] = new Interval(nx,ny,ns); + return this.quads[q]; + }; + + this.query = function(min_x,min_y,max_x,max_y) { + if (min_x > max_x || min_y > max_y) + console.log("error:", min_x,min_y,max_x,max_y); + + let quads = this.quad_mask(min_x,min_y,max_x,max_y); + let quad_list = []; + + for (let quad = 0 ; quad < 4 ; quad++) + { + if ((quads & (1 << quad)) == 0) + continue; + if (!this.quads[quad]) + continue; + + let new_list = this.quads[quad].query(min_x,min_y,max_x,max_y); + quad_list = quad_list.concat(new_list); + } + + // multiple quadrants? put this in the span list + //if ((quads & 0x10) != 0) + quad_list = quad_list.concat(this.span); + + //console.log("query:", this.x, this.y, min_x,min_y,max_x,max_y, quads, quad_list); + return quad_list; + }; +}; diff --git a/sketch.js b/sketch.js index b333912..6d88126 100644 --- a/sketch.js +++ b/sketch.js @@ -367,6 +367,6 @@ function draw() pop(); // they are dragging; do not try to do any additional work - if (!mouseIsPressed && !vx && !vy) + if (!mouseIsPressed) redraw = stl.do_work(camera, 200); } diff --git a/stl.js b/stl.js index aebb82c..9d475f0 100644 --- a/stl.js +++ b/stl.js @@ -91,16 +91,19 @@ function STL(rawbytes_arraybuffer) this.visible_segments = []; this.segments = []; this.coplanar = []; + this.interval = null; // project the triangles into the screen mapping console.log("projecting triangles"); for(let t of this.triangles) this.project_triangle(t, camera); +/* // sort the screen mapped triangles by Z console.log("sorting triangles"); for(let key in this.screen_map) this.screen_map[key].sort((a,b) => a.min.z - b.min.z); +*/ } this.project_triangle = function(t,camera) @@ -142,7 +145,7 @@ function STL(rawbytes_arraybuffer) this.segments.push({ p0: t2, p1: t0 }); } - +/* // build the screen map for all of the sectors // that might contain this triangle's projection let min_key_x = Math.trunc(t.min.x/stl_key2d_scale); @@ -161,6 +164,7 @@ function STL(rawbytes_arraybuffer) this.screen_map[key] = [t]; } } +*/ } @@ -186,6 +190,7 @@ function STL(rawbytes_arraybuffer) } this.project(camera); + return true; } @@ -194,6 +199,24 @@ function STL(rawbytes_arraybuffer) // only processes some of the segments per call. this.do_hidden = function(camera,ms) { + if (!this.interval) + { + // ofload the interval work until we are + // starting to compute hidden lines + this.interval = new Interval(0,0,width/2); + + // insert the triangle into the interval tree for + // faster queries of segment intersections + for(t of this.triangles) + { + if (t.invisible) + continue; + this.interval.insert(t, t.min.x, t.min.y, t.max.x, t.max.y); + } + + return true; + } + let num_segments = this.segments.length; if (num_segments == 0) return false; @@ -207,7 +230,7 @@ function STL(rawbytes_arraybuffer) while(this.segments.length != 0) { let s = this.segments.shift(); - let visible_segment = hidden_wire(s, this.screen_map, this.segments); + let visible_segment = hidden_wire(s, this.interval, this.segments); if (visible_segment) this.visible_segments.push(visible_segment); count++; diff --git a/triangle.js b/triangle.js index 0c355e3..7c5dca5 100644 --- a/triangle.js +++ b/triangle.js @@ -75,6 +75,10 @@ function Triangle(p0, p1, p2) // either off-screen or backface culling this.invisible = false; + // discarded triangles are completely eliminated, so they will + // not be processed ever again. + this.discarded = false; + // compute the coordinates in screen space and decide // if it is onscreen or backfaced culled @@ -83,6 +87,9 @@ function Triangle(p0, p1, p2) this.generation = generation; this.invisible = true; // assume it will be discarded + if (this.discarded) + return false; + let s0 = camera.project(this.model[0], this.screen[0]); let s1 = camera.project(this.model[1], this.screen[1]); let s2 = camera.project(this.model[2], this.screen[2]); @@ -161,7 +168,10 @@ function Triangle(p0, p1, p2) // all three points match; this must be a duplicate // triangle of some sort. if (matches == 0b111) - console.log("three points match? " + this + " " + t); + { + //console.log("three points match? " + this + " " + t); + return -1; + } return 0; } @@ -176,6 +186,12 @@ function Triangle(p0, p1, p2) for(let t of triangle_map[stl_key3d(p)]) { let edges = this.coplanar_check(t); + if (edges === -1) + { + t.discarded = true; + continue; + } + if (edges == 0) continue;