Index: draw.html ================================================================== --- draw.html +++ draw.html @@ -1,10 +1,11 @@ + ADDED wireframe_model.js Index: wireframe_model.js ================================================================== --- wireframe_model.js +++ wireframe_model.js @@ -0,0 +1,345 @@ + + +// global namespace object +var WireframeModel; + + +(function(){ + +// A wireframe model and all the controls needed to move the camera and draw. +// despite the name, it may also include polygon surfaces. + +var origin = [0, 0, 0]; + + + +var points = []; //global +var solo_points = {}; +var lines = []; + +var point_projections = []; +var line_midpoint_projections = []; + + +var highlight_object = null; +var mouse_dragging = false; +var last_mouse_down = null; +var mouse_loc = null; + + + +var zoom_scale = 2.667; // scalar zoom property. +var zoom_dist = 1; + + + +function moveCamera(offset, cameracentric){ + if(cameracentric) offset = matrix_mult(view_transform, [offset])[0]; + //alert("offset: " + offset); + vector_add(origin, offset, origin); } + + + + + + +// converts a point from three space to the canvas plane. +// Note: because of depth perspective, this conversion is not +// easy to define if the point lies behind the camera. +// There are two options: +// When drawing a line, another colinear point in front of the camera may be provided +// to help find an alertnate point. +// if both points lie behind the camera or the colinear_point is not provided, +// this function will return null. + +function project(canvas, xyz, view_transform, origin, colinear_point){ + if(!xyz) return null; // point has been deleted or does not exist + + var w = canvas.width; + var h = canvas.height; + + var scale = Math.min(w, h); + + var v = xyz.slice(0); + if(origin) v = vector_minus(v, origin, v); + + var z = vector_dot(view_transform[2], v); + + if(z <= -zoom_dist){ + if(!colinear_point) return null; + + var v2 = colinear_point.slice(0); + if(origin) vector_minus(v2, origin, v2); + + var z2 = vector_dot(view_transform[2], v2); + + if(z2 < 0) return null; + + // get the coefficients for a complex combination. + // t*z + (1-t)*z2 = 0.0002 -- z of new point is just barely infront of the camera. + + var t = (0.0002 - z2)/(z - z2); // no division by zero, z is negative, z2 is positive + + vector_add(vector_scale(v, t, v), + vector_scale(v2, 1-t, v2), + v); + + z = vector_dot(view_transform[2], v); } + + + var scale2 = zoom_scale * scale / (zoom_dist + z); + + return [ scale2 * vector_dot(view_transform[0], v) + 0.5 * w, + scale2 * vector_dot(view_transform[1], v) + 0.5 * h ]; } + + + +// removes deleted points and lines +// works with global objects. + +function cleanupDeletedPoints(){ + var newpoints = []; + var newlines = []; + var pointmap = {}; + + var j = 0; + + for(var i = 0; i < points.length; ++i){ + + if(points[i]){ + newpoints.push(points[i]); + pointmap[i] = j; + ++j; } + + else{ + pointmap[i] = -1; }} + + + for(var i = 0; i < lines.length; ++i){ + if(!lines[i]) continue; + var a = lines[i][0]; + var b = lines[i][1]; + + if(pointmap[a] != -1 && pointmap[b] != -1){ + newlines.push([ pointmap[a], pointmap[b] ]); }} + + points = newpoints; + lines = newlines; } + + +function addPoint(pt){ + points.push(pt.slice(0)); + var index = points.length - 1; + solo_points[index] = true; + return index; } + + +function addLine(pt0, pt1){ + delete solo_points[pt0]; + delete solo_points[pt1]; + lines.push([pt0, pt1]); + return lines.length - 1; } + + + +function draw(canvas){ + var ctx = canvas.getContext('2d'); + var w = canvas.width; + var h = canvas.height; + + // ctx.fillStyle = "rgba(100, 100, 100, .05)"; + + + ctx.beginPath(); + + for(var i = 0; i < lines.length; ++i){ + var line = lines[i]; + if(!line) continue; + //draw line + + var pt0 = points[line[0]]; + var pt1 = points[line[1]]; + + var _pt0 = point_projections[line[0]]; + var _pt1 = point_projections[line[1]]; + + // finish the line even if one point is on wrong side of viewer. + if(!_pt0 && _pt1){ + _pt0 = project(canvas, pt0, view_transform, origin, pt1); } + else if(_pt0 && !_pt1){ + _pt1 = project(canvas, pt1, view_transform, origin, pt0); } + + if(!_pt0 || !_pt1) continue; + + var x0 = _pt0[0]; + var y0 = _pt0[1]; + + var x1 = _pt1[0]; + var y1 = _pt1[1]; + + ctx.moveTo(x0, y0); + ctx.lineTo(x1, y1); } + + ctx.clearRect(0, 0, w, h); + + + ctx.strokeStyle = "rgba(0, 0, 0, 0.9)"; + ctx.stroke(); + + ctx.fillStyle = "rgba(0, 0, 0, 0.9)"; + + for(var k in solo_points){ + var pt = point_projections[k]; + if(pt) ctx.fillRect(pt[0] - 2, pt[1] - 2, 4, 4); } + + // + // The following drawing of axes is to help provide an perspective + // The nuances of this draw behavior are created to provide visual cues for interpreting perspective and depth. + // + // Because we use a 2d canvas library for the drawing the rest of the points/shapes after projection, these visual information nuances are important. + // + + // axis lines and dots + + var faxis_pt = project(canvas, vector_add(origin, [0, 0, FORWARD_AXIS_LEN]), view_transform, origin); + var vaxis_pt = project(canvas, vector_add(origin, [0, -VERTICAL_AXIS_LEN, 0]), view_transform, origin); + + ctx.fillStyle = AXIS_POINT_COLOR; + + var fdot = vector_dot(view_transform[2], [0, 0, 1]); + + var a = MIN_AXIS_POINT_RADIUS; //random letters for intermediate computations + var b = MAX_AXIS_POINT_RADIUS; + var c = (a+b)/2; + var d = (b-a)/2; + + var fr = c - d * fdot; + var vdot = vector_dot(view_transform[2], [0, -1, 0]); + var vr = c - d * vdot; + + ctx.lineWidth = AXIS_LINE_WIDTH; + ctx.beginPath(); + + if(fdot >=0 && faxis_pt){ // occlude this axis, draw first + ctx.moveTo(w/2, h/2); + ctx.lineTo(faxis_pt[0], faxis_pt[1]); + ctx.fillRect(faxis_pt[0] - fr, faxis_pt[1] - fr, 2*fr, 2*fr); } + + if(vdot >=0 && vaxis_pt){ // occlude this axis, draw first + ctx.moveTo(w/2, h/2); + ctx.lineTo(vaxis_pt[0], vaxis_pt[1]); + ctx.fillRect(vaxis_pt[0] - vr, vaxis_pt[1] - vr, 2*vr, 2*vr); } + + ctx.stroke(); + + + // draw center red square + ctx.fillStyle = CENTER_POINT_COLOR; + var cr = CENTER_POINT_RADIUS; + ctx.fillRect(w/2 - cr, h/2 - cr, 2*cr, 2*cr); + + ctx.fillStyle = AXIS_POINT_COLOR; + + + if(fdot < 0 && faxis_pt){ + ctx.beginPath(); + ctx.moveTo(w/2, h/2); + ctx.lineTo(faxis_pt[0], faxis_pt[1]); + ctx.stroke(); + ctx.fillRect(faxis_pt[0] - fr, faxis_pt[1] - fr, 2*fr, 2*fr); } + + if(vdot < 0 && vaxis_pt){ + ctx.beginPath(); + ctx.moveTo(w/2, h/2); + ctx.lineTo(vaxis_pt[0], vaxis_pt[1]); + ctx.stroke(); + ctx.fillRect(vaxis_pt[0] - vr, vaxis_pt[1] - vr, 2*vr, 2*vr); } + + + // reset line width + ctx.lineWidth = LINE_WIDTH; + + + + + + + for(var i = 0; i < selected_points.length; ++i){ + var pt; + + if(selected_points[i] == -1) + pt = [w/2, h/2]; + else pt = point_projections[selected_points[i]]; + + ctx.fillStyle = POINT_COLOR; + if(pt) ctx.fillRect(pt[0] - 4, pt[1] - 4, 8, 8); } + + + if(highlight_object !== null){ + + + if(highlight_object < point_projections.length){ + var pt; + if(highlight_object == -1) + pt = [w/2, h/2]; + else pt = point_projections[highlight_object]; + + ctx.fillStyle = "rgba(50, 50, 50, .6)"; + ctx.fillRect(pt[0] - 4, pt[1] - 4, 8, 8); // TODO getting a error here that pt is null + document.body.style.cursor = "hand"; } + + else if(highlight_object < point_projections.length + line_midpoint_projections.length){ + // alert("drawing highlighted line"); + + var line = lines[highlight_object - point_projections.length]; + if(line){ + var pt1 = point_projections[line[0]]; + var pt2 = point_projections[line[1]]; + + if(pt1 && pt2){ + + ctx.beginPath(); + + ctx.moveTo(pt1[0], pt1[1]); + ctx.lineTo(pt2[0], pt2[1]); + ctx.lineWidth = 3; + ctx.stroke(); + ctx.lineWidth = 1; }}} + + else{ + throw "highlight object index to large"; }} + + else{ + document.body.style.cursor = "crosshair"; } + + if(mouse_dragging){ + + if(selected_points.length && !getKeyState(16)){ // if shift is held, we are selecting more points. + if(mouse_loc && getKeyState(1000)){ + ctx.beginPath(); + var pt = point_projections[selected_points[0]]; + if(pt){ + ctx.moveTo(mouse_loc[0], mouse_loc[1]); + ctx.lineTo(pt[0], pt[1]); } + ctx.stroke(); }} + + else if(mouse_loc && last_mouse_down){ + + + if(getKeyState(1000)){ // select points inside square + var minx = Math.min(mouse_loc[0], last_mouse_down[0]); + var maxx = Math.max(mouse_loc[0], last_mouse_down[0]); + var miny = Math.min(mouse_loc[1], last_mouse_down[1]); + var maxy = Math.max(mouse_loc[1], last_mouse_down[1]); + ctx.beginPath(); + ctx.rect(minx, miny, maxx - minx, maxy - miny); + ctx.stroke(); }} + } + + ctx.fillStyle = "rgb(0,0,0)"; + writeMsg(canvas, msg); } + + + + +})();