From 514b0e8bb8c5d7136e609451787a20c961e38085 Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Tue, 31 Mar 2020 18:00:01 +0300 Subject: [PATCH 01/45] Added cuboid creation to canvas --- cvat-canvas/src/typescript/drawHandler.ts | 38 +++++++++++-- cvat-ui/src/actions/annotation-actions.ts | 6 ++ .../controls-side-bar/controls-side-bar.tsx | 9 ++- .../controls-side-bar/draw-cuboid-control.tsx | 57 +++++++++++++++++++ .../controls-side-bar/draw-shape-popover.tsx | 7 ++- cvat-ui/src/reducers/interfaces.ts | 2 + 6 files changed, 110 insertions(+), 9 deletions(-) create mode 100644 cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-cuboid-control.tsx diff --git a/cvat-canvas/src/typescript/drawHandler.ts b/cvat-canvas/src/typescript/drawHandler.ts index 4afe75ec259d..ac39a6770af3 100644 --- a/cvat-canvas/src/typescript/drawHandler.ts +++ b/cvat-canvas/src/typescript/drawHandler.ts @@ -227,14 +227,25 @@ export class DrawHandlerImpl implements DrawHandler { } private drawPolyshape(): void { - let size = this.drawData.numberOfPoints; - const sizeDecrement = function sizeDecrement(): void { + let size = this.drawData.shapeType === 'cuboid' ? 0 : this.drawData.numberOfPoints; + + const sizeDecrement = (): void => { if (!--size) { this.drawInstance.draw('done'); } - }.bind(this); + }; - if (this.drawData.numberOfPoints) { + const sizeCuboidIncrement = (): void => { + if (++size === 4) { + this.drawInstance.draw('done'); + } + }; + + if (this.drawData.shapeType === 'cuboid') { + this.drawInstance.on('drawstart', sizeCuboidIncrement); + this.drawInstance.on('drawpoint', sizeCuboidIncrement); + this.drawInstance.on('undopoint', (): number => size--); + } else if (this.drawData.numberOfPoints) { this.drawInstance.on('drawstart', sizeDecrement); this.drawInstance.on('drawpoint', sizeDecrement); this.drawInstance.on('undopoint', (): number => size++); @@ -242,7 +253,7 @@ export class DrawHandlerImpl implements DrawHandler { // Add ability to cancel the latest drawn point this.canvas.on('mousedown.draw', (e: MouseEvent): void => { - if (e.which === 3) { + if (e.button === 2) { e.stopPropagation(); e.preventDefault(); this.drawInstance.draw('undo'); @@ -316,6 +327,13 @@ export class DrawHandlerImpl implements DrawHandler { shapeType, points, }, Date.now() - this.startTimestamp); + // TODO: think about correct right condition for cuboids + } else if (shapeType === 'cuboid' + && points.length === 4 * 2) { + this.onDrawDone({ + shapeType, + points, + }, Date.now() - this.startTimestamp); } }); } @@ -349,6 +367,14 @@ export class DrawHandlerImpl implements DrawHandler { this.drawPolyshape(); } + private drawCuboid(): void { + this.drawInstance = (this.canvas as any).polyline() + .addClass('cvat_canvas_shape_drawing').attr({ + 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, + }); + this.drawPolyshape(); + } + private pastePolyshape(): void { this.drawInstance.on('done', (e: CustomEvent): void => { const targetPoints = this.drawInstance @@ -580,6 +606,8 @@ export class DrawHandlerImpl implements DrawHandler { this.drawPolyline(); } else if (this.drawData.shapeType === 'points') { this.drawPoints(); + } else if (this.drawData.shapeType === 'cuboid') { + this.drawCuboid(); } this.setupDrawEvents(); } diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index 46989db34630..16b9d03824ec 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -1055,6 +1055,8 @@ export function rememberObject( activeControl = ActiveControl.DRAW_POLYLINE; } else if (shapeType === ShapeType.POINTS) { activeControl = ActiveControl.DRAW_POINTS; + } else if (shapeType === ShapeType.CUBOID) { + activeControl = ActiveControl.DRAW_CUBOID; } return { @@ -1382,6 +1384,8 @@ export function pasteShapeAsync(): ThunkAction, {}, {}, AnyAction> activeControl = ActiveControl.DRAW_POLYGON; } else if (initialState.shapeType === ShapeType.POLYLINE) { activeControl = ActiveControl.DRAW_POLYLINE; + } else if (initialState.shapeType === ShapeType.CUBOID) { + activeControl = ActiveControl.DRAW_CUBOID; } dispatch({ @@ -1443,6 +1447,8 @@ export function repeatDrawShapeAsync(): ThunkAction, {}, {}, AnyAc activeControl = ActiveControl.DRAW_POLYGON; } else if (activeShapeType === ShapeType.POLYLINE) { activeControl = ActiveControl.DRAW_POLYLINE; + } else if (activeShapeType === ShapeType.CUBOID) { + activeControl = ActiveControl.DRAW_CUBOID; } dispatch({ diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx index 16063fbb86c8..334cd43f83e7 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/controls-side-bar.tsx @@ -18,6 +18,7 @@ import DrawRectangleControl from './draw-rectangle-control'; import DrawPolygonControl from './draw-polygon-control'; import DrawPolylineControl from './draw-polyline-control'; import DrawPointsControl from './draw-points-control'; +import DrawCuboidControl from './draw-cuboid-control'; import SetupTagControl from './setup-tag-control'; import MergeControl from './merge-control'; import GroupControl from './group-control'; @@ -80,7 +81,8 @@ export default function ControlsSideBarComponent(props: Props): JSX.Element { SWITCH_DRAW_MODE: (event: KeyboardEvent | undefined) => { preventDefault(event); const drawing = [ActiveControl.DRAW_POINTS, ActiveControl.DRAW_POLYGON, - ActiveControl.DRAW_POLYLINE, ActiveControl.DRAW_RECTANGLE].includes(activeControl); + ActiveControl.DRAW_POLYLINE, ActiveControl.DRAW_RECTANGLE, + ActiveControl.DRAW_CUBOID].includes(activeControl); if (!drawing) { canvasInstance.cancel(); @@ -177,7 +179,10 @@ export default function ControlsSideBarComponent(props: Props): JSX.Element { canvasInstance={canvasInstance} isDrawing={activeControl === ActiveControl.DRAW_POINTS} /> - + { + canvasInstance.draw({ enabled: false }); + }, + } : {}; + + return ( + + )} + > + + + ); +} + +export default React.memo(DrawPolygonControl); diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover.tsx index a32f3ba8e204..287f1ea794c8 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover.tsx @@ -79,7 +79,7 @@ function DrawShapePopoverComponent(props: Props): JSX.Element { { - shapeType === ShapeType.RECTANGLE ? ( + shapeType === ShapeType.RECTANGLE && ( <> @@ -109,7 +109,10 @@ function DrawShapePopoverComponent(props: Props): JSX.Element { - ) : ( + ) + } + { + shapeType !== ShapeType.RECTANGLE && shapeType !== ShapeType.CUBOID && ( Number of points: diff --git a/cvat-ui/src/reducers/interfaces.ts b/cvat-ui/src/reducers/interfaces.ts index c99be219f70b..3829d1ea8b7a 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -259,6 +259,7 @@ export enum ActiveControl { DRAW_POLYGON = 'draw_polygon', DRAW_POLYLINE = 'draw_polyline', DRAW_POINTS = 'draw_points', + DRAW_CUBOID = 'draw_cuboid', MERGE = 'merge', GROUP = 'group', SPLIT = 'split', @@ -270,6 +271,7 @@ export enum ShapeType { POLYGON = 'polygon', POLYLINE = 'polyline', POINTS = 'points', + CUBOID = 'cuboid', } export enum ObjectType { From ae79dc993cf5cf50196e6c4e3b6c6368d980bf9c Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Thu, 2 Apr 2020 14:25:11 +0300 Subject: [PATCH 02/45] wip --- cvat-canvas/src/typescript/drawHandler.ts | 188 +++++++++++++++++++++- cvat-core/src/annotations-collection.js | 4 + cvat-core/src/annotations-objects.js | 8 + cvat-core/src/enums.js | 2 + 4 files changed, 200 insertions(+), 2 deletions(-) diff --git a/cvat-canvas/src/typescript/drawHandler.ts b/cvat-canvas/src/typescript/drawHandler.ts index ac39a6770af3..a1b220c544fb 100644 --- a/cvat-canvas/src/typescript/drawHandler.ts +++ b/cvat-canvas/src/typescript/drawHandler.ts @@ -229,6 +229,8 @@ export class DrawHandlerImpl implements DrawHandler { private drawPolyshape(): void { let size = this.drawData.shapeType === 'cuboid' ? 0 : this.drawData.numberOfPoints; + if (this.drawData.shapeType === 'cuboid') this.addCrosshair(); + const sizeDecrement = (): void => { if (!--size) { this.drawInstance.draw('done'); @@ -327,12 +329,12 @@ export class DrawHandlerImpl implements DrawHandler { shapeType, points, }, Date.now() - this.startTimestamp); - // TODO: think about correct right condition for cuboids + // TODO: think about correct constraign for cuboids } else if (shapeType === 'cuboid' && points.length === 4 * 2) { this.onDrawDone({ shapeType, - points, + points: DrawHandlerImpl.cuboidPointsBy4Points(points), }, Date.now() - this.startTimestamp); } }); @@ -367,6 +369,188 @@ export class DrawHandlerImpl implements DrawHandler { this.drawPolyshape(); } + private static sortPointsClockwise(points: any[]): any[] { + points.sort((a, b): number => a.y - b.y); + // Get center y + const cy = (points[0].y + points[points.length - 1].y) / 2; + + // Sort from right to left + points.sort((a, b): number => b.x - a.x); + + // Get center x + const cx = (points[0].x + points[points.length - 1].x) / 2; + + // Center point + const center = { + x: cx, + y: cy, + }; + + // Starting angle used to reference other angles + let startAng: number | undefined; + points.forEach((point): void => { + let ang = Math.atan2(point.y - center.y, point.x - center.x); + if (!startAng) { + startAng = ang; + // ensure that all points are clockwise of the start point + } else if (ang < startAng) { + ang += Math.PI * 2; + } + // eslint-disable-next-line no-param-reassign + point.angle = ang; // add the angle to the point + }); + + // first sort clockwise + points.sort((a, b): number => a.angle - b.angle); + return points.reverse(); + } + + private static setupCuboidPoints(actualPoints: any[]): any[] { + let left; + let right; + let left2; + let right2; + let p1; + let p2; + let p3; + let p4; + + const height = Math.abs(actualPoints[0].x - actualPoints[1].x) + < Math.abs(actualPoints[1].x - actualPoints[2].x) + ? Math.abs(actualPoints[1].y - actualPoints[0].y) + : Math.abs(actualPoints[1].y - actualPoints[2].y); + + // seperate into left and right point + // we pick the first and third point because we know assume they will be on + // opposite corners + if (actualPoints[0].x < actualPoints[2].x) { + [left,, right] = actualPoints; + } else { + [right,, left] = actualPoints; + } + + // get other 2 points using the given height + if (left.y < right.y) { + left2 = { x: left.x, y: left.y + height }; + right2 = { x: right.x, y: right.y - height }; + } else { + left2 = { x: left.x, y: left.y - height }; + right2 = { x: right.x, y: right.y + height }; + } + + // get the vector for the last point relative to the previous point + const vec = { + x: actualPoints[3].x - actualPoints[2].x, + y: actualPoints[3].y - actualPoints[2].y, + }; + + if (left.y < left2.y) { + p1 = left; + p2 = left2; + } else { + p1 = left2; + p2 = left; + } + + if (right.y < right2.y) { + p3 = right; + p4 = right2; + } else { + p3 = right2; + p4 = right; + } + + const p5 = { x: p3.x + vec.x, y: p3.y + vec.y + 0.1 }; + const p6 = { x: p4.x + vec.x, y: p4.y + vec.y - 0.1 }; + + p1.y += 0.1; + return [p1, p2, p3, p4, p5, p6]; + } + + private static cuboidPointsBy4Points(points: any[]): any[] { + const actualPoints = []; + for (let i = 0; i < 4; i++) { + const [x, y] = points.slice(i * 2, i * 2 + 2); + actualPoints.push({ x, y }); + } + const unsortedPlanePoints = actualPoints.slice(0, 3); + function rotate(array: any[], times: number): void{ + let t = times; + while (t--) { + const temp = array.shift(); + array.push(temp); + } + } + + const plane2 = { + p1: actualPoints[0], + p2: actualPoints[0], + p3: actualPoints[0], + p4: actualPoints[0], + }; + + // completing the plane + const vector = { + x: actualPoints[2].x - actualPoints[1].x, + y: actualPoints[2].y - actualPoints[1].y, + }; + + // sorting the first plane + unsortedPlanePoints.push({ + x: actualPoints[0].x + vector.x, + y: actualPoints[0].y + vector.y, + }); + const sortedPlanePoints = DrawHandlerImpl.sortPointsClockwise(unsortedPlanePoints); + let leftIndex = 0; + for (let i = 0; i < 4; i++) { + leftIndex = sortedPlanePoints[i].x < sortedPlanePoints[leftIndex].x ? i : leftIndex; + } + rotate(sortedPlanePoints, leftIndex); + const plane1 = { + p1: sortedPlanePoints[0], + p2: sortedPlanePoints[1], + p3: sortedPlanePoints[2], + p4: sortedPlanePoints[3], + }; + + const vec = { + x: actualPoints[3].x - actualPoints[2].x, + y: actualPoints[3].y - actualPoints[2].y, + }; + // determine the orientation + const angle = Math.atan2(vec.y, vec.x); + + // making the other plane + plane2.p1 = { x: plane1.p1.x + vec.x, y: plane1.p1.y + vec.y }; + plane2.p2 = { x: plane1.p2.x + vec.x, y: plane1.p2.y + vec.y }; + plane2.p3 = { x: plane1.p3.x + vec.x, y: plane1.p3.y + vec.y }; + plane2.p4 = { x: plane1.p4.x + vec.x, y: plane1.p4.y + vec.y }; + + + let cuboidPoints; + // right + if (Math.abs(angle) < Math.PI / 2 - 0.1) { + return DrawHandlerImpl.setupCuboidPoints(actualPoints); + } + + // left + if (Math.abs(angle) > Math.PI / 2 + 0.1) { + return DrawHandlerImpl.setupCuboidPoints(actualPoints); + } + // down + if (angle > 0) { + cuboidPoints = [plane1.p1, plane2.p1, plane1.p2, plane2.p2, plane1.p3, plane2.p3]; + cuboidPoints[0].y += 0.1; + cuboidPoints[4].y += 0.1; + return [plane1.p1, plane2.p1, plane1.p2, plane2.p2, plane1.p3, plane2.p3]; + } + // up + cuboidPoints = [plane2.p1, plane1.p1, plane2.p2, plane1.p2, plane2.p3, plane1.p3]; + cuboidPoints[0].y += 0.1; + cuboidPoints[4].y += 0.1; + return cuboidPoints; + } + private drawCuboid(): void { this.drawInstance = (this.canvas as any).polyline() .addClass('cvat_canvas_shape_drawing').attr({ diff --git a/cvat-core/src/annotations-collection.js b/cvat-core/src/annotations-collection.js index e3ba2735bc96..4caef596ed27 100644 --- a/cvat-core/src/annotations-collection.js +++ b/cvat-core/src/annotations-collection.js @@ -13,6 +13,7 @@ PolygonShape, PolylineShape, PointsShape, + CuboidShape, RectangleTrack, PolygonTrack, PolylineTrack, @@ -58,6 +59,9 @@ case 'points': shapeModel = new PointsShape(shapeData, clientID, color, injection); break; + case 'cuboid': + shapeModel = new CuboidShape(shapeData, clientID, color, injection); + break; default: throw new DataError( `An unexpected type of shape "${type}"`, diff --git a/cvat-core/src/annotations-objects.js b/cvat-core/src/annotations-objects.js index 47c65fb9898d..b2b33fd3afd7 100644 --- a/cvat-core/src/annotations-objects.js +++ b/cvat-core/src/annotations-objects.js @@ -1351,6 +1351,13 @@ } } + class CuboidShape extends PolyShape { + constructor(data, clientID, color, injection) { + super(data, clientID, color, injection); + this.shapeType = ObjectShape.CUBOID; + } + } + class RectangleTrack extends Track { constructor(data, clientID, color, injection) { super(data, clientID, color, injection); @@ -1810,6 +1817,7 @@ PolygonShape, PolylineShape, PointsShape, + CuboidShape, RectangleTrack, PolygonTrack, PolylineTrack, diff --git a/cvat-core/src/enums.js b/cvat-core/src/enums.js index feef4825bdb0..545eb1222b27 100644 --- a/cvat-core/src/enums.js +++ b/cvat-core/src/enums.js @@ -93,6 +93,7 @@ * @property {string} POLYGON 'polygon' * @property {string} POLYLINE 'polyline' * @property {string} POINTS 'points' + * @property {string} CUBOID 'cuboid' * @readonly */ const ObjectShape = Object.freeze({ @@ -100,6 +101,7 @@ POLYGON: 'polygon', POLYLINE: 'polyline', POINTS: 'points', + CUBOID: 'cuboid', }); /** From fa236282cae777f27fb08380aab5e51e7685b6d1 Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Fri, 3 Apr 2020 18:42:01 +0300 Subject: [PATCH 03/45] Added cube icon --- cvat-ui/src/assets/cube-icon.svg | 1 + .../controls-side-bar/draw-cuboid-control.tsx | 4 ++-- cvat-ui/src/icons.tsx | 4 ++++ 3 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 cvat-ui/src/assets/cube-icon.svg diff --git a/cvat-ui/src/assets/cube-icon.svg b/cvat-ui/src/assets/cube-icon.svg new file mode 100644 index 000000000000..991c585f654a --- /dev/null +++ b/cvat-ui/src/assets/cube-icon.svg @@ -0,0 +1 @@ + diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-cuboid-control.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-cuboid-control.tsx index 363f328528f4..351ed192b7c5 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-cuboid-control.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-cuboid-control.tsx @@ -9,7 +9,7 @@ import Icon from 'antd/lib/icon'; import { Canvas } from 'cvat-canvas'; import { ShapeType } from 'reducers/interfaces'; -import { PolygonIcon } from 'icons'; +import { CubeIcon } from 'icons'; import DrawShapePopoverContainer from 'containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover'; @@ -48,7 +48,7 @@ function DrawPolygonControl(props: Props): JSX.Element { > ); diff --git a/cvat-ui/src/icons.tsx b/cvat-ui/src/icons.tsx index c7cb9fb3d7af..8df3ebd45a73 100644 --- a/cvat-ui/src/icons.tsx +++ b/cvat-ui/src/icons.tsx @@ -39,6 +39,7 @@ import SVGObjectOutsideIcon from './assets/object-outside-icon.svg'; import SVGObjectInsideIcon from './assets/object-inside-icon.svg'; import SVGBackgroundIcon from './assets/background-icon.svg'; import SVGForegroundIcon from './assets/foreground-icon.svg'; +import SVGCubeIcon from './assets/cube-icon.svg'; export const CVATLogo = React.memo( (): JSX.Element => , @@ -145,3 +146,6 @@ export const BackgroundIcon = React.memo( export const ForegroundIcon = React.memo( (): JSX.Element => , ); +export const CubeIcon = React.memo( + (): JSX.Element => , +); From da987d39a5f460fc1e78e441563eedca5e288f55 Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Tue, 7 Apr 2020 09:40:47 +0300 Subject: [PATCH 04/45] added cuboid core model --- cvat-canvas/src/typescript/canvasView.ts | 9 + cvat-canvas/src/typescript/drawHandler.ts | 40 +-- cvat-canvas/src/typescript/shared.ts | 303 ++++++++++++++++++++++ cvat-canvas/src/typescript/svg.patch.ts | 300 +++++++++++++++++++++ cvat-core/src/annotations-collection.js | 4 + cvat-core/src/annotations-objects.js | 118 +++++++++ cvat-core/src/statistics.js | 8 + cvat-ui/src/actions/annotation-actions.ts | 4 +- 8 files changed, 770 insertions(+), 16 deletions(-) diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index 27160436bf5d..464f2d742b90 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -1099,6 +1099,9 @@ export class CanvasViewImpl implements CanvasView, Listener { } else if (state.shapeType === 'points') { this.svgShapes[state.clientID] = this .addPoints(stringified, state); + } else if (state.shapeType === 'cuboid') { + this.svgShapes[state.clientID] = this + .addCuboid(stringified, state); } } @@ -1530,6 +1533,12 @@ export class CanvasViewImpl implements CanvasView, Listener { return polyline; } + private addCuboid(points: string, state: any): SVG.PolyLine { + const cube = this.adoptedContent.cube(points).addClass('cvat_canvas_shape') + + return cube; + } + private setupPoints(basicPolyline: SVG.PolyLine, state: any): any { this.selectize(true, basicPolyline); diff --git a/cvat-canvas/src/typescript/drawHandler.ts b/cvat-canvas/src/typescript/drawHandler.ts index 0b72d8a0926f..c30d85326ef1 100644 --- a/cvat-canvas/src/typescript/drawHandler.ts +++ b/cvat-canvas/src/typescript/drawHandler.ts @@ -469,9 +469,11 @@ export class DrawHandlerImpl implements DrawHandler { const p5 = { x: p3.x + vec.x, y: p3.y + vec.y + 0.1 }; const p6 = { x: p4.x + vec.x, y: p4.y + vec.y - 0.1 }; + const p7 = { x: p1.x + vec.x, y: p1.y + vec.y + 0.1 }; + const p8 = { x: p2.x + vec.x, y: p2.y + vec.y - 0.1 }; p1.y += 0.1; - return [p1, p2, p3, p4, p5, p6]; + return [p1, p2, p3, p4, p5, p6, p7, p8]; } private static cuboidPointsBy4Points(points: any[]): any[] { @@ -537,25 +539,33 @@ export class DrawHandlerImpl implements DrawHandler { let cuboidPoints; // right if (Math.abs(angle) < Math.PI / 2 - 0.1) { - return DrawHandlerImpl.setupCuboidPoints(actualPoints); - } - + cuboidPoints = DrawHandlerImpl.setupCuboidPoints(actualPoints); // left - if (Math.abs(angle) > Math.PI / 2 + 0.1) { - return DrawHandlerImpl.setupCuboidPoints(actualPoints); - } + } else if (Math.abs(angle) > Math.PI / 2 + 0.1) { + cuboidPoints = DrawHandlerImpl.setupCuboidPoints(actualPoints); // down - if (angle > 0) { - cuboidPoints = [plane1.p1, plane2.p1, plane1.p2, plane2.p2, plane1.p3, plane2.p3]; + } else if (angle > 0) { + cuboidPoints = [ + plane1.p1, plane2.p1, plane1.p2, plane2.p2, + plane1.p3, plane2.p3, plane1.p4, plane2.p4, + ]; cuboidPoints[0].y += 0.1; cuboidPoints[4].y += 0.1; - return [plane1.p1, plane2.p1, plane1.p2, plane2.p2, plane1.p3, plane2.p3]; - } // up - cuboidPoints = [plane2.p1, plane1.p1, plane2.p2, plane1.p2, plane2.p3, plane1.p3]; - cuboidPoints[0].y += 0.1; - cuboidPoints[4].y += 0.1; - return cuboidPoints; + } else { + cuboidPoints = [ + plane2.p1, plane1.p1, plane2.p2, plane1.p2, + plane2.p3, plane1.p3, plane2.p4, plane1.p4, + ]; + cuboidPoints[0].y += 0.1; + cuboidPoints[4].y += 0.1; + } + + return cuboidPoints.reduce((arr: number[], point: any): number[] => { + arr.push(point.x); + arr.push(point.y); + return arr; + }, []); } private drawCuboid(): void { diff --git a/cvat-canvas/src/typescript/shared.ts b/cvat-canvas/src/typescript/shared.ts index 2a1136ca4aab..93a76430f622 100644 --- a/cvat-canvas/src/typescript/shared.ts +++ b/cvat-canvas/src/typescript/shared.ts @@ -100,3 +100,306 @@ export function displayShapeSize( return shapeSize; } + + + + +export const Cube = SVG.invent({ + create: 'g', + inherit: SVG.G, + extend: { + + constructorMethod(viewModel): any { + this.attr('points', viewModel.getPoints()); + this.projectionLineEnable = false; + this.setupFaces(viewModel); + this.setupEdges(viewModel); + this.setupProjections(viewModel); + this.setupGrabPoints(); + this.hideProjections(); + this.hideGrabPoints(); + + return this; + }, + + setupFaces(viewModel): void { + this.face = this.polygon(viewModel.front.canvasPoints); + this.right = this.polygon(viewModel.right.canvasPoints); + this.dorsal = this.polygon(viewModel.dorsal.canvasPoints); + this.left = this.polygon(viewModel.left.canvasPoints); + }, + + setupProjections(viewModel): void { + this.ftProj = this.line(this.updateProjectionLine(viewModel.ft.getEquation(), + viewModel.ft.canvasPoints[0], viewModel.vplCanvas)); + this.fbProj = this.line(this.updateProjectionLine(viewModel.fb.getEquation(), + viewModel.ft.canvasPoints[0], viewModel.vplCanvas)); + this.rtProj = this.line(this.updateProjectionLine(viewModel.rt.getEquation(), + viewModel.rt.canvasPoints[1], viewModel.vprCanvas)); + this.rbProj = this.line(this.updateProjectionLine(viewModel.rb.getEquation(), + viewModel.rb.canvasPoints[1], viewModel.vprCanvas)); + + this.ftProj.stroke({ color: '#C0C0C0' }); + this.fbProj.stroke({ color: '#C0C0C0' }); + this.rtProj.stroke({ color: '#C0C0C0' }); + this.rbProj.stroke({ color: '#C0C0C0' }); + }, + + setupEdges(viewModel): void { + this.frontLeftEdge = this.line(viewModel.fl.canvasPoints); + this.frontRightEdge = this.line(viewModel.fr.canvasPoints); + this.dorsalRightEdge = this.line(viewModel.dr.canvasPoints); + this.dorsalLeftEdge = this.line(viewModel.dl.canvasPoints); + + this.frontTopEdge = this.line(viewModel.ft.canvasPoints); + this.rightTopEdge = this.line(viewModel.rt.canvasPoints); + this.frontBotEdge = this.line(viewModel.fb.canvasPoints); + this.rightBotEdge = this.line(viewModel.rb.canvasPoints); + }, + + setupGrabPoints(): void { + this.flCenter = this.circle().addClass('svg_select_points').addClass('svg_select_points_l'); + this.frCenter = this.circle().addClass('svg_select_points').addClass('svg_select_points_r'); + this.drCenter = this.circle().addClass('svg_select_points').addClass('svg_select_points_ew'); + this.dlCenter = this.circle().addClass('svg_select_points').addClass('svg_select_points_ew'); + + this.ftCenter = this.circle().addClass('svg_select_points').addClass('svg_select_points_t'); + this.fbCenter = this.circle().addClass('svg_select_points').addClass('svg_select_points_b'); + + const grabPoints = this.getGrabPoints(); + const edges = this.getEdges(); + for (let i = 0; i < grabPoints.length; i += 1) { + const edge = edges[`${i}`]; + const cx = (edge.attr('x2') + edge.attr('x1')) / 2; + const cy = (edge.attr('y2') + edge.attr('y1')) / 2; + grabPoints[`${i}`].center(cx, cy); + } + }, + + updateGrabPoints(): void { + const centers = this.getGrabPoints(); + const edges = this.getEdges(); + for (let i = 0; i < centers.length; i += 1) { + const edge = edges[`${i}`]; + centers[`${i}`].center(edge.cx(), edge.cy()); + } + }, + + move(dx, dy): void { + this.face.dmove(dx, dy); + this.dorsal.dmove(dx, dy); + this.right.dmove(dx, dy); + this.left.dmove(dx, dy); + + const edges = this.getEdges(); + edges.forEach((edge): void => { + edge.dmove(dx, dy); + }); + }, + + showProjections(): void { + if (this.projectionLineEnable) { + this.ftProj.show(); + this.fbProj.show(); + this.rtProj.show(); + this.rbProj.show(); + } + }, + + hideProjections(): void { + this.ftProj.hide(); + this.fbProj.hide(); + this.rtProj.hide(); + this.rbProj.hide(); + }, + + showGrabPoints(): void { + const grabPoints = this.getGrabPoints(); + grabPoints.forEach((point): void => { + point.show(); + }); + }, + + hideGrabPoints(): void { + const grabPoints = this.getGrabPoints(); + grabPoints.forEach((point): void => { + point.hide(); + }); + }, + + updateView(viewModel): void { + const convertedPoints = window.cvat.translate.points.actualToCanvas( + viewModel.getPoints(), + ); + this.updatePolygons(viewModel); + this.updateLines(viewModel); + this.updateProjections(viewModel); + this.updateGrabPoints(); + this.attr('points', convertedPoints); + }, + + updatePolygons(viewModel): void { + this.face.plot(viewModel.front.canvasPoints); + this.right.plot(viewModel.right.canvasPoints); + this.dorsal.plot(viewModel.dorsal.canvasPoints); + this.left.plot(viewModel.left.canvasPoints); + }, + + updateLines(viewModel): void { + this.frontLeftEdge.plot(viewModel.fl.canvasPoints); + this.frontRightEdge.plot(viewModel.fr.canvasPoints); + this.dorsalRightEdge.plot(viewModel.dr.canvasPoints); + this.dorsalLeftEdge.plot(viewModel.dl.canvasPoints); + + this.frontTopEdge.plot(viewModel.ft.canvasPoints); + this.rightTopEdge.plot(viewModel.rt.canvasPoints); + this.frontBotEdge.plot(viewModel.fb.canvasPoints); + this.rightBotEdge.plot(viewModel.rb.canvasPoints); + }, + + updateThickness(): void { + const edges = this.getEdges(); + const width = this.attr('stroke-width'); + const baseWidthOffset = 1.75; + const expandedWidthOffset = 3; + edges.forEach((edge): void => { + edge.on('mouseover', function (): void { + this.attr({ 'stroke-width': width * expandedWidthOffset }); + }).on('mouseout', function (): void { + this.attr({ 'stroke-width': width * baseWidthOffset }); + }).stroke({ width: width * baseWidthOffset, linecap: 'round' }); + }); + }, + + updateProjections(viewModel): void { + this.ftProj.plot(this.updateProjectionLine(viewModel.ft.getEquation(), + viewModel.ft.canvasPoints[0], viewModel.vplCanvas)); + this.fbProj.plot(this.updateProjectionLine(viewModel.fb.getEquation(), + viewModel.ft.canvasPoints[0], viewModel.vplCanvas)); + this.rtProj.plot(this.updateProjectionLine(viewModel.rt.getEquation(), + viewModel.rt.canvasPoints[1], viewModel.vprCanvas)); + this.rbProj.plot(this.updateProjectionLine(viewModel.rb.getEquation(), + viewModel.rt.canvasPoints[1], viewModel.vprCanvas)); + }, + + paintOrientationLines(): void { + const fillColor = this.attr('fill'); + const selectedColor = '#ff007f'; + this.frontTopEdge.stroke({ color: selectedColor }); + this.frontLeftEdge.stroke({ color: selectedColor }); + this.frontBotEdge.stroke({ color: selectedColor }); + this.frontRightEdge.stroke({ color: selectedColor }); + + this.rightTopEdge.stroke({ color: fillColor }); + this.rightBotEdge.stroke({ color: fillColor }); + this.dorsalRightEdge.stroke({ color: fillColor }); + this.dorsalLeftEdge.stroke({ color: fillColor }); + + this.face.stroke({ color: fillColor, width: 0 }); + this.right.stroke({ color: fillColor }); + this.dorsal.stroke({ color: fillColor }); + this.left.stroke({ color: fillColor }); + }, + + getEdges(): any[] { + const arr = []; + arr.push(this.frontLeftEdge); + arr.push(this.frontRightEdge); + arr.push(this.dorsalRightEdge); + arr.push(this.frontTopEdge); + arr.push(this.frontBotEdge); + arr.push(this.dorsalLeftEdge); + arr.push(this.rightTopEdge); + arr.push(this.rightBotEdge); + return arr; + }, + + getGrabPoints(): any[] { + const arr = []; + arr.push(this.flCenter); + arr.push(this.frCenter); + arr.push(this.drCenter); + arr.push(this.ftCenter); + arr.push(this.fbCenter); + arr.push(this.dlCenter); + return arr; + }, + + updateProjectionLine(equation, source, direction): any[] { + const x1 = source.x; + const y1 = equation.getYCanvas(x1); + + const x2 = direction.x; + const y2 = equation.getYCanvas(x2); + return [[x1, y1], [x2, y2]]; + }, + + addMouseOverEvents(): void { + this._addFaceEvents(); + }, + + addFaceEvents() { + const group = this; + this.left.on('mouseover', function () { + this.attr({ 'fill-opacity': 0.5 }); + }).on('mouseout', function () { + this.attr({ 'fill-opacity': group.attr('fill-opacity') }); + }); + this.dorsal.on('mouseover', function () { + this.attr({ 'fill-opacity': 0.5 }); + }).on('mouseout', function () { + this.attr({ 'fill-opacity': group.attr('fill-opacity') }); + }); + this.right.on('mouseover', function () { + this.attr({ 'fill-opacity': 0.5 }); + }).on('mouseout', function () { + this.attr({ 'fill-opacity': group.attr('fill-opacity') }); + }); + }, + + removeMouseOverEvents() { + const edges = this.getEdges(); + edges.forEach((edge) => { + edge.off('mouseover').off('mouseout'); + }); + this.left.off('mouseover').off('mouseout'); + this.dorsal.off('mouseover').off('mouseout'); + this.right.off('mouseover').off('mouseout'); + }, + + resetFaceOpacity() { + const group = this; + this.left.attr({ 'fill-opacity': group.attr('fill-opacity') }); + this.dorsal.attr({ 'fill-opacity': group.attr('fill-opacity') }); + this.right.attr({ 'fill-opacity': group.attr('fill-opacity') }); + }, + + addOccluded() { + const edges = this.getEdges(); + edges.forEach((edge) => { + edge.node.classList.add('occludedShape'); + }); + this.face.attr('stroke-width', 0); + this.right.attr('stroke-width', 0); + this.left.node.classList.add('occludedShape'); + this.dorsal.node.classList.add('occludedShape'); + }, + + removeOccluded() { + const edges = this.getEdges(); + edges.forEach((edge) => { + edge.node.classList.remove('occludedShape'); + }); + this.face.attr('stroke-width', this.attr('stroke-width')); + this.right.attr('stroke-width', this.attr('stroke-width')); + this.left.node.classList.remove('occludedShape'); + this.dorsal.node.classList.remove('occludedShape'); + }, + }, + construct: { + cube(points) { + return this.put(new SVG.Cube()).constructorMethod(points); + }, + }, +}); + diff --git a/cvat-canvas/src/typescript/svg.patch.ts b/cvat-canvas/src/typescript/svg.patch.ts index 1c532105c797..9f73de1e4ed8 100644 --- a/cvat-canvas/src/typescript/svg.patch.ts +++ b/cvat-canvas/src/typescript/svg.patch.ts @@ -179,3 +179,303 @@ SVG.Element.prototype.resize = function constructor(...args: any): any { for (const key of Object.keys(originalResize)) { SVG.Element.prototype.resize[key] = originalResize[key]; } + + +SVG.Cube = SVG.invent({ + create: 'g', + inherit: SVG.G, + extend: { + + constructorMethod(viewModel) { + this.attr('points', viewModel.getPoints()); + this.projectionLineEnable = false; + this.setupFaces(viewModel); + this.setupEdges(viewModel); + this.setupProjections(viewModel); + this.setupGrabPoints(); + this.hideProjections(); + this.hideGrabPoints(); + + return this; + }, + + setupFaces(viewModel) { + this.face = this.polygon(viewModel.front.canvasPoints); + this.right = this.polygon(viewModel.right.canvasPoints); + this.dorsal = this.polygon(viewModel.dorsal.canvasPoints); + this.left = this.polygon(viewModel.left.canvasPoints); + }, + + setupProjections(viewModel) { + this.ftProj = this.line(this.updateProjectionLine(viewModel.ft.getEquation(), + viewModel.ft.canvasPoints[0], viewModel.vplCanvas)); + this.fbProj = this.line(this.updateProjectionLine(viewModel.fb.getEquation(), + viewModel.ft.canvasPoints[0], viewModel.vplCanvas)); + this.rtProj = this.line(this.updateProjectionLine(viewModel.rt.getEquation(), + viewModel.rt.canvasPoints[1], viewModel.vprCanvas)); + this.rbProj = this.line(this.updateProjectionLine(viewModel.rb.getEquation(), + viewModel.rb.canvasPoints[1], viewModel.vprCanvas)); + + this.ftProj.stroke({ color: '#C0C0C0' }); + this.fbProj.stroke({ color: '#C0C0C0' }); + this.rtProj.stroke({ color: '#C0C0C0' }); + this.rbProj.stroke({ color: '#C0C0C0' }); + }, + + setupEdges(viewModel) { + this.frontLeftEdge = this.line(viewModel.fl.canvasPoints); + this.frontRightEdge = this.line(viewModel.fr.canvasPoints); + this.dorsalRightEdge = this.line(viewModel.dr.canvasPoints); + this.dorsalLeftEdge = this.line(viewModel.dl.canvasPoints); + + this.frontTopEdge = this.line(viewModel.ft.canvasPoints); + this.rightTopEdge = this.line(viewModel.rt.canvasPoints); + this.frontBotEdge = this.line(viewModel.fb.canvasPoints); + this.rightBotEdge = this.line(viewModel.rb.canvasPoints); + }, + + setupGrabPoints() { + this.flCenter = this.circle().addClass('svg_select_points').addClass('svg_select_points_l'); + this.frCenter = this.circle().addClass('svg_select_points').addClass('svg_select_points_r'); + this.drCenter = this.circle().addClass('svg_select_points').addClass('svg_select_points_ew'); + this.dlCenter = this.circle().addClass('svg_select_points').addClass('svg_select_points_ew'); + + this.ftCenter = this.circle().addClass('svg_select_points').addClass('svg_select_points_t'); + this.fbCenter = this.circle().addClass('svg_select_points').addClass('svg_select_points_b'); + + const grabPoints = this.getGrabPoints(); + const edges = this.getEdges(); + for (let i = 0; i < grabPoints.length; i += 1) { + const edge = edges[`${i}`]; + const cx = (edge.attr('x2') + edge.attr('x1')) / 2; + const cy = (edge.attr('y2') + edge.attr('y1')) / 2; + grabPoints[`${i}`].center(cx, cy); + } + }, + + updateGrabPoints() { + const centers = this.getGrabPoints(); + const edges = this.getEdges(); + for (let i = 0; i < centers.length; i += 1) { + const edge = edges[`${i}`]; + centers[`${i}`].center(edge.cx(), edge.cy()); + } + }, + + move(dx, dy) { + this.face.dmove(dx, dy); + this.dorsal.dmove(dx, dy); + this.right.dmove(dx, dy); + this.left.dmove(dx, dy); + + const edges = this.getEdges(); + edges.forEach((edge) => { + edge.dmove(dx, dy); + }); + }, + + showProjections() { + if (this.projectionLineEnable) { + this.ftProj.show(); + this.fbProj.show(); + this.rtProj.show(); + this.rbProj.show(); + } + }, + + hideProjections() { + this.ftProj.hide(); + this.fbProj.hide(); + this.rtProj.hide(); + this.rbProj.hide(); + }, + + showGrabPoints() { + const grabPoints = this.getGrabPoints(); + grabPoints.forEach((point) => { + point.show(); + }); + }, + + hideGrabPoints() { + const grabPoints = this.getGrabPoints(); + grabPoints.forEach((point) => { + point.hide(); + }); + }, + + updateView(viewModel) { + const convertedPoints = window.cvat.translate.points.actualToCanvas( + viewModel.getPoints(), + ); + this.updatePolygons(viewModel); + this.updateLines(viewModel); + this.updateProjections(viewModel); + this.updateGrabPoints(); + this.attr('points', convertedPoints); + }, + + updatePolygons(viewModel) { + this.face.plot(viewModel.front.canvasPoints); + this.right.plot(viewModel.right.canvasPoints); + this.dorsal.plot(viewModel.dorsal.canvasPoints); + this.left.plot(viewModel.left.canvasPoints); + }, + + updateLines(viewModel) { + this.frontLeftEdge.plot(viewModel.fl.canvasPoints); + this.frontRightEdge.plot(viewModel.fr.canvasPoints); + this.dorsalRightEdge.plot(viewModel.dr.canvasPoints); + this.dorsalLeftEdge.plot(viewModel.dl.canvasPoints); + + this.frontTopEdge.plot(viewModel.ft.canvasPoints); + this.rightTopEdge.plot(viewModel.rt.canvasPoints); + this.frontBotEdge.plot(viewModel.fb.canvasPoints); + this.rightBotEdge.plot(viewModel.rb.canvasPoints); + }, + + updateThickness() { + const edges = this.getEdges(); + const width = this.attr('stroke-width'); + const baseWidthOffset = 1.75; + const expandedWidthOffset = 3; + edges.forEach((edge) => { + edge.on('mouseover', function () { + this.attr({ 'stroke-width': width * expandedWidthOffset }); + }).on('mouseout', function () { + this.attr({ 'stroke-width': width * baseWidthOffset }); + }).stroke({ width: width * baseWidthOffset, linecap: 'round' }); + }); + }, + + updateProjections(viewModel) { + this.ftProj.plot(this.updateProjectionLine(viewModel.ft.getEquation(), + viewModel.ft.canvasPoints[0], viewModel.vplCanvas)); + this.fbProj.plot(this.updateProjectionLine(viewModel.fb.getEquation(), + viewModel.ft.canvasPoints[0], viewModel.vplCanvas)); + this.rtProj.plot(this.updateProjectionLine(viewModel.rt.getEquation(), + viewModel.rt.canvasPoints[1], viewModel.vprCanvas)); + this.rbProj.plot(this.updateProjectionLine(viewModel.rb.getEquation(), + viewModel.rt.canvasPoints[1], viewModel.vprCanvas)); + }, + + paintOrientationLines() { + const fillColor = this.attr('fill'); + const selectedColor = '#ff007f'; + this.frontTopEdge.stroke({ color: selectedColor }); + this.frontLeftEdge.stroke({ color: selectedColor }); + this.frontBotEdge.stroke({ color: selectedColor }); + this.frontRightEdge.stroke({ color: selectedColor }); + + this.rightTopEdge.stroke({ color: fillColor }); + this.rightBotEdge.stroke({ color: fillColor }); + this.dorsalRightEdge.stroke({ color: fillColor }); + this.dorsalLeftEdge.stroke({ color: fillColor }); + + this.face.stroke({ color: fillColor, width: 0 }); + this.right.stroke({ color: fillColor }); + this.dorsal.stroke({ color: fillColor }); + this.left.stroke({ color: fillColor }); + }, + + getEdges() { + const arr = []; + arr.push(this.frontLeftEdge); + arr.push(this.frontRightEdge); + arr.push(this.dorsalRightEdge); + arr.push(this.frontTopEdge); + arr.push(this.frontBotEdge); + arr.push(this.dorsalLeftEdge); + arr.push(this.rightTopEdge); + arr.push(this.rightBotEdge); + return arr; + }, + + getGrabPoints() { + const arr = []; + arr.push(this.flCenter); + arr.push(this.frCenter); + arr.push(this.drCenter); + arr.push(this.ftCenter); + arr.push(this.fbCenter); + arr.push(this.dlCenter); + return arr; + }, + + updateProjectionLine(equation, source, direction) { + const x1 = source.x; + const y1 = equation.getYCanvas(x1); + + const x2 = direction.x; + const y2 = equation.getYCanvas(x2); + return [[x1, y1], [x2, y2]]; + }, + + addMouseOverEvents() { + this._addFaceEvents(); + }, + + _addFaceEvents() { + const group = this; + this.left.on('mouseover', function () { + this.attr({ 'fill-opacity': 0.5 }); + }).on('mouseout', function () { + this.attr({ 'fill-opacity': group.attr('fill-opacity') }); + }); + this.dorsal.on('mouseover', function () { + this.attr({ 'fill-opacity': 0.5 }); + }).on('mouseout', function () { + this.attr({ 'fill-opacity': group.attr('fill-opacity') }); + }); + this.right.on('mouseover', function () { + this.attr({ 'fill-opacity': 0.5 }); + }).on('mouseout', function () { + this.attr({ 'fill-opacity': group.attr('fill-opacity') }); + }); + }, + + removeMouseOverEvents() { + const edges = this.getEdges(); + edges.forEach((edge) => { + edge.off('mouseover').off('mouseout'); + }); + this.left.off('mouseover').off('mouseout'); + this.dorsal.off('mouseover').off('mouseout'); + this.right.off('mouseover').off('mouseout'); + }, + + resetFaceOpacity() { + const group = this; + this.left.attr({ 'fill-opacity': group.attr('fill-opacity') }); + this.dorsal.attr({ 'fill-opacity': group.attr('fill-opacity') }); + this.right.attr({ 'fill-opacity': group.attr('fill-opacity') }); + }, + + addOccluded() { + const edges = this.getEdges(); + edges.forEach((edge) => { + edge.node.classList.add('occludedShape'); + }); + this.face.attr('stroke-width', 0); + this.right.attr('stroke-width', 0); + this.left.node.classList.add('occludedShape'); + this.dorsal.node.classList.add('occludedShape'); + }, + + removeOccluded() { + const edges = this.getEdges(); + edges.forEach((edge) => { + edge.node.classList.remove('occludedShape'); + }); + this.face.attr('stroke-width', this.attr('stroke-width')); + this.right.attr('stroke-width', this.attr('stroke-width')); + this.left.node.classList.remove('occludedShape'); + this.dorsal.node.classList.remove('occludedShape'); + }, + }, + construct: { + cube(points) { + return this.put(new SVG.Cube()).constructorMethod(points); + }, + }, +}); diff --git a/cvat-core/src/annotations-collection.js b/cvat-core/src/annotations-collection.js index 4caef596ed27..136cad5615b1 100644 --- a/cvat-core/src/annotations-collection.js +++ b/cvat-core/src/annotations-collection.js @@ -595,6 +595,10 @@ shape: 0, track: 0, }, + cuboid: { + shape: 0, + track: 0, + }, tags: 0, manually: 0, interpolated: 0, diff --git a/cvat-core/src/annotations-objects.js b/cvat-core/src/annotations-objects.js index b2b33fd3afd7..d3640e3412da 100644 --- a/cvat-core/src/annotations-objects.js +++ b/cvat-core/src/annotations-objects.js @@ -69,6 +69,12 @@ `Points must have at least 1 points, but got ${points.length / 2}`, ); } + } else if (shapeType === ObjectShape.CUBOID) { + if (points.length / 2 !== 8) { + throw new DataError( + `Points must have exact 8 points, but got ${points.length / 2}`, + ); + } } else { throw new ArgumentError( `Unknown value of shapeType has been recieved ${shapeType}`, @@ -1356,6 +1362,118 @@ super(data, clientID, color, injection); this.shapeType = ObjectShape.CUBOID; } + + static makeHull(geoPoints) { + // Returns the convex hull, assuming that each points[i] <= points[i + 1]. + function makeHullPresorted(points) { + if (points.length <= 1) return points.slice(); + + // Andrew's monotone chain algorithm. Positive y coordinates correspond to 'up' + // as per the mathematical convention, instead of 'down' as per the computer + // graphics convention. This doesn't affect the correctness of the result. + + const upperHull = []; + for (let i = 0; i < points.length; i += 1) { + const p = points[`${i}`]; + while (upperHull.length >= 2) { + const q = upperHull[upperHull.length - 1]; + const r = upperHull[upperHull.length - 2]; + if ((q.x - r.x) * (p.y - r.y) >= (q.y - r.y) * (p.x - r.x)) upperHull.pop(); + else break; + } + upperHull.push(p); + } + upperHull.pop(); + + const lowerHull = []; + for (let i = points.length - 1; i >= 0; i -= 1) { + const p = points[`${i}`]; + while (lowerHull.length >= 2) { + const q = lowerHull[lowerHull.length - 1]; + const r = lowerHull[lowerHull.length - 2]; + if ((q.x - r.x) * (p.y - r.y) >= (q.y - r.y) * (p.x - r.x)) lowerHull.pop(); + else break; + } + lowerHull.push(p); + } + lowerHull.pop(); + + if (upperHull.length + === 1 && lowerHull.length + === 1 && upperHull[0].x + === lowerHull[0].x && upperHull[0].y + === lowerHull[0].y) return upperHull; + return upperHull.concat(lowerHull); + } + + function POINT_COMPARATOR(a, b) { + if (a.x < b.x) return -1; + if (a.x > b.x) return +1; + if (a.y < b.y) return -1; + if (a.y > b.y) return +1; + return 0; + } + + const newPoints = geoPoints.slice(); + newPoints.sort(POINT_COMPARATOR); + return makeHullPresorted(newPoints); + } + + static contain(points, x, y) { + function isLeft(P0, P1, P2) { + return ((P1.x - P0.x) * (P2.y - P0.y) - (P2.x - P0.x) * (P1.y - P0.y)); + } + points = CuboidShape.makeHull(points); + let wn = 0; + for (let i = 0; i < points.length; i += 1) { + const p1 = points[`${i}`]; + const p2 = points[i + 1] || points[0]; + + if (p1.y <= y) { + if (p2.y > y) { + if (isLeft(p1, p2, { x, y }) > 0) { + wn += 1; + } + } + } else if (p2.y < y) { + if (isLeft(p1, p2, { x, y }) < 0) { + wn -= 1; + } + } + } + + return wn !== 0; + } + + static distance(actualPoints, x, y) { + const points = []; + + for (let i = 0; i < 16; i += 2) { + points.push({ x: actualPoints[i], y: actualPoints[i + 1] }); + } + + if (!CuboidShape.contain(points, x, y)) return null; + + let minDistance = Number.MAX_SAFE_INTEGER; + for (let i = 0; i < points.length; i += 1) { + const p1 = points[`${i}`]; + const p2 = points[i + 1] || points[0]; + + // perpendicular from point to straight length + const distance = (Math.abs((p2.y - p1.y) * x + - (p2.x - p1.x) * y + p2.x * p1.y - p2.y * p1.x)) + / Math.sqrt(Math.pow(p2.y - p1.y, 2) + Math.pow(p2.x - p1.x, 2)); + + // check if perpendicular belongs to the straight segment + const a = Math.pow(p1.x - x, 2) + Math.pow(p1.y - y, 2); + const b = Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2); + const c = Math.pow(p2.x - x, 2) + Math.pow(p2.y - y, 2); + if (distance < minDistance && (a + b - c) >= 0 && (c + b - a) >= 0) { + minDistance = distance; + } + } + return minDistance; + } } class RectangleTrack extends Track { diff --git a/cvat-core/src/statistics.js b/cvat-core/src/statistics.js index 36910eb7f14c..8e9974461f7b 100644 --- a/cvat-core/src/statistics.js +++ b/cvat-core/src/statistics.js @@ -34,6 +34,10 @@ * tracks: 19, * shapes: 20, * }, + * cuboids: { + * tracks: 21, + * shapes: 22, + * }, * tags: 66, * manually: 186, * interpolated: 500, @@ -69,6 +73,10 @@ * tracks: 19, * shapes: 20, * }, + * cuboids: { + * tracks: 21, + * shapes: 22, + * }, * tags: 66, * manually: 186, * interpolated: 500, diff --git a/cvat-ui/src/actions/annotation-actions.ts b/cvat-ui/src/actions/annotation-actions.ts index 16b9d03824ec..ac3f123cd18b 100644 --- a/cvat-ui/src/actions/annotation-actions.ts +++ b/cvat-ui/src/actions/annotation-actions.ts @@ -98,12 +98,14 @@ async function jobInfoGenerator(job: any): Promise> { 'track count': total.rectangle.shape + total.rectangle.track + total.polygon.shape + total.polygon.track + total.polyline.shape + total.polyline.track - + total.points.shape + total.points.track, + + total.points.shape + total.points.track + + total.cuboid.shape + total.cuboid.track, 'object count': total.total, 'box count': total.rectangle.shape + total.rectangle.track, 'polygon count': total.polygon.shape + total.polygon.track, 'polyline count': total.polyline.shape + total.polyline.track, 'points count': total.points.shape + total.points.track, + 'cuboids count': total.cuboid.shape + total.cuboid.track, 'tag count': total.tags, }; } From eb22e02bc521b675627117f0712ce1594185b628 Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Wed, 8 Apr 2020 09:27:58 +0300 Subject: [PATCH 05/45] wip --- cvat-canvas/src/typescript/canvasView.ts | 9 +- cvat-canvas/src/typescript/cuboid.ts | 428 ++++++++++++++++++++++ cvat-canvas/src/typescript/drawHandler.ts | 196 +--------- cvat-canvas/src/typescript/shared.ts | 348 +++--------------- cvat-canvas/src/typescript/svg.patch.ts | 105 +++--- 5 files changed, 546 insertions(+), 540 deletions(-) create mode 100644 cvat-canvas/src/typescript/cuboid.ts diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index 464f2d742b90..86e7a8cfc566 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -38,6 +38,7 @@ import { Size, Configuration, } from './canvasModel'; +import { Cuboid2PointViewModel } from './cuboid'; export interface CanvasView { html(): HTMLDivElement; @@ -1533,11 +1534,15 @@ export class CanvasViewImpl implements CanvasView, Listener { return polyline; } - private addCuboid(points: string, state: any): SVG.PolyLine { - const cube = this.adoptedContent.cube(points).addClass('cvat_canvas_shape') + // TODO: verify tslint + private addCuboid(points: string, state: any): SVG.Cube { + const cube = this.adoptedContent.cube( + new Cuboid2PointViewModel(pointsToArray(points)), + ).addClass('cvat_canvas_shape'); return cube; } + // tslint:enable private setupPoints(basicPolyline: SVG.PolyLine, state: any): any { this.selectize(true, basicPolyline); diff --git a/cvat-canvas/src/typescript/cuboid.ts b/cvat-canvas/src/typescript/cuboid.ts new file mode 100644 index 000000000000..469a6e004276 --- /dev/null +++ b/cvat-canvas/src/typescript/cuboid.ts @@ -0,0 +1,428 @@ +/* eslint-disable func-names */ +/* eslint-disable no-underscore-dangle */ +/* eslint-disable curly */ +/* + * Copyright (C) 2018 Intel Corporation + * + * SPDX-License-Identifier: MIT + */ + + +import { convertToArray, intersection, convertArrayToDoubleArray } from './shared'; + +const MIN_EDGE_LENGTH = 3; + +class Equation { + private a: number; + private b: number; + private c: number; + private cCanvas: number; + + public constructor(p1: number[], p2: number[]) { + this.a = p1[1] - p2[1]; + this.b = p2[0] - p1[0]; + this.c = this.b * p1[1] + this.a * p1[0]; + + const p1Canvas = { x: p1[0], y: p1[1] }; + this.cCanvas = this.b * p1Canvas.y + this.a * p1Canvas.x; + } + + // get the line equation in actual coordinates + public getY(x: number): number { + return (this.c - this.a * x) / this.b; + } +} + +class Figure { + private indices: number[]; + private viewmodel: any; + + public constructor(indices: number[], Vmodel: any) { + this.indices = indices; + this.viewmodel = Vmodel; + } + + public get points(): any[] { + const points = []; + for (const index of this.indices) { + points.push(this.viewmodel.points[index]); + } + return points; + } + + // sets the point for a given edge, points must be given in + // array form in the same ordering as the getter + // if you only need to update a subset of the points, + // simply put null for the points you want to keep + public set points(newPoints) { + const oldPoints = this.viewmodel.points; + for (let i = 0; i < newPoints.length; i += 1) { + if (newPoints[i] !== null) { + oldPoints[this.indices[i]] = { x: newPoints[i].x, y: newPoints[i].y }; + } + } + } +} + +class Edge extends Figure { + public getEquation(): Equation { + let { points } = this; + points = convertToArray(points); + return new Equation(points[0], points[1]); + } +} + +export class Cuboid2PointViewModel { + private points: any[]; + private fr: Edge; + private fl: Edge; + private dr: Edge; + private dl: Edge; + private ft: Edge; + private rt: Edge; + private lt: Edge; + private dt: Edge; + private fb: Edge; + private rb: Edge; + private lb: Edge; + private db: Edge; + public edgeList: Edge[]; + private front: Figure; + private right: Figure; + private dorsal: Figure; + private left: Figure; + private top: Figure; + private bot: Figure; + public facesList: Figure[]; + public vpl: number[] | null; + public vpr: number[] | null; + + public constructor(points: any[]) { + this.points = convertArrayToDoubleArray(points); + this.initEdges(); + this.initFaces(); + this.updateVanishingPoints(); + this.buildBackEdge(); + this.updatePoints(); + } + + public getPoints(): any[] { + return this.points; + } + + public setPoints(points: any[]): void { + this.points = points; + } + + public updatePoints(): void { + // making sure that the edges are vertical + this.fr.points[0].x = this.fr.points[1].x; + this.fl.points[0].x = this.fl.points[1].x; + this.dr.points[0].x = this.dr.points[1].x; + this.dl.points[0].x = this.dl.points[1].x; + } + + public computeSideEdgeConstraints(edge: any): any { + const midLength = this.fr.points[1].y - this.fr.points[0].y - 1; + + const minY = edge.canvasPoints[1].y - midLength; + const maxY = edge.canvasPoints[0].y + midLength; + + const y1 = edge.points[0].y; + const y2 = edge.points[1].y; + + const miny1 = y2 - midLength; + const maxy1 = y2 - MIN_EDGE_LENGTH; + + const miny2 = y1 + MIN_EDGE_LENGTH; + const maxy2 = y1 + midLength; + + return { + constraint: { + minY, + maxY, + }, + y1Range: { + max: maxy1, + min: miny1, + }, + y2Range: { + max: maxy2, + min: miny2, + }, + }; + } + + // boolean value parameter controls which edges should be used to recalculate vanishing points + private updateVanishingPoints(): void { + const leftEdge = this.fl.points; + const rightEdge = this.dr.points; + const midEdge = this.fr.points; + + this.vpl = intersection(leftEdge[0], midEdge[0], leftEdge[1], midEdge[1]); + this.vpr = intersection(rightEdge[0], midEdge[0], rightEdge[1], midEdge[1]); + if (this.vpl === null) { + // shift the edge slightly to avoid edge case + leftEdge[0][1] -= 0.001; + leftEdge[0][0] += 0.001; + leftEdge[1][0] += 0.001; + this.vpl = intersection(leftEdge[0], midEdge[0], leftEdge[1], midEdge[1]); + } + if (this.vpr === null) { + // shift the edge slightly to avoid edge case + rightEdge[0][1] -= 0.001; + rightEdge[0][0] -= 0.001; + rightEdge[1][0] -= 0.001; + this.vpr = intersection(leftEdge[0], midEdge[0], leftEdge[1], midEdge[1]); + } + } + + private initEdges(): void { + this.fl = new Edge([0, 1], this); + this.fr = new Edge([2, 3], this); + this.dr = new Edge([4, 5], this); + this.dl = new Edge([6, 7], this); + + this.ft = new Edge([0, 2], this); + this.lt = new Edge([0, 6], this); + this.rt = new Edge([2, 4], this); + this.dt = new Edge([6, 4], this); + + this.fb = new Edge([1, 3], this); + this.lb = new Edge([1, 7], this); + this.rb = new Edge([3, 5], this); + this.db = new Edge([7, 5], this); + + this.edgeList = [this.fl, this.fr, this.dl, this.dr, this.ft, this.lt, + this.rt, this.dt, this.fb, this.lb, this.rb, this.db]; + } + + private initFaces(): void { + this.front = new Figure([0, 1, 3, 2], this); + this.right = new Figure([2, 3, 5, 4], this); + this.dorsal = new Figure([4, 5, 7, 6], this); + this.left = new Figure([6, 7, 1, 0], this); + this.top = new Figure([0, 2, 4, 6], this); + this.bot = new Figure([1, 3, 5, 7], this); + + this.facesList = [this.front, this.right, this.dorsal, this.left]; + } + + private buildBackEdge(): void { + this.updateVanishingPoints(); + const leftPoints = this.dr.points; + const rightPoints = this.fl.points; + const topIndex = 6; + const botIndex = 7; + + const vpLeft = this.vpl; + const vpRight = this.vpr; + + let p1 = intersection(vpLeft, leftPoints[0], vpRight, rightPoints[0]); + let p2 = intersection(vpLeft, leftPoints[1], vpRight, rightPoints[1]); + + if (p1 === null) { + p1 = [p2[0], vpLeft[1]]; + } else if (p2 === null) { + p2 = [p1[0], vpLeft[1]]; + } + + this.points[topIndex] = p1; + this.points[botIndex] = p2; + + // Making sure that the vertical edges stay vertical + this.updatePoints(); + } +} + +function sortPointsClockwise(points: any[]): any[] { + points.sort((a, b): number => a.y - b.y); + // Get center y + const cy = (points[0].y + points[points.length - 1].y) / 2; + + // Sort from right to left + points.sort((a, b): number => b.x - a.x); + + // Get center x + const cx = (points[0].x + points[points.length - 1].x) / 2; + + // Center point + const center = { + x: cx, + y: cy, + }; + + // Starting angle used to reference other angles + let startAng: number | undefined; + points.forEach((point): void => { + let ang = Math.atan2(point.y - center.y, point.x - center.x); + if (!startAng) { + startAng = ang; + // ensure that all points are clockwise of the start point + } else if (ang < startAng) { + ang += Math.PI * 2; + } + // eslint-disable-next-line no-param-reassign + point.angle = ang; // add the angle to the point + }); + + // first sort clockwise + points.sort((a, b): number => a.angle - b.angle); + return points.reverse(); +} + +function setupCuboidPoints(actualPoints: any[]): any[] { + let left; + let right; + let left2; + let right2; + let p1; + let p2; + let p3; + let p4; + + const height = Math.abs(actualPoints[0].x - actualPoints[1].x) + < Math.abs(actualPoints[1].x - actualPoints[2].x) + ? Math.abs(actualPoints[1].y - actualPoints[0].y) + : Math.abs(actualPoints[1].y - actualPoints[2].y); + + // seperate into left and right point + // we pick the first and third point because we know assume they will be on + // opposite corners + if (actualPoints[0].x < actualPoints[2].x) { + [left,, right] = actualPoints; + } else { + [right,, left] = actualPoints; + } + + // get other 2 points using the given height + if (left.y < right.y) { + left2 = { x: left.x, y: left.y + height }; + right2 = { x: right.x, y: right.y - height }; + } else { + left2 = { x: left.x, y: left.y - height }; + right2 = { x: right.x, y: right.y + height }; + } + + // get the vector for the last point relative to the previous point + const vec = { + x: actualPoints[3].x - actualPoints[2].x, + y: actualPoints[3].y - actualPoints[2].y, + }; + + if (left.y < left2.y) { + p1 = left; + p2 = left2; + } else { + p1 = left2; + p2 = left; + } + + if (right.y < right2.y) { + p3 = right; + p4 = right2; + } else { + p3 = right2; + p4 = right; + } + + const p5 = { x: p3.x + vec.x, y: p3.y + vec.y + 0.1 }; + const p6 = { x: p4.x + vec.x, y: p4.y + vec.y - 0.1 }; + const p7 = { x: p1.x + vec.x, y: p1.y + vec.y + 0.1 }; + const p8 = { x: p2.x + vec.x, y: p2.y + vec.y - 0.1 }; + + p1.y += 0.1; + return [p1, p2, p3, p4, p5, p6, p7, p8]; +} + +export function cuboidPointsBy4Points(points: any[]): any[] { + const actualPoints = []; + for (let i = 0; i < 4; i++) { + const [x, y] = points.slice(i * 2, i * 2 + 2); + actualPoints.push({ x, y }); + } + const unsortedPlanePoints = actualPoints.slice(0, 3); + function rotate(array: any[], times: number): void{ + let t = times; + while (t--) { + const temp = array.shift(); + array.push(temp); + } + } + + const plane2 = { + p1: actualPoints[0], + p2: actualPoints[0], + p3: actualPoints[0], + p4: actualPoints[0], + }; + + // completing the plane + const vector = { + x: actualPoints[2].x - actualPoints[1].x, + y: actualPoints[2].y - actualPoints[1].y, + }; + + // sorting the first plane + unsortedPlanePoints.push({ + x: actualPoints[0].x + vector.x, + y: actualPoints[0].y + vector.y, + }); + const sortedPlanePoints = sortPointsClockwise(unsortedPlanePoints); + let leftIndex = 0; + for (let i = 0; i < 4; i++) { + leftIndex = sortedPlanePoints[i].x < sortedPlanePoints[leftIndex].x ? i : leftIndex; + } + rotate(sortedPlanePoints, leftIndex); + const plane1 = { + p1: sortedPlanePoints[0], + p2: sortedPlanePoints[1], + p3: sortedPlanePoints[2], + p4: sortedPlanePoints[3], + }; + + const vec = { + x: actualPoints[3].x - actualPoints[2].x, + y: actualPoints[3].y - actualPoints[2].y, + }; + // determine the orientation + const angle = Math.atan2(vec.y, vec.x); + + // making the other plane + plane2.p1 = { x: plane1.p1.x + vec.x, y: plane1.p1.y + vec.y }; + plane2.p2 = { x: plane1.p2.x + vec.x, y: plane1.p2.y + vec.y }; + plane2.p3 = { x: plane1.p3.x + vec.x, y: plane1.p3.y + vec.y }; + plane2.p4 = { x: plane1.p4.x + vec.x, y: plane1.p4.y + vec.y }; + + + let cuboidPoints; + // right + if (Math.abs(angle) < Math.PI / 2 - 0.1) { + cuboidPoints = setupCuboidPoints(actualPoints); + // left + } else if (Math.abs(angle) > Math.PI / 2 + 0.1) { + cuboidPoints = setupCuboidPoints(actualPoints); + // down + } else if (angle > 0) { + cuboidPoints = [ + plane1.p1, plane2.p1, plane1.p2, plane2.p2, + plane1.p3, plane2.p3, plane1.p4, plane2.p4, + ]; + cuboidPoints[0].y += 0.1; + cuboidPoints[4].y += 0.1; + // up + } else { + cuboidPoints = [ + plane2.p1, plane1.p1, plane2.p2, plane1.p2, + plane2.p3, plane1.p3, plane2.p4, plane1.p4, + ]; + cuboidPoints[0].y += 0.1; + cuboidPoints[4].y += 0.1; + } + + return cuboidPoints.reduce((arr: number[], point: any): number[] => { + arr.push(point.x); + arr.push(point.y); + return arr; + }, []); +} diff --git a/cvat-canvas/src/typescript/drawHandler.ts b/cvat-canvas/src/typescript/drawHandler.ts index c30d85326ef1..6f1225a51be4 100644 --- a/cvat-canvas/src/typescript/drawHandler.ts +++ b/cvat-canvas/src/typescript/drawHandler.ts @@ -23,6 +23,8 @@ import { Box, } from './shared'; +import { Cuboid2PointViewModel, cuboidPointsBy4Points } from './cuboid'; + export interface DrawHandler { draw(drawData: DrawData, geometry: Geometry): void; transform(geometry: Geometry): void; @@ -341,7 +343,7 @@ export class DrawHandlerImpl implements DrawHandler { && points.length === 4 * 2) { this.onDrawDone({ shapeType, - points: DrawHandlerImpl.cuboidPointsBy4Points(points), + points: cuboidPointsBy4Points(points), }, Date.now() - this.startTimestamp); } }); @@ -376,198 +378,6 @@ export class DrawHandlerImpl implements DrawHandler { this.drawPolyshape(); } - private static sortPointsClockwise(points: any[]): any[] { - points.sort((a, b): number => a.y - b.y); - // Get center y - const cy = (points[0].y + points[points.length - 1].y) / 2; - - // Sort from right to left - points.sort((a, b): number => b.x - a.x); - - // Get center x - const cx = (points[0].x + points[points.length - 1].x) / 2; - - // Center point - const center = { - x: cx, - y: cy, - }; - - // Starting angle used to reference other angles - let startAng: number | undefined; - points.forEach((point): void => { - let ang = Math.atan2(point.y - center.y, point.x - center.x); - if (!startAng) { - startAng = ang; - // ensure that all points are clockwise of the start point - } else if (ang < startAng) { - ang += Math.PI * 2; - } - // eslint-disable-next-line no-param-reassign - point.angle = ang; // add the angle to the point - }); - - // first sort clockwise - points.sort((a, b): number => a.angle - b.angle); - return points.reverse(); - } - - private static setupCuboidPoints(actualPoints: any[]): any[] { - let left; - let right; - let left2; - let right2; - let p1; - let p2; - let p3; - let p4; - - const height = Math.abs(actualPoints[0].x - actualPoints[1].x) - < Math.abs(actualPoints[1].x - actualPoints[2].x) - ? Math.abs(actualPoints[1].y - actualPoints[0].y) - : Math.abs(actualPoints[1].y - actualPoints[2].y); - - // seperate into left and right point - // we pick the first and third point because we know assume they will be on - // opposite corners - if (actualPoints[0].x < actualPoints[2].x) { - [left,, right] = actualPoints; - } else { - [right,, left] = actualPoints; - } - - // get other 2 points using the given height - if (left.y < right.y) { - left2 = { x: left.x, y: left.y + height }; - right2 = { x: right.x, y: right.y - height }; - } else { - left2 = { x: left.x, y: left.y - height }; - right2 = { x: right.x, y: right.y + height }; - } - - // get the vector for the last point relative to the previous point - const vec = { - x: actualPoints[3].x - actualPoints[2].x, - y: actualPoints[3].y - actualPoints[2].y, - }; - - if (left.y < left2.y) { - p1 = left; - p2 = left2; - } else { - p1 = left2; - p2 = left; - } - - if (right.y < right2.y) { - p3 = right; - p4 = right2; - } else { - p3 = right2; - p4 = right; - } - - const p5 = { x: p3.x + vec.x, y: p3.y + vec.y + 0.1 }; - const p6 = { x: p4.x + vec.x, y: p4.y + vec.y - 0.1 }; - const p7 = { x: p1.x + vec.x, y: p1.y + vec.y + 0.1 }; - const p8 = { x: p2.x + vec.x, y: p2.y + vec.y - 0.1 }; - - p1.y += 0.1; - return [p1, p2, p3, p4, p5, p6, p7, p8]; - } - - private static cuboidPointsBy4Points(points: any[]): any[] { - const actualPoints = []; - for (let i = 0; i < 4; i++) { - const [x, y] = points.slice(i * 2, i * 2 + 2); - actualPoints.push({ x, y }); - } - const unsortedPlanePoints = actualPoints.slice(0, 3); - function rotate(array: any[], times: number): void{ - let t = times; - while (t--) { - const temp = array.shift(); - array.push(temp); - } - } - - const plane2 = { - p1: actualPoints[0], - p2: actualPoints[0], - p3: actualPoints[0], - p4: actualPoints[0], - }; - - // completing the plane - const vector = { - x: actualPoints[2].x - actualPoints[1].x, - y: actualPoints[2].y - actualPoints[1].y, - }; - - // sorting the first plane - unsortedPlanePoints.push({ - x: actualPoints[0].x + vector.x, - y: actualPoints[0].y + vector.y, - }); - const sortedPlanePoints = DrawHandlerImpl.sortPointsClockwise(unsortedPlanePoints); - let leftIndex = 0; - for (let i = 0; i < 4; i++) { - leftIndex = sortedPlanePoints[i].x < sortedPlanePoints[leftIndex].x ? i : leftIndex; - } - rotate(sortedPlanePoints, leftIndex); - const plane1 = { - p1: sortedPlanePoints[0], - p2: sortedPlanePoints[1], - p3: sortedPlanePoints[2], - p4: sortedPlanePoints[3], - }; - - const vec = { - x: actualPoints[3].x - actualPoints[2].x, - y: actualPoints[3].y - actualPoints[2].y, - }; - // determine the orientation - const angle = Math.atan2(vec.y, vec.x); - - // making the other plane - plane2.p1 = { x: plane1.p1.x + vec.x, y: plane1.p1.y + vec.y }; - plane2.p2 = { x: plane1.p2.x + vec.x, y: plane1.p2.y + vec.y }; - plane2.p3 = { x: plane1.p3.x + vec.x, y: plane1.p3.y + vec.y }; - plane2.p4 = { x: plane1.p4.x + vec.x, y: plane1.p4.y + vec.y }; - - - let cuboidPoints; - // right - if (Math.abs(angle) < Math.PI / 2 - 0.1) { - cuboidPoints = DrawHandlerImpl.setupCuboidPoints(actualPoints); - // left - } else if (Math.abs(angle) > Math.PI / 2 + 0.1) { - cuboidPoints = DrawHandlerImpl.setupCuboidPoints(actualPoints); - // down - } else if (angle > 0) { - cuboidPoints = [ - plane1.p1, plane2.p1, plane1.p2, plane2.p2, - plane1.p3, plane2.p3, plane1.p4, plane2.p4, - ]; - cuboidPoints[0].y += 0.1; - cuboidPoints[4].y += 0.1; - // up - } else { - cuboidPoints = [ - plane2.p1, plane1.p1, plane2.p2, plane1.p2, - plane2.p3, plane1.p3, plane2.p4, plane1.p4, - ]; - cuboidPoints[0].y += 0.1; - cuboidPoints[4].y += 0.1; - } - - return cuboidPoints.reduce((arr: number[], point: any): number[] => { - arr.push(point.x); - arr.push(point.y); - return arr; - }, []); - } - private drawCuboid(): void { this.drawInstance = (this.canvas as any).polyline() .addClass('cvat_canvas_shape_drawing').attr({ diff --git a/cvat-canvas/src/typescript/shared.ts b/cvat-canvas/src/typescript/shared.ts index 93a76430f622..da5f62737531 100644 --- a/cvat-canvas/src/typescript/shared.ts +++ b/cvat-canvas/src/typescript/shared.ts @@ -4,6 +4,7 @@ import * as SVG from 'svg.js'; import consts from './consts'; +import { Position } from './canvasModel'; export interface ShapeSizeElement { sizeElement: any; @@ -25,6 +26,11 @@ export interface BBox { y: number; } +interface Point { + x: number; + y: number; +} + // Translate point array from the canvas coordinate system // to the coordinate system of a client export function translateFromSVG(svg: SVGSVGElement, points: number[]): number[] { @@ -102,304 +108,60 @@ export function displayShapeSize( } +export function convertToArray(points: Point[]): number[][] { + const arr: number[][] = []; + points.forEach((point: Point): void => { + arr.push([point.x, point.y]); + }); + return arr; +} +export function convertArrayToObjects(pointsArray: number[][]): Point[] { + return pointsArray.reduce((points: Point[], point: number[]): Point[] => { + const [x, y] = point; + points.push({ x, y }); + return points; + }, []); +} -export const Cube = SVG.invent({ - create: 'g', - inherit: SVG.G, - extend: { - - constructorMethod(viewModel): any { - this.attr('points', viewModel.getPoints()); - this.projectionLineEnable = false; - this.setupFaces(viewModel); - this.setupEdges(viewModel); - this.setupProjections(viewModel); - this.setupGrabPoints(); - this.hideProjections(); - this.hideGrabPoints(); - - return this; - }, - - setupFaces(viewModel): void { - this.face = this.polygon(viewModel.front.canvasPoints); - this.right = this.polygon(viewModel.right.canvasPoints); - this.dorsal = this.polygon(viewModel.dorsal.canvasPoints); - this.left = this.polygon(viewModel.left.canvasPoints); - }, - - setupProjections(viewModel): void { - this.ftProj = this.line(this.updateProjectionLine(viewModel.ft.getEquation(), - viewModel.ft.canvasPoints[0], viewModel.vplCanvas)); - this.fbProj = this.line(this.updateProjectionLine(viewModel.fb.getEquation(), - viewModel.ft.canvasPoints[0], viewModel.vplCanvas)); - this.rtProj = this.line(this.updateProjectionLine(viewModel.rt.getEquation(), - viewModel.rt.canvasPoints[1], viewModel.vprCanvas)); - this.rbProj = this.line(this.updateProjectionLine(viewModel.rb.getEquation(), - viewModel.rb.canvasPoints[1], viewModel.vprCanvas)); - - this.ftProj.stroke({ color: '#C0C0C0' }); - this.fbProj.stroke({ color: '#C0C0C0' }); - this.rtProj.stroke({ color: '#C0C0C0' }); - this.rbProj.stroke({ color: '#C0C0C0' }); - }, - - setupEdges(viewModel): void { - this.frontLeftEdge = this.line(viewModel.fl.canvasPoints); - this.frontRightEdge = this.line(viewModel.fr.canvasPoints); - this.dorsalRightEdge = this.line(viewModel.dr.canvasPoints); - this.dorsalLeftEdge = this.line(viewModel.dl.canvasPoints); - - this.frontTopEdge = this.line(viewModel.ft.canvasPoints); - this.rightTopEdge = this.line(viewModel.rt.canvasPoints); - this.frontBotEdge = this.line(viewModel.fb.canvasPoints); - this.rightBotEdge = this.line(viewModel.rb.canvasPoints); - }, - - setupGrabPoints(): void { - this.flCenter = this.circle().addClass('svg_select_points').addClass('svg_select_points_l'); - this.frCenter = this.circle().addClass('svg_select_points').addClass('svg_select_points_r'); - this.drCenter = this.circle().addClass('svg_select_points').addClass('svg_select_points_ew'); - this.dlCenter = this.circle().addClass('svg_select_points').addClass('svg_select_points_ew'); - - this.ftCenter = this.circle().addClass('svg_select_points').addClass('svg_select_points_t'); - this.fbCenter = this.circle().addClass('svg_select_points').addClass('svg_select_points_b'); - - const grabPoints = this.getGrabPoints(); - const edges = this.getEdges(); - for (let i = 0; i < grabPoints.length; i += 1) { - const edge = edges[`${i}`]; - const cx = (edge.attr('x2') + edge.attr('x1')) / 2; - const cy = (edge.attr('y2') + edge.attr('y1')) / 2; - grabPoints[`${i}`].center(cx, cy); - } - }, - - updateGrabPoints(): void { - const centers = this.getGrabPoints(); - const edges = this.getEdges(); - for (let i = 0; i < centers.length; i += 1) { - const edge = edges[`${i}`]; - centers[`${i}`].center(edge.cx(), edge.cy()); - } - }, - - move(dx, dy): void { - this.face.dmove(dx, dy); - this.dorsal.dmove(dx, dy); - this.right.dmove(dx, dy); - this.left.dmove(dx, dy); - - const edges = this.getEdges(); - edges.forEach((edge): void => { - edge.dmove(dx, dy); - }); - }, - - showProjections(): void { - if (this.projectionLineEnable) { - this.ftProj.show(); - this.fbProj.show(); - this.rtProj.show(); - this.rbProj.show(); - } - }, - - hideProjections(): void { - this.ftProj.hide(); - this.fbProj.hide(); - this.rtProj.hide(); - this.rbProj.hide(); - }, - - showGrabPoints(): void { - const grabPoints = this.getGrabPoints(); - grabPoints.forEach((point): void => { - point.show(); - }); - }, - - hideGrabPoints(): void { - const grabPoints = this.getGrabPoints(); - grabPoints.forEach((point): void => { - point.hide(); - }); - }, - - updateView(viewModel): void { - const convertedPoints = window.cvat.translate.points.actualToCanvas( - viewModel.getPoints(), - ); - this.updatePolygons(viewModel); - this.updateLines(viewModel); - this.updateProjections(viewModel); - this.updateGrabPoints(); - this.attr('points', convertedPoints); - }, - - updatePolygons(viewModel): void { - this.face.plot(viewModel.front.canvasPoints); - this.right.plot(viewModel.right.canvasPoints); - this.dorsal.plot(viewModel.dorsal.canvasPoints); - this.left.plot(viewModel.left.canvasPoints); - }, - - updateLines(viewModel): void { - this.frontLeftEdge.plot(viewModel.fl.canvasPoints); - this.frontRightEdge.plot(viewModel.fr.canvasPoints); - this.dorsalRightEdge.plot(viewModel.dr.canvasPoints); - this.dorsalLeftEdge.plot(viewModel.dl.canvasPoints); - - this.frontTopEdge.plot(viewModel.ft.canvasPoints); - this.rightTopEdge.plot(viewModel.rt.canvasPoints); - this.frontBotEdge.plot(viewModel.fb.canvasPoints); - this.rightBotEdge.plot(viewModel.rb.canvasPoints); - }, - - updateThickness(): void { - const edges = this.getEdges(); - const width = this.attr('stroke-width'); - const baseWidthOffset = 1.75; - const expandedWidthOffset = 3; - edges.forEach((edge): void => { - edge.on('mouseover', function (): void { - this.attr({ 'stroke-width': width * expandedWidthOffset }); - }).on('mouseout', function (): void { - this.attr({ 'stroke-width': width * baseWidthOffset }); - }).stroke({ width: width * baseWidthOffset, linecap: 'round' }); - }); - }, - - updateProjections(viewModel): void { - this.ftProj.plot(this.updateProjectionLine(viewModel.ft.getEquation(), - viewModel.ft.canvasPoints[0], viewModel.vplCanvas)); - this.fbProj.plot(this.updateProjectionLine(viewModel.fb.getEquation(), - viewModel.ft.canvasPoints[0], viewModel.vplCanvas)); - this.rtProj.plot(this.updateProjectionLine(viewModel.rt.getEquation(), - viewModel.rt.canvasPoints[1], viewModel.vprCanvas)); - this.rbProj.plot(this.updateProjectionLine(viewModel.rb.getEquation(), - viewModel.rt.canvasPoints[1], viewModel.vprCanvas)); - }, - - paintOrientationLines(): void { - const fillColor = this.attr('fill'); - const selectedColor = '#ff007f'; - this.frontTopEdge.stroke({ color: selectedColor }); - this.frontLeftEdge.stroke({ color: selectedColor }); - this.frontBotEdge.stroke({ color: selectedColor }); - this.frontRightEdge.stroke({ color: selectedColor }); - - this.rightTopEdge.stroke({ color: fillColor }); - this.rightBotEdge.stroke({ color: fillColor }); - this.dorsalRightEdge.stroke({ color: fillColor }); - this.dorsalLeftEdge.stroke({ color: fillColor }); - - this.face.stroke({ color: fillColor, width: 0 }); - this.right.stroke({ color: fillColor }); - this.dorsal.stroke({ color: fillColor }); - this.left.stroke({ color: fillColor }); - }, - - getEdges(): any[] { - const arr = []; - arr.push(this.frontLeftEdge); - arr.push(this.frontRightEdge); - arr.push(this.dorsalRightEdge); - arr.push(this.frontTopEdge); - arr.push(this.frontBotEdge); - arr.push(this.dorsalLeftEdge); - arr.push(this.rightTopEdge); - arr.push(this.rightBotEdge); - return arr; - }, - - getGrabPoints(): any[] { - const arr = []; - arr.push(this.flCenter); - arr.push(this.frCenter); - arr.push(this.drCenter); - arr.push(this.ftCenter); - arr.push(this.fbCenter); - arr.push(this.dlCenter); - return arr; - }, - - updateProjectionLine(equation, source, direction): any[] { - const x1 = source.x; - const y1 = equation.getYCanvas(x1); - - const x2 = direction.x; - const y2 = equation.getYCanvas(x2); - return [[x1, y1], [x2, y2]]; - }, - - addMouseOverEvents(): void { - this._addFaceEvents(); - }, +export function convertArrayToDoubleArray(points: number[]): number[][] { + if (points.length % 2 !== 0) { + throw new Error('Points array must have length multiple of two.'); + } - addFaceEvents() { - const group = this; - this.left.on('mouseover', function () { - this.attr({ 'fill-opacity': 0.5 }); - }).on('mouseout', function () { - this.attr({ 'fill-opacity': group.attr('fill-opacity') }); - }); - this.dorsal.on('mouseover', function () { - this.attr({ 'fill-opacity': 0.5 }); - }).on('mouseout', function () { - this.attr({ 'fill-opacity': group.attr('fill-opacity') }); - }); - this.right.on('mouseover', function () { - this.attr({ 'fill-opacity': 0.5 }); - }).on('mouseout', function () { - this.attr({ 'fill-opacity': group.attr('fill-opacity') }); - }); - }, + const pointsArray = []; - removeMouseOverEvents() { - const edges = this.getEdges(); - edges.forEach((edge) => { - edge.off('mouseover').off('mouseout'); - }); - this.left.off('mouseover').off('mouseout'); - this.dorsal.off('mouseover').off('mouseout'); - this.right.off('mouseover').off('mouseout'); - }, + for (let i = 0; i < points.length; i += 2) { + pointsArray.push([points[i], points[i]]); + } - resetFaceOpacity() { - const group = this; - this.left.attr({ 'fill-opacity': group.attr('fill-opacity') }); - this.dorsal.attr({ 'fill-opacity': group.attr('fill-opacity') }); - this.right.attr({ 'fill-opacity': group.attr('fill-opacity') }); - }, + return pointsArray; +} - addOccluded() { - const edges = this.getEdges(); - edges.forEach((edge) => { - edge.node.classList.add('occludedShape'); - }); - this.face.attr('stroke-width', 0); - this.right.attr('stroke-width', 0); - this.left.node.classList.add('occludedShape'); - this.dorsal.node.classList.add('occludedShape'); - }, +function line(p1: number[], p2: number[]): number[] { + const a = p1[1] - p2[1]; + const b = p2[0] - p1[0]; + const c = b * p1[1] + a * p1[0]; + return [a, b, c]; +} - removeOccluded() { - const edges = this.getEdges(); - edges.forEach((edge) => { - edge.node.classList.remove('occludedShape'); - }); - this.face.attr('stroke-width', this.attr('stroke-width')); - this.right.attr('stroke-width', this.attr('stroke-width')); - this.left.node.classList.remove('occludedShape'); - this.dorsal.node.classList.remove('occludedShape'); - }, - }, - construct: { - cube(points) { - return this.put(new SVG.Cube()).constructorMethod(points); - }, - }, -}); +export function intersection( + p1: number[], p2: number[], p3: number[], p4: number[] +): number[] | null { + const L1 = line(p1, p2); + const L2 = line(p3, p4); + + const D = L1[0] * L2[1] - L1[1] * L2[0]; + const Dx = L1[2] * L2[1] - L1[1] * L2[2]; + const Dy = L1[0] * L2[2] - L1[2] * L2[0]; + + let x = null; + let y = null; + if (D !== 0) { + x = Dx / D; + y = Dy / D; + return [x, y]; + } + return null; +} diff --git a/cvat-canvas/src/typescript/svg.patch.ts b/cvat-canvas/src/typescript/svg.patch.ts index 9f73de1e4ed8..d36e7b7346bc 100644 --- a/cvat-canvas/src/typescript/svg.patch.ts +++ b/cvat-canvas/src/typescript/svg.patch.ts @@ -186,7 +186,7 @@ SVG.Cube = SVG.invent({ inherit: SVG.G, extend: { - constructorMethod(viewModel) { + constructorMethod(viewModel: any) { this.attr('points', viewModel.getPoints()); this.projectionLineEnable = false; this.setupFaces(viewModel); @@ -199,22 +199,22 @@ SVG.Cube = SVG.invent({ return this; }, - setupFaces(viewModel) { - this.face = this.polygon(viewModel.front.canvasPoints); - this.right = this.polygon(viewModel.right.canvasPoints); - this.dorsal = this.polygon(viewModel.dorsal.canvasPoints); - this.left = this.polygon(viewModel.left.canvasPoints); + setupFaces(viewModel: any) { + this.face = this.polygon(viewModel.front.points); + this.right = this.polygon(viewModel.right.points); + this.dorsal = this.polygon(viewModel.dorsal.points); + this.left = this.polygon(viewModel.left.points); }, - setupProjections(viewModel) { + setupProjections(viewModel: any) { this.ftProj = this.line(this.updateProjectionLine(viewModel.ft.getEquation(), - viewModel.ft.canvasPoints[0], viewModel.vplCanvas)); + viewModel.ft.points[0], viewModel.vplCanvas)); this.fbProj = this.line(this.updateProjectionLine(viewModel.fb.getEquation(), - viewModel.ft.canvasPoints[0], viewModel.vplCanvas)); + viewModel.ft.points[0], viewModel.vplCanvas)); this.rtProj = this.line(this.updateProjectionLine(viewModel.rt.getEquation(), - viewModel.rt.canvasPoints[1], viewModel.vprCanvas)); + viewModel.rt.points[1], viewModel.vprCanvas)); this.rbProj = this.line(this.updateProjectionLine(viewModel.rb.getEquation(), - viewModel.rb.canvasPoints[1], viewModel.vprCanvas)); + viewModel.rb.points[1], viewModel.vprCanvas)); this.ftProj.stroke({ color: '#C0C0C0' }); this.fbProj.stroke({ color: '#C0C0C0' }); @@ -222,16 +222,16 @@ SVG.Cube = SVG.invent({ this.rbProj.stroke({ color: '#C0C0C0' }); }, - setupEdges(viewModel) { - this.frontLeftEdge = this.line(viewModel.fl.canvasPoints); - this.frontRightEdge = this.line(viewModel.fr.canvasPoints); - this.dorsalRightEdge = this.line(viewModel.dr.canvasPoints); - this.dorsalLeftEdge = this.line(viewModel.dl.canvasPoints); + setupEdges(viewModel: any) { + this.frontLeftEdge = this.line(viewModel.fl.points); + this.frontRightEdge = this.line(viewModel.fr.points); + this.dorsalRightEdge = this.line(viewModel.dr.points); + this.dorsalLeftEdge = this.line(viewModel.dl.points); - this.frontTopEdge = this.line(viewModel.ft.canvasPoints); - this.rightTopEdge = this.line(viewModel.rt.canvasPoints); - this.frontBotEdge = this.line(viewModel.fb.canvasPoints); - this.rightBotEdge = this.line(viewModel.rb.canvasPoints); + this.frontTopEdge = this.line(viewModel.ft.points); + this.rightTopEdge = this.line(viewModel.rt.points); + this.frontBotEdge = this.line(viewModel.fb.points); + this.rightBotEdge = this.line(viewModel.rb.points); }, setupGrabPoints() { @@ -262,14 +262,14 @@ SVG.Cube = SVG.invent({ } }, - move(dx, dy) { + move(dx: Number, dy: Number) { this.face.dmove(dx, dy); this.dorsal.dmove(dx, dy); this.right.dmove(dx, dy); this.left.dmove(dx, dy); const edges = this.getEdges(); - edges.forEach((edge) => { + edges.forEach((edge: any) => { edge.dmove(dx, dy); }); }, @@ -292,19 +292,19 @@ SVG.Cube = SVG.invent({ showGrabPoints() { const grabPoints = this.getGrabPoints(); - grabPoints.forEach((point) => { + grabPoints.forEach((point: any) => { point.show(); }); }, hideGrabPoints() { const grabPoints = this.getGrabPoints(); - grabPoints.forEach((point) => { + grabPoints.forEach((point: any) => { point.hide(); }); }, - updateView(viewModel) { + updateView(viewModel: any) { const convertedPoints = window.cvat.translate.points.actualToCanvas( viewModel.getPoints(), ); @@ -315,23 +315,23 @@ SVG.Cube = SVG.invent({ this.attr('points', convertedPoints); }, - updatePolygons(viewModel) { - this.face.plot(viewModel.front.canvasPoints); - this.right.plot(viewModel.right.canvasPoints); - this.dorsal.plot(viewModel.dorsal.canvasPoints); - this.left.plot(viewModel.left.canvasPoints); + updatePolygons(viewModel: any) { + this.face.plot(viewModel.front.points); + this.right.plot(viewModel.right.points); + this.dorsal.plot(viewModel.dorsal.points); + this.left.plot(viewModel.left.points); }, - updateLines(viewModel) { - this.frontLeftEdge.plot(viewModel.fl.canvasPoints); - this.frontRightEdge.plot(viewModel.fr.canvasPoints); - this.dorsalRightEdge.plot(viewModel.dr.canvasPoints); - this.dorsalLeftEdge.plot(viewModel.dl.canvasPoints); + updateLines(viewModel: any) { + this.frontLeftEdge.plot(viewModel.fl.points); + this.frontRightEdge.plot(viewModel.fr.points); + this.dorsalRightEdge.plot(viewModel.dr.points); + this.dorsalLeftEdge.plot(viewModel.dl.points); - this.frontTopEdge.plot(viewModel.ft.canvasPoints); - this.rightTopEdge.plot(viewModel.rt.canvasPoints); - this.frontBotEdge.plot(viewModel.fb.canvasPoints); - this.rightBotEdge.plot(viewModel.rb.canvasPoints); + this.frontTopEdge.plot(viewModel.ft.points); + this.rightTopEdge.plot(viewModel.rt.points); + this.frontBotEdge.plot(viewModel.fb.points); + this.rightBotEdge.plot(viewModel.rb.points); }, updateThickness() { @@ -339,7 +339,7 @@ SVG.Cube = SVG.invent({ const width = this.attr('stroke-width'); const baseWidthOffset = 1.75; const expandedWidthOffset = 3; - edges.forEach((edge) => { + edges.forEach((edge: any) => { edge.on('mouseover', function () { this.attr({ 'stroke-width': width * expandedWidthOffset }); }).on('mouseout', function () { @@ -348,15 +348,15 @@ SVG.Cube = SVG.invent({ }); }, - updateProjections(viewModel) { + updateProjections(viewModel: any) { this.ftProj.plot(this.updateProjectionLine(viewModel.ft.getEquation(), - viewModel.ft.canvasPoints[0], viewModel.vplCanvas)); + viewModel.ft.points[0], viewModel.vplCanvas)); this.fbProj.plot(this.updateProjectionLine(viewModel.fb.getEquation(), - viewModel.ft.canvasPoints[0], viewModel.vplCanvas)); + viewModel.ft.points[0], viewModel.vplCanvas)); this.rtProj.plot(this.updateProjectionLine(viewModel.rt.getEquation(), - viewModel.rt.canvasPoints[1], viewModel.vprCanvas)); + viewModel.rt.points[1], viewModel.vprCanvas)); this.rbProj.plot(this.updateProjectionLine(viewModel.rb.getEquation(), - viewModel.rt.canvasPoints[1], viewModel.vprCanvas)); + viewModel.rt.points[1], viewModel.vprCanvas)); }, paintOrientationLines() { @@ -402,12 +402,12 @@ SVG.Cube = SVG.invent({ return arr; }, - updateProjectionLine(equation, source, direction) { + updateProjectionLine(equation: any, source: any, direction: any) { const x1 = source.x; - const y1 = equation.getYCanvas(x1); + const y1 = equation.getY(x1); const x2 = direction.x; - const y2 = equation.getYCanvas(x2); + const y2 = equation.getY(x2); return [[x1, y1], [x2, y2]]; }, @@ -436,7 +436,7 @@ SVG.Cube = SVG.invent({ removeMouseOverEvents() { const edges = this.getEdges(); - edges.forEach((edge) => { + edges.forEach((edge: any) => { edge.off('mouseover').off('mouseout'); }); this.left.off('mouseover').off('mouseout'); @@ -453,7 +453,7 @@ SVG.Cube = SVG.invent({ addOccluded() { const edges = this.getEdges(); - edges.forEach((edge) => { + edges.forEach((edge: any) => { edge.node.classList.add('occludedShape'); }); this.face.attr('stroke-width', 0); @@ -464,7 +464,7 @@ SVG.Cube = SVG.invent({ removeOccluded() { const edges = this.getEdges(); - edges.forEach((edge) => { + edges.forEach((edge: any) => { edge.node.classList.remove('occludedShape'); }); this.face.attr('stroke-width', this.attr('stroke-width')); @@ -474,7 +474,8 @@ SVG.Cube = SVG.invent({ }, }, construct: { - cube(points) { + cube(points: Number[]) { + // tslint:disable-next-line return this.put(new SVG.Cube()).constructorMethod(points); }, }, From 3a161899793795c72e34839e390d20464b3fde9f Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Wed, 8 Apr 2020 11:54:45 +0300 Subject: [PATCH 06/45] wip --- cvat-canvas/src/typescript/canvasView.ts | 4 +++- cvat-canvas/src/typescript/cuboid.ts | 5 ----- cvat-canvas/src/typescript/svg.patch.ts | 12 ++++++------ 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index 86e7a8cfc566..dcf464751dc1 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -1535,12 +1535,14 @@ export class CanvasViewImpl implements CanvasView, Listener { } // TODO: verify tslint - private addCuboid(points: string, state: any): SVG.Cube { + private addCuboid(points: string, state: any): any { const cube = this.adoptedContent.cube( new Cuboid2PointViewModel(pointsToArray(points)), ).addClass('cvat_canvas_shape'); return cube; + + // return this.addPolyline(points, state); } // tslint:enable diff --git a/cvat-canvas/src/typescript/cuboid.ts b/cvat-canvas/src/typescript/cuboid.ts index 469a6e004276..f58182bfa208 100644 --- a/cvat-canvas/src/typescript/cuboid.ts +++ b/cvat-canvas/src/typescript/cuboid.ts @@ -16,15 +16,11 @@ class Equation { private a: number; private b: number; private c: number; - private cCanvas: number; public constructor(p1: number[], p2: number[]) { this.a = p1[1] - p2[1]; this.b = p2[0] - p1[0]; this.c = this.b * p1[1] + this.a * p1[0]; - - const p1Canvas = { x: p1[0], y: p1[1] }; - this.cCanvas = this.b * p1Canvas.y + this.a * p1Canvas.x; } // get the line equation in actual coordinates @@ -67,7 +63,6 @@ class Figure { class Edge extends Figure { public getEquation(): Equation { let { points } = this; - points = convertToArray(points); return new Equation(points[0], points[1]); } } diff --git a/cvat-canvas/src/typescript/svg.patch.ts b/cvat-canvas/src/typescript/svg.patch.ts index d36e7b7346bc..0b1d628bc60f 100644 --- a/cvat-canvas/src/typescript/svg.patch.ts +++ b/cvat-canvas/src/typescript/svg.patch.ts @@ -208,13 +208,13 @@ SVG.Cube = SVG.invent({ setupProjections(viewModel: any) { this.ftProj = this.line(this.updateProjectionLine(viewModel.ft.getEquation(), - viewModel.ft.points[0], viewModel.vplCanvas)); + viewModel.ft.points[0], viewModel.vpl)); this.fbProj = this.line(this.updateProjectionLine(viewModel.fb.getEquation(), - viewModel.ft.points[0], viewModel.vplCanvas)); + viewModel.ft.points[0], viewModel.vpl)); this.rtProj = this.line(this.updateProjectionLine(viewModel.rt.getEquation(), - viewModel.rt.points[1], viewModel.vprCanvas)); + viewModel.rt.points[1], viewModel.vpr)); this.rbProj = this.line(this.updateProjectionLine(viewModel.rb.getEquation(), - viewModel.rb.points[1], viewModel.vprCanvas)); + viewModel.rb.points[1], viewModel.vpr)); this.ftProj.stroke({ color: '#C0C0C0' }); this.fbProj.stroke({ color: '#C0C0C0' }); @@ -403,10 +403,10 @@ SVG.Cube = SVG.invent({ }, updateProjectionLine(equation: any, source: any, direction: any) { - const x1 = source.x; + const x1 = source[0]; const y1 = equation.getY(x1); - const x2 = direction.x; + const x2 = direction[0]; const y2 = equation.getY(x2); return [[x1, y1], [x2, y2]]; }, From f23e35428f6edd658f94db3d6009255330a236a1 Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Mon, 13 Apr 2020 11:15:36 +0300 Subject: [PATCH 07/45] transfer to actual points in cuboids --- cvat-canvas/src/typescript/svg.patch.ts | 111 ++++++++++++------------ 1 file changed, 55 insertions(+), 56 deletions(-) diff --git a/cvat-canvas/src/typescript/svg.patch.ts b/cvat-canvas/src/typescript/svg.patch.ts index 0b1d628bc60f..7376a3096860 100644 --- a/cvat-canvas/src/typescript/svg.patch.ts +++ b/cvat-canvas/src/typescript/svg.patch.ts @@ -186,7 +186,7 @@ SVG.Cube = SVG.invent({ inherit: SVG.G, extend: { - constructorMethod(viewModel: any) { + constructorMethod(viewModel) { this.attr('points', viewModel.getPoints()); this.projectionLineEnable = false; this.setupFaces(viewModel); @@ -199,22 +199,22 @@ SVG.Cube = SVG.invent({ return this; }, - setupFaces(viewModel: any) { - this.face = this.polygon(viewModel.front.points); - this.right = this.polygon(viewModel.right.points); - this.dorsal = this.polygon(viewModel.dorsal.points); - this.left = this.polygon(viewModel.left.points); + setupFaces(viewModel) { + this.face = this.polygon(viewModel.front.canvasPoints); + this.right = this.polygon(viewModel.right.canvasPoints); + this.dorsal = this.polygon(viewModel.dorsal.canvasPoints); + this.left = this.polygon(viewModel.left.canvasPoints); }, - setupProjections(viewModel: any) { + setupProjections(viewModel) { this.ftProj = this.line(this.updateProjectionLine(viewModel.ft.getEquation(), - viewModel.ft.points[0], viewModel.vpl)); + viewModel.ft.canvasPoints[0], viewModel.vplCanvas)); this.fbProj = this.line(this.updateProjectionLine(viewModel.fb.getEquation(), - viewModel.ft.points[0], viewModel.vpl)); + viewModel.ft.canvasPoints[0], viewModel.vplCanvas)); this.rtProj = this.line(this.updateProjectionLine(viewModel.rt.getEquation(), - viewModel.rt.points[1], viewModel.vpr)); + viewModel.rt.canvasPoints[1], viewModel.vprCanvas)); this.rbProj = this.line(this.updateProjectionLine(viewModel.rb.getEquation(), - viewModel.rb.points[1], viewModel.vpr)); + viewModel.rb.canvasPoints[1], viewModel.vprCanvas)); this.ftProj.stroke({ color: '#C0C0C0' }); this.fbProj.stroke({ color: '#C0C0C0' }); @@ -222,16 +222,16 @@ SVG.Cube = SVG.invent({ this.rbProj.stroke({ color: '#C0C0C0' }); }, - setupEdges(viewModel: any) { - this.frontLeftEdge = this.line(viewModel.fl.points); - this.frontRightEdge = this.line(viewModel.fr.points); - this.dorsalRightEdge = this.line(viewModel.dr.points); - this.dorsalLeftEdge = this.line(viewModel.dl.points); + setupEdges(viewModel) { + this.frontLeftEdge = this.line(viewModel.fl.canvasPoints); + this.frontRightEdge = this.line(viewModel.fr.canvasPoints); + this.dorsalRightEdge = this.line(viewModel.dr.canvasPoints); + this.dorsalLeftEdge = this.line(viewModel.dl.canvasPoints); - this.frontTopEdge = this.line(viewModel.ft.points); - this.rightTopEdge = this.line(viewModel.rt.points); - this.frontBotEdge = this.line(viewModel.fb.points); - this.rightBotEdge = this.line(viewModel.rb.points); + this.frontTopEdge = this.line(viewModel.ft.canvasPoints); + this.rightTopEdge = this.line(viewModel.rt.canvasPoints); + this.frontBotEdge = this.line(viewModel.fb.canvasPoints); + this.rightBotEdge = this.line(viewModel.rb.canvasPoints); }, setupGrabPoints() { @@ -262,14 +262,14 @@ SVG.Cube = SVG.invent({ } }, - move(dx: Number, dy: Number) { + move(dx, dy) { this.face.dmove(dx, dy); this.dorsal.dmove(dx, dy); this.right.dmove(dx, dy); this.left.dmove(dx, dy); const edges = this.getEdges(); - edges.forEach((edge: any) => { + edges.forEach((edge) => { edge.dmove(dx, dy); }); }, @@ -292,19 +292,19 @@ SVG.Cube = SVG.invent({ showGrabPoints() { const grabPoints = this.getGrabPoints(); - grabPoints.forEach((point: any) => { + grabPoints.forEach((point) => { point.show(); }); }, hideGrabPoints() { const grabPoints = this.getGrabPoints(); - grabPoints.forEach((point: any) => { + grabPoints.forEach((point) => { point.hide(); }); }, - updateView(viewModel: any) { + updateView(viewModel) { const convertedPoints = window.cvat.translate.points.actualToCanvas( viewModel.getPoints(), ); @@ -315,23 +315,23 @@ SVG.Cube = SVG.invent({ this.attr('points', convertedPoints); }, - updatePolygons(viewModel: any) { - this.face.plot(viewModel.front.points); - this.right.plot(viewModel.right.points); - this.dorsal.plot(viewModel.dorsal.points); - this.left.plot(viewModel.left.points); + updatePolygons(viewModel) { + this.face.plot(viewModel.front.canvasPoints); + this.right.plot(viewModel.right.canvasPoints); + this.dorsal.plot(viewModel.dorsal.canvasPoints); + this.left.plot(viewModel.left.canvasPoints); }, - updateLines(viewModel: any) { - this.frontLeftEdge.plot(viewModel.fl.points); - this.frontRightEdge.plot(viewModel.fr.points); - this.dorsalRightEdge.plot(viewModel.dr.points); - this.dorsalLeftEdge.plot(viewModel.dl.points); + updateLines(viewModel) { + this.frontLeftEdge.plot(viewModel.fl.canvasPoints); + this.frontRightEdge.plot(viewModel.fr.canvasPoints); + this.dorsalRightEdge.plot(viewModel.dr.canvasPoints); + this.dorsalLeftEdge.plot(viewModel.dl.canvasPoints); - this.frontTopEdge.plot(viewModel.ft.points); - this.rightTopEdge.plot(viewModel.rt.points); - this.frontBotEdge.plot(viewModel.fb.points); - this.rightBotEdge.plot(viewModel.rb.points); + this.frontTopEdge.plot(viewModel.ft.canvasPoints); + this.rightTopEdge.plot(viewModel.rt.canvasPoints); + this.frontBotEdge.plot(viewModel.fb.canvasPoints); + this.rightBotEdge.plot(viewModel.rb.canvasPoints); }, updateThickness() { @@ -339,7 +339,7 @@ SVG.Cube = SVG.invent({ const width = this.attr('stroke-width'); const baseWidthOffset = 1.75; const expandedWidthOffset = 3; - edges.forEach((edge: any) => { + edges.forEach((edge) => { edge.on('mouseover', function () { this.attr({ 'stroke-width': width * expandedWidthOffset }); }).on('mouseout', function () { @@ -348,15 +348,15 @@ SVG.Cube = SVG.invent({ }); }, - updateProjections(viewModel: any) { + updateProjections(viewModel) { this.ftProj.plot(this.updateProjectionLine(viewModel.ft.getEquation(), - viewModel.ft.points[0], viewModel.vplCanvas)); + viewModel.ft.canvasPoints[0], viewModel.vplCanvas)); this.fbProj.plot(this.updateProjectionLine(viewModel.fb.getEquation(), - viewModel.ft.points[0], viewModel.vplCanvas)); + viewModel.ft.canvasPoints[0], viewModel.vplCanvas)); this.rtProj.plot(this.updateProjectionLine(viewModel.rt.getEquation(), - viewModel.rt.points[1], viewModel.vprCanvas)); + viewModel.rt.canvasPoints[1], viewModel.vprCanvas)); this.rbProj.plot(this.updateProjectionLine(viewModel.rb.getEquation(), - viewModel.rt.points[1], viewModel.vprCanvas)); + viewModel.rt.canvasPoints[1], viewModel.vprCanvas)); }, paintOrientationLines() { @@ -402,12 +402,12 @@ SVG.Cube = SVG.invent({ return arr; }, - updateProjectionLine(equation: any, source: any, direction: any) { - const x1 = source[0]; - const y1 = equation.getY(x1); + updateProjectionLine(equation, source, direction) { + const x1 = source.x; + const y1 = equation.getYCanvas(x1); - const x2 = direction[0]; - const y2 = equation.getY(x2); + const x2 = direction.x; + const y2 = equation.getYCanvas(x2); return [[x1, y1], [x2, y2]]; }, @@ -436,7 +436,7 @@ SVG.Cube = SVG.invent({ removeMouseOverEvents() { const edges = this.getEdges(); - edges.forEach((edge: any) => { + edges.forEach((edge) => { edge.off('mouseover').off('mouseout'); }); this.left.off('mouseover').off('mouseout'); @@ -453,7 +453,7 @@ SVG.Cube = SVG.invent({ addOccluded() { const edges = this.getEdges(); - edges.forEach((edge: any) => { + edges.forEach((edge) => { edge.node.classList.add('occludedShape'); }); this.face.attr('stroke-width', 0); @@ -464,7 +464,7 @@ SVG.Cube = SVG.invent({ removeOccluded() { const edges = this.getEdges(); - edges.forEach((edge: any) => { + edges.forEach((edge) => { edge.node.classList.remove('occludedShape'); }); this.face.attr('stroke-width', this.attr('stroke-width')); @@ -474,9 +474,8 @@ SVG.Cube = SVG.invent({ }, }, construct: { - cube(points: Number[]) { - // tslint:disable-next-line + cube(points) { return this.put(new SVG.Cube()).constructorMethod(points); }, }, -}); +}); \ No newline at end of file From 7c8340d4c557adc4fe39c4c82d9d9658a9fd35b1 Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Mon, 13 Apr 2020 11:18:57 +0300 Subject: [PATCH 08/45] transfer to actual points in cuboids --- cvat-canvas/src/typescript/canvasView.ts | 44 ++++++++++-- cvat-canvas/src/typescript/cuboid.ts | 86 +++++++++++++++++++----- 2 files changed, 106 insertions(+), 24 deletions(-) diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index dcf464751dc1..bcad559874de 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -1080,6 +1080,9 @@ export class CanvasViewImpl implements CanvasView, Listener { if (state.shapeType === 'rectangle') { this.svgShapes[state.clientID] = this .addRect(translatedPoints, state); + } else if (state.shapeType === 'cuboid') { + this.svgShapes[state.clientID] = this + .addCuboid(state); } else { const stringified = translatedPoints.reduce( (acc: string, val: number, idx: number): string => { @@ -1100,9 +1103,6 @@ export class CanvasViewImpl implements CanvasView, Listener { } else if (state.shapeType === 'points') { this.svgShapes[state.clientID] = this .addPoints(stringified, state); - } else if (state.shapeType === 'cuboid') { - this.svgShapes[state.clientID] = this - .addCuboid(stringified, state); } } @@ -1259,6 +1259,10 @@ export class CanvasViewImpl implements CanvasView, Listener { return; } + if (state.shapeType === 'cuboid') { + shape.addMouseOverEvents(); + } + shape.addClass('cvat_canvas_shape_activated'); if (state.shapeType === 'points') { this.content.append(this.svgShapes[clientID] @@ -1535,10 +1539,36 @@ export class CanvasViewImpl implements CanvasView, Listener { } // TODO: verify tslint - private addCuboid(points: string, state: any): any { - const cube = this.adoptedContent.cube( - new Cuboid2PointViewModel(pointsToArray(points)), - ).addClass('cvat_canvas_shape'); + private addCuboid(state: any): any { + const { offset } = this.controller.geometry; + const points = state.points.slice(0, 12).reduce( + (acc: any[], current: number, i: number): any[] => { + if (i % 2 === 0) { + acc.push({ x: current }); + return acc; + } + acc.slice(-1)[0].y = current; + return acc; + }, [], + ); + + const model = new Cuboid2PointViewModel(points, offset); + + const cube = this.adoptedContent.cube(model) + .fill(state.color).attr({ + clientID: state.clientID, + 'color-rendering': 'optimizeQuality', + id: `cvat_canvas_shape_${state.clientID}`, + fill: state.color, + 'shape-rendering': 'geometricprecision', + stroke: state.color, + 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, + 'data-z-order': state.zOrder, + }); + + cube.projectionLineEnable = true; + cube.addMouseOverEvents(); + cube.paintOrientationLines(); return cube; diff --git a/cvat-canvas/src/typescript/cuboid.ts b/cvat-canvas/src/typescript/cuboid.ts index f58182bfa208..ae627e4b0414 100644 --- a/cvat-canvas/src/typescript/cuboid.ts +++ b/cvat-canvas/src/typescript/cuboid.ts @@ -12,28 +12,45 @@ import { convertToArray, intersection, convertArrayToDoubleArray } from './share const MIN_EDGE_LENGTH = 3; +interface Point { + x: number; + y: number; +} + class Equation { private a: number; private b: number; private c: number; + private cCanvas: number; + private viewModel: Cuboid2PointViewModel; - public constructor(p1: number[], p2: number[]) { + public constructor(p1: number[], p2: number[], model: Cuboid2PointViewModel) { + this.viewModel = model; this.a = p1[1] - p2[1]; this.b = p2[0] - p1[0]; this.c = this.b * p1[1] + this.a * p1[0]; + + const temp = { x: p1[0], y: p1[1] }; + const p1Canvas = this.viewModel.actualToCanvas([temp])[0]; + this.cCanvas = this.b * p1Canvas.y + this.a * p1Canvas.x; } // get the line equation in actual coordinates public getY(x: number): number { return (this.c - this.a * x) / this.b; } + + // get the line equation in canvas coordinates + public getYCanvas(x: number): number { + return (this.cCanvas - this.a * x) / this.b; + } } class Figure { private indices: number[]; - private viewmodel: any; + public viewmodel: Cuboid2PointViewModel; - public constructor(indices: number[], Vmodel: any) { + public constructor(indices: number[], Vmodel: Cuboid2PointViewModel) { this.indices = indices; this.viewmodel = Vmodel; } @@ -58,17 +75,25 @@ class Figure { } } } + + public get canvasPoints(): Point[] { + let { points } = this; + points = this.viewmodel.actualToCanvas(points); + return points; + } } class Edge extends Figure { public getEquation(): Equation { let { points } = this; - return new Equation(points[0], points[1]); + points = convertToArray(points); + return new Equation(points[0], points[1], this.viewmodel); } } export class Cuboid2PointViewModel { - private points: any[]; + private canvasOffset: number; + public points: any[]; private fr: Edge; private fl: Edge; private dr: Edge; @@ -92,8 +117,9 @@ export class Cuboid2PointViewModel { public vpl: number[] | null; public vpr: number[] | null; - public constructor(points: any[]) { - this.points = convertArrayToDoubleArray(points); + public constructor(points: Point[], offset: number) { + this.canvasOffset = offset; + this.points = points; this.initEdges(); this.initFaces(); this.updateVanishingPoints(); @@ -101,11 +127,25 @@ export class Cuboid2PointViewModel { this.updatePoints(); } - public getPoints(): any[] { + public canvasToActual(points: Point[]): Point[] { + return points.map((point): Point => ({ + x: point.x - this.canvasOffset, + y: point.y - this.canvasOffset, + })); + } + + public actualToCanvas(points: Point[]): Point[] { + return points.map((point): Point => ({ + x: point.x + this.canvasOffset, + y: point.y + this.canvasOffset, + })); + } + + public getPoints(): Point[] { return this.points; } - public setPoints(points: any[]): void { + public setPoints(points: Point[]): void { this.points = points; } @@ -118,7 +158,7 @@ export class Cuboid2PointViewModel { } public computeSideEdgeConstraints(edge: any): any { - const midLength = this.fr.points[1].y - this.fr.points[0].y - 1; + const midLength = this.fr.canvasPoints[1].y - this.fr.canvasPoints[0].y - 1; const minY = edge.canvasPoints[1].y - midLength; const maxY = edge.canvasPoints[0].y + midLength; @@ -150,9 +190,9 @@ export class Cuboid2PointViewModel { // boolean value parameter controls which edges should be used to recalculate vanishing points private updateVanishingPoints(): void { - const leftEdge = this.fl.points; - const rightEdge = this.dr.points; - const midEdge = this.fr.points; + const leftEdge = convertToArray(this.fl.points); + const rightEdge = convertToArray(this.dr.points); + const midEdge = convertToArray(this.fr.points); this.vpl = intersection(leftEdge[0], midEdge[0], leftEdge[1], midEdge[1]); this.vpr = intersection(rightEdge[0], midEdge[0], rightEdge[1], midEdge[1]); @@ -205,8 +245,8 @@ export class Cuboid2PointViewModel { private buildBackEdge(): void { this.updateVanishingPoints(); - const leftPoints = this.dr.points; - const rightPoints = this.fl.points; + const leftPoints = convertToArray(this.dr.points); + const rightPoints = convertToArray(this.fl.points); const topIndex = 6; const botIndex = 7; @@ -222,12 +262,24 @@ export class Cuboid2PointViewModel { p2 = [p1[0], vpLeft[1]]; } - this.points[topIndex] = p1; - this.points[botIndex] = p2; + this.points[topIndex] = { x: p1[0], y: p1[1] }; + this.points[botIndex] = { x: p2[0], y: p2[1] }; // Making sure that the vertical edges stay vertical this.updatePoints(); } + + public get vplCanvas(): Point { + const { vpl } = this; + const vp = { x: vpl[0], y: vpl[1] }; + return this.actualToCanvas([vp])[0]; + } + + public get vprCanvas(): Point { + const { vpr } = this; + const vp = { x: vpr[0], y: vpr[1] }; + return this.actualToCanvas([vp])[0]; + } } function sortPointsClockwise(points: any[]): any[] { From 8b646cb2be90658d4b20fdeaee1b548a09126209 Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Tue, 14 Apr 2020 21:16:52 +0300 Subject: [PATCH 09/45] wip --- cvat-canvas/src/typescript/canvasView.ts | 109 +++++---- cvat-canvas/src/typescript/cuboid.ts | 6 +- cvat-canvas/src/typescript/shared.ts | 4 + cvat-canvas/src/typescript/svg.patch.ts | 288 ++++++++++++++++++++++- 4 files changed, 351 insertions(+), 56 deletions(-) diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index bcad559874de..91e7652f8781 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -1259,10 +1259,6 @@ export class CanvasViewImpl implements CanvasView, Listener { return; } - if (state.shapeType === 'cuboid') { - shape.addMouseOverEvents(); - } - shape.addClass('cvat_canvas_shape_activated'); if (state.shapeType === 'points') { this.content.append(this.svgShapes[clientID] @@ -1271,7 +1267,7 @@ export class CanvasViewImpl implements CanvasView, Listener { this.content.append(shape.node); } - if (!state.pinned) { + if (!state.pinned && state.shapeType !== 'cuboid') { (shape as any).draggable().on('dragstart', (): void => { this.mode = Mode.DRAG; if (text) { @@ -1315,57 +1311,68 @@ export class CanvasViewImpl implements CanvasView, Listener { this.selectize(true, shape); } - let shapeSizeElement: ShapeSizeElement | null = null; - let resized = false; - (shape as any).resize().on('resizestart', (): void => { - this.mode = Mode.RESIZE; - if (state.shapeType === 'rectangle') { - shapeSizeElement = displayShapeSize(this.adoptedContent, this.adoptedText); - } - resized = false; - if (text) { - text.addClass('cvat_canvas_hidden'); - } - }).on('resizing', (): void => { - resized = true; - if (shapeSizeElement) { - shapeSizeElement.update(shape); - } - }).on('resizedone', (): void => { - if (shapeSizeElement) { - shapeSizeElement.rm(); - } + if (state.shapeType !== 'cuboid') { + let shapeSizeElement: ShapeSizeElement | null = null; + let resized = false; + (shape as any).resize().on('resizestart', (): void => { + this.mode = Mode.RESIZE; + if (state.shapeType === 'rectangle') { + shapeSizeElement = displayShapeSize(this.adoptedContent, this.adoptedText); + } + resized = false; + if (text) { + text.addClass('cvat_canvas_hidden'); + } + }).on('resizing', (): void => { + resized = true; + if (shapeSizeElement) { + shapeSizeElement.update(shape); + } + }).on('resizedone', (): void => { + if (shapeSizeElement) { + shapeSizeElement.rm(); + } - if (text) { - text.removeClass('cvat_canvas_hidden'); - this.updateTextPosition( - text, - shape, - ); - } + if (text) { + text.removeClass('cvat_canvas_hidden'); + this.updateTextPosition( + text, + shape, + ); + } - this.mode = Mode.IDLE; + this.mode = Mode.IDLE; - if (resized) { - const { offset } = this.controller.geometry; + if (resized) { + const { offset } = this.controller.geometry; - const points = pointsToArray( - shape.attr('points') || `${shape.attr('x')},${shape.attr('y')} ` - + `${shape.attr('x') + shape.attr('width')},` - + `${shape.attr('y') + shape.attr('height')}`, - ).map((x: number): number => x - offset); + const points = pointsToArray( + shape.attr('points') || `${shape.attr('x')},${shape.attr('y')} ` + + `${shape.attr('x') + shape.attr('width')},` + + `${shape.attr('y') + shape.attr('height')}`, + ).map((x: number): number => x - offset); - this.drawnStates[state.clientID].points = points; - this.canvas.dispatchEvent(new CustomEvent('canvas.resizeshape', { - bubbles: false, - cancelable: true, - detail: { - id: state.clientID, - }, - })); - this.onEditDone(state, points); - } - }); + this.drawnStates[state.clientID].points = points; + this.canvas.dispatchEvent(new CustomEvent('canvas.resizeshape', { + bubbles: false, + cancelable: true, + detail: { + id: state.clientID, + }, + })); + this.onEditDone(state, points); + } + }); + } + + + if (shape && state.shapeType === 'cuboid') { + (shape as any).selectize(false); + (shape as any).addEvents(); + (shape as any).showGrabPoints(consts.BASE_POINT_SIZE / this.geometry.scale, + consts.BASE_STROKE_WIDTH / this.geometry.scale); + (shape as any).addMouseOverEvents(); + } this.activeElement = { ...this.activeElement, diff --git a/cvat-canvas/src/typescript/cuboid.ts b/cvat-canvas/src/typescript/cuboid.ts index ae627e4b0414..7b38cb08f70e 100644 --- a/cvat-canvas/src/typescript/cuboid.ts +++ b/cvat-canvas/src/typescript/cuboid.ts @@ -8,16 +8,16 @@ */ -import { convertToArray, intersection, convertArrayToDoubleArray } from './shared'; +import { convertToArray, intersection } from './shared'; -const MIN_EDGE_LENGTH = 3; +export const MIN_EDGE_LENGTH = 3; interface Point { x: number; y: number; } -class Equation { +export class Equation { private a: number; private b: number; private c: number; diff --git a/cvat-canvas/src/typescript/shared.ts b/cvat-canvas/src/typescript/shared.ts index da5f62737531..63a4e7ba6531 100644 --- a/cvat-canvas/src/typescript/shared.ts +++ b/cvat-canvas/src/typescript/shared.ts @@ -165,3 +165,7 @@ export function intersection( return null; } + +export function clamp(x: number, min: number, max: number): number { + return Math.min(Math.max(x, min), max); +}; diff --git a/cvat-canvas/src/typescript/svg.patch.ts b/cvat-canvas/src/typescript/svg.patch.ts index 7376a3096860..785698f8ff06 100644 --- a/cvat-canvas/src/typescript/svg.patch.ts +++ b/cvat-canvas/src/typescript/svg.patch.ts @@ -9,6 +9,8 @@ import 'svg.resize.js'; import 'svg.select.js'; import 'svg.draw.js'; +import { Equation, MIN_EDGE_LENGTH } from './cuboid'; + // Update constructor const originalDraw = SVG.Element.prototype.draw; SVG.Element.prototype.draw = function constructor(...args: any): any { @@ -187,6 +189,7 @@ SVG.Cube = SVG.invent({ extend: { constructorMethod(viewModel) { + this.viewModel = viewModel; this.attr('points', viewModel.getPoints()); this.projectionLineEnable = false; this.setupFaces(viewModel); @@ -253,6 +256,266 @@ SVG.Cube = SVG.invent({ } }, + addEvents() { + const edges = this.getEdges(); + const grabPoints =this.getGrabPoints(); + const draggableFaces = [ + this.left, + this.dorsal, + this.right, + ]; + + if (this.viewModel.dl.points[0].x > this.viewModel.fl.points[0].x) { + this.orientation = 1; + } else { + this.orientation = 2; + } + + this.updateGrabPoints(); + edges.forEach((edge) => { + edge.on('resizestart', () => { + // TODO: dipatch proper canvas event + }).on('resizedone', () => { + this.updateModel(); + this.updateViewModel(); + // TODO: dipatch proper canvas event + }); + }); + grabPoints.forEach((grabPoint) => { + grabPoint.on('dragstart', () => { + // TODO: dipatch proper canvas event + }).on('dragend', () => { + // TODO: dipatch proper canvas event + this.updateModel(); + this.updateViewModel(); + }); + }); + + draggableFaces.forEach((face) => { + face.on('dragstart', () => { + // TODO: dipatch proper canvas event + }).on('dragend', () => { + // TODO: dipatch proper canvas event + this.updateModel(); + this.updateViewModel(); + this.updateGrabPoints(); + }); + }); + + this.makeDraggable(); + // this.makeResizable(); + }, + + makeDraggable() { + let startPoint: any = null; + let startPosition: any = null; + + this.draggable().off('dragend').on('dragstart', (e) => { + startPoint = e.detail.p; + startPosition = this.viewModel.getPoints(); + }).on('dragmove', (e) => { + e.preventDefault(); + this.translatePoints(startPoint, startPosition, e.detail.p); + this.refreshView(); + }).on('dragend', () => { + this.updateModel(); + this.updateViewModel(); + }); + + // Controllable vertical edges + this.flCenter.draggable(function (x) { + const vpX = this.cx() - this.viewModel.vplCanvas.x > 0 ? this.viewModel.vplCanvas.x : 0; + return { x: x < this.viewModel.fr.canvasPoints[0].x && x > vpX + MIN_EDGE_LENGTH }; + }).on('dragmove', function () { + this.frontLeftEdge.center(this.cx(), this.cy()); + + const [ position ] = this.viewModel.canvasToActual([{x: this.frontLeftEdge.attr('x1'), y: this.frontLeftEdge.attr('y1')}]); + const { x } = position; + + const y1 = this.viewModel.ft.getEquation().getY(x); + const y2 = this.viewModel.fb.getEquation().getY(x); + + const topPoint = { x, y: y1 }; + const botPoint = { x, y: y2 }; + + this.viewModel.fl.points = [topPoint, botPoint]; + this.updateViewAndVM(); + }); + + this.drCenter.draggable(function (x) { + let xStatus; + if (this.cx() < this.viewModel.fr.canvasPoints[0].x) { + xStatus = x < this.viewModel.fr.canvasPoints[0].x - MIN_EDGE_LENGTH + && x > this.viewModel.vprCanvas.x + MIN_EDGE_LENGTH; + } else { + xStatus = x > this.viewModel.fr.canvasPoints[0].x + MIN_EDGE_LENGTH + && x < this.viewModel.vprCanvas.x - MIN_EDGE_LENGTH; + } + return { x: xStatus, y: this.attr('y1') }; + }).on('dragmove', function () { + this.dorsalRightEdge.center(this.cx(), this.cy()); + + const [ position ] = this.viewModel.canvasToActual([{x: this.dorsalRightEdge.attr('x1'), y: this.dorsalRightEdge.attr('y1')}]); + const { x } = position; + + const y1 = this.viewModel.rt.getEquation().getY(x); + const y2 = this.viewModel.rb.getEquation().getY(x); + + const topPoint = { x, y: y1 }; + const botPoint = { x, y: y2 }; + + this.viewModel.dr.points = [topPoint, botPoint]; + this.updateViewAndVM(); + }); + + this.dlCenter.draggable(function (x) { + let xStatus; + if (this.cx() < this.viewModel.fl.canvasPoints[0].x) { + xStatus = x < this.viewModel.fl.canvasPoints[0].x - MIN_EDGE_LENGTH + && x > this.viewModel.vprCanvas.x + MIN_EDGE_LENGTH; + } else { + xStatus = x > this.viewModel.fl.canvasPoints[0].x + MIN_EDGE_LENGTH + && x < this.viewModel.vprCanvas.x + MIN_EDGE_LENGTH; + } + return { x: xStatus, y: this.attr('y1') }; + }).on('dragmove', function () { + this.dorsalLeftEdge.center(this.cx(), this.cy()); + + const position = this.viewModel.canvasToActual([{x: this.dorsalLeftEdge.attr('x1'), y: this.dorsalLeftEdge.attr('y1')}]); + const { x } = position; + + const y1 = this.viewModel.lt.getEquation().getY(x); + const y2 = this.viewModel.lb.getEquation().getY(x); + + const topPoint = { x, y: y1 }; + const botPoint = { x, y: y2 }; + + this.viewModel.dl.points = [topPoint, botPoint]; + this.updateViewAndVM(true); + }); + + this.frCenter.draggable((x) => { + return { x: x > this.viewModel.fl.canvasPoints[0].x, y: this.attr('y1') }; + }).on('dragmove', () => { + this.frontRightEdge.center(this.cx(), this.cy()); + + const [ position ]= this.viewModel.canvasToActual([{x: this.frontRightEdge.attr('x1'), y: this.frontRightEdge.attr('y1')}]); + const { x } = position; + + const y1 = this.viewModel.ft.getEquation().getY(x); + const y2 = this.viewModel.fb.getEquation().getY(x); + + const topPoint = { x, y: y1 }; + const botPoint = { x, y: y2 }; + + this.viewModel.fr.points = [topPoint, botPoint]; + this.updateViewAndVM(true); + }); + + + // Controllable 'horizontal' edges + this.ftCenter.draggable((x, y) => { + return { x: x === this.cx(), y: y < this.fbCenter.cy() - MIN_EDGE_LENGTH }; + }).on('dragmove', () => { + this.frontTopEdge.center(this.cx(), this.cy()); + this.horizontalEdgeControl(this.viewModel.top, this.frontTopEdge.attr('x2'), this.frontTopEdge.attr('y2')); + this.updateViewAndVM(); + }); + + this.fbCenter.draggable((x, y) => { + return { x: x === this.cx(), y: y > this.ftCenter.cy() + MIN_EDGE_LENGTH }; + }).on('dragmove', () => { + this.frontBotEdge.center(this.cx(), this.cy()); + this.horizontalEdgeControl(this.viewModel.bot, this.frontBotEdge.attr('x2'), this.frontBotEdge.attr('y2')); + this.updateViewAndVM(); + }); + + // Controllable faces + this.left.draggable((x, y) => ({ x: x < Math.min(this.viewModel.dr.canvasPoints[0].x, this.viewModel.fr.canvasPoints[0].x) - MIN_EDGE_LENGTH, y })).on('dragmove', () => { + this.faceDragControl(this.viewModel.left, this.attr('points')); + }); + this.dorsal.draggable().on('dragmove', () => { + this.faceDragControl(this.viewModel.dorsal, this.attr('points')); + }); + this.right.draggable((x, y) => ({ x: x > Math.min(this.viewModel.dl.canvasPoints[0].x, this.viewModel.fl.canvasPoints[0].x) + MIN_EDGE_LENGTH, y })).on('dragmove', () => { + this.faceDragControl(this.viewModel.right, this.attr('points'), true); + }); + }, + + translatePoints(startPoint, startPosition, currentPosition) { + const dx = currentPosition.x - startPoint.x; + const dy = currentPosition.y - startPoint.y; + const newPoints: any[] = []; + for (let i = 0; i < startPosition.length; i += 1) { + newPoints[i] = { x: startPosition[i].x + dx, y: startPosition[i].y + dy }; + } + this.viewModel.setPoints(newPoints); + }, + + computeHeightFace(point, index) { + switch (index) { + // fl + case 1: { + const p2 = this.updatedEdge(this.viewModel.fr.points[0], point, this.viewModel.vpl); + const p3 = this.updatedEdge(this.viewModel.dr.points[0], p2, this.viewModel.vpr); + const p4 = this.updatedEdge(this.viewModel.dl.points[0], point, this.viewModel.vpr); + return [point, p2, p3, p4]; + } + // fr + case 2: { + const p2 = this.updatedEdge(this.viewModel.fl.points[0], point, this.viewModel.vpl); + const p3 = this.updatedEdge(this.viewModel.dr.points[0], point, this.viewModel.vpr); + const p4 = this.updatedEdge(this.viewModel.dl.points[0], p3, this.viewModel.vpr); + return [p2, point, p3, p4]; + } + // dr + case 3: { + const p2 = this.updatedEdge(this.viewModel.dl.points[0], point, this.viewModel.vpl); + const p3 = this.updatedEdge(this.viewModel.fr.points[0], point, this.viewModel.vpr); + const p4 = this.updatedEdge(this.viewModel.fl.points[0], p2, this.viewModel.vpr); + return [p4, p3, point, p2]; + } + // dl + case 4: { + const p2 = this.updatedEdge(this.viewModel.dr.points[0], point, this.viewModel.vpl); + const p3 = this.updatedEdge(this.viewModel.fl.points[0], point, this.viewModel.vpr); + const p4 = this.updatedEdge(this.viewModel.fr.points[0], p2, this.viewModel.vpr); + return [p3, p4, p2, point]; + } + default: { + return [null, null, null, null]; + } + } + }, + + updateViewAndVM() { + this.viewModel.buildBackEdge(); + this.refreshView(); + }, + + refreshView() { + // TODO: fixit + this.udpateView(this.viewModel); + }, + + updatedEdge(target, base, pivot) { + const targetX = target.x; + const line = new Equation(pivot, + [base.x, base.y], this.viewModel); + const newY = line.getY(targetX); + return { x: targetX, y: newY }; + }, + + resizeControl(vmEdge, updatedEdge, constraints) { + const [ topPoint ] = this.viewModel.canvasToActual([{x: updatedEdge.attr('x1'), y: updatedEdge.attr('y1')}]); + const [ botPoint ] = this.viewModel.canvasToActual([{x: updatedEdge.attr('x2'), y: updatedEdge.attr('y2')}]); + + topPoint.y = Math.min(Math.max(topPoint.y, constraints.y1Range.min), constraints.y1Range.max); + botPoint.y = Math.min(Math.max(botPoint.y, constraints.y2Range.min), constraints.y2Range.max); + + vmEdge.points = [topPoint, botPoint]; + }, + updateGrabPoints() { const centers = this.getGrabPoints(); const edges = this.getEdges(); @@ -260,6 +523,27 @@ SVG.Cube = SVG.invent({ const edge = edges[`${i}`]; centers[`${i}`].center(edge.cx(), edge.cy()); } + + this.dorsalRightEdge.selectize({ + points: 't,b', + rotationPoint: false, + }).resize().on('resizing', function (e) { + if (e.detail.event.shiftKey) { + this.resizeControl(this.viewModel.dr, + this, + this.viewModel.computeSideEdgeConstraints(this.viewModel.dr)); + } else { + const [ midPointUp ] = this.viewModel.canvasToActual([{x: this.dorsalRightEdge.attr('x1'), y: this.dorsalRightEdge.attr('y1')}])[0]; + const [ midPointDown ] = this.viewModel.canvasToActual([{x: this.dorsalRightEdge.attr('x2'), y: this.dorsalRightEdge.attr('y2')}])[0]; + this.viewModel.top.points = this.computeHeightFace(midPointUp, 3); + this.viewModel.bot.points = this.computeHeightFace(midPointDown, 3); + } + this.updateViewAndVM(); + }); + this.drCenter.show(); + + this.dorsalLeftEdge.selectize(false); + this.dlCenter.hide(); }, move(dx, dy) { @@ -290,10 +574,10 @@ SVG.Cube = SVG.invent({ this.rbProj.hide(); }, - showGrabPoints() { + showGrabPoints(radius: number, stroke: number, color: string) { const grabPoints = this.getGrabPoints(); grabPoints.forEach((point) => { - point.show(); + point.radius(radius).attr('stroke-width', stroke).show(); }); }, From fb5bf71baabee0fbbd7cf960140b90c200230368 Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Thu, 16 Apr 2020 01:39:05 +0300 Subject: [PATCH 10/45] WIP: cuboid on canvas points only, cuboid selectizing and strokes, color changing --- cvat-canvas/src/typescript/canvasView.ts | 138 ++-- cvat-canvas/src/typescript/consts.ts | 2 + cvat-canvas/src/typescript/cuboid.ts | 198 ++--- cvat-canvas/src/typescript/shared.ts | 59 +- cvat-canvas/src/typescript/svg.patch.ts | 963 ++++++++++++----------- 5 files changed, 647 insertions(+), 713 deletions(-) diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index 91e7652f8781..7893012f17f2 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -38,7 +38,6 @@ import { Size, Configuration, } from './canvasModel'; -import { Cuboid2PointViewModel } from './cuboid'; export interface CanvasView { html(): HTMLDivElement; @@ -1080,9 +1079,6 @@ export class CanvasViewImpl implements CanvasView, Listener { if (state.shapeType === 'rectangle') { this.svgShapes[state.clientID] = this .addRect(translatedPoints, state); - } else if (state.shapeType === 'cuboid') { - this.svgShapes[state.clientID] = this - .addCuboid(state); } else { const stringified = translatedPoints.reduce( (acc: string, val: number, idx: number): string => { @@ -1103,6 +1099,9 @@ export class CanvasViewImpl implements CanvasView, Listener { } else if (state.shapeType === 'points') { this.svgShapes[state.clientID] = this .addPoints(stringified, state); + } else if (state.shapeType === 'cuboid') { + this.svgShapes[state.clientID] = this + .addCuboid(stringified, state); } } @@ -1267,7 +1266,7 @@ export class CanvasViewImpl implements CanvasView, Listener { this.content.append(shape.node); } - if (!state.pinned && state.shapeType !== 'cuboid') { + if (!state.pinned) { (shape as any).draggable().on('dragstart', (): void => { this.mode = Mode.DRAG; if (text) { @@ -1311,68 +1310,57 @@ export class CanvasViewImpl implements CanvasView, Listener { this.selectize(true, shape); } - if (state.shapeType !== 'cuboid') { - let shapeSizeElement: ShapeSizeElement | null = null; - let resized = false; - (shape as any).resize().on('resizestart', (): void => { - this.mode = Mode.RESIZE; - if (state.shapeType === 'rectangle') { - shapeSizeElement = displayShapeSize(this.adoptedContent, this.adoptedText); - } - resized = false; - if (text) { - text.addClass('cvat_canvas_hidden'); - } - }).on('resizing', (): void => { - resized = true; - if (shapeSizeElement) { - shapeSizeElement.update(shape); - } - }).on('resizedone', (): void => { - if (shapeSizeElement) { - shapeSizeElement.rm(); - } - - if (text) { - text.removeClass('cvat_canvas_hidden'); - this.updateTextPosition( - text, - shape, - ); - } - - this.mode = Mode.IDLE; + let shapeSizeElement: ShapeSizeElement | null = null; + let resized = false; + (shape as any).resize().on('resizestart', (): void => { + this.mode = Mode.RESIZE; + if (state.shapeType === 'rectangle') { + shapeSizeElement = displayShapeSize(this.adoptedContent, this.adoptedText); + } + resized = false; + if (text) { + text.addClass('cvat_canvas_hidden'); + } + }).on('resizing', (): void => { + resized = true; + if (shapeSizeElement) { + shapeSizeElement.update(shape); + } + }).on('resizedone', (): void => { + if (shapeSizeElement) { + shapeSizeElement.rm(); + } - if (resized) { - const { offset } = this.controller.geometry; + if (text) { + text.removeClass('cvat_canvas_hidden'); + this.updateTextPosition( + text, + shape, + ); + } - const points = pointsToArray( - shape.attr('points') || `${shape.attr('x')},${shape.attr('y')} ` - + `${shape.attr('x') + shape.attr('width')},` - + `${shape.attr('y') + shape.attr('height')}`, - ).map((x: number): number => x - offset); + this.mode = Mode.IDLE; - this.drawnStates[state.clientID].points = points; - this.canvas.dispatchEvent(new CustomEvent('canvas.resizeshape', { - bubbles: false, - cancelable: true, - detail: { - id: state.clientID, - }, - })); - this.onEditDone(state, points); - } - }); - } + if (resized) { + const { offset } = this.controller.geometry; + const points = pointsToArray( + shape.attr('points') || `${shape.attr('x')},${shape.attr('y')} ` + + `${shape.attr('x') + shape.attr('width')},` + + `${shape.attr('y') + shape.attr('height')}`, + ).map((x: number): number => x - offset); - if (shape && state.shapeType === 'cuboid') { - (shape as any).selectize(false); - (shape as any).addEvents(); - (shape as any).showGrabPoints(consts.BASE_POINT_SIZE / this.geometry.scale, - consts.BASE_STROKE_WIDTH / this.geometry.scale); - (shape as any).addMouseOverEvents(); - } + this.drawnStates[state.clientID].points = points; + this.canvas.dispatchEvent(new CustomEvent('canvas.resizeshape', { + bubbles: false, + cancelable: true, + detail: { + id: state.clientID, + }, + })); + this.onEditDone(state, points); + } + }); this.activeElement = { ...this.activeElement, @@ -1545,23 +1533,8 @@ export class CanvasViewImpl implements CanvasView, Listener { return polyline; } - // TODO: verify tslint - private addCuboid(state: any): any { - const { offset } = this.controller.geometry; - const points = state.points.slice(0, 12).reduce( - (acc: any[], current: number, i: number): any[] => { - if (i % 2 === 0) { - acc.push({ x: current }); - return acc; - } - acc.slice(-1)[0].y = current; - return acc; - }, [], - ); - - const model = new Cuboid2PointViewModel(points, offset); - - const cube = this.adoptedContent.cube(model) + private addCuboid(points: string, state: any): any { + const cube = (this.adoptedContent as any).cube(points) .fill(state.color).attr({ clientID: state.clientID, 'color-rendering': 'optimizeQuality', @@ -1573,15 +1546,8 @@ export class CanvasViewImpl implements CanvasView, Listener { 'data-z-order': state.zOrder, }); - cube.projectionLineEnable = true; - cube.addMouseOverEvents(); - cube.paintOrientationLines(); - return cube; - - // return this.addPolyline(points, state); } - // tslint:enable private setupPoints(basicPolyline: SVG.PolyLine, state: any): any { this.selectize(true, basicPolyline); diff --git a/cvat-canvas/src/typescript/consts.ts b/cvat-canvas/src/typescript/consts.ts index 23a36f11c7dc..2945b48d5f53 100644 --- a/cvat-canvas/src/typescript/consts.ts +++ b/cvat-canvas/src/typescript/consts.ts @@ -10,6 +10,7 @@ const AREA_THRESHOLD = 9; const SIZE_THRESHOLD = 3; const POINTS_STROKE_WIDTH = 1.5; const POINTS_SELECTED_STROKE_WIDTH = 4; +const MIN_EDGE_LENGTH = 3; const UNDEFINED_ATTRIBUTE_VALUE = '__undefined__'; export default { @@ -21,5 +22,6 @@ export default { SIZE_THRESHOLD, POINTS_STROKE_WIDTH, POINTS_SELECTED_STROKE_WIDTH, + MIN_EDGE_LENGTH, UNDEFINED_ATTRIBUTE_VALUE, }; diff --git a/cvat-canvas/src/typescript/cuboid.ts b/cvat-canvas/src/typescript/cuboid.ts index 7b38cb08f70e..396418172a97 100644 --- a/cvat-canvas/src/typescript/cuboid.ts +++ b/cvat-canvas/src/typescript/cuboid.ts @@ -7,58 +7,71 @@ * SPDX-License-Identifier: MIT */ +import consts from './consts'; -import { convertToArray, intersection } from './shared'; - -export const MIN_EDGE_LENGTH = 3; - -interface Point { +export interface Point { x: number; y: number; } +function line(p1: Point, p2: Point): number[] { + const a = p1.y - p2.y; + const b = p2.x - p1.x; + const c = b * p1.y + a * p1.x; + return [a, b, c]; +} + +function intersection( + p1: Point, p2: Point, p3: Point, p4: Point, +): Point | null { + const L1 = line(p1, p2); + const L2 = line(p3, p4); + + const D = L1[0] * L2[1] - L1[1] * L2[0]; + const Dx = L1[2] * L2[1] - L1[1] * L2[2]; + const Dy = L1[0] * L2[2] - L1[2] * L2[0]; + + let x = null; + let y = null; + if (D !== 0) { + x = Dx / D; + y = Dy / D; + return { x, y }; + } + + return null; +} + export class Equation { private a: number; private b: number; private c: number; - private cCanvas: number; - private viewModel: Cuboid2PointViewModel; - - public constructor(p1: number[], p2: number[], model: Cuboid2PointViewModel) { - this.viewModel = model; - this.a = p1[1] - p2[1]; - this.b = p2[0] - p1[0]; - this.c = this.b * p1[1] + this.a * p1[0]; - - const temp = { x: p1[0], y: p1[1] }; - const p1Canvas = this.viewModel.actualToCanvas([temp])[0]; - this.cCanvas = this.b * p1Canvas.y + this.a * p1Canvas.x; + + public constructor(p1: Point, p2: Point) { + this.a = p1.y - p2.y; + this.b = p2.x - p1.x; + this.c = this.b * p1.y + this.a * p1.x; } // get the line equation in actual coordinates public getY(x: number): number { return (this.c - this.a * x) / this.b; } - - // get the line equation in canvas coordinates - public getYCanvas(x: number): number { - return (this.cCanvas - this.a * x) / this.b; - } } -class Figure { +export class Figure { private indices: number[]; - public viewmodel: Cuboid2PointViewModel; + private allPoints: Point[]; - public constructor(indices: number[], Vmodel: Cuboid2PointViewModel) { + public constructor(indices: number[], points: Point[]) { this.indices = indices; - this.viewmodel = Vmodel; + this.allPoints = points; } - public get points(): any[] { + public get points(): Point[] { const points = []; for (const index of this.indices) { - points.push(this.viewmodel.points[index]); + points.push(this.allPoints[index]); } return points; } @@ -68,32 +81,22 @@ class Figure { // if you only need to update a subset of the points, // simply put null for the points you want to keep public set points(newPoints) { - const oldPoints = this.viewmodel.points; for (let i = 0; i < newPoints.length; i += 1) { if (newPoints[i] !== null) { - oldPoints[this.indices[i]] = { x: newPoints[i].x, y: newPoints[i].y }; + this.allPoints[this.indices[i]] = { x: newPoints[i].x, y: newPoints[i].y }; } } } - - public get canvasPoints(): Point[] { - let { points } = this; - points = this.viewmodel.actualToCanvas(points); - return points; - } } -class Edge extends Figure { +export class Edge extends Figure { public getEquation(): Equation { - let { points } = this; - points = convertToArray(points); - return new Equation(points[0], points[1], this.viewmodel); + return new Equation(this.points[0], this.points[1]); } } -export class Cuboid2PointViewModel { - private canvasOffset: number; - public points: any[]; +export class CuboidModel { + public points: Point[]; private fr: Edge; private fl: Edge; private dr: Edge; @@ -114,11 +117,10 @@ export class Cuboid2PointViewModel { private top: Figure; private bot: Figure; public facesList: Figure[]; - public vpl: number[] | null; - public vpr: number[] | null; + public vpl: Point | null; + public vpr: Point | null; - public constructor(points: Point[], offset: number) { - this.canvasOffset = offset; + public constructor(points?: Point[]) { this.points = points; this.initEdges(); this.initFaces(); @@ -127,20 +129,6 @@ export class Cuboid2PointViewModel { this.updatePoints(); } - public canvasToActual(points: Point[]): Point[] { - return points.map((point): Point => ({ - x: point.x - this.canvasOffset, - y: point.y - this.canvasOffset, - })); - } - - public actualToCanvas(points: Point[]): Point[] { - return points.map((point): Point => ({ - x: point.x + this.canvasOffset, - y: point.y + this.canvasOffset, - })); - } - public getPoints(): Point[] { return this.points; } @@ -158,18 +146,18 @@ export class Cuboid2PointViewModel { } public computeSideEdgeConstraints(edge: any): any { - const midLength = this.fr.canvasPoints[1].y - this.fr.canvasPoints[0].y - 1; + const midLength = this.fr.points[1].y - this.fr.points[0].y - 1; - const minY = edge.canvasPoints[1].y - midLength; - const maxY = edge.canvasPoints[0].y + midLength; + const minY = edge.points[1].y - midLength; + const maxY = edge.points[0].y + midLength; const y1 = edge.points[0].y; const y2 = edge.points[1].y; const miny1 = y2 - midLength; - const maxy1 = y2 - MIN_EDGE_LENGTH; + const maxy1 = y2 - consts.MIN_EDGE_LENGTH; - const miny2 = y1 + MIN_EDGE_LENGTH; + const miny2 = y1 + consts.MIN_EDGE_LENGTH; const maxy2 = y1 + midLength; return { @@ -190,63 +178,63 @@ export class Cuboid2PointViewModel { // boolean value parameter controls which edges should be used to recalculate vanishing points private updateVanishingPoints(): void { - const leftEdge = convertToArray(this.fl.points); - const rightEdge = convertToArray(this.dr.points); - const midEdge = convertToArray(this.fr.points); + const leftEdge = this.fl.points; + const rightEdge = this.dr.points; + const midEdge = this.fr.points; this.vpl = intersection(leftEdge[0], midEdge[0], leftEdge[1], midEdge[1]); this.vpr = intersection(rightEdge[0], midEdge[0], rightEdge[1], midEdge[1]); if (this.vpl === null) { // shift the edge slightly to avoid edge case - leftEdge[0][1] -= 0.001; - leftEdge[0][0] += 0.001; - leftEdge[1][0] += 0.001; + leftEdge[0].y -= 0.001; + leftEdge[0].x += 0.001; + leftEdge[1].x += 0.001; this.vpl = intersection(leftEdge[0], midEdge[0], leftEdge[1], midEdge[1]); } if (this.vpr === null) { // shift the edge slightly to avoid edge case - rightEdge[0][1] -= 0.001; - rightEdge[0][0] -= 0.001; - rightEdge[1][0] -= 0.001; + rightEdge[0].y -= 0.001; + rightEdge[0].x -= 0.001; + rightEdge[1].x -= 0.001; this.vpr = intersection(leftEdge[0], midEdge[0], leftEdge[1], midEdge[1]); } } private initEdges(): void { - this.fl = new Edge([0, 1], this); - this.fr = new Edge([2, 3], this); - this.dr = new Edge([4, 5], this); - this.dl = new Edge([6, 7], this); + this.fl = new Edge([0, 1], this.points); + this.fr = new Edge([2, 3], this.points); + this.dr = new Edge([4, 5], this.points); + this.dl = new Edge([6, 7], this.points); - this.ft = new Edge([0, 2], this); - this.lt = new Edge([0, 6], this); - this.rt = new Edge([2, 4], this); - this.dt = new Edge([6, 4], this); + this.ft = new Edge([0, 2], this.points); + this.lt = new Edge([0, 6], this.points); + this.rt = new Edge([2, 4], this.points); + this.dt = new Edge([6, 4], this.points); - this.fb = new Edge([1, 3], this); - this.lb = new Edge([1, 7], this); - this.rb = new Edge([3, 5], this); - this.db = new Edge([7, 5], this); + this.fb = new Edge([1, 3], this.points); + this.lb = new Edge([1, 7], this.points); + this.rb = new Edge([3, 5], this.points); + this.db = new Edge([7, 5], this.points); this.edgeList = [this.fl, this.fr, this.dl, this.dr, this.ft, this.lt, this.rt, this.dt, this.fb, this.lb, this.rb, this.db]; } private initFaces(): void { - this.front = new Figure([0, 1, 3, 2], this); - this.right = new Figure([2, 3, 5, 4], this); - this.dorsal = new Figure([4, 5, 7, 6], this); - this.left = new Figure([6, 7, 1, 0], this); - this.top = new Figure([0, 2, 4, 6], this); - this.bot = new Figure([1, 3, 5, 7], this); + this.front = new Figure([0, 1, 3, 2], this.points); + this.right = new Figure([2, 3, 5, 4], this.points); + this.dorsal = new Figure([4, 5, 7, 6], this.points); + this.left = new Figure([6, 7, 1, 0], this.points); + this.top = new Figure([0, 2, 4, 6], this.points); + this.bot = new Figure([1, 3, 5, 7], this.points); this.facesList = [this.front, this.right, this.dorsal, this.left]; } private buildBackEdge(): void { this.updateVanishingPoints(); - const leftPoints = convertToArray(this.dr.points); - const rightPoints = convertToArray(this.fl.points); + const leftPoints = this.dr.points; + const rightPoints = this.fl.points; const topIndex = 6; const botIndex = 7; @@ -257,29 +245,17 @@ export class Cuboid2PointViewModel { let p2 = intersection(vpLeft, leftPoints[1], vpRight, rightPoints[1]); if (p1 === null) { - p1 = [p2[0], vpLeft[1]]; + p1 = { x: p2.x, y: vpLeft.y }; } else if (p2 === null) { - p2 = [p1[0], vpLeft[1]]; + p2 = { x: p1.x, y: vpLeft.y }; } - this.points[topIndex] = { x: p1[0], y: p1[1] }; - this.points[botIndex] = { x: p2[0], y: p2[1] }; + this.points[topIndex] = { x: p1.x, y: p1.y }; + this.points[botIndex] = { x: p2.x, y: p2.y }; // Making sure that the vertical edges stay vertical this.updatePoints(); } - - public get vplCanvas(): Point { - const { vpl } = this; - const vp = { x: vpl[0], y: vpl[1] }; - return this.actualToCanvas([vp])[0]; - } - - public get vprCanvas(): Point { - const { vpr } = this; - const vp = { x: vpr[0], y: vpr[1] }; - return this.actualToCanvas([vp])[0]; - } } function sortPointsClockwise(points: any[]): any[] { diff --git a/cvat-canvas/src/typescript/shared.ts b/cvat-canvas/src/typescript/shared.ts index 63a4e7ba6531..f07f1dc11aa8 100644 --- a/cvat-canvas/src/typescript/shared.ts +++ b/cvat-canvas/src/typescript/shared.ts @@ -116,54 +116,17 @@ export function convertToArray(points: Point[]): number[][] { return arr; } -export function convertArrayToObjects(pointsArray: number[][]): Point[] { - return pointsArray.reduce((points: Point[], point: number[]): Point[] => { - const [x, y] = point; - points.push({ x, y }); - return points; - }, []); -} - -export function convertArrayToDoubleArray(points: number[]): number[][] { - if (points.length % 2 !== 0) { - throw new Error('Points array must have length multiple of two.'); - } - - const pointsArray = []; - - for (let i = 0; i < points.length; i += 2) { - pointsArray.push([points[i], points[i]]); - } - - return pointsArray; -} - -function line(p1: number[], p2: number[]): number[] { - const a = p1[1] - p2[1]; - const b = p2[0] - p1[0]; - const c = b * p1[1] + a * p1[0]; - return [a, b, c]; -} - -export function intersection( - p1: number[], p2: number[], p3: number[], p4: number[] -): number[] | null { - const L1 = line(p1, p2); - const L2 = line(p3, p4); - - const D = L1[0] * L2[1] - L1[1] * L2[0]; - const Dx = L1[2] * L2[1] - L1[1] * L2[2]; - const Dy = L1[0] * L2[2] - L1[2] * L2[0]; - - let x = null; - let y = null; - if (D !== 0) { - x = Dx / D; - y = Dy / D; - return [x, y]; - } - - return null; +export function pointsToObjects(stringified: string): Point[] { + return pointsToArray(stringified) + .reduce((points: Point[], coord: number, index: number): Point[] => { + if (index % 2 === 0) { + points.push({ x: coord, y: null }); + } else { + // eslint-disable-next-line no-param-reassign + points.slice(-1)[0].y = coord; + } + return points; + }, []); } export function clamp(x: number, min: number, max: number): number { diff --git a/cvat-canvas/src/typescript/svg.patch.ts b/cvat-canvas/src/typescript/svg.patch.ts index 785698f8ff06..7eef8a0c940e 100644 --- a/cvat-canvas/src/typescript/svg.patch.ts +++ b/cvat-canvas/src/typescript/svg.patch.ts @@ -9,7 +9,9 @@ import 'svg.resize.js'; import 'svg.select.js'; import 'svg.draw.js'; -import { Equation, MIN_EDGE_LENGTH } from './cuboid'; +import { Point, Equation, CuboidModel } from './cuboid'; +import { pointsToObjects } from './shared'; +// import consts from './consts' // Update constructor const originalDraw = SVG.Element.prototype.draw; @@ -183,41 +185,40 @@ for (const key of Object.keys(originalResize)) { } -SVG.Cube = SVG.invent({ +(SVG as any).Cube = SVG.invent({ create: 'g', inherit: SVG.G, extend: { - - constructorMethod(viewModel) { - this.viewModel = viewModel; - this.attr('points', viewModel.getPoints()); - this.projectionLineEnable = false; - this.setupFaces(viewModel); - this.setupEdges(viewModel); - this.setupProjections(viewModel); + constructorMethod(points: string) { + this.attr('points', points); + this._viewModel = new CuboidModel(pointsToObjects(points)); + this.setupFaces(); + this.setupEdges(); + this.setupProjections(); this.setupGrabPoints(); + this.hideProjections(); this.hideGrabPoints(); return this; }, - setupFaces(viewModel) { - this.face = this.polygon(viewModel.front.canvasPoints); - this.right = this.polygon(viewModel.right.canvasPoints); - this.dorsal = this.polygon(viewModel.dorsal.canvasPoints); - this.left = this.polygon(viewModel.left.canvasPoints); + setupFaces() { + this.face = this.polygon(this._viewModel.front.points); + this.right = this.polygon(this._viewModel.right.points); + this.dorsal = this.polygon(this._viewModel.dorsal.points); + this.left = this.polygon(this._viewModel.left.points); }, - setupProjections(viewModel) { - this.ftProj = this.line(this.updateProjectionLine(viewModel.ft.getEquation(), - viewModel.ft.canvasPoints[0], viewModel.vplCanvas)); - this.fbProj = this.line(this.updateProjectionLine(viewModel.fb.getEquation(), - viewModel.ft.canvasPoints[0], viewModel.vplCanvas)); - this.rtProj = this.line(this.updateProjectionLine(viewModel.rt.getEquation(), - viewModel.rt.canvasPoints[1], viewModel.vprCanvas)); - this.rbProj = this.line(this.updateProjectionLine(viewModel.rb.getEquation(), - viewModel.rb.canvasPoints[1], viewModel.vprCanvas)); + setupProjections() { + this.ftProj = this.line(this.updateProjectionLine(this._viewModel.ft.getEquation(), + this._viewModel.ft.points[0], this._viewModel.vpl)); + this.fbProj = this.line(this.updateProjectionLine(this._viewModel.fb.getEquation(), + this._viewModel.ft.points[0], this._viewModel.vpl)); + this.rtProj = this.line(this.updateProjectionLine(this._viewModel.rt.getEquation(), + this._viewModel.rt.points[1], this._viewModel.vpr)); + this.rbProj = this.line(this.updateProjectionLine(this._viewModel.rb.getEquation(), + this._viewModel.rb.points[1], this._viewModel.vpr)); this.ftProj.stroke({ color: '#C0C0C0' }); this.fbProj.stroke({ color: '#C0C0C0' }); @@ -225,16 +226,16 @@ SVG.Cube = SVG.invent({ this.rbProj.stroke({ color: '#C0C0C0' }); }, - setupEdges(viewModel) { - this.frontLeftEdge = this.line(viewModel.fl.canvasPoints); - this.frontRightEdge = this.line(viewModel.fr.canvasPoints); - this.dorsalRightEdge = this.line(viewModel.dr.canvasPoints); - this.dorsalLeftEdge = this.line(viewModel.dl.canvasPoints); + setupEdges() { + this.frontLeftEdge = this.line(this._viewModel.fl.points); + this.frontRightEdge = this.line(this._viewModel.fr.points); + this.dorsalRightEdge = this.line(this._viewModel.dr.points); + this.dorsalLeftEdge = this.line(this._viewModel.dl.points); - this.frontTopEdge = this.line(viewModel.ft.canvasPoints); - this.rightTopEdge = this.line(viewModel.rt.canvasPoints); - this.frontBotEdge = this.line(viewModel.fb.canvasPoints); - this.rightBotEdge = this.line(viewModel.rb.canvasPoints); + this.frontTopEdge = this.line(this._viewModel.ft.points); + this.rightTopEdge = this.line(this._viewModel.rt.points); + this.frontBotEdge = this.line(this._viewModel.fb.points); + this.rightBotEdge = this.line(this._viewModel.rb.points); }, setupGrabPoints() { @@ -249,314 +250,13 @@ SVG.Cube = SVG.invent({ const grabPoints = this.getGrabPoints(); const edges = this.getEdges(); for (let i = 0; i < grabPoints.length; i += 1) { - const edge = edges[`${i}`]; + const edge = edges[i]; const cx = (edge.attr('x2') + edge.attr('x1')) / 2; const cy = (edge.attr('y2') + edge.attr('y1')) / 2; - grabPoints[`${i}`].center(cx, cy); - } - }, - - addEvents() { - const edges = this.getEdges(); - const grabPoints =this.getGrabPoints(); - const draggableFaces = [ - this.left, - this.dorsal, - this.right, - ]; - - if (this.viewModel.dl.points[0].x > this.viewModel.fl.points[0].x) { - this.orientation = 1; - } else { - this.orientation = 2; - } - - this.updateGrabPoints(); - edges.forEach((edge) => { - edge.on('resizestart', () => { - // TODO: dipatch proper canvas event - }).on('resizedone', () => { - this.updateModel(); - this.updateViewModel(); - // TODO: dipatch proper canvas event - }); - }); - grabPoints.forEach((grabPoint) => { - grabPoint.on('dragstart', () => { - // TODO: dipatch proper canvas event - }).on('dragend', () => { - // TODO: dipatch proper canvas event - this.updateModel(); - this.updateViewModel(); - }); - }); - - draggableFaces.forEach((face) => { - face.on('dragstart', () => { - // TODO: dipatch proper canvas event - }).on('dragend', () => { - // TODO: dipatch proper canvas event - this.updateModel(); - this.updateViewModel(); - this.updateGrabPoints(); - }); - }); - - this.makeDraggable(); - // this.makeResizable(); - }, - - makeDraggable() { - let startPoint: any = null; - let startPosition: any = null; - - this.draggable().off('dragend').on('dragstart', (e) => { - startPoint = e.detail.p; - startPosition = this.viewModel.getPoints(); - }).on('dragmove', (e) => { - e.preventDefault(); - this.translatePoints(startPoint, startPosition, e.detail.p); - this.refreshView(); - }).on('dragend', () => { - this.updateModel(); - this.updateViewModel(); - }); - - // Controllable vertical edges - this.flCenter.draggable(function (x) { - const vpX = this.cx() - this.viewModel.vplCanvas.x > 0 ? this.viewModel.vplCanvas.x : 0; - return { x: x < this.viewModel.fr.canvasPoints[0].x && x > vpX + MIN_EDGE_LENGTH }; - }).on('dragmove', function () { - this.frontLeftEdge.center(this.cx(), this.cy()); - - const [ position ] = this.viewModel.canvasToActual([{x: this.frontLeftEdge.attr('x1'), y: this.frontLeftEdge.attr('y1')}]); - const { x } = position; - - const y1 = this.viewModel.ft.getEquation().getY(x); - const y2 = this.viewModel.fb.getEquation().getY(x); - - const topPoint = { x, y: y1 }; - const botPoint = { x, y: y2 }; - - this.viewModel.fl.points = [topPoint, botPoint]; - this.updateViewAndVM(); - }); - - this.drCenter.draggable(function (x) { - let xStatus; - if (this.cx() < this.viewModel.fr.canvasPoints[0].x) { - xStatus = x < this.viewModel.fr.canvasPoints[0].x - MIN_EDGE_LENGTH - && x > this.viewModel.vprCanvas.x + MIN_EDGE_LENGTH; - } else { - xStatus = x > this.viewModel.fr.canvasPoints[0].x + MIN_EDGE_LENGTH - && x < this.viewModel.vprCanvas.x - MIN_EDGE_LENGTH; - } - return { x: xStatus, y: this.attr('y1') }; - }).on('dragmove', function () { - this.dorsalRightEdge.center(this.cx(), this.cy()); - - const [ position ] = this.viewModel.canvasToActual([{x: this.dorsalRightEdge.attr('x1'), y: this.dorsalRightEdge.attr('y1')}]); - const { x } = position; - - const y1 = this.viewModel.rt.getEquation().getY(x); - const y2 = this.viewModel.rb.getEquation().getY(x); - - const topPoint = { x, y: y1 }; - const botPoint = { x, y: y2 }; - - this.viewModel.dr.points = [topPoint, botPoint]; - this.updateViewAndVM(); - }); - - this.dlCenter.draggable(function (x) { - let xStatus; - if (this.cx() < this.viewModel.fl.canvasPoints[0].x) { - xStatus = x < this.viewModel.fl.canvasPoints[0].x - MIN_EDGE_LENGTH - && x > this.viewModel.vprCanvas.x + MIN_EDGE_LENGTH; - } else { - xStatus = x > this.viewModel.fl.canvasPoints[0].x + MIN_EDGE_LENGTH - && x < this.viewModel.vprCanvas.x + MIN_EDGE_LENGTH; - } - return { x: xStatus, y: this.attr('y1') }; - }).on('dragmove', function () { - this.dorsalLeftEdge.center(this.cx(), this.cy()); - - const position = this.viewModel.canvasToActual([{x: this.dorsalLeftEdge.attr('x1'), y: this.dorsalLeftEdge.attr('y1')}]); - const { x } = position; - - const y1 = this.viewModel.lt.getEquation().getY(x); - const y2 = this.viewModel.lb.getEquation().getY(x); - - const topPoint = { x, y: y1 }; - const botPoint = { x, y: y2 }; - - this.viewModel.dl.points = [topPoint, botPoint]; - this.updateViewAndVM(true); - }); - - this.frCenter.draggable((x) => { - return { x: x > this.viewModel.fl.canvasPoints[0].x, y: this.attr('y1') }; - }).on('dragmove', () => { - this.frontRightEdge.center(this.cx(), this.cy()); - - const [ position ]= this.viewModel.canvasToActual([{x: this.frontRightEdge.attr('x1'), y: this.frontRightEdge.attr('y1')}]); - const { x } = position; - - const y1 = this.viewModel.ft.getEquation().getY(x); - const y2 = this.viewModel.fb.getEquation().getY(x); - - const topPoint = { x, y: y1 }; - const botPoint = { x, y: y2 }; - - this.viewModel.fr.points = [topPoint, botPoint]; - this.updateViewAndVM(true); - }); - - - // Controllable 'horizontal' edges - this.ftCenter.draggable((x, y) => { - return { x: x === this.cx(), y: y < this.fbCenter.cy() - MIN_EDGE_LENGTH }; - }).on('dragmove', () => { - this.frontTopEdge.center(this.cx(), this.cy()); - this.horizontalEdgeControl(this.viewModel.top, this.frontTopEdge.attr('x2'), this.frontTopEdge.attr('y2')); - this.updateViewAndVM(); - }); - - this.fbCenter.draggable((x, y) => { - return { x: x === this.cx(), y: y > this.ftCenter.cy() + MIN_EDGE_LENGTH }; - }).on('dragmove', () => { - this.frontBotEdge.center(this.cx(), this.cy()); - this.horizontalEdgeControl(this.viewModel.bot, this.frontBotEdge.attr('x2'), this.frontBotEdge.attr('y2')); - this.updateViewAndVM(); - }); - - // Controllable faces - this.left.draggable((x, y) => ({ x: x < Math.min(this.viewModel.dr.canvasPoints[0].x, this.viewModel.fr.canvasPoints[0].x) - MIN_EDGE_LENGTH, y })).on('dragmove', () => { - this.faceDragControl(this.viewModel.left, this.attr('points')); - }); - this.dorsal.draggable().on('dragmove', () => { - this.faceDragControl(this.viewModel.dorsal, this.attr('points')); - }); - this.right.draggable((x, y) => ({ x: x > Math.min(this.viewModel.dl.canvasPoints[0].x, this.viewModel.fl.canvasPoints[0].x) + MIN_EDGE_LENGTH, y })).on('dragmove', () => { - this.faceDragControl(this.viewModel.right, this.attr('points'), true); - }); - }, - - translatePoints(startPoint, startPosition, currentPosition) { - const dx = currentPosition.x - startPoint.x; - const dy = currentPosition.y - startPoint.y; - const newPoints: any[] = []; - for (let i = 0; i < startPosition.length; i += 1) { - newPoints[i] = { x: startPosition[i].x + dx, y: startPosition[i].y + dy }; + grabPoints[i].center(cx, cy); } - this.viewModel.setPoints(newPoints); }, - computeHeightFace(point, index) { - switch (index) { - // fl - case 1: { - const p2 = this.updatedEdge(this.viewModel.fr.points[0], point, this.viewModel.vpl); - const p3 = this.updatedEdge(this.viewModel.dr.points[0], p2, this.viewModel.vpr); - const p4 = this.updatedEdge(this.viewModel.dl.points[0], point, this.viewModel.vpr); - return [point, p2, p3, p4]; - } - // fr - case 2: { - const p2 = this.updatedEdge(this.viewModel.fl.points[0], point, this.viewModel.vpl); - const p3 = this.updatedEdge(this.viewModel.dr.points[0], point, this.viewModel.vpr); - const p4 = this.updatedEdge(this.viewModel.dl.points[0], p3, this.viewModel.vpr); - return [p2, point, p3, p4]; - } - // dr - case 3: { - const p2 = this.updatedEdge(this.viewModel.dl.points[0], point, this.viewModel.vpl); - const p3 = this.updatedEdge(this.viewModel.fr.points[0], point, this.viewModel.vpr); - const p4 = this.updatedEdge(this.viewModel.fl.points[0], p2, this.viewModel.vpr); - return [p4, p3, point, p2]; - } - // dl - case 4: { - const p2 = this.updatedEdge(this.viewModel.dr.points[0], point, this.viewModel.vpl); - const p3 = this.updatedEdge(this.viewModel.fl.points[0], point, this.viewModel.vpr); - const p4 = this.updatedEdge(this.viewModel.fr.points[0], p2, this.viewModel.vpr); - return [p3, p4, p2, point]; - } - default: { - return [null, null, null, null]; - } - } - }, - - updateViewAndVM() { - this.viewModel.buildBackEdge(); - this.refreshView(); - }, - - refreshView() { - // TODO: fixit - this.udpateView(this.viewModel); - }, - - updatedEdge(target, base, pivot) { - const targetX = target.x; - const line = new Equation(pivot, - [base.x, base.y], this.viewModel); - const newY = line.getY(targetX); - return { x: targetX, y: newY }; - }, - - resizeControl(vmEdge, updatedEdge, constraints) { - const [ topPoint ] = this.viewModel.canvasToActual([{x: updatedEdge.attr('x1'), y: updatedEdge.attr('y1')}]); - const [ botPoint ] = this.viewModel.canvasToActual([{x: updatedEdge.attr('x2'), y: updatedEdge.attr('y2')}]); - - topPoint.y = Math.min(Math.max(topPoint.y, constraints.y1Range.min), constraints.y1Range.max); - botPoint.y = Math.min(Math.max(botPoint.y, constraints.y2Range.min), constraints.y2Range.max); - - vmEdge.points = [topPoint, botPoint]; - }, - - updateGrabPoints() { - const centers = this.getGrabPoints(); - const edges = this.getEdges(); - for (let i = 0; i < centers.length; i += 1) { - const edge = edges[`${i}`]; - centers[`${i}`].center(edge.cx(), edge.cy()); - } - - this.dorsalRightEdge.selectize({ - points: 't,b', - rotationPoint: false, - }).resize().on('resizing', function (e) { - if (e.detail.event.shiftKey) { - this.resizeControl(this.viewModel.dr, - this, - this.viewModel.computeSideEdgeConstraints(this.viewModel.dr)); - } else { - const [ midPointUp ] = this.viewModel.canvasToActual([{x: this.dorsalRightEdge.attr('x1'), y: this.dorsalRightEdge.attr('y1')}])[0]; - const [ midPointDown ] = this.viewModel.canvasToActual([{x: this.dorsalRightEdge.attr('x2'), y: this.dorsalRightEdge.attr('y2')}])[0]; - this.viewModel.top.points = this.computeHeightFace(midPointUp, 3); - this.viewModel.bot.points = this.computeHeightFace(midPointDown, 3); - } - this.updateViewAndVM(); - }); - this.drCenter.show(); - - this.dorsalLeftEdge.selectize(false); - this.dlCenter.hide(); - }, - - move(dx, dy) { - this.face.dmove(dx, dy); - this.dorsal.dmove(dx, dy); - this.right.dmove(dx, dy); - this.left.dmove(dx, dy); - - const edges = this.getEdges(); - edges.forEach((edge) => { - edge.dmove(dx, dy); - }); - }, showProjections() { if (this.projectionLineEnable) { @@ -576,92 +276,18 @@ SVG.Cube = SVG.invent({ showGrabPoints(radius: number, stroke: number, color: string) { const grabPoints = this.getGrabPoints(); - grabPoints.forEach((point) => { + grabPoints.forEach((point: SVG.Circle) => { point.radius(radius).attr('stroke-width', stroke).show(); }); }, hideGrabPoints() { const grabPoints = this.getGrabPoints(); - grabPoints.forEach((point) => { + grabPoints.forEach((point: SVG.Circle) => { point.hide(); }); }, - updateView(viewModel) { - const convertedPoints = window.cvat.translate.points.actualToCanvas( - viewModel.getPoints(), - ); - this.updatePolygons(viewModel); - this.updateLines(viewModel); - this.updateProjections(viewModel); - this.updateGrabPoints(); - this.attr('points', convertedPoints); - }, - - updatePolygons(viewModel) { - this.face.plot(viewModel.front.canvasPoints); - this.right.plot(viewModel.right.canvasPoints); - this.dorsal.plot(viewModel.dorsal.canvasPoints); - this.left.plot(viewModel.left.canvasPoints); - }, - - updateLines(viewModel) { - this.frontLeftEdge.plot(viewModel.fl.canvasPoints); - this.frontRightEdge.plot(viewModel.fr.canvasPoints); - this.dorsalRightEdge.plot(viewModel.dr.canvasPoints); - this.dorsalLeftEdge.plot(viewModel.dl.canvasPoints); - - this.frontTopEdge.plot(viewModel.ft.canvasPoints); - this.rightTopEdge.plot(viewModel.rt.canvasPoints); - this.frontBotEdge.plot(viewModel.fb.canvasPoints); - this.rightBotEdge.plot(viewModel.rb.canvasPoints); - }, - - updateThickness() { - const edges = this.getEdges(); - const width = this.attr('stroke-width'); - const baseWidthOffset = 1.75; - const expandedWidthOffset = 3; - edges.forEach((edge) => { - edge.on('mouseover', function () { - this.attr({ 'stroke-width': width * expandedWidthOffset }); - }).on('mouseout', function () { - this.attr({ 'stroke-width': width * baseWidthOffset }); - }).stroke({ width: width * baseWidthOffset, linecap: 'round' }); - }); - }, - - updateProjections(viewModel) { - this.ftProj.plot(this.updateProjectionLine(viewModel.ft.getEquation(), - viewModel.ft.canvasPoints[0], viewModel.vplCanvas)); - this.fbProj.plot(this.updateProjectionLine(viewModel.fb.getEquation(), - viewModel.ft.canvasPoints[0], viewModel.vplCanvas)); - this.rtProj.plot(this.updateProjectionLine(viewModel.rt.getEquation(), - viewModel.rt.canvasPoints[1], viewModel.vprCanvas)); - this.rbProj.plot(this.updateProjectionLine(viewModel.rb.getEquation(), - viewModel.rt.canvasPoints[1], viewModel.vprCanvas)); - }, - - paintOrientationLines() { - const fillColor = this.attr('fill'); - const selectedColor = '#ff007f'; - this.frontTopEdge.stroke({ color: selectedColor }); - this.frontLeftEdge.stroke({ color: selectedColor }); - this.frontBotEdge.stroke({ color: selectedColor }); - this.frontRightEdge.stroke({ color: selectedColor }); - - this.rightTopEdge.stroke({ color: fillColor }); - this.rightBotEdge.stroke({ color: fillColor }); - this.dorsalRightEdge.stroke({ color: fillColor }); - this.dorsalLeftEdge.stroke({ color: fillColor }); - - this.face.stroke({ color: fillColor, width: 0 }); - this.right.stroke({ color: fillColor }); - this.dorsal.stroke({ color: fillColor }); - this.left.stroke({ color: fillColor }); - }, - getEdges() { const arr = []; arr.push(this.frontLeftEdge); @@ -686,80 +312,481 @@ SVG.Cube = SVG.invent({ return arr; }, - updateProjectionLine(equation, source, direction) { + updateProjectionLine(equation: Equation, source: Point, direction: Point) { const x1 = source.x; - const y1 = equation.getYCanvas(x1); + const y1 = equation.getY(x1); const x2 = direction.x; - const y2 = equation.getYCanvas(x2); + const y2 = equation.getY(x2); return [[x1, y1], [x2, y2]]; }, - addMouseOverEvents() { - this._addFaceEvents(); + selectize(value: any, options: any) { + this.face.selectize(value, options); + this.dorsalRightEdge.selectize(value, options) }, - _addFaceEvents() { - const group = this; - this.left.on('mouseover', function () { - this.attr({ 'fill-opacity': 0.5 }); - }).on('mouseout', function () { - this.attr({ 'fill-opacity': group.attr('fill-opacity') }); - }); - this.dorsal.on('mouseover', function () { - this.attr({ 'fill-opacity': 0.5 }); - }).on('mouseout', function () { - this.attr({ 'fill-opacity': group.attr('fill-opacity') }); - }); - this.right.on('mouseover', function () { - this.attr({ 'fill-opacity': 0.5 }); - }).on('mouseout', function () { - this.attr({ 'fill-opacity': group.attr('fill-opacity') }); - }); + attr(a: any, v: any, n: any) { + const _attr = SVG.Element.prototype.attr.bind(this); + if (a === 'fill' && v !== undefined) { + _attr(a, v, n); + this.paintOrientationLines(); + } else if (a === 'stroke-width' && typeof v === "number") { + _attr(a, v, n); + this.updateThickness(); + } else { + return _attr(a, v, n); + } + + return this; }, - removeMouseOverEvents() { - const edges = this.getEdges(); - edges.forEach((edge) => { - edge.off('mouseover').off('mouseout'); + updateThickness() { + const edges = [this.frontLeftEdge, this.frontRightEdge, this.frontTopEdge, this.frontBotEdge] + const width = this.attr('stroke-width'); + edges.forEach((edge: SVG.Element) => { + edge.attr('stroke-width', width * (this.strokeOffset || 1.75)); + }); + this.on('mouseover', () => { + edges.forEach((edge: SVG.Element) => { + this.strokeOffset = 2.5; + edge.attr('stroke-width', width * this.strokeOffset); + }) + }).on('mouseout', () => { + edges.forEach((edge: SVG.Element) => { + this.strokeOffset = 1.75; + edge.attr('stroke-width', width * this.strokeOffset); + }) }); - this.left.off('mouseover').off('mouseout'); - this.dorsal.off('mouseover').off('mouseout'); - this.right.off('mouseover').off('mouseout'); }, - resetFaceOpacity() { - const group = this; - this.left.attr({ 'fill-opacity': group.attr('fill-opacity') }); - this.dorsal.attr({ 'fill-opacity': group.attr('fill-opacity') }); - this.right.attr({ 'fill-opacity': group.attr('fill-opacity') }); - }, + paintOrientationLines() { + const fillColor = this.attr('fill'); + const selectedColor = '#ff007f'; + this.frontTopEdge.stroke({ color: selectedColor }); + this.frontLeftEdge.stroke({ color: selectedColor }); + this.frontBotEdge.stroke({ color: selectedColor }); + this.frontRightEdge.stroke({ color: selectedColor }); - addOccluded() { - const edges = this.getEdges(); - edges.forEach((edge) => { - edge.node.classList.add('occludedShape'); - }); - this.face.attr('stroke-width', 0); - this.right.attr('stroke-width', 0); - this.left.node.classList.add('occludedShape'); - this.dorsal.node.classList.add('occludedShape'); - }, + this.rightTopEdge.stroke({ color: fillColor }); + this.rightBotEdge.stroke({ color: fillColor }); + this.dorsalRightEdge.stroke({ color: fillColor }); + this.dorsalLeftEdge.stroke({ color: fillColor }); - removeOccluded() { - const edges = this.getEdges(); - edges.forEach((edge) => { - edge.node.classList.remove('occludedShape'); - }); - this.face.attr('stroke-width', this.attr('stroke-width')); - this.right.attr('stroke-width', this.attr('stroke-width')); - this.left.node.classList.remove('occludedShape'); - this.dorsal.node.classList.remove('occludedShape'); + this.face.stroke({ color: fillColor, width: 0 }); + this.right.stroke({ color: fillColor }); + this.dorsal.stroke({ color: fillColor }); + this.left.stroke({ color: fillColor }); }, + + // addEvents() { + // const edges = this.getEdges(); + // const grabPoints =this.getGrabPoints(); + // const draggableFaces = [ + // this.left, + // this.dorsal, + // this.right, + // ]; + + // if (this._viewModel.dl.points[0].x > this.viewModel.fl.points[0].x) { + // this.orientation = 1; + // } else { + // this.orientation = 2; + // } + + // this.updateGrabPoints(); + // edges.forEach((edge) => { + // edge.on('resizestart', () => { + // // TODO: dipatch proper canvas event + // }).on('resizedone', () => { + // this.updateModel(); + // this.updateViewModel(); + // // TODO: dipatch proper canvas event + // }); + // }); + // grabPoints.forEach((grabPoint) => { + // grabPoint.on('dragstart', () => { + // // TODO: dipatch proper canvas event + // }).on('dragend', () => { + // // TODO: dipatch proper canvas event + // this.updateModel(); + // this.updateViewModel(); + // }); + // }); + + // draggableFaces.forEach((face) => { + // face.on('dragstart', () => { + // // TODO: dipatch proper canvas event + // }).on('dragend', () => { + // // TODO: dipatch proper canvas event + // this.updateModel(); + // this.updateViewModel(); + // this.updateGrabPoints(); + // }); + // }); + + // this.makeDraggable(); + // // this.makeResizable(); + // }, + + // makeDraggable() { + // let startPoint: any = null; + // let startPosition: any = null; + + // this.draggable().off('dragend').on('dragstart', (e) => { + // startPoint = e.detail.p; + // startPosition = this.viewModel.getPoints(); + // }).on('dragmove', (e) => { + // e.preventDefault(); + // this.translatePoints(startPoint, startPosition, e.detail.p); + // this.refreshView(); + // }).on('dragend', () => { + // this.updateModel(); + // this.updateViewModel(); + // }); + + // // Controllable vertical edges + // this.flCenter.draggable(function (x) { + // const vpX = this.cx() - this.viewModel.vplCanvas.x > 0 ? this.viewModel.vplCanvas.x : 0; + // return { x: x < this.viewModel.fr.canvasPoints[0].x && x > vpX + consts.MIN_EDGE_LENGTH }; + // }).on('dragmove', function () { + // this.frontLeftEdge.center(this.cx(), this.cy()); + + // const [ position ] = this.viewModel.canvasToActual([{x: this.frontLeftEdge.attr('x1'), y: this.frontLeftEdge.attr('y1')}]); + // const { x } = position; + + // const y1 = this.viewModel.ft.getEquation().getY(x); + // const y2 = this.viewModel.fb.getEquation().getY(x); + + // const topPoint = { x, y: y1 }; + // const botPoint = { x, y: y2 }; + + // this.viewModel.fl.points = [topPoint, botPoint]; + // this.updateViewAndVM(); + // }); + + // this.drCenter.draggable(function (x) { + // let xStatus; + // if (this.cx() < this.viewModel.fr.canvasPoints[0].x) { + // xStatus = x < this.viewModel.fr.canvasPoints[0].x - consts.MIN_EDGE_LENGTH + // && x > this.viewModel.vprCanvas.x + consts.MIN_EDGE_LENGTH; + // } else { + // xStatus = x > this.viewModel.fr.canvasPoints[0].x + consts.MIN_EDGE_LENGTH + // && x < this.viewModel.vprCanvas.x - consts.MIN_EDGE_LENGTH; + // } + // return { x: xStatus, y: this.attr('y1') }; + // }).on('dragmove', function () { + // this.dorsalRightEdge.center(this.cx(), this.cy()); + + // const [ position ] = this.viewModel.canvasToActual([{x: this.dorsalRightEdge.attr('x1'), y: this.dorsalRightEdge.attr('y1')}]); + // const { x } = position; + + // const y1 = this.viewModel.rt.getEquation().getY(x); + // const y2 = this.viewModel.rb.getEquation().getY(x); + + // const topPoint = { x, y: y1 }; + // const botPoint = { x, y: y2 }; + + // this.viewModel.dr.points = [topPoint, botPoint]; + // this.updateViewAndVM(); + // }); + + // this.dlCenter.draggable(function (x) { + // let xStatus; + // if (this.cx() < this.viewModel.fl.canvasPoints[0].x) { + // xStatus = x < this.viewModel.fl.canvasPoints[0].x - consts.MIN_EDGE_LENGTH + // && x > this.viewModel.vprCanvas.x + consts.MIN_EDGE_LENGTH; + // } else { + // xStatus = x > this.viewModel.fl.canvasPoints[0].x + consts.MIN_EDGE_LENGTH + // && x < this.viewModel.vprCanvas.x + consts.MIN_EDGE_LENGTH; + // } + // return { x: xStatus, y: this.attr('y1') }; + // }).on('dragmove', function () { + // this.dorsalLeftEdge.center(this.cx(), this.cy()); + + // const position = this.viewModel.canvasToActual([{x: this.dorsalLeftEdge.attr('x1'), y: this.dorsalLeftEdge.attr('y1')}]); + // const { x } = position; + + // const y1 = this.viewModel.lt.getEquation().getY(x); + // const y2 = this.viewModel.lb.getEquation().getY(x); + + // const topPoint = { x, y: y1 }; + // const botPoint = { x, y: y2 }; + + // this.viewModel.dl.points = [topPoint, botPoint]; + // this.updateViewAndVM(true); + // }); + + // this.frCenter.draggable((x) => { + // return { x: x > this.viewModel.fl.canvasPoints[0].x, y: this.attr('y1') }; + // }).on('dragmove', () => { + // this.frontRightEdge.center(this.cx(), this.cy()); + + // const [ position ]= this.viewModel.canvasToActual([{x: this.frontRightEdge.attr('x1'), y: this.frontRightEdge.attr('y1')}]); + // const { x } = position; + + // const y1 = this.viewModel.ft.getEquation().getY(x); + // const y2 = this.viewModel.fb.getEquation().getY(x); + + // const topPoint = { x, y: y1 }; + // const botPoint = { x, y: y2 }; + + // this.viewModel.fr.points = [topPoint, botPoint]; + // this.updateViewAndVM(true); + // }); + + + // // Controllable 'horizontal' edges + // this.ftCenter.draggable((x, y) => { + // return { x: x === this.cx(), y: y < this.fbCenter.cy() - consts.MIN_EDGE_LENGTH }; + // }).on('dragmove', () => { + // this.frontTopEdge.center(this.cx(), this.cy()); + // this.horizontalEdgeControl(this.viewModel.top, this.frontTopEdge.attr('x2'), this.frontTopEdge.attr('y2')); + // this.updateViewAndVM(); + // }); + + // this.fbCenter.draggable((x, y) => { + // return { x: x === this.cx(), y: y > this.ftCenter.cy() + consts.MIN_EDGE_LENGTH }; + // }).on('dragmove', () => { + // this.frontBotEdge.center(this.cx(), this.cy()); + // this.horizontalEdgeControl(this.viewModel.bot, this.frontBotEdge.attr('x2'), this.frontBotEdge.attr('y2')); + // this.updateViewAndVM(); + // }); + + // // Controllable faces + // this.left.draggable((x, y) => ({ x: x < Math.min(this.viewModel.dr.canvasPoints[0].x, this.viewModel.fr.canvasPoints[0].x) - consts.MIN_EDGE_LENGTH, y })).on('dragmove', () => { + // this.faceDragControl(this.viewModel.left, this.attr('points')); + // }); + // this.dorsal.draggable().on('dragmove', () => { + // this.faceDragControl(this.viewModel.dorsal, this.attr('points')); + // }); + // this.right.draggable((x, y) => ({ x: x > Math.min(this.viewModel.dl.canvasPoints[0].x, this.viewModel.fl.canvasPoints[0].x) + consts.MIN_EDGE_LENGTH, y })).on('dragmove', () => { + // this.faceDragControl(this.viewModel.right, this.attr('points'), true); + // }); + // }, + + // translatePoints(startPoint, startPosition, currentPosition) { + // const dx = currentPosition.x - startPoint.x; + // const dy = currentPosition.y - startPoint.y; + // const newPoints: any[] = []; + // for (let i = 0; i < startPosition.length; i += 1) { + // newPoints[i] = { x: startPosition[i].x + dx, y: startPosition[i].y + dy }; + // } + // this.viewModel.setPoints(newPoints); + // }, + + // computeHeightFace(point, index) { + // switch (index) { + // // fl + // case 1: { + // const p2 = this.updatedEdge(this.viewModel.fr.points[0], point, this.viewModel.vpl); + // const p3 = this.updatedEdge(this.viewModel.dr.points[0], p2, this.viewModel.vpr); + // const p4 = this.updatedEdge(this.viewModel.dl.points[0], point, this.viewModel.vpr); + // return [point, p2, p3, p4]; + // } + // // fr + // case 2: { + // const p2 = this.updatedEdge(this.viewModel.fl.points[0], point, this.viewModel.vpl); + // const p3 = this.updatedEdge(this.viewModel.dr.points[0], point, this.viewModel.vpr); + // const p4 = this.updatedEdge(this.viewModel.dl.points[0], p3, this.viewModel.vpr); + // return [p2, point, p3, p4]; + // } + // // dr + // case 3: { + // const p2 = this.updatedEdge(this.viewModel.dl.points[0], point, this.viewModel.vpl); + // const p3 = this.updatedEdge(this.viewModel.fr.points[0], point, this.viewModel.vpr); + // const p4 = this.updatedEdge(this.viewModel.fl.points[0], p2, this.viewModel.vpr); + // return [p4, p3, point, p2]; + // } + // // dl + // case 4: { + // const p2 = this.updatedEdge(this.viewModel.dr.points[0], point, this.viewModel.vpl); + // const p3 = this.updatedEdge(this.viewModel.fl.points[0], point, this.viewModel.vpr); + // const p4 = this.updatedEdge(this.viewModel.fr.points[0], p2, this.viewModel.vpr); + // return [p3, p4, p2, point]; + // } + // default: { + // return [null, null, null, null]; + // } + // } + // }, + + // updateViewAndVM() { + // this.viewModel.buildBackEdge(); + // this.refreshView(); + // }, + + // refreshView() { + // // TODO: fixit + // this.udpateView(this.viewModel); + // }, + + // updatedEdge(target, base, pivot) { + // const targetX = target.x; + // const line = new Equation(pivot, + // [base.x, base.y], this.viewModel); + // const newY = line.getY(targetX); + // return { x: targetX, y: newY }; + // }, + + // resizeControl(vmEdge, updatedEdge, constraints) { + // const [ topPoint ] = this.viewModel.canvasToActual([{x: updatedEdge.attr('x1'), y: updatedEdge.attr('y1')}]); + // const [ botPoint ] = this.viewModel.canvasToActual([{x: updatedEdge.attr('x2'), y: updatedEdge.attr('y2')}]); + + // topPoint.y = Math.min(Math.max(topPoint.y, constraints.y1Range.min), constraints.y1Range.max); + // botPoint.y = Math.min(Math.max(botPoint.y, constraints.y2Range.min), constraints.y2Range.max); + + // vmEdge.points = [topPoint, botPoint]; + // }, + + // updateGrabPoints() { + // const centers = this.getGrabPoints(); + // const edges = this.getEdges(); + // for (let i = 0; i < centers.length; i += 1) { + // const edge = edges[`${i}`]; + // centers[`${i}`].center(edge.cx(), edge.cy()); + // } + + // this.dorsalRightEdge.selectize({ + // points: 't,b', + // rotationPoint: false, + // }).resize().on('resizing', function (e) { + // if (e.detail.event.shiftKey) { + // this.resizeControl(this.viewModel.dr, + // this, + // this.viewModel.computeSideEdgeConstraints(this.viewModel.dr)); + // } else { + // const [ midPointUp ] = this.viewModel.canvasToActual([{x: this.dorsalRightEdge.attr('x1'), y: this.dorsalRightEdge.attr('y1')}])[0]; + // const [ midPointDown ] = this.viewModel.canvasToActual([{x: this.dorsalRightEdge.attr('x2'), y: this.dorsalRightEdge.attr('y2')}])[0]; + // this.viewModel.top.points = this.computeHeightFace(midPointUp, 3); + // this.viewModel.bot.points = this.computeHeightFace(midPointDown, 3); + // } + // this.updateViewAndVM(); + // }); + // this.drCenter.show(); + + // this.dorsalLeftEdge.selectize(false); + // this.dlCenter.hide(); + // }, + + // move(dx, dy) { + // this.face.dmove(dx, dy); + // this.dorsal.dmove(dx, dy); + // this.right.dmove(dx, dy); + // this.left.dmove(dx, dy); + + // const edges = this.getEdges(); + // edges.forEach((edge) => { + // edge.dmove(dx, dy); + // }); + // }, + + // updateView(viewModel) { + // const convertedPoints = window.cvat.translate.points.actualToCanvas( + // viewModel.getPoints(), + // ); + // this.updatePolygons(viewModel); + // this.updateLines(viewModel); + // this.updateProjections(viewModel); + // this.updateGrabPoints(); + // this.attr('points', convertedPoints); + // }, + + // updatePolygons(viewModel) { + // this.face.plot(viewModel.front.canvasPoints); + // this.right.plot(viewModel.right.canvasPoints); + // this.dorsal.plot(viewModel.dorsal.canvasPoints); + // this.left.plot(viewModel.left.canvasPoints); + // }, + + // updateLines(viewModel) { + // this.frontLeftEdge.plot(viewModel.fl.canvasPoints); + // this.frontRightEdge.plot(viewModel.fr.canvasPoints); + // this.dorsalRightEdge.plot(viewModel.dr.canvasPoints); + // this.dorsalLeftEdge.plot(viewModel.dl.canvasPoints); + + // this.frontTopEdge.plot(viewModel.ft.canvasPoints); + // this.rightTopEdge.plot(viewModel.rt.canvasPoints); + // this.frontBotEdge.plot(viewModel.fb.canvasPoints); + // this.rightBotEdge.plot(viewModel.rb.canvasPoints); + // }, + + // updateProjections(viewModel) { + // this.ftProj.plot(this.updateProjectionLine(viewModel.ft.getEquation(), + // viewModel.ft.canvasPoints[0], viewModel.vplCanvas)); + // this.fbProj.plot(this.updateProjectionLine(viewModel.fb.getEquation(), + // viewModel.ft.canvasPoints[0], viewModel.vplCanvas)); + // this.rtProj.plot(this.updateProjectionLine(viewModel.rt.getEquation(), + // viewModel.rt.canvasPoints[1], viewModel.vprCanvas)); + // this.rbProj.plot(this.updateProjectionLine(viewModel.rb.getEquation(), + // viewModel.rt.canvasPoints[1], viewModel.vprCanvas)); + // }, + + // addMouseOverEvents() { + // this._addFaceEvents(); + // }, + + // _addFaceEvents() { + // const group = this; + // this.left.on('mouseover', function () { + // this.attr({ 'fill-opacity': 0.5 }); + // }).on('mouseout', function () { + // this.attr({ 'fill-opacity': group.attr('fill-opacity') }); + // }); + // this.dorsal.on('mouseover', function () { + // this.attr({ 'fill-opacity': 0.5 }); + // }).on('mouseout', function () { + // this.attr({ 'fill-opacity': group.attr('fill-opacity') }); + // }); + // this.right.on('mouseover', function () { + // this.attr({ 'fill-opacity': 0.5 }); + // }).on('mouseout', function () { + // this.attr({ 'fill-opacity': group.attr('fill-opacity') }); + // }); + // }, + + // removeMouseOverEvents() { + // const edges = this.getEdges(); + // edges.forEach((edge) => { + // edge.off('mouseover').off('mouseout'); + // }); + // this.left.off('mouseover').off('mouseout'); + // this.dorsal.off('mouseover').off('mouseout'); + // this.right.off('mouseover').off('mouseout'); + // }, + + // resetFaceOpacity() { + // const group = this; + // this.left.attr({ 'fill-opacity': group.attr('fill-opacity') }); + // this.dorsal.attr({ 'fill-opacity': group.attr('fill-opacity') }); + // this.right.attr({ 'fill-opacity': group.attr('fill-opacity') }); + // }, + + // addOccluded() { + // const edges = this.getEdges(); + // edges.forEach((edge) => { + // edge.node.classList.add('occludedShape'); + // }); + // this.face.attr('stroke-width', 0); + // this.right.attr('stroke-width', 0); + // this.left.node.classList.add('occludedShape'); + // this.dorsal.node.classList.add('occludedShape'); + // }, + + // removeOccluded() { + // const edges = this.getEdges(); + // edges.forEach((edge) => { + // edge.node.classList.remove('occludedShape'); + // }); + // this.face.attr('stroke-width', this.attr('stroke-width')); + // this.right.attr('stroke-width', this.attr('stroke-width')); + // this.left.node.classList.remove('occludedShape'); + // this.dorsal.node.classList.remove('occludedShape'); + // }, }, construct: { - cube(points) { - return this.put(new SVG.Cube()).constructorMethod(points); + cube(points: string) { + return this.put(new (SVG as any).Cube()).constructorMethod(points); }, }, }); \ No newline at end of file From 6a5ef5c9e530f003bc450baaad1aa226785c46ed Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Fri, 17 Apr 2020 01:55:21 +0300 Subject: [PATCH 11/45] drag events with redispatching --- cvat-canvas/src/typescript/shared.ts | 6 ++ cvat-canvas/src/typescript/svg.patch.ts | 132 +++++++++++++++++++++++- 2 files changed, 136 insertions(+), 2 deletions(-) diff --git a/cvat-canvas/src/typescript/shared.ts b/cvat-canvas/src/typescript/shared.ts index f07f1dc11aa8..986aa426e754 100644 --- a/cvat-canvas/src/typescript/shared.ts +++ b/cvat-canvas/src/typescript/shared.ts @@ -129,6 +129,12 @@ export function pointsToObjects(stringified: string): Point[] { }, []); } +export function pointObjectsToString(points: Point[]): string { + return points.reduce((acc: string, point: Point): string => { + return `${acc}${point.x} ${point.y},`; + }, '').slice(0, -1); +} + export function clamp(x: number, min: number, max: number): number { return Math.min(Math.max(x, min), max); }; diff --git a/cvat-canvas/src/typescript/svg.patch.ts b/cvat-canvas/src/typescript/svg.patch.ts index 7eef8a0c940e..849dd16c12f4 100644 --- a/cvat-canvas/src/typescript/svg.patch.ts +++ b/cvat-canvas/src/typescript/svg.patch.ts @@ -10,7 +10,7 @@ import 'svg.select.js'; import 'svg.draw.js'; import { Point, Equation, CuboidModel } from './cuboid'; -import { pointsToObjects } from './shared'; +import { pointsToObjects, pointObjectsToString } from './shared'; // import consts from './consts' // Update constructor @@ -190,7 +190,7 @@ for (const key of Object.keys(originalResize)) { inherit: SVG.G, extend: { constructorMethod(points: string) { - this.attr('points', points); + this._attr('points', points); this._viewModel = new CuboidModel(pointsToObjects(points)); this.setupFaces(); this.setupEdges(); @@ -200,6 +200,8 @@ for (const key of Object.keys(originalResize)) { this.hideProjections(); this.hideGrabPoints(); + this.addEvents(); + return this; }, @@ -326,6 +328,8 @@ for (const key of Object.keys(originalResize)) { this.dorsalRightEdge.selectize(value, options) }, + _attr: SVG.Element.prototype.attr, + attr(a: any, v: any, n: any) { const _attr = SVG.Element.prototype.attr.bind(this); if (a === 'fill' && v !== undefined) { @@ -334,6 +338,20 @@ for (const key of Object.keys(originalResize)) { } else if (a === 'stroke-width' && typeof v === "number") { _attr(a, v, n); this.updateThickness(); + } else if (a === 'projections-visiable' && v === true) { + this.showProjections(); + } else if (a === 'projections-visiable' && v === false) { + this.hideProjections(); + } else if (a === 'points' && typeof v === 'string' && v.length) { + const points = pointsToObjects(a); + if (points.length !== 8 ) { + throw new Error('cuboid points quantity must be exact 8'); + } + pointsToObjects(a).forEach((point: Point, i: number) => { + this._viewModel.points[i] = point; + }); + + this.updateViewAndVM(); } else { return _attr(a, v, n); } @@ -379,6 +397,116 @@ for (const key of Object.keys(originalResize)) { this.left.stroke({ color: fillColor }); }, + dmove(dx: number, dy: number) { + this._viewModel.points.forEach((point: Point) => { + point.x += dx; + point.y += dy; + }); + + this.updateViewAndVM(); + }, + + updateViewAndVM() { + this._viewModel.buildBackEdge(); + this.updateView(); + this._attr('points', pointObjectsToString(this._viewModel.points)); + + }, + + updateView() { + this.updatePolygons(); + this.updateLines(); + this.updateProjections(); + // this.updateGrabPoints(); + }, + + updatePolygons() { + this.face.plot(this._viewModel.front.points); + this.right.plot(this._viewModel.right.points); + this.dorsal.plot(this._viewModel.dorsal.points); + this.left.plot(this._viewModel.left.points); + }, + + updateLines() { + this.frontLeftEdge.plot(this._viewModel.fl.points); + this.frontRightEdge.plot(this._viewModel.fr.points); + this.dorsalRightEdge.plot(this._viewModel.dr.points); + this.dorsalLeftEdge.plot(this._viewModel.dl.points); + + this.frontTopEdge.plot(this._viewModel.ft.points); + this.rightTopEdge.plot(this._viewModel.rt.points); + this.frontBotEdge.plot(this._viewModel.fb.points); + this.rightBotEdge.plot(this._viewModel.rb.points); + }, + + updateProjections() { + this.ftProj.plot(this.updateProjectionLine(this._viewModel.ft.getEquation(), + this._viewModel.ft.points[0], this._viewModel.vpl)); + this.fbProj.plot(this.updateProjectionLine(this._viewModel.fb.getEquation(), + this._viewModel.ft.points[0], this._viewModel.vpl)); + this.rtProj.plot(this.updateProjectionLine(this._viewModel.rt.getEquation(), + this._viewModel.rt.points[1], this._viewModel.vpr)); + this.rbProj.plot(this.updateProjectionLine(this._viewModel.rb.getEquation(), + this._viewModel.rt.points[1], this._viewModel.vpr)); + }, + + addDragEvents() { + this.face.draggable().on('dragstart', (e: CustomEvent) => { + this.dragPoint = { x: e.detail.p.x, + y: e.detail.p.y}; + this.fire('dragstart', e.detail); + }).on('dragmove', (e: CustomEvent) => { + this.dmove(e.detail.p.x - this.dragPoint.x, + e.detail.p.y - this.dragPoint.y); + this.dragPoint = { x: e.detail.p.x, + y: e.detail.p.y } + this.fire('dragmove', e.detail); + }).on('dragend', (e: CustomEvent) => { + this.fire('dragend', e.detail); + }); + + const faces = [this.right, this.dorsal, this.left]; + faces.forEach((face: any, i: number) => { + face.draggable().on('dragstart', (e: CustomEvent) => { + this.dragPoint = { x: e.detail.p.x, + y: e.detail.p.y}; + this.fire('dragstart', e.detail); + }).on('dragmove', (e: CustomEvent) => { + this._viewModel.facesList[i+1].points.forEach((point: Point) => { + point.x += e.detail.p.x - this.dragPoint.x; + point.y += e.detail.p.y - this.dragPoint.y; + }); + this.dragPoint = { x: e.detail.p.x, + y: e.detail.p.y }; + + this.updateViewAndVM(); + this.fire('dragmove', e.detail); + }).on('dragend', (e: CustomEvent) => { + this.fire('dragend', e.detail); + }); + }); + }, + + removeDragEvents() { + const faces = [this.face, this.right, this.dorsal, this.left] + faces.forEach((face: any) => { + face.draggable(false); + face.off('dragstart'); + face.off('dragmove'); + face.off('dragsend'); + }) + }, + + draggable(value: any, constraint: any) { + const _draggable = SVG.Element.prototype.draggable.bind(this) + if (value !== false) { + this.addDragEvents(); + } else { + this.removeDragEvents(); + } + return _draggable(value, constraint); + }, + // addEvents() { // const edges = this.getEdges(); // const grabPoints =this.getGrabPoints(); From c6bb483d9f57bf8e7480d51644aafb81245ef1ba Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Fri, 17 Apr 2020 03:22:27 +0300 Subject: [PATCH 12/45] Added additional grab points --- cvat-canvas/src/typescript/svg.patch.ts | 54 ++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 5 deletions(-) diff --git a/cvat-canvas/src/typescript/svg.patch.ts b/cvat-canvas/src/typescript/svg.patch.ts index 849dd16c12f4..5c52819aff41 100644 --- a/cvat-canvas/src/typescript/svg.patch.ts +++ b/cvat-canvas/src/typescript/svg.patch.ts @@ -200,8 +200,6 @@ for (const key of Object.keys(originalResize)) { this.hideProjections(); this.hideGrabPoints(); - this.addEvents(); - return this; }, @@ -276,10 +274,18 @@ for (const key of Object.keys(originalResize)) { this.rbProj.hide(); }, - showGrabPoints(radius: number, stroke: number, color: string) { + showGrabPoints() { const grabPoints = this.getGrabPoints(); grabPoints.forEach((point: SVG.Circle) => { - point.radius(radius).attr('stroke-width', stroke).show(); + point.attr('stroke-width', this.attr('stroke-width')) + .fill(this.attr('stroke')) + .attr('fill-opacity', 1) + .on('mouseover', () => { + point.attr('stroke-width', this.attr('stroke-width') * 2); + }) + .on('mouseout', () => { + point.attr('stroke-width', this.attr('stroke-width')); + }).show(); }); }, @@ -326,6 +332,11 @@ for (const key of Object.keys(originalResize)) { selectize(value: any, options: any) { this.face.selectize(value, options); this.dorsalRightEdge.selectize(value, options) + if (value === true) { + this.showGrabPoints(); + } else { + this.hideGrabPoints(); + } }, _attr: SVG.Element.prototype.attr, @@ -417,7 +428,7 @@ for (const key of Object.keys(originalResize)) { this.updatePolygons(); this.updateLines(); this.updateProjections(); - // this.updateGrabPoints(); + this.updateGrabPoints(); }, updatePolygons() { @@ -450,6 +461,15 @@ for (const key of Object.keys(originalResize)) { this._viewModel.rt.points[1], this._viewModel.vpr)); }, + updateGrabPoints() { + const centers = this.getGrabPoints(); + const edges = this.getEdges(); + for (let i = 0; i < centers.length; i += 1) { + const edge = edges[i]; + centers[i].center(edge.cx(), edge.cy()); + } + }, + addDragEvents() { this.face.draggable().on('dragstart', (e: CustomEvent) => { this.dragPoint = { x: e.detail.p.x, @@ -507,6 +527,30 @@ for (const key of Object.keys(originalResize)) { return _draggable(value, constraint); }, + addResizeEvents() { + this.face.resize().on('resizestart', (e: CustomEvent) => { + console.log(e); + }).on('resizing', (e: CustomEvent) => { + console.log(e); + }).on('resizedone', (e: CustomEvent) => { + console.log(e); + }) + }, + + removeResizeEvents() { + + }, + + resize(options: any) { + const _resize = SVG.Element.prototype.resize.bind(this); + if (options !== false) { + this.addResizeEvents(); + } else { + this.removeResizeEvents(); + } + return _resize(options); + }, + // addEvents() { // const edges = this.getEdges(); // const grabPoints =this.getGrabPoints(); From 73e8988edc09e57216b4173fa0a2426e0a6130f2 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Fri, 17 Apr 2020 11:18:22 +0300 Subject: [PATCH 13/45] Resizing using front face --- cvat-canvas/src/typescript/canvasView.ts | 2 +- cvat-canvas/src/typescript/cuboid.ts | 14 +- cvat-canvas/src/typescript/svg.patch.ts | 376 ++++++++++++----------- 3 files changed, 218 insertions(+), 174 deletions(-) diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index 7893012f17f2..594e13f8bd3b 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -1190,7 +1190,7 @@ export class CanvasViewImpl implements CanvasView, Listener { (shape as any).off('resizestart'); (shape as any).off('resizing'); (shape as any).off('resizedone'); - (shape as any).resize(false); + (shape as any).resize('stop'); // TODO: Hide text only if it is hidden by settings const text = this.svgTexts[clientID]; diff --git a/cvat-canvas/src/typescript/cuboid.ts b/cvat-canvas/src/typescript/cuboid.ts index 396418172a97..8a255a2259f9 100644 --- a/cvat-canvas/src/typescript/cuboid.ts +++ b/cvat-canvas/src/typescript/cuboid.ts @@ -2,7 +2,7 @@ /* eslint-disable no-underscore-dangle */ /* eslint-disable curly */ /* - * Copyright (C) 2018 Intel Corporation + * Copyright (C) 2020 Intel Corporation * * SPDX-License-Identifier: MIT */ @@ -81,9 +81,10 @@ export class Figure { // if you only need to update a subset of the points, // simply put null for the points you want to keep public set points(newPoints) { + const oldPoints = this.allPoints; for (let i = 0; i < newPoints.length; i += 1) { if (newPoints[i] !== null) { - this.allPoints[this.indices[i]] = { x: newPoints[i].x, y: newPoints[i].y }; + oldPoints[this.indices[i]] = { x: newPoints[i].x, y: newPoints[i].y }; } } } @@ -135,6 +136,15 @@ export class CuboidModel { public setPoints(points: Point[]): void { this.points = points; + // for (const edge of this.edgeList) { + // edge.points = points; + // } + + // for (const face of this.facesList) { + // face.points = points; + // } + + // this.updatePoints(); } public updatePoints(): void { diff --git a/cvat-canvas/src/typescript/svg.patch.ts b/cvat-canvas/src/typescript/svg.patch.ts index 7eef8a0c940e..2b1434ab17bd 100644 --- a/cvat-canvas/src/typescript/svg.patch.ts +++ b/cvat-canvas/src/typescript/svg.patch.ts @@ -9,6 +9,7 @@ import 'svg.resize.js'; import 'svg.select.js'; import 'svg.draw.js'; +import consts from './consts'; import { Point, Equation, CuboidModel } from './cuboid'; import { pointsToObjects } from './shared'; // import consts from './consts' @@ -190,16 +191,13 @@ for (const key of Object.keys(originalResize)) { inherit: SVG.G, extend: { constructorMethod(points: string) { - this.attr('points', points); this._viewModel = new CuboidModel(pointsToObjects(points)); this.setupFaces(); this.setupEdges(); this.setupProjections(); - this.setupGrabPoints(); - this.hideProjections(); - this.hideGrabPoints(); + this.attr('points', points); return this; }, @@ -238,26 +236,6 @@ for (const key of Object.keys(originalResize)) { this.rightBotEdge = this.line(this._viewModel.rb.points); }, - setupGrabPoints() { - this.flCenter = this.circle().addClass('svg_select_points').addClass('svg_select_points_l'); - this.frCenter = this.circle().addClass('svg_select_points').addClass('svg_select_points_r'); - this.drCenter = this.circle().addClass('svg_select_points').addClass('svg_select_points_ew'); - this.dlCenter = this.circle().addClass('svg_select_points').addClass('svg_select_points_ew'); - - this.ftCenter = this.circle().addClass('svg_select_points').addClass('svg_select_points_t'); - this.fbCenter = this.circle().addClass('svg_select_points').addClass('svg_select_points_b'); - - const grabPoints = this.getGrabPoints(); - const edges = this.getEdges(); - for (let i = 0; i < grabPoints.length; i += 1) { - const edge = edges[i]; - const cx = (edge.attr('x2') + edge.attr('x1')) / 2; - const cy = (edge.attr('y2') + edge.attr('y1')) / 2; - grabPoints[i].center(cx, cy); - } - }, - - showProjections() { if (this.projectionLineEnable) { this.ftProj.show(); @@ -274,20 +252,6 @@ for (const key of Object.keys(originalResize)) { this.rbProj.hide(); }, - showGrabPoints(radius: number, stroke: number, color: string) { - const grabPoints = this.getGrabPoints(); - grabPoints.forEach((point: SVG.Circle) => { - point.radius(radius).attr('stroke-width', stroke).show(); - }); - }, - - hideGrabPoints() { - const grabPoints = this.getGrabPoints(); - grabPoints.forEach((point: SVG.Circle) => { - point.hide(); - }); - }, - getEdges() { const arr = []; arr.push(this.frontLeftEdge); @@ -301,17 +265,6 @@ for (const key of Object.keys(originalResize)) { return arr; }, - getGrabPoints() { - const arr = []; - arr.push(this.flCenter); - arr.push(this.frCenter); - arr.push(this.drCenter); - arr.push(this.ftCenter); - arr.push(this.fbCenter); - arr.push(this.dlCenter); - return arr; - }, - updateProjectionLine(equation: Equation, source: Point, direction: Point) { const x1 = source.x; const y1 = equation.getY(x1); @@ -323,7 +276,121 @@ for (const key of Object.keys(originalResize)) { selectize(value: any, options: any) { this.face.selectize(value, options); - this.dorsalRightEdge.selectize(value, options) + this.dorsalRightEdge.selectize(value, options); + + return this; + }, + + resize(value?: string | object) { + this.face.resize(value); + this.dorsalRightEdge.resize(value); + + this.face.off('resizing').off('resizedone').off('resizestart'); + this.dorsalRightEdge.off('resizing').off('resizedone').off('resizestart'); + + if (value !== 'stop') { + let cubePoints: Point[] = []; + let resizablePointIndex: null | number = null; + + this.face.on('resizestart', (event: CustomEvent) => { + cubePoints = JSON.parse(JSON.stringify(this._viewModel.getPoints())); + const { target } = event.detail.event.detail.event; + const { parentElement } = target; + resizablePointIndex = Array + .from(parentElement.children) + .indexOf(target); + this.fire(new CustomEvent('resizedone', event)); + }).on('resizing', (event: CustomEvent) => { + const { dx, dy } = event.detail; + const facePoints = this.face + .attr('points') + .split(/\s/) + .map((point: string): Point => { + const [x, y]: number[] = point.split(',').map((coord: string): number => +coord); + return { + x, + y, + }; + }); + + if (facePoints[2].x - facePoints[1].x + dx < consts.MIN_EDGE_LENGTH + || facePoints[1].y - facePoints[0].y + dy < consts.MIN_EDGE_LENGTH + ) { + this.face.plot(this._viewModel.front.points); + return; + } + + if ([0, 1].includes(resizablePointIndex)) { + // up top or bottom edge on the front face + if (resizablePointIndex === 0) { + this._viewModel.ft.points[0].y = cubePoints[0].y + dy; + } else if (resizablePointIndex === 1) { + this._viewModel.fb.points[0].y = cubePoints[1].y + dy; + } + + // shift back edge on dx (only one point, the second will be shifted later) + this._viewModel.dl.points[0].x = cubePoints[7].x + dx; + + // get top and bottom x and y for this edge (front left) + const x1 = cubePoints[0].x + dx; + const x2 = cubePoints[1].x + dx; + const y1 = this._viewModel.ft.getEquation().getY(x1); + const y2 = this._viewModel.fb.getEquation().getY(x2); + + // now compute new coordinates for top and bottom faces + const midPointUp = { x: x1, y: y1 }; + const midPointDown = { x: x2, y: y2 }; + const topPoints = this.computeHeightFace(midPointUp, 1); + const bottomPoints = this.computeHeightFace(midPointDown, 1); + + // and apply them + this._viewModel.top.points = topPoints; + this._viewModel.bot.points = bottomPoints; + } else if ([2, 3].includes(resizablePointIndex)) { + // up top or bottom edge on the front face + if (resizablePointIndex === 3) { + this._viewModel.ft.points[1].y = cubePoints[2].y + dy; + } else if (resizablePointIndex === 2) { + this._viewModel.fb.points[1].y = cubePoints[3].y + dy; + } + + // shift back edge on dx (only one point, the second will be shifted later) + this._viewModel.dr.points[0].x = cubePoints[4].x + dx; + + // get top and bottom x and y for this edge (front left) + const x1 = cubePoints[2].x + dx; + const x2 = cubePoints[3].x + dx; + const y1 = this._viewModel.ft.getEquation().getY(x1); + const y2 = this._viewModel.fb.getEquation().getY(x2); + + // now compute new coordinates for top and bottom faces + const midPointUp = { x: x1, y: y1 }; + const midPointDown = { x: x2, y: y2 }; + const topPoints = this.computeHeightFace(midPointUp, 2); + const bottomPoints = this.computeHeightFace(midPointDown, 2); + + // and apply them + this._viewModel.top.points = topPoints; + this._viewModel.bot.points = bottomPoints; + } + + this.updateView(); + this.face.plot(this._viewModel.front.points); + this.fire(new CustomEvent('resizing', event)); + }).on('resizedone', () => { + this.fire(new CustomEvent('resizedone', event)); + }); + + this.dorsalRightEdge.on('resizestart', () => { + // TODO + }).on('resizing', () => { + // TODO + }).on('resizedone', () => { + // TODO + }); + } + + return this; }, attr(a: any, v: any, n: any) { @@ -381,7 +448,6 @@ for (const key of Object.keys(originalResize)) { // addEvents() { // const edges = this.getEdges(); - // const grabPoints =this.getGrabPoints(); // const draggableFaces = [ // this.left, // this.dorsal, @@ -394,7 +460,6 @@ for (const key of Object.keys(originalResize)) { // this.orientation = 2; // } - // this.updateGrabPoints(); // edges.forEach((edge) => { // edge.on('resizestart', () => { // // TODO: dipatch proper canvas event @@ -404,15 +469,6 @@ for (const key of Object.keys(originalResize)) { // // TODO: dipatch proper canvas event // }); // }); - // grabPoints.forEach((grabPoint) => { - // grabPoint.on('dragstart', () => { - // // TODO: dipatch proper canvas event - // }).on('dragend', () => { - // // TODO: dipatch proper canvas event - // this.updateModel(); - // this.updateViewModel(); - // }); - // }); // draggableFaces.forEach((face) => { // face.on('dragstart', () => { @@ -421,7 +477,6 @@ for (const key of Object.keys(originalResize)) { // // TODO: dipatch proper canvas event // this.updateModel(); // this.updateViewModel(); - // this.updateGrabPoints(); // }); // }); @@ -575,41 +630,41 @@ for (const key of Object.keys(originalResize)) { // this.viewModel.setPoints(newPoints); // }, - // computeHeightFace(point, index) { - // switch (index) { - // // fl - // case 1: { - // const p2 = this.updatedEdge(this.viewModel.fr.points[0], point, this.viewModel.vpl); - // const p3 = this.updatedEdge(this.viewModel.dr.points[0], p2, this.viewModel.vpr); - // const p4 = this.updatedEdge(this.viewModel.dl.points[0], point, this.viewModel.vpr); - // return [point, p2, p3, p4]; - // } - // // fr - // case 2: { - // const p2 = this.updatedEdge(this.viewModel.fl.points[0], point, this.viewModel.vpl); - // const p3 = this.updatedEdge(this.viewModel.dr.points[0], point, this.viewModel.vpr); - // const p4 = this.updatedEdge(this.viewModel.dl.points[0], p3, this.viewModel.vpr); - // return [p2, point, p3, p4]; - // } - // // dr - // case 3: { - // const p2 = this.updatedEdge(this.viewModel.dl.points[0], point, this.viewModel.vpl); - // const p3 = this.updatedEdge(this.viewModel.fr.points[0], point, this.viewModel.vpr); - // const p4 = this.updatedEdge(this.viewModel.fl.points[0], p2, this.viewModel.vpr); - // return [p4, p3, point, p2]; - // } - // // dl - // case 4: { - // const p2 = this.updatedEdge(this.viewModel.dr.points[0], point, this.viewModel.vpl); - // const p3 = this.updatedEdge(this.viewModel.fl.points[0], point, this.viewModel.vpr); - // const p4 = this.updatedEdge(this.viewModel.fr.points[0], p2, this.viewModel.vpr); - // return [p3, p4, p2, point]; - // } - // default: { - // return [null, null, null, null]; - // } - // } - // }, + computeHeightFace(point: Point, index: number) { + switch (index) { + // fl + case 1: { + const p2 = this.updatedEdge(this._viewModel.fr.points[0], point, this._viewModel.vpl); + const p3 = this.updatedEdge(this._viewModel.dr.points[0], p2, this._viewModel.vpr); + const p4 = this.updatedEdge(this._viewModel.dl.points[0], point, this._viewModel.vpr); + return [point, p2, p3, p4]; + } + // fr + case 2: { + const p1 = this.updatedEdge(this._viewModel.fl.points[0], point, this._viewModel.vpl); + const p3 = this.updatedEdge(this._viewModel.dr.points[0], point, this._viewModel.vpr); + const p4 = this.updatedEdge(this._viewModel.dl.points[0], p1, this._viewModel.vpr); + return [p1, point, p3, p4]; + } + // dr + case 3: { + const p2 = this.updatedEdge(this._viewModel.dl.points[0], point, this._viewModel.vpl); + const p3 = this.updatedEdge(this._viewModel.fr.points[0], point, this._viewModel.vpr); + const p4 = this.updatedEdge(this._viewModel.fl.points[0], p2, this._viewModel.vpr); + return [p4, p3, point, p2]; + } + // dl + case 4: { + const p2 = this.updatedEdge(this._viewModel.dr.points[0], point, this._viewModel.vpl); + const p3 = this.updatedEdge(this._viewModel.fl.points[0], point, this._viewModel.vpr); + const p4 = this.updatedEdge(this._viewModel.fr.points[0], p2, this._viewModel.vpr); + return [p3, p4, p2, point]; + } + default: { + return [null, null, null, null]; + } + } + }, // updateViewAndVM() { // this.viewModel.buildBackEdge(); @@ -621,13 +676,12 @@ for (const key of Object.keys(originalResize)) { // this.udpateView(this.viewModel); // }, - // updatedEdge(target, base, pivot) { - // const targetX = target.x; - // const line = new Equation(pivot, - // [base.x, base.y], this.viewModel); - // const newY = line.getY(targetX); - // return { x: targetX, y: newY }; - // }, + updatedEdge(target: Point, base: Point, pivot: Point) { + const targetX = target.x; + const line = new Equation(pivot, base); + const newY = line.getY(targetX); + return { x: targetX, y: newY }; + }, // resizeControl(vmEdge, updatedEdge, constraints) { // const [ topPoint ] = this.viewModel.canvasToActual([{x: updatedEdge.attr('x1'), y: updatedEdge.attr('y1')}]); @@ -639,36 +693,6 @@ for (const key of Object.keys(originalResize)) { // vmEdge.points = [topPoint, botPoint]; // }, - // updateGrabPoints() { - // const centers = this.getGrabPoints(); - // const edges = this.getEdges(); - // for (let i = 0; i < centers.length; i += 1) { - // const edge = edges[`${i}`]; - // centers[`${i}`].center(edge.cx(), edge.cy()); - // } - - // this.dorsalRightEdge.selectize({ - // points: 't,b', - // rotationPoint: false, - // }).resize().on('resizing', function (e) { - // if (e.detail.event.shiftKey) { - // this.resizeControl(this.viewModel.dr, - // this, - // this.viewModel.computeSideEdgeConstraints(this.viewModel.dr)); - // } else { - // const [ midPointUp ] = this.viewModel.canvasToActual([{x: this.dorsalRightEdge.attr('x1'), y: this.dorsalRightEdge.attr('y1')}])[0]; - // const [ midPointDown ] = this.viewModel.canvasToActual([{x: this.dorsalRightEdge.attr('x2'), y: this.dorsalRightEdge.attr('y2')}])[0]; - // this.viewModel.top.points = this.computeHeightFace(midPointUp, 3); - // this.viewModel.bot.points = this.computeHeightFace(midPointDown, 3); - // } - // this.updateViewAndVM(); - // }); - // this.drCenter.show(); - - // this.dorsalLeftEdge.selectize(false); - // this.dlCenter.hide(); - // }, - // move(dx, dy) { // this.face.dmove(dx, dy); // this.dorsal.dmove(dx, dy); @@ -681,46 +705,56 @@ for (const key of Object.keys(originalResize)) { // }); // }, - // updateView(viewModel) { - // const convertedPoints = window.cvat.translate.points.actualToCanvas( - // viewModel.getPoints(), - // ); - // this.updatePolygons(viewModel); - // this.updateLines(viewModel); - // this.updateProjections(viewModel); - // this.updateGrabPoints(); - // this.attr('points', convertedPoints); - // }, + updateView() { + this.updateFaces(); + this.updateEdges(); + this.updateProjections(); - // updatePolygons(viewModel) { - // this.face.plot(viewModel.front.canvasPoints); - // this.right.plot(viewModel.right.canvasPoints); - // this.dorsal.plot(viewModel.dorsal.canvasPoints); - // this.left.plot(viewModel.left.canvasPoints); - // }, + // to correct getting of points in resizedone, dragdone + this.attr('points', this._viewModel + .getPoints() + .reduce((acc: string, point: Point): string => `${acc} ${point.x},${point.y}`, '').trim()); + }, - // updateLines(viewModel) { - // this.frontLeftEdge.plot(viewModel.fl.canvasPoints); - // this.frontRightEdge.plot(viewModel.fr.canvasPoints); - // this.dorsalRightEdge.plot(viewModel.dr.canvasPoints); - // this.dorsalLeftEdge.plot(viewModel.dl.canvasPoints); + updateFaces() { + const viewModel = this._viewModel; + + const frontPoints = viewModel.front.points; + this.face.resize() + .resize(frontPoints[2].x - frontPoints[0].x, frontPoints[1].y - frontPoints[0].y) + .move(frontPoints[0].x, frontPoints[0].y); + + this.right.plot(viewModel.right.points); + this.dorsal.plot(viewModel.dorsal.points); + this.left.plot(viewModel.left.points); + }, - // this.frontTopEdge.plot(viewModel.ft.canvasPoints); - // this.rightTopEdge.plot(viewModel.rt.canvasPoints); - // this.frontBotEdge.plot(viewModel.fb.canvasPoints); - // this.rightBotEdge.plot(viewModel.rb.canvasPoints); - // }, + updateEdges() { + const viewModel = this._viewModel; - // updateProjections(viewModel) { - // this.ftProj.plot(this.updateProjectionLine(viewModel.ft.getEquation(), - // viewModel.ft.canvasPoints[0], viewModel.vplCanvas)); - // this.fbProj.plot(this.updateProjectionLine(viewModel.fb.getEquation(), - // viewModel.ft.canvasPoints[0], viewModel.vplCanvas)); - // this.rtProj.plot(this.updateProjectionLine(viewModel.rt.getEquation(), - // viewModel.rt.canvasPoints[1], viewModel.vprCanvas)); - // this.rbProj.plot(this.updateProjectionLine(viewModel.rb.getEquation(), - // viewModel.rt.canvasPoints[1], viewModel.vprCanvas)); - // }, + this.frontLeftEdge.plot(viewModel.fl.points); + this.frontRightEdge.plot(viewModel.fr.points); + this.dorsalRightEdge.plot(viewModel.dr.points); + this.dorsalLeftEdge.plot(viewModel.dl.points); + + this.frontTopEdge.plot(viewModel.ft.points); + this.rightTopEdge.plot(viewModel.rt.points); + this.frontBotEdge.plot(viewModel.fb.points); + this.rightBotEdge.plot(viewModel.rb.points); + }, + + updateProjections() { + const viewModel = this._viewModel; + + // this.ftProj.plot(this.updateProjectionLine(viewModel.ft.getEquation(), + // viewModel.ft.points[0], viewModel.vplCanvas)); + // this.fbProj.plot(this.updateProjectionLine(viewModel.fb.getEquation(), + // viewModel.ft.points[0], viewModel.vplCanvas)); + // this.rtProj.plot(this.updateProjectionLine(viewModel.rt.getEquation(), + // viewModel.rt.points[1], viewModel.vprCanvas)); + // this.rbProj.plot(this.updateProjectionLine(viewModel.rb.getEquation(), + // viewModel.rt.points[1], viewModel.vprCanvas)); + }, // addMouseOverEvents() { // this._addFaceEvents(); @@ -789,4 +823,4 @@ for (const key of Object.keys(originalResize)) { return this.put(new (SVG as any).Cube()).constructorMethod(points); }, }, -}); \ No newline at end of file +}); From 24fc773993b4adc2880a78c3c03f793662225f6c Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Fri, 17 Apr 2020 13:02:02 +0300 Subject: [PATCH 14/45] Fixed typos --- cvat-canvas/src/typescript/svg.patch.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cvat-canvas/src/typescript/svg.patch.ts b/cvat-canvas/src/typescript/svg.patch.ts index f008eeef0b38..66c070acf249 100644 --- a/cvat-canvas/src/typescript/svg.patch.ts +++ b/cvat-canvas/src/typescript/svg.patch.ts @@ -300,7 +300,7 @@ for (const key of Object.keys(originalResize)) { resizablePointIndex = Array .from(parentElement.children) .indexOf(target); - this.fire(new CustomEvent('resizedone', event)); + this.fire(new CustomEvent('resizestart', event)); }).on('resizing', (event: CustomEvent) => { const { dx, dy } = event.detail; const facePoints = this.face From b8a21c84b69b3a837f1a2b885c65033f60ea02cd Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Fri, 17 Apr 2020 13:53:04 +0300 Subject: [PATCH 15/45] Dorsal right edge resizing --- cvat-canvas/src/typescript/svg.patch.ts | 58 ++++++++++++++++++++----- 1 file changed, 47 insertions(+), 11 deletions(-) diff --git a/cvat-canvas/src/typescript/svg.patch.ts b/cvat-canvas/src/typescript/svg.patch.ts index 66c070acf249..5d7fdd80b086 100644 --- a/cvat-canvas/src/typescript/svg.patch.ts +++ b/cvat-canvas/src/typescript/svg.patch.ts @@ -378,16 +378,52 @@ for (const key of Object.keys(originalResize)) { this.updateView(); this.face.plot(this._viewModel.front.points); this.fire(new CustomEvent('resizing', event)); - }).on('resizedone', () => { + }).on('resizedone', (event: CustomEvent) => { this.fire(new CustomEvent('resizedone', event)); }); - this.dorsalRightEdge.on('resizestart', () => { - // TODO - }).on('resizing', () => { - // TODO - }).on('resizedone', () => { - // TODO + this.dorsalRightEdge.on('resizestart', (event: CustomEvent) => { + cubePoints = JSON.parse(JSON.stringify(this._viewModel.getPoints())); + const { target } = event.detail.event.detail.event; + const { parentElement } = target; + resizablePointIndex = Array + .from(parentElement.children) + .indexOf(target); + this.fire(new CustomEvent('resizestart', event)); + }).on('resizing', (event: CustomEvent) => { + const { dx, dy } = event.detail; + + // up top or bottom edge on the dorsal face + if (resizablePointIndex === 0) { + this._viewModel.dt.points[1].y = cubePoints[4].y + dy; + } else if (resizablePointIndex === 1) { + this._viewModel.db.points[1].y = cubePoints[5].y + dy; + } + + // shift front edge on dx (only one point, the second will be shifted later) + this._viewModel.fr.points[0].x = cubePoints[2].x + dx; + + // get top and bottom x and y for this edge (front left) + const x1 = cubePoints[4].x + dx; + const x2 = cubePoints[5].x + dx; + const y1 = this._viewModel.dt.getEquation().getY(x1); + const y2 = this._viewModel.db.getEquation().getY(x2); + + // now compute new coordinates for top and bottom faces + const midPointUp = { x: x1, y: y1 }; + const midPointDown = { x: x2, y: y2 }; + const topPoints = this.computeHeightFace(midPointUp, 3); + const bottomPoints = this.computeHeightFace(midPointDown, 3); + + // and apply them + this._viewModel.top.points = topPoints; + this._viewModel.bot.points = bottomPoints; + + this.updateView(); + this.face.plot(this._viewModel.front.points); + this.fire(new CustomEvent('resizing', event)); + }).on('resizedone', (event: CustomEvent) => { + this.fire(new CustomEvent('resizedone', event)); }); } @@ -770,10 +806,10 @@ for (const key of Object.keys(originalResize)) { } // dr case 3: { - const p2 = this.updatedEdge(this._viewModel.dl.points[0], point, this._viewModel.vpl); - const p3 = this.updatedEdge(this._viewModel.fr.points[0], point, this._viewModel.vpr); - const p4 = this.updatedEdge(this._viewModel.fl.points[0], p2, this._viewModel.vpr); - return [p4, p3, point, p2]; + const p2 = this.updatedEdge(this._viewModel.fr.points[0], point, this._viewModel.vpr); + const p4 = this.updatedEdge(this._viewModel.dl.points[0], point, this._viewModel.vpl); + const p1 = this.updatedEdge(this._viewModel.fl.points[0], p4, this._viewModel.vpr); + return [p1, p2, point, p4]; } // dl case 4: { From aa611bb775580782997bd6af4f1eadd86277796c Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Fri, 17 Apr 2020 14:05:59 +0300 Subject: [PATCH 16/45] Updated comments --- cvat-canvas/src/typescript/svg.patch.ts | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/cvat-canvas/src/typescript/svg.patch.ts b/cvat-canvas/src/typescript/svg.patch.ts index 5d7fdd80b086..28c2b53914a9 100644 --- a/cvat-canvas/src/typescript/svg.patch.ts +++ b/cvat-canvas/src/typescript/svg.patch.ts @@ -322,17 +322,22 @@ for (const key of Object.keys(originalResize)) { } if ([0, 1].includes(resizablePointIndex)) { - // up top or bottom edge on the front face + // if we changed Y for the first end of the edge + // we must change Y for the second end of the edge on the same value + // here we up top or bottom edge on the front face if (resizablePointIndex === 0) { this._viewModel.ft.points[0].y = cubePoints[0].y + dy; } else if (resizablePointIndex === 1) { this._viewModel.fb.points[0].y = cubePoints[1].y + dy; } - // shift back edge on dx (only one point, the second will be shifted later) + // we must shift neightbour edge on X (dl for fl, dr for fr) + // actually shifting happens in computeHeightFace() function + // but this function is going to use this point, so we must update it + // shift back edge on dx (only one point to computing, the second will be shifted later) this._viewModel.dl.points[0].x = cubePoints[7].x + dx; - // get top and bottom x and y for this edge (front left) + // compute new coordinates for this cube point const x1 = cubePoints[0].x + dx; const x2 = cubePoints[1].x + dx; const y1 = this._viewModel.ft.getEquation().getY(x1); @@ -344,33 +349,29 @@ for (const key of Object.keys(originalResize)) { const topPoints = this.computeHeightFace(midPointUp, 1); const bottomPoints = this.computeHeightFace(midPointDown, 1); - // and apply them + // and apply these faces to the cube + // all points of the cube will be updated properly this._viewModel.top.points = topPoints; this._viewModel.bot.points = bottomPoints; } else if ([2, 3].includes(resizablePointIndex)) { - // up top or bottom edge on the front face if (resizablePointIndex === 3) { this._viewModel.ft.points[1].y = cubePoints[2].y + dy; } else if (resizablePointIndex === 2) { this._viewModel.fb.points[1].y = cubePoints[3].y + dy; } - // shift back edge on dx (only one point, the second will be shifted later) this._viewModel.dr.points[0].x = cubePoints[4].x + dx; - // get top and bottom x and y for this edge (front left) const x1 = cubePoints[2].x + dx; const x2 = cubePoints[3].x + dx; const y1 = this._viewModel.ft.getEquation().getY(x1); const y2 = this._viewModel.fb.getEquation().getY(x2); - // now compute new coordinates for top and bottom faces const midPointUp = { x: x1, y: y1 }; const midPointDown = { x: x2, y: y2 }; const topPoints = this.computeHeightFace(midPointUp, 2); const bottomPoints = this.computeHeightFace(midPointDown, 2); - // and apply them this._viewModel.top.points = topPoints; this._viewModel.bot.points = bottomPoints; } @@ -393,29 +394,24 @@ for (const key of Object.keys(originalResize)) { }).on('resizing', (event: CustomEvent) => { const { dx, dy } = event.detail; - // up top or bottom edge on the dorsal face if (resizablePointIndex === 0) { this._viewModel.dt.points[1].y = cubePoints[4].y + dy; } else if (resizablePointIndex === 1) { this._viewModel.db.points[1].y = cubePoints[5].y + dy; } - // shift front edge on dx (only one point, the second will be shifted later) this._viewModel.fr.points[0].x = cubePoints[2].x + dx; - // get top and bottom x and y for this edge (front left) const x1 = cubePoints[4].x + dx; const x2 = cubePoints[5].x + dx; const y1 = this._viewModel.dt.getEquation().getY(x1); const y2 = this._viewModel.db.getEquation().getY(x2); - // now compute new coordinates for top and bottom faces const midPointUp = { x: x1, y: y1 }; const midPointDown = { x: x2, y: y2 }; const topPoints = this.computeHeightFace(midPointUp, 3); const bottomPoints = this.computeHeightFace(midPointDown, 3); - // and apply them this._viewModel.top.points = topPoints; this._viewModel.bot.points = bottomPoints; From e8c6e394f869b78ad7c97ae052f208fcc331aa1e Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Sat, 18 Apr 2020 01:54:24 +0300 Subject: [PATCH 17/45] Fixed resize of objects with not standard perspective --- cvat-canvas/src/typescript/cuboid.ts | 44 ++++-- cvat-canvas/src/typescript/svg.patch.ts | 183 +++++++++++++----------- 2 files changed, 132 insertions(+), 95 deletions(-) diff --git a/cvat-canvas/src/typescript/cuboid.ts b/cvat-canvas/src/typescript/cuboid.ts index 8a255a2259f9..c83b0c38e8ee 100644 --- a/cvat-canvas/src/typescript/cuboid.ts +++ b/cvat-canvas/src/typescript/cuboid.ts @@ -187,10 +187,19 @@ export class CuboidModel { } // boolean value parameter controls which edges should be used to recalculate vanishing points - private updateVanishingPoints(): void { - const leftEdge = this.fl.points; - const rightEdge = this.dr.points; - const midEdge = this.fr.points; + private updateVanishingPoints(buildright: boolean): void { + let leftEdge = []; + let rightEdge = []; + let midEdge = []; + if (buildright) { + leftEdge = this.fr.points; + rightEdge = this.dl.points; + midEdge = this.fl.points; + } else { + leftEdge = this.fl.points; + rightEdge = this.dr.points; + midEdge = this.fr.points; + } this.vpl = intersection(leftEdge[0], midEdge[0], leftEdge[1], midEdge[1]); this.vpr = intersection(rightEdge[0], midEdge[0], rightEdge[1], midEdge[1]); @@ -241,12 +250,27 @@ export class CuboidModel { this.facesList = [this.front, this.right, this.dorsal, this.left]; } - private buildBackEdge(): void { - this.updateVanishingPoints(); - const leftPoints = this.dr.points; - const rightPoints = this.fl.points; - const topIndex = 6; - const botIndex = 7; + private buildBackEdge(buildright: boolean): void { + this.updateVanishingPoints(buildright); + let leftPoints = []; + let rightPoints = []; + + let topIndex = 0; + let botIndex = 0; + + if (buildright) { + this.updateVanishingPoints(true); + leftPoints = this.dl.points; + rightPoints = this.fr.points; + topIndex = 4; + botIndex = 5; + } else { + this.updateVanishingPoints(false); + leftPoints = this.dr.points; + rightPoints = this.fl.points; + topIndex = 6; + botIndex = 7; + } const vpLeft = this.vpl; const vpRight = this.vpr; diff --git a/cvat-canvas/src/typescript/svg.patch.ts b/cvat-canvas/src/typescript/svg.patch.ts index 28c2b53914a9..c9a1e6427652 100644 --- a/cvat-canvas/src/typescript/svg.patch.ts +++ b/cvat-canvas/src/typescript/svg.patch.ts @@ -290,6 +290,10 @@ for (const key of Object.keys(originalResize)) { this.dorsalRightEdge.off('resizing').off('resizedone').off('resizestart'); if (value !== 'stop') { + const accumulatedOffset: Point = { + x: 0, + y: 0, + }; let cubePoints: Point[] = []; let resizablePointIndex: null | number = null; @@ -301,9 +305,16 @@ for (const key of Object.keys(originalResize)) { .from(parentElement.children) .indexOf(target); this.fire(new CustomEvent('resizestart', event)); + accumulatedOffset.x = 0; + accumulatedOffset.y = 0; }).on('resizing', (event: CustomEvent) => { - const { dx, dy } = event.detail; - const facePoints = this.face + let { dx, dy } = event.detail; + let dxPortion = dx - accumulatedOffset.x; + let dyPortion = dy - accumulatedOffset.y; + accumulatedOffset.x += dxPortion; + accumulatedOffset.y += dyPortion; + + let facePoints = this.face .attr('points') .split(/\s/) .map((point: string): Point => { @@ -319,64 +330,72 @@ for (const key of Object.keys(originalResize)) { ) { this.face.plot(this._viewModel.front.points); return; - } + } - if ([0, 1].includes(resizablePointIndex)) { - // if we changed Y for the first end of the edge - // we must change Y for the second end of the edge on the same value - // here we up top or bottom edge on the front face - if (resizablePointIndex === 0) { - this._viewModel.ft.points[0].y = cubePoints[0].y + dy; - } else if (resizablePointIndex === 1) { - this._viewModel.fb.points[0].y = cubePoints[1].y + dy; - } - // we must shift neightbour edge on X (dl for fl, dr for fr) - // actually shifting happens in computeHeightFace() function - // but this function is going to use this point, so we must update it - // shift back edge on dx (only one point to computing, the second will be shifted later) - this._viewModel.dl.points[0].x = cubePoints[7].x + dx; + if ([0, 1].includes(resizablePointIndex)) { + facePoints = this._viewModel.getPoints(); - // compute new coordinates for this cube point - const x1 = cubePoints[0].x + dx; - const x2 = cubePoints[1].x + dx; + const x1 = facePoints[0].x + dxPortion; + const x2 = facePoints[1].x + dxPortion; const y1 = this._viewModel.ft.getEquation().getY(x1); const y2 = this._viewModel.fb.getEquation().getY(x2); + const topPoint = { x: x1, y: y1 }; + const botPoint = { x: x2, y: y2 }; + + this._viewModel.fl.points = [topPoint, botPoint]; + this.updateViewAndVM(); - // now compute new coordinates for top and bottom faces - const midPointUp = { x: x1, y: y1 }; - const midPointDown = { x: x2, y: y2 }; + + facePoints = this._viewModel.getPoints(); + let midPointUp: Point | null = null; + let midPointDown: Point | null = null; + if (resizablePointIndex === 0) { + midPointUp = { x: facePoints[0].x, y: facePoints[0].y + dyPortion } + midPointDown = { x: facePoints[1].x, y: facePoints[1].y }; + } else if (resizablePointIndex === 1) { + midPointUp = { x: facePoints[0].x, y: facePoints[0].y }; + midPointDown = { x: facePoints[1].x, y: facePoints[1].y + dyPortion }; + } + const topPoints = this.computeHeightFace(midPointUp, 1); const bottomPoints = this.computeHeightFace(midPointDown, 1); - - // and apply these faces to the cube - // all points of the cube will be updated properly this._viewModel.top.points = topPoints; - this._viewModel.bot.points = bottomPoints; + this._viewModel.bot.points = bottomPoints; + this.updateViewAndVM(false); } else if ([2, 3].includes(resizablePointIndex)) { - if (resizablePointIndex === 3) { - this._viewModel.ft.points[1].y = cubePoints[2].y + dy; - } else if (resizablePointIndex === 2) { - this._viewModel.fb.points[1].y = cubePoints[3].y + dy; - } + facePoints = this._viewModel.getPoints(); - this._viewModel.dr.points[0].x = cubePoints[4].x + dx; - - const x1 = cubePoints[2].x + dx; - const x2 = cubePoints[3].x + dx; + const x1 = facePoints[2].x + dxPortion; + const x2 = facePoints[3].x + dxPortion; const y1 = this._viewModel.ft.getEquation().getY(x1); const y2 = this._viewModel.fb.getEquation().getY(x2); + const topPoint = { x: x1, y: y1 }; + const botPoint = { x: x2, y: y2 }; + + this._viewModel.fr.points = [topPoint, botPoint]; + this.updateViewAndVM(true); + - const midPointUp = { x: x1, y: y1 }; - const midPointDown = { x: x2, y: y2 }; + + facePoints = this._viewModel.getPoints(); + let midPointUp: Point | null = null; + let midPointDown: Point | null = null; + if (resizablePointIndex === 3) { + midPointUp = { x: facePoints[2].x, y: facePoints[2].y + dyPortion } + midPointDown = { x: facePoints[3].x, y: facePoints[3].y }; + } else if (resizablePointIndex === 2) { + midPointUp = { x: facePoints[2].x, y: facePoints[2].y }; + midPointDown = { x: facePoints[3].x, y: facePoints[3].y + dyPortion }; + } + const topPoints = this.computeHeightFace(midPointUp, 2); const bottomPoints = this.computeHeightFace(midPointDown, 2); - this._viewModel.top.points = topPoints; this._viewModel.bot.points = bottomPoints; + this.updateViewAndVM(false); } - this.updateView(); this.face.plot(this._viewModel.front.points); this.fire(new CustomEvent('resizing', event)); }).on('resizedone', (event: CustomEvent) => { @@ -391,31 +410,46 @@ for (const key of Object.keys(originalResize)) { .from(parentElement.children) .indexOf(target); this.fire(new CustomEvent('resizestart', event)); + accumulatedOffset.x = 0; + accumulatedOffset.y = 0; }).on('resizing', (event: CustomEvent) => { - const { dx, dy } = event.detail; - - if (resizablePointIndex === 0) { - this._viewModel.dt.points[1].y = cubePoints[4].y + dy; - } else if (resizablePointIndex === 1) { - this._viewModel.db.points[1].y = cubePoints[5].y + dy; - } + let { dx, dy } = event.detail; + let dxPortion = dx - accumulatedOffset.x; + let dyPortion = dy - accumulatedOffset.y; + accumulatedOffset.x += dxPortion; + accumulatedOffset.y += dyPortion; + + let facePoints = this._viewModel.getPoints(); + + const x1 = facePoints[4].x + dxPortion; + const x2 = facePoints[5].x + dxPortion; + const y1 = this._viewModel.rt.getEquation().getY(x1); + const y2 = this._viewModel.rb.getEquation().getY(x2); + const topPoint = { x: x1, y: y1 }; + const botPoint = { x: x2, y: y2 }; + + this._viewModel.dr.points = [topPoint, botPoint]; + this.updateViewAndVM(); - this._viewModel.fr.points[0].x = cubePoints[2].x + dx; - const x1 = cubePoints[4].x + dx; - const x2 = cubePoints[5].x + dx; - const y1 = this._viewModel.dt.getEquation().getY(x1); - const y2 = this._viewModel.db.getEquation().getY(x2); - const midPointUp = { x: x1, y: y1 }; - const midPointDown = { x: x2, y: y2 }; + facePoints = this._viewModel.getPoints(); + let midPointUp: Point | null = null; + let midPointDown: Point | null = null; + if (resizablePointIndex === 0) { + midPointUp = { x: facePoints[4].x, y: facePoints[4].y + dyPortion } + midPointDown = { x: facePoints[5].x, y: facePoints[5].y }; + } else if (resizablePointIndex === 1) { + midPointUp = { x: facePoints[4].x, y: facePoints[4].y }; + midPointDown = { x: facePoints[5].x, y: facePoints[5].y + dyPortion }; + } + const topPoints = this.computeHeightFace(midPointUp, 3); const bottomPoints = this.computeHeightFace(midPointDown, 3); - this._viewModel.top.points = topPoints; - this._viewModel.bot.points = bottomPoints; + this._viewModel.bot.points = bottomPoints; + this.updateViewAndVM(false); - this.updateView(); this.face.plot(this._viewModel.front.points); this.fire(new CustomEvent('resizing', event)); }).on('resizedone', (event: CustomEvent) => { @@ -490,18 +524,10 @@ for (const key of Object.keys(originalResize)) { this.updateViewAndVM(); }, - updateViewAndVM() { - this._viewModel.buildBackEdge(); + updateViewAndVM(build: boolean) { + this._viewModel.buildBackEdge(build); this.updateView(); this._attr('points', pointObjectsToString(this._viewModel.points)); - - }, - - updateView() { - this.updatePolygons(); - this.updateLines(); - this.updateProjections(); - this.updateGrabPoints(); }, updatePolygons() { @@ -797,15 +823,15 @@ for (const key of Object.keys(originalResize)) { case 2: { const p1 = this.updatedEdge(this._viewModel.fl.points[0], point, this._viewModel.vpl); const p3 = this.updatedEdge(this._viewModel.dr.points[0], point, this._viewModel.vpr); - const p4 = this.updatedEdge(this._viewModel.dl.points[0], p1, this._viewModel.vpr); + const p4 = this.updatedEdge(this._viewModel.dl.points[0], p3, this._viewModel.vpr); return [p1, point, p3, p4]; } // dr case 3: { - const p2 = this.updatedEdge(this._viewModel.fr.points[0], point, this._viewModel.vpr); - const p4 = this.updatedEdge(this._viewModel.dl.points[0], point, this._viewModel.vpl); - const p1 = this.updatedEdge(this._viewModel.fl.points[0], p4, this._viewModel.vpr); - return [p1, p2, point, p4]; + const p2 = this.updatedEdge(this._viewModel.dl.points[0], point, this._viewModel.vpl); + const p3 = this.updatedEdge(this._viewModel.fr.points[0], point, this._viewModel.vpr); + const p4 = this.updatedEdge(this._viewModel.fl.points[0], p2, this._viewModel.vpr); + return [p4, p3, point, p2]; } // dl case 4: { @@ -897,19 +923,6 @@ for (const key of Object.keys(originalResize)) { this.rightBotEdge.plot(viewModel.rb.points); }, - updateProjections() { - const viewModel = this._viewModel; - - // this.ftProj.plot(this.updateProjectionLine(viewModel.ft.getEquation(), - // viewModel.ft.points[0], viewModel.vplCanvas)); - // this.fbProj.plot(this.updateProjectionLine(viewModel.fb.getEquation(), - // viewModel.ft.points[0], viewModel.vplCanvas)); - // this.rtProj.plot(this.updateProjectionLine(viewModel.rt.getEquation(), - // viewModel.rt.points[1], viewModel.vprCanvas)); - // this.rbProj.plot(this.updateProjectionLine(viewModel.rb.getEquation(), - // viewModel.rt.points[1], viewModel.vprCanvas)); - }, - // addMouseOverEvents() { // this._addFaceEvents(); // }, From e0426d6ebbdbd3ed5a0d486bc246a4db122f6d72 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Sat, 18 Apr 2020 02:15:23 +0300 Subject: [PATCH 18/45] Little refactoring, removed commented code --- cvat-canvas/src/typescript/cuboid.ts | 4 +- cvat-canvas/src/typescript/shared.ts | 26 +-- cvat-canvas/src/typescript/svg.patch.ts | 296 +----------------------- 3 files changed, 14 insertions(+), 312 deletions(-) diff --git a/cvat-canvas/src/typescript/cuboid.ts b/cvat-canvas/src/typescript/cuboid.ts index c83b0c38e8ee..320be701ea3a 100644 --- a/cvat-canvas/src/typescript/cuboid.ts +++ b/cvat-canvas/src/typescript/cuboid.ts @@ -125,8 +125,8 @@ export class CuboidModel { this.points = points; this.initEdges(); this.initFaces(); - this.updateVanishingPoints(); - this.buildBackEdge(); + this.updateVanishingPoints(false); + this.buildBackEdge(false); this.updatePoints(); } diff --git a/cvat-canvas/src/typescript/shared.ts b/cvat-canvas/src/typescript/shared.ts index 986aa426e754..7440fc378581 100644 --- a/cvat-canvas/src/typescript/shared.ts +++ b/cvat-canvas/src/typescript/shared.ts @@ -4,7 +4,6 @@ import * as SVG from 'svg.js'; import consts from './consts'; -import { Position } from './canvasModel'; export interface ShapeSizeElement { sizeElement: any; @@ -107,7 +106,6 @@ export function displayShapeSize( return shapeSize; } - export function convertToArray(points: Point[]): number[][] { const arr: number[][] = []; points.forEach((point: Point): void => { @@ -116,25 +114,17 @@ export function convertToArray(points: Point[]): number[][] { return arr; } -export function pointsToObjects(stringified: string): Point[] { - return pointsToArray(stringified) - .reduce((points: Point[], coord: number, index: number): Point[] => { - if (index % 2 === 0) { - points.push({ x: coord, y: null }); - } else { - // eslint-disable-next-line no-param-reassign - points.slice(-1)[0].y = coord; - } - return points; - }, []); +export function parsePoints(stringified: string): Point[] { + return stringified.trim().split(/\s/).map((point: string): Point => { + const [x, y] = point.split(',').map((coord: string): number => +coord); + return { x, y }; + }); } -export function pointObjectsToString(points: Point[]): string { - return points.reduce((acc: string, point: Point): string => { - return `${acc}${point.x} ${point.y},`; - }, '').slice(0, -1); +export function stringifyPoints(points: Point[]): string { + return points.map((point: Point): string => `${point.x},${point.y}`).join(' '); } export function clamp(x: number, min: number, max: number): number { return Math.min(Math.max(x, min), max); -}; +} diff --git a/cvat-canvas/src/typescript/svg.patch.ts b/cvat-canvas/src/typescript/svg.patch.ts index c9a1e6427652..31ec9661193e 100644 --- a/cvat-canvas/src/typescript/svg.patch.ts +++ b/cvat-canvas/src/typescript/svg.patch.ts @@ -11,8 +11,7 @@ import 'svg.draw.js'; import consts from './consts'; import { Point, Equation, CuboidModel } from './cuboid'; -import { pointsToObjects, pointObjectsToString } from './shared'; -// import consts from './consts' +import { parsePoints, stringifyPoints } from './shared'; // Update constructor const originalDraw = SVG.Element.prototype.draw; @@ -191,7 +190,7 @@ for (const key of Object.keys(originalResize)) { inherit: SVG.G, extend: { constructorMethod(points: string) { - this._viewModel = new CuboidModel(pointsToObjects(points)); + this._viewModel = new CuboidModel(parsePoints(points)); this.setupFaces(); this.setupEdges(); this.setupProjections(); @@ -314,16 +313,7 @@ for (const key of Object.keys(originalResize)) { accumulatedOffset.x += dxPortion; accumulatedOffset.y += dyPortion; - let facePoints = this.face - .attr('points') - .split(/\s/) - .map((point: string): Point => { - const [x, y]: number[] = point.split(',').map((coord: string): number => +coord); - return { - x, - y, - }; - }); + let facePoints = parsePoints(this.face.attr('points')); if (facePoints[2].x - facePoints[1].x + dx < consts.MIN_EDGE_LENGTH || facePoints[1].y - facePoints[0].y + dy < consts.MIN_EDGE_LENGTH @@ -527,7 +517,7 @@ for (const key of Object.keys(originalResize)) { updateViewAndVM(build: boolean) { this._viewModel.buildBackEdge(build); this.updateView(); - this._attr('points', pointObjectsToString(this._viewModel.points)); + this._attr('points', stringifyPoints(this._viewModel.points)); }, updatePolygons() { @@ -626,190 +616,6 @@ for (const key of Object.keys(originalResize)) { return _draggable(value, constraint); }, - // addEvents() { - // const edges = this.getEdges(); - // const draggableFaces = [ - // this.left, - // this.dorsal, - // this.right, - // ]; - - // if (this._viewModel.dl.points[0].x > this.viewModel.fl.points[0].x) { - // this.orientation = 1; - // } else { - // this.orientation = 2; - // } - - // edges.forEach((edge) => { - // edge.on('resizestart', () => { - // // TODO: dipatch proper canvas event - // }).on('resizedone', () => { - // this.updateModel(); - // this.updateViewModel(); - // // TODO: dipatch proper canvas event - // }); - // }); - - // draggableFaces.forEach((face) => { - // face.on('dragstart', () => { - // // TODO: dipatch proper canvas event - // }).on('dragend', () => { - // // TODO: dipatch proper canvas event - // this.updateModel(); - // this.updateViewModel(); - // }); - // }); - - // this.makeDraggable(); - // // this.makeResizable(); - // }, - - // makeDraggable() { - // let startPoint: any = null; - // let startPosition: any = null; - - // this.draggable().off('dragend').on('dragstart', (e) => { - // startPoint = e.detail.p; - // startPosition = this.viewModel.getPoints(); - // }).on('dragmove', (e) => { - // e.preventDefault(); - // this.translatePoints(startPoint, startPosition, e.detail.p); - // this.refreshView(); - // }).on('dragend', () => { - // this.updateModel(); - // this.updateViewModel(); - // }); - - // // Controllable vertical edges - // this.flCenter.draggable(function (x) { - // const vpX = this.cx() - this.viewModel.vplCanvas.x > 0 ? this.viewModel.vplCanvas.x : 0; - // return { x: x < this.viewModel.fr.canvasPoints[0].x && x > vpX + consts.MIN_EDGE_LENGTH }; - // }).on('dragmove', function () { - // this.frontLeftEdge.center(this.cx(), this.cy()); - - // const [ position ] = this.viewModel.canvasToActual([{x: this.frontLeftEdge.attr('x1'), y: this.frontLeftEdge.attr('y1')}]); - // const { x } = position; - - // const y1 = this.viewModel.ft.getEquation().getY(x); - // const y2 = this.viewModel.fb.getEquation().getY(x); - - // const topPoint = { x, y: y1 }; - // const botPoint = { x, y: y2 }; - - // this.viewModel.fl.points = [topPoint, botPoint]; - // this.updateViewAndVM(); - // }); - - // this.drCenter.draggable(function (x) { - // let xStatus; - // if (this.cx() < this.viewModel.fr.canvasPoints[0].x) { - // xStatus = x < this.viewModel.fr.canvasPoints[0].x - consts.MIN_EDGE_LENGTH - // && x > this.viewModel.vprCanvas.x + consts.MIN_EDGE_LENGTH; - // } else { - // xStatus = x > this.viewModel.fr.canvasPoints[0].x + consts.MIN_EDGE_LENGTH - // && x < this.viewModel.vprCanvas.x - consts.MIN_EDGE_LENGTH; - // } - // return { x: xStatus, y: this.attr('y1') }; - // }).on('dragmove', function () { - // this.dorsalRightEdge.center(this.cx(), this.cy()); - - // const [ position ] = this.viewModel.canvasToActual([{x: this.dorsalRightEdge.attr('x1'), y: this.dorsalRightEdge.attr('y1')}]); - // const { x } = position; - - // const y1 = this.viewModel.rt.getEquation().getY(x); - // const y2 = this.viewModel.rb.getEquation().getY(x); - - // const topPoint = { x, y: y1 }; - // const botPoint = { x, y: y2 }; - - // this.viewModel.dr.points = [topPoint, botPoint]; - // this.updateViewAndVM(); - // }); - - // this.dlCenter.draggable(function (x) { - // let xStatus; - // if (this.cx() < this.viewModel.fl.canvasPoints[0].x) { - // xStatus = x < this.viewModel.fl.canvasPoints[0].x - consts.MIN_EDGE_LENGTH - // && x > this.viewModel.vprCanvas.x + consts.MIN_EDGE_LENGTH; - // } else { - // xStatus = x > this.viewModel.fl.canvasPoints[0].x + consts.MIN_EDGE_LENGTH - // && x < this.viewModel.vprCanvas.x + consts.MIN_EDGE_LENGTH; - // } - // return { x: xStatus, y: this.attr('y1') }; - // }).on('dragmove', function () { - // this.dorsalLeftEdge.center(this.cx(), this.cy()); - - // const position = this.viewModel.canvasToActual([{x: this.dorsalLeftEdge.attr('x1'), y: this.dorsalLeftEdge.attr('y1')}]); - // const { x } = position; - - // const y1 = this.viewModel.lt.getEquation().getY(x); - // const y2 = this.viewModel.lb.getEquation().getY(x); - - // const topPoint = { x, y: y1 }; - // const botPoint = { x, y: y2 }; - - // this.viewModel.dl.points = [topPoint, botPoint]; - // this.updateViewAndVM(true); - // }); - - // this.frCenter.draggable((x) => { - // return { x: x > this.viewModel.fl.canvasPoints[0].x, y: this.attr('y1') }; - // }).on('dragmove', () => { - // this.frontRightEdge.center(this.cx(), this.cy()); - - // const [ position ]= this.viewModel.canvasToActual([{x: this.frontRightEdge.attr('x1'), y: this.frontRightEdge.attr('y1')}]); - // const { x } = position; - - // const y1 = this.viewModel.ft.getEquation().getY(x); - // const y2 = this.viewModel.fb.getEquation().getY(x); - - // const topPoint = { x, y: y1 }; - // const botPoint = { x, y: y2 }; - - // this.viewModel.fr.points = [topPoint, botPoint]; - // this.updateViewAndVM(true); - // }); - - - // // Controllable 'horizontal' edges - // this.ftCenter.draggable((x, y) => { - // return { x: x === this.cx(), y: y < this.fbCenter.cy() - consts.MIN_EDGE_LENGTH }; - // }).on('dragmove', () => { - // this.frontTopEdge.center(this.cx(), this.cy()); - // this.horizontalEdgeControl(this.viewModel.top, this.frontTopEdge.attr('x2'), this.frontTopEdge.attr('y2')); - // this.updateViewAndVM(); - // }); - - // this.fbCenter.draggable((x, y) => { - // return { x: x === this.cx(), y: y > this.ftCenter.cy() + consts.MIN_EDGE_LENGTH }; - // }).on('dragmove', () => { - // this.frontBotEdge.center(this.cx(), this.cy()); - // this.horizontalEdgeControl(this.viewModel.bot, this.frontBotEdge.attr('x2'), this.frontBotEdge.attr('y2')); - // this.updateViewAndVM(); - // }); - - // // Controllable faces - // this.left.draggable((x, y) => ({ x: x < Math.min(this.viewModel.dr.canvasPoints[0].x, this.viewModel.fr.canvasPoints[0].x) - consts.MIN_EDGE_LENGTH, y })).on('dragmove', () => { - // this.faceDragControl(this.viewModel.left, this.attr('points')); - // }); - // this.dorsal.draggable().on('dragmove', () => { - // this.faceDragControl(this.viewModel.dorsal, this.attr('points')); - // }); - // this.right.draggable((x, y) => ({ x: x > Math.min(this.viewModel.dl.canvasPoints[0].x, this.viewModel.fl.canvasPoints[0].x) + consts.MIN_EDGE_LENGTH, y })).on('dragmove', () => { - // this.faceDragControl(this.viewModel.right, this.attr('points'), true); - // }); - // }, - - // translatePoints(startPoint, startPosition, currentPosition) { - // const dx = currentPosition.x - startPoint.x; - // const dy = currentPosition.y - startPoint.y; - // const newPoints: any[] = []; - // for (let i = 0; i < startPosition.length; i += 1) { - // newPoints[i] = { x: startPosition[i].x + dx, y: startPosition[i].y + dy }; - // } - // this.viewModel.setPoints(newPoints); - // }, - computeHeightFace(point: Point, index: number) { switch (index) { // fl @@ -846,16 +652,6 @@ for (const key of Object.keys(originalResize)) { } }, - // updateViewAndVM() { - // this.viewModel.buildBackEdge(); - // this.refreshView(); - // }, - - // refreshView() { - // // TODO: fixit - // this.udpateView(this.viewModel); - // }, - updatedEdge(target: Point, base: Point, pivot: Point) { const targetX = target.x; const line = new Equation(pivot, base); @@ -863,28 +659,6 @@ for (const key of Object.keys(originalResize)) { return { x: targetX, y: newY }; }, - // resizeControl(vmEdge, updatedEdge, constraints) { - // const [ topPoint ] = this.viewModel.canvasToActual([{x: updatedEdge.attr('x1'), y: updatedEdge.attr('y1')}]); - // const [ botPoint ] = this.viewModel.canvasToActual([{x: updatedEdge.attr('x2'), y: updatedEdge.attr('y2')}]); - - // topPoint.y = Math.min(Math.max(topPoint.y, constraints.y1Range.min), constraints.y1Range.max); - // botPoint.y = Math.min(Math.max(botPoint.y, constraints.y2Range.min), constraints.y2Range.max); - - // vmEdge.points = [topPoint, botPoint]; - // }, - - // move(dx, dy) { - // this.face.dmove(dx, dy); - // this.dorsal.dmove(dx, dy); - // this.right.dmove(dx, dy); - // this.left.dmove(dx, dy); - - // const edges = this.getEdges(); - // edges.forEach((edge) => { - // edge.dmove(dx, dy); - // }); - // }, - updateView() { this.updateFaces(); this.updateEdges(); @@ -922,68 +696,6 @@ for (const key of Object.keys(originalResize)) { this.frontBotEdge.plot(viewModel.fb.points); this.rightBotEdge.plot(viewModel.rb.points); }, - - // addMouseOverEvents() { - // this._addFaceEvents(); - // }, - - // _addFaceEvents() { - // const group = this; - // this.left.on('mouseover', function () { - // this.attr({ 'fill-opacity': 0.5 }); - // }).on('mouseout', function () { - // this.attr({ 'fill-opacity': group.attr('fill-opacity') }); - // }); - // this.dorsal.on('mouseover', function () { - // this.attr({ 'fill-opacity': 0.5 }); - // }).on('mouseout', function () { - // this.attr({ 'fill-opacity': group.attr('fill-opacity') }); - // }); - // this.right.on('mouseover', function () { - // this.attr({ 'fill-opacity': 0.5 }); - // }).on('mouseout', function () { - // this.attr({ 'fill-opacity': group.attr('fill-opacity') }); - // }); - // }, - - // removeMouseOverEvents() { - // const edges = this.getEdges(); - // edges.forEach((edge) => { - // edge.off('mouseover').off('mouseout'); - // }); - // this.left.off('mouseover').off('mouseout'); - // this.dorsal.off('mouseover').off('mouseout'); - // this.right.off('mouseover').off('mouseout'); - // }, - - // resetFaceOpacity() { - // const group = this; - // this.left.attr({ 'fill-opacity': group.attr('fill-opacity') }); - // this.dorsal.attr({ 'fill-opacity': group.attr('fill-opacity') }); - // this.right.attr({ 'fill-opacity': group.attr('fill-opacity') }); - // }, - - // addOccluded() { - // const edges = this.getEdges(); - // edges.forEach((edge) => { - // edge.node.classList.add('occludedShape'); - // }); - // this.face.attr('stroke-width', 0); - // this.right.attr('stroke-width', 0); - // this.left.node.classList.add('occludedShape'); - // this.dorsal.node.classList.add('occludedShape'); - // }, - - // removeOccluded() { - // const edges = this.getEdges(); - // edges.forEach((edge) => { - // edge.node.classList.remove('occludedShape'); - // }); - // this.face.attr('stroke-width', this.attr('stroke-width')); - // this.right.attr('stroke-width', this.attr('stroke-width')); - // this.left.node.classList.remove('occludedShape'); - // this.dorsal.node.classList.remove('occludedShape'); - // }, }, construct: { cube(points: string) { From 1ec859a8d7ce2415b5d8eff2da48e3d09ab4abe8 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Sat, 18 Apr 2020 02:21:32 +0300 Subject: [PATCH 19/45] Little name refactoring --- cvat-canvas/src/typescript/cuboid.ts | 54 +++++++++++------------ cvat-canvas/src/typescript/drawHandler.ts | 4 +- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/cvat-canvas/src/typescript/cuboid.ts b/cvat-canvas/src/typescript/cuboid.ts index 320be701ea3a..692f6b647721 100644 --- a/cvat-canvas/src/typescript/cuboid.ts +++ b/cvat-canvas/src/typescript/cuboid.ts @@ -328,7 +328,7 @@ function sortPointsClockwise(points: any[]): any[] { return points.reverse(); } -function setupCuboidPoints(actualPoints: any[]): any[] { +function setupCuboidPoints(points: Point[]): any[] { let left; let right; let left2; @@ -338,18 +338,18 @@ function setupCuboidPoints(actualPoints: any[]): any[] { let p3; let p4; - const height = Math.abs(actualPoints[0].x - actualPoints[1].x) - < Math.abs(actualPoints[1].x - actualPoints[2].x) - ? Math.abs(actualPoints[1].y - actualPoints[0].y) - : Math.abs(actualPoints[1].y - actualPoints[2].y); + const height = Math.abs(points[0].x - points[1].x) + < Math.abs(points[1].x - points[2].x) + ? Math.abs(points[1].y - points[0].y) + : Math.abs(points[1].y - points[2].y); // seperate into left and right point // we pick the first and third point because we know assume they will be on // opposite corners - if (actualPoints[0].x < actualPoints[2].x) { - [left,, right] = actualPoints; + if (points[0].x < points[2].x) { + [left,, right] = points; } else { - [right,, left] = actualPoints; + [right,, left] = points; } // get other 2 points using the given height @@ -363,8 +363,8 @@ function setupCuboidPoints(actualPoints: any[]): any[] { // get the vector for the last point relative to the previous point const vec = { - x: actualPoints[3].x - actualPoints[2].x, - y: actualPoints[3].y - actualPoints[2].y, + x: points[3].x - points[2].x, + y: points[3].y - points[2].y, }; if (left.y < left2.y) { @@ -392,13 +392,13 @@ function setupCuboidPoints(actualPoints: any[]): any[] { return [p1, p2, p3, p4, p5, p6, p7, p8]; } -export function cuboidPointsBy4Points(points: any[]): any[] { - const actualPoints = []; +export function cuboidFrom4Points(flattenedPoints: any[]): any[] { + const points: Point[] = []; for (let i = 0; i < 4; i++) { - const [x, y] = points.slice(i * 2, i * 2 + 2); - actualPoints.push({ x, y }); + const [x, y] = flattenedPoints.slice(i * 2, i * 2 + 2); + points.push({ x, y }); } - const unsortedPlanePoints = actualPoints.slice(0, 3); + const unsortedPlanePoints = points.slice(0, 3); function rotate(array: any[], times: number): void{ let t = times; while (t--) { @@ -408,22 +408,22 @@ export function cuboidPointsBy4Points(points: any[]): any[] { } const plane2 = { - p1: actualPoints[0], - p2: actualPoints[0], - p3: actualPoints[0], - p4: actualPoints[0], + p1: points[0], + p2: points[0], + p3: points[0], + p4: points[0], }; // completing the plane const vector = { - x: actualPoints[2].x - actualPoints[1].x, - y: actualPoints[2].y - actualPoints[1].y, + x: points[2].x - points[1].x, + y: points[2].y - points[1].y, }; // sorting the first plane unsortedPlanePoints.push({ - x: actualPoints[0].x + vector.x, - y: actualPoints[0].y + vector.y, + x: points[0].x + vector.x, + y: points[0].y + vector.y, }); const sortedPlanePoints = sortPointsClockwise(unsortedPlanePoints); let leftIndex = 0; @@ -439,8 +439,8 @@ export function cuboidPointsBy4Points(points: any[]): any[] { }; const vec = { - x: actualPoints[3].x - actualPoints[2].x, - y: actualPoints[3].y - actualPoints[2].y, + x: points[3].x - points[2].x, + y: points[3].y - points[2].y, }; // determine the orientation const angle = Math.atan2(vec.y, vec.x); @@ -455,10 +455,10 @@ export function cuboidPointsBy4Points(points: any[]): any[] { let cuboidPoints; // right if (Math.abs(angle) < Math.PI / 2 - 0.1) { - cuboidPoints = setupCuboidPoints(actualPoints); + cuboidPoints = setupCuboidPoints(points); // left } else if (Math.abs(angle) > Math.PI / 2 + 0.1) { - cuboidPoints = setupCuboidPoints(actualPoints); + cuboidPoints = setupCuboidPoints(points); // down } else if (angle > 0) { cuboidPoints = [ diff --git a/cvat-canvas/src/typescript/drawHandler.ts b/cvat-canvas/src/typescript/drawHandler.ts index 6f1225a51be4..428a26ce772c 100644 --- a/cvat-canvas/src/typescript/drawHandler.ts +++ b/cvat-canvas/src/typescript/drawHandler.ts @@ -23,7 +23,7 @@ import { Box, } from './shared'; -import { Cuboid2PointViewModel, cuboidPointsBy4Points } from './cuboid'; +import { cuboidFrom4Points } from './cuboid'; export interface DrawHandler { draw(drawData: DrawData, geometry: Geometry): void; @@ -343,7 +343,7 @@ export class DrawHandlerImpl implements DrawHandler { && points.length === 4 * 2) { this.onDrawDone({ shapeType, - points: cuboidPointsBy4Points(points), + points: cuboidFrom4Points(points), }, Date.now() - this.startTimestamp); } }); From 5af3b61ee8b87e4ebb6b4cc39b7dbce0621b41d1 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Sat, 18 Apr 2020 03:14:50 +0300 Subject: [PATCH 20/45] Added orientation --- cvat-canvas/src/typescript/cuboid.ts | 24 +- cvat-canvas/src/typescript/svg.patch.ts | 414 ++++++++++++++---------- 2 files changed, 258 insertions(+), 180 deletions(-) diff --git a/cvat-canvas/src/typescript/cuboid.ts b/cvat-canvas/src/typescript/cuboid.ts index 692f6b647721..765644c46bce 100644 --- a/cvat-canvas/src/typescript/cuboid.ts +++ b/cvat-canvas/src/typescript/cuboid.ts @@ -14,6 +14,11 @@ export interface Point { y: number; } +export enum Orientation { + LEFT = 'left', + RIGHT = 'right', +} + function line(p1: Point, p2: Point): number[] { const a = p1.y - p2.y; const b = p2.x - p1.x; @@ -120,6 +125,7 @@ export class CuboidModel { public facesList: Figure[]; public vpl: Point | null; public vpr: Point | null; + public orientation: Orientation; public constructor(points?: Point[]) { this.points = points; @@ -128,6 +134,7 @@ export class CuboidModel { this.updateVanishingPoints(false); this.buildBackEdge(false); this.updatePoints(); + this.updateOrientation(); } public getPoints(): Point[] { @@ -136,15 +143,16 @@ export class CuboidModel { public setPoints(points: Point[]): void { this.points = points; - // for (const edge of this.edgeList) { - // edge.points = points; - // } + } - // for (const face of this.facesList) { - // face.points = points; - // } + public updateOrientation(): void { + if (this.dl.points[0].x > this.fl.points[0].x) { + this.orientation = Orientation.LEFT; + } else { + this.orientation = Orientation.RIGHT; + } - // this.updatePoints(); + console.log(this.orientation); } public updatePoints(): void { @@ -259,13 +267,11 @@ export class CuboidModel { let botIndex = 0; if (buildright) { - this.updateVanishingPoints(true); leftPoints = this.dl.points; rightPoints = this.fr.points; topIndex = 4; botIndex = 5; } else { - this.updateVanishingPoints(false); leftPoints = this.dr.points; rightPoints = this.fl.points; topIndex = 6; diff --git a/cvat-canvas/src/typescript/svg.patch.ts b/cvat-canvas/src/typescript/svg.patch.ts index 31ec9661193e..54103561a7cb 100644 --- a/cvat-canvas/src/typescript/svg.patch.ts +++ b/cvat-canvas/src/typescript/svg.patch.ts @@ -10,7 +10,12 @@ import 'svg.select.js'; import 'svg.draw.js'; import consts from './consts'; -import { Point, Equation, CuboidModel } from './cuboid'; +import { + Point, + Equation, + CuboidModel, + Orientation, +} from './cuboid'; import { parsePoints, stringifyPoints } from './shared'; // Update constructor @@ -190,7 +195,7 @@ for (const key of Object.keys(originalResize)) { inherit: SVG.G, extend: { constructorMethod(points: string) { - this._viewModel = new CuboidModel(parsePoints(points)); + this.cuboidModel = new CuboidModel(parsePoints(points)); this.setupFaces(); this.setupEdges(); this.setupProjections(); @@ -201,21 +206,21 @@ for (const key of Object.keys(originalResize)) { }, setupFaces() { - this.face = this.polygon(this._viewModel.front.points); - this.right = this.polygon(this._viewModel.right.points); - this.dorsal = this.polygon(this._viewModel.dorsal.points); - this.left = this.polygon(this._viewModel.left.points); + this.face = this.polygon(this.cuboidModel.front.points); + this.right = this.polygon(this.cuboidModel.right.points); + this.dorsal = this.polygon(this.cuboidModel.dorsal.points); + this.left = this.polygon(this.cuboidModel.left.points); }, setupProjections() { - this.ftProj = this.line(this.updateProjectionLine(this._viewModel.ft.getEquation(), - this._viewModel.ft.points[0], this._viewModel.vpl)); - this.fbProj = this.line(this.updateProjectionLine(this._viewModel.fb.getEquation(), - this._viewModel.ft.points[0], this._viewModel.vpl)); - this.rtProj = this.line(this.updateProjectionLine(this._viewModel.rt.getEquation(), - this._viewModel.rt.points[1], this._viewModel.vpr)); - this.rbProj = this.line(this.updateProjectionLine(this._viewModel.rb.getEquation(), - this._viewModel.rb.points[1], this._viewModel.vpr)); + this.ftProj = this.line(this.updateProjectionLine(this.cuboidModel.ft.getEquation(), + this.cuboidModel.ft.points[0], this.cuboidModel.vpl)); + this.fbProj = this.line(this.updateProjectionLine(this.cuboidModel.fb.getEquation(), + this.cuboidModel.ft.points[0], this.cuboidModel.vpl)); + this.rtProj = this.line(this.updateProjectionLine(this.cuboidModel.rt.getEquation(), + this.cuboidModel.rt.points[1], this.cuboidModel.vpr)); + this.rbProj = this.line(this.updateProjectionLine(this.cuboidModel.rb.getEquation(), + this.cuboidModel.rb.points[1], this.cuboidModel.vpr)); this.ftProj.stroke({ color: '#C0C0C0' }); this.fbProj.stroke({ color: '#C0C0C0' }); @@ -224,15 +229,15 @@ for (const key of Object.keys(originalResize)) { }, setupEdges() { - this.frontLeftEdge = this.line(this._viewModel.fl.points); - this.frontRightEdge = this.line(this._viewModel.fr.points); - this.dorsalRightEdge = this.line(this._viewModel.dr.points); - this.dorsalLeftEdge = this.line(this._viewModel.dl.points); - - this.frontTopEdge = this.line(this._viewModel.ft.points); - this.rightTopEdge = this.line(this._viewModel.rt.points); - this.frontBotEdge = this.line(this._viewModel.fb.points); - this.rightBotEdge = this.line(this._viewModel.rb.points); + this.frontLeftEdge = this.line(this.cuboidModel.fl.points); + this.frontRightEdge = this.line(this.cuboidModel.fr.points); + this.dorsalRightEdge = this.line(this.cuboidModel.dr.points); + this.dorsalLeftEdge = this.line(this.cuboidModel.dl.points); + + this.frontTopEdge = this.line(this.cuboidModel.ft.points); + this.rightTopEdge = this.line(this.cuboidModel.rt.points); + this.frontBotEdge = this.line(this.cuboidModel.fb.points); + this.rightBotEdge = this.line(this.cuboidModel.rb.points); }, showProjections() { @@ -273,31 +278,138 @@ for (const key of Object.keys(originalResize)) { return [[x1, y1], [x2, y2]]; }, - selectize(value: any, options: any) { + selectize(value: boolean, options: object) { this.face.selectize(value, options); - this.dorsalRightEdge.selectize(value, options); + if (this.cuboidModel.orientation === Orientation.LEFT) { + this.dorsalLeftEdge.selectize(false, options); + this.dorsalRightEdge.selectize(value, options); + } else { + this.dorsalRightEdge.selectize(false, options); + this.dorsalLeftEdge.selectize(value, options); + } return this; }, resize(value?: string | object) { this.face.resize(value); - this.dorsalRightEdge.resize(value); - - this.face.off('resizing').off('resizedone').off('resizestart'); - this.dorsalRightEdge.off('resizing').off('resizedone').off('resizestart'); - - if (value !== 'stop') { - const accumulatedOffset: Point = { - x: 0, - y: 0, - }; - let cubePoints: Point[] = []; - let resizablePointIndex: null | number = null; - - this.face.on('resizestart', (event: CustomEvent) => { - cubePoints = JSON.parse(JSON.stringify(this._viewModel.getPoints())); + + if (value === 'stop') { + this.dorsalRightEdge.resize(value); + this.dorsalLeftEdge.resize(value); + this.face.off('resizing').off('resizedone').off('resizestart'); + this.dorsalRightEdge.off('resizing').off('resizedone').off('resizestart'); + this.dorsalLeftEdge.off('resizing').off('resizedone').off('resizestart'); + return; + } + + const accumulatedOffset: Point = { + x: 0, + y: 0, + }; + let cubePoints: Point[] = []; + let resizablePointIndex: null | number = null; + + this.face.on('resizestart', (event: CustomEvent) => { + cubePoints = JSON.parse(JSON.stringify(this.cuboidModel.getPoints())); + const { target } = event.detail.event.detail.event; + const { parentElement } = target; + resizablePointIndex = Array + .from(parentElement.children) + .indexOf(target); + this.fire(new CustomEvent('resizestart', event)); + accumulatedOffset.x = 0; + accumulatedOffset.y = 0; + }).on('resizing', (event: CustomEvent) => { + let { dx, dy } = event.detail; + let dxPortion = dx - accumulatedOffset.x; + let dyPortion = dy - accumulatedOffset.y; + accumulatedOffset.x += dxPortion; + accumulatedOffset.y += dyPortion; + + let facePoints = parsePoints(this.face.attr('points')); + + if (facePoints[2].x - facePoints[1].x + dx < consts.MIN_EDGE_LENGTH + || facePoints[1].y - facePoints[0].y + dy < consts.MIN_EDGE_LENGTH + ) { + this.face.plot(this.cuboidModel.front.points); + return; + } + + if ([0, 1].includes(resizablePointIndex)) { + let cuboidPoints = this.cuboidModel.getPoints(); + + const x1 = cuboidPoints[0].x + dxPortion; + const x2 = cuboidPoints[1].x + dxPortion; + const y1 = this.cuboidModel.ft.getEquation().getY(x1); + const y2 = this.cuboidModel.fb.getEquation().getY(x2); + const topPoint = { x: x1, y: y1 }; + const botPoint = { x: x2, y: y2 }; + + this.cuboidModel.fl.points = [topPoint, botPoint]; + this.updateViewAndVM(); + + + cuboidPoints = this.cuboidModel.getPoints(); + let midPointUp: Point | null = null; + let midPointDown: Point | null = null; + if (resizablePointIndex === 0) { + midPointUp = { x: cuboidPoints[0].x, y: cuboidPoints[0].y + dyPortion } + midPointDown = { x: cuboidPoints[1].x, y: cuboidPoints[1].y }; + } else if (resizablePointIndex === 1) { + midPointUp = { x: cuboidPoints[0].x, y: cuboidPoints[0].y }; + midPointDown = { x: cuboidPoints[1].x, y: cuboidPoints[1].y + dyPortion }; + } + + const topPoints = this.computeHeightFace(midPointUp, 1); + const bottomPoints = this.computeHeightFace(midPointDown, 1); + this.cuboidModel.top.points = topPoints; + this.cuboidModel.bot.points = bottomPoints; + this.updateViewAndVM(false); + } else if ([2, 3].includes(resizablePointIndex)) { + let cuboidPoints = this.cuboidModel.getPoints(); + + const x1 = cuboidPoints[2].x + dxPortion; + const x2 = cuboidPoints[3].x + dxPortion; + const y1 = this.cuboidModel.ft.getEquation().getY(x1); + const y2 = this.cuboidModel.fb.getEquation().getY(x2); + const topPoint = { x: x1, y: y1 }; + const botPoint = { x: x2, y: y2 }; + + this.cuboidModel.fr.points = [topPoint, botPoint]; + this.updateViewAndVM(true); + + + + cuboidPoints = this.cuboidModel.getPoints(); + let midPointUp: Point | null = null; + let midPointDown: Point | null = null; + if (resizablePointIndex === 3) { + midPointUp = { x: cuboidPoints[2].x, y: cuboidPoints[2].y + dyPortion } + midPointDown = { x: cuboidPoints[3].x, y: cuboidPoints[3].y }; + } else if (resizablePointIndex === 2) { + midPointUp = { x: cuboidPoints[2].x, y: cuboidPoints[2].y }; + midPointDown = { x: cuboidPoints[3].x, y: cuboidPoints[3].y + dyPortion }; + } + + const topPoints = this.computeHeightFace(midPointUp, 2); + const bottomPoints = this.computeHeightFace(midPointDown, 2); + this.cuboidModel.top.points = topPoints; + this.cuboidModel.bot.points = bottomPoints; + this.updateViewAndVM(false); + } + + this.face.plot(this.cuboidModel.front.points); + this.fire(new CustomEvent('resizing', event)); + }).on('resizedone', (event: CustomEvent) => { + this.fire(new CustomEvent('resizedone', event)); + }); + + if (this.cuboidModel.orientation === Orientation.LEFT) { + this.dorsalRightEdge.resize(value); + this.dorsalRightEdge.on('resizestart', (event: CustomEvent) => { + cubePoints = JSON.parse(JSON.stringify(this.cuboidModel.getPoints())); const { target } = event.detail.event.detail.event; const { parentElement } = target; resizablePointIndex = Array @@ -313,87 +425,46 @@ for (const key of Object.keys(originalResize)) { accumulatedOffset.x += dxPortion; accumulatedOffset.y += dyPortion; - let facePoints = parsePoints(this.face.attr('points')); + let cuboidPoints = this.cuboidModel.getPoints(); - if (facePoints[2].x - facePoints[1].x + dx < consts.MIN_EDGE_LENGTH - || facePoints[1].y - facePoints[0].y + dy < consts.MIN_EDGE_LENGTH - ) { - this.face.plot(this._viewModel.front.points); - return; - } + const x1 = cuboidPoints[4].x + dxPortion; + const x2 = cuboidPoints[5].x + dxPortion; + const y1 = this.cuboidModel.rt.getEquation().getY(x1); + const y2 = this.cuboidModel.rb.getEquation().getY(x2); + const topPoint = { x: x1, y: y1 }; + const botPoint = { x: x2, y: y2 }; + + this.cuboidModel.dr.points = [topPoint, botPoint]; + this.updateViewAndVM(); - if ([0, 1].includes(resizablePointIndex)) { - facePoints = this._viewModel.getPoints(); - - const x1 = facePoints[0].x + dxPortion; - const x2 = facePoints[1].x + dxPortion; - const y1 = this._viewModel.ft.getEquation().getY(x1); - const y2 = this._viewModel.fb.getEquation().getY(x2); - const topPoint = { x: x1, y: y1 }; - const botPoint = { x: x2, y: y2 }; - - this._viewModel.fl.points = [topPoint, botPoint]; - this.updateViewAndVM(); - - - facePoints = this._viewModel.getPoints(); - let midPointUp: Point | null = null; - let midPointDown: Point | null = null; - if (resizablePointIndex === 0) { - midPointUp = { x: facePoints[0].x, y: facePoints[0].y + dyPortion } - midPointDown = { x: facePoints[1].x, y: facePoints[1].y }; - } else if (resizablePointIndex === 1) { - midPointUp = { x: facePoints[0].x, y: facePoints[0].y }; - midPointDown = { x: facePoints[1].x, y: facePoints[1].y + dyPortion }; - } - - const topPoints = this.computeHeightFace(midPointUp, 1); - const bottomPoints = this.computeHeightFace(midPointDown, 1); - this._viewModel.top.points = topPoints; - this._viewModel.bot.points = bottomPoints; - this.updateViewAndVM(false); - } else if ([2, 3].includes(resizablePointIndex)) { - facePoints = this._viewModel.getPoints(); - - const x1 = facePoints[2].x + dxPortion; - const x2 = facePoints[3].x + dxPortion; - const y1 = this._viewModel.ft.getEquation().getY(x1); - const y2 = this._viewModel.fb.getEquation().getY(x2); - const topPoint = { x: x1, y: y1 }; - const botPoint = { x: x2, y: y2 }; - - this._viewModel.fr.points = [topPoint, botPoint]; - this.updateViewAndVM(true); - - - - facePoints = this._viewModel.getPoints(); - let midPointUp: Point | null = null; - let midPointDown: Point | null = null; - if (resizablePointIndex === 3) { - midPointUp = { x: facePoints[2].x, y: facePoints[2].y + dyPortion } - midPointDown = { x: facePoints[3].x, y: facePoints[3].y }; - } else if (resizablePointIndex === 2) { - midPointUp = { x: facePoints[2].x, y: facePoints[2].y }; - midPointDown = { x: facePoints[3].x, y: facePoints[3].y + dyPortion }; - } - - const topPoints = this.computeHeightFace(midPointUp, 2); - const bottomPoints = this.computeHeightFace(midPointDown, 2); - this._viewModel.top.points = topPoints; - this._viewModel.bot.points = bottomPoints; - this.updateViewAndVM(false); + + cuboidPoints = this.cuboidModel.getPoints(); + let midPointUp: Point | null = null; + let midPointDown: Point | null = null; + if (resizablePointIndex === 0) { + midPointUp = { x: cuboidPoints[4].x, y: cuboidPoints[4].y + dyPortion } + midPointDown = { x: cuboidPoints[5].x, y: cuboidPoints[5].y }; + } else if (resizablePointIndex === 1) { + midPointUp = { x: cuboidPoints[4].x, y: cuboidPoints[4].y }; + midPointDown = { x: cuboidPoints[5].x, y: cuboidPoints[5].y + dyPortion }; } + + const topPoints = this.computeHeightFace(midPointUp, 3); + const bottomPoints = this.computeHeightFace(midPointDown, 3); + this.cuboidModel.top.points = topPoints; + this.cuboidModel.bot.points = bottomPoints; + this.updateViewAndVM(false); - this.face.plot(this._viewModel.front.points); + this.face.plot(this.cuboidModel.front.points); this.fire(new CustomEvent('resizing', event)); }).on('resizedone', (event: CustomEvent) => { this.fire(new CustomEvent('resizedone', event)); }); - - this.dorsalRightEdge.on('resizestart', (event: CustomEvent) => { - cubePoints = JSON.parse(JSON.stringify(this._viewModel.getPoints())); + } else { + this.dorsalLeftEdge.resize(); + this.dorsalLeftEdge.on('resizestart', (event: CustomEvent) => { + cubePoints = JSON.parse(JSON.stringify(this.cuboidModel.getPoints())); const { target } = event.detail.event.detail.event; const { parentElement } = target; resizablePointIndex = Array @@ -409,44 +480,44 @@ for (const key of Object.keys(originalResize)) { accumulatedOffset.x += dxPortion; accumulatedOffset.y += dyPortion; - let facePoints = this._viewModel.getPoints(); + let cuboidPoints = this.cuboidModel.getPoints(); - const x1 = facePoints[4].x + dxPortion; - const x2 = facePoints[5].x + dxPortion; - const y1 = this._viewModel.rt.getEquation().getY(x1); - const y2 = this._viewModel.rb.getEquation().getY(x2); + const x1 = cuboidPoints[6].x + dxPortion; + const x2 = cuboidPoints[7].x + dxPortion; + const y1 = this.cuboidModel.lt.getEquation().getY(x1); + const y2 = this.cuboidModel.lb.getEquation().getY(x2); const topPoint = { x: x1, y: y1 }; const botPoint = { x: x2, y: y2 }; - this._viewModel.dr.points = [topPoint, botPoint]; - this.updateViewAndVM(); + this.cuboidModel.dl.points = [topPoint, botPoint]; + this.updateViewAndVM(true); - facePoints = this._viewModel.getPoints(); + cuboidPoints = this.cuboidModel.getPoints(); let midPointUp: Point | null = null; let midPointDown: Point | null = null; if (resizablePointIndex === 0) { - midPointUp = { x: facePoints[4].x, y: facePoints[4].y + dyPortion } - midPointDown = { x: facePoints[5].x, y: facePoints[5].y }; + midPointUp = { x: cuboidPoints[6].x, y: cuboidPoints[6].y + dyPortion } + midPointDown = { x: cuboidPoints[7].x, y: cuboidPoints[7].y }; } else if (resizablePointIndex === 1) { - midPointUp = { x: facePoints[4].x, y: facePoints[4].y }; - midPointDown = { x: facePoints[5].x, y: facePoints[5].y + dyPortion }; + midPointUp = { x: cuboidPoints[6].x, y: cuboidPoints[6].y }; + midPointDown = { x: cuboidPoints[7].x, y: cuboidPoints[7].y + dyPortion }; } - const topPoints = this.computeHeightFace(midPointUp, 3); - const bottomPoints = this.computeHeightFace(midPointDown, 3); - this._viewModel.top.points = topPoints; - this._viewModel.bot.points = bottomPoints; + const topPoints = this.computeHeightFace(midPointUp, 4); + const bottomPoints = this.computeHeightFace(midPointDown, 4); + this.cuboidModel.top.points = topPoints; + this.cuboidModel.bot.points = bottomPoints; this.updateViewAndVM(false); - this.face.plot(this._viewModel.front.points); + this.face.plot(this.cuboidModel.front.points); this.fire(new CustomEvent('resizing', event)); }).on('resizedone', (event: CustomEvent) => { this.fire(new CustomEvent('resizedone', event)); }); - } - + } + return this; }, @@ -506,7 +577,7 @@ for (const key of Object.keys(originalResize)) { }, dmove(dx: number, dy: number) { - this._viewModel.points.forEach((point: Point) => { + this.cuboidModel.points.forEach((point: Point) => { point.x += dx; point.y += dy; }); @@ -515,39 +586,40 @@ for (const key of Object.keys(originalResize)) { }, updateViewAndVM(build: boolean) { - this._viewModel.buildBackEdge(build); + this.cuboidModel.updateOrientation(); + this.cuboidModel.buildBackEdge(build); this.updateView(); - this._attr('points', stringifyPoints(this._viewModel.points)); + this._attr('points', stringifyPoints(this.cuboidModel.points)); }, updatePolygons() { - this.face.plot(this._viewModel.front.points); - this.right.plot(this._viewModel.right.points); - this.dorsal.plot(this._viewModel.dorsal.points); - this.left.plot(this._viewModel.left.points); + this.face.plot(this.cuboidModel.front.points); + this.right.plot(this.cuboidModel.right.points); + this.dorsal.plot(this.cuboidModel.dorsal.points); + this.left.plot(this.cuboidModel.left.points); }, updateLines() { - this.frontLeftEdge.plot(this._viewModel.fl.points); - this.frontRightEdge.plot(this._viewModel.fr.points); - this.dorsalRightEdge.plot(this._viewModel.dr.points); - this.dorsalLeftEdge.plot(this._viewModel.dl.points); - - this.frontTopEdge.plot(this._viewModel.ft.points); - this.rightTopEdge.plot(this._viewModel.rt.points); - this.frontBotEdge.plot(this._viewModel.fb.points); - this.rightBotEdge.plot(this._viewModel.rb.points); + this.frontLeftEdge.plot(this.cuboidModel.fl.points); + this.frontRightEdge.plot(this.cuboidModel.fr.points); + this.dorsalRightEdge.plot(this.cuboidModel.dr.points); + this.dorsalLeftEdge.plot(this.cuboidModel.dl.points); + + this.frontTopEdge.plot(this.cuboidModel.ft.points); + this.rightTopEdge.plot(this.cuboidModel.rt.points); + this.frontBotEdge.plot(this.cuboidModel.fb.points); + this.rightBotEdge.plot(this.cuboidModel.rb.points); }, updateProjections() { - this.ftProj.plot(this.updateProjectionLine(this._viewModel.ft.getEquation(), - this._viewModel.ft.points[0], this._viewModel.vpl)); - this.fbProj.plot(this.updateProjectionLine(this._viewModel.fb.getEquation(), - this._viewModel.ft.points[0], this._viewModel.vpl)); - this.rtProj.plot(this.updateProjectionLine(this._viewModel.rt.getEquation(), - this._viewModel.rt.points[1], this._viewModel.vpr)); - this.rbProj.plot(this.updateProjectionLine(this._viewModel.rb.getEquation(), - this._viewModel.rt.points[1], this._viewModel.vpr)); + this.ftProj.plot(this.updateProjectionLine(this.cuboidModel.ft.getEquation(), + this.cuboidModel.ft.points[0], this.cuboidModel.vpl)); + this.fbProj.plot(this.updateProjectionLine(this.cuboidModel.fb.getEquation(), + this.cuboidModel.ft.points[0], this.cuboidModel.vpl)); + this.rtProj.plot(this.updateProjectionLine(this.cuboidModel.rt.getEquation(), + this.cuboidModel.rt.points[1], this.cuboidModel.vpr)); + this.rbProj.plot(this.updateProjectionLine(this.cuboidModel.rb.getEquation(), + this.cuboidModel.rt.points[1], this.cuboidModel.vpr)); }, updateGrabPoints() { @@ -581,7 +653,7 @@ for (const key of Object.keys(originalResize)) { y: e.detail.p.y}; this.fire('dragstart', e.detail); }).on('dragmove', (e: CustomEvent) => { - this._viewModel.facesList[i+1].points.forEach((point: Point) => { + this.cuboidModel.facesList[i+1].points.forEach((point: Point) => { point.x += e.detail.p.x - this.dragPoint.x; point.y += e.detail.p.y - this.dragPoint.y; }); @@ -620,30 +692,30 @@ for (const key of Object.keys(originalResize)) { switch (index) { // fl case 1: { - const p2 = this.updatedEdge(this._viewModel.fr.points[0], point, this._viewModel.vpl); - const p3 = this.updatedEdge(this._viewModel.dr.points[0], p2, this._viewModel.vpr); - const p4 = this.updatedEdge(this._viewModel.dl.points[0], point, this._viewModel.vpr); + const p2 = this.updatedEdge(this.cuboidModel.fr.points[0], point, this.cuboidModel.vpl); + const p3 = this.updatedEdge(this.cuboidModel.dr.points[0], p2, this.cuboidModel.vpr); + const p4 = this.updatedEdge(this.cuboidModel.dl.points[0], point, this.cuboidModel.vpr); return [point, p2, p3, p4]; } // fr case 2: { - const p1 = this.updatedEdge(this._viewModel.fl.points[0], point, this._viewModel.vpl); - const p3 = this.updatedEdge(this._viewModel.dr.points[0], point, this._viewModel.vpr); - const p4 = this.updatedEdge(this._viewModel.dl.points[0], p3, this._viewModel.vpr); + const p1 = this.updatedEdge(this.cuboidModel.fl.points[0], point, this.cuboidModel.vpl); + const p3 = this.updatedEdge(this.cuboidModel.dr.points[0], point, this.cuboidModel.vpr); + const p4 = this.updatedEdge(this.cuboidModel.dl.points[0], p3, this.cuboidModel.vpr); return [p1, point, p3, p4]; } // dr case 3: { - const p2 = this.updatedEdge(this._viewModel.dl.points[0], point, this._viewModel.vpl); - const p3 = this.updatedEdge(this._viewModel.fr.points[0], point, this._viewModel.vpr); - const p4 = this.updatedEdge(this._viewModel.fl.points[0], p2, this._viewModel.vpr); + const p2 = this.updatedEdge(this.cuboidModel.dl.points[0], point, this.cuboidModel.vpl); + const p3 = this.updatedEdge(this.cuboidModel.fr.points[0], point, this.cuboidModel.vpr); + const p4 = this.updatedEdge(this.cuboidModel.fl.points[0], p2, this.cuboidModel.vpr); return [p4, p3, point, p2]; } // dl case 4: { - const p2 = this.updatedEdge(this._viewModel.dr.points[0], point, this._viewModel.vpl); - const p3 = this.updatedEdge(this._viewModel.fl.points[0], point, this._viewModel.vpr); - const p4 = this.updatedEdge(this._viewModel.fr.points[0], p2, this._viewModel.vpr); + const p2 = this.updatedEdge(this.cuboidModel.dr.points[0], point, this.cuboidModel.vpl); + const p3 = this.updatedEdge(this.cuboidModel.fl.points[0], point, this.cuboidModel.vpr); + const p4 = this.updatedEdge(this.cuboidModel.fr.points[0], p2, this.cuboidModel.vpr); return [p3, p4, p2, point]; } default: { @@ -665,13 +737,13 @@ for (const key of Object.keys(originalResize)) { this.updateProjections(); // to correct getting of points in resizedone, dragdone - this.attr('points', this._viewModel + this.attr('points', this.cuboidModel .getPoints() .reduce((acc: string, point: Point): string => `${acc} ${point.x},${point.y}`, '').trim()); }, updateFaces() { - const viewModel = this._viewModel; + const viewModel = this.cuboidModel; const frontPoints = viewModel.front.points; this.face.resize() @@ -684,7 +756,7 @@ for (const key of Object.keys(originalResize)) { }, updateEdges() { - const viewModel = this._viewModel; + const viewModel = this.cuboidModel; this.frontLeftEdge.plot(viewModel.fl.points); this.frontRightEdge.plot(viewModel.fr.points); From b203a1c9fce3aa4300d3f5424faae6d04f7a35f3 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 21 Apr 2020 00:06:04 +0300 Subject: [PATCH 21/45] Code refactoring, added resize constraints --- cvat-canvas/src/typescript/cuboid.ts | 2 - cvat-canvas/src/typescript/svg.patch.ts | 292 ++++++++++-------------- 2 files changed, 125 insertions(+), 169 deletions(-) diff --git a/cvat-canvas/src/typescript/cuboid.ts b/cvat-canvas/src/typescript/cuboid.ts index 765644c46bce..24901f1adfdd 100644 --- a/cvat-canvas/src/typescript/cuboid.ts +++ b/cvat-canvas/src/typescript/cuboid.ts @@ -151,8 +151,6 @@ export class CuboidModel { } else { this.orientation = Orientation.RIGHT; } - - console.log(this.orientation); } public updatePoints(): void { diff --git a/cvat-canvas/src/typescript/svg.patch.ts b/cvat-canvas/src/typescript/svg.patch.ts index 54103561a7cb..03488f84511b 100644 --- a/cvat-canvas/src/typescript/svg.patch.ts +++ b/cvat-canvas/src/typescript/svg.patch.ts @@ -15,6 +15,7 @@ import { Equation, CuboidModel, Orientation, + Edge, } from './cuboid'; import { parsePoints, stringifyPoints } from './shared'; @@ -190,6 +191,42 @@ for (const key of Object.keys(originalResize)) { } +enum EdgeIndex { + FL = 1, + FR = 2, + DR = 3, + DL = 4, +} + +function getEdgeIndex(cuboidPoint: number): EdgeIndex { + switch (cuboidPoint) { + case 0: + case 1: + return EdgeIndex.FL; + case 2: + case 3: + return EdgeIndex.FR; + case 4: + case 5: + return EdgeIndex.DR; + default: + return EdgeIndex.DL; + } +} + +function getTopDown(edgeIndex: EdgeIndex): number[] { + switch (edgeIndex) { + case EdgeIndex.FL: + return [0, 1]; + case EdgeIndex.FR: + return [2, 3]; + case EdgeIndex.DR: + return [4, 5]; + default: + return [6, 7]; + } +} + (SVG as any).Cube = SVG.invent({ create: 'g', inherit: SVG.G, @@ -304,23 +341,27 @@ for (const key of Object.keys(originalResize)) { return; } + function getResizedPointIndex(event: CustomEvent): number { + const { target } = event.detail.event.detail.event; + const { parentElement } = target; + return Array + .from(parentElement.children) + .indexOf(target); + } + + let resizedCubePoint: null | number = null; const accumulatedOffset: Point = { x: 0, y: 0, }; - let cubePoints: Point[] = []; - let resizablePointIndex: null | number = null; this.face.on('resizestart', (event: CustomEvent) => { - cubePoints = JSON.parse(JSON.stringify(this.cuboidModel.getPoints())); - const { target } = event.detail.event.detail.event; - const { parentElement } = target; - resizablePointIndex = Array - .from(parentElement.children) - .indexOf(target); - this.fire(new CustomEvent('resizestart', event)); accumulatedOffset.x = 0; accumulatedOffset.y = 0; + const resizedFacePoint = getResizedPointIndex(event); + resizedCubePoint = [0, 1].includes(resizedFacePoint) ? resizedFacePoint + : 5 - resizedFacePoint; // 2,3 -> 3,2 + this.fire(new CustomEvent('resizestart', event)); }).on('resizing', (event: CustomEvent) => { let { dx, dy } = event.detail; let dxPortion = dx - accumulatedOffset.x; @@ -328,77 +369,45 @@ for (const key of Object.keys(originalResize)) { accumulatedOffset.x += dxPortion; accumulatedOffset.y += dyPortion; - let facePoints = parsePoints(this.face.attr('points')); - - if (facePoints[2].x - facePoints[1].x + dx < consts.MIN_EDGE_LENGTH - || facePoints[1].y - facePoints[0].y + dy < consts.MIN_EDGE_LENGTH + const edge = getEdgeIndex(resizedCubePoint); + const [edgeTopIndex, edgeBottomIndex] = getTopDown(edge); + + let cuboidPoints = this.cuboidModel.getPoints(); + let x1 = cuboidPoints[edgeTopIndex].x + dxPortion; + let x2 = cuboidPoints[edgeBottomIndex].x + dxPortion; + if (edge === EdgeIndex.FL + && (cuboidPoints[2].x - (cuboidPoints[0].x + dxPortion) < consts.MIN_EDGE_LENGTH) ) { - this.face.plot(this.cuboidModel.front.points); - return; + x1 = cuboidPoints[edgeTopIndex].x; + x2 = cuboidPoints[edgeBottomIndex].x; + } else if (edge === EdgeIndex.FR + && (cuboidPoints[2].x + dxPortion - cuboidPoints[0].x < consts.MIN_EDGE_LENGTH) + ) { + x1 = cuboidPoints[edgeTopIndex].x; + x2 = cuboidPoints[edgeBottomIndex].x; } - - if ([0, 1].includes(resizablePointIndex)) { - let cuboidPoints = this.cuboidModel.getPoints(); - - const x1 = cuboidPoints[0].x + dxPortion; - const x2 = cuboidPoints[1].x + dxPortion; - const y1 = this.cuboidModel.ft.getEquation().getY(x1); - const y2 = this.cuboidModel.fb.getEquation().getY(x2); - const topPoint = { x: x1, y: y1 }; - const botPoint = { x: x2, y: y2 }; - + const y1 = this.cuboidModel.ft.getEquation().getY(x1); + const y2 = this.cuboidModel.fb.getEquation().getY(x2); + const topPoint = { x: x1, y: y1 }; + const botPoint = { x: x2, y: y2 }; + if (edge === 1) { this.cuboidModel.fl.points = [topPoint, botPoint]; - this.updateViewAndVM(); - - - cuboidPoints = this.cuboidModel.getPoints(); - let midPointUp: Point | null = null; - let midPointDown: Point | null = null; - if (resizablePointIndex === 0) { - midPointUp = { x: cuboidPoints[0].x, y: cuboidPoints[0].y + dyPortion } - midPointDown = { x: cuboidPoints[1].x, y: cuboidPoints[1].y }; - } else if (resizablePointIndex === 1) { - midPointUp = { x: cuboidPoints[0].x, y: cuboidPoints[0].y }; - midPointDown = { x: cuboidPoints[1].x, y: cuboidPoints[1].y + dyPortion }; - } - - const topPoints = this.computeHeightFace(midPointUp, 1); - const bottomPoints = this.computeHeightFace(midPointDown, 1); - this.cuboidModel.top.points = topPoints; - this.cuboidModel.bot.points = bottomPoints; - this.updateViewAndVM(false); - } else if ([2, 3].includes(resizablePointIndex)) { - let cuboidPoints = this.cuboidModel.getPoints(); - - const x1 = cuboidPoints[2].x + dxPortion; - const x2 = cuboidPoints[3].x + dxPortion; - const y1 = this.cuboidModel.ft.getEquation().getY(x1); - const y2 = this.cuboidModel.fb.getEquation().getY(x2); - const topPoint = { x: x1, y: y1 }; - const botPoint = { x: x2, y: y2 }; - + } else { this.cuboidModel.fr.points = [topPoint, botPoint]; - this.updateViewAndVM(true); - - - - cuboidPoints = this.cuboidModel.getPoints(); - let midPointUp: Point | null = null; - let midPointDown: Point | null = null; - if (resizablePointIndex === 3) { - midPointUp = { x: cuboidPoints[2].x, y: cuboidPoints[2].y + dyPortion } - midPointDown = { x: cuboidPoints[3].x, y: cuboidPoints[3].y }; - } else if (resizablePointIndex === 2) { - midPointUp = { x: cuboidPoints[2].x, y: cuboidPoints[2].y }; - midPointDown = { x: cuboidPoints[3].x, y: cuboidPoints[3].y + dyPortion }; - } - - const topPoints = this.computeHeightFace(midPointUp, 2); - const bottomPoints = this.computeHeightFace(midPointDown, 2); + } + this.updateViewAndVM(edge === EdgeIndex.FR); + + cuboidPoints = this.cuboidModel.getPoints(); + const midPointUp = { ...cuboidPoints[edgeTopIndex] }; + const midPointDown = { ...cuboidPoints[edgeBottomIndex] }; + (edgeTopIndex === resizedCubePoint ? midPointUp : midPointDown).y += dyPortion; + if (midPointDown.y - midPointUp.y > consts.MIN_EDGE_LENGTH) { + const topPoints = this.computeHeightFace(midPointUp, edge); + const bottomPoints = this.computeHeightFace(midPointDown, edge); this.cuboidModel.top.points = topPoints; this.cuboidModel.bot.points = bottomPoints; - this.updateViewAndVM(false); - } + this.updateViewAndVM(false); + } this.face.plot(this.cuboidModel.front.points); this.fire(new CustomEvent('resizing', event)); @@ -406,73 +415,12 @@ for (const key of Object.keys(originalResize)) { this.fire(new CustomEvent('resizedone', event)); }); - if (this.cuboidModel.orientation === Orientation.LEFT) { - this.dorsalRightEdge.resize(value); - this.dorsalRightEdge.on('resizestart', (event: CustomEvent) => { - cubePoints = JSON.parse(JSON.stringify(this.cuboidModel.getPoints())); - const { target } = event.detail.event.detail.event; - const { parentElement } = target; - resizablePointIndex = Array - .from(parentElement.children) - .indexOf(target); - this.fire(new CustomEvent('resizestart', event)); + function setupDorsalEdge(edge: SVG.Line, orientation: Orientation) { + edge.on('resizestart', (event: CustomEvent) => { accumulatedOffset.x = 0; accumulatedOffset.y = 0; - }).on('resizing', (event: CustomEvent) => { - let { dx, dy } = event.detail; - let dxPortion = dx - accumulatedOffset.x; - let dyPortion = dy - accumulatedOffset.y; - accumulatedOffset.x += dxPortion; - accumulatedOffset.y += dyPortion; - - let cuboidPoints = this.cuboidModel.getPoints(); - - const x1 = cuboidPoints[4].x + dxPortion; - const x2 = cuboidPoints[5].x + dxPortion; - const y1 = this.cuboidModel.rt.getEquation().getY(x1); - const y2 = this.cuboidModel.rb.getEquation().getY(x2); - const topPoint = { x: x1, y: y1 }; - const botPoint = { x: x2, y: y2 }; - - this.cuboidModel.dr.points = [topPoint, botPoint]; - this.updateViewAndVM(); - - - - cuboidPoints = this.cuboidModel.getPoints(); - let midPointUp: Point | null = null; - let midPointDown: Point | null = null; - if (resizablePointIndex === 0) { - midPointUp = { x: cuboidPoints[4].x, y: cuboidPoints[4].y + dyPortion } - midPointDown = { x: cuboidPoints[5].x, y: cuboidPoints[5].y }; - } else if (resizablePointIndex === 1) { - midPointUp = { x: cuboidPoints[4].x, y: cuboidPoints[4].y }; - midPointDown = { x: cuboidPoints[5].x, y: cuboidPoints[5].y + dyPortion }; - } - - const topPoints = this.computeHeightFace(midPointUp, 3); - const bottomPoints = this.computeHeightFace(midPointDown, 3); - this.cuboidModel.top.points = topPoints; - this.cuboidModel.bot.points = bottomPoints; - this.updateViewAndVM(false); - - this.face.plot(this.cuboidModel.front.points); - this.fire(new CustomEvent('resizing', event)); - }).on('resizedone', (event: CustomEvent) => { - this.fire(new CustomEvent('resizedone', event)); - }); - } else { - this.dorsalLeftEdge.resize(); - this.dorsalLeftEdge.on('resizestart', (event: CustomEvent) => { - cubePoints = JSON.parse(JSON.stringify(this.cuboidModel.getPoints())); - const { target } = event.detail.event.detail.event; - const { parentElement } = target; - resizablePointIndex = Array - .from(parentElement.children) - .indexOf(target); + resizedCubePoint = getResizedPointIndex(event) + (orientation === Orientation.LEFT ? 4 : 6); this.fire(new CustomEvent('resizestart', event)); - accumulatedOffset.x = 0; - accumulatedOffset.y = 0; }).on('resizing', (event: CustomEvent) => { let { dx, dy } = event.detail; let dxPortion = dx - accumulatedOffset.x; @@ -480,43 +428,53 @@ for (const key of Object.keys(originalResize)) { accumulatedOffset.x += dxPortion; accumulatedOffset.y += dyPortion; - let cuboidPoints = this.cuboidModel.getPoints(); - - const x1 = cuboidPoints[6].x + dxPortion; - const x2 = cuboidPoints[7].x + dxPortion; - const y1 = this.cuboidModel.lt.getEquation().getY(x1); - const y2 = this.cuboidModel.lb.getEquation().getY(x2); - const topPoint = { x: x1, y: y1 }; - const botPoint = { x: x2, y: y2 }; + const edge = getEdgeIndex(resizedCubePoint); + const [edgeTopIndex, edgeBottomIndex] = getTopDown(edge); - this.cuboidModel.dl.points = [topPoint, botPoint]; - this.updateViewAndVM(true); - - - - cuboidPoints = this.cuboidModel.getPoints(); - let midPointUp: Point | null = null; - let midPointDown: Point | null = null; - if (resizablePointIndex === 0) { - midPointUp = { x: cuboidPoints[6].x, y: cuboidPoints[6].y + dyPortion } - midPointDown = { x: cuboidPoints[7].x, y: cuboidPoints[7].y }; - } else if (resizablePointIndex === 1) { - midPointUp = { x: cuboidPoints[6].x, y: cuboidPoints[6].y }; - midPointDown = { x: cuboidPoints[7].x, y: cuboidPoints[7].y + dyPortion }; - } + let cuboidPoints = this.cuboidModel.getPoints(); + const x1 = cuboidPoints[edgeTopIndex].x + dxPortion; + const x2 = cuboidPoints[edgeBottomIndex].x + dxPortion; + const y1 = (orientation === Orientation.LEFT + ? this.cuboidModel.rt : this.cuboidModel.lt).getEquation().getY(x1); + const y2 = (orientation === Orientation.LEFT + ? this.cuboidModel.rb : this.cuboidModel.lb).getEquation().getY(x2); - const topPoints = this.computeHeightFace(midPointUp, 4); - const bottomPoints = this.computeHeightFace(midPointDown, 4); - this.cuboidModel.top.points = topPoints; - this.cuboidModel.bot.points = bottomPoints; - this.updateViewAndVM(false); + const frontTopPoint = orientation === Orientation.LEFT ? 2 : 0; + if ((cuboidPoints[frontTopPoint].y - y1) > consts.MIN_EDGE_LENGTH) { + const topPoint = { x: x1, y: y1 }; + const botPoint = { x: x2, y: y2 }; + (orientation === Orientation.LEFT + ? this.cuboidModel.dr : this.cuboidModel.dl).points = [topPoint, botPoint]; + this.updateViewAndVM(edge === EdgeIndex.DL); + } + cuboidPoints = this.cuboidModel.getPoints(); + const midPointUp = { ...cuboidPoints[edgeTopIndex] }; + const midPointDown = { ...cuboidPoints[edgeBottomIndex] }; + (edgeTopIndex === resizedCubePoint ? midPointUp : midPointDown).y += dyPortion; + + if (midPointDown.y - midPointUp.y > consts.MIN_EDGE_LENGTH) { + const topPoints = this.computeHeightFace(midPointUp, edge); + const bottomPoints = this.computeHeightFace(midPointDown, edge); + this.cuboidModel.top.points = topPoints; + this.cuboidModel.bot.points = bottomPoints; + } + + this.updateViewAndVM(false); this.face.plot(this.cuboidModel.front.points); this.fire(new CustomEvent('resizing', event)); }).on('resizedone', (event: CustomEvent) => { this.fire(new CustomEvent('resizedone', event)); }); - } + } + + if (this.cuboidModel.orientation === Orientation.LEFT) { + this.dorsalRightEdge.resize(value); + setupDorsalEdge.call(this, this.dorsalRightEdge, this.cuboidModel.orientation); + } else { + this.dorsalLeftEdge.resize(value); + setupDorsalEdge.call(this, this.dorsalLeftEdge, this.cuboidModel.orientation); + } return this; }, From c19986075fa3e4869ff306ae5752c96bf9ffce9a Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Tue, 21 Apr 2020 01:14:56 +0300 Subject: [PATCH 22/45] Changing perspective --- cvat-canvas/src/typescript/svg.patch.ts | 104 ++++++++++++++++++------ 1 file changed, 77 insertions(+), 27 deletions(-) diff --git a/cvat-canvas/src/typescript/svg.patch.ts b/cvat-canvas/src/typescript/svg.patch.ts index 03488f84511b..406e7431ad78 100644 --- a/cvat-canvas/src/typescript/svg.patch.ts +++ b/cvat-canvas/src/typescript/svg.patch.ts @@ -17,7 +17,7 @@ import { Orientation, Edge, } from './cuboid'; -import { parsePoints, stringifyPoints } from './shared'; +import { parsePoints, stringifyPoints, clamp } from './shared'; // Update constructor const originalDraw = SVG.Element.prototype.draw; @@ -415,6 +415,37 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { this.fire(new CustomEvent('resizedone', event)); }); + function computeSideEdgeConstraints(edge: Edge, fr: Edge) { + const midLength = fr.points[1].y - fr.points[0].y - 1; + + const minY = edge.points[1].y - midLength; + const maxY = edge.points[0].y + midLength; + + const y1 = edge.points[0].y; + const y2 = edge.points[1].y; + + const miny1 = y2 - midLength; + const maxy1 = y2 - consts.MIN_EDGE_LENGTH; + + const miny2 = y1 + consts.MIN_EDGE_LENGTH; + const maxy2 = y1 + midLength; + + return { + constraint: { + minY, + maxY, + }, + y1Range: { + max: maxy1, + min: miny1, + }, + y2Range: { + max: maxy2, + min: miny2, + }, + }; + } + function setupDorsalEdge(edge: SVG.Line, orientation: Orientation) { edge.on('resizestart', (event: CustomEvent) => { accumulatedOffset.x = 0; @@ -430,35 +461,54 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { const edge = getEdgeIndex(resizedCubePoint); const [edgeTopIndex, edgeBottomIndex] = getTopDown(edge); - let cuboidPoints = this.cuboidModel.getPoints(); - const x1 = cuboidPoints[edgeTopIndex].x + dxPortion; - const x2 = cuboidPoints[edgeBottomIndex].x + dxPortion; - const y1 = (orientation === Orientation.LEFT - ? this.cuboidModel.rt : this.cuboidModel.lt).getEquation().getY(x1); - const y2 = (orientation === Orientation.LEFT - ? this.cuboidModel.rb : this.cuboidModel.lb).getEquation().getY(x2); - - const frontTopPoint = orientation === Orientation.LEFT ? 2 : 0; - if ((cuboidPoints[frontTopPoint].y - y1) > consts.MIN_EDGE_LENGTH) { - const topPoint = { x: x1, y: y1 }; - const botPoint = { x: x2, y: y2 }; - (orientation === Orientation.LEFT - ? this.cuboidModel.dr : this.cuboidModel.dl).points = [topPoint, botPoint]; + + if (!event.detail.event.shiftKey) { + const x1 = cuboidPoints[edgeTopIndex].x + dxPortion; + const x2 = cuboidPoints[edgeBottomIndex].x + dxPortion; + const y1 = (orientation === Orientation.LEFT + ? this.cuboidModel.rt : this.cuboidModel.lt).getEquation().getY(x1); + const y2 = (orientation === Orientation.LEFT + ? this.cuboidModel.rb : this.cuboidModel.lb).getEquation().getY(x2); + + const frontTopPoint = orientation === Orientation.LEFT ? 2 : 0; + if (cuboidPoints[edgeTopIndex].x < cuboidPoints[frontTopPoint].x + && x1 < cuboidPoints[frontTopPoint].x - consts.MIN_EDGE_LENGTH + && x1 > this.cuboidModel.vpr.x + consts.MIN_EDGE_LENGTH + || cuboidPoints[edgeTopIndex].x >= cuboidPoints[frontTopPoint].x + && x1 > cuboidPoints[frontTopPoint].x + consts.MIN_EDGE_LENGTH + && x1 < this.cuboidModel.vpr.x + consts.MIN_EDGE_LENGTH + ) { + const topPoint = { x: x1, y: y1 }; + const botPoint = { x: x2, y: y2 }; + (orientation === Orientation.LEFT + ? this.cuboidModel.dr : this.cuboidModel.dl).points = [topPoint, botPoint]; + this.updateViewAndVM(edge === EdgeIndex.DL); + } + + + cuboidPoints = this.cuboidModel.getPoints(); + const midPointUp = { ...cuboidPoints[edgeTopIndex] }; + const midPointDown = { ...cuboidPoints[edgeBottomIndex] }; + (edgeTopIndex === resizedCubePoint ? midPointUp : midPointDown).y += dyPortion; + if (midPointDown.y - midPointUp.y > consts.MIN_EDGE_LENGTH) { + const topPoints = this.computeHeightFace(midPointUp, edge); + const bottomPoints = this.computeHeightFace(midPointDown, edge); + this.cuboidModel.top.points = topPoints; + this.cuboidModel.bot.points = bottomPoints; + } + } else { + const midPointUp = { ...cuboidPoints[edgeTopIndex] }; + const midPointDown = { ...cuboidPoints[edgeBottomIndex] }; + (edgeTopIndex === resizedCubePoint ? midPointUp : midPointDown).y += dyPortion; + const dorselEdge = (orientation === Orientation.LEFT ? this.cuboidModel.dr : this.cuboidModel.dl); + const constraints = computeSideEdgeConstraints(dorselEdge, this.cuboidModel.fr); + midPointUp.y = clamp(midPointUp.y, constraints.y1Range.min, constraints.y1Range.max); + midPointDown.y = clamp(midPointDown.y, constraints.y2Range.min, constraints.y2Range.max); + dorselEdge.points = [midPointUp, midPointDown]; this.updateViewAndVM(edge === EdgeIndex.DL); } - - cuboidPoints = this.cuboidModel.getPoints(); - const midPointUp = { ...cuboidPoints[edgeTopIndex] }; - const midPointDown = { ...cuboidPoints[edgeBottomIndex] }; - (edgeTopIndex === resizedCubePoint ? midPointUp : midPointDown).y += dyPortion; - - if (midPointDown.y - midPointUp.y > consts.MIN_EDGE_LENGTH) { - const topPoints = this.computeHeightFace(midPointUp, edge); - const bottomPoints = this.computeHeightFace(midPointDown, edge); - this.cuboidModel.top.points = topPoints; - this.cuboidModel.bot.points = bottomPoints; - } + this.updateViewAndVM(false); this.face.plot(this.cuboidModel.front.points); From 64ec0e9fac59be330781963ad5defbf99042e577 Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Tue, 21 Apr 2020 13:26:43 +0300 Subject: [PATCH 23/45] fixed merge --- cvat-canvas/src/typescript/svg.patch.ts | 179 +++++++++++++----------- 1 file changed, 95 insertions(+), 84 deletions(-) diff --git a/cvat-canvas/src/typescript/svg.patch.ts b/cvat-canvas/src/typescript/svg.patch.ts index 406e7431ad78..52ea82d2ce1b 100644 --- a/cvat-canvas/src/typescript/svg.patch.ts +++ b/cvat-canvas/src/typescript/svg.patch.ts @@ -10,7 +10,7 @@ import 'svg.select.js'; import 'svg.draw.js'; import consts from './consts'; -import { +import { Point, Equation, CuboidModel, @@ -201,13 +201,13 @@ enum EdgeIndex { function getEdgeIndex(cuboidPoint: number): EdgeIndex { switch (cuboidPoint) { case 0: - case 1: + case 1: return EdgeIndex.FL; - case 2: + case 2: case 3: return EdgeIndex.FR; case 4: - case 5: + case 5: return EdgeIndex.DR; default: return EdgeIndex.DL; @@ -218,9 +218,9 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { switch (edgeIndex) { case EdgeIndex.FL: return [0, 1]; - case EdgeIndex.FR: + case EdgeIndex.FR: return [2, 3]; - case EdgeIndex.DR: + case EdgeIndex.DR: return [4, 5]; default: return [6, 7]; @@ -354,14 +354,14 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { x: 0, y: 0, }; - + this.face.on('resizestart', (event: CustomEvent) => { accumulatedOffset.x = 0; accumulatedOffset.y = 0; const resizedFacePoint = getResizedPointIndex(event); resizedCubePoint = [0, 1].includes(resizedFacePoint) ? resizedFacePoint : 5 - resizedFacePoint; // 2,3 -> 3,2 - this.fire(new CustomEvent('resizestart', event)); + this.fire(new CustomEvent('resizestart', event)); }).on('resizing', (event: CustomEvent) => { let { dx, dy } = event.detail; let dxPortion = dx - accumulatedOffset.x; @@ -371,7 +371,7 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { const edge = getEdgeIndex(resizedCubePoint); const [edgeTopIndex, edgeBottomIndex] = getTopDown(edge); - + let cuboidPoints = this.cuboidModel.getPoints(); let x1 = cuboidPoints[edgeTopIndex].x + dxPortion; let x2 = cuboidPoints[edgeBottomIndex].x + dxPortion; @@ -380,7 +380,7 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { ) { x1 = cuboidPoints[edgeTopIndex].x; x2 = cuboidPoints[edgeBottomIndex].x; - } else if (edge === EdgeIndex.FR + } else if (edge === EdgeIndex.FR && (cuboidPoints[2].x + dxPortion - cuboidPoints[0].x < consts.MIN_EDGE_LENGTH) ) { x1 = cuboidPoints[edgeTopIndex].x; @@ -405,9 +405,9 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { const topPoints = this.computeHeightFace(midPointUp, edge); const bottomPoints = this.computeHeightFace(midPointDown, edge); this.cuboidModel.top.points = topPoints; - this.cuboidModel.bot.points = bottomPoints; - this.updateViewAndVM(false); - } + this.cuboidModel.bot.points = bottomPoints; + this.updateViewAndVM(false); + } this.face.plot(this.cuboidModel.front.points); this.fire(new CustomEvent('resizing', event)); @@ -417,19 +417,19 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { function computeSideEdgeConstraints(edge: Edge, fr: Edge) { const midLength = fr.points[1].y - fr.points[0].y - 1; - + const minY = edge.points[1].y - midLength; const maxY = edge.points[0].y + midLength; - + const y1 = edge.points[0].y; const y2 = edge.points[1].y; - + const miny1 = y2 - midLength; const maxy1 = y2 - consts.MIN_EDGE_LENGTH; - + const miny2 = y1 + consts.MIN_EDGE_LENGTH; const maxy2 = y1 + midLength; - + return { constraint: { minY, @@ -466,26 +466,26 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { if (!event.detail.event.shiftKey) { const x1 = cuboidPoints[edgeTopIndex].x + dxPortion; const x2 = cuboidPoints[edgeBottomIndex].x + dxPortion; - const y1 = (orientation === Orientation.LEFT + const y1 = (orientation === Orientation.LEFT ? this.cuboidModel.rt : this.cuboidModel.lt).getEquation().getY(x1); - const y2 = (orientation === Orientation.LEFT + const y2 = (orientation === Orientation.LEFT ? this.cuboidModel.rb : this.cuboidModel.lb).getEquation().getY(x2); - + const frontTopPoint = orientation === Orientation.LEFT ? 2 : 0; if (cuboidPoints[edgeTopIndex].x < cuboidPoints[frontTopPoint].x && x1 < cuboidPoints[frontTopPoint].x - consts.MIN_EDGE_LENGTH && x1 > this.cuboidModel.vpr.x + consts.MIN_EDGE_LENGTH || cuboidPoints[edgeTopIndex].x >= cuboidPoints[frontTopPoint].x - && x1 > cuboidPoints[frontTopPoint].x + consts.MIN_EDGE_LENGTH + && x1 > cuboidPoints[frontTopPoint].x + consts.MIN_EDGE_LENGTH && x1 < this.cuboidModel.vpr.x + consts.MIN_EDGE_LENGTH ) { const topPoint = { x: x1, y: y1 }; const botPoint = { x: x2, y: y2 }; - (orientation === Orientation.LEFT + (orientation === Orientation.LEFT ? this.cuboidModel.dr : this.cuboidModel.dl).points = [topPoint, botPoint]; this.updateViewAndVM(edge === EdgeIndex.DL); } - + cuboidPoints = this.cuboidModel.getPoints(); const midPointUp = { ...cuboidPoints[edgeTopIndex] }; @@ -495,7 +495,7 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { const topPoints = this.computeHeightFace(midPointUp, edge); const bottomPoints = this.computeHeightFace(midPointDown, edge); this.cuboidModel.top.points = topPoints; - this.cuboidModel.bot.points = bottomPoints; + this.cuboidModel.bot.points = bottomPoints; } } else { const midPointUp = { ...cuboidPoints[edgeTopIndex] }; @@ -508,7 +508,7 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { dorselEdge.points = [midPointUp, midPointDown]; this.updateViewAndVM(edge === EdgeIndex.DL); } - + this.updateViewAndVM(false); this.face.plot(this.cuboidModel.front.points); @@ -525,7 +525,75 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { this.dorsalLeftEdge.resize(value); setupDorsalEdge.call(this, this.dorsalLeftEdge, this.cuboidModel.orientation); } + return this; + }, + + draggable(value: any, constraint: any) { + const _draggable = SVG.Element.prototype.draggable.bind(this) + const faces = [this.right, this.dorsal, this.left] + const accumulatedOffset: Point = { + x: 0, + y: 0, + }; + + if (value === false) { + [this.face, ...faces].forEach((face: any) => { + face.draggable(false); + face.off('dragstart'); + face.off('dragmove'); + face.off('dragsend'); + }) + return + } + + this.face.draggable().on('dragstart', (event: CustomEvent) => { + accumulatedOffset.x = 0; + accumulatedOffset.y = 0; + + this.fire(new CustomEvent('dragstart', event)); + }).on('dragmove', (event: CustomEvent) => { + const dx = event.detail.p.x - event.detail.handler.startPoints.point.x; + const dy = event.detail.p.y - event.detail.handler.startPoints.point.y; + let dxPortion = dx - accumulatedOffset.x; + let dyPortion = dy - accumulatedOffset.y; + accumulatedOffset.x += dxPortion; + accumulatedOffset.y += dyPortion; + + this.dmove(dxPortion, dyPortion); + + this.fire(new CustomEvent('dragmove', event)); + }).on('dragend', (event: CustomEvent) => { + + this.fire(new CustomEvent('dragend', event)); + }) + + faces.forEach((face: any, i: number) => { + face.draggable().on('dragstart', (event: CustomEvent) => { + accumulatedOffset.x = 0; + accumulatedOffset.y = 0; + + this.fire(new CustomEvent('dragstart', event)); + }).on('dragmove', (event: CustomEvent) => { + const dx = event.detail.p.x - event.detail.handler.startPoints.point.x; + const dy = event.detail.p.y - event.detail.handler.startPoints.point.y; + let dxPortion = dx - accumulatedOffset.x; + let dyPortion = dy - accumulatedOffset.y; + accumulatedOffset.x += dxPortion; + accumulatedOffset.y += dyPortion; + this.cuboidModel.facesList[i+1].points.forEach((point: Point) => { + point.x += dxPortion; + point.y += dyPortion; + }); + + this.updateViewAndVM(); + + this.fire(new CustomEvent('dragmove', event)); + }).on('dragend', (event: CustomEvent) => { + + this.fire(new CustomEvent('dragend', event)); + }) + }) return this; }, @@ -639,63 +707,6 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { } }, - addDragEvents() { - this.face.draggable().on('dragstart', (e: CustomEvent) => { - this.dragPoint = { x: e.detail.p.x, - y: e.detail.p.y}; - this.fire('dragstart', e.detail); - }).on('dragmove', (e: CustomEvent) => { - this.dmove(e.detail.p.x - this.dragPoint.x, - e.detail.p.y - this.dragPoint.y); - this.dragPoint = { x: e.detail.p.x, - y: e.detail.p.y } - this.fire('dragmove', e.detail); - }).on('dragend', (e: CustomEvent) => { - this.fire('dragend', e.detail); - }); - - const faces = [this.right, this.dorsal, this.left]; - faces.forEach((face: any, i: number) => { - face.draggable().on('dragstart', (e: CustomEvent) => { - this.dragPoint = { x: e.detail.p.x, - y: e.detail.p.y}; - this.fire('dragstart', e.detail); - }).on('dragmove', (e: CustomEvent) => { - this.cuboidModel.facesList[i+1].points.forEach((point: Point) => { - point.x += e.detail.p.x - this.dragPoint.x; - point.y += e.detail.p.y - this.dragPoint.y; - }); - this.dragPoint = { x: e.detail.p.x, - y: e.detail.p.y }; - - this.updateViewAndVM(); - this.fire('dragmove', e.detail); - }).on('dragend', (e: CustomEvent) => { - this.fire('dragend', e.detail); - }); - }); - }, - - removeDragEvents() { - const faces = [this.face, this.right, this.dorsal, this.left] - faces.forEach((face: any) => { - face.draggable(false); - face.off('dragstart'); - face.off('dragmove'); - face.off('dragsend'); - }) - }, - - draggable(value: any, constraint: any) { - const _draggable = SVG.Element.prototype.draggable.bind(this) - if (value !== false) { - this.addDragEvents(); - } else { - this.removeDragEvents(); - } - return _draggable(value, constraint); - }, - computeHeightFace(point: Point, index: number) { switch (index) { // fl @@ -752,7 +763,7 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { updateFaces() { const viewModel = this.cuboidModel; - + const frontPoints = viewModel.front.points; this.face.resize() .resize(frontPoints[2].x - frontPoints[0].x, frontPoints[1].y - frontPoints[0].y) From 435db426846e2c15ffd2d778de9ff90f5c7de737 Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Tue, 21 Apr 2020 17:00:47 +0300 Subject: [PATCH 24/45] fixed cuboid figure updating --- cvat-canvas/src/typescript/svg.patch.ts | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/cvat-canvas/src/typescript/svg.patch.ts b/cvat-canvas/src/typescript/svg.patch.ts index 52ea82d2ce1b..6b2335ab9696 100644 --- a/cvat-canvas/src/typescript/svg.patch.ts +++ b/cvat-canvas/src/typescript/svg.patch.ts @@ -541,7 +541,7 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { face.draggable(false); face.off('dragstart'); face.off('dragmove'); - face.off('dragsend'); + face.off('dragend'); }) return } @@ -574,19 +574,9 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { this.fire(new CustomEvent('dragstart', event)); }).on('dragmove', (event: CustomEvent) => { - const dx = event.detail.p.x - event.detail.handler.startPoints.point.x; - const dy = event.detail.p.y - event.detail.handler.startPoints.point.y; - let dxPortion = dx - accumulatedOffset.x; - let dyPortion = dy - accumulatedOffset.y; - accumulatedOffset.x += dxPortion; - accumulatedOffset.y += dyPortion; - - this.cuboidModel.facesList[i+1].points.forEach((point: Point) => { - point.x += dxPortion; - point.y += dyPortion; - }); + this.cuboidModel.facesList[i+1].points = parsePoints(face.attr('points')); - this.updateViewAndVM(); + this.updateViewAndVM(i === 0); this.fire(new CustomEvent('dragmove', event)); }).on('dragend', (event: CustomEvent) => { @@ -764,11 +754,7 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { updateFaces() { const viewModel = this.cuboidModel; - const frontPoints = viewModel.front.points; - this.face.resize() - .resize(frontPoints[2].x - frontPoints[0].x, frontPoints[1].y - frontPoints[0].y) - .move(frontPoints[0].x, frontPoints[0].y); - + this.face.plot(viewModel.front.points); this.right.plot(viewModel.right.points); this.dorsal.plot(viewModel.dorsal.points); this.left.plot(viewModel.left.points); From 63ef6be7c554efbf019a1fc95a338a5e8fb1fb54 Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Wed, 22 Apr 2020 01:28:17 +0300 Subject: [PATCH 25/45] cuboids: fixed draggable, added bitmap support and projections controls --- cvat-canvas/src/typescript/canvasModel.ts | 5 + cvat-canvas/src/typescript/canvasView.ts | 70 +++++++++--- cvat-canvas/src/typescript/svg.patch.ts | 101 ++++++++++++------ cvat-ui/src/actions/settings-actions.ts | 10 ++ .../standard-workspace/canvas-wrapper.tsx | 8 +- .../objects-side-bar/appearance-block.tsx | 10 ++ .../objects-side-bar/objects-side-bar.tsx | 6 ++ .../standard-workspace/canvas-wrapper.tsx | 3 + .../objects-side-bar/objects-side-bar.tsx | 16 +++ cvat-ui/src/reducers/interfaces.ts | 1 + cvat-ui/src/reducers/settings-reducer.ts | 10 ++ 11 files changed, 191 insertions(+), 49 deletions(-) diff --git a/cvat-canvas/src/typescript/canvasModel.ts b/cvat-canvas/src/typescript/canvasModel.ts index 13f99be1b08a..08cd6a880f21 100644 --- a/cvat-canvas/src/typescript/canvasModel.ts +++ b/cvat-canvas/src/typescript/canvasModel.ts @@ -49,6 +49,7 @@ export enum RectDrawingMethod { export interface Configuration { displayAllText?: boolean; undefinedAttrValue?: string; + showProjections?: boolean; } export interface DrawData { @@ -517,6 +518,10 @@ export class CanvasModelImpl extends MasterImpl implements CanvasModel { this.data.configuration.displayAllText = configuration.displayAllText; } + if (typeof (configuration.showProjections) !== 'undefined') { + this.data.configuration.showProjections = configuration.showProjections; + } + if (typeof (configuration.undefinedAttrValue) !== 'undefined') { this.data.configuration.undefinedAttrValue = configuration.undefinedAttrValue; } diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index 594e13f8bd3b..e08c88491b4a 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -924,26 +924,63 @@ export class CanvasViewImpl implements CanvasView, Listener { for (const state of states) { if (state.hidden || state.outside) continue; ctx.fillStyle = 'white'; - if (['rectangle', 'polygon'].includes(state.shapeType)) { - const points = state.shapeType === 'rectangle' ? [ - state.points[0], // xtl - state.points[1], // ytl - state.points[2], // xbr - state.points[1], // ytl - state.points[2], // xbr - state.points[3], // ybr - state.points[0], // xtl - state.points[3], // ybr - ] : state.points; + if (['rectangle', 'polygon', 'cuboid'].includes(state.shapeType)) { + let points = []; + if (state.shapeType === 'rectangle') { + points = [ + state.points[0], // xtl + state.points[1], // ytl + state.points[2], // xbr + state.points[1], // ytl + state.points[2], // xbr + state.points[3], // ybr + state.points[0], // xtl + state.points[3], // ybr + ]; + } else if (state.shapeType === 'cuboid') { + points = [ + state.points[0], + state.points[1], + state.points[4], + state.points[5], + state.points[8], + state.points[9], + state.points[12], + state.points[13], + ]; + } else { + points = [...state.points]; + } ctx.beginPath(); ctx.moveTo(points[0], points[1]); for (let i = 0; i < points.length; i += 2) { ctx.lineTo(points[i], points[i + 1]); } ctx.closePath(); + ctx.fill(); } - ctx.fill(); + if (state.shapeType === 'cuboid') { + for (let i = 0; i < 5; i++) { + const points = [ + state.points[(0 + i * 4) % 16], + state.points[(1 + i * 4) % 16], + state.points[(2 + i * 4) % 16], + state.points[(3 + i * 4) % 16], + state.points[(6 + i * 4) % 16], + state.points[(7 + i * 4) % 16], + state.points[(4 + i * 4) % 16], + state.points[(5 + i * 4) % 16], + ]; + ctx.beginPath(); + ctx.moveTo(points[0], points[1]); + for (let j = 0; j < points.length; j += 2) { + ctx.lineTo(points[j], points[j + 1]); + } + ctx.closePath(); + ctx.fill(); + } + } } } } @@ -1187,6 +1224,10 @@ export class CanvasViewImpl implements CanvasView, Listener { this.selectize(false, shape); } + if (drawnState.shapeType === 'cuboid') { + (shape as any).attr('projections', false); + } + (shape as any).off('resizestart'); (shape as any).off('resizing'); (shape as any).off('resizedone'); @@ -1266,6 +1307,11 @@ export class CanvasViewImpl implements CanvasView, Listener { this.content.append(shape.node); } + const { showProjections } = this.configuration; + if (state.shapeType === 'cuboid' && showProjections) { + (shape as any).attr('projections', true); + } + if (!state.pinned) { (shape as any).draggable().on('dragstart', (): void => { this.mode = Mode.DRAG; diff --git a/cvat-canvas/src/typescript/svg.patch.ts b/cvat-canvas/src/typescript/svg.patch.ts index 6b2335ab9696..0d0899fbf8e5 100644 --- a/cvat-canvas/src/typescript/svg.patch.ts +++ b/cvat-canvas/src/typescript/svg.patch.ts @@ -529,15 +529,15 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { }, draggable(value: any, constraint: any) { - const _draggable = SVG.Element.prototype.draggable.bind(this) - const faces = [this.right, this.dorsal, this.left] + const { cuboidModel } = this; + const faces = [this.face, this.right, this.dorsal, this.left] const accumulatedOffset: Point = { x: 0, y: 0, }; if (value === false) { - [this.face, ...faces].forEach((face: any) => { + faces.forEach((face: any) => { face.draggable(false); face.off('dragstart'); face.off('dragmove'); @@ -567,23 +567,45 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { this.fire(new CustomEvent('dragend', event)); }) - faces.forEach((face: any, i: number) => { - face.draggable().on('dragstart', (event: CustomEvent) => { - accumulatedOffset.x = 0; - accumulatedOffset.y = 0; + this.left.draggable((x: number, y: number) => ({ + x: x < Math.min(cuboidModel.dr.points[0].x, + cuboidModel.fr.points[0].x) - consts.MIN_EDGE_LENGTH, y + })).on('dragstart', (event: CustomEvent) => { + this.fire(new CustomEvent('dragstart', event)); + }).on('dragmove', (event: CustomEvent) => { + this.cuboidModel.left.points = parsePoints(this.left.attr('points')); + this.updateViewAndVM(); - this.fire(new CustomEvent('dragstart', event)); - }).on('dragmove', (event: CustomEvent) => { - this.cuboidModel.facesList[i+1].points = parsePoints(face.attr('points')); + this.fire(new CustomEvent('dragmove', event)); + }).on('dragend', (event: CustomEvent) => { + this.fire(new CustomEvent('dragend', event)); + }); - this.updateViewAndVM(i === 0); + this.dorsal.draggable().on('dragstart', (event: CustomEvent) => { + this.fire(new CustomEvent('dragstart', event)); + }).on('dragmove', (event: CustomEvent) => { + this.cuboidModel.dorsal.points = parsePoints(this.dorsal.attr('points')); + this.updateViewAndVM(); - this.fire(new CustomEvent('dragmove', event)); - }).on('dragend', (event: CustomEvent) => { + this.fire(new CustomEvent('dragmove', event)); + }).on('dragend', (event: CustomEvent) => { + this.fire(new CustomEvent('dragend', event)); + }); + + this.right.draggable((x: number, y: number) => ({ + x: x > Math.min(cuboidModel.dl.points[0].x, + cuboidModel.fl.points[0].x) + consts.MIN_EDGE_LENGTH, y + })).on('dragstart', (event: CustomEvent) => { + this.fire(new CustomEvent('dragstart', event)); + }).on('dragmove', (event: CustomEvent) => { + this.cuboidModel.right.points = parsePoints(this.right.attr('points')); + this.updateViewAndVM(true); + + this.fire(new CustomEvent('dragmove', event)); + }).on('dragend', (event: CustomEvent) => { + this.fire(new CustomEvent('dragend', event)); + }); - this.fire(new CustomEvent('dragend', event)); - }) - }) return this; }, @@ -591,9 +613,22 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { attr(a: any, v: any, n: any) { const _attr = SVG.Element.prototype.attr.bind(this); - if (a === 'fill' && v !== undefined) { + if ((a === 'fill' || a === 'stroke') && v !== undefined) { _attr(a, v, n); this.paintOrientationLines(); + } else if (a === 'projections') { + _attr(a, v, n) + if (v === true) { + this.ftProj.show(); + this.fbProj.show(); + this.rtProj.show(); + this.rbProj.show(); + } else { + this.ftProj.hide(); + this.fbProj.hide(); + this.rtProj.hide(); + this.rbProj.hide(); + } } else if (a === 'stroke-width' && typeof v === "number") { _attr(a, v, n); this.updateThickness(); @@ -625,21 +660,26 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { paintOrientationLines() { const fillColor = this.attr('fill'); + const strokeColor = this.attr('stroke'); const selectedColor = '#ff007f'; this.frontTopEdge.stroke({ color: selectedColor }); this.frontLeftEdge.stroke({ color: selectedColor }); this.frontBotEdge.stroke({ color: selectedColor }); this.frontRightEdge.stroke({ color: selectedColor }); - this.rightTopEdge.stroke({ color: fillColor }); - this.rightBotEdge.stroke({ color: fillColor }); - this.dorsalRightEdge.stroke({ color: fillColor }); - this.dorsalLeftEdge.stroke({ color: fillColor }); - - this.face.stroke({ color: fillColor, width: 0 }); - this.right.stroke({ color: fillColor }); - this.dorsal.stroke({ color: fillColor }); - this.left.stroke({ color: fillColor }); + this.rightTopEdge.stroke({ color: strokeColor }); + this.rightBotEdge.stroke({ color: strokeColor }); + this.dorsalRightEdge.stroke({ color: strokeColor }); + this.dorsalLeftEdge.stroke({ color: strokeColor }); + + this.face.stroke({ color: strokeColor, width: 0 }) + .fill({ color: fillColor }); + this.right.stroke({ color: strokeColor }) + .fill({ color: fillColor }); + this.dorsal.stroke({ color: strokeColor }) + .fill({ color: fillColor }); + this.left.stroke({ color: strokeColor }) + .fill({ color: fillColor }); }, dmove(dx: number, dy: number) { @@ -688,15 +728,6 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { this.cuboidModel.rt.points[1], this.cuboidModel.vpr)); }, - updateGrabPoints() { - const centers = this.getGrabPoints(); - const edges = this.getEdges(); - for (let i = 0; i < centers.length; i += 1) { - const edge = edges[i]; - centers[i].center(edge.cx(), edge.cy()); - } - }, - computeHeightFace(point: Point, index: number) { switch (index) { // fl diff --git a/cvat-ui/src/actions/settings-actions.ts b/cvat-ui/src/actions/settings-actions.ts index f06b0fb0eb6b..d149521d4934 100644 --- a/cvat-ui/src/actions/settings-actions.ts +++ b/cvat-ui/src/actions/settings-actions.ts @@ -18,6 +18,7 @@ export enum SettingsActionTypes { CHANGE_SELECTED_SHAPES_OPACITY = 'CHANGE_SELECTED_SHAPES_OPACITY', CHANGE_SHAPES_COLOR_BY = 'CHANGE_SHAPES_COLOR_BY', CHANGE_SHAPES_BLACK_BORDERS = 'CHANGE_SHAPES_BLACK_BORDERS', + CHANGE_SHAPES_SHOW_PROJECTIONS = 'CHANGE_SHAPES_SHOW_PROJECTIONS', CHANGE_SHOW_UNLABELED_REGIONS = 'CHANGE_SHOW_UNLABELED_REGIONS', CHANGE_FRAME_STEP = 'CHANGE_FRAME_STEP', CHANGE_FRAME_SPEED = 'CHANGE_FRAME_SPEED', @@ -77,6 +78,15 @@ export function changeShowBitmap(showBitmap: boolean): AnyAction { }; } +export function changeShowProjections(showProjections: boolean): AnyAction { + return { + type: SettingsActionTypes.CHANGE_SHAPES_SHOW_PROJECTIONS, + payload: { + showProjections, + }, + }; +} + export function switchRotateAll(rotateAll: boolean): AnyAction { return { type: SettingsActionTypes.SWITCH_ROTATE_ALL, diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx index 8296f287b1f8..dd2fa4393b56 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx @@ -44,6 +44,7 @@ interface Props { selectedOpacity: number; blackBorders: boolean; showBitmap: boolean; + showProjections: boolean; grid: boolean; gridSize: number; gridColor: GridColor; @@ -139,12 +140,15 @@ export default class CanvasWrapperComponent extends React.PureComponent { workspace, frameFetching, showObjectsTextAlways, + showProjections, } = this.props; - if (prevProps.showObjectsTextAlways !== showObjectsTextAlways) { + if (prevProps.showObjectsTextAlways !== showObjectsTextAlways + || prevProps.showProjections !== showProjections) { canvasInstance.configure({ undefinedAttrValue: consts.UNDEFINED_ATTRIBUTE_VALUE, displayAllText: showObjectsTextAlways, + showProjections, }); } @@ -534,7 +538,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { } = this.props; const [state] = annotations.filter((el: any) => (el.clientID === activatedStateID)); - if (state.shapeType !== ShapeType.RECTANGLE) { + if (![ShapeType.CUBOID, ShapeType.RECTANGLE].includes(state.shapeType)) { onUpdateContextMenu(activatedStateID !== null, e.detail.mouseEvent.clientX, e.detail.mouseEvent.clientY, ContextMenuType.CANVAS_SHAPE_POINT, e.detail.pointID); } diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/appearance-block.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/appearance-block.tsx index d3ed0f03d4d3..11c161b911e6 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/appearance-block.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/appearance-block.tsx @@ -25,6 +25,7 @@ interface Props { selectedOpacity: number; blackBorders: boolean; showBitmap: boolean; + showProjections: boolean; collapseAppearance(): void; changeShapesColorBy(event: RadioChangeEvent): void; @@ -32,6 +33,7 @@ interface Props { changeSelectedShapesOpacity(event: SliderValue): void; changeShapesBlackBorders(event: CheckboxChangeEvent): void; changeShowBitmap(event: CheckboxChangeEvent): void; + changeShowProjections(event: CheckboxChangeEvent): void; } function AppearanceBlock(props: Props): JSX.Element { @@ -42,12 +44,14 @@ function AppearanceBlock(props: Props): JSX.Element { selectedOpacity, blackBorders, showBitmap, + showProjections, collapseAppearance, changeShapesColorBy, changeShapesOpacity, changeSelectedShapesOpacity, changeShapesBlackBorders, changeShowBitmap, + changeShowProjections, } = props; return ( @@ -95,6 +99,12 @@ function AppearanceBlock(props: Props): JSX.Element { > Show bitmap + + Show projections + diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar.tsx index b173d870e9e1..51d5e20fa0ae 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/objects-side-bar/objects-side-bar.tsx @@ -30,6 +30,7 @@ interface Props { selectedOpacity: number; blackBorders: boolean; showBitmap: boolean; + showProjections: boolean; collapseSidebar(): void; collapseAppearance(): void; @@ -39,6 +40,7 @@ interface Props { changeSelectedShapesOpacity(event: SliderValue): void; changeShapesBlackBorders(event: CheckboxChangeEvent): void; changeShowBitmap(event: CheckboxChangeEvent): void; + changeShowProjections(event: CheckboxChangeEvent): void; } function ObjectsSideBar(props: Props): JSX.Element { @@ -50,6 +52,7 @@ function ObjectsSideBar(props: Props): JSX.Element { selectedOpacity, blackBorders, showBitmap, + showProjections, collapseSidebar, collapseAppearance, changeShapesColorBy, @@ -57,6 +60,7 @@ function ObjectsSideBar(props: Props): JSX.Element { changeSelectedShapesOpacity, changeShapesBlackBorders, changeShowBitmap, + changeShowProjections, } = props; const appearanceProps = { @@ -67,12 +71,14 @@ function ObjectsSideBar(props: Props): JSX.Element { selectedOpacity, blackBorders, showBitmap, + showProjections, changeShapesColorBy, changeShapesOpacity, changeSelectedShapesOpacity, changeShapesBlackBorders, changeShowBitmap, + changeShowProjections, }; return ( diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx index 12501c44432e..5fe9ac7ce7c8 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/canvas-wrapper.tsx @@ -63,6 +63,7 @@ interface StateToProps { selectedOpacity: number; blackBorders: boolean; showBitmap: boolean; + showProjections: boolean; grid: boolean; gridSize: number; gridColor: GridColor; @@ -173,6 +174,7 @@ function mapStateToProps(state: CombinedState): StateToProps { selectedOpacity, blackBorders, showBitmap, + showProjections, }, }, shortcuts: { @@ -197,6 +199,7 @@ function mapStateToProps(state: CombinedState): StateToProps { selectedOpacity, blackBorders, showBitmap, + showProjections, grid, gridSize, gridColor, diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/objects-side-bar.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/objects-side-bar.tsx index 309700cc715f..906e0fc2835f 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/objects-side-bar.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/objects-side-bar/objects-side-bar.tsx @@ -28,6 +28,7 @@ import { changeSelectedShapesOpacity as changeSelectedShapesOpacityAction, changeShapesBlackBorders as changeShapesBlackBordersAction, changeShowBitmap as changeShowUnlabeledRegionsAction, + changeShowProjections as changeShowProjectionsAction, } from 'actions/settings-actions'; @@ -39,6 +40,7 @@ interface StateToProps { selectedOpacity: number; blackBorders: boolean; showBitmap: boolean; + showProjections: boolean; } interface DispatchToProps { @@ -50,6 +52,7 @@ interface DispatchToProps { changeSelectedShapesOpacity(selectedShapesOpacity: number): void; changeShapesBlackBorders(blackBorders: boolean): void; changeShowBitmap(showBitmap: boolean): void; + changeShowProjections(showProjections: boolean): void; } function mapStateToProps(state: CombinedState): StateToProps { @@ -65,6 +68,7 @@ function mapStateToProps(state: CombinedState): StateToProps { selectedOpacity, blackBorders, showBitmap, + showProjections, }, }, } = state; @@ -77,6 +81,7 @@ function mapStateToProps(state: CombinedState): StateToProps { selectedOpacity, blackBorders, showBitmap, + showProjections, }; } @@ -140,6 +145,9 @@ function mapDispatchToProps(dispatch: any): DispatchToProps { changeShowBitmap(showBitmap: boolean) { dispatch(changeShowUnlabeledRegionsAction(showBitmap)); }, + changeShowProjections(showProjections: boolean) { + dispatch(changeShowProjectionsAction(showProjections)); + }, }; } @@ -190,6 +198,11 @@ class ObjectsSideBarContainer extends React.PureComponent { changeShowBitmap(event.target.checked); }; + private changeShowProjections = (event: CheckboxChangeEvent): void => { + const { changeShowProjections } = this.props; + changeShowProjections(event.target.checked); + }; + public render(): JSX.Element { const { sidebarCollapsed, @@ -199,6 +212,7 @@ class ObjectsSideBarContainer extends React.PureComponent { selectedOpacity, blackBorders, showBitmap, + showProjections, collapseSidebar, collapseAppearance, } = this.props; @@ -212,6 +226,7 @@ class ObjectsSideBarContainer extends React.PureComponent { selectedOpacity={selectedOpacity} blackBorders={blackBorders} showBitmap={showBitmap} + showProjections={showProjections} collapseSidebar={collapseSidebar} collapseAppearance={collapseAppearance} changeShapesColorBy={this.changeShapesColorBy} @@ -219,6 +234,7 @@ class ObjectsSideBarContainer extends React.PureComponent { changeSelectedShapesOpacity={this.changeSelectedShapesOpacity} changeShapesBlackBorders={this.changeShapesBlackBorders} changeShowBitmap={this.changeShowBitmap} + changeShowProjections={this.changeShowProjections} /> ); } diff --git a/cvat-ui/src/reducers/interfaces.ts b/cvat-ui/src/reducers/interfaces.ts index f0435498dba1..0c599a0fc11a 100644 --- a/cvat-ui/src/reducers/interfaces.ts +++ b/cvat-ui/src/reducers/interfaces.ts @@ -438,6 +438,7 @@ export interface ShapesSettingsState { selectedOpacity: number; blackBorders: boolean; showBitmap: boolean; + showProjections: boolean; } export interface SettingsState { diff --git a/cvat-ui/src/reducers/settings-reducer.ts b/cvat-ui/src/reducers/settings-reducer.ts index 00cc552767cf..4ccd386fac03 100644 --- a/cvat-ui/src/reducers/settings-reducer.ts +++ b/cvat-ui/src/reducers/settings-reducer.ts @@ -23,6 +23,7 @@ const defaultState: SettingsState = { selectedOpacity: 30, blackBorders: false, showBitmap: false, + showProjections: false, }, workspace: { autoSave: false, @@ -129,6 +130,15 @@ export default (state = defaultState, action: AnyAction): SettingsState => { }, }; } + case SettingsActionTypes.CHANGE_SHAPES_SHOW_PROJECTIONS: { + return { + ...state, + shapes: { + ...state.shapes, + showProjections: action.payload.showProjections, + }, + }; + } case SettingsActionTypes.CHANGE_SHOW_UNLABELED_REGIONS: { return { ...state, From 6b8290eb80706110b8af65000394dd800a29b01b Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Wed, 22 Apr 2020 19:58:29 +0300 Subject: [PATCH 26/45] added cuboid copy/paste --- cvat-canvas/src/typescript/drawHandler.ts | 11 ++++++++++ cvat-canvas/src/typescript/svg.patch.ts | 25 ++++++++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/cvat-canvas/src/typescript/drawHandler.ts b/cvat-canvas/src/typescript/drawHandler.ts index 428a26ce772c..107d491b7b8a 100644 --- a/cvat-canvas/src/typescript/drawHandler.ts +++ b/cvat-canvas/src/typescript/drawHandler.ts @@ -466,6 +466,15 @@ export class DrawHandlerImpl implements DrawHandler { this.pastePolyshape(); } + private pasteCuboid(points: string): void { + this.drawInstance = (this.canvas as any).cube(points).addClass('cvat_canvas_shape_drawing').attr({ + 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, + 'face-stroke': 'black', + }); + this.pasteShape(); + this.pastePolyshape(); + } + private pastePoints(initialPoints: string): void { function moveShape( shape: SVG.PolyLine, @@ -597,6 +606,8 @@ export class DrawHandlerImpl implements DrawHandler { this.pastePolyline(stringifiedPoints); } else if (this.drawData.shapeType === 'points') { this.pastePoints(stringifiedPoints); + } else if (this.drawData.shapeType === 'cuboid') { + this.pasteCuboid(stringifiedPoints); } } this.setupPasteEvents(); diff --git a/cvat-canvas/src/typescript/svg.patch.ts b/cvat-canvas/src/typescript/svg.patch.ts index 0d0899fbf8e5..2fe68a789bee 100644 --- a/cvat-canvas/src/typescript/svg.patch.ts +++ b/cvat-canvas/src/typescript/svg.patch.ts @@ -632,6 +632,9 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { } else if (a === 'stroke-width' && typeof v === "number") { _attr(a, v, n); this.updateThickness(); + } else if (a === 'face-stroke' && v !== undefined) { + _attr(a,v,n); + this.paintOrientationLines() } else { return _attr(a, v, n); } @@ -661,7 +664,7 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { paintOrientationLines() { const fillColor = this.attr('fill'); const strokeColor = this.attr('stroke'); - const selectedColor = '#ff007f'; + const selectedColor = this.attr('face-stroke') || '#ff007f'; this.frontTopEdge.stroke({ color: selectedColor }); this.frontLeftEdge.stroke({ color: selectedColor }); this.frontBotEdge.stroke({ color: selectedColor }); @@ -691,6 +694,26 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { this.updateViewAndVM(); }, + x(x?: number) { + if (typeof x === 'number') { + const { x: xInitial } = this.bbox(); + this.dmove(x - xInitial, 0); + return this; + } else { + return this.bbox().x; + } + }, + + y(y?: number) { + if (typeof y === 'number') { + const { y: yInitial } = this.bbox(); + this.dmove(0, y - yInitial); + return this; + } else { + return this.bbox().y; + } + }, + updateViewAndVM(build: boolean) { this.cuboidModel.updateOrientation(); this.cuboidModel.buildBackEdge(build); From a2a845a732087f6dc361785588ef0376a52508f8 Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Thu, 23 Apr 2020 00:44:10 +0300 Subject: [PATCH 27/45] fixed cuboid undo/redo --- cvat-canvas/src/typescript/canvasView.ts | 6 ++++- cvat-canvas/src/typescript/cuboid.ts | 9 +++++-- cvat-canvas/src/typescript/svg.patch.ts | 32 ++++++++++++------------ 3 files changed, 28 insertions(+), 19 deletions(-) diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index e08c88491b4a..f62b166f72f2 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -1075,7 +1075,9 @@ export class CanvasViewImpl implements CanvasView, Listener { return `${acc}${val},`; }, '', ); - (shape as any).clear(); + if (state.shapeType !== 'cuboid') { + (shape as any).clear(); + } shape.attr('points', stringified); if (state.shapeType === 'points') { @@ -1592,6 +1594,8 @@ export class CanvasViewImpl implements CanvasView, Listener { 'data-z-order': state.zOrder, }); + (window as any).cube = cube; + return cube; } diff --git a/cvat-canvas/src/typescript/cuboid.ts b/cvat-canvas/src/typescript/cuboid.ts index 24901f1adfdd..7bf04777841e 100644 --- a/cvat-canvas/src/typescript/cuboid.ts +++ b/cvat-canvas/src/typescript/cuboid.ts @@ -141,8 +141,13 @@ export class CuboidModel { return this.points; } - public setPoints(points: Point[]): void { - this.points = points; + public setPoints(points: (Point | null)[]): void { + points.forEach((point: Point | null, i: number): void => { + if (point !== null) { + this.points[i].x = point.x; + this.points[i].y = point.y; + } + }); } public updateOrientation(): void { diff --git a/cvat-canvas/src/typescript/svg.patch.ts b/cvat-canvas/src/typescript/svg.patch.ts index 2fe68a789bee..1cd794a3ee81 100644 --- a/cvat-canvas/src/typescript/svg.patch.ts +++ b/cvat-canvas/src/typescript/svg.patch.ts @@ -238,7 +238,7 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { this.setupProjections(); this.hideProjections(); - this.attr('points', points); + this._attr('points', points); return this; }, @@ -612,12 +612,16 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { _attr: SVG.Element.prototype.attr, attr(a: any, v: any, n: any) { - const _attr = SVG.Element.prototype.attr.bind(this); - if ((a === 'fill' || a === 'stroke') && v !== undefined) { - _attr(a, v, n); + if ((a === 'fill' || a === 'stroke' || a === 'face-stroke') + && v !== undefined) { + this._attr(a, v, n); this.paintOrientationLines(); + } else if (a === 'points' && typeof v === 'string') { + const points = parsePoints(v); + this.cuboidModel.setPoints(points); + this.updateViewAndVM(); } else if (a === 'projections') { - _attr(a, v, n) + this._attr(a, v, n); if (v === true) { this.ftProj.show(); this.fbProj.show(); @@ -630,13 +634,10 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { this.rbProj.hide(); } } else if (a === 'stroke-width' && typeof v === "number") { - _attr(a, v, n); + this._attr(a, v, n); this.updateThickness(); - } else if (a === 'face-stroke' && v !== undefined) { - _attr(a,v,n); - this.paintOrientationLines() } else { - return _attr(a, v, n); + return this._attr(a ,v, n); } return this; @@ -718,7 +719,11 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { this.cuboidModel.updateOrientation(); this.cuboidModel.buildBackEdge(build); this.updateView(); - this._attr('points', stringifyPoints(this.cuboidModel.points)); + + // to correct getting of points in resizedone, dragdone + this._attr('points', this.cuboidModel + .getPoints() + .reduce((acc: string, point: Point): string => `${acc} ${point.x},${point.y}`, '').trim()); }, updatePolygons() { @@ -798,11 +803,6 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { this.updateFaces(); this.updateEdges(); this.updateProjections(); - - // to correct getting of points in resizedone, dragdone - this.attr('points', this.cuboidModel - .getPoints() - .reduce((acc: string, point: Point): string => `${acc} ${point.x},${point.y}`, '').trim()); }, updateFaces() { From ad600cea523d98eccf4b59f3737f5159b3cc6b72 Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Thu, 23 Apr 2020 17:55:09 +0300 Subject: [PATCH 28/45] fixed cuboid merge and cuboid partly outside --- cvat-canvas/src/typescript/drawHandler.ts | 84 ++++++++++++++++++++++- cvat-core/src/annotations-collection.js | 4 ++ cvat-core/src/annotations-objects.js | 59 ++++++++++++---- 3 files changed, 132 insertions(+), 15 deletions(-) diff --git a/cvat-canvas/src/typescript/drawHandler.ts b/cvat-canvas/src/typescript/drawHandler.ts index 107d491b7b8a..d3da98fd3e1f 100644 --- a/cvat-canvas/src/typescript/drawHandler.ts +++ b/cvat-canvas/src/typescript/drawHandler.ts @@ -103,6 +103,84 @@ export class DrawHandlerImpl implements DrawHandler { }; } + private getFinalCuboidCoordinates(targetPoints: number[]): { + points: number[]; + box: Box; + } { + const { offset } = this.geometry; + let points = targetPoints; + + const box = { + xtl: 0, + ytl: 0, + xbr: Number.MAX_SAFE_INTEGER, + ybr: Number.MAX_SAFE_INTEGER, + }; + + const frameWidth = this.geometry.image.width; + const frameHeight = this.geometry.image.height; + + const cuboidOffsets = []; + const minCuboidOffset = { + d: Number.MAX_SAFE_INTEGER, + dx: 0, + dy: 0, + }; + + for (let i = 0; i < points.length - 1; i += 2) { + const [x, y] = points.slice(i); + + if (x >= offset && x <= offset + frameWidth + && y >= offset && y <= offset + frameHeight) continue; + + let xOffset = 0; + let yOffset = 0; + + if (x < offset) { + xOffset = offset - x; + } else if (x > offset + frameWidth) { + xOffset = offset + frameWidth - x; + } + + if (y < offset) { + yOffset = offset - y; + } else if (y > offset + frameHeight) { + yOffset = offset + frameHeight - y; + } + + cuboidOffsets.push([xOffset, yOffset]); + } + + if (cuboidOffsets.length === points.length / 2) { + cuboidOffsets.forEach((offsetCoords: number[]): void => { + if (Math.sqrt((offsetCoords[0] ** 2) + (offsetCoords[1] ** 2)) + < minCuboidOffset.d) { + minCuboidOffset.d = Math.sqrt((offsetCoords[0] ** 2) + (offsetCoords[1] ** 2)); + [minCuboidOffset.dx, minCuboidOffset.dy] = offsetCoords; + } + }); + + points = points.map((coord: number, i: number): number => { + const finalCoord = coord + (i % 2 === 0 ? minCuboidOffset.dx : minCuboidOffset.dy); + + if (i % 2 === 0) { + box.xtl = Math.max(box.xtl, finalCoord); + box.xbr = Math.min(box.xbr, finalCoord); + } else { + box.ytl = Math.max(box.ytl, finalCoord); + box.ybr = Math.min(box.ybr, finalCoord); + } + + return finalCoord; + }); + } + + return { + points: points.map((coord: number): number => coord - offset), + box, + }; + } + private addCrosshair(): void { const { x, y } = this.cursorPosition; this.crosshair = { @@ -312,8 +390,9 @@ export class DrawHandlerImpl implements DrawHandler { this.drawInstance.on('drawdone', (e: CustomEvent): void => { const targetPoints = pointsToArray((e.target as SVGElement).getAttribute('points')); - const { points, box } = this.getFinalPolyshapeCoordinates(targetPoints); const { shapeType } = this.drawData; + const { points, box } = shapeType === 'cuboid' ? this.getFinalCuboidCoordinates(targetPoints) + : this.getFinalPolyshapeCoordinates(targetPoints); this.release(); if (this.canceled) return; @@ -393,7 +472,8 @@ export class DrawHandlerImpl implements DrawHandler { .split(/[,\s]/g) .map((coord: string): number => +coord); - const { points } = this.getFinalPolyshapeCoordinates(targetPoints); + const { points } = this.drawData.initialState.shapeType === 'cuboid' ? this.getFinalCuboidCoordinates(targetPoints) + : this.getFinalPolyshapeCoordinates(targetPoints); this.release(); this.onDrawDone({ shapeType: this.drawData.initialState.shapeType, diff --git a/cvat-core/src/annotations-collection.js b/cvat-core/src/annotations-collection.js index 136cad5615b1..411d277caafe 100644 --- a/cvat-core/src/annotations-collection.js +++ b/cvat-core/src/annotations-collection.js @@ -18,6 +18,7 @@ PolygonTrack, PolylineTrack, PointsTrack, + CuboidTrack, Track, Shape, Tag, @@ -91,6 +92,9 @@ case 'points': trackModel = new PointsTrack(trackData, clientID, color, injection); break; + case 'cuboid': + trackModel = new CuboidTrack(trackData, clientID, color, injection); + break; default: throw new DataError( `An unexpected type of track "${type}"`, diff --git a/cvat-core/src/annotations-objects.js b/cvat-core/src/annotations-objects.js index d3640e3412da..501b85ab745a 100644 --- a/cvat-core/src/annotations-objects.js +++ b/cvat-core/src/annotations-objects.js @@ -115,6 +115,35 @@ return area >= MIN_SHAPE_AREA; } + function fitPoints(shapeType, points, maxX, maxY) { + const fittedPoints = []; + + for (let i = 0; i < points.length - 1; i += 2) { + const x = points[i]; + const y = points[i + 1]; + + checkObjectType('coordinate', x, 'number', null); + checkObjectType('coordinate', y, 'number', null); + + fittedPoints.push( + Math.clamp(x, 0, maxX), + Math.clamp(y, 0, maxY), + ); + } + + return shapeType === ObjectShape.CUBOID ? points : fittedPoints; + } + + function checkOutside(points, width, height) { + let inside = false; + for (let i = 0; i < points.length - 1; i += 2) { + const [x, y] = points.slice(i); + inside = inside || (x >= 0 && x <= width && y >= 0 && y <= height); + } + + return !inside; + } + function validateAttributeValue(value, attr) { const { values } = attr; const type = attr.inputType; @@ -292,20 +321,9 @@ checkNumberOfPoints(this.shapeType, data.points); // cut points const { width, height } = this.frameMeta[frame]; - for (let i = 0; i < data.points.length - 1; i += 2) { - const x = data.points[i]; - const y = data.points[i + 1]; - - checkObjectType('coordinate', x, 'number', null); - checkObjectType('coordinate', y, 'number', null); + fittedPoints = fitPoints(this.shapeType, data.points, width, height); - fittedPoints.push( - Math.clamp(x, 0, width), - Math.clamp(y, 0, height), - ); - } - - if (!checkShapeArea(this.shapeType, fittedPoints)) { + if ((!checkShapeArea(this.shapeType, fittedPoints)) || checkOutside(fittedPoints, width, height)) { fittedPoints = []; } } @@ -1361,6 +1379,7 @@ constructor(data, clientID, color, injection) { super(data, clientID, color, injection); this.shapeType = ObjectShape.CUBOID; + checkNumberOfPoints(this.shapeType, this.points); } static makeHull(geoPoints) { @@ -1925,10 +1944,23 @@ } } + class CuboidTrack extends PolyTrack { + constructor(data, clientID, color, injection) { + super(data, clientID, color, injection); + this.shapeType = ObjectShape.CUBOID; + for (const shape of Object.values(this.shapes)) { + checkNumberOfPoints(this.shapeType, shape.points); + } + } + + // TODO: interpolation; + } + RectangleTrack.distance = RectangleShape.distance; PolygonTrack.distance = PolygonShape.distance; PolylineTrack.distance = PolylineShape.distance; PointsTrack.distance = PointsShape.distance; + CuboidTrack.distance = CuboidShape.distance; module.exports = { RectangleShape, @@ -1940,6 +1972,7 @@ PolygonTrack, PolylineTrack, PointsTrack, + CuboidTrack, Track, Shape, Tag, From 379dfd386cd5bc5f2f5721415c8e9afdb114fa8c Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Mon, 27 Apr 2020 19:39:54 +0300 Subject: [PATCH 29/45] added cuboid edge resize points --- cvat-canvas/src/typescript/svg.patch.ts | 248 +++++++++++++++++++++--- 1 file changed, 218 insertions(+), 30 deletions(-) diff --git a/cvat-canvas/src/typescript/svg.patch.ts b/cvat-canvas/src/typescript/svg.patch.ts index 1cd794a3ee81..5fc984bf710c 100644 --- a/cvat-canvas/src/typescript/svg.patch.ts +++ b/cvat-canvas/src/typescript/svg.patch.ts @@ -277,6 +277,36 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { this.rightBotEdge = this.line(this.cuboidModel.rb.points); }, + setupGrabPoints(circleType) { + const viewModel = this.cuboidModel; + const circle = typeof circleType === 'function' ? circleType : this.circle; + + this.flCenter = circle(0, 0).addClass('svg_select_points').addClass('svg_select_points_l'); + this.frCenter = circle(0, 0).addClass('svg_select_points').addClass('svg_select_points_r'); + this.ftCenter = circle(0, 0).addClass('svg_select_points').addClass('svg_select_points_t'); + this.fbCenter = circle(0, 0).addClass('svg_select_points').addClass('svg_select_points_b'); + + this.drCenter = circle(0, 0).addClass('svg_select_points').addClass('svg_select_points_ew'); + this.dlCenter = circle(0, 0).addClass('svg_select_points').addClass('svg_select_points_ew'); + + const grabPoints = this.getGrabPoints(); + const edges = this.getEdges(); + for (let i = 0; i < grabPoints.length; i += 1) { + const edge = edges[i]; + const cx = (edge.attr('x2') + edge.attr('x1')) / 2; + const cy = (edge.attr('y2') + edge.attr('y1')) / 2; + grabPoints[i].center(cx, cy); + } + + if (viewModel.orientation === Orientation.LEFT) { + this.dlCenter.hide(); + } else { + this.drCenter.hide(); + } + + + }, + showProjections() { if (this.projectionLineEnable) { this.ftProj.show(); @@ -306,6 +336,17 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { return arr; }, + getGrabPoints() { + const arr = []; + arr.push(this.flCenter); + arr.push(this.frCenter); + arr.push(this.drCenter); + arr.push(this.ftCenter); + arr.push(this.fbCenter); + arr.push(this.dlCenter); + return arr; + }, + updateProjectionLine(equation: Equation, source: Point, direction: Point) { const x1 = source.x; const y1 = equation.getY(x1); @@ -326,6 +367,14 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { this.dorsalLeftEdge.selectize(value, options); } + if (value === false) { + this.getGrabPoints().forEach((point) => {point.remove()}); + } else { + this.setupGrabPoints(this.face.remember('_selectHandler').drawPoint.bind( + {nested: this, options: this.face.remember('_selectHandler').options} + )); + } + return this; }, @@ -338,6 +387,13 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { this.face.off('resizing').off('resizedone').off('resizestart'); this.dorsalRightEdge.off('resizing').off('resizedone').off('resizestart'); this.dorsalLeftEdge.off('resizing').off('resizedone').off('resizestart'); + + this.getGrabPoints().forEach((point: SVG.Element) => { + point.off('dragstart'); + point.off('dragmove'); + point.off('dragend'); + }) + return; } @@ -525,6 +581,145 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { this.dorsalLeftEdge.resize(value); setupDorsalEdge.call(this, this.dorsalLeftEdge, this.cuboidModel.orientation); } + + function horizontalEdgeControl(updatingFace, midX, midY) { + const leftPoints = this.updatedEdge( + this.cuboidModel.fl.points[0], + {x: midX, y: midY}, + this.cuboidModel.vpl, + ); + const rightPoints = this.updatedEdge( + this.cuboidModel.dr.points[0], + {x: midX, y: midY}, + this.cuboidModel.vpr, + ); + + updatingFace.points = [leftPoints, {x: midX, y: midY}, rightPoints, null]; + } + + + this.drCenter.draggable((x: number) => { + let xStatus; + if (this.drCenter.cx() < this.cuboidModel.fr.points[0].x) { + xStatus = x < this.cuboidModel.fr.points[0].x - consts.MIN_EDGE_LENGTH + && x > this.cuboidModel.vpr.x + consts.MIN_EDGE_LENGTH; + } else { + xStatus = x > this.cuboidModel.fr.points[0].x + consts.MIN_EDGE_LENGTH + && x < this.cuboidModel.vpr.x - consts.MIN_EDGE_LENGTH; + } + return { x: xStatus, y: this.drCenter.attr('y1') }; + }).on('dragstart', ((event: CustomEvent) => { + this.fire(new CustomEvent('resizestart', event)); + })).on('dragmove', (event: CustomEvent) => { + this.dorsalRightEdge.center(this.drCenter.cx(), this.drCenter.cy()); + + const x = this.dorsalRightEdge.attr('x1'); + const y1 = this.cuboidModel.rt.getEquation().getY(x); + const y2 = this.cuboidModel.rb.getEquation().getY(x); + const topPoint = { x, y: y1 }; + const botPoint = { x, y: y2 }; + + this.cuboidModel.dr.points = [topPoint, botPoint]; + this.updateViewAndVM(); + }).on('dragend', (event: CustomEvent) => { + this.fire(new CustomEvent('resizedone', event)); + }); + + this.dlCenter.draggable((x: number) => { + let xStatus; + if (this.dlCenter.cx() < this.cuboidModel.fl.points[0].x) { + xStatus = x < this.cuboidModel.fl.points[0].x - consts.MIN_EDGE_LENGTH + && x > this.cuboidModel.vpr.x + consts.MIN_EDGE_LENGTH; + } else { + xStatus = x > this.cuboidModel.fl.points[0].x + consts.MIN_EDGE_LENGTH + && x < this.cuboidModel.vpr.x - consts.MIN_EDGE_LENGTH; + } + return { x: xStatus, y: this.dlCenter.attr('y1') }; + }).on('dragstart', ((event: CustomEvent) => { + this.fire(new CustomEvent('resizestart', event)); + })).on('dragmove', (event: CustomEvent) => { + this.dorsalLeftEdge.center(this.dlCenter.cx(), this.dlCenter.cy()); + + const x = this.dorsalLeftEdge.attr('x1'); + const y1 = this.cuboidModel.lt.getEquation().getY(x); + const y2 = this.cuboidModel.lb.getEquation().getY(x); + const topPoint = { x, y: y1 }; + const botPoint = { x, y: y2 }; + + this.cuboidModel.dl.points = [topPoint, botPoint]; + this.updateViewAndVM(true); + }).on('dragend', (event: CustomEvent) => { + this.fire(new CustomEvent('resizedone', event)); + });; + + this.flCenter.draggable((x: number) => { + const vpX = this.flCenter.cx() - this.cuboidModel.vpl.x > 0 ? this.cuboidModel.vpl.x : 0; + return { x: x < this.cuboidModel.fr.points[0].x && x > vpX + consts.MIN_EDGE_LENGTH }; + }).on('dragstart', ((event: CustomEvent) => { + this.fire(new CustomEvent('resizestart', event)); + })).on('dragmove', (event: CustomEvent) => { + this.frontLeftEdge.center(this.flCenter.cx(), this.flCenter.cy()); + + const x = this.frontLeftEdge.attr('x1'); + const y1 = this.cuboidModel.ft.getEquation().getY(x); + const y2 = this.cuboidModel.fb.getEquation().getY(x); + const topPoint = { x, y: y1 }; + const botPoint = { x, y: y2 }; + + this.cuboidModel.fl.points = [topPoint, botPoint]; + this.updateViewAndVM(); + this.fire(new CustomEvent('resizing', event)); + }).on('dragend', (event: CustomEvent) => { + this.fire(new CustomEvent('resizedone', event)); + }); + + this.frCenter.draggable((x: number) => { + return { x: x > this.cuboidModel.fl.points[0].x, y: this.frCenter.attr('y1') }; + }).on('dragstart', ((event: CustomEvent) => { + this.fire(new CustomEvent('resizestart', event)); + })).on('dragmove', (event: CustomEvent) => { + this.frontRightEdge.center(this.frCenter.cx(), this.frCenter.cy()); + + const x = this.frontRightEdge.attr('x1'); + const y1 = this.cuboidModel.ft.getEquation().getY(x); + const y2 = this.cuboidModel.fb.getEquation().getY(x); + const topPoint = { x, y: y1 }; + const botPoint = { x, y: y2 }; + + this.cuboidModel.fr.points = [topPoint, botPoint]; + this.updateViewAndVM(true); + this.fire(new CustomEvent('resizing', event)); + }).on('dragend', (event: CustomEvent) => { + this.fire(new CustomEvent('resizedone', event)); + }); + + this.ftCenter.draggable((x: number, y: number) => { + return { x: x === this.ftCenter.cx(), y: y < this.fbCenter.cy() - consts.MIN_EDGE_LENGTH }; + }).on('dragstart', ((event: CustomEvent) => { + this.fire(new CustomEvent('resizestart', event)); + })).on('dragmove', (event: CustomEvent) => { + this.frontTopEdge.center(this.ftCenter.cx(), this.ftCenter.cy()); + horizontalEdgeControl.call(this, this.cuboidModel.top, this.frontTopEdge.attr('x2'), this.frontTopEdge.attr('y2')); + this.updateViewAndVM(); + this.fire(new CustomEvent('resizing', event)); + }).on('dragend', (event: CustomEvent) => { + this.fire(new CustomEvent('resizedone', event)); + }); + + this.fbCenter.draggable((x: number, y: number) => { + return { x: x === this.fbCenter.cx(), y: y > this.ftCenter.cy() + consts.MIN_EDGE_LENGTH }; + }).on('dragstart', ((event: CustomEvent) => { + this.fire(new CustomEvent('resizestart', event)); + })).on('dragmove', (event: CustomEvent) => { + this.frontBotEdge.center(this.fbCenter.cx(), this.fbCenter.cy()); + horizontalEdgeControl.call(this, this.cuboidModel.bot, this.frontBotEdge.attr('x2'), this.frontBotEdge.attr('y2')); + this.updateViewAndVM(); + this.fire(new CustomEvent('resizing', event)); + }).on('dragend', (event: CustomEvent) => { + this.fire(new CustomEvent('resizedone', event)); + }); + + return this; }, @@ -726,36 +921,6 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { .reduce((acc: string, point: Point): string => `${acc} ${point.x},${point.y}`, '').trim()); }, - updatePolygons() { - this.face.plot(this.cuboidModel.front.points); - this.right.plot(this.cuboidModel.right.points); - this.dorsal.plot(this.cuboidModel.dorsal.points); - this.left.plot(this.cuboidModel.left.points); - }, - - updateLines() { - this.frontLeftEdge.plot(this.cuboidModel.fl.points); - this.frontRightEdge.plot(this.cuboidModel.fr.points); - this.dorsalRightEdge.plot(this.cuboidModel.dr.points); - this.dorsalLeftEdge.plot(this.cuboidModel.dl.points); - - this.frontTopEdge.plot(this.cuboidModel.ft.points); - this.rightTopEdge.plot(this.cuboidModel.rt.points); - this.frontBotEdge.plot(this.cuboidModel.fb.points); - this.rightBotEdge.plot(this.cuboidModel.rb.points); - }, - - updateProjections() { - this.ftProj.plot(this.updateProjectionLine(this.cuboidModel.ft.getEquation(), - this.cuboidModel.ft.points[0], this.cuboidModel.vpl)); - this.fbProj.plot(this.updateProjectionLine(this.cuboidModel.fb.getEquation(), - this.cuboidModel.ft.points[0], this.cuboidModel.vpl)); - this.rtProj.plot(this.updateProjectionLine(this.cuboidModel.rt.getEquation(), - this.cuboidModel.rt.points[1], this.cuboidModel.vpr)); - this.rbProj.plot(this.updateProjectionLine(this.cuboidModel.rb.getEquation(), - this.cuboidModel.rt.points[1], this.cuboidModel.vpr)); - }, - computeHeightFace(point: Point, index: number) { switch (index) { // fl @@ -803,6 +968,7 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { this.updateFaces(); this.updateEdges(); this.updateProjections(); + this.updateGrabPoints(); }, updateFaces() { @@ -827,6 +993,28 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { this.frontBotEdge.plot(viewModel.fb.points); this.rightBotEdge.plot(viewModel.rb.points); }, + + updateProjections() { + const viewModel = this.cuboidModel; + + this.ftProj.plot(this.updateProjectionLine(viewModel.ft.getEquation(), + viewModel.ft.points[0], viewModel.vpl)); + this.fbProj.plot(this.updateProjectionLine(viewModel.fb.getEquation(), + viewModel.ft.points[0], viewModel.vpl)); + this.rtProj.plot(this.updateProjectionLine(viewModel.rt.getEquation(), + viewModel.rt.points[1], viewModel.vpr)); + this.rbProj.plot(this.updateProjectionLine(viewModel.rb.getEquation(), + viewModel.rt.points[1], viewModel.vpr)); + }, + + updateGrabPoints() { + const centers = this.getGrabPoints(); + const edges = this.getEdges(); + for (let i = 0; i < centers.length; i += 1) { + const edge = edges[i]; + centers[i].center(edge.cx(), edge.cy()); + } + }, }, construct: { cube(points: string) { From c8616b52bc5cd9d6d7761a254993c517e3cfde8c Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Mon, 27 Apr 2020 22:43:12 +0300 Subject: [PATCH 30/45] added test for cuboid in cvat-core --- cvat-core/tests/api/annotations.js | 10 ++++++-- cvat-core/tests/mocks/dummy-data.mock.js | 29 ++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/cvat-core/tests/api/annotations.js b/cvat-core/tests/api/annotations.js index ae0572cfe38b..e02f3c5a6fbb 100644 --- a/cvat-core/tests/api/annotations.js +++ b/cvat-core/tests/api/annotations.js @@ -25,7 +25,7 @@ describe('Feature: get annotations', () => { const task = (await window.cvat.tasks.get({ id: 100 }))[0]; const annotations = await task.annotations.get(0); expect(Array.isArray(annotations)).toBeTruthy(); - expect(annotations).toHaveLength(11); + expect(annotations).toHaveLength(12); for (const state of annotations) { expect(state).toBeInstanceOf(window.cvat.classes.ObjectState); } @@ -692,7 +692,7 @@ describe('Feature: get statistics', () => { await task.annotations.clear(true); const statistics = await task.annotations.statistics(); expect(statistics).toBeInstanceOf(window.cvat.classes.Statistics); - expect(statistics.total.total).toBe(29); + expect(statistics.total.total).toBe(30); }); test('get statistics from a job', async () => { @@ -719,6 +719,9 @@ describe('Feature: select object', () => { result = await task.annotations.select(annotations, 613, 811); expect(result.state.shapeType).toBe(window.cvat.enums.ObjectShape.POLYGON); expect(result.state.points.length).toBe(94); + result = await task.annotations.select(annotations, 600, 900); + expect(result.state.shapeType).toBe(window.cvat.enums.ObjectShape.CUBOID); + expect(result.state.points.length).toBe(16); }); test('select object in a job', async () => { @@ -733,6 +736,9 @@ describe('Feature: select object', () => { result = await job.annotations.select(annotations, 1490, 237); expect(result.state.shapeType).toBe(window.cvat.enums.ObjectShape.POLYGON); expect(result.state.points.length).toBe(94); + result = await job.annotations.select(annotations, 600, 900); + expect(result.state.shapeType).toBe(window.cvat.enums.ObjectShape.CUBOID); + expect(result.state.points.length).toBe(16); }); test('trying to select from not object states', async () => { diff --git a/cvat-core/tests/mocks/dummy-data.mock.js b/cvat-core/tests/mocks/dummy-data.mock.js index b4a6ea223ca9..d5c65e9bade9 100644 --- a/cvat-core/tests/mocks/dummy-data.mock.js +++ b/cvat-core/tests/mocks/dummy-data.mock.js @@ -2514,6 +2514,35 @@ const taskAnnotationsDummyData = { "label_id": 2, "group": 0, "attributes": [] + }, { + "type": "cuboid", + "occluded": false, + "z_order":12, + "points": [ + 37.037109375, + 834.1583663313359, + 37.037109375, + 1005.6748046875, + 500.1052119006872, + 850.3421313142153, + 500.1052119006872, + 1021.9585696703798, + 600.6842465753452, + 763.1514501284273, + 600.6842465753452, + 934.6678884845915, + 137.82724152601259, + 747.0278858154179, + 137.82724152601259, + 918.4444406426646, + ], + "id": 137, + "frame": 0, + "label_id": 1, + "group": 0, + "attributes": [ + + ] }], "tracks":[] } From d027d3a1ec8591f906d620b778e27d92c8a61a7f Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Mon, 27 Apr 2020 22:45:55 +0300 Subject: [PATCH 31/45] added CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d8a3778c9f99..261bbe149266 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Ability to display a bitmap in the new UI - Button to reset colors settings (brightness, saturation, contrast) in the new UI - Added option to display shape text always +- Cuboids feature was migrated from old UI to new one. ### Changed - Increase preview size of a task till 256, 256 on the server From cc18b0ab34994e94207de2c1b89f904b3cf8462c Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Tue, 28 Apr 2020 00:29:57 +0300 Subject: [PATCH 32/45] fixed merge --- cvat-core/src/annotations-collection.js | 1 - .../annotation-page/standard-workspace/canvas-wrapper.tsx | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/cvat-core/src/annotations-collection.js b/cvat-core/src/annotations-collection.js index 5c209e4e396b..d3a7f4ac82a9 100644 --- a/cvat-core/src/annotations-collection.js +++ b/cvat-core/src/annotations-collection.js @@ -158,7 +158,6 @@ } for (const shape of data.shapes) { - if (shape.type === 'cuboid') continue; const clientID = ++this.count; const shapeModel = shapeFactory(shape, clientID, this.injection); this.shapes[shapeModel.frame] = this.shapes[shapeModel.frame] || []; diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx index 3b9bcb08925d..bbf15c62e9cb 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/canvas-wrapper.tsx @@ -145,6 +145,7 @@ export default class CanvasWrapperComponent extends React.PureComponent { workspace, frameFetching, showObjectsTextAlways, + automaticBordering, showProjections, } = this.props; From 364bf1f3bfb2654bd70b02ab8c73379c375f54e5 Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Tue, 28 Apr 2020 02:43:00 +0300 Subject: [PATCH 33/45] added cursors for cuboid --- cvat-canvas/src/scss/canvas.scss | 3 ++- cvat-canvas/src/typescript/svg.patch.ts | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/cvat-canvas/src/scss/canvas.scss b/cvat-canvas/src/scss/canvas.scss index 537ebe68494d..152c91d13ad7 100644 --- a/cvat-canvas/src/scss/canvas.scss +++ b/cvat-canvas/src/scss/canvas.scss @@ -134,7 +134,8 @@ polyline.cvat_canvas_shape_splitting { cursor: nwse-resize; } -.svg_select_points_l:hover, .svg_select_points_r:hover { +.svg_select_points_l:hover, .svg_select_points_r:hover, +.svg_select_points_ew:hover { cursor: ew-resize; } diff --git a/cvat-canvas/src/typescript/svg.patch.ts b/cvat-canvas/src/typescript/svg.patch.ts index 70d999c7ed84..8e1383fda55e 100644 --- a/cvat-canvas/src/typescript/svg.patch.ts +++ b/cvat-canvas/src/typescript/svg.patch.ts @@ -375,6 +375,25 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { this.setupGrabPoints(this.face.remember('_selectHandler').drawPoint.bind( {nested: this, options: this.face.remember('_selectHandler').options} )); + + // setup proper classes for selection points for proper cursor + Array.from(this.face.remember('_selectHandler').nested.node.children) + .forEach((point: SVG.Circle, i: number) => { + point.classList.add(`svg_select_points_${['lt', 'lb', 'rb', 'rt'][i]}`) + }); + + if (this.cuboidModel.orientation === Orientation.LEFT) { + Array.from(this.dorsalRightEdge.remember('_selectHandler').nested.node.children) + .forEach((point: SVG.Circle, i: number) => { + point.classList.add(`svg_select_points_${['rt', 'rb'][i]}`) + }); + } else { + Array.from(this.dorsalLeftEdge.remember('_selectHandler').nested.node.children) + .forEach((point: SVG.Circle, i: number) => { + point.classList.add(`svg_select_points_${['lt', 'lb'][i]}`) + }); + } + } return this; From b59684762a3048ce45b0c860f74b943db9ae7b97 Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Wed, 29 Apr 2020 01:06:01 +0300 Subject: [PATCH 34/45] cuboid control points fixes --- .gitignore | 5 ++++ cvat-canvas/src/typescript/svg.patch.ts | 35 ++++--------------------- 2 files changed, 10 insertions(+), 30 deletions(-) diff --git a/.gitignore b/.gitignore index 00e9b3f9ddd8..23872063a325 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,9 @@ __pycache__ # Ignore development npm files node_modules +# Ignore npm logs file +npm-debug.log* +yarn-debug.log* +yarn-error.log* + .DS_Store diff --git a/cvat-canvas/src/typescript/svg.patch.ts b/cvat-canvas/src/typescript/svg.patch.ts index 8e1383fda55e..a4dc7ff31c4d 100644 --- a/cvat-canvas/src/typescript/svg.patch.ts +++ b/cvat-canvas/src/typescript/svg.patch.ts @@ -370,7 +370,7 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { } if (value === false) { - this.getGrabPoints().forEach((point) => {point.remove()}); + this.getGrabPoints().forEach((point) => {point && point.remove()}); } else { this.setupGrabPoints(this.face.remember('_selectHandler').drawPoint.bind( {nested: this, options: this.face.remember('_selectHandler').options} @@ -385,12 +385,12 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { if (this.cuboidModel.orientation === Orientation.LEFT) { Array.from(this.dorsalRightEdge.remember('_selectHandler').nested.node.children) .forEach((point: SVG.Circle, i: number) => { - point.classList.add(`svg_select_points_${['rt', 'rb'][i]}`) + point.classList.add(`svg_select_points_${['t', 'b'][i]}`) }); } else { Array.from(this.dorsalLeftEdge.remember('_selectHandler').nested.node.children) .forEach((point: SVG.Circle, i: number) => { - point.classList.add(`svg_select_points_${['lt', 'lb'][i]}`) + point.classList.add(`svg_select_points_${['t', 'b'][i]}`) }); } @@ -530,10 +530,8 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { resizedCubePoint = getResizedPointIndex(event) + (orientation === Orientation.LEFT ? 4 : 6); this.fire(new CustomEvent('resizestart', event)); }).on('resizing', (event: CustomEvent) => { - let { dx, dy } = event.detail; - let dxPortion = dx - accumulatedOffset.x; + let { dy } = event.detail; let dyPortion = dy - accumulatedOffset.y; - accumulatedOffset.x += dxPortion; accumulatedOffset.y += dyPortion; const edge = getEdgeIndex(resizedCubePoint); @@ -541,29 +539,6 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { let cuboidPoints = this.cuboidModel.getPoints(); if (!event.detail.event.shiftKey) { - const x1 = cuboidPoints[edgeTopIndex].x + dxPortion; - const x2 = cuboidPoints[edgeBottomIndex].x + dxPortion; - const y1 = (orientation === Orientation.LEFT - ? this.cuboidModel.rt : this.cuboidModel.lt).getEquation().getY(x1); - const y2 = (orientation === Orientation.LEFT - ? this.cuboidModel.rb : this.cuboidModel.lb).getEquation().getY(x2); - - const frontTopPoint = orientation === Orientation.LEFT ? 2 : 0; - if (cuboidPoints[edgeTopIndex].x < cuboidPoints[frontTopPoint].x - && x1 < cuboidPoints[frontTopPoint].x - consts.MIN_EDGE_LENGTH - && x1 > this.cuboidModel.vpr.x + consts.MIN_EDGE_LENGTH - || cuboidPoints[edgeTopIndex].x >= cuboidPoints[frontTopPoint].x - && x1 > cuboidPoints[frontTopPoint].x + consts.MIN_EDGE_LENGTH - && x1 < this.cuboidModel.vpr.x + consts.MIN_EDGE_LENGTH - ) { - const topPoint = { x: x1, y: y1 }; - const botPoint = { x: x2, y: y2 }; - (orientation === Orientation.LEFT - ? this.cuboidModel.dr : this.cuboidModel.dl).points = [topPoint, botPoint]; - this.updateViewAndVM(edge === EdgeIndex.DL); - } - - cuboidPoints = this.cuboidModel.getPoints(); const midPointUp = { ...cuboidPoints[edgeTopIndex] }; const midPointDown = { ...cuboidPoints[edgeBottomIndex] }; @@ -1033,7 +1008,7 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { const edges = this.getEdges(); for (let i = 0; i < centers.length; i += 1) { const edge = edges[i]; - centers[i].center(edge.cx(), edge.cy()); + if (centers[i]) centers[i].center(edge.cx(), edge.cy()); } }, }, From 130b5e31de1a0b923b4c6c700486433e784428bb Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Wed, 29 Apr 2020 01:58:14 +0300 Subject: [PATCH 35/45] cuboid unpinned by default --- cvat-core/src/annotations-objects.js | 1 + 1 file changed, 1 insertion(+) diff --git a/cvat-core/src/annotations-objects.js b/cvat-core/src/annotations-objects.js index b545bb0182d1..7d6f334d968b 100644 --- a/cvat-core/src/annotations-objects.js +++ b/cvat-core/src/annotations-objects.js @@ -1406,6 +1406,7 @@ constructor(data, clientID, color, injection) { super(data, clientID, color, injection); this.shapeType = ObjectShape.CUBOID; + this.pinned = false; checkNumberOfPoints(this.shapeType, this.points); } From cf7899b4a3b6d6066e0b4e939b148a6a211df189 Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Wed, 29 Apr 2020 14:47:19 +0300 Subject: [PATCH 36/45] cuboid drawing fixes --- CHANGELOG.md | 2 +- cvat-canvas/src/typescript/canvasView.ts | 3 +++ cvat-canvas/src/typescript/drawHandler.ts | 22 ++++--------------- cvat-core/src/annotations-objects.js | 1 + .../controls-side-bar/draw-shape-popover.tsx | 2 +- 5 files changed, 10 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1cb6270b155..ffd51e3a5ac9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Re-Identification algorithm to merging bounding boxes automatically to the new UI () - Methods ``import`` and ``export`` to import/export raw annotations for Job and Task in ``cvat-core`` () - Versioning of client packages (``cvat-core``, ``cvat-canvas``, ``cvat-ui``). Initial versions are set to 1.0.0 () -- Cuboids feature was migrated from old UI to new one. +- Cuboids feature was migrated from old UI to new one. () ### Changed - diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index c66066b099e2..418f71d81cb9 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -721,7 +721,10 @@ export class CanvasViewImpl implements CanvasView, Listener { public notify(model: CanvasModel & Master, reason: UpdateReasons): void { this.geometry = this.controller.geometry; if (reason === UpdateReasons.CONFIG_UPDATED) { + const { activeElement } = this; + this.deactivate(); this.configuration = model.configuration; + this.activate(activeElement); this.editHandler.configurate(this.configuration); this.drawHandler.configurate(this.configuration); diff --git a/cvat-canvas/src/typescript/drawHandler.ts b/cvat-canvas/src/typescript/drawHandler.ts index 738aa32419fc..0e4fb25695b0 100644 --- a/cvat-canvas/src/typescript/drawHandler.ts +++ b/cvat-canvas/src/typescript/drawHandler.ts @@ -316,9 +316,7 @@ export class DrawHandlerImpl implements DrawHandler { } private drawPolyshape(): void { - let size = this.drawData.shapeType === 'cuboid' ? 0 : this.drawData.numberOfPoints; - - if (this.drawData.shapeType === 'cuboid') this.addCrosshair(); + let size = this.drawData.shapeType === 'cuboid' ? 4 : this.drawData.numberOfPoints; const sizeDecrement = (): void => { if (!--size) { @@ -326,21 +324,9 @@ export class DrawHandlerImpl implements DrawHandler { } }; - const sizeCuboidIncrement = (): void => { - if (++size === 4) { - this.drawInstance.draw('done'); - } - }; - - if (this.drawData.shapeType === 'cuboid') { - this.drawInstance.on('drawstart', sizeCuboidIncrement); - this.drawInstance.on('drawpoint', sizeCuboidIncrement); - this.drawInstance.on('undopoint', (): number => size--); - } else if (this.drawData.numberOfPoints) { - this.drawInstance.on('drawstart', sizeDecrement); - this.drawInstance.on('drawpoint', sizeDecrement); - this.drawInstance.on('undopoint', (): number => size++); - } + this.drawInstance.on('drawstart', sizeDecrement); + this.drawInstance.on('drawpoint', sizeDecrement); + this.drawInstance.on('undopoint', (): number => size++); // Add ability to cancel the latest drawn point this.canvas.on('mousedown.draw', (e: MouseEvent): void => { diff --git a/cvat-core/src/annotations-objects.js b/cvat-core/src/annotations-objects.js index 7d6f334d968b..00c0d881bc8c 100644 --- a/cvat-core/src/annotations-objects.js +++ b/cvat-core/src/annotations-objects.js @@ -1976,6 +1976,7 @@ constructor(data, clientID, color, injection) { super(data, clientID, color, injection); this.shapeType = ObjectShape.CUBOID; + this.pinned = false; for (const shape of Object.values(this.shapes)) { checkNumberOfPoints(this.shapeType, shape.points); } diff --git a/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover.tsx b/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover.tsx index 6324d9a34716..8633ae6c5c2b 100644 --- a/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover.tsx +++ b/cvat-ui/src/containers/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover.tsx @@ -121,7 +121,7 @@ class DrawShapePopoverContainer extends React.PureComponent { rectDrawingMethod, numberOfPoints, shapeType, - crosshair: shapeType === ShapeType.RECTANGLE, + crosshair: [ShapeType.RECTANGLE, ShapeType.CUBOID].includes(shapeType), }); onDrawStart(shapeType, selectedLabelID, From 4df4fd5148e329d5f51dc11ecbbc69eb88612faa Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Wed, 29 Apr 2020 15:10:49 +0300 Subject: [PATCH 37/45] added cuboid interpolation for client side --- cvat-core/src/annotations-objects.js | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/cvat-core/src/annotations-objects.js b/cvat-core/src/annotations-objects.js index 00c0d881bc8c..b23290f34468 100644 --- a/cvat-core/src/annotations-objects.js +++ b/cvat-core/src/annotations-objects.js @@ -1534,20 +1534,15 @@ } interpolatePosition(leftPosition, rightPosition, offset) { - const positionOffset = [ - rightPosition.points[0] - leftPosition.points[0], - rightPosition.points[1] - leftPosition.points[1], - rightPosition.points[2] - leftPosition.points[2], - rightPosition.points[3] - leftPosition.points[3], - ]; - - return { // xtl, ytl, xbr, ybr - points: [ - leftPosition.points[0] + positionOffset[0] * offset, - leftPosition.points[1] + positionOffset[1] * offset, - leftPosition.points[2] + positionOffset[2] * offset, - leftPosition.points[3] + positionOffset[3] * offset, - ], + + const positionOffset = leftPosition.points.map((point, index) => ( + rightPosition.points[index] - point + )) + + return { + points: leftPosition.points.map((point ,index) => ( + point + positionOffset[index] * offset + )), occluded: leftPosition.occluded, outside: leftPosition.outside, zOrder: leftPosition.zOrder, @@ -1990,6 +1985,7 @@ PolylineTrack.distance = PolylineShape.distance; PointsTrack.distance = PointsShape.distance; CuboidTrack.distance = CuboidShape.distance; + CuboidTrack.interpolatePosition = RectangleTrack.interpolatePosition; module.exports = { RectangleShape, From 8ffa020e68e440a945166cc44bb8ee6fea2d4e3a Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Wed, 29 Apr 2020 15:11:40 +0300 Subject: [PATCH 38/45] deleted comments --- cvat-core/src/annotations-objects.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/cvat-core/src/annotations-objects.js b/cvat-core/src/annotations-objects.js index b23290f34468..ecf876347f45 100644 --- a/cvat-core/src/annotations-objects.js +++ b/cvat-core/src/annotations-objects.js @@ -1976,8 +1976,6 @@ checkNumberOfPoints(this.shapeType, shape.points); } } - - // TODO: interpolation; } RectangleTrack.distance = RectangleShape.distance; From 11e9470615b7d01cefc00000b8d83866f657bbcc Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Wed, 29 Apr 2020 16:06:37 +0300 Subject: [PATCH 39/45] added cuboid reset perspective --- cvat-canvas/src/typescript/canvasView.ts | 4 ++++ cvat-canvas/src/typescript/svg.patch.ts | 22 ++++++++++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index 418f71d81cb9..17472374595c 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -437,6 +437,10 @@ export class CanvasViewImpl implements CanvasView, Listener { .filter((_state: any): boolean => ( _state.clientID === self.activeElement.clientID )); + if (['cuboid', 'rectangle'].includes(state.shapeType)) { + e.preventDefault(); + return; + } if (e.ctrlKey) { const { points } = state; self.onEditDone( diff --git a/cvat-canvas/src/typescript/svg.patch.ts b/cvat-canvas/src/typescript/svg.patch.ts index a4dc7ff31c4d..01e9f6dc14e8 100644 --- a/cvat-canvas/src/typescript/svg.patch.ts +++ b/cvat-canvas/src/typescript/svg.patch.ts @@ -385,12 +385,14 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { if (this.cuboidModel.orientation === Orientation.LEFT) { Array.from(this.dorsalRightEdge.remember('_selectHandler').nested.node.children) .forEach((point: SVG.Circle, i: number) => { - point.classList.add(`svg_select_points_${['t', 'b'][i]}`) + point.classList.add(`svg_select_points_${['t', 'b'][i]}`); + point.ondblclick = this.resetPerspective.bind(this); }); } else { Array.from(this.dorsalLeftEdge.remember('_selectHandler').nested.node.children) .forEach((point: SVG.Circle, i: number) => { - point.classList.add(`svg_select_points_${['t', 'b'][i]}`) + point.classList.add(`svg_select_points_${['t', 'b'][i]}`); + point.ondblclick = this.resetPerspective.bind(this); }); } @@ -906,6 +908,22 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { } }, + resetPerspective(){ + if (this.cuboidModel.orientation === Orientation.LEFT) { + const edgePoints = this.cuboidModel.dl.points; + const constraints = this.cuboidModel.computeSideEdgeConstraints(this.cuboidModel.dl); + edgePoints[0].y = constraints.y1Range.min; + this.cuboidModel.dl.points = [edgePoints[0],edgePoints[1]]; + this.updateViewAndVM(true); + } else { + const edgePoints = this.cuboidModel.dr.points; + const constraints = this.cuboidModel.computeSideEdgeConstraints(this.cuboidModel.dr); + edgePoints[0].y = constraints.y1Range.min; + this.cuboidModel.dr.points = [edgePoints[0],edgePoints[1]]; + this.updateViewAndVM(); + } + }, + updateViewAndVM(build: boolean) { this.cuboidModel.updateOrientation(); this.cuboidModel.buildBackEdge(build); From dc450b3e91b6b930f0255faf1af4a23dbbd387ba Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Thu, 30 Apr 2020 01:47:35 +0300 Subject: [PATCH 40/45] wip cuboid zOrder --- cvat-canvas/src/typescript/svg.patch.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cvat-canvas/src/typescript/svg.patch.ts b/cvat-canvas/src/typescript/svg.patch.ts index 01e9f6dc14e8..a34c805b6978 100644 --- a/cvat-canvas/src/typescript/svg.patch.ts +++ b/cvat-canvas/src/typescript/svg.patch.ts @@ -829,6 +829,10 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { } else if (a === 'stroke-width' && typeof v === "number") { this._attr(a, v, n); this.updateThickness(); + } else if (a === 'data-z-order') { + this._attr(a, v, n); + [this.face, this.left, this.dorsal, this.right, ...this.getEdges(), ...this.getGrabPoints()] + .forEach((el) => {if (el) el.attr(a, v, n)}) } else { return this._attr(a ,v, n); } From 10b97d25e17a1c4a907a9c939791b944d28fce8d Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Thu, 30 Apr 2020 02:43:51 +0300 Subject: [PATCH 41/45] fixed cuboid drawing --- cvat-canvas/src/typescript/canvasView.ts | 8 +++++++- cvat-canvas/src/typescript/drawHandler.ts | 2 +- cvat-canvas/src/typescript/svg.patch.ts | 8 +++++--- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index 17472374595c..a4c0c7091c55 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -1619,7 +1619,13 @@ export class CanvasViewImpl implements CanvasView, Listener { 'data-z-order': state.zOrder, }); - (window as any).cube = cube; + if (state.occluded) { + cube.addClass('cvat_canvas_shape_occluded'); + } + + if (state.hidden || state.outside) { + cube.style('display', 'none'); + } return cube; } diff --git a/cvat-canvas/src/typescript/drawHandler.ts b/cvat-canvas/src/typescript/drawHandler.ts index 0e4fb25695b0..8aa8b55101fb 100644 --- a/cvat-canvas/src/typescript/drawHandler.ts +++ b/cvat-canvas/src/typescript/drawHandler.ts @@ -319,7 +319,7 @@ export class DrawHandlerImpl implements DrawHandler { let size = this.drawData.shapeType === 'cuboid' ? 4 : this.drawData.numberOfPoints; const sizeDecrement = (): void => { - if (!--size) { + if (--size === 0) { this.drawInstance.draw('done'); } }; diff --git a/cvat-canvas/src/typescript/svg.patch.ts b/cvat-canvas/src/typescript/svg.patch.ts index a34c805b6978..5aabf032a590 100644 --- a/cvat-canvas/src/typescript/svg.patch.ts +++ b/cvat-canvas/src/typescript/svg.patch.ts @@ -412,9 +412,11 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { this.dorsalLeftEdge.off('resizing').off('resizedone').off('resizestart'); this.getGrabPoints().forEach((point: SVG.Element) => { - point.off('dragstart'); - point.off('dragmove'); - point.off('dragend'); + if (point) { + point.off('dragstart'); + point.off('dragmove'); + point.off('dragend'); + } }) return; From 8c2e520fdea754a616cbec44e8ac288f09c9bc02 Mon Sep 17 00:00:00 2001 From: Boris Sekachev Date: Thu, 30 Apr 2020 11:41:18 +0300 Subject: [PATCH 42/45] Fixed zOrder for cuboids --- cvat-canvas/src/typescript/canvasView.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cvat-canvas/src/typescript/canvasView.ts b/cvat-canvas/src/typescript/canvasView.ts index a4c0c7091c55..63acbefa6c85 100644 --- a/cvat-canvas/src/typescript/canvasView.ts +++ b/cvat-canvas/src/typescript/canvasView.ts @@ -1617,7 +1617,7 @@ export class CanvasViewImpl implements CanvasView, Listener { stroke: state.color, 'stroke-width': consts.BASE_STROKE_WIDTH / this.geometry.scale, 'data-z-order': state.zOrder, - }); + }).addClass('cvat_canvas_shape'); if (state.occluded) { cube.addClass('cvat_canvas_shape_occluded'); From 69ef6098ddb3dc9f212982bab5032f346cfd758c Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Thu, 30 Apr 2020 12:35:05 +0300 Subject: [PATCH 43/45] added top and bottom faces to cuboid --- cvat-canvas/src/typescript/svg.patch.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cvat-canvas/src/typescript/svg.patch.ts b/cvat-canvas/src/typescript/svg.patch.ts index 5aabf032a590..eabf8944acab 100644 --- a/cvat-canvas/src/typescript/svg.patch.ts +++ b/cvat-canvas/src/typescript/svg.patch.ts @@ -245,6 +245,8 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { }, setupFaces() { + this.bot = this.polygon(this.cuboidModel.bot.points); + this.top = this.polygon(this.cuboidModel.top.points); this.face = this.polygon(this.cuboidModel.front.points); this.right = this.polygon(this.cuboidModel.right.points); this.dorsal = this.polygon(this.cuboidModel.dorsal.points); @@ -875,6 +877,10 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { this.dorsalRightEdge.stroke({ color: strokeColor }); this.dorsalLeftEdge.stroke({ color: strokeColor }); + this.bot.stroke({ color: strokeColor }) + .fill({ color: fillColor }); + this.top.stroke({ color: strokeColor }) + .fill({ color: fillColor }); this.face.stroke({ color: strokeColor, width: 0 }) .fill({ color: fillColor }); this.right.stroke({ color: strokeColor }) @@ -994,6 +1000,8 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { updateFaces() { const viewModel = this.cuboidModel; + this.bot.plot(viewModel.bot.points); + this.top.plot(viewModel.top.points); this.face.plot(viewModel.front.points); this.right.plot(viewModel.right.points); this.dorsal.plot(viewModel.dorsal.points); From 86a7a8915473618cbcc43e21f108366d2e7d1d0e Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Thu, 30 Apr 2020 15:06:53 +0300 Subject: [PATCH 44/45] fixed faces order and edge color --- cvat-canvas/src/typescript/svg.patch.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cvat-canvas/src/typescript/svg.patch.ts b/cvat-canvas/src/typescript/svg.patch.ts index eabf8944acab..b16019dd7b7d 100644 --- a/cvat-canvas/src/typescript/svg.patch.ts +++ b/cvat-canvas/src/typescript/svg.patch.ts @@ -247,10 +247,10 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { setupFaces() { this.bot = this.polygon(this.cuboidModel.bot.points); this.top = this.polygon(this.cuboidModel.top.points); - this.face = this.polygon(this.cuboidModel.front.points); this.right = this.polygon(this.cuboidModel.right.points); this.dorsal = this.polygon(this.cuboidModel.dorsal.points); this.left = this.polygon(this.cuboidModel.left.points); + this.face = this.polygon(this.cuboidModel.front.points); }, setupProjections() { @@ -833,7 +833,7 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { } else if (a === 'stroke-width' && typeof v === "number") { this._attr(a, v, n); this.updateThickness(); - } else if (a === 'data-z-order') { + } else if (a === 'data-z-order' && typeof v !== 'undefined') { this._attr(a, v, n); [this.face, this.left, this.dorsal, this.right, ...this.getEdges(), ...this.getGrabPoints()] .forEach((el) => {if (el) el.attr(a, v, n)}) @@ -866,7 +866,7 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { paintOrientationLines() { const fillColor = this.attr('fill'); const strokeColor = this.attr('stroke'); - const selectedColor = this.attr('face-stroke') || '#ff007f'; + const selectedColor = this.attr('face-stroke') || '#b0bec5'; this.frontTopEdge.stroke({ color: selectedColor }); this.frontLeftEdge.stroke({ color: selectedColor }); this.frontBotEdge.stroke({ color: selectedColor }); @@ -1002,10 +1002,10 @@ function getTopDown(edgeIndex: EdgeIndex): number[] { this.bot.plot(viewModel.bot.points); this.top.plot(viewModel.top.points); - this.face.plot(viewModel.front.points); this.right.plot(viewModel.right.points); this.dorsal.plot(viewModel.dorsal.points); this.left.plot(viewModel.left.points); + this.face.plot(viewModel.front.points); }, updateEdges() { From 8dd786fcec4e42ea1439ed2c47f0f7361853afcb Mon Sep 17 00:00:00 2001 From: Dmitry Kalinin Date: Thu, 30 Apr 2020 18:22:27 +0300 Subject: [PATCH 45/45] disabled cuboid tracks --- .../standard-workspace/controls-side-bar/draw-shape-popover.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover.tsx b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover.tsx index 082b2d98f13d..4b0d716b4121 100644 --- a/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover.tsx +++ b/cvat-ui/src/components/annotation-page/standard-workspace/controls-side-bar/draw-shape-popover.tsx @@ -48,7 +48,7 @@ function DrawShapePopoverComponent(props: Props): JSX.Element { } = props; const trackDisabled = shapeType === ShapeType.POLYGON || shapeType === ShapeType.POLYLINE - || (shapeType === ShapeType.POINTS && numberOfPoints !== 1); + || shapeType === ShapeType.CUBOID || (shapeType === ShapeType.POINTS && numberOfPoints !== 1); return (