diff --git a/tools/spatialDraw/DrawingManager.js b/tools/spatialDraw/DrawingManager.js
index d9e91b4..93d303b 100644
--- a/tools/spatialDraw/DrawingManager.js
+++ b/tools/spatialDraw/DrawingManager.js
@@ -1,5 +1,3 @@
-/* global MeshLine, MeshLineMaterial, MeshLineRaycast */
-
/** Class that draws to a 3D scene. */
class DrawingManager {
/**
@@ -10,7 +8,8 @@ class DrawingManager {
constructor(scene, camera) {
this.toolMap = {
'LINE': new DrawingManager.Tool.Line(this),
- 'ICON': new DrawingManager.Tool.Icon(this)
+ 'POLYGON': new DrawingManager.Tool.Polygon(this),
+ 'ICON': new DrawingManager.Tool.Icon(this),
};
this.cursorMap = {
'PROJECTION': new DrawingManager.Cursor.SmoothProjection(),
@@ -614,6 +613,150 @@ DrawingManager.Tool.Line = class extends DrawingManager.Tool {
}
};
+DrawingManager.Tool.Polygon = class extends DrawingManager.Tool {
+ /**
+ * Creates a Polygon Tool.
+ */
+ constructor(drawingManager) {
+ super(drawingManager);
+
+ this.currentPolygon = null;
+
+ this.lastPointTime = 0;
+ this.minimumUpdate = {
+ distance: 5,
+ time: 1000
+ };
+ }
+
+ /**
+ * Starts drawing with the tool.
+ * @param {THREE.Object3D} parent - The parent object to draw in.
+ * @param {THREE.Vector3} position - The position of the cursor.
+ */
+ startDraw(parent, position) {
+ this.currentPolygon = {
+ points: [position.clone()],
+ meshLine: new MeshLine(),
+ obj: null,
+ };
+
+ this.lastPointTime = Date.now();
+ }
+
+ /**
+ * Updates drawing with the tool.
+ * @param {THREE.Object3D} parent - The parent object to draw in.
+ * @param {THREE.Vector3} position - The position of the cursor.
+ */
+ moveDraw(parent, position) {
+ if (!this.currentPolygon) {
+ return;
+ }
+ const lastPosition = this.currentPolygon.points[this.currentPolygon.points.length - 1];
+ const newPosition = position.clone();
+
+ if (newPosition.clone().sub(lastPosition).length() < this.minimumUpdate.length && Date.now() - this.lastPointTime < this.minimumUpdate.time) {
+ return; // Return if the cursor hasn't moved far enough and enough time hasn't passed, simplifies path when cursor doesn't move much for a bit
+ }
+ this.lastPointTime = Date.now();
+
+ this.currentPolygon.points.push(newPosition);
+ if (this.currentPolygon.points.length < 3) {
+ this.currentPolygon.meshLine.setPoints(this.currentPolygon.points);
+ } else {
+ this.currentPolygon.meshLine.setPoints([...this.currentPolygon.points, this.currentPolygon.points[0]]);
+ }
+ if (this.currentPolygon.obj) { // Clear previous in-progress drawing
+ this.currentPolygon.obj.geometry.dispose();
+ this.currentPolygon.obj.material.dispose();
+ this.currentPolygon.obj.parent.remove(this.currentPolygon.obj);
+ this.currentPolygon.obj = null;
+ }
+ const meshLine = new THREE.Mesh(this.currentPolygon.meshLine, this.meshLineMaterial);
+ meshLine.raycast = MeshLineRaycast;
+ this.currentPolygon.obj = meshLine;
+ parent.add(this.currentPolygon.obj);
+ }
+
+ /**
+ * Finishes drawing with the tool. Can be called when tool is not currently drawing.
+ * @param {THREE.Object3D} parent - The parent object to draw in.
+ * @param {THREE.Vector3} position - The position of the cursor.
+ */
+ endDraw(parent) {
+ if (this.currentPolygon && this.currentPolygon.obj) {
+ const drawingId = `${Math.round(Math.random() * 100000000)}`;
+
+ const serialized = {
+ tool: 'POLYGON',
+ points: this.currentPolygon.points,
+ size: this.size,
+ color: this.color,
+ drawingId: drawingId
+ };
+ this.drawFromSerialized(parent, serialized);
+
+ this.currentPolygon.obj.geometry.dispose();
+ this.currentPolygon.obj.material.dispose();
+ this.currentPolygon.obj.parent.remove(this.currentPolygon.obj);
+ this.currentPolygon = null;
+
+ const undoEvent = {
+ type: 'erase',
+ data: {
+ drawingId
+ }
+ };
+
+ this.drawingManager.pushUndoEvent(undoEvent);
+ this.lastPointTime = 0;
+ } else {
+ this.currentPolygon = null;
+ this.lastPointTime = 0;
+ }
+ }
+
+ /**
+ * Creates a drawing from a serialized version.
+ * @param {THREE.Object3D} parent - The parent object to draw in.
+ * @param {Object} drawing - The serialized object defining the object to be drawn.
+ */
+ drawFromSerialized(parent, drawing) {
+ const meshLineMaterial = generateMeshLineMaterial(drawing.size, drawing.color);
+
+ const points = drawing.points.map(point => new THREE.Vector3(point.x, point.y, point.z));
+ if (points.length >= 3) {
+ points.push(points[0]);
+ }
+ const meshLine = new MeshLine();
+ meshLine.setPoints(points);
+ const mesh = new THREE.Mesh(meshLine, meshLineMaterial);
+ mesh.raycast = MeshLineRaycast;
+ mesh.drawingId = drawing.drawingId;
+ mesh.serialized = drawing;
+ parent.add(mesh);
+ }
+
+ /**
+ * Sets the drawing size.
+ * @param {number} size - The size.
+ */
+ setSize(size) {
+ super.setSize(size);
+ this.meshLineMaterial = generateMeshLineMaterial(size, this.color);
+ }
+
+ /**
+ * Sets the drawing color.
+ * @param {string} color - The color.
+ */
+ setColor(color) {
+ super.setColor(color);
+ this.meshLineMaterial = generateMeshLineMaterial(this.size, color);
+ }
+};
+
DrawingManager.Tool.Icon = class extends DrawingManager.Tool {
/**
* Creates an Icon Tool.
diff --git a/tools/spatialDraw/index.html b/tools/spatialDraw/index.html
index 9b81195..42e9832 100644
--- a/tools/spatialDraw/index.html
+++ b/tools/spatialDraw/index.html
@@ -1,5 +1,6 @@
+
Spatial Sensor
@@ -15,12 +16,14 @@
font-weight: normal;
font-style: normal;
}
+
@font-face {
font-family: 'Roboto';
src: url('resources/roboto-bold.ttf');
font-weight: bold;
font-style: normal;
}
+
#text {
position: absolute;
left: 0;
@@ -39,27 +42,29 @@
cursor: pointer;
font-family: "Roboto", "Avenir", "Futura", Helvetica Neue, Helvetica, Arial, sans-serif;
font-size: 500%;
- color: rgb(45,255,255);
+ color: rgb(45, 255, 255);
}
+
#text:hover {
opacity: 0.75;
cursor: pointer;
}
+
#launchIcon {
- background-color: rgba(0,0,0,0);
+ background-color: rgba(0, 0, 0, 0);
width: 400px;
height: 400px;
border-radius: 50px;
}
-
+
#uiParent {
display: flex;
justify-content: start;
align-items: center;
}
-
+
#ui {
- background-color: rgba(0,0,0,0.9);
+ background-color: rgba(0, 0, 0, 0.9);
border-radius: 0 20px 20px 0;
width: fit-content;
padding: 10px;
@@ -69,12 +74,12 @@
align-items: center;
transform-origin: left;
}
-
+
.circle {
border-radius: 100%;
margin-top: 10px;
}
-
+
.circle:first-child {
margin-top: 0;
}
@@ -83,45 +88,57 @@
width: 100%;
height: 100%;
}
-
+
.color {
width: 44px;
height: 44px;
}
-
+
.color.active {
width: 36px;
height: 36px;
border: 4px solid white;
}
-
+
+ .polygon {
+ display: inline-block;
+ border-radius: 100%;
+ width: 36px;
+ height: 36px;
+ border: 4px solid slategray;
+ }
+
+ .polygon.active {
+ border: 4px solid white;
+ }
+
.erase {
width: 36px;
height: 36px;
border: 4px solid slategray;
}
-
+
.erase.active {
border: 4px solid white;
}
-
+
.size {
width: 36px;
height: 36px;
border: 4px solid white;
display: none;
}
-
+
.size.active {
display: initial;
}
-
+
.undo {
width: 36px;
height: 36px;
border: 4px solid slategray;
}
-
+
.undo.active {
border: 4px solid white;
}
@@ -152,13 +169,14 @@
width: 36px;
height: 36px;
border: 4px solid slategray;
- transform: translateY(0); /* needed to prevent fixed child from positioning relative to #ui*/
+ transform: translateY(0);
+ /* needed to prevent fixed child from positioning relative to #ui*/
}
.iconMenu.active {
border: 4px solid white;
}
-
+
.iconMenuPopout {
border-radius: 0 15px 15px 0;
padding: 8px;
@@ -166,28 +184,28 @@
top: -12px;
left: calc(100% + 14px);
width: max-content;
- background-color: rgba(0,0,0,0.9);
+ background-color: rgba(0, 0, 0, 0.9);
display: none;
}
-
+
.iconMenuPopout.active {
display: initial;
}
-
+
.cursorMenu {
margin-top: 10px;
}
-
+
.cursor {
width: 52px;
height: 52px;
background-color: darkslategray;
}
-
+
.cursor.active {
background-color: slategray;
}
-
+
.projectionCursor {
border-radius: 10px 10px 0 0;
}
@@ -195,7 +213,7 @@
.offsetCursor {
border-radius: 0 0 10px 10px;
}
-
+
.cursor svg {
width: 100%;
height: 100%;
@@ -203,29 +221,44 @@
.tool-color-gradient {
/*animation: tool-color-gradient-anim 0.3s infinite alternate;*/
- background: linear-gradient(90deg, #000000,#000000, #00ffff, #ee7752, #e73c7e, #23a6d5, #23d5ab,#000000, #000000);
+ background: linear-gradient(90deg, #000000, #000000, #00ffff, #ee7752, #e73c7e, #23a6d5, #23d5ab, #000000, #000000);
background-size: 2000% 200%;
animation: gradient 10s infinite;
border-radius: 50px;
}
@keyframes gradient {
- 0% { background-position: 0% 50%; }
- 50% { background-position: 100% 50%; }
- 100% { background-position: 0% 50%; }
+ 0% {
+ background-position: 0% 50%;
+ }
+
+ 50% {
+ background-position: 100% 50%;
+ }
+
+ 100% {
+ background-position: 0% 50%;
+ }
}
+
@@ -263,11 +296,19 @@
+
@@ -278,7 +319,10 @@
-
+
-
-
-
+
+
+
+
+
+
@@ -319,9 +378,18 @@
-
-
-
+
+
+
+
+
+
@@ -331,17 +399,31 @@
-
+
+
-
-
+
+
+
+
-
-
-
+
+
+
+
+
+
@@ -351,7 +433,8 @@
-
+
+
@@ -361,8 +444,11 @@
-
-
+
+
@@ -371,8 +457,12 @@
-
-
+
+
+
@@ -386,4 +476,5 @@
+
diff --git a/tools/spatialDraw/index.js b/tools/spatialDraw/index.js
index 0fe9709..a1d9604 100644
--- a/tools/spatialDraw/index.js
+++ b/tools/spatialDraw/index.js
@@ -1,4 +1,4 @@
-/* global DrawingManager, realGl, gl, proxies */
+/* global SpatialInterface, Envelope, DrawingManager, realGl, gl, proxies */
gl.enableWebGL2 = false;
@@ -87,6 +87,8 @@ sizeCircles.forEach((circle, i) => {
});
const colorCircles = Array.from(ui.children).filter(child => child.classList.contains('color'));
const eraseCircle = Array.from(ui.children).filter(child => child.classList.contains('erase'))[0];
+const polygonCircle = Array.from(ui.children).filter(child => child.classList.contains('polygon'))[0];
+
colorCircles.forEach(circle => {
circle.style.backgroundColor = circle.dataset.color;
circle.addEventListener('pointerdown', e => {
@@ -100,6 +102,14 @@ eraseCircle.addEventListener('pointerdown', e => {
eraseCircle.classList.add('active');
drawingManager.setEraseMode(true);
});
+polygonCircle.addEventListener('pointerdown', e => {
+ e.stopPropagation();
+ if (drawingManager.tool !== drawingManager.toolMap['POLYGON']) {
+ drawingManager.setTool(drawingManager.toolMap['POLYGON']);
+ } else {
+ drawingManager.setTool(drawingManager.toolMap['LINE']);
+ }
+});
const undoCircle = Array.from(ui.children).filter(child => child.classList.contains('undo'))[0];
undoCircle.addEventListener('pointerdown', e => {
e.stopPropagation();
@@ -234,9 +244,15 @@ function initDrawingApp() {
const toolName = Object.keys(drawingManager.toolMap).find(name => drawingManager.toolMap[name] === tool);
if (toolName === 'ICON') {
iconMenu.classList.add('active');
+ polygonCircle.classList.remove('active');
} else {
iconCircles.forEach(iconCircle => iconCircle.classList.remove('active'));
iconMenu.classList.remove('active');
+ if (toolName === 'POLYGON') {
+ polygonCircle.classList.add('active');
+ } else {
+ polygonCircle.classList.remove('active');
+ }
}
});