3D annotation view significant performance update & refactoring (#5442)

<!-- Raised an issue to propose your change
(https://github.com/cvat-ai/cvat/issues).
It helps to avoid duplication of efforts from multiple independent
contributors.
Discuss your ideas with maintainers to be sure that changes will be
approved and merged.
Read the
[CONTRIBUTION](https://github.com/cvat-ai/cvat/blob/develop/CONTRIBUTING.md)
guide. -->

<!-- Provide a general summary of your changes in the Title above -->

### Motivation and context
<!-- Why is this change required? What problem does it solve? If it
fixes an open
issue, please link to the issue here. Describe your changes in detail,
add
screenshots. -->

### How has this been tested?
<!-- Please describe in detail how you tested your changes.
Include details of your testing environment, and the tests you ran to
see how your change affects other areas of the code, etc. -->

### Checklist
<!-- Go over all the following points, and put an `x` in all the boxes
that apply.
If an item isn't applicable by a reason then ~~explicitly
strikethrough~~ the whole
line. If you don't do that github will show an incorrect process for the
pull request.
If you're unsure about any of these, don't hesitate to ask. We're here
to help! -->
- [x] I submit my changes into the `develop` branch
- [ ] I have added a description of my changes into
[CHANGELOG](https://github.com/cvat-ai/cvat/blob/develop/CHANGELOG.md)
file
- [ ] I have updated the [documentation](
https://github.com/cvat-ai/cvat/blob/develop/README.md#documentation)
accordingly
- [ ] I have added tests to cover my changes
- [ ] I have linked related issues ([read github docs](

https://help.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword))
- [x] I have increased versions of npm packages if it is necessary
([cvat-canvas](https://github.com/cvat-ai/cvat/tree/develop/cvat-canvas#versioning),

[cvat-core](https://github.com/cvat-ai/cvat/tree/develop/cvat-core#versioning),
[cvat-data](https://github.com/cvat-ai/cvat/tree/develop/cvat-data#versioning)
and
[cvat-ui](https://github.com/cvat-ai/cvat/tree/develop/cvat-ui#versioning))

### License

- [x] I submit _my code changes_ under the same [MIT License](
https://github.com/cvat-ai/cvat/blob/develop/LICENSE) that covers the
project.
  Feel free to contact the maintainers if that's a concern.
main
Boris Sekachev 3 years ago committed by GitHub
parent ac78fab0b9
commit 6be7e4cceb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,6 +1,6 @@
{ {
"name": "cvat-canvas3d", "name": "cvat-canvas3d",
"version": "0.0.5", "version": "0.0.6",
"description": "Part of Computer Vision Annotation Tool which presents its canvas3D library", "description": "Part of Computer Vision Annotation Tool which presents its canvas3D library",
"main": "src/canvas3d.ts", "main": "src/canvas3d.ts",
"scripts": { "scripts": {
@ -17,6 +17,7 @@
], ],
"devDependencies": {}, "devDependencies": {},
"dependencies": { "dependencies": {
"cvat-core": "link:./../cvat-core",
"@types/three": "^0.125.3", "@types/three": "^0.125.3",
"camera-controls": "^1.25.3", "camera-controls": "^1.25.3",
"three": "^0.126.1" "three": "^0.126.1"

@ -3,6 +3,7 @@
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import { ObjectState } from '.';
import { import {
Canvas3dModel, Mode, DrawData, ActiveElement, GroupData, Configuration, Canvas3dModel, Mode, DrawData, ActiveElement, GroupData, Configuration,
} from './canvas3dModel'; } from './canvas3dModel';
@ -10,10 +11,10 @@ import {
export interface Canvas3dController { export interface Canvas3dController {
readonly drawData: DrawData; readonly drawData: DrawData;
readonly activeElement: ActiveElement; readonly activeElement: ActiveElement;
readonly selected: any;
readonly groupData: GroupData; readonly groupData: GroupData;
readonly configuration: Configuration; readonly configuration: Configuration;
readonly imageIsDeleted: boolean; readonly imageIsDeleted: boolean;
readonly objects: ObjectState[];
mode: Mode; mode: Mode;
group(groupData: GroupData): void; group(groupData: GroupData): void;
} }
@ -41,10 +42,6 @@ export class Canvas3dControllerImpl implements Canvas3dController {
return this.model.data.activeElement; return this.model.data.activeElement;
} }
public get selected(): any {
return this.model.data.selected;
}
public get imageIsDeleted(): any { public get imageIsDeleted(): any {
return this.model.imageIsDeleted; return this.model.imageIsDeleted;
} }
@ -57,6 +54,10 @@ export class Canvas3dControllerImpl implements Canvas3dController {
return this.model.configuration; return this.model.configuration;
} }
public get objects(): ObjectState[] {
return this.model.objects;
}
public group(groupData: GroupData): void { public group(groupData: GroupData): void {
this.model.group(groupData); this.model.group(groupData);
} }

@ -3,6 +3,7 @@
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import { ObjectState } from '.';
import { MasterImpl } from './master'; import { MasterImpl } from './master';
export interface Size { export interface Size {
@ -17,7 +18,7 @@ export interface ActiveElement {
export interface GroupData { export interface GroupData {
enabled: boolean; enabled: boolean;
grouped?: []; grouped: ObjectState[];
} }
export interface Configuration { export interface Configuration {
@ -76,24 +77,20 @@ export enum UpdateReasons {
DRAW = 'draw', DRAW = 'draw',
SELECT = 'select', SELECT = 'select',
CANCEL = 'cancel', CANCEL = 'cancel',
DATA_FAILED = 'data_failed',
DRAG_CANVAS = 'drag_canvas', DRAG_CANVAS = 'drag_canvas',
SHAPE_ACTIVATED = 'shape_activated', SHAPE_ACTIVATED = 'shape_activated',
GROUP = 'group', GROUP = 'group',
FITTED_CANVAS = 'fitted_canvas', FITTED_CANVAS = 'fitted_canvas',
CONFIG_UPDATED = 'config_updated', CONFIG_UPDATED = 'config_updated',
SHAPES_CONFIG_UPDATED = 'shapes_config_updated',
} }
export enum Mode { export enum Mode {
IDLE = 'idle', IDLE = 'idle',
DRAG = 'drag',
RESIZE = 'resize',
DRAW = 'draw', DRAW = 'draw',
EDIT = 'edit', EDIT = 'edit',
INTERACT = 'interact',
DRAG_CANVAS = 'drag_canvas', DRAG_CANVAS = 'drag_canvas',
GROUP = 'group', GROUP = 'group',
BUSY = 'busy',
} }
export interface Canvas3dDataModel { export interface Canvas3dDataModel {
@ -106,13 +103,15 @@ export interface Canvas3dDataModel {
imageIsDeleted: boolean; imageIsDeleted: boolean;
drawData: DrawData; drawData: DrawData;
mode: Mode; mode: Mode;
exception: Error | null; objects: ObjectState[];
objects: any[];
groupedObjects: any[];
selected: any;
shapeProperties: ShapeProperties; shapeProperties: ShapeProperties;
groupData: GroupData; groupData: GroupData;
configuration: Configuration; configuration: Configuration;
isFrameUpdating: boolean;
nextSetupRequest: {
frameData: any;
objectStates: ObjectState[];
} | null;
} }
export interface Canvas3dModel { export interface Canvas3dModel {
@ -121,17 +120,20 @@ export interface Canvas3dModel {
readonly imageIsDeleted: boolean; readonly imageIsDeleted: boolean;
readonly groupData: GroupData; readonly groupData: GroupData;
readonly configuration: Configuration; readonly configuration: Configuration;
setup(frameData: any, objectStates: any[]): void; readonly objects: ObjectState[];
setup(frameData: any, objectStates: ObjectState[]): void;
isAbleToChangeFrame(): boolean; isAbleToChangeFrame(): boolean;
draw(drawData: DrawData): void; draw(drawData: DrawData): void;
cancel(): void; cancel(): void;
dragCanvas(enable: boolean): void; dragCanvas(enable: boolean): void;
activate(clientID: string | null, attributeID: number | null): void; activate(clientID: string | null, attributeID: number | null): void;
configureShapes(shapeProperties: any): void; configureShapes(shapeProperties: ShapeProperties): void;
configure(configuration: Configuration): void; configure(configuration: Configuration): void;
fit(): void; fit(): void;
group(groupData: GroupData): void; group(groupData: GroupData): void;
destroy(): void; destroy(): void;
updateCanvasObjects(): void;
unlockFrameUpdating(): void;
} }
export class Canvas3dModelImpl extends MasterImpl implements Canvas3dModel { export class Canvas3dModelImpl extends MasterImpl implements Canvas3dModel {
@ -149,7 +151,6 @@ export class Canvas3dModelImpl extends MasterImpl implements Canvas3dModel {
width: 0, width: 0,
}, },
objects: [], objects: [],
groupedObjects: [],
image: null, image: null,
imageID: null, imageID: null,
imageOffset: 0, imageOffset: 0,
@ -163,12 +164,10 @@ export class Canvas3dModelImpl extends MasterImpl implements Canvas3dModel {
initialState: null, initialState: null,
}, },
mode: Mode.IDLE, mode: Mode.IDLE,
exception: null,
groupData: { groupData: {
enabled: false, enabled: false,
grouped: [], grouped: [],
}, },
selected: null,
shapeProperties: { shapeProperties: {
opacity: 40, opacity: 40,
outlined: false, outlined: false,
@ -179,16 +178,38 @@ export class Canvas3dModelImpl extends MasterImpl implements Canvas3dModel {
configuration: { configuration: {
resetZoom: false, resetZoom: false,
}, },
isFrameUpdating: false,
nextSetupRequest: null,
}; };
} }
public setup(frameData: any, objectStates: any[]): void { public updateCanvasObjects(): void {
this.notify(UpdateReasons.OBJECTS_UPDATED);
}
public unlockFrameUpdating(): void {
this.data.isFrameUpdating = false;
if (this.data.nextSetupRequest) {
try {
const { frameData, objectStates } = this.data.nextSetupRequest;
this.setup(frameData, objectStates);
} finally {
this.data.nextSetupRequest = null;
}
}
}
public setup(frameData: any, objectStates: ObjectState[]): void {
if (this.data.imageID !== frameData.number) { if (this.data.imageID !== frameData.number) {
if ([Mode.EDIT, Mode.DRAG, Mode.RESIZE].includes(this.data.mode)) { if ([Mode.EDIT].includes(this.data.mode)) {
throw Error(`Canvas is busy. Action: ${this.data.mode}`); throw Error(`Canvas is busy. Action: ${this.data.mode}`);
} }
} }
if ([Mode.EDIT, Mode.BUSY].includes(this.data.mode)) {
if (this.data.isFrameUpdating) {
this.data.nextSetupRequest = {
frameData, objectStates,
};
return; return;
} }
@ -198,6 +219,7 @@ export class Canvas3dModelImpl extends MasterImpl implements Canvas3dModel {
return; return;
} }
this.data.isFrameUpdating = true;
this.data.imageID = frameData.number; this.data.imageID = frameData.number;
frameData frameData
.data((): void => { .data((): void => {
@ -205,24 +227,17 @@ export class Canvas3dModelImpl extends MasterImpl implements Canvas3dModel {
this.notify(UpdateReasons.IMAGE_CHANGED); this.notify(UpdateReasons.IMAGE_CHANGED);
}) })
.then((data: Image): void => { .then((data: Image): void => {
if (frameData.number !== this.data.imageID) {
// already another image
return;
}
this.data.imageSize = { this.data.imageSize = {
height: frameData.height as number, height: frameData.height as number,
width: frameData.width as number, width: frameData.width as number,
}; };
this.data.imageIsDeleted = frameData.deleted; this.data.imageIsDeleted = frameData.deleted;
this.data.image = data; this.data.image = data;
this.notify(UpdateReasons.IMAGE_CHANGED);
this.data.objects = objectStates; this.data.objects = objectStates;
this.notify(UpdateReasons.OBJECTS_UPDATED); this.notify(UpdateReasons.IMAGE_CHANGED);
}) })
.catch((exception: any): void => { .catch((exception: any): void => {
this.data.exception = exception; this.data.isFrameUpdating = false;
this.notify(UpdateReasons.DATA_FAILED);
throw exception; throw exception;
}); });
} }
@ -235,9 +250,13 @@ export class Canvas3dModelImpl extends MasterImpl implements Canvas3dModel {
return this.data.mode; return this.data.mode;
} }
public get objects(): ObjectState[] {
return [...this.data.objects];
}
public isAbleToChangeFrame(): boolean { public isAbleToChangeFrame(): boolean {
const isUnable = [Mode.DRAG, Mode.EDIT, Mode.RESIZE, Mode.INTERACT, Mode.BUSY].includes(this.data.mode) || const isUnable = [Mode.EDIT].includes(this.data.mode) ||
(this.data.mode === Mode.DRAW && typeof this.data.drawData.redraw === 'number'); this.data.isFrameUpdating || (this.data.mode === Mode.DRAW && typeof this.data.drawData.redraw === 'number');
return !isUnable; return !isUnable;
} }
@ -288,11 +307,11 @@ export class Canvas3dModelImpl extends MasterImpl implements Canvas3dModel {
this.notify(UpdateReasons.DRAG_CANVAS); this.notify(UpdateReasons.DRAG_CANVAS);
} }
public activate(clientID: string, attributeID: number | null): void { public activate(clientID: string | null, attributeID: number | null): void {
if (this.data.activeElement.clientID === clientID && this.data.activeElement.attributeID === attributeID) { if (this.data.activeElement.clientID === clientID && this.data.activeElement.attributeID === attributeID) {
return; return;
} }
if (this.data.mode !== Mode.IDLE) { if (this.data.mode !== Mode.IDLE && clientID !== null) {
throw Error(`Canvas is busy. Action: ${this.data.mode}`); throw Error(`Canvas is busy. Action: ${this.data.mode}`);
} }
if (typeof clientID === 'number') { if (typeof clientID === 'number') {
@ -334,13 +353,27 @@ export class Canvas3dModelImpl extends MasterImpl implements Canvas3dModel {
} }
public configureShapes(shapeProperties: ShapeProperties): void { public configureShapes(shapeProperties: ShapeProperties): void {
this.data.drawData.enabled = false; if (typeof shapeProperties.opacity === 'number') {
this.data.mode = Mode.IDLE; this.data.shapeProperties.opacity = Math.max(0, Math.min(shapeProperties.opacity, 100));
this.cancel(); }
this.data.shapeProperties = {
...shapeProperties, if (typeof shapeProperties.selectedOpacity === 'number') {
}; this.data.shapeProperties.selectedOpacity = Math.max(0, Math.min(shapeProperties.selectedOpacity, 100));
this.notify(UpdateReasons.OBJECTS_UPDATED); }
if (['Label', 'Instance', 'Group'].includes(shapeProperties.colorBy)) {
this.data.shapeProperties.colorBy = shapeProperties.colorBy;
}
if (typeof shapeProperties.outlined === 'boolean') {
this.data.shapeProperties.outlined = shapeProperties.outlined;
}
if (typeof shapeProperties.outlineColor === 'string') {
this.data.shapeProperties.outlineColor = shapeProperties.outlineColor;
}
this.notify(UpdateReasons.SHAPES_CONFIG_UPDATED);
} }
public fit(): void { public fit(): void {

File diff suppressed because it is too large Load Diff

@ -9,15 +9,20 @@ const DOLLY_FACTOR = 5;
const MAX_DISTANCE = 100; const MAX_DISTANCE = 100;
const MIN_DISTANCE = 0.3; const MIN_DISTANCE = 0.3;
const ZOOM_FACTOR = 7; const ZOOM_FACTOR = 7;
const ROTATION_HELPER_OFFSET = 0.1; const ROTATION_HELPER_OFFSET = 0.75;
const CAMERA_REFERENCE = 'camRef'; const CAMERA_REFERENCE = 'camRef';
const CUBOID_EDGE_NAME = 'edges'; const CUBOID_EDGE_NAME = 'edges';
const ROTATION_HELPER = 'rotationHelper'; const ROTATION_HELPER_NAME = '2DRotationHelper';
const PLANE_ROTATION_HELPER = 'planeRotationHelper';
const RESIZE_HELPER_NAME = '2DResizeHelper';
const ROTATION_SPEED = 80; const ROTATION_SPEED = 80;
const FOV_DEFAULT = 1; const FOV_DEFAULT = 1;
const FOV_MAX = 2; const FOV_MAX = 2;
const FOV_MIN = 0; const FOV_MIN = 0;
const FOV_INC = 0.08; const FOV_INC = 0.08;
const DEFAULT_GROUP_COLOR = '#e0e0e0';
const DEFAULT_OUTLINE_COLOR = '#000000';
const GROUPING_COLOR = '#8b008b';
export default { export default {
BASE_GRID_WIDTH, BASE_GRID_WIDTH,
@ -29,10 +34,15 @@ export default {
ROTATION_HELPER_OFFSET, ROTATION_HELPER_OFFSET,
CAMERA_REFERENCE, CAMERA_REFERENCE,
CUBOID_EDGE_NAME, CUBOID_EDGE_NAME,
ROTATION_HELPER, ROTATION_HELPER_NAME,
PLANE_ROTATION_HELPER,
RESIZE_HELPER_NAME,
ROTATION_SPEED, ROTATION_SPEED,
FOV_DEFAULT, FOV_DEFAULT,
FOV_MAX, FOV_MAX,
FOV_MIN, FOV_MIN,
FOV_INC, FOV_INC,
DEFAULT_GROUP_COLOR,
DEFAULT_OUTLINE_COLOR,
GROUPING_COLOR,
}; };

@ -1,8 +1,9 @@
// Copyright (C) 2021-2022 Intel Corporation // Copyright (C) 2021-2022 Intel Corporation
// Copyright (C) 2022 CVAT.ai Corporation
// //
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
import * as THREE from 'three'; import * as THREE from 'three';
import { BufferGeometryUtils } from 'three/examples/jsm/utils/BufferGeometryUtils';
import { ViewType } from './canvas3dModel'; import { ViewType } from './canvas3dModel';
import constants from './consts'; import constants from './consts';
@ -10,11 +11,25 @@ export interface Indexable {
[key: string]: any; [key: string]: any;
} }
export function makeCornerPointsMatrix(x: number, y: number, z: number): number[][] {
return ([
[1 * x, 1 * y, 1 * z],
[1 * x, 1 * y, -1 * z],
[1 * x, -1 * y, 1 * z],
[1 * x, -1 * y, -1 * z],
[-1 * x, 1 * y, 1 * z],
[-1 * x, 1 * y, -1 * z],
[-1 * x, -1 * y, 1 * z],
[-1 * x, -1 * y, -1 * z],
]);
}
export class CuboidModel { export class CuboidModel {
public perspective: THREE.Mesh; public perspective: THREE.Mesh;
public top: THREE.Mesh; public top: THREE.Mesh;
public side: THREE.Mesh; public side: THREE.Mesh;
public front: THREE.Mesh; public front: THREE.Mesh;
public wireframe: THREE.LineSegments;
public constructor(outline: string, outlineColor: string) { public constructor(outline: string, outlineColor: string) {
const geometry = new THREE.BoxGeometry(1, 1, 1); const geometry = new THREE.BoxGeometry(1, 1, 1);
@ -26,24 +41,72 @@ export class CuboidModel {
}); });
this.perspective = new THREE.Mesh(geometry, material); this.perspective = new THREE.Mesh(geometry, material);
const geo = new THREE.EdgesGeometry(this.perspective.geometry); const geo = new THREE.EdgesGeometry(this.perspective.geometry);
const wireframe = new THREE.LineSegments( this.wireframe = new THREE.LineSegments(
geo, geo,
outline === 'line' outline === 'line' ? new THREE.LineBasicMaterial({ color: outlineColor, linewidth: 4 }) :
? new THREE.LineBasicMaterial({ color: outlineColor, linewidth: 4 }) new THREE.LineDashedMaterial({
: new THREE.LineDashedMaterial({
color: outlineColor, color: outlineColor,
dashSize: 0.05, dashSize: 0.05,
gapSize: 0.05, gapSize: 0.05,
}), }),
); );
wireframe.computeLineDistances(); this.wireframe.computeLineDistances();
wireframe.renderOrder = 1; this.wireframe.renderOrder = 1;
this.perspective.add(wireframe); this.perspective.add(this.wireframe);
this.top = new THREE.Mesh(geometry, material); this.top = new THREE.Mesh(geometry, material);
this.side = new THREE.Mesh(geometry, material); this.side = new THREE.Mesh(geometry, material);
this.front = new THREE.Mesh(geometry, material); this.front = new THREE.Mesh(geometry, material);
const planeTop = new THREE.Mesh(
new THREE.PlaneBufferGeometry(1, 1, 1, 1),
new THREE.MeshBasicMaterial({
color: 0xff0000,
visible: false,
}),
);
const planeSide = new THREE.Mesh(
new THREE.PlaneBufferGeometry(1, 1, 1, 1),
new THREE.MeshBasicMaterial({
color: 0xff0000,
visible: false,
}),
);
const planeFront = new THREE.Mesh(
new THREE.PlaneBufferGeometry(1, 1, 1, 1),
new THREE.MeshBasicMaterial({
color: 0xff0000,
visible: false,
}),
);
this.top.add(planeTop);
planeTop.rotation.set(0, 0, 0);
planeTop.position.set(0, 0, 0.5);
planeTop.name = constants.PLANE_ROTATION_HELPER;
this.side.add(planeSide);
planeSide.rotation.set(-Math.PI / 2, 0, Math.PI);
planeTop.position.set(0, 0.5, 0);
planeSide.name = constants.PLANE_ROTATION_HELPER;
this.front.add(planeFront);
planeFront.rotation.set(0, Math.PI / 2, 0);
planeTop.position.set(0.5, 0, 0);
planeFront.name = constants.PLANE_ROTATION_HELPER;
const cornerPoints = makeCornerPointsMatrix(0.5, 0.5, 0.5);
for (let i = 0; i < cornerPoints.length; i++) {
const point = new THREE.Vector3().fromArray(cornerPoints[i]);
const helper = new THREE.Mesh(new THREE.SphereGeometry(0.1));
helper.visible = false;
helper.name = `cuboidNodeHelper_${i}`;
this.perspective.add(helper);
helper.position.copy(point);
}
const camRotateHelper = new THREE.Object3D(); const camRotateHelper = new THREE.Object3D();
camRotateHelper.translateX(-2); camRotateHelper.translateX(-2);
camRotateHelper.name = 'camRefRot'; camRotateHelper.name = 'camRefRot';
@ -71,29 +134,25 @@ export class CuboidModel {
} }
public attachCameraReference(): void { public attachCameraReference(): void {
// Attach Cam Reference
const topCameraReference = new THREE.Object3D(); const topCameraReference = new THREE.Object3D();
topCameraReference.translateZ(2); topCameraReference.translateZ(2);
topCameraReference.name = constants.CAMERA_REFERENCE; topCameraReference.name = constants.CAMERA_REFERENCE;
this.top.add(topCameraReference); this.top.add(topCameraReference);
this.top.userData = { ...this.top.userData, camReference: topCameraReference };
const sideCameraReference = new THREE.Object3D(); const sideCameraReference = new THREE.Object3D();
sideCameraReference.translateY(2); sideCameraReference.translateY(2);
sideCameraReference.name = constants.CAMERA_REFERENCE; sideCameraReference.name = constants.CAMERA_REFERENCE;
this.side.add(sideCameraReference); this.side.add(sideCameraReference);
this.side.userData = { ...this.side.userData, camReference: sideCameraReference };
const frontCameraReference = new THREE.Object3D(); const frontCameraReference = new THREE.Object3D();
frontCameraReference.translateX(2); frontCameraReference.translateX(2);
frontCameraReference.name = constants.CAMERA_REFERENCE; frontCameraReference.name = constants.CAMERA_REFERENCE;
this.front.add(frontCameraReference); this.front.add(frontCameraReference);
this.front.userData = { ...this.front.userData, camReference: frontCameraReference };
} }
public getReferenceCoordinates(viewType: string): THREE.Vector3 { public getReferenceCoordinates(viewType: string): THREE.Vector3 {
const { elements } = (this as Indexable)[viewType].getObjectByName(constants.CAMERA_REFERENCE).matrixWorld; const camRef = (this as Indexable)[viewType].getObjectByName(constants.CAMERA_REFERENCE);
return new THREE.Vector3(elements[12], elements[13], elements[14]); return camRef.getWorldPosition(new THREE.Vector3());
} }
public setName(clientId: any): void { public setName(clientId: any): void {
@ -102,18 +161,17 @@ export class CuboidModel {
}); });
} }
public setOriginalColor(color: string): void {
[ViewType.PERSPECTIVE, ViewType.TOP, ViewType.SIDE, ViewType.FRONT].forEach((view): void => {
((this as Indexable)[view] as any).originalColor = color;
});
}
public setColor(color: string): void { public setColor(color: string): void {
this.setOutlineColor(color);
[ViewType.PERSPECTIVE, ViewType.TOP, ViewType.SIDE, ViewType.FRONT].forEach((view): void => { [ViewType.PERSPECTIVE, ViewType.TOP, ViewType.SIDE, ViewType.FRONT].forEach((view): void => {
((this as Indexable)[view].material as THREE.MeshBasicMaterial).color.set(color); ((this as Indexable)[view].material as THREE.MeshBasicMaterial).color.set(color);
}); });
} }
public setOutlineColor(color: string): void {
(this.wireframe.material as THREE.MeshBasicMaterial).color.set(color);
}
public setOpacity(opacity: number): void { public setOpacity(opacity: number): void {
[ViewType.PERSPECTIVE, ViewType.TOP, ViewType.SIDE, ViewType.FRONT].forEach((view): void => { [ViewType.PERSPECTIVE, ViewType.TOP, ViewType.SIDE, ViewType.FRONT].forEach((view): void => {
((this as Indexable)[view].material as THREE.MeshBasicMaterial).opacity = opacity / 100; ((this as Indexable)[view].material as THREE.MeshBasicMaterial).opacity = opacity / 100;
@ -121,69 +179,72 @@ export class CuboidModel {
} }
} }
export function setEdges(instance: THREE.Mesh): THREE.LineSegments { export function createCuboidEdges(instance: THREE.Mesh): THREE.LineSegments {
const edges = new THREE.EdgesGeometry(instance.geometry); const geometry = new THREE.EdgesGeometry(instance.geometry);
const line = new THREE.LineSegments(edges, new THREE.LineBasicMaterial({ color: '#ffffff', linewidth: 3 })); const edges = new THREE.LineSegments(geometry, new THREE.LineBasicMaterial({ color: '#ffffff', linewidth: 3 }));
line.name = constants.CUBOID_EDGE_NAME; edges.name = constants.CUBOID_EDGE_NAME;
instance.add(line); instance.add(edges);
return line; return edges;
} }
export function setTranslationHelper(instance: THREE.Mesh): void { export function removeCuboidEdges(instance: THREE.Mesh): void {
const edges = instance.getObjectByName(constants.CUBOID_EDGE_NAME);
instance.remove(edges);
}
export function createResizeHelper(instance: THREE.Mesh): void {
const sphereGeometry = new THREE.SphereGeometry(0.1); const sphereGeometry = new THREE.SphereGeometry(0.1);
const sphereMaterial = new THREE.MeshBasicMaterial({ color: '#ffffff', opacity: 1 }); const sphereMaterial = new THREE.MeshBasicMaterial({ color: '#ff0000', opacity: 1 });
instance.geometry.deleteAttribute('normal'); const cornerPoints = makeCornerPointsMatrix(0.5, 0.5, 0.5);
instance.geometry.deleteAttribute('uv');
// eslint-disable-next-line no-param-reassign for (let i = 0; i < cornerPoints.length; i++) {
instance.geometry = BufferGeometryUtils.mergeVertices(instance.geometry); const point = new THREE.Vector3().fromArray(cornerPoints[i]);
const vertices = []; const tmpSphere = new THREE.Mesh(new THREE.SphereGeometry(0.1));
const positionAttribute = instance.geometry.getAttribute('position'); instance.add(tmpSphere);
for (let i = 0; i < positionAttribute.count; i++) { tmpSphere.position.copy(point);
const vertex = new THREE.Vector3(); const globalPosition = tmpSphere.getWorldPosition(new THREE.Vector3());
vertex.fromBufferAttribute(positionAttribute, i); instance.remove(tmpSphere);
vertices.push(vertex);
} const helper = new THREE.Mesh(sphereGeometry.clone(), sphereMaterial.clone());
const helpers = []; helper.position.copy(globalPosition);
for (let i = 0; i < vertices.length; i++) { helper.name = `${constants.RESIZE_HELPER_NAME}_${i}`;
helpers[i] = new THREE.Mesh(sphereGeometry.clone(), sphereMaterial.clone()); instance.parent.add(helper);
helpers[i].position.set(vertices[i].x, vertices[i].y, vertices[i].z);
helpers[i].up.set(0, 0, 1);
helpers[i].name = 'resizeHelper';
instance.add(helpers[i]);
helpers[i].scale.set(1 / instance.scale.x, 1 / instance.scale.y, 1 / instance.scale.z);
} }
// eslint-disable-next-line no-param-reassign }
instance.userData = { ...instance.userData, resizeHelpers: helpers };
export function removeResizeHelper(instance: THREE.Mesh): void {
instance.parent.children.filter((child: THREE.Object3D) => child.name.startsWith(constants.RESIZE_HELPER_NAME))
.forEach((helper) => {
instance.parent.remove(helper);
});
} }
export function createRotationHelper(instance: THREE.Mesh, viewType: ViewType): void { export function createRotationHelper(instance: THREE.Mesh, viewType: ViewType): void {
const sphereGeometry = new THREE.SphereGeometry(0.1); if ([ViewType.TOP, ViewType.SIDE, ViewType.FRONT].includes(viewType)) {
const sphereMaterial = new THREE.MeshBasicMaterial({ color: '#ffffff', opacity: 1 }); // Create a temporary element to get correct position
const rotationHelper = new THREE.Mesh(sphereGeometry, sphereMaterial); const tmpSphere = new THREE.Mesh(new THREE.SphereGeometry(0.1));
rotationHelper.name = constants.ROTATION_HELPER; instance.add(tmpSphere);
switch (viewType) { if (viewType === ViewType.TOP) {
case ViewType.TOP: tmpSphere.translateY(constants.ROTATION_HELPER_OFFSET);
rotationHelper.position.set( } else {
(instance.geometry as THREE.BoxGeometry).parameters.height / 2 + constants.ROTATION_HELPER_OFFSET, tmpSphere.translateZ(constants.ROTATION_HELPER_OFFSET);
instance.position.y, }
instance.position.z, const globalPosition = tmpSphere.getWorldPosition(new THREE.Vector3());
); instance.remove(tmpSphere);
instance.add(rotationHelper.clone());
// eslint-disable-next-line no-param-reassign // Create rotation helper itself first
instance.userData = { ...instance.userData, rotationHelpers: rotationHelper.clone() }; const sphereGeometry = new THREE.SphereGeometry(0.1);
break; const sphereMaterial = new THREE.MeshBasicMaterial({ color: '#33b864', opacity: 1 });
case ViewType.SIDE: const rotationHelper = new THREE.Mesh(sphereGeometry, sphereMaterial);
case ViewType.FRONT: rotationHelper.name = constants.ROTATION_HELPER_NAME;
rotationHelper.position.set( instance.parent.add(rotationHelper);
instance.position.x, rotationHelper.position.copy(globalPosition);
instance.position.y, }
(instance.geometry as THREE.BoxGeometry).parameters.depth / 2 + constants.ROTATION_HELPER_OFFSET, }
);
instance.add(rotationHelper.clone()); export function removeRotationHelper(instance: THREE.Mesh): void {
// eslint-disable-next-line no-param-reassign const helper = instance.parent.getObjectByName(constants.ROTATION_HELPER_NAME);
instance.userData = { ...instance.userData, rotationHelpers: rotationHelper.clone() }; if (helper) {
break; instance.parent.remove(helper);
default:
break;
} }
} }

@ -0,0 +1,9 @@
// Copyright (C) 2022 CVAT.ai Corporation
//
// SPDX-License-Identifier: MIT
import ObjectState from 'cvat-core/src/object-state';
import { Label } from 'cvat-core/src/labels';
import { ShapeType } from 'cvat-core/src/enums';
export { ObjectState, Label, ShapeType };

@ -1496,13 +1496,13 @@ export function pasteShapeAsync(): ThunkAction {
if (initialState && canvasInstance) { if (initialState && canvasInstance) {
const activeControl = ShapeTypeToControl[initialState.shapeType as ShapeType] || ActiveControl.CURSOR; const activeControl = ShapeTypeToControl[initialState.shapeType as ShapeType] || ActiveControl.CURSOR;
canvasInstance.cancel();
dispatch({ dispatch({
type: AnnotationActionTypes.PASTE_SHAPE, type: AnnotationActionTypes.PASTE_SHAPE,
payload: { payload: {
activeControl, activeControl,
}, },
}); });
canvasInstance.cancel();
if (initialState.objectType === ObjectType.TAG) { if (initialState.objectType === ObjectType.TAG) {
const objectState = new cvat.classes.ObjectState({ const objectState = new cvat.classes.ObjectState({

@ -39,6 +39,7 @@ interface Props {
contextMenuVisibility: boolean; contextMenuVisibility: boolean;
activeLabelID: number; activeLabelID: number;
activeObjectType: ObjectType; activeObjectType: ObjectType;
activatedStateID: number | null;
onSetupCanvas: () => void; onSetupCanvas: () => void;
onGroupObjects: (enabled: boolean) => void; onGroupObjects: (enabled: boolean) => void;
onResetCanvas(): void; onResetCanvas(): void;
@ -182,6 +183,7 @@ const CanvasWrapperComponent = (props: Props): ReactElement => {
frame, frame,
jobInstance, jobInstance,
activeLabelID, activeLabelID,
activatedStateID,
resetZoom, resetZoom,
activeObjectType, activeObjectType,
onShapeDrawn, onShapeDrawn,
@ -336,6 +338,10 @@ const CanvasWrapperComponent = (props: Props): ReactElement => {
}; };
}, []); }, []);
useEffect(() => {
canvasInstance.activate(activatedStateID);
}, [activatedStateID]);
useEffect(() => { useEffect(() => {
canvasInstance.configure({ resetZoom }); canvasInstance.configure({ resetZoom });
}, [resetZoom]); }, [resetZoom]);
@ -522,16 +528,18 @@ const CanvasWrapperComponent = (props: Props): ReactElement => {
handle={<span className='cvat-resizable-handle-horizontal' />} handle={<span className='cvat-resizable-handle-horizontal' />}
onResize={(e: SyntheticEvent) => setViewSize({ type: ViewType.PERSPECTIVE, e })} onResize={(e: SyntheticEvent) => setViewSize({ type: ViewType.PERSPECTIVE, e })}
> >
{frameFetching ? ( <>
<svg id='cvat_canvas_loading_animation'> {frameFetching ? (
<circle id='cvat_canvas_loading_circle' r='30' cx='50%' cy='50%' /> <svg id='cvat_canvas_loading_animation'>
</svg> <circle id='cvat_canvas_loading_circle' r='30' cx='50%' cy='50%' />
) : null} </svg>
<div className='cvat-canvas3d-perspective' id='cvat-canvas3d-perspective'> ) : null}
<div className='cvat-canvas-container cvat-canvas-container-overflow' ref={perspectiveView} /> <div className='cvat-canvas3d-perspective' id='cvat-canvas3d-perspective'>
<ArrowGroup /> <div className='cvat-canvas-container cvat-canvas-container-overflow' ref={perspectiveView} />
<ControlGroup /> <ArrowGroup />
</div> <ControlGroup />
</div>
</>
</ResizableBox> </ResizableBox>
<div <div
className='cvat-canvas3d-orthographic-views' className='cvat-canvas3d-orthographic-views'

@ -44,6 +44,7 @@ interface StateToProps {
annotations: any[]; annotations: any[];
contextMenuVisibility: boolean; contextMenuVisibility: boolean;
activeLabelID: number; activeLabelID: number;
activatedStateID: number | null;
activeObjectType: ObjectType; activeObjectType: ObjectType;
workspace: Workspace; workspace: Workspace;
frame: number; frame: number;
@ -78,6 +79,7 @@ function mapStateToProps(state: CombinedState): StateToProps {
}, },
annotations: { annotations: {
states: annotations, states: annotations,
activatedStateID,
}, },
workspace, workspace,
}, },
@ -105,6 +107,7 @@ function mapStateToProps(state: CombinedState): StateToProps {
outlined, outlined,
outlineColor, outlineColor,
activeLabelID, activeLabelID,
activatedStateID,
activeObjectType, activeObjectType,
resetZoom, resetZoom,
workspace, workspace,

@ -225,7 +225,7 @@ class ObjectItemContainer extends React.PureComponent<Props> {
private activate = (activeElementID?: number): void => { private activate = (activeElementID?: number): void => {
const { const {
objectState, ready, activeControl, activateObject, canvasInstance, objectState, ready, activeControl, activateObject,
} = this.props; } = this.props;
if (ready && activeControl === ActiveControl.CURSOR) { if (ready && activeControl === ActiveControl.CURSOR) {
@ -233,9 +233,6 @@ class ObjectItemContainer extends React.PureComponent<Props> {
objectState.clientID, objectState.clientID,
(Number.isInteger(activeElementID) ? activeElementID : null) as number | null, (Number.isInteger(activeElementID) ? activeElementID : null) as number | null,
); );
if (canvasInstance instanceof Canvas3d) {
canvasInstance.activate(objectState.clientID);
}
} }
}; };

Loading…
Cancel
Save