<html>
<script src='matrix_vector_lib.js' ></script>
<script>
//
// Editor modes
//
// Motion mode -- change the camera center using the original movement system
// --------Camera mode -- adjust the camera, scale zoom and position zoom, make it stationary or rotating about the center
// Edit mode -- create points at camera center, move points, create lines by clicking on successive points, create closed polygons etc.
// Editing Strategy
// During draw mode only the very edges of the screen rotate the view, and at reduced speed
// pressing enter or clicking on the center point creates a new point.
// points are moved with the arrow keys, the direction is absolute, that means it does not depend on the camera angle.
// the arrow keys move the selected point.
// clicking on a second point when one point is already selected creates a line.
// TODO control with zoom scales the selected object
// control with rotate rotates the selected objects
// 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.
// TODO when lines are deleted, readd the solo points to the solo_points list
//
// TODO z for undo shift + z for redo.
//
// TODO drop down minimalist menu in top right of canvas.
// drag to connect points
var canvas;
var origin = [0, 0, 0];
var frame_rate = 20;
var saveTimeout = 10*1000;
var no_rotate_area = 0.80;
var key_state = {};
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.
function addKeyListener(keycode, func){
if(!key_state[keycode]) key_state[keycode] = {};
var key_obj = key_state[keycode];
if(!key_obj.listeners) key_obj.listeners = [];
var listeners = key_obj.listeners;
listeners.push(func);
}
function keyDown(event, code){
var time = (new Date()).getTime();
var code = event.keyCode;
// alert("you pressed: " + code);
var key_obj = key_state[code];
if(!key_obj) key_obj = key_state[code] = {}; // create object
// update state etc.
var state = key_obj.state;
if(state) return; //key is already pressed.
state = 0;
var latest = key_obj.latest;
if(latest && time - latest < key_state_augmentation_delay)
state = key_obj.state = key_obj.lastState + 1;
else
state = key_obj.state = 1;
key_obj.latest = time;
key_obj.lastState = state;
// call listener functions
var listeners = key_obj.listeners
if(!listeners) return; //nothing to do.
for(var i = 0; i < listeners.length; ++i)
listeners[i](event, state, state); }
function keyUp(event){
var time = (new Date()).getTime();
var code = event.keyCode;
var key_obj = key_state[code];
if(!key_obj) return; //nothing to do.
key_obj.latest = time;
key_obj.lastState = key_obj.state;
var state = key_obj.state = 0;
var lastState = key_obj.lastState;
var listeners = key_obj.listeners;
if(!listeners) return; //nothing to do.
for(var i = 0; i < listeners.length; ++i)
listeners[i](event, state, lastState); }
function setUiEvents(){
document.body.onkeydown = keyDown;
document.body.onkeyup = keyUp;
document.body.onmousedown = function(event){
var x = event.pageX;
var y = event.pageY;
x -= canvas.offsetLeft;
y -= canvas.offsetTop;
last_mouse_down = [x, y];
var button = event.button;
var keycode = button + 1000; //treat clicks like a button press but with higher keycode.
keyDown({keyCode: keycode}); } // it seems the keyCode of the original event cant be overwritten.
document.body.onmousewheel = function(event){
var delta = event.wheelDelta;
//if(editor_mode == MODE_DRAWING){
if(delta < 0)
zoom_dist /= zoom_factor;
else if(delta > 0)
zoom_dist *= zoom_factor; }
document.body.onmouseup = function(event){
var button = event.button;
var keycode = button + 1000; //treat clicks like a button press but with higher keycode.
keyUp({keyCode: keycode});
last_mouse_down = null;
mouse_dragging = false; }}
function changeVelocity(direction, positive, state, lastState){
if(state == 0){
state = lastState;
positive = !positive; } // reverse the velocity
var delta = Math.pow(2, state - 1) * max_move_delta / frame_rate;
if(!positive) delta = -delta;
var velocityChange = [0, 0, 0];
velocityChange[direction] = delta;
vector_add(delta_position, velocityChange, delta_position); }
//
// This
//
function leftPress(e, s, lasts){
changeVelocity(0, false, s, lasts); }
function rightPress(e, s, lasts){
changeVelocity(0, true, s, lasts); }
function downPress(e, s, lasts){
changeVelocity(1, false, s, lasts); }
function upPress(e, s, lasts){
changeVelocity(1, true, s, lasts); }
function backwardPress(e, s, lasts){
changeVelocity(2, false, s, lasts); }
function forwardPress(e, s, lasts){
changeVelocity(2, true, s, lasts); }
function zoomInPress(e, s, lasts){
//if(editor_mode == MODE_CAMERA_POSITION)
// changeVelocity(2, true, s, lasts);
//else if(editor_mode == MODE_CAMERA_ROTATION)
zoom_dist *= zoom_factor; }
function zoomOutPress(e, s, lasts){
//if(editor_mode == MODE_CAMERA_POSITION)
// changeVelocity(2, true, s, lasts);
//else if(editor_mode == MODE_CAMERA_ROTATION)
zoom_dist *= zoom_factor; }
function copySelectedPoints(e, s, lasts){
if(s){
var new_selection = [];
var selection_map = {};
for(var i = 0; i < selected_points.length; ++i){
selection_map[selected_points[i]] = i;
var pt = points[selected_points[i]];
var newpt = addPoint(pt);
new_selection[i] = newpt; }
//if(key_state[16] && key_state[16].state){ //shift + space copies lines as well.
// copy lines as well
for(var i = 0; i < lines.length; ++i){
var line = lines[i];
if(!line) continue;
var pt1 = lines[i][0];
var pt2 = lines[i][1];
var i1 = selection_map[pt1];
var i2 = selection_map[pt2];
if(i1 !== undefined && i2 !== undefined){ // both points are selected
addLine(new_selection[i1], new_selection[i2]); }}
selected_points = new_selection; }}
addKeyListener(32, copySelectedPoints);
addKeyListener(37, leftPress); // arrow keys
addKeyListener(38, forwardPress);
addKeyListener(39, rightPress);
addKeyListener(40, backwardPress);
addKeyListener(65, leftPress); // wasd
addKeyListener(87, forwardPress);
addKeyListener(68, rightPress);
addKeyListener(83, backwardPress);
addKeyListener(69, upPress);
addKeyListener(81, downPress);
addKeyListener(88, function(e, s, lasts){ // x
//if(editor_mode == MODE_DRAWING && s){ // alert('deleting points');
if(s){
if(highlight_object !== null && highlight_object > 0){ // delete highlighted object
if(highlight_object < point_projections.length){ // point
points[highlight_object] = null; }
else if(highlight_object < point_projections.length + line_midpoint_projections.length){ // line
lines[highlight_object - point_projections.length] = null; }
else throw "highlight object index to large"; }
else if(selected_points.length){ // delete selected points
for(var i = 0; i < selected_points.length; ++i){
points[selected_points[i]] = null; }
selected_points.length = 0; }
cleanupDeletedPoints();
highlight_object = null;
selected_points.length = 0; }});
// addKeyListener(1000, aForwardPress); // mouse buttons
// addKeyListener(1002, cameraBackwardPress);
// addKeyListener(13, function(e, s, lasts){ if(s) nextMode(); }); // switch modes
// select point
addKeyListener(1000, function(e, s, lasts){
//if(editor_mode == MODE_DRAWING){
if(s){
if(highlight_object !== null){
if(highlight_object == -1)
highlight_object = addPoint(origin); // create a new point at the origin
var alreadySelected = false; // only set for shift clicks
if(key_state[16] && key_state[16].state){ // shift key is pressed.
for(var i = 0; i < selected_points.length; ++i){
if(selected_points[i] == highlight_object){
selected_points.splice(i, 1); // deselect point
alreadySelected = true;
break; }}}
else{
selected_points.length = 0;}
if(!alreadySelected) selected_points.push(highlight_object); }
else if(!key_state[16] || !key_state[16].state)
selected_points.length = 0; }
else if(lasts && mouse_dragging ){ // drag actions
// connect selected points to highlight point with lines
if(selected_points.length && (!key_state[16] || !key_state[16].state)){ // TODO right now this really only works for drawing a single line,
if(highlight_object !== null && highlight_object < point_projections.length){
if(highlight_object == -1) highlight_object = addPoint(origin);
for(var i = 0; i < selected_points.length; ++i){
var pt = selected_points[i];
if(pt == highlight_object) continue;
addLine(pt, highlight_object); }
selected_points.length = 0; }}
// select points inside selection rectangle
else if(mouse_loc && last_mouse_down){
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]);
var selected_point_map = {};
if(!key_state[16] || !key_state[16].state){
selected_points.length = 0;
selected_lines.length = 0; }
else{
for(var i = 0; i < selected_points.length; ++i){
selected_point_map[selected_points[i]] = i; }}
for(var i = 0; i < point_projections.length; ++i){
if(selected_point_map[i]) continue; // already selected.
var pt = point_projections[i];
if(!pt) continue;
var _x = pt[0];
var _y = pt[1];
if(minx < _x && _x < maxx
&& miny < _y && _y < maxy){
selected_points.push(i); }}}
}});
// to prevent camera from rotating beyond straight vertical
var vertical_camera_rotation = 0;
var NAVIGATION_ZOOM_DIST = 0.01;
var zoom_dist = 1;
var zoom_factor = 0.83;
var zoom_scale = 2.667; // scalar zoom property.
var last_zoom_dist = 1;
// camera animation
var max_move_delta = 0.01;
var max_angle_delta = Math.PI / 2;
var delta_horizontal_angle = 0;
var delta_vertical_angle = 0;
var delta_position = [0,0,0];
// var MODE_DRAWING = 0;
// var MODE_CAMERA_POSITION = 1; // change the camera focus point
// var MODE_CAMERA_ROTATION = 2; // rotate and zoom camera
// var editor_mode = MODE_DRAWING;
/*
function drawMode(){
editor_mode = MODE_DRAWING;
delta_position = [0,0,0];
// delta_horizontal_angle = 0;
// delta_vertical_angle = 0;
if(last_zoom_dist) zoom_dist = last_zoom_dist;
else zoom_dist = 1;
document.body.style.cursor = "crosshair"; }
function cameraPositionMode(){
document.body.style.cursor = "default";
last_zoom_dist = zoom_dist;
delta_horizontal_angle = 0;
delta_vertical_angle = 0;
zoom_dist = NAVIGATION_ZOOM_DIST;
editor_mode = MODE_CAMERA_POSITION; }
function cameraRotationMode(){
document.body.style.cursor = "default";
delta_horizontal_angle = 0;
delta_vertical_angle = 0;
if(last_zoom_dist) zoom_dist = last_zoom_dist;
else zoom_dist = 1;
editor_mode = MODE_CAMERA_ROTATION; }
function nextMode(){
if(editor_mode == MODE_DRAWING) cameraPositionMode();
else if(editor_mode == MODE_CAMERA_POSITION) drawMode(); }
// skipping camera rotation mode
//else if(editor_mode == MODE_CAMERA_POSITION) cameraRotationMode();
//else if(editor_mode == MODE_CAMERA_ROTATION) drawMode(); }
*/
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 MIN_DRAG_DIST = 10;
var POINT_HIGHLIGHT_DIST = 10;
var LINE_HIGHLIGHT_DIST = 15;
var selected_points = [];
var selected_lines = [];
var msg = "3D drawing tool";
var view_transform =
[[1, 0, 0],
[0, 1, 0],
[0, 0, 1]];
// camera functions
function rotateView(norm, theta, cameracentric){
// camera centric uses the camera axes to perform the rotation instead of the space axes.
if(cameracentric) norm = matrix_mult(view_transform, [norm])[0];
var rotation = vector_rotation(norm, theta);
view_transform = matrix_mult(rotation, view_transform); }
function rotateHorizontal(theta){
rotateView([0, 1, 0], theta, false); }
function rotateVertical(theta){
if(vertical_camera_rotation + theta > Math.PI/2)
theta = Math.PI/2 - vertical_camera_rotation;
if(vertical_camera_rotation + theta < -Math.PI/2)
theta = -Math.PI/2 - vertical_camera_rotation;
vertical_camera_rotation += theta;
rotateView([1, 0, 0], theta, true); }
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 addBox(xyz, wdh){
var x = xyz[0];
var y = xyz[1];
var z = xyz[2];
var w = wdh[0];
var d = wdh[1];
var h = wdh[2];
var x0 = x - w/2;
var y0 = y - d/2;
var z0 = z - d/2;
var x1 = x + w/2;
var y1 = y + d/2;
var z1 = z + h/2;
var a = addPoint([x0, y0, z0]);
var b = addPoint([x1, y0, z0]);
var c = addPoint([x1, y1, z0]);
var d = addPoint([x0, y1, z0]);
var e = addPoint([x0, y0, z1]);
var f = addPoint([x1, y0, z1]);
var g = addPoint([x1, y1, z1]);
var h = addPoint([x0, y1, z1]);
addLine(a, b);
addLine(b, c);
addLine(c, d);
addLine(d, a);
addLine(e, f);
addLine(f, g);
addLine(g, h);
addLine(h, e);
addLine(a, e);
addLine(b, f);
addLine(c, g);
addLine(d, h); }
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);
var hoffset = h * (1 - no_rotate_area)/ 2;
var woffset = w * (1 - no_rotate_area)/ 2;
ctx.fillStyle = "rgba(128, 128, 128, 0.05)";
ctx.fillRect(0, 0, w, hoffset);
ctx.fillRect(0, hoffset, woffset, h - 2 * hoffset);
ctx.fillRect(w - woffset, hoffset, woffset, h - 2 * hoffset);
ctx.fillRect(0, h - hoffset, w, hoffset);
ctx.fillStyle = "rgb(0, 0, 128)";
ctx.stroke();
for(var k in solo_points){
var pt = point_projections[k];
if(pt) ctx.fillRect(pt[0] - 2, pt[1] - 2, 4, 4); }
//if(editor_mode == MODE_DRAWING){
ctx.fillStyle = "rgba(250, 0, 0, 0.9)";
ctx.fillRect(w/2 - 2, h/2 - 2, 4, 4);
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 = "rgba(0, 0, 0, 0.9)";
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);
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 && (!key_state[16] || !key_state[16].state)){ // if shift is held, we are selecting more points.
if(mouse_loc){
ctx.beginPath();
for(var i = 0; i < selected_points.length; ++i){
var pt = point_projections[selected_points[i]];
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){ // 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)";
ctx.fillText(msg, 5, 20); }
//function buildFractal(children, func, levels, origin, scale, baseonly){
//}
function serpenskiPyramid(level, origin, scale){
if(!scale) scale = 1;
if(!origin) origin = [0, 0, 0];
if(level === undefined) level = 4;
var spoints = [[0,0,0], [0.5,0,0], [0, -0.5, 0], [0, 0, 0.5]];
for(var i = 0; i < spoints.length; ++i){
var x = spoints[i];
vector_scale(x, scale);
vector_add(x, origin, x); }
if(level > 0){
for(var i = 0; i < spoints.length; ++i){
var point = spoints[i];
vector_add(origin, point, origin);
serpenskiPyramid(level - 1, origin, 0.5 * scale);
vector_minus(origin, point, origin); }
}
else if(level == 0){
var pointRefs = [];
for(var i = 0; i < spoints.length; ++i){
vector_scale(spoints[i], 2); // scale to points to full length;
pointRefs[i] = addPoint(spoints[i]); }
for(var i = 0; i < spoints.length; ++i){
var a = pointRefs[i];
for(var j = i + 1; j < spoints.length; ++j){
var b = pointRefs[j];
addLine(a, b); }}}
}
function buildArray(origin, increment, size, buildfunc, extra1, extra2, extra3){
var point = origin.slice(0);
point_indexes = [];
for(var i = 0; i < size.length; ++i){
point_indexes[i] = 0; }
var last_index = point_indexes.length - 1;
var index = last_index;
// build object at origin.
buildfunc(point, extra1, extra2, extra3);
while(index >= 0){
//depth first
if(point_indexes[index] == size[index] - 1){
point[index] = origin[index];
point_indexes[index] = 0;
--index; }
else{
++point_indexes[index];
point[index] += increment[index];
index = last_index; // always go to the end position
buildfunc(point, extra1, extra2, extra3); }}
}
window.onload = function(){
// load localStorage saved state.
if(localStorage.points && localStorage.lines){
points = JSON.parse(localStorage.points);
lines = JSON.parse(localStorage.lines); }
canvas = document.createElement('canvas');
document.body.appendChild(canvas);
canvas.width = window.innerWidth - 20;
canvas.height = window.innerHeight - 20;
window.onresize = function(){
canvas.width = window.innerWidth - 20;
canvas.height = window.innerHeight - 20; }
//addBox([0, 0, 0], [.5, .5, .5]);
/*
buildArray( [0, 0, 0], // origin
[2, 2, 2], // increment
[11, 1, 11], // size
addBox,
[.5, .5, .5]); */
//var a = addPoint([0, 0, 1]);
//var b = addPoint([.5, 0, 1]);
//var c = addPoint([0, .5, 1]);
//addLine(a, b);
//addLine(b, c);
//addLine(c, a);
//serpenskiPyramid(5, null, 1);
function getRotationAngle(x, size, max, center_size){
if(!center_size) center_size = 0.25; // the proportional size of the area in which no rotation is effected by the mouse movement
if(!max) max = Math.PI/128;
x -= size/2;
// center does nothing
if(Math.abs(x) < size * center_size/2) x = 0;
else if(x < 0) x += size * center_size/2;
else x -= size * center_size/2;
return max * x / ((1 - center_size) * size/2); }
document.body.onmousemove = function(event){
//var test = function(event){
var x = event.pageX;
var y = event.pageY;
x -= canvas.offsetLeft;
y -= canvas.offsetTop;
mouse_loc = [x, y]
var w = canvas.width;
var h = canvas.height;
if(!mouse_dragging && last_mouse_down){
var _x = x - last_mouse_down[0];
var _y = y - last_mouse_down[1];
var dist2 = _x*_x + _y*_y;
if(dist2 > MIN_DRAG_DIST * MIN_DRAG_DIST){
mouse_dragging = true; }}
/*
if(editor_mode == MODE_CAMERA_ROTATION){ // rotate about center
delta_horizontal_angle = getRotationAngle(x, w, max_angle_delta/frame_rate);
delta_vertical_angle = -getRotationAngle(y, h, max_angle_delta/frame_rate); }
if(editor_mode == MODE_CAMERA_POSITION){
delta_horizontal_angle = -getRotationAngle(x, w, max_angle_delta/frame_rate);
delta_vertical_angle = getRotationAngle(y, h, max_angle_delta/frame_rate); }
else if(editor_mode == MODE_DRAWING){ */
delta_horizontal_angle = getRotationAngle(x, w, max_angle_delta/frame_rate, no_rotate_area);
delta_vertical_angle = -getRotationAngle(y, h, max_angle_delta/frame_rate, no_rotate_area);
var min_point_dist2 = LINE_HIGHLIGHT_DIST * LINE_HIGHLIGHT_DIST;
highlight_object = null;
var _x = canvas.width/2 - x;
var _y = canvas.height/2 - y;
var point_dist = _x*_x + _y*_y;
if(point_dist < POINT_HIGHLIGHT_DIST * POINT_HIGHLIGHT_DIST){
highlight_object = -1;
min_point_dist = point_dist; }
for(var i = 0; i < line_midpoint_projections.length; ++i){
var pt = line_midpoint_projections[i];
if(!pt) continue;
var _x = pt[0] - x;
var _y = pt[1] - y;
point_dist = _x*_x + _y*_y;
if(point_dist < min_point_dist2){ // alert('highlighting line');
highlight_object = i + point_projections.length;
min_point_dist = point_dist; }}
min_point_dist2 = Math.min( min_point_dist2, POINT_HIGHLIGHT_DIST * POINT_HIGHLIGHT_DIST);
for(var i = 0; i < point_projections.length; ++i){
var pt = point_projections[i];
if(!pt) continue;
var _x = pt[0] - x;
var _y = pt[1] - y;
point_dist = _x*_x + _y*_y;
if(point_dist < min_point_dist2){
highlight_object = i;
min_point_dist = point_dist; }}
}
setUiEvents();
document.body.oncontextmenu = function(){
return false; };
// draw(canvas);
// var ctx = canvas.getContext('2d');
// ctx.fillRect(0, 0, canvas.width, canvas.height);
//
function animateLoop(){
//if(editor_mode == MODE_DRAWING){
if(selected_points.length){ // move selection if exists
for(var i = 0; i < selected_points.length; ++i){
var pt = points[selected_points[i]];
vector_add(pt, delta_position, pt); }}
else{
moveCamera(delta_position, false); }
//else if(editor_mode == MODE_CAMERA_POSITION){
// if(delta_position) moveCamera(delta_position, true); } // motion depends on camera angle
if(delta_horizontal_angle) rotateHorizontal(delta_horizontal_angle);
if(delta_vertical_angle) rotateVertical(delta_vertical_angle);
// overwrite point projections
// TODO this may not be perfect if points are deleted etc.
point_projections.length = 0;
for(var i = 0; i < points.length; ++i){
point_projections[i] = project(canvas, points[i], view_transform, origin); }
line_midpoint_projections.length = 0;
for(var i = 0; i < lines.length; ++i){
var line = lines[i];
if(!line){
line_midpoint_projections[i] = null;
continue; }
var pt1 = point_projections[line[0]];
var pt2 = point_projections[line[1]];
if(pt1 && pt2){
line_midpoint_projections[i] = [(pt1[0] + pt2[0])/2, (pt1[1] + pt2[1])/2]; }
else{
line_midpoint_projections[i] = null; }}
draw(canvas);
}
setInterval(animateLoop, 1000/frame_rate);
var saveAction = function(){
localStorage.points = JSON.stringify(points);
localStorage.lines = JSON.stringify(lines);
}
setInterval(saveAction, saveTimeout);
}
</script>
<body></body>
</html>