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.

591 lines
16 KiB
JavaScript

import * as THREE from './lib/three.module.js';
import {globalObjectCategory} from './obj_cfg.js';
import {saveWorldList} from "./save.js"
import { intersect } from './util.js';
function Annotation(sceneMeta, world, frameInfo){
this.world = world;
this.data = this.world.data;
//this.coordinatesOffset = this.world.coordinatesOffset;
this.boxes_load_time = 0;
this.frameInfo = frameInfo;
this.modified = false;
this.setModified = function(){
this.modified=true;
if (pointsGlobalConfig.autoSave)
{
saveWorldList([this.world]);
}
};
this.resetModified = function(){this.modified=false;};
this.sort_boxes = function(){
this.boxes = this.boxes.sort(function(x,y){
return x.position.y - y.position.y;
});
};
this.findBoxByTrackId = function(id){
if (this.boxes){
let box = this.boxes.find(function(x){
return x.obj_track_id == id;
});
return box;
}
return null;
};
this.findIntersectedBoxes = function(box){
return this.boxes.filter(b=>b!=box).filter(b=>intersect(box, b));
};
this.preload = function(on_preload_finished){
this.on_preload_finished = on_preload_finished;
this.load_annotation((boxes)=>this.proc_annotation(boxes));
};
this.go_cmd_received = false;
this.webglScene = null;
this.on_go_finished = null;
this.go = function(webglScene, on_go_finished){
this.webglScene = webglScene;
if (this.preloaded){
//this.boxes.forEach(b=>this.webglScene.add(b));
if (this.data.cfg.color_obj != "no"){
this.color_boxes();
}
if (on_go_finished)
on_go_finished();
} else {
this.go_cmd_received = true;
this.on_go_finished = on_go_finished;
}
};
// internal funcs below
this._afterPreload = function(){
this.preloaded = true;
console.log("annotation preloaded");
if (this.on_preload_finished){
this.on_preload_finished();
}
if (this.go_cmd_received){
this.go(this.webglScene, this.on_go_finished);
}
};
this.unload = function(){
if (this.boxes){
this.boxes.forEach((b)=>{
//this.webglGroup.remove(b);
if (b.boxEditor)
b.boxEditor.detach();
});
}
};
this.deleteAll = function(){
this.remove_all_boxes();
};
this.boxToAnn = function(box){
let ann = {
psr: {
position:{
x: box.position.x,
y: box.position.y,
z: box.position.z,
},
scale:{
x: box.scale.x,
y: box.scale.y,
z: box.scale.z,
},
rotation:{
x:box.rotation.x,
y:box.rotation.y,
z:box.rotation.z,
},
},
obj_type: box.obj_type,
obj_id: String(box.obj_track_id),
obj_attr: box.obj_attr,
//vertices: vertices,
};
return ann;
};
this.toBoxAnnotations = function(){
let anns = this.boxes.map((b)=>{
//var vertices = psr_to_xyz(b.position, b.scale, b.rotation);
let ann = this.boxToAnn(b);
if (b.annotator)
ann.annotator = b.annotator;
if (b.follows)
ann.follows = b.follows;
return ann;
});
anns.sort((a,b)=>a.obj_id- b.obj_id);
return anns;
};
// to real-world position (no offset)
this.ann_to_vector_global = function(box) {
let posG = this.world.lidarPosToScene(box.position);
let rotG = this.world.lidarRotToScene(box.rotation);
return [
posG.x - this.world.coordinatesOffset[0], posG.y-this.world.coordinatesOffset[1], posG.z-this.world.coordinatesOffset[2],
rotG.x, rotG.y, rotG.z,
box.scale.x, box.scale.y, box.scale.z,
];
};
// real-world position to ann
this.vector_global_to_ann = function(v)
{
let posG = new THREE.Vector3(v[0]+this.world.coordinatesOffset[0],
v[1]+this.world.coordinatesOffset[1],
v[2]+this.world.coordinatesOffset[2]);
let rotG = new THREE.Euler(v[3],v[4],v[5]);
let rotL = this.world.sceneRotToLidar(rotG);
let posL = this.world.scenePosToLidar(posG);
return {
position: {x: posL.x, y: posL.y, z: posL.z},
rotation: {x: rotL.x, y: rotL.y, z: rotL.z},
scale: {x: v[6], y: v[7], z: v[8]}
};
};
// this.vector_to_ann = function(v){
// return {
// position:{
// x:v[0],// + this.coordinatesOffset[0],
// y:v[1],// + this.coordinatesOffset[1],
// z:v[2],// + this.coordinatesOffset[2],
// },
// rotation:{
// x:v[3],
// y:v[4],
// z:v[5],
// },
// scale:{
// x:v[6],
// y:v[7],
// z:v[8],
// },
// };
// };
this.remove_all_boxes = function(){
if (this.boxes){
this.boxes.forEach((b)=>{
this.webglGroup.remove(b);
this.world.data.dbg.free();
b.geometry.dispose();
b.material.dispose();
b.world = null;
b.boxEditor = null;
});
this.boxes = [];
}
else{
console.error("destroy empty world!")
}
};
this.new_bbox_cube=function(color){
var h = 0.5;
var body = [
//top
-h,h,h, h,h,h,
h,h,h, h,-h,h,
h,-h,h, -h,-h,h,
-h,-h,h, -h, h, h,
//botom
-h,h,-h, h,h,-h,
h,h,-h, h,-h,-h,
h,-h,-h, -h,-h,-h,
-h,-h,-h, -h, h, -h,
// vertical lines
-h,h,h, -h,h,-h,
h,h,h, h,h,-h,
h,-h,h, h,-h,-h,
-h,-h,h, -h,-h,-h,
//direction
h, 0, h, 1.5*h, 0, h,
//h/2, -h, h+0.1, h, 0, h+0.1,
//h/2, h, h+0.1, h, 0, h+0.1,
//side direction
// h, h/2, h, h, h, 0,
// h, h/2, -h, h, h, 0,
// h, 0, 0, h, h, 0,
];
this.world.data.dbg.alloc();
var bbox = new THREE.BufferGeometry();
bbox.setAttribute( 'position', new THREE.Float32BufferAttribute(body, 3 ) );
if (!color){
color = 0x00ff00;
}
/*
https://threejs.org/docs/index.html#api/en/materials/LineBasicMaterial
linewidth is 1, regardless of set value.
*/
var material = new THREE.LineBasicMaterial( { color: color, linewidth: 1, opacity: this.data.cfg.box_opacity, transparent: true } );
var box = new THREE.LineSegments( bbox, material );
box.scale.x=1.8;
box.scale.y=4.5;
box.scale.z=1.5;
box.name="bbox";
box.obj_type="car";
//box.computeLineDistances();
return box;
};
this.createCuboid = function(pos, scale, rotation, obj_type, track_id, obj_attr){
let mesh = this.new_bbox_cube(parseInt("0x"+globalObjectCategory.get_obj_cfg_by_type(obj_type).color.slice(1)));
mesh.position.x = pos.x;
mesh.position.y = pos.y;
mesh.position.z = pos.z;
mesh.scale.x = scale.x;
mesh.scale.y = scale.y;
mesh.scale.z = scale.z;
mesh.rotation.x = rotation.x;
mesh.rotation.y = rotation.y;
mesh.rotation.z = rotation.z;
mesh.obj_track_id = track_id; //tracking id
mesh.obj_type = obj_type;
mesh.obj_attr = obj_attr;
mesh.obj_local_id = this.get_new_box_local_id();
mesh.world = this.world;
return mesh;
};
/*
pos: offset position, after transformed
*/
this.add_box=function(pos, scale, rotation, obj_type, track_id, obj_attr){
let mesh = this.createCuboid(pos, scale, rotation, obj_type, track_id, obj_attr)
this.boxes.push(mesh);
this.sort_boxes();
this.webglGroup.add(mesh);
return mesh;
};
this.load_box = function(box){
this.webglGroup.add(box);
};
this.unload_box = function(box){
this.webglGroup.remove(box);
};
this.remove_box=function(box){
this.world.data.dbg.free();
box.geometry.dispose();
box.material.dispose();
//selected_box.dispose();
this.boxes = this.boxes.filter(function(x){return x !=box;});
};
this.set_box_opacity=function(box_opacity){
this.boxes.forEach(function(x){
x.material.opacity = box_opacity;
});
};
this.translate_box_position=function(pos, theta, axis, delta){
switch (axis){
case 'x':
pos.x += delta*Math.cos(theta);
pos.y += delta*Math.sin(theta);
break;
case 'y':
pos.x += delta*Math.cos(Math.PI/2 + theta);
pos.y += delta*Math.sin(Math.PI/2 + theta);
break;
case 'z':
pos.z += delta;
break;
}
};
this.find_boxes_inside_rect = function(x,y,w,h, camera){
let selected_boxes_by_rect = [];
if (!this.boxes)
return selected_boxes_by_rect;
var p = new THREE.Vector3();
for (var i=0; i< this.boxes.length; i++){
let box_center = this.boxes[i].position;
let pw = this.world.lidarPosToScene(box_center);
p.set(pw.x, pw.y, pw.z);
p.project(camera);
p.x = p.x/p.z;
p.y = p.y/p.z;
//console.log(p);
if ((p.x > x) && (p.x < x+w) && (p.y>y) && (p.y<y+h)){
selected_boxes_by_rect.push(this.boxes[i]);
}
}
console.log("select boxes", selected_boxes_by_rect.length);
return selected_boxes_by_rect;
},
this.proc_annotation = function(boxes){
// boxes = this.transformBoxesByEgoPose(boxes);
// boxes = this.transformBoxesByOffset(boxes);
// //var boxes = JSON.parse(this.responseText);
//console.log(ret);
this.boxes = this.createBoxes(boxes); //create in future world
this.webglGroup = new THREE.Group();
this.webglGroup.name = "annotations";
this.boxes.forEach(b=>this.webglGroup.add(b));
this.world.webglGroup.add(this.webglGroup);
this.boxes_load_time = new Date().getTime();
console.log(this.boxes_load_time, this.frameInfo.scene, this.frameInfo.frame, "loaded boxes ", this.boxes_load_time - this.create_time, "ms");
this.sort_boxes();
this._afterPreload();
};
this.load_annotation=function(on_load){
if (this.data.cfg.disableLabels){
on_load([]);
}else {
var xhr = new XMLHttpRequest();
// we defined the xhr
var _self = this;
xhr.onreadystatechange = function () {
if (this.readyState != 4) return;
if (this.status == 200) {
let ann = _self.frameInfo.anno_to_boxes(this.responseText);
on_load(ann);
}
// end of state change: it can be after some time (async)
};
xhr.open('GET', "/load_annotation"+"?scene="+this.frameInfo.scene+"&frame="+this.frameInfo.frame, true);
xhr.send();
}
};
this.reloadAnnotation=function(done){
this.load_annotation(ann=>{
this.reapplyAnnotation(ann, done);
});
};
this.reapplyAnnotation = function(boxes, done){
// these boxes haven't attached a world
//boxes = this.transformBoxesByOffset(boxes);
// mark all old boxes
this.boxes.forEach(b=>{b.delete=true;});
let pendingBoxList=[];
boxes.forEach(nb=>{ // nb is annotation format, not a true box
let old_box = this.boxes.find(function(x){
return x.obj_track_id == nb.obj_id && x.obj_track_id != "" && nb.obj_id != "" && x.obj_type == nb.obj_type;;
});
if (old_box){
// found
// update psr
delete old_box.delete; // unmark delete flag
old_box.position.set(nb.psr.position.x, nb.psr.position.y, nb.psr.position.z);
old_box.scale.set(nb.psr.scale.x, nb.psr.scale.y, nb.psr.scale.z);
old_box.rotation.set(nb.psr.rotation.x, nb.psr.rotation.y, nb.psr.rotation.z);
old_box.obj_attr = nb.obj_attr;
old_box.annotator = nb.annotator;
old_box.changed=false; // clear changed flag.
}else{
// not found
let box=this.createOneBoxByAnn(nb);
pendingBoxList.push(box);
}
});
// delete removed
let toBeDelBoxes = this.boxes.filter(b=>b.delete);
toBeDelBoxes.forEach(b=>{
if (b.boxEditor){
b.boxEditor.detach("donthide");
}
this.webglGroup.remove(b);
this.remove_box(b);
})
pendingBoxList.forEach(b=>{
this.boxes.push(b);
})
//todo, restore point color
//todo, update imagecontext, selected box, ...
//refer to normal delete operation
// re-color again
this.world.lidar.recolor_all_points();
this.color_boxes();
// add new boxes
pendingBoxList.forEach(b=>{
this.webglGroup.add(b);
})
this.resetModified();
if (done)
done();
}
this.createOneBoxByAnn = function(annotation){
let b = annotation;
let mesh = this.createCuboid(b.psr.position,
b.psr.scale,
b.psr.rotation,
b.obj_type,
b.obj_id,
b.obj_attr);
if (b.annotator){
mesh.annotator = b.annotator;
}
if (b.follows)
mesh.follows = b.follows;
return mesh;
};
this.createBoxes = function(annotations){
return annotations.map((b)=>{
return this.createOneBoxByAnn(b);
});
};
this.box_local_id = 0;
this.get_new_box_local_id=function(){
var ret = this.box_local_id;
this.box_local_id+=1;
return ret;
};
this.color_box = function(box)
{
if (this.data.cfg.color_obj == "category" || this.data.cfg.color_obj == "no")
{
let color = globalObjectCategory.get_color_by_category(box.obj_type);
box.material.color.r=color.x;
box.material.color.g=color.y;
box.material.color.b=color.z;
}
else
{
let color = globalObjectCategory.get_color_by_id(box.obj_track_id);
box.material.color.r=color.x;
box.material.color.g=color.y;
box.material.color.b=color.z;
}
}
this.color_boxes = function()
{
this.boxes.forEach(box=>{
this.color_box(box);
})
}
}
export{Annotation}