////////////////////////////////////////////////////////////// //DEFINE MATRIX CLASS function Matrix(ary) { this.mtx = ary this.height = ary.length; this.width = ary[0].length; } Matrix.prototype.toString = function() { var s = [] for (var i = 0; i < this.mtx.length; i++) s.push( this.mtx[i].join(",") ); return s.join("\n"); } // returns a new matrix Matrix.prototype.transpose = function() { var transposed = []; for (var i = 0; i < this.width; i++) { transposed[i] = []; for (var j = 0; j < this.height; j++) { transposed[i][j] = this.mtx[j][i]; } } return new Matrix(transposed); } // returns a new matrix Matrix.prototype.mult = function(other) { if (this.width != other.height) { throw "error: incompatible sizes"; } var result = []; for (var i = 0; i < this.height; i++) { result[i] = []; for (var j = 0; j < other.width; j++) { var sum = 0; for (var k = 0; k < this.width; k++) { sum += this.mtx[i][k] * other.mtx[k][j]; } result[i][j] = sum; } } return new Matrix(result); } ////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////// // BEGIN 3D GRAPH SECTION ////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////// maxVal = 20; middleX = 171; middleY = 160; zoomRatio = maxVal / middleX; tx = 8.0; ty = 10.0; tz = 4.0; ////////////////////////////////////////////////////////////// // make rotation matrices // functions to update the matrices function RZ(phi) { PHI = phi; Rz = new Matrix([[ Math.cos(phi) , Math.sin(phi) , 0, 0], [- Math.sin(phi), Math.cos(phi), 0, 0], [0,0,1,0],[0,0,0,1]]); } function RX(theta) { THETA = theta; Rx = new Matrix([[1,0,0,0], [0,Math.cos(theta),Math.sin(theta),0], [0,-Math.sin(theta),Math.cos(theta),0], [0,0,0,1]]); } function TZ(z) { Tz = new Matrix([[1,0,0,0],[0,1,0,0],[0,0,0,0],[0,0,z,1]]); } function updateR() { R = Rz.mult(Rx); R = R.mult(Tz); } function updateRZ(phi) { RZ(phi); updateR(); } function updateRX(theta) { RX(theta); updateR(); } function updateTZ(z) { TZ(z); updateR(); } // set the default view orientation RZ( Math.PI / 4.0 ); RX( Math.PI / 4.0 ); updateTZ(10.0); ////////////////////////////////////////////////////////////// // point class - constructed x, y, and z // // used for data organization // // project uses the projection matrix function Point(xx, yy, zz) { this.m = new Matrix([[xx,yy,zz,0]]) this.project = function() { return this.m.mult(R) }; }; ///////////////////////////////////////////////////////////// // line class - constructed with two points, an // // use this to add a line to the graph // // use update class to update the positions of function Line(start, end, id) { this.start = start; // start point this.end = end; // end point this.id = id; // uniquely identifiable jquery string this.update = function() { // find start and end points in 2d space var s = this.start.project(); var e = this.end.project(); // draw line between two points $(this.id).attr('x1', s.mtx[0][0] + middleX ); $(this.id).attr('y1', s.mtx[0][1] + middleY ); $(this.id).attr('x2', e.mtx[0][0] + middleX ); $(this.id).attr('y2', e.mtx[0][1] + middleY ); } //update graphics when constructed this.update(); }; function Dot(id) { // make dotted lines for X, Y, and Z components to "point" towards point // cannot use jquery to make SVG elements // elements must be created in the SVG namespace var lx = document.createElementNS('http://www.w3.org/2000/svg','line'); var ly = document.createElementNS('http://www.w3.org/2000/svg','line'); var lz = document.createElementNS('http://www.w3.org/2000/svg','line'); //give the lines identifiers lx.setAttribute('id',id + '-x'); ly.setAttribute('id',id + '-y'); lz.setAttribute('id',id + '-z'); this.xDotID = '#3d-graph #' + id + '-x'; this.yDotID = '#3d-graph #' + id + '-y'; this.zDotID = '#3d-graph #' + id + '-z'; //color the lines and make them dotted lx.setAttribute('style',"stroke:rgb(250,100,100);stroke-width:2;stroke-dasharray: 3, 3;"); ly.setAttribute('style',"stroke:rgb(100,250,100);stroke-width:2;stroke-dasharray: 3, 3;"); lz.setAttribute('style',"stroke:rgb(100,100,250);stroke-width:2;stroke-dasharray: 3, 3;"); // add the new elements to the SVG $('#3d-graph').append(lx); $('#3d-graph').append(ly); $('#3d-graph').append(lz); // make the actual dot var circ = document.createElementNS('http://www.w3.org/2000/svg','circle'); // style the dot circ.setAttribute('stroke','black'); circ.setAttribute('stroke-width','1.5'); circ.setAttribute('id',id+'-point'); circ.setAttribute('r','4.5'); circ.setAttribute('fill','rgb(200,000,000)'); // add the dot to the svg $('#3d-graph').append(circ); this.$dot = $('#3d-graph #' + id + '-point') this.hasError = false; this.update = function () { this.xDot.update(); this.yDot.update(); this.zDot.update(); if ( this.hasError ) { this.hyp.update(); } dotPos = this.dot.project(); this.$dot.attr('cx',dotPos.mtx[0][0] + middleX); this.$dot.attr('cy',dotPos.mtx[0][1] + middleY); } this.changePoint = function() { this.x = tx / zoomRatio; this.y = ty / zoomRatio; this.z = tz / zoomRatio; //make points for lines //from y axis to x point this.xDot = new Line( new Point(0,this.y,0), new Point(this.x,this.y,0), this.xDotID ); //from x axis to y point this.yDot = new Line( new Point(this.x,0,0), new Point(this.x,this.y,0), this.yDotID ); //from combined point to final point this.zDot = new Line( new Point(this.x,this.y,0), new Point(this.x, this.y, this.z), this.zDotID ); //change point for circle this.dot = new Point(this.x, this.y, this.z); //SOFT length checking - will draw a line showing the length //check length this.len = Math.sqrt( this.x*this.x + this.y*this.y + this.z*this.z ); $("#length").html( Math.round(this.len) * zoomRatio ); if ( this.len > middleX && this.hasError === false ) { this.hasError = true; $('#length').addClass("error"); this.$hyp = $(document.createElementNS('http://www.w3.org/2000/svg','line')); this.$hyp.attr('style','stroke:red;stroke-width:1;'); this.$hyp.attr('id','hyp-error'); $('#3d-graph').append(this.$hyp); this.$dot.attr('stroke','rgb(150,0,0)'); this.$dot.attr('r','4'); this.$dot.attr('fill','rgb(255,0,0)'); } else if ( this.len <= middleX && this.hasError === true ) { $('#length').removeClass("error"); this.hasError = false; this.$hyp.remove(); this.$dot.attr('stroke','black'); this.$dot.attr('r','3'); this.$dot.attr('fill','rgb(200,200,200)'); } if ( this.hasError === true ) { this.hyp = new Line( origin, new Point(this.x, this.y, this.z), '#hyp-error' ); } this.update(); } this.changePoint(); } // update all lines when refreshing the display lines = []; function updateLines() { for ( l in lines ) { lines[l].update(); } } // update all dots when refreshing the display dots = []; function updateDots() { for( d in dots ) { dots[d].update(); } } $(document).ready(function () { //attach a mover for left drag and middle click $("#3d-graph").mousedown( function(e) { e.preventDefault(); ///////////////////////////////////////////////////////// // LEFT MOUSE DRAG POINT if ( e.which == 1 ) { var startX = tx; //mainDot.x; var startY = ty; //mainDot.y; var startZ = tz; //mainDot.z; var mStartX = e.screenX; var mStartY = e.screenY; $("html").mousemove( function(e) { var dx = ( e.screenX - mStartX ) * zoomRatio; var dy = ( e.screenY - mStartY ) * zoomRatio; //var r = Rz.mult(Rx); var rz = new Matrix([[ Math.sin(PHI) , Math.cos(PHI) , 0, 0],[- Math.cos(PHI), Math.sin(PHI), 0, 0],[0,0,1,0],[0,0,0,1]]); var m = new Matrix([[0,-dx,0,0]]) m = m.mult(rz); var newPoint = new Array(); tx = startX + m.mtx[0][0]; ty = startY + m.mtx[0][1]; tz = startZ - dy* Math.sin(THETA); var newLength = Math.sqrt((tx*tx)+(ty*ty)+(tz*tz)); //limit the point to a 20 unit radius sphere if ( newLength > 19.9 ) { tz=((19.9)*(tz/newLength)); ty=((19.9)*(ty/newLength)); tx=((19.9)*(tx/newLength)); } else if ( Math.abs(newLength) < 5.1 ) { tz=((5.1)*(tz/newLength)); ty=((5.1)*(ty/newLength)); tx=((5.1)*(tx/newLength)); } mainDot.changePoint(); }); } ///////////////////////////////////////////////////////// // MIDDLE MOUSE ROTATE else if ( e.which == 2 ) { var startX = (e.screenX + 100 * PHI); var startY = e.screenY + 100 * THETA; $("html").mousemove( function(e) { var newPhi = (-e.screenX + startX) / 100.0; var newTheta = (-e.screenY + startY) / 100.0; if (newTheta > Math.PI || newTheta < 0.0 ) { newTheta = THETA; } RZ( newPhi ); updateRX( newTheta ); updateLines(); updateDots(); }); } }); //mouse up, so stop moving or rotating $("html").mouseup( function( e ) { $('html').unbind('mousemove'); if ( typeof sendPosInterval !== "undefined" ) { window.clearInterval(sendPosInterval); } if ( typeof sendPosInterval !== "undefined" ) { window.clearInterval(inputInterval); } }); //add lines to the graph origin = new Point(0,0,0); x = new Point(middleX,0,0); y = new Point(0,middleX,0); var z = new Point(0,0,middleX); var nx = new Point(-middleX,0,0); var ny = new Point(0,-middleX,0); var nz = new Point(0,0,-middleX); var xAxis = new Line( origin, x, "#axis-x" ); var yAxis = new Line( origin, y, "#axis-y" ); var zAxis = new Line( origin, z, "#axis-z" ); var nxAxis = new Line( origin, nx, "#neg-axis-x" ); var nyAxis = new Line( origin, ny, "#neg-axis-y" ); var nzAxis = new Line( origin, nz, "#neg-axis-z" ); //add the lines to an array so they can all be updated at once on refresh lines.push( xAxis ); lines.push( yAxis ); lines.push( zAxis ); lines.push( nxAxis ); lines.push( nyAxis ); lines.push( nzAxis ); // add the dots to the graph mainDot = new Dot("graph-dot-1"); //add the dots to an array so they can all be updated at once on refresh dots.push(mainDot); });