// requires matrix_vector_lib.js
// TODO we could make some libraryless way of checking dependencies and versions.
// 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.
function __WireframeModel(){
var WFMObj = this;
var view_origin = [0, 0, 0];
var view_transform = [[1, 0, 0],
[0, 1, 0],
[0, 0, 1]];
var points = []; //global
var solo_points = {};
var lines = [];
// just because these actual objects is returned
// doesn't mean you should modify it externally if you can help it.
WFMObj.getPoints = function getPoints(){
return points; }
WFMObj.getLines = function getLines(){
return lines; }
WFMObj.getViewTransform = function getViewTransform(){
return view_transform; }
WFMObj.addPoint = function addPoint(pt){
// Check type of passed object.
if(Array.isArray && !Array.isArray(pt)){ // only check if Array.isArray function is available
throw "pt object is not an array"; }
if(pt.length != 3){
throw "pt array object not length 3"; }
for(var i = 0; i < 3; ++i){
if(isNaN(pt[i])) throw "pt object array does not store a number at index " + i; }
points.push(pt);
return points.length - 1; }
WFMObj.addLine = function addLine(line){
if(Array.isArray && !Array.isArray(line)){
throw "line object is not an array" }
if(line.length != 2){
throw "line object must be between 2 points. line object has length: " + line.length; }
for(var i = 0; i < 2; ++i){
var ptindex = line[i];
if(isNaN(ptindex) || Math.floor(ptindex) != ptindex){
throw "pt ref at index " + i + " of line was not an integer: " + ptindex; }
if(ptindex < 0 || ptindex >= points.length){
throw "pt ref at index " + i + " of line was not in bounds: " + ptindex; }}
lines.push(line);
return lines.length - 1; }
}
// use constructor as global namespace object.
WireframeModel = __WireframeModel;
})();
/*
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); }
*/