Skip to content

Commit

Permalink
Polygon Selection (#261)
Browse files Browse the repository at this point in the history
  • Loading branch information
simonbethke authored Nov 6, 2024
1 parent bccc9ed commit 32b20cd
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 0 deletions.
3 changes: 3 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { registerTransformHandlerEvents } from './transform-handler';
import { ToolManager } from './tools/tool-manager';
import { RectSelection } from './tools/rect-selection';
import { BrushSelection } from './tools/brush-selection';
import { PolygonSelection } from './tools/polygon-selection';
import { SphereSelection } from './tools/sphere-selection';
import { MoveTool } from './tools/move-tool';
import { RotateTool } from './tools/rotate-tool';
Expand Down Expand Up @@ -69,6 +70,7 @@ const initShortcuts = (events: Events) => {
shortcuts.register(['G', 'g'], { event: 'grid.toggleVisible' });
shortcuts.register(['C', 'c'], { event: 'tool.toggleCoordSpace' });
shortcuts.register(['F', 'f'], { event: 'camera.focus' });
shortcuts.register(['O', 'o'], { event: 'tool.polygonSelection', sticky: true });
shortcuts.register(['B', 'b'], { event: 'tool.brushSelection', sticky: true });
shortcuts.register(['R', 'r'], { event: 'tool.rectSelection', sticky: true });
shortcuts.register(['P', 'p'], { event: 'tool.rectSelection', sticky: true });
Expand Down Expand Up @@ -176,6 +178,7 @@ const main = async () => {
const toolManager = new ToolManager(events);
toolManager.register('rectSelection', new RectSelection(events, editorUI.toolsContainer.dom));
toolManager.register('brushSelection', new BrushSelection(events, editorUI.toolsContainer.dom));
toolManager.register('polygonSelection', new PolygonSelection(events, editorUI.toolsContainer.dom));
toolManager.register('sphereSelection', new SphereSelection(events, scene, editorUI.canvasContainer));
toolManager.register('move', new MoveTool(events, scene));
toolManager.register('rotate', new RotateTool(events, scene));
Expand Down
141 changes: 141 additions & 0 deletions src/tools/polygon-selection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import { Events } from "../events";

type Point = {x: number, y: number};

class PolygonSelection {
activate: () => void;
deactivate: () => void;

constructor(events: Events, parent: HTMLElement) {
let points: Point[] = [];
let currentPoint: Point = null;

// create svg
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svg.id = 'polygon-select-svg';
svg.classList.add('select-svg');

// create polyline element
const polyline = document.createElementNS(svg.namespaceURI, 'polyline') as SVGPolylineElement;
polyline.setAttribute('fill', 'none');
polyline.setAttribute('stroke-width', '1');
polyline.setAttribute('stroke-dasharray', '5, 5');
polyline.setAttribute('stroke-dashoffset', '0');

// create canvas
const selectCanvas = document.createElement('canvas');
selectCanvas.id = 'polygon-select-canvas';

const context = selectCanvas.getContext('2d');
context.globalCompositeOperation = 'copy';

const prev = { x: 0, y: 0 };
let dragId: number | undefined;

const paint = () => {
polyline.setAttribute('points', [...points, currentPoint].reduce((prev, current) => prev + `${current.x}, ${current.y} `, ""));
polyline.setAttribute('stroke', isClosed() ? '#fa6' : '#f60');
};

const isClosed = () => points.length > 1 && Math.abs(currentPoint.x - points[0].x) < 4 && Math.abs(currentPoint.y - points[0].y) < 4;

const pointermove = (e: PointerEvent) => {
currentPoint = {x: e.offsetX, y: e.offsetY};

if(points.length > 0){
paint();
}
};

const pointerdown = (e: PointerEvent) => {
if (points.length > 0 || (e.pointerType === 'mouse' ? e.button === 0 : e.isPrimary)) {
e.preventDefault();
e.stopPropagation();
}
};

const pointerup = (e: PointerEvent) => {
if (e.pointerType === 'mouse' ? e.button === 0 : e.isPrimary) {
e.preventDefault();
e.stopPropagation();

if(isClosed())
commitSelection(e);
else
points.push(currentPoint);
}
};

const dblclick = (e: PointerEvent) => {
if(points.length > 0){
points.push(currentPoint);

e.preventDefault();
e.stopPropagation();

commitSelection(e);
}
};

const commitSelection = (e: PointerEvent) => {
// initialize canvas
if (selectCanvas.width !== parent.clientWidth || selectCanvas.height !== parent.clientHeight) {
selectCanvas.width = parent.clientWidth;
selectCanvas.height = parent.clientHeight;
}

// clear canvas
context.clearRect(0, 0, selectCanvas.width, selectCanvas.height);

context.beginPath();
context.fillStyle = '#f60';
context.beginPath();
points.forEach((p, idx) => {
if(idx === 0){
context.moveTo(p.x, p.y);
}
else {
context.lineTo(p.x, p.y);
}
});
context.closePath();
context.fill();

events.fire(
'select.byMask',
e.shiftKey ? 'add' : (e.ctrlKey ? 'remove' : 'set'),
selectCanvas,
context
);

points = [];
paint();
};

this.activate = () => {
svg.style.display = 'inline';
parent.style.display = 'block';
parent.addEventListener('pointerdown', pointerdown);
parent.addEventListener('pointermove', pointermove);
parent.addEventListener('pointerup', pointerup);
parent.addEventListener('dblclick', dblclick);
};

this.deactivate = () => {
// cancel active operation
svg.style.display = 'none';
parent.style.display = 'none';
parent.removeEventListener('pointerdown', pointerdown);
parent.removeEventListener('pointermove', pointermove);
parent.removeEventListener('pointerup', pointerup);
parent.removeEventListener('dblclick', dblclick);
points = [];
paint();
};

svg.appendChild(polyline);
parent.appendChild(svg);
}
}

export { PolygonSelection };
11 changes: 11 additions & 0 deletions src/ui/bottom-toolbar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import undoSvg from './svg/undo.svg';
import redoSvg from './svg/redo.svg';
import pickerSvg from './svg/select-picker.svg';
import brushSvg from './svg/select-brush.svg';
import polygonSvg from './svg/select-poly.svg';
import sphereSvg from './svg/select-sphere.svg';
// import lassoSvg from './svg/select-lasso.svg';
// import cropSvg from './svg/crop.svg';
Expand Down Expand Up @@ -46,6 +47,11 @@ class BottomToolbar extends Container {
class: 'bottom-toolbar-tool'
});

const polygon = new Button({
id: 'bottom-toolbar-polygon',
class: 'bottom-toolbar-tool'
});

const brush = new Button({
id: 'bottom-toolbar-brush',
class: 'bottom-toolbar-tool'
Expand Down Expand Up @@ -93,6 +99,7 @@ class BottomToolbar extends Container {
undo.dom.appendChild(createSvg(undoSvg));
redo.dom.appendChild(createSvg(redoSvg));
picker.dom.appendChild(createSvg(pickerSvg));
polygon.dom.appendChild(createSvg(polygonSvg));
brush.dom.appendChild(createSvg(brushSvg));
sphere.dom.appendChild(createSvg(sphereSvg));
// lasso.dom.appendChild(createSvg(lassoSvg));
Expand All @@ -102,6 +109,7 @@ class BottomToolbar extends Container {
this.append(redo);
this.append(new Element({ class: 'bottom-toolbar-separator' }));
this.append(picker);
this.append(polygon);
this.append(brush);
// this.append(lasso);
this.append(new Element({ class: 'bottom-toolbar-separator' }));
Expand All @@ -115,6 +123,7 @@ class BottomToolbar extends Container {

undo.dom.addEventListener('click', () => events.fire('edit.undo'));
redo.dom.addEventListener('click', () => events.fire('edit.redo'));
polygon.dom.addEventListener('click', () => events.fire('tool.polygonSelection'));
brush.dom.addEventListener('click', () => events.fire('tool.brushSelection'));
picker.dom.addEventListener('click', () => events.fire('tool.rectSelection'));
sphere.dom.addEventListener('click', () => events.fire('tool.sphereSelection'));
Expand All @@ -129,6 +138,7 @@ class BottomToolbar extends Container {
events.on('tool.activated', (toolName: string) => {
picker.class[toolName === 'rectSelection' ? 'add' : 'remove']('active');
brush.class[toolName === 'brushSelection' ? 'add' : 'remove']('active');
polygon.class[toolName === 'polygonSelection' ? 'add' : 'remove']('active');
sphere.class[toolName === 'sphereSelection' ? 'add' : 'remove']('active');
translate.class[toolName === 'move' ? 'add' : 'remove']('active');
rotate.class[toolName === 'rotate' ? 'add' : 'remove']('active');
Expand All @@ -144,6 +154,7 @@ class BottomToolbar extends Container {
tooltips.register(redo, localize('tooltip.redo'));
tooltips.register(picker, localize('tooltip.picker'));
tooltips.register(brush, localize('tooltip.brush'));
tooltips.register(polygon, localize('tooltip.polygon'));
// tooltips.register(lasso, 'Lasso Select');
tooltips.register(sphere, localize('tooltip.sphere'));
// tooltips.register(crop, 'Crop');
Expand Down
2 changes: 2 additions & 0 deletions src/ui/localization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ const localizeInit = () => {
"tooltip.undo": "Rückgängig ( Strg + Z )",
"tooltip.redo": "Wiederholen ( Strg + Shift + Z )",
"tooltip.picker": "Einzelselektion ( P )",
"tooltip.polygon": "Polygonselektion ( O )",
"tooltip.brush": "Pinselselektion ( B )",
"tooltip.sphere": "Kugelselektion",
"tooltip.translate": "Verschieben ( 1 )",
Expand Down Expand Up @@ -303,6 +304,7 @@ const localizeInit = () => {
"tooltip.undo": "Undo ( Ctrl + Z )",
"tooltip.redo": "Redo ( Ctrl + Shift + Z )",
"tooltip.picker": "Picker Select ( P )",
"tooltip.polygon": "Polygon Select ( O )",
"tooltip.brush": "Brush Select ( B )",
"tooltip.sphere": "Sphere Select",
"tooltip.translate": "Translate ( 1 )",
Expand Down
4 changes: 4 additions & 0 deletions src/ui/svg/select-poly.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 32b20cd

Please sign in to comment.