/* * Parse ascii or binary STL file into a list of triangles * Binary Header (84 bytes): * 80 byte name * uint32_t number of triangles * * Binary Triangle (50 bytes) * 3 32-bit float normals * 9 32-bit float x,y,z tripples * uint16_t attributes (ignored) */ let x_offset; let y_offset; let z_scale = 1; dark_mode = true; redblue_mode = false; // blue color suggested by https://mastodon.sdf.org/@elb/105351977660915938 red_color = 0xff0000; blue_color = 0x14ecfc; verbose = false; stl = false; stl2 = false; let camera; let camera2; // for 3D eye_separation = 2; redraw = false; reproject = false; let vx = 0; let vy = 0; let vz = 0; let last_x = 0; let last_y = 0; let move_lookat = false; let camera_psi = 0; let camera_theta = 0; let camera_radius = 100; let start_time = 0; let tri_per_sec = 0; function computeEye() { // normalize theta and psi if (camera_theta < -Math.PI) camera_theta += 2 * Math.PI; else if (camera_theta > +Math.PI) camera_theta -= 2 * Math.PI; if (camera_psi < -Math.PI) camera_psi += 2 * Math.PI; else if (camera_psi > +Math.PI) camera_psi -= 2 * Math.PI; camera.eye.x = camera_radius * Math.sin(camera_theta) * Math.sin(camera_psi); camera.eye.y = camera_radius * Math.sin(camera_theta) * Math.cos(camera_psi); camera.eye.z = camera_radius * Math.cos(camera_theta); if (camera_theta < 0) camera.up.z = -1; else camera.up.z = +1; camera.eye.add(camera.lookat); camera.update_matrix(); // duplicate for 3D (lookat is shared) // should scale the eye separation based on the radius since // otherwise it becomes weird at long distances // in DARK mode, the eye glasses seem to be backwards? // normally left eye is red, right eye is blue, but // that messes up with a dark background. camera2.eye.x = camera_radius * Math.sin(camera_theta) * Math.sin(camera_psi + eye_separation * Math.PI / 180); camera2.eye.y = camera_radius * Math.sin(camera_theta) * Math.cos(camera_psi + eye_separation * Math.PI / 180); camera2.eye.z = camera_radius * Math.cos(camera_theta); // the lookat and up values are shared between the cameras camera2.eye.add(camera.lookat); camera2.update_matrix(); } function loadBytes(file, callback) { let oReq = new XMLHttpRequest(); oReq.open("GET", file, true); oReq.responseType = "arraybuffer"; oReq.onload = function(oEvent) { let arrayBuffer = oReq.response; if (arrayBuffer && callback) { callback(arrayBuffer); } } oReq.send(null); } function setup() { let canvas = createCanvas(windowWidth-10, windowHeight-30); // WEBGL? x_offset = width/2; y_offset = height/2; // Move the canvas so it’s inside our
. canvas.parent('sketch-holder'); //createCanvas(1000, 1080); // WEBGL? background(0); loadBytes("test.stl", function(d){ stl = new STL(d); stl2 = new STL(d); reproject = true; }); // initial viewport vx = vy = vz = 0; camera_theta = 70 * Math.PI / 180; camera_psi = -150 * Math.PI / 180; camera_radius = 170; let eye = createVector(0,camera_radius,0); let eye2 = createVector(0,camera_radius,0); let lookat = createVector(0,0,00); let up = createVector(0,0,1); let fov = 60; camera = new Camera(eye,lookat,up,fov); camera2 = new Camera(eye2,lookat,up,fov); computeEye(); } function v3_line(p0,p1) { if (verbose) { push() color(255,255,255,40); stroke(0.1); text(p0.z.toFixed(2), p0.x, -p0.y); text(p1.z.toFixed(2), p1.x, -p1.y); pop(); } line(p0.x, -p0.y, p1.x, -p1.y); } function drawAxis(camera, lookat) { // draw an axis marker at the look-at point const origin = camera.project(lookat) const xaxis = camera.project(new p5.Vector(5,0,0).add(lookat)); const yaxis = camera.project(new p5.Vector(0,5,0).add(lookat)); const zaxis = camera.project(new p5.Vector(0,0,5).add(lookat)); strokeWeight(5); if (!xaxis || !yaxis || !zaxis) { // draw them anyway, since no good ordering is possible stroke(255,0,0); if (xaxis) v3_line(origin, xaxis); stroke(0,255,0); if (yaxis) v3_line(origin, yaxis); stroke(0,0,255); if (zaxis) v3_line(origin, zaxis); return; } // draw the axis lines in back-to-front order const xd = xaxis.z; const yd = yaxis.z; const zd = zaxis.z; if (xd > yd && yd > zd) { stroke(255,0,0); v3_line(origin, xaxis); stroke(0,255,0); v3_line(origin, yaxis); stroke(0,0,255); v3_line(origin, zaxis); } else if (xd > zd && zd > yd) { stroke(255,0,0); v3_line(origin, xaxis); stroke(0,0,255); v3_line(origin, zaxis); stroke(0,255,0); v3_line(origin, yaxis); } else if (yd > xd && xd > zd) { stroke(0,255,0); v3_line(origin, yaxis); stroke(255,0,0); v3_line(origin, xaxis); stroke(0,0,255); v3_line(origin, zaxis); } else if (yd > zd && zd > xd) { stroke(0,255,0); v3_line(origin, yaxis); stroke(0,0,255); v3_line(origin, zaxis); stroke(255,0,0); v3_line(origin, xaxis); } else if (zd > xd && xd > yd) { stroke(0,0,255); v3_line(origin, zaxis); stroke(255,0,0); v3_line(origin, xaxis); stroke(0,255,0); v3_line(origin, yaxis); } else if (zd > yd && yd > xd) { stroke(0,0,255); v3_line(origin, zaxis); stroke(0,255,0); v3_line(origin, yaxis); stroke(255,0,0); v3_line(origin, xaxis); } else { // wtf how did we end up here? } } function keyReleased() { vx = vy = vz = 0; move_lookat = false; } function keyPressed() { console.log(keyCode); if (keyCode == SHIFT) move_lookat = true; if (keyCode == LEFT_ARROW) vx = -10; else if (keyCode == RIGHT_ARROW) vx = +10; if (keyCode == UP_ARROW) vz = -10; else if (keyCode == DOWN_ARROW) vz = +10; //return false; } function cameraView(theta,psi) { camera_theta = theta * Math.PI / 180; camera_psi = psi * Math.PI / 180; computeEye(); reproject = true; } function keyTyped() { if (key === 'v') { verbose = !verbose; reproject = true; } if (key === '1') cameraView(90, 0); if (key === '2') cameraView(90, 90); if (key === '3') cameraView(90, 180); if (key === '4') cameraView(90, 270); if (key === '5') cameraView(1, 1); if (key === 't') { redblue_mode = !redblue_mode; reproject = true; } } function mousePressed() { last_x = mouseX; last_y = mouseY; } function mouseWheel(event) { vz = event.delta * 0.5; } function windowResized() { resizeCanvas(windowWidth-10, windowHeight-30); camera.width = width; camera.height = height; x_offset = width/2; y_offset = height/2; reproject = true; } function draw() { if (!stl) return; if (mouseIsPressed && mouseY >= 0) { vx = (mouseX - last_x) * 0.5; vy = (mouseY - last_y) * 0.5; last_x = mouseX; last_y = mouseY; } if (vx != 0 || vy != 0 || vz != 0) { camera_radius += vz; if (camera_radius <= 0) camera_radius = 1; if (move_lookat) { camera.lookat.x += vx; camera.lookat.z += vy; } else { camera_psi += vx * 0.01; camera_theta -= vy * 0.01; } computeEye(); reproject = true; vx = 0; vy = 0; vz = 0; } // if there are segments left to process, continue to force redraw if (!stl || !(redraw || reproject)) return; redraw = false; if(reproject) { reproject = false; redraw = true; stl.project(camera); if (redblue_mode) stl2.project(camera2); start_time = performance.now(); tri_per_sec = 0; } // they are dragging; do not try to do any additional work // and only compute the alterntate view if we're in 3D mode // if there was work done, return true to force another // pass through the draw loop. if (!mouseIsPressed) { if (redblue_mode) stl2.do_work(camera2, 200); stl.do_work(camera, 200); } if (dark_mode) { background(0); fill(9); } else { background(255); fill(253); } noStroke(); textSize(128); textAlign(RIGHT, BOTTOM); text("plotter.vision", width, height); fill(dark_mode ? 150 : 80); textSize(12); textAlign(LEFT, BOTTOM); text("camera " + int(camera.eye.x) + "," + int(camera.eye.y) + "," + int(camera.eye.z), 10, 30); text("lookat " + int(camera.lookat.x) + "," + int(camera.lookat.y) + "," + int(camera.lookat.z), 10, 50); text("theta " + int(camera_theta * 180 / Math.PI), 10, 100); text(" psi " + int(camera_psi * 180 / Math.PI), 10, 120); text(" r " + int(camera_radius), 10, 140); if (stl.segments.length == 0) { if (tri_per_sec == 0) tri_per_sec = int(stl.triangles.length * 1000 / (performance.now() - start_time)); text("tri/s " + tri_per_sec, 10, 180); } push(); translate(x_offset, y_offset); scale(z_scale); // draw all of our in-processing segments lightly strokeWeight(1); if (redblue_mode) stroke(200,0,200,100); else stroke(0,200,0); for(let s of stl.segments) v3_line(s.p0, s.p1); if (verbose) { stroke(100,0,0,100); for(let s of stl.coplanar) v3_line(s.p0, s.p1); } if (stl.segments.length != 0 || (redblue_mode && stl2.segments.length != 0)) { // if there are in process ones, // draw an XYZ axis at the lookat // and keep computing drawAxis(camera, camera.lookat); redraw = true; } else { // all done, this should be our last pass through // the draw loop redraw = false; } // Draw all of our visible segments sharply strokeWeight(1); if (dark_mode) stroke(255,255,255); else stroke(0,0,0); if (redblue_mode) { stroke( ((blue_color) >> 16) & 0xFF, ((blue_color) >> 8) & 0xFF, ((blue_color) >> 0) & 0xFF, 200 ); for(let s of stl2.visible_segments) v3_line(s.p0, s.p1); stroke( ((red_color) >> 16) & 0xFF, ((red_color) >> 8) & 0xFF, ((red_color) >> 0) & 0xFF, 80 ); } for(let s of stl.visible_segments) v3_line(s.p0, s.p1); pop(); }