diff --git a/macrovision.css b/macrovision.css
index 95357089..4d7be3a8 100644
--- a/macrovision.css
+++ b/macrovision.css
@@ -290,6 +290,14 @@ select.menu-item {
color: #000;
}
+#rulers {
+ position: absolute;
+ display: block;
+ width: 100%;
+ height: 100%;
+ background: #00000000;
+}
+
#display {
display: block;
width: 100%;
@@ -720,6 +728,11 @@ option.filtered {
top: 60%;
}
+#ruler {
+ left: 0%;
+ top: 80%;
+}
+
#fit {
right: 0%;
top: 80%;
diff --git a/macrovision.html b/macrovision.html
index d50db2ca..b6310792 100644
--- a/macrovision.html
+++ b/macrovision.html
@@ -298,9 +298,14 @@
+
+
diff --git a/macrovision.js b/macrovision.js
index 75d16d3d..af56f946 100644
--- a/macrovision.js
+++ b/macrovision.js
@@ -41,6 +41,12 @@ let sizeHandle = null;
let worldSizeDirty = false;
+let rulerMode = false;
+
+let rulers = [];
+
+let currentRuler = undefined;
+
const tagDefs = {
"anthro": "Anthro",
"feral": "Feral",
@@ -532,6 +538,50 @@ function updateSizes(dirtyOnly = false) {
document.querySelector("#ground").style.top = pos2pix({x: 0, y: 0}).y + "px";
+ drawRulers();
+}
+
+function drawRulers() {
+ const canvas = document.querySelector("#rulers");
+
+ /** @type {CanvasRenderingContext2D} */
+
+ const ctx = canvas.getContext("2d");
+ ctx.canvas.width = canvas.clientWidth;
+ ctx.canvas.height = canvas.clientHeight;
+
+
+ rulers.concat(currentRuler ? [currentRuler] : []).forEach(rulerDef => {
+ ctx.save();
+ ctx.beginPath();
+ const start = pos2pix({x: rulerDef.x0, y: rulerDef.y0});
+ const end = pos2pix({x: rulerDef.x1, y: rulerDef.y1});
+ ctx.moveTo(start.x, start.y);
+ ctx.lineTo(end.x, end.y);
+ ctx.lineWidth = 5;
+ ctx.stroke();
+ const center = { x: (start.x + end.x) / 2, y: (start.y + end.y) / 2 };
+ ctx.fillStyle = "#eeeeee";
+ ctx.font = 'normal 24pt coda';
+ ctx.translate(center.x, center.y);
+ let angle = Math.atan2(end.y - start.y, end.x - start.x);
+
+ if (angle < -Math.PI/2) {
+ angle += Math.PI;
+ }
+ if (angle > Math.PI/2) {
+ angle -= Math.PI;
+ }
+ ctx.rotate(angle);
+ const offsetX = Math.cos(angle + Math.PI/2);
+ const offsetY = Math.sin(angle + Math.PI/2);
+
+ const distance = Math.sqrt(Math.pow(rulerDef.y1 - rulerDef.y0, 2) + Math.pow(rulerDef.x1 - rulerDef.x0, 2));
+ const distanceInUnits = math.unit(distance, "meters").to(document.querySelector("#options-height-unit").value);
+ const textSize = ctx.measureText(distanceInUnits.format({ precision: 3}));
+ ctx.fillText(distanceInUnits.format({ precision: 3}), -offsetX * 10 - textSize.width / 2, -offsetY*10);
+ ctx.restore();
+ });
}
function drawScales(ifDirty = false) {
@@ -1855,6 +1905,11 @@ function prepareSidebar() {
name: "Add Image",
id: "menu-add-image",
icon: "fas fa-camera"
+ },
+ {
+ name: "Clear Rulers",
+ id: "menu-clear-rulers",
+ icon: "fas fa-ruler"
}
].forEach(entry => {
const buttonHolder = document.createElement("div");
@@ -2582,13 +2637,15 @@ document.addEventListener("DOMContentLoaded", () => {
});
document.querySelector("#world").addEventListener("touchstart", e => {
- panning = true;
- panOffsetX = e.touches[0].clientX;
- panOffsetY = e.touches[0].clientY;
- e.preventDefault();
- Object.keys(entities).forEach(key => {
- document.querySelector("#entity-" + key).classList.add("no-transition");
- });
+ if (!rulerMode) {
+ panning = true;
+ panOffsetX = e.touches[0].clientX;
+ panOffsetY = e.touches[0].clientY;
+ e.preventDefault();
+ Object.keys(entities).forEach(key => {
+ document.querySelector("#entity-" + key).classList.add("no-transition");
+ });
+ }
});
document.querySelector("#world").addEventListener("touchend", e => {
@@ -2597,6 +2654,39 @@ document.addEventListener("DOMContentLoaded", () => {
document.querySelector("#entity-" + key).classList.remove("no-transition");
});
});
+ document.querySelector("#world").addEventListener("mousedown", e => {
+ // only left mouse clicks
+ if (e.which == 1 && rulerMode) {
+ let entX = document.querySelector("#entities").getBoundingClientRect().x;
+ let entY = document.querySelector("#entities").getBoundingClientRect().y;
+ const pos = pix2pos({ x: e.clientX - entX, y: e.clientY - entY });
+ currentRuler = { x0: pos.x, y0: pos.y, x1: pos.y, y1: pos.y };
+ }
+ });
+ document.querySelector("#world").addEventListener("mouseup", e => {
+ // only left mouse clicks
+ if (e.which == 1 && currentRuler) {
+ rulers.push(currentRuler);
+ currentRuler = null;
+ rulerMode = false;
+ }
+ });
+
+ document.querySelector("#world").addEventListener("touchstart", e => {
+ if (rulerMode) {
+ let entX = document.querySelector("#entities").getBoundingClientRect().x;
+ let entY = document.querySelector("#entities").getBoundingClientRect().y;
+ const pos = pix2pos({ x: e.touches[0].clientX - entX, y: e.touches[0].clientY - entY });
+ currentRuler = { x0: pos.x, y0: pos.y, x1: pos.y, y1: pos.y };
+ }
+ });
+ document.querySelector("#world").addEventListener("touchend", e => {
+ if (currentRuler) {
+ rulers.push(currentRuler);
+ currentRuler = null;
+ rulerMode = false;
+ }
+ });
document.querySelector("body").appendChild(testCtx.canvas);
@@ -2821,6 +2911,23 @@ document.addEventListener("DOMContentLoaded", () => {
sizeHandle = null;
});
+ document.querySelector("#ruler").addEventListener("click", e => {
+ rulerMode = !rulerMode;
+ if (rulerMode) {
+ toast("Ready to draw a ruler mark");
+ } else {
+ toast("Cancelled ruler mode");
+ }
+ });
+
+ document.querySelector("#ruler").addEventListener("mousedown", e => {
+ e.stopPropagation();
+ });
+
+ document.querySelector("#ruler").addEventListener("touchstart", e => {
+ e.stopPropagation();
+ });
+
document.querySelector("#fit").addEventListener("click", e => {
if (selected) {
let targets = {};
@@ -2937,6 +3044,11 @@ document.addEventListener("DOMContentLoaded", () => {
})
+ document.querySelector("#menu-clear-rulers").addEventListener("click", e => {
+ rulers = [];
+ drawRulers();
+ });
+
document.addEventListener("paste", e => {
let index = 0;
@@ -3532,6 +3644,28 @@ function clearFilter() {
});
}
+document.addEventListener("mousemove", (e) => {
+ if (currentRuler) {
+ let entX = document.querySelector("#entities").getBoundingClientRect().x;
+ let entY = document.querySelector("#entities").getBoundingClientRect().y;
+ let position = pix2pos({ x: e.clientX - entX, y: e.clientY - entY });
+ currentRuler.x1 = position.x;
+ currentRuler.y1 = position.y;
+ }
+ drawRulers();
+});
+
+document.addEventListener("touchmove", (e) => {
+ if (currentRuler) {
+ let entX = document.querySelector("#entities").getBoundingClientRect().x;
+ let entY = document.querySelector("#entities").getBoundingClientRect().y;
+ let position = pix2pos({ x: e.touches[0].clientX - entX, y: e.touches[0].clientY - entY });
+ currentRuler.x1 = position.x;
+ currentRuler.y1 = position.y;
+ }
+ drawRulers();
+});
+
document.addEventListener("mousemove", (e) => {
if (clicked) {
let position = pix2pos({ x: e.clientX - dragOffsetX, y: e.clientY - dragOffsetY });