You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1559 lines
42 KiB
JavaScript

import { matmul2 } from "./util.js";
import { Quaternion, Vector3 } from "./lib/three.module.js";
class ProjectiveView {
constructor(
ui,
cfg,
on_edge_changed,
on_direction_changed,
on_auto_shrink,
on_moved,
on_scale,
on_wheel,
on_fit_size,
on_auto_rotate,
on_reset_rotate,
on_focus,
on_box_remove,
fn_isActive
) {
this.ui = ui;
this.cfg = cfg;
this.on_edge_changed = on_edge_changed;
this.on_direction_changed = on_direction_changed;
this.on_auto_shrink = on_auto_shrink;
this.on_moved = on_moved;
this.on_scale = on_scale;
this.on_wheel = on_wheel;
this.on_fit_size = on_fit_size;
this.on_auto_rotate = on_auto_rotate;
this.on_reset_rotate = on_reset_rotate;
this.on_focus = on_focus;
this.on_box_remove = on_box_remove;
this.isActive = fn_isActive;
this.lines = {
top: ui.querySelector("#line-top"),
bottom: ui.querySelector("#line-bottom"),
left: ui.querySelector("#line-left"),
right: ui.querySelector("#line-right"),
direction: ui.querySelector("#line-direction"),
};
this.orgPointInd = ui.querySelector("#origin-point-indicator");
this.svg = ui.querySelector("#view-svg");
this.handles = {
top: ui.querySelector("#line-top-handle"),
bottom: ui.querySelector("#line-bottom-handle"),
left: ui.querySelector("#line-left-handle"),
right: ui.querySelector("#line-right-handle"),
direction: ui.querySelector("#line-direction-handle"),
topleft: ui.querySelector("#top-left-handle"),
topright: ui.querySelector("#top-right-handle"),
bottomleft: ui.querySelector("#bottom-left-handle"),
bottomright: ui.querySelector("#bottom-right-handle"),
move: ui.querySelector("#move-handle"),
};
this.buttons = {
fit_position: ui.querySelector("#v-fit-position"),
fit_size: ui.querySelector("#v-fit-size"),
fit_rotation: ui.querySelector("#v-fit-rotation"),
fit_all: ui.querySelector("#v-fit-all"),
reset_rotation: ui.querySelector("#v-reset-rotation"),
fit_moving_direction: ui.querySelector("#v-fit-moving-direction"),
};
ui.onkeydown = this.on_key_down.bind(this);
ui.onmouseenter = (event) => {
if (this.isActive()) {
ui.focus();
ui.querySelector("#v-buttons").style.display = "inherit";
if (this.on_focus) this.on_focus();
}
};
ui.onmouseleave = (event) => {
if (this.showButtonsTimer) clearTimeout(this.showButtonsTimer);
this.hide_buttons();
ui.blur();
};
ui.onwheel = (event) => {
event.stopPropagation();
event.preventDefault();
this.on_wheel(event.deltaY);
};
this.install_edge_hanler("left", this.handles.left, this.lines, {
x: -1,
y: 0,
});
this.install_edge_hanler("right", this.handles.right, this.lines, {
x: 1,
y: 0,
});
this.install_edge_hanler("top", this.handles.top, this.lines, {
x: 0,
y: 1,
});
this.install_edge_hanler("bottom", this.handles.bottom, this.lines, {
x: 0,
y: -1,
});
this.install_edge_hanler("top,left", this.handles.topleft, this.lines, {
x: -1,
y: 1,
});
this.install_edge_hanler("top,right", this.handles.topright, this.lines, {
x: 1,
y: 1,
});
this.install_edge_hanler(
"bottom,left",
this.handles.bottomleft,
this.lines,
{ x: -1, y: -1 }
);
this.install_edge_hanler(
"bottom,right",
this.handles.bottomright,
this.lines,
{ x: 1, y: -1 }
);
this.install_edge_hanler(
"left,right,top,bottom",
this.handles.move,
this.lines,
null
);
if (this.on_direction_changed) {
this.install_direction_handler("line-direction");
}
this.install_buttons();
}
mouse_start_pos = null;
view_handle_dimension = {
//dimension of the enclosed box
x: 0, //width
y: 0, //height
};
view_center = {
x: 0,
y: 0,
};
line(name) {
return this.lines[name];
}
show_lines() {
let theme = document.documentElement.className;
let lineColor = "yellow";
if (theme == "theme-light") lineColor = "red";
for (var l in this.lines) {
this.lines[l].style.stroke = lineColor;
}
}
hide_lines() {
for (var l in this.lines) {
this.lines[l].style.stroke = "#00000000";
}
}
hightlight_line(line) {
let theme = document.documentElement.className;
let lineColor = "red";
if (theme == "theme-light") lineColor = "blue";
line.style.stroke = lineColor;
}
disable_handle_except(exclude) {
for (var h in this.handles) {
if (this.handles[h] != exclude) this.handles[h].style.display = "none";
}
}
enable_handles() {
for (var h in this.handles) {
this.handles[h].style.display = "inherit";
}
}
move_lines(delta, direction) {
var x1 = this.view_center.x - this.view_handle_dimension.x / 2;
var y1 = this.view_center.y - this.view_handle_dimension.y / 2;
var x2 = this.view_center.x + this.view_handle_dimension.x / 2;
var y2 = this.view_center.y + this.view_handle_dimension.y / 2;
if (direction) {
if (direction.x == 1) {
//right
x2 += delta.x;
} else if (direction.x == -1) {
//left
x1 += delta.x;
}
if (direction.y == -1) {
//bottom
y2 += delta.y;
} else if (direction.y == 1) {
//top
y1 += delta.y;
}
} else {
x1 += delta.x;
y1 += delta.y;
x2 += delta.x;
y2 += delta.y;
}
this.set_line_pos(
Math.ceil(x1),
Math.ceil(x2),
Math.ceil(y1),
Math.ceil(y2)
);
}
set_line_pos(x1, x2, y1, y2) {
this.lines.top.setAttribute("x1", "0%");
this.lines.top.setAttribute("y1", y1);
this.lines.top.setAttribute("x2", "100%");
this.lines.top.setAttribute("y2", y1);
this.lines.bottom.setAttribute("x1", "0%");
this.lines.bottom.setAttribute("y1", y2);
this.lines.bottom.setAttribute("x2", "100%");
this.lines.bottom.setAttribute("y2", y2);
this.lines.left.setAttribute("x1", x1);
this.lines.left.setAttribute("y1", "0%");
this.lines.left.setAttribute("x2", x1);
this.lines.left.setAttribute("y2", "100%");
this.lines.right.setAttribute("x1", x2);
this.lines.right.setAttribute("y1", "0%");
this.lines.right.setAttribute("x2", x2);
this.lines.right.setAttribute("y2", "100%");
}
set_org_point_ind_pos(viewWidth, viewHeight, objPos, objRot) {
/*
cos -sin
sin cos
*
objPos.x
objPos.y
*/
let c = Math.cos(objRot); // for topview, x goes upward, so we add pi/2
let s = Math.sin(objRot);
let relx = c * -objPos.x + s * -objPos.y;
let rely = -s * -objPos.x + c * -objPos.y;
let radius = Math.sqrt(
(viewWidth * viewWidth) / 4 + (viewHeight * viewHeight) / 4
);
let distToRog = Math.sqrt(relx * relx + rely * rely);
let indPosX3d = (relx * radius) / distToRog;
let indPosY3d = (rely * radius) / distToRog;
let indPosX = -indPosY3d;
let indPosY = -indPosX3d;
let dotRelPos = 0.8;
// now its pixel coordinates, x goes right, y goes down
if (indPosX > (viewWidth / 2) * dotRelPos) {
let shrinkRatio = ((viewWidth / 2) * dotRelPos) / indPosX;
indPosX = (viewWidth / 2) * dotRelPos;
indPosY = indPosY * shrinkRatio;
}
if (indPosX < (-viewWidth / 2) * dotRelPos) {
let shrinkRatio = ((-viewWidth / 2) * dotRelPos) / indPosX;
indPosX = (-viewWidth / 2) * dotRelPos;
indPosY = indPosY * shrinkRatio;
}
if (indPosY > (viewHeight / 2) * dotRelPos) {
let shrinkRatio = ((viewHeight / 2) * dotRelPos) / indPosY;
indPosY = (viewHeight / 2) * dotRelPos;
indPosX = indPosX * shrinkRatio;
}
if (indPosY < (-viewHeight / 2) * dotRelPos) {
let shrinkRatio = ((-viewHeight / 2) * dotRelPos) / indPosY;
indPosY = (-viewHeight / 2) * dotRelPos;
indPosX = indPosX * shrinkRatio;
}
this.orgPointInd.setAttribute("cx", viewWidth / 2 + indPosX);
this.orgPointInd.setAttribute("cy", viewHeight / 2 + indPosY);
}
// when direction handler is draging
rotate_lines(theta) {
console.log(theta);
theta = -theta - Math.PI / 2;
console.log(theta);
// we use rotation matrix
var trans_matrix = [
Math.cos(theta),
Math.sin(theta),
this.view_center.x,
-Math.sin(theta),
Math.cos(theta),
this.view_center.y,
0,
0,
1,
];
var points; /*= `[
-view_handle_dimension.x/2, view_handle_dimension.x/2,view_handle_dimension.x/2,-view_handle_dimension.x/2, 0,
-view_handle_dimension.y/2, -view_handle_dimension.y/2,view_handle_dimension.y/2, view_handle_dimension.y/2, -this.view_center.y,
1,1,1,1,1
]; */
var trans_points; //= matmul2(trans_matrix, points, 3);
//console.log(points);
//var trans_points ;//= matmul2(trans_matrix, points, 3);
//console.log(trans_points);
points = [0, -this.view_center.y, 1];
trans_points = matmul2(trans_matrix, points, 3);
this.lines.direction.setAttribute("x2", Math.ceil(trans_points[0]));
this.lines.direction.setAttribute("y2", Math.ceil(trans_points[1]));
points = [
-this.view_center.x,
this.view_center.x, //-view_handle_dimension.x/2, view_handle_dimension.x/2,
-this.view_handle_dimension.y / 2,
-this.view_handle_dimension.y / 2,
1,
1,
];
var trans_points = matmul2(trans_matrix, points, 3);
this.lines.top.setAttribute("x1", Math.ceil(trans_points[0]));
this.lines.top.setAttribute("y1", Math.ceil(trans_points[0 + 2]));
this.lines.top.setAttribute("x2", Math.ceil(trans_points[1]));
this.lines.top.setAttribute("y2", Math.ceil(trans_points[1 + 2]));
points = [
-this.view_handle_dimension.x / 2,
-this.view_handle_dimension.x / 2,
-this.view_center.y,
this.view_center.y,
1,
1,
];
trans_points = matmul2(trans_matrix, points, 3);
this.lines.left.setAttribute("x1", Math.ceil(trans_points[0]));
this.lines.left.setAttribute("y1", Math.ceil(trans_points[0 + 2]));
this.lines.left.setAttribute("x2", Math.ceil(trans_points[1]));
this.lines.left.setAttribute("y2", Math.ceil(trans_points[1 + 2]));
points = [
this.view_center.x,
-this.view_center.x,
this.view_handle_dimension.y / 2,
this.view_handle_dimension.y / 2,
1,
1,
];
trans_points = matmul2(trans_matrix, points, 3);
this.lines.bottom.setAttribute("x1", Math.ceil(trans_points[1]));
this.lines.bottom.setAttribute("y1", Math.ceil(trans_points[1 + 2]));
this.lines.bottom.setAttribute("x2", Math.ceil(trans_points[0]));
this.lines.bottom.setAttribute("y2", Math.ceil(trans_points[0 + 2]));
points = [
this.view_handle_dimension.x / 2,
this.view_handle_dimension.x / 2,
-this.view_center.y,
this.view_center.y,
1,
1,
];
trans_points = matmul2(trans_matrix, points, 3);
this.lines.right.setAttribute("x1", Math.ceil(trans_points[0]));
this.lines.right.setAttribute("y1", Math.ceil(trans_points[0 + 2]));
this.lines.right.setAttribute("x2", Math.ceil(trans_points[1]));
this.lines.right.setAttribute("y2", Math.ceil(trans_points[1 + 2]));
}
update_view_handle(viewport, obj_dimension, obj_pos, obj_rot) {
var viewport_ratio = viewport.width / viewport.height;
var box_ratio = obj_dimension.x / obj_dimension.y;
var width = 0;
var height = 0;
if (box_ratio > viewport_ratio) {
//handle width is viewport.width*2/3
width = (viewport.width * (2 / 3)) / viewport.zoom_ratio;
height = width / box_ratio;
} else {
//handle height is viewport.height*2/3
height = (viewport.height * 2) / 3 / viewport.zoom_ratio;
width = height * box_ratio;
}
this.view_handle_dimension.x = width;
this.view_handle_dimension.y = height;
// viewport width/height is position-irrelavent
// so x and y is relative value.
var x = viewport.width / 2; //viewport.left + viewport.width/2;
var y = viewport.height / 2; //viewport.bottom - viewport.height/2;
var left = x - width / 2;
var right = x + width / 2;
var top = y - height / 2;
var bottom = y + height / 2;
this.view_center.x = x;
this.view_center.y = y;
this.set_line_pos(left, right, top, bottom);
if (obj_pos && obj_rot) {
this.set_org_point_ind_pos(
viewport.width,
viewport.height,
obj_pos,
obj_rot
);
}
// note when the object is too thin, the height/width value may be negative,
// this causes error reporting, but we just let it be.
var de = this.handles.left;
de.setAttribute("x", Math.ceil(left - 10));
de.setAttribute("y", "0%"); //Math.ceil(top+10));
de.setAttribute("height", "100%"); //Math.ceil(bottom-top-20));
de.setAttribute("width", 20);
de = this.handles.right;
de.setAttribute("x", Math.ceil(right - 10));
de.setAttribute("y", "0%"); //Math.ceil(top+10));
de.setAttribute("height", "100%"); //Math.ceil(bottom-top-20));
de.setAttribute("width", 20);
de = this.handles.top;
de.setAttribute("x", "0%"); //Math.ceil(left+10));
de.setAttribute("y", Math.ceil(top - 10));
de.setAttribute("width", "100%"); //Math.ceil(right-left-20));
de.setAttribute("height", 20);
de = this.handles.bottom;
de.setAttribute("x", "0%"); //Math.ceil(left+10));
de.setAttribute("y", Math.ceil(bottom - 10));
de.setAttribute("width", "100%"); //Math.ceil(right-left-20));
de.setAttribute("height", 20);
de = this.handles.topleft;
de.setAttribute("x", Math.ceil(left - 10));
de.setAttribute("y", Math.ceil(top - 10));
de = this.handles.topright;
de.setAttribute("x", Math.ceil(right - 10));
de.setAttribute("y", Math.ceil(top - 10));
de = this.handles.bottomleft;
de.setAttribute("x", Math.ceil(left - 10));
de.setAttribute("y", Math.ceil(bottom - 10));
de = this.handles.bottomright;
de.setAttribute("x", Math.ceil(right - 10));
de.setAttribute("y", Math.ceil(bottom - 10));
//direction
if (this.on_direction_changed) {
de = this.lines.direction;
de.setAttribute("x1", Math.ceil((left + right) / 2));
de.setAttribute("y1", Math.ceil((top + bottom) / 2));
de.setAttribute("x2", Math.ceil((left + right) / 2));
de.setAttribute("y2", Math.ceil(0));
de = this.handles.direction;
de.setAttribute("x", Math.ceil((left + right) / 2 - 10));
de.setAttribute("y", 0); //Math.ceil(top+10));
de.setAttribute("height", Math.ceil((bottom - top) / 2 - 10 + top));
} else {
de = this.lines.direction;
de.style.display = "none";
de = this.handles.direction;
de.style.display = "none";
}
// move handle
de = this.ui.querySelector("#move-handle");
de.setAttribute("x", Math.ceil((left + right) / 2 - 10));
de.setAttribute("y", Math.ceil((top + bottom) / 2 - 10));
}
showButtonsTimer = null;
hide_buttons(delay) {
this.ui.querySelector("#v-buttons").style.display = "none";
if (delay) {
if (this.showButtonsTimer) {
clearTimeout(this.showButtonsTimer);
}
this.showButtonsTimer = setTimeout(() => {
this.ui.querySelector("#v-buttons").style.display = "inherit";
}, 200);
}
}
hide() {
this.hide_lines(this.lines);
}
//install_move_handler();
install_edge_hanler(name, handle, lines, direction) {
handle.onmouseenter = () => {
if (this.isActive()) {
this.show_lines();
if (name)
name.split(",").forEach((n) => this.hightlight_line(lines[n]));
}
};
handle.onmouseleave = () => this.hide();
// handle.onmouseup = event=>{
// if (event.which!=1)
// return;
// //line.style["stroke-dasharray"]="none";
// //hide();
// handle.onmouseleave = hide;
// };
handle.ondblclick = (event) => {
if (event.which != 1) return;
event.stopPropagation();
event.preventDefault();
this.on_auto_shrink(direction); //if double click on 'move' handler, the directoin is null
};
handle.onmousedown = (event) => {
if (event.which != 1) return;
var svg = this.svg;
//
event.stopPropagation();
event.preventDefault();
this.disable_handle_except(handle);
this.hide_buttons();
handle.onmouseleave = null;
this.mouse_start_pos = { x: event.layerX, y: event.layerY };
let mouse_cur_pos = {
x: this.mouse_start_pos.x,
y: this.mouse_start_pos.y,
};
console.log(this.mouse_start_pos);
svg.onmouseup = (event) => {
svg.onmousemove = null;
svg.onmouseup = null;
this.enable_handles();
// restore color
//hide();
handle.onmouseleave = this.hide.bind(this);
this.ui.querySelector("#v-buttons").style.display = "inherit";
var handle_delta = {
x: mouse_cur_pos.x - this.mouse_start_pos.x,
y: -(mouse_cur_pos.y - this.mouse_start_pos.y), //reverse since it'll be used by 3d-coord system
};
console.log("delta", handle_delta);
if (
handle_delta.x == 0 &&
handle_delta.y == 0 &&
!event.ctrlKey &&
!event.shiftKey
) {
return;
}
var ratio_delta = {
x: handle_delta.x / this.view_handle_dimension.x,
y: handle_delta.y / this.view_handle_dimension.y,
};
if (direction) {
this.on_edge_changed(
ratio_delta,
direction,
event.ctrlKey,
event.shiftKey
);
// if (event.ctrlKey){
// this.on_auto_shrink(direction);
// }
} else {
// when intall handler for mover, the direcion is left null
this.on_moved(ratio_delta);
}
};
svg.onmousemove = (event) => {
if (event.which != 1) return;
mouse_cur_pos = { x: event.layerX, y: event.layerY };
var handle_delta = {
x: mouse_cur_pos.x - this.mouse_start_pos.x,
y: mouse_cur_pos.y - this.mouse_start_pos.y, // don't reverse direction
};
this.move_lines(handle_delta, direction);
};
};
}
install_direction_handler(linename) {
var handle = this.ui.querySelector("#" + linename + "-handle");
var line = this.ui.querySelector("#" + linename);
var svg = this.svg;
handle.onmouseenter = (event) => {
if (this.isActive()) {
this.show_lines();
this.hightlight_line(line);
}
};
handle.onmouseleave = () => this.hide();
handle.ondblclick = (event) => {
event.stopPropagation();
event.preventDefault();
//transform_bbox(this_axis+"_rotate_reverse");
this.on_direction_changed(Math.PI);
};
// function hide(event){
// line.style.stroke="#00000000";
// };
// handle.onmouseup = event=>{
// if (event.which!=1)
// return;
// //line.style["stroke-dasharray"]="none";
// //line.style.stroke="#00000000";
// handle.onmouseleave = hide;
// };
handle.onmousedown = (event) => {
if (event.which != 1) return;
event.stopPropagation();
event.preventDefault();
//line.style.stroke="yellow";
handle.onmouseleave = null;
//show_lines(lines);
this.disable_handle_except(handle);
this.hide_buttons();
let handle_center = {
x: parseInt(line.getAttribute("x1")),
};
this.mouse_start_pos = {
x: event.layerX,
y: event.layerY,
handle_offset_x: handle_center.x - event.layerX,
};
let mouse_cur_pos = {
x: this.mouse_start_pos.x,
y: this.mouse_start_pos.y,
};
console.log(this.mouse_start_pos);
let theta = 0;
svg.onmousemove = (event) => {
mouse_cur_pos = { x: event.layerX, y: event.layerY };
let handle_center_cur_pos = {
x: mouse_cur_pos.x + this.mouse_start_pos.handle_offset_x,
y: mouse_cur_pos.y,
};
theta = Math.atan2(
handle_center_cur_pos.y - this.view_center.y,
handle_center_cur_pos.x - this.view_center.x
);
console.log(theta);
this.rotate_lines(theta);
};
svg.onmouseup = (event) => {
svg.onmousemove = null;
svg.onmouseup = null;
// restore color
//line.style.stroke="#00000000";
this.enable_handles();
handle.onmouseleave = this.hide.bind(this);
this.ui.querySelector("#v-buttons").style.display = "inherit";
if (theta == 0) {
return;
}
this.on_direction_changed(-theta - Math.PI / 2, event.ctrlKey);
};
};
}
on_key_down(event) {
switch (event.key) {
case "e":
event.preventDefault();
event.stopPropagation();
this.on_direction_changed(-this.cfg.rotateStep, event.ctrlKey);
this.hide_buttons(true);
return true;
case "q":
event.preventDefault();
event.stopPropagation();
this.on_direction_changed(this.cfg.rotateStep, event.ctrlKey);
this.hide_buttons(true);
break;
case "f":
event.preventDefault();
event.stopPropagation();
this.on_direction_changed(-this.cfg.rotateStep, true);
this.hide_buttons(true);
break;
case "r":
event.preventDefault();
event.stopPropagation();
this.on_direction_changed(this.cfg.rotateStep, true);
this.hide_buttons(true);
break;
case "g":
event.preventDefault();
event.stopPropagation();
this.on_direction_changed(Math.PI, false);
break;
case "w":
case "ArrowUp":
event.preventDefault();
event.stopPropagation();
this.on_moved({ x: 0, y: this.cfg.moveStep });
this.hide_buttons(true);
break;
case "s":
if (!event.ctrlKey) {
event.preventDefault();
event.stopPropagation();
this.on_moved({ x: 0, y: -this.cfg.moveStep });
this.hide_buttons(true);
break;
} else {
console.log("ctrl+s");
}
break;
case "ArrowDown":
event.preventDefault();
event.stopPropagation();
this.on_moved({ x: 0, y: -this.cfg.moveStep });
this.hide_buttons(true);
break;
case "a":
if (event.ctrlKey) {
break;
}
// no break;
case "ArrowLeft":
event.preventDefault();
event.stopPropagation();
this.on_moved({ x: -this.cfg.moveStep, y: 0 });
this.hide_buttons(true);
break;
case "d":
if (event.ctrlKey) {
console.log("ctrl+d");
this.on_box_remove();
break;
}
case "ArrowRight":
event.preventDefault();
event.stopPropagation();
this.on_moved({ x: this.cfg.moveStep, y: 0 });
this.hide_buttons(true);
break;
case "Delete":
this.on_box_remove();
break;
}
}
install_buttons() {
let buttons = this.buttons;
let ignore_left_mouse_down = (event) => {
if (event.which == 1) {
event.stopPropagation();
}
};
if (buttons.fit_rotation) {
buttons.fit_rotation.onmousedown = ignore_left_mouse_down;
buttons.fit_rotation.onclick = (event) => {
this.on_auto_rotate("noscaling");
};
}
if (buttons.fit_position && this.on_fit_size) {
buttons.fit_position.onmousedown = ignore_left_mouse_down;
buttons.fit_position.onclick = (event) => {
this.on_fit_size("noscaling");
};
}
if (buttons.fit_size && this.on_fit_size) {
buttons.fit_size.onmousedown = ignore_left_mouse_down;
buttons.fit_size.onclick = (event) => {
this.on_fit_size();
};
}
buttons.fit_all.onmousedown = ignore_left_mouse_down;
buttons.fit_all.onclick = (event) => {
//console.log("auto rotate button clicked.");
this.on_auto_rotate();
//event.currentTarget.blur(); // this bluring will disable focus on sideview also, which is not expected.
};
if (buttons.reset_rotation) {
buttons.reset_rotation.onmousedown = ignore_left_mouse_down;
buttons.reset_rotation.onclick = (event) => {
//console.log("auto rotate button clicked.");
this.on_reset_rotate();
//event.currentTarget.blur(); // this bluring will disable focus on sideview also, which is not expected.
};
}
if (buttons.fit_moving_direction) {
buttons.fit_moving_direction.onmousedown = ignore_left_mouse_down;
buttons.fit_moving_direction.onclick = (event) => {
//console.log("auto rotate button clicked.");
this.on_auto_rotate("noscaling", "moving-direction");
//event.currentTarget.blur(); // this bluring will disable focus on sideview also, which is not expected.
};
}
}
}
class ProjectiveViewOps {
constructor(
ui,
editorCfg,
boxEditor,
views,
boxOp,
func_on_box_changed,
func_on_box_remove
) {
this.ui = ui;
this.cfg = editorCfg;
this.on_box_changed = func_on_box_changed;
this.views = views;
this.boxOp = boxOp;
this.boxEditor = boxEditor;
//internals
var scope = this;
function default_on_del() {
if (scope.box) {
func_on_box_remove(scope.box);
}
}
function default_on_focus() {
// this is a long chain!
if (scope.box && scope.box.boxEditor.boxEditorManager)
scope.box.boxEditor.boxEditorManager.globalHeader.update_box_info(
scope.box
);
}
// direction: 1, -1
// axis: x,y,z
function auto_shrink(extreme, direction) {
for (var axis in direction) {
if (direction[axis] != 0) {
var end = "max";
if (direction[axis] === -1) {
end = "min";
}
var delta =
direction[axis] * extreme[end][axis] - scope.box.scale[axis] / 2;
console.log(extreme, delta);
scope.boxOp.translate_box(
scope.box,
axis,
(direction[axis] * delta) / 2
);
scope.box.scale[axis] += delta;
}
}
}
//direction is in 3d
function auto_stick(delta, direction, use_box_bottom_as_limit) {
//let old_dim = scope.box.world.lidar.get_points_dimmension_of_box(scope.box, true);
//let old_scale = scope.box.scale;
let virtbox = {
position: {
x: scope.box.position.x,
y: scope.box.position.y,
z: scope.box.position.z,
},
scale: {
x: scope.box.scale.x,
y: scope.box.scale.y,
z: scope.box.scale.z,
},
rotation: {
x: scope.box.rotation.x,
y: scope.box.rotation.y,
z: scope.box.rotation.z,
},
};
scope.boxOp.translate_box(virtbox, "x", (delta.x / 2) * direction.x);
scope.boxOp.translate_box(virtbox, "y", (delta.y / 2) * direction.y);
scope.boxOp.translate_box(virtbox, "z", (delta.z / 2) * direction.z);
virtbox.scale.x += delta.x;
virtbox.scale.y += delta.y;
virtbox.scale.z += delta.z;
// note dim is the relative value
let new_dim = scope.box.world.lidar.get_points_dimmension_of_box(
virtbox,
use_box_bottom_as_limit
);
for (var axis in direction) {
if (direction[axis] != 0) {
var end = "max";
if (direction[axis] === -1) {
end = "min";
}
//scope.box.scale[axis]/2 - direction[axis]*extreme[end][axis];
var truedelta =
delta[axis] / 2 +
direction[axis] * new_dim[end][axis] -
scope.box.scale[axis] / 2;
console.log(new_dim, delta);
scope.boxOp.translate_box(
scope.box,
axis,
direction[axis] * truedelta
);
//scope.box.scale[axis] -= delta;
}
}
scope.on_box_changed(scope.box);
}
function on_edge_changed(delta, direction) {
console.log(delta);
scope.boxOp.translate_box(scope.box, "x", (delta.x / 2) * direction.x);
scope.boxOp.translate_box(scope.box, "y", (delta.y / 2) * direction.y);
scope.boxOp.translate_box(scope.box, "z", (delta.z / 2) * direction.z);
scope.box.scale.x += delta.x;
scope.box.scale.y += delta.y;
scope.box.scale.z += delta.z;
scope.on_box_changed(scope.box);
}
function get_wheel_multiplier(wheel_direction) {
var multiplier = 1.0;
if (wheel_direction > 0) {
multiplier = 1.1;
} else {
multiplier = 0.9;
}
return multiplier;
}
///////////////////////////////////////////////////////////////////////////////////
// direction is null if triggered by dbclick on 'move' handler
function on_z_auto_shrink(direction) {
var extreme = scope.box.world.lidar.get_points_dimmension_of_box(
scope.box,
true
);
if (!direction) {
["x", "y"].forEach(function (axis) {
scope.boxOp.translate_box(
scope.box,
axis,
(extreme.max[axis] + extreme.min[axis]) / 2
);
scope.box.scale[axis] = extreme.max[axis] - extreme.min[axis];
});
} else {
direction = {
x: direction.y,
y: -direction.x,
z: 0,
};
auto_shrink(extreme, direction);
}
scope.on_box_changed(scope.box);
}
function on_z_edge_changed(ratio, direction2d, autoShrink, lockScale) {
var delta = {
x: scope.box.scale.x * ratio.y * direction2d.y,
y: scope.box.scale.y * ratio.x * direction2d.x,
z: 0,
};
let direction3d = {
x: direction2d.y,
y: -direction2d.x,
z: 0,
};
if (!autoShrink && !lockScale) {
on_edge_changed(delta, direction3d);
} else if (autoShrink) {
on_edge_changed(delta, direction3d);
on_z_auto_shrink(direction2d);
} else if (lockScale) {
auto_stick(delta, direction3d, true);
}
}
function on_z_direction_changed(theta, sticky) {
// points indices shall be obtained before rotation.
let box = scope.box;
scope.boxOp.rotate_z(box, theta, sticky);
scope.on_box_changed(box);
}
//ratio.y vertical
//ratio.x horizental
// box.x vertical
// box.y horizental
function limit_move_step(v, min_abs_v) {
if (v < 0) return Math.min(v, -min_abs_v);
else if (v > 0) return Math.max(v, min_abs_v);
else return v;
}
function on_z_moved(ratio) {
let delta = {
x: scope.box.scale.x * ratio.y,
y: -scope.box.scale.y * ratio.x,
};
delta.x = limit_move_step(delta.x, 0.02);
delta.y = limit_move_step(delta.y, 0.02);
// scope.boxOp.translate_box(scope.box, "x", delta.x);
// scope.boxOp.translate_box(scope.box, "y", delta.y);
// scope.on_box_changed(scope.box);
scope.boxEditor.onOpCmd({
op: "translate",
params: {
delta,
},
});
}
function on_z_scaled(ratio) {
ratio = {
x: ratio.y,
y: ratio.x,
z: 0,
};
for (var axis in ratio) {
if (ratio[axis] != 0) {
scope.box.scale[axis] *= 1 + ratio[axis];
}
}
scope.on_box_changed(scope.box);
}
function on_z_wheel(wheel_direction) {
let multiplier = get_wheel_multiplier(wheel_direction);
let newRatio = (scope.views[0].zoom_ratio *= multiplier);
scope.boxEditor.updateViewZoomRatio(0, newRatio);
//z_view_handle.update_view_handle(scope.views[0].getViewPort(), {x: scope.box.scale.y, y:scope.box.scale.x});
}
function on_z_fit_size(noscaling) {
if (noscaling) {
// fit position only
scope.boxOp.auto_rotate_xyz(
scope.box,
null,
{ x: true, y: true, z: false },
scope.on_box_changed,
noscaling,
"dontrotate"
);
} else {
scope.boxOp.fit_size(scope.box, ["x", "y"]);
scope.on_box_changed(scope.box);
}
}
function on_z_auto_rotate(noscaling, rotate_method) {
if (rotate_method == "moving-direction") {
let estimatedRot = scope.boxOp.estimate_rotation_by_moving_direciton(
scope.box
);
if (estimatedRot) {
scope.box.rotation.z = estimatedRot.z;
scope.on_box_changed(scope.box);
}
} else {
scope.boxOp.auto_rotate_xyz(
scope.box,
null,
noscaling ? null : { x: false, y: false, z: true },
scope.on_box_changed,
noscaling
);
}
}
function on_z_reset_rotate() {
scope.box.rotation.z = 0;
scope.on_box_changed(scope.box);
}
this.z_view_handle = new ProjectiveView(
scope.ui.querySelector("#z-view-manipulator"),
editorCfg,
on_z_edge_changed,
on_z_direction_changed,
on_z_auto_shrink,
on_z_moved,
on_z_scaled,
on_z_wheel,
on_z_fit_size,
on_z_auto_rotate,
on_z_reset_rotate,
default_on_focus,
default_on_del,
this.isActive.bind(this)
);
///////////////////////////////////////////////////////////////////////////////////
function on_y_edge_changed(ratio, direction2d, autoShrink, lockScale) {
var delta = {
x: scope.box.scale.x * ratio.x * direction2d.x,
z: scope.box.scale.z * ratio.y * direction2d.y,
y: 0,
};
let direction3d = {
x: direction2d.x,
z: direction2d.y,
y: 0,
};
if (!autoShrink && !lockScale) {
on_edge_changed(delta, direction3d);
} else if (autoShrink) {
on_edge_changed(delta, direction3d);
on_y_auto_shrink(direction2d);
} else if (lockScale) {
auto_stick(delta, direction3d, direction2d.y === 0);
}
}
function on_y_auto_shrink(direction) {
if (!direction) {
var extreme = scope.box.world.lidar.get_points_dimmension_of_box(
scope.box,
false
);
["x", "z"].forEach(function (axis) {
scope.boxOp.translate_box(
scope.box,
axis,
(extreme.max[axis] + extreme.min[axis]) / 2
);
scope.box.scale[axis] = extreme.max[axis] - extreme.min[axis];
});
} else {
direction = {
x: direction.x,
y: 0,
z: direction.y,
};
if (direction.z != 0) {
var extreme = scope.box.world.lidar.get_points_dimmension_of_box(
scope.box,
false
);
auto_shrink(extreme, direction);
} else {
var extreme = scope.box.world.lidar.get_points_dimmension_of_box(
scope.box,
true
);
auto_shrink(extreme, direction);
}
}
scope.on_box_changed(scope.box);
}
function on_y_moved(ratio) {
var delta = {
x: limit_move_step(scope.box.scale.x * ratio.x, 0.02),
z: limit_move_step(scope.box.scale.z * ratio.y, 0.02),
};
// scope.boxOp.translate_box(scope.box, "x", delta.x);
// scope.boxOp.translate_box(scope.box, "z", delta.z);
// scope.on_box_changed(scope.box);
scope.boxEditor.onOpCmd({
op: "translate",
params: {
delta,
},
});
}
function on_y_direction_changed(theta, sticky) {
scope.boxOp.change_rotation_y(
scope.box,
theta,
sticky,
scope.on_box_changed
);
}
function on_y_scaled(ratio) {
ratio = {
x: ratio.x,
y: 0,
z: ratio.y,
};
for (var axis in ratio) {
if (ratio[axis] != 0) {
scope.box.scale[axis] *= 1 + ratio[axis];
}
}
scope.on_box_changed(scope.box);
}
function on_y_wheel(wheel_direction) {
let multiplier = get_wheel_multiplier(wheel_direction);
let newRatio = (scope.views[1].zoom_ratio *= multiplier);
scope.boxEditor.updateViewZoomRatio(1, newRatio);
}
function on_y_reset_rotate() {
scope.box.rotation.y = 0;
scope.on_box_changed(scope.box);
}
function on_y_auto_rotate() {
scope.boxOp.auto_rotate_y(scope.box, scope.on_box_changed);
}
this.y_view_handle = new ProjectiveView(
scope.ui.querySelector("#y-view-manipulator"),
editorCfg,
on_y_edge_changed,
on_y_direction_changed,
on_y_auto_shrink,
on_y_moved,
on_y_scaled,
on_y_wheel,
null,
on_y_auto_rotate,
on_y_reset_rotate,
default_on_focus,
default_on_del,
this.isActive.bind(this)
);
///////////////////////////////////////////////////////////////////////////////////
function on_x_edge_changed(ratio, direction2d, autoShrink, lockScale) {
var delta = {
y: scope.box.scale.y * ratio.x * direction2d.x,
z: scope.box.scale.z * ratio.y * direction2d.y,
x: 0,
};
let direction3d = {
y: -direction2d.x,
z: direction2d.y,
x: 0,
};
if (!autoShrink && !lockScale) {
on_edge_changed(delta, direction3d);
} else if (autoShrink) {
on_edge_changed(delta, direction3d);
on_x_auto_shrink(direction2d);
} else if (lockScale) {
auto_stick(delta, direction3d, direction2d.y === 0);
}
}
function on_x_auto_shrink(direction) {
if (!direction) {
var extreme = scope.box.world.lidar.get_points_dimmension_of_box(
scope.box,
false
);
["y", "z"].forEach(function (axis) {
scope.boxOp.translate_box(
scope.box,
axis,
(extreme.max[axis] + extreme.min[axis]) / 2
);
scope.box.scale[axis] = extreme.max[axis] - extreme.min[axis];
});
} else {
direction = {
x: 0,
y: -direction.x,
z: direction.y,
};
if (direction.z != 0) {
var extreme = scope.box.world.lidar.get_points_dimmension_of_box(
scope.box,
false
);
auto_shrink(extreme, direction);
} else {
var extreme = scope.box.world.lidar.get_points_dimmension_of_box(
scope.box,
true
);
auto_shrink(extreme, direction);
}
}
scope.on_box_changed(scope.box);
}
function on_x_moved(ratio) {
var delta = {
y: limit_move_step(scope.box.scale.y * -ratio.x, 0.02),
z: limit_move_step(scope.box.scale.z * ratio.y, 0.02),
};
// scope.boxOp.translate_box(scope.box, "y", delta.y);
// scope.boxOp.translate_box(scope.box, "z", delta.z);
// scope.on_box_changed(scope.box);
scope.boxEditor.onOpCmd({
op: "translate",
params: {
delta,
},
});
}
function on_x_direction_changed(theta, sticky) {
scope.boxOp.change_rotation_x(
scope.box,
-theta,
sticky,
scope.on_box_changed
);
}
function on_x_scaled(ratio) {
ratio = {
y: ratio.x,
z: ratio.y,
};
for (var axis in ratio) {
if (ratio[axis] != 0) {
scope.box.scale[axis] *= 1 + ratio[axis];
}
}
scope.on_box_changed(scope.box);
}
function on_x_wheel(wheel_direction) {
let multiplier = get_wheel_multiplier(wheel_direction);
let newRatio = (scope.views[2].zoom_ratio *= multiplier);
scope.boxEditor.updateViewZoomRatio(2, newRatio);
}
function on_x_reset_rotate() {
scope.box.rotation.x = 0;
scope.on_box_changed(scope.box);
}
function on_x_auto_rotate() {
scope.boxOp.auto_rotate_x(scope.box, scope.on_box_changed);
}
this.x_view_handle = new ProjectiveView(
scope.ui.querySelector("#x-view-manipulator"),
editorCfg,
on_x_edge_changed,
on_x_direction_changed,
on_x_auto_shrink,
on_x_moved,
on_x_scaled,
on_x_wheel,
null,
on_x_auto_rotate,
on_x_reset_rotate,
default_on_focus,
default_on_del,
this.isActive.bind(this)
);
} // end of constructor
// exports
hideAllHandlers() {
this.ui
.querySelectorAll(".subview-svg")
.forEach((ui) => (ui.style.display = "none"));
//this.ui.querySelectorAll(".v-buttons-wrapper").forEach(ui=>ui.style.display="none");
}
showAllHandlers() {
this.ui
.querySelectorAll(".subview-svg")
.forEach((ui) => (ui.style.display = ""));
//this.ui.querySelectorAll(".v-buttons-wrapper").forEach(ui=>ui.style.display="");
}
isActive() {
return !!this.box;
}
////////////////////////////////////////////////////////////////////////////////////////
// public interface
box = undefined;
attachBox(box) {
this.box = box;
//this.show();
this.showAllHandlers();
this.update_view_handle(box);
}
detach(box) {
this.box = null;
this.hideAllHandlers();
}
update_view_handle() {
if (this.box) {
let boxPos = this.box.position;
this.z_view_handle.update_view_handle(
this.views[0].getViewPort(),
{ x: this.box.scale.y, y: this.box.scale.x },
{ x: boxPos.x, y: boxPos.y },
this.box.rotation.z
);
this.y_view_handle.update_view_handle(this.views[1].getViewPort(), {
x: this.box.scale.x,
y: this.box.scale.z,
});
this.x_view_handle.update_view_handle(this.views[2].getViewPort(), {
x: this.box.scale.y,
y: this.box.scale.z,
});
}
}
}
export { ProjectiveViewOps };