wireframe_model.js at trunk
Not logged in

File wireframe_model.js artifact 52461a961c on branch trunk




// requires matrix_vector_lib.js
// TODO we could make some libraryless way of checking dependencies and versions.


// global namespace object
var WireframeModel;


(function(){





// 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 alternate 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, colinear_point){
    if(!xyz) return null;  // point has been deleted or does not exist
    
	var view_transform = view.transform;
	var origin = view.origin;
	var zoom_scale = view.zoom_scale;
	var zoom_dist = view.zoom_dist;
	
	if(!zoom_scale) zoom_scale = 1;
	if(!zoom_dist) zoom_dist = 0;
	if(!origin) origin = [0,0,0];
	
	
    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){  // this point is on the wrong side of the viewer, find the closest point on the correct side if we can.
        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 ]; }



//  Just like the project function above but for a set of points.
//  It's put into a loop a little better.
function project_all(canvas, points, point_projections, view){

	var view_transform = view.transform;
	var origin = view.origin;
	var zoom_scale = view.zoom_scale;
	var zoom_dist = view.zoom_dist;
	
	if(!zoom_scale) zoom_scale = 1;
	if(!zoom_dist) zoom_dist = 0;
	if(!origin) origin = [0,0,0];
	

    if(!point_projections) point_projections = [];
	point_projections.length = points.length;
    
    var w = canvas.width;
    var h = canvas.height;
	var w_2 = w/2;
	var h_2 = h/2;
    
    var scale = zoom_scale * Math.min(w, h);
	
	var v00 = view_transform[0][0];
	var v01 = view_transform[0][1];
	var v02 = view_transform[0][2];
	var v10 = view_transform[1][0];
	var v11 = view_transform[1][1];
	var v12 = view_transform[1][2];
	var v20 = view_transform[2][0];
	var v21 = view_transform[2][1];
	var v22 = view_transform[2][2];
	
    
	for(var i = 0; i < points.length; ++i){
		var pt = points[i];
		
		var x = pt[0];
		var y = pt[1];
		var z = pt[2];
		
		if(origin){
	         x -= origin[0];
			 y -= origin[1];
			 z -= origin[2]; }
		
		var depth = x*v20 + y*v21 + z*v22;
		var scale2 = scale / (zoom_dist + depth);
		
		if(!point_projections[i]) point_projections[i] = [0, 0];
		
		var point_projection = point_projections[i];
		
		point_projection[0] = scale2 * (x*v00 + y*v01 + z*v02) + w_2;
		point_projection[1] = scale2 * (x*v10 + y*v11 + z*v22) + h_2; }
	
	return point_projections; }




//  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 point_projections = [];
	//var solo_points = {};
	var lines = [];
	var zoom_scale = 1;
	var zoom_dist = 1;
	
	// 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.getPoint = function getPoint(index){
		return points[index].slice(0); }
	
	WFMObj.getLines = function getLines(){
		return lines; }
		
	WFMObj.getLine = function getLine(index){
		return lines[index].slice(0); }
	
	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; }

		
	WFMObj.draw = function draw(canvas){
		var ctx = canvas.getContext("2d");
		
		var view = {};
		view.transform = view_transform;
		view.origin = view_origin;
		view.zoom_scale = zoom_scale;
		view.zoom_dist = zoom_dist;
		
		project_all(canvas, points, point_projections, view);
		// project_all()
		
		for(var i = 0; i < point_projections.length; ++i){
			var point_projection = point_projections[i];
			if(point_projection) ctx.fillRect(point_projection[0] - 1, point_projection[1] - 1, 2, 2); }
	}
}

//  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); }
	
	
	
	
   

   

   
//  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); }
	
*/