Check-in [257c455196]
Not logged in
Overview
SHA1:257c455196f51dd0e71c3f0119d944dc8080febe
Date: 2012-12-23 18:51:48
User: Derek
Comment:initial commit
Timelines: family | ancestors | descendants | both | trunk
Other Links: files | file ages | folders | manifest
Tags And Properties
Context
2012-12-23
19:18
[1bc6e97248] removed superfulous code and comments. updated TODOs (user: Derek, tags: trunk)
18:51
[257c455196] initial commit (user: Derek, tags: trunk)
18:24
[433b76e4df] initial empty check-in (user: dir7, tags: trunk)
Changes

Added draw.html version [e44e8eb350].

            1  +<html>
            2  +<script src='matrix_vector_lib.js' ></script>
            3  +
            4  +<script>
            5  +
            6  +//
            7  +//  Editor modes
            8  +//
            9  +//  Motion mode  -- change the camera center using the original movement system
           10  +//  --------Camera mode  -- adjust the camera,  scale zoom and position zoom, make it stationary or rotating about the center
           11  +//  Edit mode -- create points at camera center, move points, create lines by clicking on successive points, create closed polygons etc.
           12  +
           13  +//  Editing Strategy
           14  +
           15  +//  During draw mode only the very edges of the screen rotate the view, and at reduced speed
           16  +//  pressing enter or clicking on the center point creates a new point.
           17  +//  points are moved with the arrow keys, the direction is absolute, that means it does not depend on the camera angle.
           18  +//  the arrow keys move the selected point.
           19  +// clicking on a second point when one point is already selected creates a line.
           20  +
           21  +//  TODO  control with zoom scales the selected object
           22  +//        control with rotate rotates the selected objects
           23  +
           24  +//  TODO clicking and dragging on a line allows you to place a point somewhere on that line.  Perhaps with the ctrl key it would break up the existing line into line segments.
           25  +
           26  +//  TODO when lines are deleted, readd the solo points to the solo_points list
           27  +//  
           28  +
           29  +//  TODO z for undo shift + z for redo.
           30  +//
           31  +
           32  +//  TODO drop down minimalist menu in top right of canvas.
           33  +
           34  +
           35  +//  drag to connect points
           36  +
           37  +
           38  +var canvas;
           39  +var origin = [0, 0, 0];
           40  +var frame_rate = 20;
           41  +var saveTimeout = 10*1000;
           42  +
           43  +var no_rotate_area = 0.80;
           44  +            
           45  +            
           46  +
           47  +            
           48  +
           49  +var key_state = {};
           50  +var key_state_augmentation_delay = 150; // the permissible time in milliseconds since the last key event to still allow the key state to augment instead of clearing.
           51  +
           52  +function addKeyListener(keycode, func){
           53  +    if(!key_state[keycode]) key_state[keycode] = {};
           54  +    
           55  +    var key_obj = key_state[keycode];
           56  +    if(!key_obj.listeners) key_obj.listeners = [];
           57  +    
           58  +    var listeners = key_obj.listeners;
           59  +    listeners.push(func);
           60  +}
           61  +
           62  +
           63  +function keyDown(event, code){
           64  +    var time = (new Date()).getTime();
           65  +    var code = event.keyCode;
           66  +    // alert("you pressed: " + code);
           67  +        
           68  +    var key_obj = key_state[code];
           69  +    if(!key_obj) key_obj = key_state[code] = {}; // create object
           70  +
           71  +    
           72  +    //  update state etc.
           73  +    
           74  +    var state = key_obj.state;
           75  +    if(state) return; //key is already pressed.
           76  +    
           77  +    state = 0;
           78  +    
           79  +    var latest = key_obj.latest;
           80  +
           81  +    if(latest && time - latest < key_state_augmentation_delay)
           82  +        state = key_obj.state = key_obj.lastState + 1;
           83  +    else
           84  +        state = key_obj.state = 1;
           85  +        
           86  +    key_obj.latest = time;
           87  +    
           88  +    key_obj.lastState = state;
           89  +
           90  +
           91  +    //  call listener functions 
           92  +    var listeners = key_obj.listeners
           93  +    if(!listeners) return;  //nothing to do.
           94  +      
           95  +    for(var i = 0; i < listeners.length; ++i)
           96  +        listeners[i](event, state, state); }
           97  +
           98  +    
           99  +function keyUp(event){
          100  +    var time = (new Date()).getTime();
          101  +    var code = event.keyCode;
          102  +    
          103  +    var key_obj = key_state[code];
          104  +    if(!key_obj) return;  //nothing to do.
          105  +      
          106  +    key_obj.latest = time;
          107  +    key_obj.lastState = key_obj.state;
          108  +    var state = key_obj.state = 0;
          109  +    var lastState = key_obj.lastState;
          110  +    
          111  +    var listeners = key_obj.listeners;
          112  +    if(!listeners) return;  //nothing to do.
          113  +    
          114  +    for(var i = 0; i < listeners.length; ++i)
          115  +        listeners[i](event, state, lastState); }
          116  +
          117  +        
          118  +        
          119  +        
          120  +function setUiEvents(){
          121  +    document.body.onkeydown = keyDown;
          122  +    document.body.onkeyup = keyUp;
          123  +    
          124  +    document.body.onmousedown = function(event){
          125  +        var x = event.pageX;
          126  +        var y = event.pageY;
          127  +        x -= canvas.offsetLeft;
          128  +        y -= canvas.offsetTop;
          129  +        last_mouse_down = [x, y];
          130  +        
          131  +        var button = event.button;
          132  +        var keycode = button + 1000;  //treat clicks like a button press but with higher keycode.
          133  +        keyDown({keyCode: keycode}); }  // it seems the keyCode of the original event cant be overwritten.
          134  +        
          135  +    document.body.onmousewheel = function(event){
          136  +        var delta = event.wheelDelta;
          137  +        
          138  +        //if(editor_mode == MODE_DRAWING){
          139  +		if(delta < 0)
          140  +			zoom_dist /= zoom_factor;
          141  +		else if(delta > 0)
          142  +			zoom_dist *= zoom_factor; }
          143  +        
          144  +        
          145  +    document.body.onmouseup = function(event){
          146  +        var button = event.button;
          147  +        var keycode = button + 1000;  //treat clicks like a button press but with higher keycode.
          148  +        keyUp({keyCode: keycode});
          149  +        last_mouse_down = null;
          150  +        mouse_dragging = false; }}
          151  +        
          152  +
          153  +
          154  +    
          155  +function changeVelocity(direction, positive, state, lastState){
          156  +    if(state == 0){
          157  +        state = lastState;
          158  +        positive = !positive; }  // reverse the velocity
          159  +    
          160  +    var delta = Math.pow(2, state - 1) * max_move_delta / frame_rate;
          161  +    if(!positive) delta = -delta;
          162  +    
          163  +    var velocityChange = [0, 0, 0];
          164  +    velocityChange[direction] = delta;
          165  +    vector_add(delta_position, velocityChange, delta_position); }
          166  +    
          167  +
          168  +//
          169  +//  This 
          170  +//
          171  +    
          172  +function leftPress(e, s, lasts){
          173  +    changeVelocity(0, false, s, lasts); }
          174  +    
          175  +function rightPress(e, s, lasts){
          176  +     changeVelocity(0, true, s, lasts); }
          177  +     
          178  +function downPress(e, s, lasts){
          179  +    changeVelocity(1, false, s, lasts); }
          180  +    
          181  +function upPress(e, s, lasts){
          182  +    changeVelocity(1, true, s, lasts); }
          183  +
          184  +function backwardPress(e, s, lasts){
          185  +    changeVelocity(2, false, s, lasts); }
          186  +    
          187  +function forwardPress(e, s, lasts){
          188  +    changeVelocity(2, true, s, lasts); }
          189  +        
          190  +        
          191  +function zoomInPress(e, s, lasts){
          192  +    //if(editor_mode == MODE_CAMERA_POSITION)
          193  +    //    changeVelocity(2, true, s, lasts);
          194  +    //else if(editor_mode == MODE_CAMERA_ROTATION)
          195  +        zoom_dist *= zoom_factor; }
          196  +        
          197  +        
          198  +function zoomOutPress(e, s, lasts){
          199  +    //if(editor_mode == MODE_CAMERA_POSITION)
          200  +    //    changeVelocity(2, true, s, lasts);
          201  +    //else if(editor_mode == MODE_CAMERA_ROTATION)
          202  +        zoom_dist *= zoom_factor; }
          203  +
          204  +
          205  +function copySelectedPoints(e, s, lasts){
          206  +    if(s){
          207  +        var new_selection = [];
          208  +        var selection_map = {};
          209  +        
          210  +        for(var i = 0; i < selected_points.length; ++i){
          211  +            selection_map[selected_points[i]] = i;
          212  +            var pt = points[selected_points[i]];
          213  +            
          214  +            var newpt = addPoint(pt);
          215  +            new_selection[i] = newpt; }
          216  +        
          217  +        //if(key_state[16] && key_state[16].state){  //shift + space copies lines as well.
          218  +        
          219  +        // copy lines as well
          220  +        for(var i = 0; i < lines.length; ++i){
          221  +           var line = lines[i];
          222  +           if(!line) continue;
          223  +           
          224  +           var pt1 = lines[i][0];
          225  +           var pt2 = lines[i][1];
          226  +           var i1 = selection_map[pt1];
          227  +           var i2 = selection_map[pt2];
          228  +           
          229  +           if(i1 !== undefined && i2 !== undefined){  // both points are selected
          230  +               addLine(new_selection[i1], new_selection[i2]); }}
          231  +       
          232  +        selected_points = new_selection; }}
          233  +            
          234  +            
          235  +addKeyListener(32, copySelectedPoints);        
          236  +    
          237  +addKeyListener(37, leftPress);  // arrow keys
          238  +addKeyListener(38, forwardPress);
          239  +addKeyListener(39, rightPress);
          240  +addKeyListener(40, backwardPress);
          241  +
          242  +
          243  +addKeyListener(65, leftPress);  // wasd
          244  +addKeyListener(87, forwardPress);
          245  +addKeyListener(68, rightPress);
          246  +addKeyListener(83, backwardPress);
          247  +addKeyListener(69, upPress);
          248  +addKeyListener(81, downPress);
          249  +
          250  +
          251  +addKeyListener(88, function(e, s, lasts){  // x
          252  +
          253  +    //if(editor_mode == MODE_DRAWING && s){  // alert('deleting points');
          254  +	
          255  +    if(s){
          256  +        if(highlight_object !== null && highlight_object > 0){  // delete highlighted object
          257  +            
          258  +            if(highlight_object < point_projections.length){  // point
          259  +                points[highlight_object] = null; }
          260  +                
          261  +            else if(highlight_object < point_projections.length + line_midpoint_projections.length){  // line
          262  +                lines[highlight_object - point_projections.length] = null; }
          263  +            else throw "highlight object index to large"; }
          264  +        
          265  +        else if(selected_points.length){  // delete selected points
          266  +            for(var i = 0; i < selected_points.length; ++i){
          267  +                points[selected_points[i]] = null; }
          268  +            selected_points.length = 0; }
          269  +        
          270  +        cleanupDeletedPoints();
          271  +        highlight_object = null;
          272  +        selected_points.length = 0; }});
          273  +
          274  +// addKeyListener(1000, aForwardPress);    // mouse buttons
          275  +// addKeyListener(1002, cameraBackwardPress);
          276  +
          277  +
          278  +
          279  +// addKeyListener(13, function(e, s, lasts){ if(s) nextMode(); });  // switch modes
          280  +
          281  +
          282  +//  select point
          283  +addKeyListener(1000, function(e, s, lasts){
          284  +
          285  +    //if(editor_mode == MODE_DRAWING){
          286  +        if(s){
          287  +            if(highlight_object !== null){
          288  +                if(highlight_object == -1)
          289  +                    highlight_object = addPoint(origin);  // create a new point at the origin
          290  +                    
          291  +                
          292  +                var alreadySelected = false;  // only set for shift clicks
          293  +                
          294  +                if(key_state[16] && key_state[16].state){  // shift key is pressed.
          295  +                
          296  +                    for(var i = 0; i < selected_points.length; ++i){
          297  +                        if(selected_points[i] == highlight_object){
          298  +                           selected_points.splice(i, 1); // deselect point
          299  +                           alreadySelected = true; 
          300  +                           break; }}}
          301  +                else{
          302  +                    selected_points.length = 0;}
          303  +                    
          304  +                if(!alreadySelected) selected_points.push(highlight_object); }
          305  +
          306  +            else if(!key_state[16] || !key_state[16].state)
          307  +                selected_points.length = 0; }
          308  +            
          309  +            
          310  +        else if(lasts && mouse_dragging ){  // drag actions
          311  +            
          312  +            //  connect selected points to highlight point with lines
          313  +            if(selected_points.length && (!key_state[16] || !key_state[16].state)){  //  TODO right now this really only works for drawing a single line, 
          314  +            
          315  +                if(highlight_object !== null && highlight_object < point_projections.length){
          316  +                    if(highlight_object == -1) highlight_object = addPoint(origin);
          317  +                    
          318  +                    for(var i = 0; i < selected_points.length; ++i){
          319  +                        var pt = selected_points[i];
          320  +                        if(pt == highlight_object) continue;
          321  +                        addLine(pt, highlight_object); }
          322  +                    selected_points.length = 0; }}
          323  +                
          324  +            // select points inside selection rectangle
          325  +            else if(mouse_loc && last_mouse_down){ 
          326  +            
          327  +                var minx = Math.min(mouse_loc[0], last_mouse_down[0]);
          328  +                var maxx = Math.max(mouse_loc[0], last_mouse_down[0]);
          329  +                var miny = Math.min(mouse_loc[1], last_mouse_down[1]);
          330  +                var maxy = Math.max(mouse_loc[1], last_mouse_down[1]);
          331  +                
          332  +                
          333  +                var selected_point_map = {};             
          334  +                
          335  +                if(!key_state[16] || !key_state[16].state){
          336  +                    selected_points.length = 0;
          337  +                    selected_lines.length = 0; }
          338  +                else{
          339  +                    for(var i = 0; i < selected_points.length; ++i){
          340  +                        selected_point_map[selected_points[i]] = i; }}
          341  +                
          342  +                for(var i = 0; i < point_projections.length; ++i){
          343  +                    if(selected_point_map[i]) continue; // already selected.
          344  +                    
          345  +                    var pt = point_projections[i];
          346  +                    if(!pt) continue;
          347  +                    var _x = pt[0];
          348  +                    var _y = pt[1];
          349  +                    
          350  +                    if(minx < _x && _x < maxx
          351  +                     && miny < _y && _y < maxy){
          352  +                       selected_points.push(i); }}}                
          353  +        }});
          354  +
          355  +
          356  +
          357  +
          358  +
          359  +// to prevent camera from rotating beyond straight vertical
          360  +var vertical_camera_rotation = 0;
          361  +var NAVIGATION_ZOOM_DIST = 0.01;
          362  +var zoom_dist = 1;
          363  +var zoom_factor = 0.83;
          364  +var zoom_scale = 2.667;  // scalar zoom property.
          365  +var last_zoom_dist = 1;
          366  +
          367  +
          368  +// camera animation
          369  +var max_move_delta = 0.01;
          370  +var max_angle_delta = Math.PI / 2;
          371  +
          372  +var delta_horizontal_angle = 0;
          373  +var delta_vertical_angle = 0;
          374  +var delta_position = [0,0,0];
          375  +
          376  +
          377  +// var MODE_DRAWING = 0;
          378  +// var MODE_CAMERA_POSITION = 1; //  change the camera focus point
          379  +// var MODE_CAMERA_ROTATION = 2; //  rotate and zoom camera
          380  +
          381  +// var editor_mode = MODE_DRAWING;
          382  +
          383  +/*
          384  +function drawMode(){
          385  +    editor_mode = MODE_DRAWING;
          386  +    delta_position = [0,0,0];
          387  +    // delta_horizontal_angle = 0;
          388  +    // delta_vertical_angle = 0;
          389  +    if(last_zoom_dist) zoom_dist = last_zoom_dist;
          390  +    else zoom_dist = 1;
          391  +               
          392  +    document.body.style.cursor = "crosshair"; }
          393  +    
          394  +function cameraPositionMode(){
          395  +    document.body.style.cursor = "default";
          396  +    last_zoom_dist = zoom_dist;
          397  +    delta_horizontal_angle = 0;
          398  +    delta_vertical_angle = 0;
          399  +    zoom_dist = NAVIGATION_ZOOM_DIST;
          400  +    editor_mode = MODE_CAMERA_POSITION; }
          401  +
          402  +function cameraRotationMode(){
          403  +    document.body.style.cursor = "default";
          404  +    delta_horizontal_angle = 0;
          405  +    delta_vertical_angle = 0;
          406  +    if(last_zoom_dist) zoom_dist = last_zoom_dist;
          407  +    else zoom_dist = 1;
          408  +    editor_mode = MODE_CAMERA_ROTATION; }
          409  +    
          410  +function nextMode(){
          411  +    if(editor_mode == MODE_DRAWING) cameraPositionMode();
          412  +    else if(editor_mode == MODE_CAMERA_POSITION) drawMode(); }
          413  +
          414  +    // skipping camera rotation mode
          415  +    //else if(editor_mode == MODE_CAMERA_POSITION) cameraRotationMode();
          416  +    //else if(editor_mode == MODE_CAMERA_ROTATION)  drawMode(); }  
          417  +    
          418  +   */ 
          419  +
          420  +
          421  +var points = []; //global
          422  +var solo_points = {};
          423  +var lines = [];
          424  +
          425  +var point_projections = [];
          426  +var line_midpoint_projections = [];
          427  +
          428  +var highlight_object = null;
          429  +var mouse_dragging = false;
          430  +var last_mouse_down = null;
          431  +var mouse_loc = null;
          432  +var MIN_DRAG_DIST = 10;
          433  +var POINT_HIGHLIGHT_DIST = 10;
          434  +var LINE_HIGHLIGHT_DIST = 15;
          435  +
          436  +var selected_points = [];
          437  +var selected_lines = [];
          438  +
          439  +var msg = "3D drawing tool";
          440  +
          441  +
          442  +var view_transform =
          443  +    [[1, 0, 0],
          444  +     [0, 1, 0],
          445  +     [0, 0, 1]];
          446  +    
          447  +
          448  +    
          449  +//  camera functions
          450  +
          451  +function rotateView(norm, theta, cameracentric){
          452  +
          453  +    // camera centric uses the camera axes to perform the rotation instead of the space axes.   
          454  +    if(cameracentric) norm = matrix_mult(view_transform, [norm])[0];
          455  +    var rotation = vector_rotation(norm, theta);
          456  +    
          457  +    view_transform = matrix_mult(rotation, view_transform); }
          458  +    
          459  +    
          460  +    
          461  +function rotateHorizontal(theta){
          462  +    rotateView([0, 1, 0], theta, false); }
          463  +    
          464  +function rotateVertical(theta){
          465  +    if(vertical_camera_rotation + theta > Math.PI/2)
          466  +        theta = Math.PI/2 - vertical_camera_rotation;
          467  +        
          468  +    if(vertical_camera_rotation + theta < -Math.PI/2)
          469  +        theta = -Math.PI/2 - vertical_camera_rotation;
          470  +        
          471  +    vertical_camera_rotation += theta;
          472  +    
          473  +    rotateView([1, 0, 0], theta, true); }
          474  +    
          475  +    
          476  +function moveCamera(offset, cameracentric){
          477  +    if(cameracentric) offset = matrix_mult(view_transform, [offset])[0];
          478  +    //alert("offset: " + offset);
          479  +    vector_add(origin, offset, origin); }
          480  +    
          481  +
          482  +// converts a point from three space to the canvas plane.
          483  +// Note: because of depth perspective, this conversion is not
          484  +// easy to define if the point lies behind the camera.
          485  +// There are two options:
          486  +// When drawing a line, another colinear point in front of the camera may be provided
          487  +// to help find an alertnate point.
          488  +// if both points lie behind the camera or the colinear_point is not provided,
          489  +// this function will return null.
          490  + 
          491  +function project(canvas, xyz, view_transform, origin, colinear_point){
          492  +    if(!xyz) return null;  // point has been deleted or does not exist
          493  +    
          494  +    var w = canvas.width;
          495  +    var h = canvas.height;
          496  +    
          497  +    var scale = Math.min(w, h);
          498  +    
          499  +    var v = xyz.slice(0);
          500  +    if(origin) v = vector_minus(v, origin, v);
          501  +    
          502  +    var z = vector_dot(view_transform[2], v);
          503  +    
          504  +    if(z <= -zoom_dist){
          505  +        if(!colinear_point) return null;
          506  +        
          507  +        var v2 = colinear_point.slice(0);
          508  +        if(origin) vector_minus(v2, origin, v2);
          509  +        
          510  +        var z2 = vector_dot(view_transform[2], v2);
          511  +        
          512  +        if(z2 < 0) return null;
          513  +        
          514  +        // get the coefficients for a complex combination.
          515  +        // t*z + (1-t)*z2 = 0.0002  -- z of new point is just barely infront of the camera.
          516  +        
          517  +        var t = (0.0002 - z2)/(z - z2);  // no division by zero, z is negative, z2 is positive
          518  +        
          519  +        vector_add(vector_scale(v, t, v),
          520  +                   vector_scale(v2, 1-t, v2),
          521  +                   v);
          522  +                   
          523  +        z = vector_dot(view_transform[2], v); }
          524  +
          525  +        
          526  +    var scale2 = zoom_scale * scale / (zoom_dist + z);
          527  +    
          528  +    return [ scale2 * vector_dot(view_transform[0], v) + 0.5 * w,
          529  +             scale2 * vector_dot(view_transform[1], v) + 0.5 * h ]; }
          530  +   
          531  +
          532  +   
          533  +//  removes deleted points and lines
          534  +//  works with global objects.
          535  +
          536  +function cleanupDeletedPoints(){
          537  +    var newpoints = [];
          538  +    var newlines = [];
          539  +    var pointmap = {};
          540  +    
          541  +    var j = 0;
          542  +    
          543  +    for(var i = 0; i < points.length; ++i){
          544  +       
          545  +       if(points[i]){
          546  +           newpoints.push(points[i]);
          547  +           pointmap[i] = j;
          548  +           ++j; }
          549  +           
          550  +       else{
          551  +           pointmap[i] = -1; }}
          552  +    
          553  +    
          554  +    for(var i = 0; i < lines.length; ++i){
          555  +        if(!lines[i]) continue;
          556  +        var a = lines[i][0];
          557  +        var b = lines[i][1];
          558  +        
          559  +        if(pointmap[a] != -1 && pointmap[b] != -1){
          560  +            newlines.push([ pointmap[a], pointmap[b] ]); }}
          561  +    
          562  +    points = newpoints;
          563  +    lines = newlines; }
          564  +        
          565  +
          566  +function addPoint(pt){
          567  +    points.push(pt.slice(0));
          568  +    var index = points.length - 1;
          569  +    solo_points[index] = true;
          570  +    return index; }
          571  +    
          572  +    
          573  +function addLine(pt0, pt1){
          574  +    delete solo_points[pt0];
          575  +    delete solo_points[pt1];
          576  +    lines.push([pt0, pt1]);
          577  +    return lines.length - 1; }
          578  +
          579  +function addBox(xyz, wdh){
          580  +    var x = xyz[0];
          581  +    var y = xyz[1];
          582  +    var z = xyz[2];
          583  +    
          584  +    var w = wdh[0];
          585  +    var d = wdh[1];
          586  +    var h = wdh[2];
          587  +    
          588  +    var x0 = x - w/2;
          589  +    var y0 = y - d/2;
          590  +    var z0 = z - d/2;
          591  +    
          592  +    var x1 = x + w/2;
          593  +    var y1 = y + d/2;
          594  +    var z1 = z + h/2;
          595  +    
          596  +    var a = addPoint([x0, y0, z0]);
          597  +    var b = addPoint([x1, y0, z0]);
          598  +    var c = addPoint([x1, y1, z0]);
          599  +    var d = addPoint([x0, y1, z0]);
          600  +    var e = addPoint([x0, y0, z1]);
          601  +    var f = addPoint([x1, y0, z1]);
          602  +    var g = addPoint([x1, y1, z1]);
          603  +    var h = addPoint([x0, y1, z1]);
          604  +   
          605  +
          606  +    addLine(a, b);
          607  +    addLine(b, c);
          608  +    addLine(c, d);
          609  +    addLine(d, a);
          610  +    addLine(e, f);
          611  +    addLine(f, g);
          612  +    addLine(g, h);
          613  +    addLine(h, e);
          614  +    
          615  +    addLine(a, e);
          616  +    addLine(b, f);
          617  +    addLine(c, g);
          618  +    addLine(d, h); }
          619  +    
          620  +    
          621  +    
          622  +    
          623  +function draw(canvas){
          624  +    var ctx = canvas.getContext('2d');
          625  +    var w = canvas.width;
          626  +    var h = canvas.height;
          627  +    
          628  +    // ctx.fillStyle = "rgba(100, 100, 100, .05)";
          629  +    
          630  +    
          631  +    ctx.beginPath();
          632  +    
          633  +    for(var i = 0; i < lines.length; ++i){
          634  +        var line = lines[i];
          635  +        if(!line) continue;
          636  +        //draw line
          637  +        
          638  +        var pt0 = points[line[0]];
          639  +        var pt1 = points[line[1]];
          640  +            
          641  +        var _pt0 = point_projections[line[0]];
          642  +        var _pt1 = point_projections[line[1]];
          643  +        
          644  +        //  finish the line even if one point is on wrong side of viewer.
          645  +        if(!_pt0 && _pt1){
          646  +            _pt0 = project(canvas, pt0, view_transform, origin, pt1); }
          647  +        else if(_pt0 && !_pt1){
          648  +            _pt1 = project(canvas, pt1, view_transform, origin, pt0); }
          649  +            
          650  +        if(!_pt0 || !_pt1) continue;
          651  +        
          652  +        var x0 = _pt0[0];
          653  +        var y0 = _pt0[1];
          654  +
          655  +        var x1 = _pt1[0];
          656  +        var y1 = _pt1[1];
          657  +        
          658  +        ctx.moveTo(x0, y0);
          659  +        ctx.lineTo(x1, y1); }
          660  +    
          661  +    ctx.clearRect(0, 0, w, h);
          662  +    var hoffset = h * (1 - no_rotate_area)/ 2;
          663  +    var woffset = w * (1 - no_rotate_area)/ 2;
          664  +    
          665  +    ctx.fillStyle = "rgba(128, 128, 128, 0.05)";
          666  +    ctx.fillRect(0, 0, w, hoffset);
          667  +    ctx.fillRect(0, hoffset, woffset, h - 2 * hoffset);
          668  +    ctx.fillRect(w - woffset, hoffset, woffset, h - 2 * hoffset);
          669  +    ctx.fillRect(0, h - hoffset, w, hoffset);
          670  +    
          671  +    ctx.fillStyle = "rgb(0, 0, 128)";
          672  +    ctx.stroke();
          673  +    
          674  +    for(var k in solo_points){
          675  +       var pt = point_projections[k];
          676  +       if(pt) ctx.fillRect(pt[0] - 2, pt[1] - 2, 4, 4); }
          677  +        
          678  +    
          679  +    
          680  +    
          681  +    //if(editor_mode == MODE_DRAWING){
          682  +	ctx.fillStyle = "rgba(250, 0, 0, 0.9)";
          683  +	ctx.fillRect(w/2 - 2, h/2 - 2, 4, 4);
          684  +	
          685  +	for(var i = 0; i < selected_points.length; ++i){
          686  +		var pt;
          687  +		
          688  +		if(selected_points[i] == -1)
          689  +			pt = [w/2, h/2];
          690  +		else pt = point_projections[selected_points[i]];
          691  +		
          692  +		ctx.fillStyle = "rgba(0, 0, 0, 0.9)";
          693  +		if(pt) ctx.fillRect(pt[0] - 4, pt[1] - 4, 8, 8); }
          694  +	
          695  +	
          696  +	if(highlight_object !== null){
          697  +		
          698  +		
          699  +		if(highlight_object < point_projections.length){
          700  +			var pt;
          701  +			if(highlight_object == -1)
          702  +				pt = [w/2, h/2];
          703  +			else pt = point_projections[highlight_object];
          704  +			
          705  +			ctx.fillStyle = "rgba(50, 50, 50, .6)";    
          706  +			ctx.fillRect(pt[0] - 4, pt[1] - 4, 8, 8);
          707  +			document.body.style.cursor = "hand"; }
          708  +			
          709  +		else if(highlight_object < point_projections.length + line_midpoint_projections.length){
          710  +			// alert("drawing highlighted line");
          711  +			
          712  +			var line = lines[highlight_object - point_projections.length];
          713  +			if(line){
          714  +				var pt1 = point_projections[line[0]];
          715  +				var pt2 = point_projections[line[1]];
          716  +				
          717  +				if(pt1 && pt2){
          718  +				
          719  +					ctx.beginPath();
          720  +					
          721  +					ctx.moveTo(pt1[0], pt1[1]);
          722  +					ctx.lineTo(pt2[0], pt2[1]);
          723  +					ctx.lineWidth = 3;
          724  +					ctx.stroke();
          725  +					ctx.lineWidth = 1; }}}
          726  +			
          727  +		else{
          728  +			throw "highlight object index to large"; }}
          729  +		
          730  +	else{
          731  +		document.body.style.cursor = "crosshair"; }
          732  +	
          733  +	if(mouse_dragging){
          734  +	
          735  +		if(selected_points.length && (!key_state[16] || !key_state[16].state)){  // if shift is held, we are selecting more points.
          736  +			if(mouse_loc){
          737  +				ctx.beginPath();
          738  +				for(var i = 0; i < selected_points.length; ++i){
          739  +					var pt = point_projections[selected_points[i]];
          740  +					if(pt){
          741  +						ctx.moveTo(mouse_loc[0], mouse_loc[1]);
          742  +						ctx.lineTo(pt[0], pt[1]); }}
          743  +				ctx.stroke(); }}
          744  +		
          745  +		else if(mouse_loc && last_mouse_down){  // select points inside square            
          746  +			var minx = Math.min(mouse_loc[0], last_mouse_down[0]);
          747  +			var maxx = Math.max(mouse_loc[0], last_mouse_down[0]);
          748  +			var miny = Math.min(mouse_loc[1], last_mouse_down[1]);
          749  +			var maxy = Math.max(mouse_loc[1], last_mouse_down[1]);
          750  +			ctx.beginPath();
          751  +			ctx.rect(minx, miny, maxx-minx, maxy-miny);
          752  +			ctx.stroke(); }
          753  +	}
          754  +    
          755  +    ctx.fillStyle = "rgb(0,0,0)";
          756  +    ctx.fillText(msg, 5, 20); }
          757  +
          758  +    
          759  +    
          760  +//function buildFractal(children, func, levels, origin, scale, baseonly){
          761  +    
          762  +//}
          763  +
          764  +
          765  +function serpenskiPyramid(level, origin, scale){
          766  +    if(!scale) scale = 1;
          767  +    if(!origin) origin = [0, 0, 0];
          768  +    if(level === undefined) level = 4;
          769  +
          770  +    var spoints = [[0,0,0], [0.5,0,0], [0, -0.5, 0], [0, 0, 0.5]];
          771  +    
          772  +    
          773  +    for(var i = 0; i < spoints.length; ++i){
          774  +        var x = spoints[i];
          775  +        vector_scale(x, scale);
          776  +        vector_add(x, origin, x); }
          777  +    
          778  +    
          779  +    if(level > 0){
          780  +        for(var i = 0; i < spoints.length; ++i){
          781  +            var point = spoints[i];
          782  +            vector_add(origin, point, origin);
          783  +            serpenskiPyramid(level - 1, origin, 0.5 * scale);
          784  +            vector_minus(origin, point, origin); }
          785  +            
          786  +    }
          787  +    else if(level == 0){
          788  +        var pointRefs = [];
          789  +        for(var i = 0; i < spoints.length; ++i){
          790  +            vector_scale(spoints[i], 2);  // scale to points to full length;
          791  +            pointRefs[i] = addPoint(spoints[i]); }
          792  +            
          793  +        
          794  +        for(var i = 0; i < spoints.length; ++i){
          795  +            var a = pointRefs[i];
          796  +            
          797  +            for(var j = i + 1; j < spoints.length; ++j){
          798  +                var b = pointRefs[j];
          799  +                addLine(a, b); }}}
          800  +}
          801  +
          802  +    
          803  +function buildArray(origin, increment, size, buildfunc, extra1, extra2, extra3){
          804  +    var point = origin.slice(0);
          805  +    point_indexes = [];
          806  +    
          807  +    for(var i = 0; i < size.length; ++i){
          808  +        point_indexes[i] = 0; }
          809  +    
          810  +    var last_index = point_indexes.length - 1;
          811  +    var index = last_index;
          812  +    
          813  +    // build object at origin.
          814  +    buildfunc(point, extra1, extra2, extra3);
          815  +    
          816  +    while(index >= 0){
          817  +        //depth first
          818  +        
          819  +        if(point_indexes[index] == size[index] - 1){
          820  +            point[index] = origin[index];
          821  +            point_indexes[index] = 0;
          822  +            --index; }
          823  +        else{
          824  +            ++point_indexes[index];
          825  +            point[index] += increment[index];
          826  +            index = last_index;  // always go to the end position
          827  +            buildfunc(point, extra1, extra2, extra3); }}
          828  +}
          829  +    
          830  +
          831  +window.onload = function(){
          832  +
          833  +    // load localStorage saved state.
          834  +    if(localStorage.points && localStorage.lines){
          835  +        points = JSON.parse(localStorage.points);
          836  +        lines = JSON.parse(localStorage.lines); }
          837  +    
          838  +    canvas = document.createElement('canvas');
          839  +    document.body.appendChild(canvas);
          840  +    
          841  +    canvas.width = window.innerWidth - 20;
          842  +    canvas.height = window.innerHeight - 20;
          843  +    
          844  +    
          845  +    window.onresize = function(){
          846  +        canvas.width = window.innerWidth - 20;
          847  +        canvas.height = window.innerHeight - 20; }
          848  +
          849  +    //addBox([0, 0, 0], [.5, .5, .5]);
          850  +    
          851  +    /*
          852  +    buildArray( [0, 0, 0],  // origin
          853  +                [2, 2, 2], // increment
          854  +                [11, 1, 11], // size
          855  +                addBox,
          856  +                [.5, .5, .5]); */
          857  +    
          858  +    //var a = addPoint([0, 0, 1]);
          859  +    //var b = addPoint([.5, 0, 1]);
          860  +    //var c = addPoint([0, .5, 1]);
          861  +    
          862  +
          863  +    
          864  +    //addLine(a, b);
          865  +    //addLine(b, c);
          866  +    //addLine(c, a);
          867  +    
          868  +    //serpenskiPyramid(5, null, 1);
          869  +    
          870  +    
          871  +    
          872  +    function getRotationAngle(x, size, max, center_size){
          873  +
          874  +        if(!center_size) center_size = 0.25;  // the proportional size of the area in which no rotation is effected by the mouse movement
          875  +        if(!max) max = Math.PI/128;
          876  +        x -= size/2;
          877  +        
          878  +        //  center does nothing
          879  +        if(Math.abs(x) < size * center_size/2) x = 0;
          880  +        else if(x < 0) x += size * center_size/2;
          881  +        else           x -= size * center_size/2;
          882  +        
          883  +        return max * x / ((1 - center_size) * size/2); }
          884  +        
          885  +    
          886  +    document.body.onmousemove = function(event){
          887  +    //var test = function(event){
          888  +
          889  +        var x = event.pageX;
          890  +        var y = event.pageY;
          891  +        x -= canvas.offsetLeft;
          892  +        y -= canvas.offsetTop;
          893  +        mouse_loc = [x, y]
          894  +
          895  +        var w = canvas.width;
          896  +        var h = canvas.height;
          897  +        
          898  +        if(!mouse_dragging && last_mouse_down){
          899  +            var _x = x - last_mouse_down[0];
          900  +            var _y = y - last_mouse_down[1];
          901  +            
          902  +            var dist2 = _x*_x + _y*_y;
          903  +            
          904  +            if(dist2 > MIN_DRAG_DIST * MIN_DRAG_DIST){
          905  +                mouse_dragging = true; }}
          906  +                
          907  +    /*
          908  +        if(editor_mode == MODE_CAMERA_ROTATION){   //  rotate about center
          909  +        
          910  +            delta_horizontal_angle = getRotationAngle(x, w, max_angle_delta/frame_rate);
          911  +            delta_vertical_angle = -getRotationAngle(y, h, max_angle_delta/frame_rate); }
          912  +
          913  +
          914  +        if(editor_mode == MODE_CAMERA_POSITION){        
          915  +        
          916  +            delta_horizontal_angle = -getRotationAngle(x, w, max_angle_delta/frame_rate);
          917  +            delta_vertical_angle = getRotationAngle(y, h, max_angle_delta/frame_rate); }
          918  +            
          919  +
          920  +        else if(editor_mode == MODE_DRAWING){ */
          921  +
          922  +		delta_horizontal_angle = getRotationAngle(x, w, max_angle_delta/frame_rate, no_rotate_area);
          923  +		delta_vertical_angle = -getRotationAngle(y, h, max_angle_delta/frame_rate, no_rotate_area);
          924  +		
          925  +		var min_point_dist2 = LINE_HIGHLIGHT_DIST * LINE_HIGHLIGHT_DIST;
          926  +		highlight_object = null;
          927  +		
          928  +		var _x = canvas.width/2 - x;
          929  +		var _y = canvas.height/2 - y;
          930  +		var point_dist = _x*_x + _y*_y;
          931  +		
          932  +		if(point_dist < POINT_HIGHLIGHT_DIST * POINT_HIGHLIGHT_DIST){
          933  +			highlight_object = -1;
          934  +			min_point_dist = point_dist; }
          935  +			
          936  +			
          937  +		for(var i = 0; i < line_midpoint_projections.length; ++i){
          938  +			var pt = line_midpoint_projections[i];
          939  +			if(!pt) continue;
          940  +			
          941  +			var _x = pt[0] - x;
          942  +			var _y = pt[1] - y;
          943  +			point_dist = _x*_x + _y*_y;
          944  +			
          945  +			if(point_dist < min_point_dist2){  // alert('highlighting line');
          946  +				highlight_object = i + point_projections.length;
          947  +				min_point_dist = point_dist; }}
          948  +				
          949  +		min_point_dist2 = Math.min( min_point_dist2, POINT_HIGHLIGHT_DIST * POINT_HIGHLIGHT_DIST);
          950  +			
          951  +		for(var i = 0; i < point_projections.length; ++i){
          952  +			var pt = point_projections[i];
          953  +			if(!pt) continue;
          954  +			
          955  +			var _x = pt[0] - x;
          956  +			var _y = pt[1] - y;
          957  +			point_dist = _x*_x + _y*_y;
          958  +			
          959  +			if(point_dist < min_point_dist2){
          960  +				highlight_object = i;
          961  +				min_point_dist = point_dist; }}
          962  +    }
          963  + 
          964  +    setUiEvents();     
          965  +        
          966  +    document.body.oncontextmenu = function(){
          967  +        return false; };
          968  +   
          969  +    
          970  +    // draw(canvas);
          971  +    // var ctx = canvas.getContext('2d');
          972  +    // ctx.fillRect(0, 0, canvas.width, canvas.height);
          973  +    
          974  +    // 
          975  +    
          976  +    
          977  +    function animateLoop(){
          978  +        
          979  +        //if(editor_mode == MODE_DRAWING){
          980  +		if(selected_points.length){  // move selection if exists
          981  +			for(var i = 0; i < selected_points.length; ++i){
          982  +				var pt = points[selected_points[i]];
          983  +				vector_add(pt, delta_position, pt); }}
          984  +		
          985  +		else{
          986  +			moveCamera(delta_position, false); }
          987  +        
          988  +        //else if(editor_mode == MODE_CAMERA_POSITION){
          989  +        //    if(delta_position)  moveCamera(delta_position, true); }  // motion depends on camera angle
          990  +        
          991  +        if(delta_horizontal_angle) rotateHorizontal(delta_horizontal_angle);
          992  +        if(delta_vertical_angle) rotateVertical(delta_vertical_angle);
          993  +        
          994  +       
          995  +        //  overwrite point projections
          996  +        //  TODO this may not be perfect if points are deleted etc.        
          997  +        point_projections.length = 0;
          998  +        for(var i = 0; i < points.length; ++i){
          999  +            point_projections[i] = project(canvas, points[i], view_transform, origin); }
         1000  +            
         1001  +        line_midpoint_projections.length = 0;
         1002  +        
         1003  +        for(var i = 0; i < lines.length; ++i){
         1004  +            var line = lines[i];
         1005  +            if(!line){
         1006  +                line_midpoint_projections[i] = null;
         1007  +                continue; }
         1008  +                
         1009  +            var pt1 = point_projections[line[0]];
         1010  +            var pt2 = point_projections[line[1]];
         1011  +            if(pt1 && pt2){
         1012  +                line_midpoint_projections[i] = [(pt1[0] + pt2[0])/2, (pt1[1] + pt2[1])/2]; }
         1013  +            else{
         1014  +                line_midpoint_projections[i] = null; }}
         1015  +            
         1016  +
         1017  +        draw(canvas);
         1018  +    }
         1019  +    setInterval(animateLoop, 1000/frame_rate);
         1020  +    
         1021  +    var saveAction = function(){
         1022  +        localStorage.points = JSON.stringify(points);
         1023  +        localStorage.lines = JSON.stringify(lines);
         1024  +    }
         1025  +        
         1026  +    setInterval(saveAction, saveTimeout);
         1027  +}
         1028  +
         1029  +
         1030  +</script>
         1031  +
         1032  +
         1033  +<body></body>
         1034  +</html>

Added matrix_vector_lib.js version [8300025cc5].

            1  +
            2  +
            3  +//  matrix and vector functions.
            4  +//
            5  +//
            6  +
            7  +function vector_add(a, b, result){
            8  +    if(!result) result = [];
            9  +
           10  +    if(a.length > b.lemgth){
           11  +        var tmp = a;
           12  +        a = b; b = tmp; }
           13  +
           14  +    for(var i = 0; i < a.length; ++i){
           15  +        result[i] = a[i] + b[i]; }
           16  +
           17  +    for(var i = a.length; i < b.length; ++i){
           18  +        result[i] = b[i]; }
           19  +
           20  +    return result; }
           21  +
           22  +    
           23  +function vector_cpy(result, a){
           24  +    for(var i = 0; i < a.length; ++i){
           25  +        result[i] = a[i]; }
           26  +}
           27  +
           28  +
           29  +
           30  +function vector_minus(a, b, result){
           31  +    if(!result) result = [];
           32  +
           33  +    var lim = Math.max(a.length, b.length);
           34  +    
           35  +    for(var i = 0; i < lim; ++i){
           36  +		var aValue, bValue;
           37  +		if(i < a.length)
           38  +			aValue = a[i];
           39  +		else
           40  +			aValue = 0;
           41  +
           42  +		if(i < b.length)
           43  +			bValue = b[i];
           44  +		else
           45  +			bValue = 0;
           46  +			
           47  +		result[i] = aValue - bValue; }
           48  +        
           49  +        
           50  +    return result; }
           51  +   
           52  +
           53  +
           54  +function vector_dot(a, b){
           55  +    var lim = Math.min(a.length, b.length);
           56  +    var result = 0;
           57  +
           58  +    for(var i = 0; i < lim; ++i){
           59  +        result += a[i] * b[i]; }
           60  +
           61  +    return result; }
           62  +    
           63  +    
           64  +function vector_norm(a){
           65  +    return Math.sqrt(vector_dot(a,a)); }
           66  +
           67  +    
           68  +    
           69  +    
           70  +function vector_scale(a, s, result){
           71  +
           72  +    if(!result) result = [];
           73  +    
           74  +    for(var i = 0; i < a.length; ++i){
           75  +        result[i] = s * a[i]; }
           76  +
           77  +    return result; }
           78  +
           79  +
           80  +
           81  +// returns true iff the zero tail padded vectors match
           82  +function vector_cmp(a, b){
           83  +
           84  +    if(a.length > b.length){
           85  +         var tmp = a;
           86  +         a = b; b = tmp; }
           87  +
           88  +    for(var i = 0; i < a.length; ++i){
           89  +        if(a[i] != b[i]) 
           90  +			return false; }
           91  +
           92  +    for(var i = a.length; i < b.length; ++i){
           93  +        if(b[i] != 0) 
           94  +			return false; }
           95  +            
           96  +    return true; }
           97  +    
           98  +    
           99  +    
          100  +function midpoint(a, b, result){
          101  +
          102  +    if(b.length > a.length){
          103  +        var tmp = a;
          104  +        a = b; b = tmp; }
          105  +        
          106  +    if(!result) result = new Array(b.length);
          107  +        
          108  +    for(var i = 0; i < a.length; ++i){
          109  +        result[i] = (a[i] + b[i])/2; }
          110  +        
          111  +    for(var i = a.length; i < b.length; ++i){
          112  +        result[i] = b[i]; }
          113  +    
          114  +    return result;
          115  +}
          116  +
          117  +
          118  +
          119  +
          120  +//this function may not care, but the expected matrix format in this project is a list of column vectors.
          121  +function matrix_transpose(matrix){
          122  +
          123  +    var result = [];
          124  +    var dim0 = matrix.length;
          125  +    if(!dim0) 
          126  +		return result;
          127  +
          128  +    var dim1 = matrix[0].length;
          129  +
          130  +    for(var i = 0; i < dim1; ++i){
          131  +        var colvector = [];
          132  +        
          133  +        for(var j = 0; j < dim0; ++j){
          134  +            colvector.push(matrix[j][i]); }
          135  +
          136  +        result.push(colvector); }
          137  +        
          138  +    return result; }
          139  +    
          140  +
          141  +
          142  +    
          143  +    
          144  +
          145  +function matrix_mult(a, b){
          146  +
          147  +    var result = [];
          148  +    var a = matrix_transpose(a);
          149  +    var dim0 = a.length;
          150  +    var dim1 = b.length;
          151  +
          152  +    for(var i = 0; i < dim1; ++i){
          153  +        var colvector = [];
          154  +        
          155  +        for(var j = 0; j < dim0; ++j){
          156  +            colvector.push( vector_dot(a[j], b[i]) );  }
          157  +        result.push(colvector); }
          158  +
          159  +    return result; }
          160  +
          161  +
          162  +    
          163  +// finds the inverse of a 3 by 3 matrix, returns false if matrix has determinant zero.
          164  +//
          165  +
          166  +function matrix33inv(m){
          167  +
          168  +    // the matrix format is a list of column vectors.
          169  +    var a = m[0][0];
          170  +	var b = m[1][0];
          171  +	var c = m[2][0];
          172  +	var d = m[0][1];
          173  +	var e = m[1][1];
          174  +	var f = m[2][1];
          175  +	var g = m[0][2];
          176  +	var h = m[1][2];
          177  +	var k = m[2][2];
          178  +	
          179  +	// [ a b c
          180  +    //   d e f
          181  +    //   g h k ]
          182  +	
          183  +	
          184  +    var A = e*k - f*h;
          185  +	var B = f*g - d*k;
          186  +	var C = d*h - e*g;	
          187  +	var D = h*c - b*k;
          188  +	var E = a*k - c*g;
          189  +	var F = g*b - a*h;
          190  +	var G = b*f - e*c;
          191  +	var H = d*c - a*f;
          192  +	var K = a*e - d*b;
          193  +	
          194  +	
          195  +    var det = (a*A + b*B + c*C);
          196  +	
          197  +	
          198  +	if(det == 0) return false;
          199  +	
          200  +	// the multiplicative inverse of the determinant.
          201  +	var di = 1/det;
          202  +	
          203  +	// the matrix format is a list of column vectors.
          204  +	var result = [[di*A, di*B, di*C],
          205  +		          [di*D, di*E, di*F],
          206  +		          [di*G, di*H, di*K]];
          207  +
          208  +	return result;  }
          209  +
          210  +    
          211  +function compute_gradient(func, point, error){
          212  +
          213  +    if(!error) error = 0.00001;
          214  +
          215  +    // find gradient
          216  +    var otherpoint = point.slice(0);
          217  +    var gradient = new Array(point.length);
          218  +    var pointvalue = func(point);
          219  +    
          220  +    for(var i = 0; i < point.length; ++i){
          221  +        otherpoint[i] += error;
          222  +        var othervalue = func(otherpoint)
          223  +        gradient[i] = (othervalue - pointvalue) / error;
          224  +        
          225  +        otherpoint[i] -= error; }
          226  +    
          227  +    return gradient;
          228  +}
          229  +    
          230  +    
          231  +    
          232  +
          233  +//
          234  +// equation at:
          235  +// http://inside.mines.edu/~gmurray/ArbitraryAxisRotation/ArbitraryAxisRotation.html
          236  +// creates a rotation matrix about the given vector.
          237  +//
          238  +function vector_rotation(vector, s){
          239  +
          240  +    var len = Math.sqrt(vector[0]*vector[0]
          241  +                        + vector[1]*vector[1]
          242  +                        + vector[2]*vector[2]);
          243  +
          244  +    var u = vector[0]/len;
          245  +    var v = vector[1]/len;
          246  +    var w = vector[2]/len;
          247  +    
          248  +    var _cos = Math.cos(s);
          249  +    var _sin = Math.sin(s);
          250  +    var _1mcos = 1 - _cos;
          251  +
          252  +    return [[u*u*_1mcos + _cos, u*v*_1mcos - w*_sin, u*w*_1mcos + v*_sin],
          253  +            [u*v*_1mcos + w*_sin, v*v*_1mcos + _cos, v*w*_1mcos - u*_sin],
          254  +            [u*w*_1mcos - v*_sin, v*w*_1mcos + u*_sin, w*w*_1mcos + _cos]]; }