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.
1743 lines
46 KiB
JavaScript
1743 lines
46 KiB
JavaScript
import { ProjectiveViewOps } from "./side_view_op.js";
|
|
import { BoxImageContext } from "./image.js";
|
|
import { saveWorldList, reloadWorldList } from "./save.js";
|
|
import { objIdManager } from "./obj_id_list.js";
|
|
import { globalKeyDownManager } from "./keydown_manager.js";
|
|
import { ml } from "./ml.js";
|
|
import { BooleanKeyframeTrack } from "./lib/three.module.js";
|
|
import { checkScene } from "./error_check.js";
|
|
import { logger } from "./log.js";
|
|
|
|
/*
|
|
2 ways to attach and edit a box
|
|
1) attach/detach
|
|
2) setTarget, tryAttach, resetTarget, this is only for batch-editor-manager
|
|
*/
|
|
function BoxEditor(
|
|
parentUi,
|
|
boxEditorManager,
|
|
viewManager,
|
|
cfg,
|
|
boxOp,
|
|
func_on_box_changed,
|
|
func_on_box_remove,
|
|
name
|
|
) {
|
|
this.boxEditorManager = boxEditorManager;
|
|
this.parentUi = parentUi;
|
|
this.name = name;
|
|
let uiTmpl = document.getElementById("box-editor-ui-template");
|
|
let tmpui = uiTmpl.content.cloneNode(true); //sub-views
|
|
|
|
parentUi.appendChild(tmpui);
|
|
this.ui = parentUi.lastElementChild;
|
|
this.boxInfoUi = this.ui.querySelector("#box-info");
|
|
|
|
this.viewManager = viewManager;
|
|
this.boxOp = boxOp;
|
|
this.boxView = this.viewManager.addBoxView(this.ui); //this.editorUi.querySelector("#sub-views")
|
|
this.projectiveViewOps = new ProjectiveViewOps(
|
|
this.ui, //this.editorUi.querySelector("#sub-views"),
|
|
cfg,
|
|
this,
|
|
this.boxView.views,
|
|
this.boxOp,
|
|
func_on_box_changed,
|
|
func_on_box_remove
|
|
);
|
|
|
|
this.focusImageContext = new BoxImageContext(
|
|
this.ui.querySelector("#focuscanvas")
|
|
);
|
|
|
|
this.pseudoBox = {
|
|
position: { x: 0, y: 0, z: 0 },
|
|
rotation: { x: 0, y: 0, z: 0 },
|
|
scale: { x: 1, y: 1, z: 1 },
|
|
};
|
|
|
|
this.copyPseudoBox = function (b) {
|
|
this.pseudoBox.position.x = b.position.x;
|
|
this.pseudoBox.position.y = b.position.y;
|
|
this.pseudoBox.position.z = b.position.z;
|
|
|
|
this.pseudoBox.rotation.x = b.rotation.x;
|
|
this.pseudoBox.rotation.y = b.rotation.y;
|
|
this.pseudoBox.rotation.z = b.rotation.z;
|
|
|
|
this.pseudoBox.scale.x = b.scale.x;
|
|
this.pseudoBox.scale.y = b.scale.y;
|
|
this.pseudoBox.scale.z = b.scale.z;
|
|
};
|
|
|
|
this.isInBatchMode = function () {
|
|
return !!this.boxEditorManager;
|
|
};
|
|
|
|
this.target = {};
|
|
|
|
this.setTarget = function (world, objTrackId, objType) {
|
|
this.target = {
|
|
world: world,
|
|
objTrackId: objTrackId,
|
|
objType: objType,
|
|
};
|
|
|
|
if (this.isInBatchMode()) {
|
|
this.pseudoBox.world = world;
|
|
this.boxView.attachBox(this.pseudoBox);
|
|
}
|
|
|
|
this.tryAttach();
|
|
this.ui.style.display = "inline-block";
|
|
this.updateInfo();
|
|
};
|
|
|
|
this.setIndex = function (index) {
|
|
this.index = index; // index as of in all editors.
|
|
};
|
|
|
|
this.setSelected = function (selected, eventId) {
|
|
if (selected) {
|
|
this.ui.className = "selected";
|
|
this.selected = true;
|
|
this.selectEventId = eventId;
|
|
} else {
|
|
if (!eventId || this.selectEventId == eventId) {
|
|
// cancel only you selected.
|
|
this.ui.className = "";
|
|
this.selected = false;
|
|
this.selectEventId = null;
|
|
}
|
|
}
|
|
};
|
|
|
|
// this.onContextMenu = (event)=>{
|
|
// if (this.boxEditorManager) // there is no manager for box editor in main ui
|
|
// this.boxEditorManager.onContextMenu(event, this);
|
|
// };
|
|
|
|
// this.ui.oncontextmenu = this.onContextMenu;
|
|
|
|
this.resetTarget = function () {
|
|
if (this.target.world) {
|
|
//unload if it's not the main world
|
|
// if (this.target.world !== this.target.world.data.world)
|
|
// this.target.world.unload();
|
|
}
|
|
|
|
this.detach();
|
|
this.target = {};
|
|
//this.ui.style.display="none";
|
|
};
|
|
|
|
this.tryAttach = function () {
|
|
// find target box, attach to me
|
|
if (this.target && this.target.world) {
|
|
let box = this.target.world.annotation.findBoxByTrackId(
|
|
this.target.objTrackId
|
|
);
|
|
if (box) {
|
|
this.attachBox(box);
|
|
}
|
|
}
|
|
};
|
|
|
|
/*
|
|
the projectiveView tiggers zoomratio changing event.
|
|
editormanager broaccasts it to all editors
|
|
*/
|
|
this._setViewZoomRatio = function (viewIndex, ratio) {
|
|
this.boxView.views[viewIndex].zoom_ratio = ratio;
|
|
};
|
|
|
|
this.updateViewZoomRatio = function (viewIndex, ratio) {
|
|
//this.upate();
|
|
if (this.boxEditorManager)
|
|
this.boxEditorManager.updateViewZoomRatio(viewIndex, ratio);
|
|
else {
|
|
this._setViewZoomRatio(viewIndex, ratio);
|
|
this.update();
|
|
//this.viewManager.render();
|
|
}
|
|
};
|
|
|
|
this.onOpCmd = function (cmd) {
|
|
if (this.boxEditorManager) this.boxEditorManager.onOpCmd(cmd, this);
|
|
else {
|
|
this.executeOpCmd(cmd);
|
|
}
|
|
};
|
|
|
|
this.executeOpCmd = function (cmd) {
|
|
if (!this.box) {
|
|
return;
|
|
}
|
|
|
|
if (cmd.op == "translate") {
|
|
for (let axis in cmd.params.delta) {
|
|
this.boxOp.translate_box(this.box, axis, cmd.params.delta[axis]);
|
|
//this.boxOp.translate_box(this.box, "y", delta.y);
|
|
}
|
|
|
|
func_on_box_changed(this.box);
|
|
}
|
|
};
|
|
|
|
this.box = null;
|
|
this.attachBox = function (box) {
|
|
if (this.box && this.box !== box) {
|
|
this.box.boxEditor = null;
|
|
console.log("detach box editor");
|
|
//todo de-highlight box
|
|
}
|
|
|
|
this.box = null;
|
|
this.show();
|
|
|
|
if (box) {
|
|
box.boxEditor = this;
|
|
this.box = box;
|
|
//this.boxOp.highlightBox(box);
|
|
this.boxView.attachBox(box);
|
|
this.projectiveViewOps.attachBox(box);
|
|
this.focusImageContext.updateFocusedImageContext(box);
|
|
|
|
//this.update();
|
|
this.updateInfo();
|
|
// this.boxView.render();
|
|
|
|
if (this.isInBatchMode()) {
|
|
this.boxEditorManager.onBoxChanged(this);
|
|
}
|
|
}
|
|
};
|
|
|
|
this.detach = function (dontHide) {
|
|
if (this.box) {
|
|
if (this.box.boxEditor === this) {
|
|
this.box.boxEditor = null;
|
|
}
|
|
//this.boxOp.unhighlightBox(this.box);
|
|
//todo de-highlight box
|
|
this.projectiveViewOps.detach();
|
|
this.boxView.detach();
|
|
|
|
if (this.isInBatchMode()) {
|
|
this.copyPseudoBox(this.box);
|
|
this.boxView.attachBox(this.pseudoBox);
|
|
}
|
|
|
|
this.focusImageContext.clear_canvas();
|
|
this.box = null;
|
|
}
|
|
|
|
if (!dontHide) this.hide();
|
|
};
|
|
|
|
this.hide = function () {
|
|
this.ui.style.display = "none";
|
|
|
|
// this is a hack, if we don't have manager, this is the main editor
|
|
// hide parent ui
|
|
// todo, add a pseudo manager, hide itself when child hide
|
|
if (!this.boxEditorManager) {
|
|
this.parentUi.style.display = "none";
|
|
}
|
|
};
|
|
this.show = function () {
|
|
this.ui.style.display = ""; //"inline-block";
|
|
if (!this.boxEditorManager) {
|
|
this.parentUi.style.display = "";
|
|
}
|
|
};
|
|
|
|
this.onBoxChanged = function () {
|
|
this.projectiveViewOps.update_view_handle();
|
|
this.focusImageContext.updateFocusedImageContext(this.box);
|
|
this.boxView.onBoxChanged();
|
|
|
|
// mark
|
|
delete this.box.annotator; // human annotator doesn't need a name
|
|
delete this.box.follows;
|
|
this.box.changed = true;
|
|
|
|
// don't mark world's change flag, for it's hard to clear it.
|
|
|
|
// inform boxEditorMgr to transfer annotation to other frames.
|
|
if (this.boxEditorManager) this.boxEditorManager.onBoxChanged(this);
|
|
|
|
this.updateInfo();
|
|
|
|
//this.boxView.render();
|
|
};
|
|
|
|
this.onDelBox = function () {
|
|
let box = this.box;
|
|
this.detach("donthide");
|
|
};
|
|
|
|
// windowresize...
|
|
this.update = function (dontRender = false) {
|
|
if (this.boxView) {
|
|
this.boxView.onBoxChanged(dontRender);
|
|
|
|
// this.boxView.updateCameraRange(this.box);
|
|
// this.boxView.updateCameraPose(this.box);
|
|
|
|
// if (!dontRender)
|
|
// this.boxView.render();
|
|
}
|
|
|
|
// boxview should be updated for pseudobox.
|
|
|
|
if (this.box === null) return;
|
|
|
|
this.projectiveViewOps.update_view_handle();
|
|
|
|
// this is not needed somtime
|
|
this.focusImageContext.updateFocusedImageContext(this.box);
|
|
|
|
// should we update info?
|
|
this.updateInfo();
|
|
};
|
|
|
|
this.updateInfo = function () {
|
|
let info = "";
|
|
if (this.target.world) {
|
|
info += String(this.target.world.frameInfo.frame);
|
|
|
|
if (this.box && this.box.annotator) info += "," + this.box.annotator;
|
|
|
|
// if (this.box && this.box.changed)
|
|
// info += " *";
|
|
}
|
|
|
|
this.boxInfoUi.innerHTML = info;
|
|
};
|
|
|
|
this.updateBoxDimension = function () {};
|
|
|
|
this.resize = function (width, height) {
|
|
// if (height + "px" == this.ui.style.height && width + "px" == this.ui.style.width)
|
|
// {
|
|
// return;
|
|
// }
|
|
|
|
this.ui.style.width = width + "px";
|
|
this.ui.style.height = height + "px";
|
|
this.boxView.render();
|
|
};
|
|
|
|
this.setResize = function (option) {
|
|
this.ui.style.resize = option;
|
|
this.ui.style["z-index"] = "0";
|
|
|
|
if (option == "both") {
|
|
this.lastSize = {
|
|
width: 0,
|
|
height: 0,
|
|
};
|
|
|
|
this.resizeObserver = new ResizeObserver((elements) => {
|
|
let rect = elements[0].contentRect;
|
|
console.log("sub-views resized.", rect);
|
|
|
|
if (rect.height == 0 || rect.width == 0) {
|
|
return;
|
|
}
|
|
|
|
if (
|
|
rect.height != this.lastSize.height ||
|
|
rect.width != this.lastSize.width
|
|
) {
|
|
// viewManager will clear backgound
|
|
// so this render is effectiveless.
|
|
//this.boxView.render();
|
|
|
|
// save
|
|
|
|
if (this.boxEditorManager) {
|
|
// there is no manager for box editor in main ui
|
|
pointsGlobalConfig.setItem("batchModeSubviewSize", {
|
|
width: rect.width,
|
|
height: rect.height,
|
|
});
|
|
this.boxEditorManager.onSubViewsResize(rect.width, rect.height);
|
|
} else {
|
|
this.boxView.render();
|
|
}
|
|
|
|
//save
|
|
this.lastSize.width = rect.width;
|
|
this.lastSize.height = rect.height;
|
|
}
|
|
});
|
|
|
|
this.resizeObserver.observe(this.ui);
|
|
}
|
|
};
|
|
}
|
|
|
|
//parentUi #batch-box-editor
|
|
function BoxEditorManager(
|
|
parentUi,
|
|
viewManager,
|
|
objectTrackView,
|
|
cfg,
|
|
boxOp,
|
|
globalHeader,
|
|
contextMenu,
|
|
configMenu,
|
|
func_on_box_changed,
|
|
func_on_box_remove,
|
|
func_on_annotation_reloaded
|
|
) {
|
|
this.viewManager = viewManager;
|
|
this.objectTrackView = objectTrackView;
|
|
this.boxOp = boxOp;
|
|
this.activeIndex = 0;
|
|
this.editorList = [];
|
|
this.cfg = cfg;
|
|
this.globalHeader = globalHeader;
|
|
this.contextMenu = contextMenu;
|
|
this.parentUi = parentUi; //#batch-box-editor
|
|
this.boxEditorGroupUi = parentUi.querySelector("#batch-box-editor-group");
|
|
this.boxEditorHeaderUi = parentUi.querySelector("#batch-box-editor-header");
|
|
this.batchSize = cfg.batchModeInstNumber;
|
|
//this.configMenu = configMenu;
|
|
|
|
this.activeEditorList = function () {
|
|
return this.editorList.slice(0, this.activeIndex);
|
|
};
|
|
|
|
this.editingTarget = {
|
|
data: null,
|
|
sceneMeta: "",
|
|
objTrackId: "",
|
|
frame: "",
|
|
frameIndex: NaN,
|
|
};
|
|
|
|
this.onExit = null;
|
|
// frame specifies the center frame to edit
|
|
|
|
// this.parentUi.addEventListener("contextmenu", event=>{
|
|
// this.contextMenu.show("boxEditorManager", event.clientX, event.clientY, this);
|
|
// event.stopPropagation();
|
|
// event.preventDefault();
|
|
// })
|
|
|
|
this.onSubViewsResize = function (width, height) {
|
|
this.viewManager.mainView.clearView();
|
|
this.editorList.forEach((e) => {
|
|
e.resize(width, height);
|
|
});
|
|
|
|
//this.viewManager.render();
|
|
};
|
|
|
|
this.calculateBestSubviewSize = function (batchSize) {
|
|
let parentRect = this.parentUi.getBoundingClientRect();
|
|
let headerRect = this.boxEditorHeaderUi.getBoundingClientRect();
|
|
let editorsGroupRect = this.boxEditorGroupUi.getBoundingClientRect();
|
|
|
|
let availableHeight = parentRect.height - headerRect.height;
|
|
let availableWidth = parentRect.width;
|
|
|
|
if (availableHeight == 0 || availableWidth == 0) {
|
|
this.batchSizeUpdated = true;
|
|
return;
|
|
}
|
|
|
|
let defaultBoxWidth = 130;
|
|
let defaultBoxHeight = 450;
|
|
|
|
let rows = 1;
|
|
let w = availableWidth / Math.ceil(batchSize / rows);
|
|
let h = availableHeight / rows;
|
|
let cost = Math.abs(w / h - defaultBoxWidth / defaultBoxHeight);
|
|
let minCost = cost;
|
|
let bestRows = rows;
|
|
while (true) {
|
|
rows += 1;
|
|
|
|
let w = Math.floor(availableWidth / Math.ceil(batchSize / rows));
|
|
let h = Math.floor(availableHeight / rows);
|
|
let cost = Math.abs(w / h - defaultBoxWidth / defaultBoxHeight);
|
|
|
|
if (cost < minCost) {
|
|
minCost = cost;
|
|
bestRows = rows;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
//bestRows
|
|
pointsGlobalConfig.batchModeSubviewSize = {
|
|
width: Math.floor(availableWidth / Math.ceil(batchSize / bestRows)),
|
|
height: Math.floor(availableHeight / bestRows),
|
|
};
|
|
};
|
|
|
|
this.setBatchSize = function (batchSize) {
|
|
this.calculateBestSubviewSize(batchSize);
|
|
|
|
this.batchSize = batchSize;
|
|
if (this.parentUi.style.display != "none") {
|
|
this.edit(
|
|
this.editingTarget.data,
|
|
this.editingTarget.sceneMeta,
|
|
this.editingTarget.frame,
|
|
this.editingTarget.objTrackId,
|
|
this.editingTarget.objType
|
|
);
|
|
}
|
|
};
|
|
|
|
this.onWindowResize = function () {
|
|
this.setBatchSize(this.batchSize);
|
|
};
|
|
|
|
this.edit = function (data, sceneMeta, frame, objTrackId, objType, onExit) {
|
|
this.show();
|
|
this.reset();
|
|
|
|
if (this.batchSizeUpdated) {
|
|
this.batchSizeUpdated = false;
|
|
this.calculateBestSubviewSize(this.batchSize);
|
|
}
|
|
|
|
if (onExit) {
|
|
// next/prev call will not update onExit
|
|
this.onExit = onExit;
|
|
}
|
|
let sceneName = sceneMeta.scene;
|
|
|
|
this.editingTarget.data = data;
|
|
this.editingTarget.sceneMeta = sceneMeta;
|
|
this.editingTarget.objTrackId = objTrackId;
|
|
|
|
this.editingTarget.objType = objType;
|
|
|
|
this.editingTarget.frame = frame;
|
|
|
|
// this.parentUi.querySelector("#object-track-id-editor").value=objTrackId;
|
|
// this.parentUi.querySelector("#object-category-selector").value=objType;
|
|
|
|
let centerIndex = sceneMeta.frames.findIndex((f) => f == frame);
|
|
this.editingTarget.frameIndex = centerIndex;
|
|
|
|
if (centerIndex < 0) {
|
|
centerIndex = 0;
|
|
}
|
|
|
|
let startIndex = Math.max(0, centerIndex - this.batchSize / 2);
|
|
|
|
if (startIndex > 0) {
|
|
if (startIndex + this.batchSize > sceneMeta.frames.length) {
|
|
startIndex -= startIndex + this.batchSize - sceneMeta.frames.length;
|
|
|
|
if (startIndex < 0) {
|
|
startIndex = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
let frames = sceneMeta.frames.slice(
|
|
startIndex,
|
|
startIndex + this.batchSize
|
|
);
|
|
|
|
//this.viewManager.mainView.clearView();
|
|
|
|
frames.forEach(async (frame, editorIndex) => {
|
|
let world = await data.getWorld(sceneName, frame);
|
|
let editor = this.addEditor();
|
|
//editor.setTarget(world, objTrackId, objType);
|
|
editor.setIndex(editorIndex);
|
|
editor.resize(
|
|
pointsGlobalConfig.batchModeSubviewSize.width,
|
|
pointsGlobalConfig.batchModeSubviewSize.height
|
|
);
|
|
|
|
if (this.editingTarget.frame == frame) {
|
|
editor.setSelected(true);
|
|
}
|
|
|
|
data.activate_world(
|
|
world,
|
|
() => {
|
|
//editor.tryAttach();
|
|
|
|
editor.setTarget(world, objTrackId, objType);
|
|
|
|
//
|
|
//this.viewManager.render();
|
|
},
|
|
true
|
|
);
|
|
});
|
|
|
|
// set obj selector
|
|
this.globalHeader.setObject(objTrackId);
|
|
};
|
|
|
|
this.onContextMenu = function (event, boxEditor) {
|
|
this.firingBoxEditor = boxEditor;
|
|
|
|
if (boxEditor.selected) {
|
|
// ok
|
|
} else {
|
|
this.getSelectedEditors().forEach((e) => e.setSelected(false));
|
|
boxEditor.setSelected(true);
|
|
}
|
|
|
|
this.contextMenu.show("boxEditor", event.clientX, event.clientY, this);
|
|
event.stopPropagation();
|
|
event.preventDefault();
|
|
};
|
|
|
|
this.parentUi.oncontextmenu = (event) => {
|
|
let ed = this.getEditorByMousePosition(event.clientX, event.clientY);
|
|
|
|
this.onContextMenu(event, ed);
|
|
};
|
|
|
|
this.handleContextMenuKeydownEvent = function (event, menuPos) {
|
|
switch (event.key) {
|
|
case "s":
|
|
this.activeEditorList().forEach((e) => e.setSelected(true));
|
|
return true;
|
|
break;
|
|
case "a":
|
|
this.autoAnnotateSelectedFrames();
|
|
break;
|
|
case "f":
|
|
this.finalizeSelectedBoxes();
|
|
break;
|
|
case "d":
|
|
this.deleteSelectedBoxes(menuPos);
|
|
break;
|
|
case "e":
|
|
this.interpolateSelectedFrames();
|
|
break;
|
|
case "g":
|
|
this.gotoThisFrame();
|
|
break;
|
|
case "t":
|
|
this.showTrajectory();
|
|
break;
|
|
default:
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
this.delayUpdateAutoGeneratedBoxesTimer = null;
|
|
|
|
this.updateAutoGeneratedBoxes = function () {
|
|
if (this.delayUpdateAutoGeneratedBoxesTimer) {
|
|
clearTimeout(this.delayUpdateAutoGeneratedBoxesTimer);
|
|
}
|
|
|
|
this.delayUpdateAutoGeneratedBoxesTimer = setTimeout(async () => {
|
|
if (this.cfg.autoUpdateInterpolatedBoxes) {
|
|
await this.updateInterpolatedBoxes();
|
|
}
|
|
|
|
await this.updatePseudoBoxes();
|
|
}, 500);
|
|
};
|
|
|
|
this.updateInterpolatedBoxes = async function () {
|
|
let editorList = this.activeEditorList();
|
|
let applyIndList = editorList.map((e) => e.box && e.box.annotator == "i");
|
|
|
|
let boxList = editorList.map((e) => e.box);
|
|
let worldList = editorList.map((e) => e.target.world);
|
|
await this.boxOp.interpolateAsync(worldList, boxList, applyIndList);
|
|
//this.activeEditorList().forEach(e=>e.tryAttach());
|
|
|
|
this.globalHeader.updateModifiedStatus();
|
|
//this.viewManager.render();
|
|
editorList.forEach((e) => {
|
|
if (e.box && e.box.annotator == "i") {
|
|
e.boxView.onBoxChanged();
|
|
}
|
|
});
|
|
};
|
|
|
|
this.updatePseudoBoxes = async function () {
|
|
let editorList = this.activeEditorList();
|
|
let boxList = editorList.map((e) => e.box);
|
|
let anns = boxList.map((b) =>
|
|
b ? b.world.annotation.ann_to_vector_global(b) : null
|
|
);
|
|
|
|
let ret = await ml.interpolate_annotation(anns);
|
|
|
|
editorList.forEach((e, i) => {
|
|
if (!e.box) {
|
|
let ann = e.target.world.annotation.vector_global_to_ann(ret[i]);
|
|
e.copyPseudoBox(ann);
|
|
e.boxView.onBoxChanged();
|
|
}
|
|
});
|
|
};
|
|
|
|
// manager
|
|
this.onBoxChanged = function (e) {
|
|
this.updateAutoGeneratedBoxes();
|
|
//
|
|
};
|
|
|
|
let onBoxChangedInBatchMode = function (box) {
|
|
if (box.boxEditor)
|
|
// if in batch mode with less than 40 windows, some box don't have editor attached.
|
|
box.boxEditor.update(); //render.
|
|
|
|
box.world.annotation.setModified();
|
|
};
|
|
|
|
this.finalizeSelectedBoxes = function () {
|
|
this.getSelectedEditors().forEach((e) => {
|
|
if (e.box) {
|
|
if (e.box.annotator) {
|
|
delete e.box.annotator;
|
|
func_on_box_changed(e.box);
|
|
//e.box.world.annotation.setModified();
|
|
e.updateInfo();
|
|
}
|
|
}
|
|
});
|
|
|
|
this.globalHeader.updateModifiedStatus();
|
|
};
|
|
|
|
this.interpolateSelectedFrames = function () {
|
|
let applyIndList = this.activeEditorList().map((e) => false); //all shoud be applied.
|
|
let selectedEditors = this.getSelectedEditors();
|
|
|
|
// if interpolate only one box, remove it if exist.
|
|
// no matter who is the annotator.
|
|
if (selectedEditors.length == 1) {
|
|
if (selectedEditors[0].box) {
|
|
func_on_box_remove(selectedEditors[0].box, true);
|
|
}
|
|
}
|
|
|
|
selectedEditors.forEach((e) => (applyIndList[e.index] = true));
|
|
this.interpolate(applyIndList);
|
|
|
|
this.updateAutoGeneratedBoxes();
|
|
};
|
|
|
|
this.deleteEmptyBoxes = function () {
|
|
let editors = this.activeEditorList();
|
|
editors.forEach((e) => {
|
|
if (e.box) {
|
|
if (e.box.world.lidar.get_box_points_number(e.box) == 0) {
|
|
func_on_box_remove(e.box, true);
|
|
}
|
|
}
|
|
});
|
|
|
|
this.updateAutoGeneratedBoxes();
|
|
};
|
|
|
|
this.deleteIntersectedBoxes = function () {
|
|
let editors = this.getSelectedEditors();
|
|
editors.forEach((e) => {
|
|
if (e.box) {
|
|
let boxes = e.box.world.annotation.findIntersectedBoxes(e.box);
|
|
|
|
boxes.forEach((b) => {
|
|
func_on_box_remove(b, true);
|
|
});
|
|
|
|
onBoxChangedInBatchMode(e.box);
|
|
}
|
|
});
|
|
};
|
|
|
|
this.deleteSelectedBoxes = function (infoBoxPos) {
|
|
let selectedEditors = this.getSelectedEditors();
|
|
|
|
if (selectedEditors.length >= 2) {
|
|
window.editor.infoBox.show(
|
|
"Confirm",
|
|
`Delete <span class="red">${selectedEditors.length}</span> selected boxes?`,
|
|
["yes", "no"],
|
|
(btn) => {
|
|
if (btn == "yes") {
|
|
selectedEditors.forEach((e) => {
|
|
if (e.box) func_on_box_remove(e.box, true);
|
|
});
|
|
|
|
this.updateAutoGeneratedBoxes();
|
|
}
|
|
},
|
|
infoBoxPos
|
|
);
|
|
} else {
|
|
selectedEditors.forEach((e) => {
|
|
if (e.box) func_on_box_remove(e.box, true);
|
|
});
|
|
|
|
this.updateAutoGeneratedBoxes();
|
|
}
|
|
};
|
|
|
|
this.autoAnnotateSelectedFrames = function () {
|
|
let applyIndList = this.activeEditorList().map((e) => false); //all shoud be applied.
|
|
this.getSelectedEditors().forEach((e) => (applyIndList[e.index] = true));
|
|
this.autoAnnotate(applyIndList);
|
|
};
|
|
|
|
this.onOpCmd = function (cmd, firingEditor) {
|
|
firingEditor.executeOpCmd(cmd);
|
|
|
|
if (this.cfg.linkEditorsInBatchMode) {
|
|
let editors = this.getSelectedEditors();
|
|
|
|
if (editors.includes(firingEditor)) {
|
|
editors
|
|
.filter((x) => x != firingEditor)
|
|
.forEach((e) => {
|
|
if (e.box && !e.box.annotator) {
|
|
e.executeOpCmd(cmd);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
this.handleContextMenuEvent = function (event) {
|
|
console.log(event.currentTarget.id, event.type);
|
|
switch (event.currentTarget.id) {
|
|
// manager
|
|
case "cm-increase-box-editor":
|
|
this.batchSize += 1;
|
|
this.edit(
|
|
this.editingTarget.data,
|
|
this.editingTarget.sceneMeta,
|
|
this.editingTarget.sceneMeta.frame,
|
|
this.editingTarget.objTrackId,
|
|
this.editingTarget.objType
|
|
);
|
|
break;
|
|
|
|
case "cm-decrease-box-editor":
|
|
this.batchSize -= 1;
|
|
this.edit(
|
|
this.editingTarget.data,
|
|
this.editingTarget.sceneMeta,
|
|
this.editingTarget.sceneMeta.frame,
|
|
this.editingTarget.objTrackId,
|
|
this.editingTarget.objType
|
|
);
|
|
break;
|
|
|
|
/////////////////////// obj instance //
|
|
|
|
case "cm-select-all":
|
|
this.activeEditorList().forEach((e) => e.setSelected(true));
|
|
return false; //don't hide context menu
|
|
break;
|
|
case "cm-select-all-previous":
|
|
this.activeEditorList().forEach((e) =>
|
|
e.setSelected(e.index <= this.firingBoxEditor.index)
|
|
);
|
|
return false; //don't hide context menu
|
|
break;
|
|
case "cm-select-all-next":
|
|
this.activeEditorList().forEach((e) =>
|
|
e.setSelected(e.index >= this.firingBoxEditor.index)
|
|
);
|
|
return false; //don't hide context menu
|
|
break;
|
|
|
|
case "cm-delete":
|
|
this.deleteSelectedBoxes({ x: event.clientX, y: event.clientY });
|
|
break;
|
|
case "cm-delete-empty-boxes":
|
|
this.deleteEmptyBoxes();
|
|
break;
|
|
case "cm-delete-intersected-boxes":
|
|
this.deleteIntersectedBoxes();
|
|
break;
|
|
case "cm-interpolate":
|
|
this.interpolateSelectedFrames();
|
|
break;
|
|
|
|
case "cm-auto-annotate":
|
|
this.autoAnnotateSelectedFrames();
|
|
break;
|
|
|
|
case "cm-auto-annotate-wo-rotation":
|
|
{
|
|
let applyIndList = this.activeEditorList().map((e) => false); //all shoud be applied.
|
|
this.getSelectedEditors().forEach(
|
|
(e) => (applyIndList[e.index] = true)
|
|
);
|
|
this.autoAnnotate(applyIndList, "dontrotate");
|
|
}
|
|
break;
|
|
|
|
case "cm-fit-moving-direction":
|
|
this.getSelectedEditors().forEach((e) => {
|
|
if (!e.box) return;
|
|
|
|
let currentBox = e.box;
|
|
let estimatedRot =
|
|
boxOp.estimate_rotation_by_moving_direciton(currentBox);
|
|
|
|
if (estimatedRot) {
|
|
currentBox.rotation.z = estimatedRot.z;
|
|
func_on_box_changed(currentBox);
|
|
}
|
|
});
|
|
|
|
this.updateAutoGeneratedBoxes();
|
|
|
|
break;
|
|
case "cm-fit-size":
|
|
this.getSelectedEditors().forEach((e) => {
|
|
if (!e.box) return;
|
|
|
|
boxOp.fit_size(e.box, ["x", "y"]);
|
|
func_on_box_changed(e.box);
|
|
});
|
|
|
|
this.updateAutoGeneratedBoxes();
|
|
break;
|
|
case "cm-fit-position":
|
|
this.getSelectedEditors().forEach((e) => {
|
|
if (!e.box) return;
|
|
boxOp.auto_rotate_xyz(
|
|
e.box,
|
|
null,
|
|
null, //{x:false, y:false, z:true},
|
|
func_on_box_changed, //onBoxChangedInBatchMode,
|
|
"noscaling",
|
|
"dontrotate"
|
|
);
|
|
});
|
|
|
|
this.updateAutoGeneratedBoxes();
|
|
break;
|
|
case "cm-fit-rotation":
|
|
this.getSelectedEditors().forEach((e) => {
|
|
if (!e.box) return;
|
|
boxOp.auto_rotate_xyz(
|
|
e.box,
|
|
null,
|
|
null,
|
|
func_on_box_changed, //onBoxChangedInBatchMode, //
|
|
"noscaling"
|
|
);
|
|
});
|
|
|
|
this.updateAutoGeneratedBoxes();
|
|
break;
|
|
case "cm-fit-bottom":
|
|
this.getSelectedEditors().forEach((e) => {
|
|
if (!e.box) return;
|
|
boxOp.fit_bottom(e.box);
|
|
|
|
func_on_box_changed(e.box);
|
|
});
|
|
|
|
this.updateAutoGeneratedBoxes();
|
|
break;
|
|
case "cm-fit-top":
|
|
this.getSelectedEditors().forEach((e) => {
|
|
if (!e.box) return;
|
|
boxOp.fit_top(e.box);
|
|
|
|
func_on_box_changed(e.box);
|
|
});
|
|
|
|
this.updateAutoGeneratedBoxes();
|
|
break;
|
|
case "cm-fit-left":
|
|
this.getSelectedEditors().forEach((e) => {
|
|
if (!e.box) return;
|
|
boxOp.fit_left(e.box);
|
|
|
|
func_on_box_changed(e.box);
|
|
});
|
|
|
|
this.updateAutoGeneratedBoxes();
|
|
break;
|
|
case "cm-fit-right":
|
|
this.getSelectedEditors().forEach((e) => {
|
|
if (!e.box) return;
|
|
boxOp.fit_right(e.box);
|
|
|
|
func_on_box_changed(e.box);
|
|
});
|
|
|
|
this.updateAutoGeneratedBoxes();
|
|
break;
|
|
case "cm-fit-rear":
|
|
this.getSelectedEditors().forEach((e) => {
|
|
if (!e.box) return;
|
|
boxOp.fit_rear(e.box);
|
|
|
|
func_on_box_changed(e.box);
|
|
});
|
|
|
|
this.updateAutoGeneratedBoxes();
|
|
break;
|
|
case "cm-fit-front":
|
|
this.getSelectedEditors().forEach((e) => {
|
|
if (!e.box) return;
|
|
boxOp.fit_front(e.box);
|
|
|
|
func_on_box_changed(e.box);
|
|
});
|
|
|
|
this.updateAutoGeneratedBoxes();
|
|
break;
|
|
case "cm-reverse-direction":
|
|
this.getSelectedEditors().forEach((e) => {
|
|
if (!e.box) return;
|
|
if (e.box.rotation.z > 0) {
|
|
e.box.rotation.z -= Math.PI;
|
|
} else {
|
|
e.box.rotation.z += Math.PI;
|
|
}
|
|
|
|
onBoxChangedInBatchMode(e.box);
|
|
});
|
|
|
|
//this.viewManager.render();
|
|
|
|
this.updateAutoGeneratedBoxes();
|
|
|
|
break;
|
|
case "cm-reset-roll-pitch":
|
|
this.getSelectedEditors().forEach((e) => {
|
|
if (!e.box) return;
|
|
e.box.rotation.x = 0;
|
|
e.box.rotation.y = 0;
|
|
e.update("dontrender");
|
|
e.box.world.annotation.setModified();
|
|
|
|
onBoxChangedInBatchMode(e.box);
|
|
});
|
|
|
|
//this.viewManager.render();
|
|
this.updateAutoGeneratedBoxes();
|
|
|
|
break;
|
|
case "cm-show-trajectory":
|
|
this.showTrajectory();
|
|
break;
|
|
|
|
case "cm-check":
|
|
{
|
|
let scene = this.editingTarget.sceneMeta.scene;
|
|
checkScene(scene);
|
|
logger.show();
|
|
logger.errorBtn.onclick();
|
|
}
|
|
break;
|
|
|
|
case "cm-finalize":
|
|
this.finalizeSelectedBoxes();
|
|
break;
|
|
|
|
case "cm-sync-size":
|
|
editor.data.worldList.forEach((w) => {
|
|
let box = w.annotation.boxes.find(
|
|
(b) => b.obj_track_id == this.firingBoxEditor.target.objTrackId
|
|
);
|
|
if (box && box !== this.firingBoxEditor.box) {
|
|
box.scale.x = this.firingBoxEditor.box.scale.x;
|
|
box.scale.y = this.firingBoxEditor.box.scale.y;
|
|
box.scale.z = this.firingBoxEditor.box.scale.z;
|
|
//saveList.push(w);
|
|
w.annotation.setModified();
|
|
|
|
onBoxChangedInBatchMode(box);
|
|
}
|
|
});
|
|
|
|
//this.activeEditorList().forEach(e=>e.update('dontrender'));
|
|
//this.viewManager.render();
|
|
this.updateAutoGeneratedBoxes();
|
|
|
|
break;
|
|
case "cm-reload":
|
|
{
|
|
let selectedEditors = this.getSelectedEditors();
|
|
this.reloadAnnotation(selectedEditors);
|
|
|
|
this.updateAutoGeneratedBoxes();
|
|
}
|
|
break;
|
|
|
|
case "cm-goto-this-frame":
|
|
{
|
|
this.gotoThisFrame();
|
|
}
|
|
break;
|
|
case "cm-follow-static-objects":
|
|
{
|
|
let b = this.firingBoxEditor.box;
|
|
editor.autoAdjust.followStaticObjects(b);
|
|
this.globalHeader.updateModifiedStatus();
|
|
|
|
this.activeEditorList().forEach((e) => {
|
|
e.tryAttach();
|
|
});
|
|
|
|
//this.viewManager.render();
|
|
this.updateAutoGeneratedBoxes();
|
|
}
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
this.reset = function () {
|
|
this.activeEditorList().forEach((e) => {
|
|
e.setSelected(false);
|
|
e.resetTarget();
|
|
});
|
|
|
|
this.viewManager.mainView.clearView();
|
|
|
|
this.activeIndex = 0;
|
|
};
|
|
|
|
this.keydownHandler = (event) => {
|
|
switch (event.key) {
|
|
case "a":
|
|
if (event.ctrlKey) {
|
|
this.activeEditorList().forEach((e) => e.setSelected(true));
|
|
}
|
|
break;
|
|
|
|
case "s":
|
|
if (event.ctrlKey) {
|
|
this._save();
|
|
console.log("saved for batch editor");
|
|
}
|
|
break;
|
|
case "+":
|
|
case "=":
|
|
this.editingTarget.data.scale_point_size(1.2);
|
|
this.viewManager.render();
|
|
break;
|
|
case "-":
|
|
this.editingTarget.data.scale_point_size(0.8);
|
|
this.viewManager.render();
|
|
break;
|
|
case "v":
|
|
case "Escape":
|
|
{
|
|
// let selected = this.getSelectedEditors();
|
|
// if (selected.length >= 2){
|
|
// selected.forEach(e=>e.setSelected(false));
|
|
// }
|
|
// else
|
|
{
|
|
this.hide();
|
|
this.reset();
|
|
if (this.onExit) this.onExit();
|
|
}
|
|
}
|
|
break;
|
|
case "PageUp":
|
|
case "3":
|
|
this.prevBatch();
|
|
break;
|
|
case "PageDown":
|
|
case "4":
|
|
this.nextBatch();
|
|
break;
|
|
case "t":
|
|
this.showTrajectory();
|
|
break;
|
|
default:
|
|
console.log(`key ${event.key} igonored`);
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
let keydownHandler = (event) => this.keydownHandler(event);
|
|
|
|
this.hide = function () {
|
|
if (this.parentUi.style.display != "none") {
|
|
this.parentUi.style.display = "none";
|
|
this.toolbox.style.display = "none";
|
|
//document.removeEventListener("keydown", keydownHandler);
|
|
globalKeyDownManager.deregister("batch-editor");
|
|
}
|
|
};
|
|
this.show = function () {
|
|
if (this.parentUi.style.display == "none") {
|
|
this.parentUi.style.display = "";
|
|
//document.addEventListener("keydown", keydownHandler);
|
|
globalKeyDownManager.register(keydownHandler, "batch-editor");
|
|
this.toolbox.style.display = "";
|
|
}
|
|
};
|
|
|
|
this.render = function () {
|
|
if (this.parentUi.style.display != "none") {
|
|
this.viewManager.render();
|
|
}
|
|
};
|
|
|
|
this._addToolBox = function () {
|
|
let template = document.getElementById("batch-editor-tools-template");
|
|
let tool = template.content.cloneNode(true);
|
|
// this.boxEditorHeaderUi.appendChild(tool);
|
|
// return this.boxEditorHeaderUi.lastElementChild;
|
|
|
|
document.getElementById("dynamic-buttons-placeholder").appendChild(tool);
|
|
return document.getElementById("dynamic-buttons-placeholder")
|
|
.lastElementChild;
|
|
};
|
|
|
|
this.toolbox = this._addToolBox();
|
|
|
|
this.reloadAnnotation = function (editorList) {
|
|
let done = (anns) => {
|
|
// update editor
|
|
editorList.forEach((e) => {
|
|
e.tryAttach();
|
|
e.update("dontrender");
|
|
});
|
|
|
|
// reload main view
|
|
if (func_on_annotation_reloaded) func_on_annotation_reloaded();
|
|
// render all, at last
|
|
|
|
this.viewManager.render();
|
|
|
|
this.globalHeader.updateModifiedStatus();
|
|
};
|
|
|
|
let worldList = editorList.map((e) => e.target.world);
|
|
|
|
let modifiedFrames = worldList.filter((w) => w && w.annotation.modified);
|
|
|
|
if (modifiedFrames.length > 0) {
|
|
window.editor.infoBox.show(
|
|
"Confirm",
|
|
`Discard changes to ${modifiedFrames.length} frames, continue to reload?`,
|
|
["yes", "no"],
|
|
(choice) => {
|
|
if (choice == "yes") {
|
|
reloadWorldList(worldList, done);
|
|
}
|
|
}
|
|
);
|
|
} else {
|
|
reloadWorldList(worldList, done);
|
|
}
|
|
};
|
|
|
|
this.interpolate = async function (applyIndList) {
|
|
let boxList = this.activeEditorList().map((e) => e.box);
|
|
let worldList = this.activeEditorList().map((e) => e.target.world);
|
|
await this.boxOp.interpolateAsync(worldList, boxList, applyIndList);
|
|
this.activeEditorList().forEach((e) => e.tryAttach());
|
|
|
|
this.globalHeader.updateModifiedStatus();
|
|
|
|
this.viewManager.render();
|
|
};
|
|
|
|
this.gotoThisFrame = function () {
|
|
let targetFrame = this.firingBoxEditor.target.world.frameInfo.frame;
|
|
let targetTrackId = this.firingBoxEditor.target.objTrackId;
|
|
this.hide();
|
|
|
|
this.reset();
|
|
if (this.onExit) this.onExit(targetFrame, targetTrackId);
|
|
};
|
|
|
|
this.autoAnnotate = async function (applyIndList, dontRotate) {
|
|
let editors = this.activeEditorList();
|
|
let boxList = editors.map((e) => e.box);
|
|
let worldList = editors.map((e) => e.target.world);
|
|
|
|
let onFinishOneBox = (i) => {
|
|
editors[i].tryAttach();
|
|
editors[i].box.world.annotation.setModified();
|
|
this.viewManager.render();
|
|
|
|
this.updateAutoGeneratedBoxes();
|
|
};
|
|
|
|
await this.boxOp.interpolateAndAutoAdjustAsync(
|
|
worldList,
|
|
boxList,
|
|
onFinishOneBox,
|
|
applyIndList,
|
|
dontRotate
|
|
);
|
|
|
|
this.globalHeader.updateModifiedStatus();
|
|
};
|
|
|
|
// this.parentUi.querySelector("#object-track-id-editor").addEventListener("keydown", function(e){
|
|
// e.stopPropagation();});
|
|
|
|
// this.parentUi.querySelector("#object-track-id-editor").addEventListener("keyup", function(e){
|
|
// e.stopPropagation();
|
|
// });
|
|
|
|
// this.parentUi.querySelector("#object-track-id-editor").onchange = (ev)=>this.object_track_id_changed(ev);
|
|
// this.parentUi.querySelector("#object-category-selector").onchange = (ev)=>this.object_category_changed(ev);
|
|
|
|
// this should follow addToolBox
|
|
|
|
// this.parentUi.querySelector("#instance-number").value = this.batchSize;
|
|
// this.parentUi.querySelector("#instance-number").onchange = (ev)=>{
|
|
// this.batchSize = parseInt(ev.currentTarget.value);
|
|
// this.edit(
|
|
// this.editingTarget.data,
|
|
// this.editingTarget.sceneMeta,
|
|
// this.editingTarget.frame,
|
|
// this.editingTarget.objTrackId,
|
|
// this.editingTarget.objType
|
|
// );
|
|
// }
|
|
|
|
this.showTrajectory = () => {
|
|
let tracks = this.editingTarget.data.worldList.map((w) => {
|
|
let box = w.annotation.findBoxByTrackId(this.editingTarget.objTrackId);
|
|
let ann = null;
|
|
if (box) {
|
|
ann = w.annotation.boxToAnn(box);
|
|
ann.psr.position = w.lidarPosToUtm(ann.psr.position);
|
|
ann.psr.rotation = w.lidarRotToUtm(ann.psr.rotation);
|
|
}
|
|
return [w.frameInfo.frame, ann, false];
|
|
});
|
|
|
|
tracks.sort((a, b) => (a[0] > b[0] ? 1 : -1));
|
|
|
|
this.objectTrackView.setObject(
|
|
this.editingTarget.objType,
|
|
this.editingTarget.objTrackId,
|
|
tracks,
|
|
(targetFrame) => {
|
|
//onExit
|
|
this.getSelectedEditors().forEach((e) => e.setSelected(false));
|
|
this.activeEditorList()
|
|
.find((e) => e.target.world.frameInfo.frame == targetFrame)
|
|
.setSelected(true);
|
|
}
|
|
);
|
|
};
|
|
|
|
this.toolbox.querySelector("#trajectory").onclick = (e) => {
|
|
this.showTrajectory();
|
|
};
|
|
|
|
this.toolbox.querySelector("#reload").onclick = (e) => {
|
|
let selectedEditors = this.activeEditorList();
|
|
this.reloadAnnotation(selectedEditors);
|
|
};
|
|
|
|
this.toolbox.querySelector("#interpolate").onclick = async () => {
|
|
//this.boxOp.interpolate_selected_object(this.editingTarget.scene, this.editingTarget.objTrackId, "");
|
|
|
|
let applyIndList = this.activeEditorList().map((e) => true); //all shoud be applied.
|
|
this.interpolate(applyIndList);
|
|
};
|
|
|
|
this.toolbox.querySelector("#auto-annotate").onclick = async () => {
|
|
let applyIndList = this.activeEditorList().map((e) => true); //all shoud be applied.
|
|
this.autoAnnotate(applyIndList);
|
|
};
|
|
|
|
this.toolbox.querySelector("#auto-annotate-translate-only").onclick =
|
|
async () => {
|
|
let applyIndList = this.activeEditorList().map((e) => true); //all shoud be applied.
|
|
this.autoAnnotate(applyIndList, "dontrotate");
|
|
};
|
|
|
|
this.toolbox.querySelector("#exit").onclick = () => {
|
|
this.hide();
|
|
|
|
this.reset();
|
|
|
|
if (this.onExit) this.onExit();
|
|
};
|
|
|
|
this.toolbox.querySelector("#next").onclick = () => {
|
|
this.nextBatch();
|
|
};
|
|
|
|
this.toolbox.querySelector("#prev").onclick = () => {
|
|
this.prevBatch();
|
|
};
|
|
|
|
this.nextBatch = function () {
|
|
let maxFrameIndex = this.editingTarget.sceneMeta.frames.length - 1;
|
|
|
|
let editors = this.activeEditorList();
|
|
let lastEditor = editors[editors.length - 1];
|
|
if (lastEditor.target.world.frameInfo.frame_index == maxFrameIndex) {
|
|
if (this.batchSize >= this.editingTarget.sceneMeta.frames.length) {
|
|
this.nextObj();
|
|
} else {
|
|
window.editor.infoBox.show("Info", "This is the last batch of frames.");
|
|
}
|
|
} else {
|
|
this.edit(
|
|
this.editingTarget.data,
|
|
this.editingTarget.sceneMeta,
|
|
this.editingTarget.sceneMeta.frames[
|
|
Math.min(
|
|
this.editingTarget.frameIndex + this.batchSize / 2,
|
|
maxFrameIndex
|
|
)
|
|
],
|
|
this.editingTarget.objTrackId,
|
|
this.editingTarget.objType
|
|
);
|
|
}
|
|
};
|
|
|
|
this.prevBatch = function () {
|
|
let firstEditor = this.activeEditorList()[0];
|
|
if (firstEditor.target.world.frameInfo.frame_index == 0) {
|
|
if (this.batchSize >= this.editingTarget.sceneMeta.frames.length) {
|
|
this.prevObj();
|
|
} else {
|
|
window.editor.infoBox.show(
|
|
"Info",
|
|
"This is the first batch of frames"
|
|
);
|
|
}
|
|
} else {
|
|
this.edit(
|
|
this.editingTarget.data,
|
|
this.editingTarget.sceneMeta,
|
|
this.editingTarget.sceneMeta.frames[
|
|
Math.max(this.editingTarget.frameIndex - this.batchSize / 2, 0)
|
|
],
|
|
this.editingTarget.objTrackId,
|
|
this.editingTarget.objType
|
|
);
|
|
}
|
|
};
|
|
|
|
this.prevObj = function () {
|
|
let idx = objIdManager.objectList.findIndex(
|
|
(x) => x.id == this.editingTarget.objTrackId
|
|
);
|
|
|
|
let objNum = objIdManager.objectList.length;
|
|
|
|
idx = (idx + objNum - 1) % objNum;
|
|
|
|
let obj = objIdManager.objectList[idx];
|
|
|
|
this.edit(
|
|
this.editingTarget.data,
|
|
this.editingTarget.sceneMeta,
|
|
this.editingTarget.sceneMeta.frames[this.editingTarget.frameIndex],
|
|
obj.id,
|
|
obj.category
|
|
);
|
|
};
|
|
|
|
this.gotoFrame = function (frameID) {
|
|
this.getSelectedEditors().forEach((e) => e.setSelected(false));
|
|
this.activeEditorList()
|
|
.find((e) => e.target.world.frameInfo.frame == frameID)
|
|
.setSelected(true);
|
|
};
|
|
|
|
this.gotoObjectFrame = function (frameId, objId) {
|
|
if (objId != this.editingTarget.objTrackId) {
|
|
let obj = objIdManager.getObjById(objId);
|
|
|
|
this.edit(
|
|
this.editingTarget.data,
|
|
this.editingTarget.sceneMeta,
|
|
frameId,
|
|
objId,
|
|
obj.category
|
|
);
|
|
}
|
|
|
|
this.getSelectedEditors().forEach((e) => e.setSelected(false));
|
|
this.activeEditorList()
|
|
.find((e) => e.target.world.frameInfo.frame == frameId)
|
|
.setSelected(true);
|
|
};
|
|
|
|
this.nextObj = function () {
|
|
let idx = objIdManager.objectList.findIndex(
|
|
(x) =>
|
|
x.id == this.editingTarget.objTrackId &&
|
|
x.category == this.editingTarget.objType
|
|
);
|
|
|
|
let objNum = objIdManager.objectList.length;
|
|
|
|
idx = (idx + 1) % objNum;
|
|
|
|
let obj = objIdManager.objectList[idx];
|
|
|
|
this.edit(
|
|
this.editingTarget.data,
|
|
this.editingTarget.sceneMeta,
|
|
this.editingTarget.sceneMeta.frames[this.editingTarget.frameIndex],
|
|
obj.id,
|
|
obj.category
|
|
);
|
|
};
|
|
|
|
// this.toolbox.querySelector("#save").onclick = ()=>{
|
|
// this._save();
|
|
// };
|
|
|
|
this.toolbox.querySelector("#finalize").onclick = () => {
|
|
this.finalize();
|
|
};
|
|
|
|
this.finalize = function () {
|
|
this.activeEditorList().forEach((e) => {
|
|
if (e.box) {
|
|
if (e.box.annotator) {
|
|
delete e.box.annotator;
|
|
func_on_box_changed(e.box);
|
|
}
|
|
e.box.world.annotation.setModified();
|
|
e.updateInfo();
|
|
}
|
|
});
|
|
|
|
this.globalHeader.updateModifiedStatus();
|
|
};
|
|
|
|
this.object_track_id_changed = function (event) {
|
|
var id = event.currentTarget.value;
|
|
|
|
if (id == "new") {
|
|
id = objIdManager.generateNewUniqueId();
|
|
this.parentUi.querySelector("#object-track-id-editor").value = id;
|
|
}
|
|
|
|
this.activeEditorList().forEach((e) => {
|
|
if (e.box) {
|
|
e.box.obj_track_id = id;
|
|
}
|
|
});
|
|
};
|
|
|
|
this.object_category_changed = function (event) {
|
|
let obj_type = event.currentTarget.value;
|
|
this.activeEditorList().forEach((e) => {
|
|
if (e.box) {
|
|
e.box.obj_type = obj_type;
|
|
}
|
|
});
|
|
};
|
|
|
|
this._save = function () {
|
|
let worldList = [];
|
|
let editorList = [];
|
|
this.activeEditorList().forEach((e) => {
|
|
worldList.push(e.target.world);
|
|
editorList.push(e);
|
|
});
|
|
|
|
saveWorldList(worldList);
|
|
};
|
|
|
|
this.updateViewZoomRatio = function (viewIndex, ratio) {
|
|
const dontRender = true;
|
|
this.activeEditorList().forEach((e) => {
|
|
e._setViewZoomRatio(viewIndex, ratio);
|
|
e.update(dontRender);
|
|
});
|
|
|
|
// render all
|
|
this.viewManager.render();
|
|
};
|
|
|
|
this.addEditor = function () {
|
|
let editor = this.allocateEditor();
|
|
this.activeIndex += 1;
|
|
return editor;
|
|
};
|
|
|
|
this.allocateEditor = function () {
|
|
if (this.activeIndex >= this.editorList.length) {
|
|
let editor = new BoxEditor(
|
|
this.boxEditorGroupUi,
|
|
this,
|
|
this.viewManager,
|
|
cfg,
|
|
this.boxOp,
|
|
func_on_box_changed,
|
|
func_on_box_remove,
|
|
String(this.activeIndex)
|
|
);
|
|
|
|
// resizable for the first editor
|
|
|
|
if (this.editorList.length == 0) {
|
|
editor.setResize("both");
|
|
}
|
|
|
|
this.editorList.push(editor);
|
|
|
|
return editor;
|
|
} else {
|
|
return this.editorList[this.activeIndex];
|
|
}
|
|
};
|
|
|
|
this.getEditorByMousePosition = function (x, y) {
|
|
return this.editorList.find((e) => {
|
|
let rect = e.ui.getBoundingClientRect();
|
|
|
|
return x > rect.left && x < rect.right && y > rect.top && y < rect.bottom;
|
|
});
|
|
};
|
|
|
|
this.parentUi.onmousedown = (event) => {
|
|
if (event.which != 1) return;
|
|
|
|
let eventId = Date.now();
|
|
|
|
let select_start_pos = {
|
|
x: event.clientX,
|
|
y: event.clientY,
|
|
};
|
|
|
|
console.log("box editor manager, on mouse down.", select_start_pos);
|
|
|
|
let select_end_pos = {
|
|
x: event.clientX,
|
|
y: event.clientY,
|
|
};
|
|
|
|
let leftMouseDown = true;
|
|
|
|
// a1<a2, b1<b2
|
|
function lineIntersect(a1, a2, b1, b2) {
|
|
if (a1 > a2) [a1, a2] = [a2, a1];
|
|
if (b1 > b2) [b1, b2] = [b2, b1];
|
|
|
|
return (
|
|
(a1 > b1 && a1 < b2) ||
|
|
(a2 > b1 && a2 < b2) ||
|
|
(b1 > a1 && b1 < a2) ||
|
|
(b2 > a1 && b2 < a2)
|
|
);
|
|
}
|
|
|
|
// a,b: left, right, right, bottom
|
|
function intersect(domRect, mouseA, mouseB) {
|
|
return (
|
|
lineIntersect(
|
|
select_end_pos.x,
|
|
select_start_pos.x,
|
|
domRect.left,
|
|
domRect.right
|
|
) &&
|
|
lineIntersect(
|
|
select_end_pos.y,
|
|
select_start_pos.y,
|
|
domRect.top,
|
|
domRect.bottom
|
|
)
|
|
);
|
|
}
|
|
|
|
this.parentUi.onmousemove = (event) => {
|
|
select_end_pos.x = event.clientX;
|
|
select_end_pos.y = event.clientY;
|
|
|
|
this.editorList.forEach((e) => {
|
|
let rect = e.ui.getBoundingClientRect();
|
|
let intersected = intersect(rect, select_start_pos, select_end_pos);
|
|
|
|
e.setSelected(intersected, event.ctrlKey ? eventId : null);
|
|
});
|
|
};
|
|
|
|
this.parentUi.onmouseup = (event) => {
|
|
if (event.which != 1) return;
|
|
|
|
leftMouseDown = false;
|
|
this.parentUi.onmousemove = null;
|
|
this.parentUi.onmouseup = null;
|
|
|
|
if (
|
|
event.clientX == select_start_pos.x &&
|
|
event.clientY == select_start_pos.y
|
|
) {
|
|
// click
|
|
|
|
let ed = this.getEditorByMousePosition(event.clientX, event.clientY);
|
|
|
|
if (event.shiftKey) {
|
|
let selectedEditors = this.getSelectedEditors();
|
|
if (selectedEditors.length == 0) {
|
|
} else if (ed.index < selectedEditors[0].index) {
|
|
this.activeEditorList().forEach((e) => {
|
|
if (e.index >= ed.index && e.index < selectedEditors[0].index) {
|
|
e.setSelected(true);
|
|
}
|
|
});
|
|
} else if (
|
|
ed.index > selectedEditors[selectedEditors.length - 1].index
|
|
) {
|
|
this.activeEditorList().forEach((e) => {
|
|
if (
|
|
e.index <= ed.index &&
|
|
e.index > selectedEditors[selectedEditors.length - 1].index
|
|
) {
|
|
e.setSelected(true);
|
|
}
|
|
});
|
|
}
|
|
} else if (event.ctrlKey) {
|
|
ed.setSelected(!ed.selected);
|
|
} else {
|
|
let selectedEditors = this.getSelectedEditors();
|
|
|
|
if (ed) {
|
|
if (ed.selected && selectedEditors.length == 1) {
|
|
ed.setSelected(false);
|
|
} else {
|
|
selectedEditors.forEach((e) => e.setSelected(false));
|
|
ed.setSelected(true);
|
|
}
|
|
} else {
|
|
selectedEditors.forEach((e) => e.setSelected(false));
|
|
}
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
this.getSelectedEditors = function () {
|
|
return this.editorList.filter((e) => e.selected);
|
|
};
|
|
}
|
|
export { BoxEditorManager, BoxEditor };
|