mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-17 01:41:22 +01:00
v1.6.07 - Opisometer class
This commit is contained in:
parent
4a1dba33f8
commit
ca1acce48c
2 changed files with 214 additions and 84 deletions
|
|
@ -10,6 +10,18 @@ class Rulers {
|
|||
return ruler;
|
||||
}
|
||||
|
||||
curve(points) {
|
||||
const curve = new Opisometer(points);
|
||||
this.data.push(curve);
|
||||
return curve;
|
||||
}
|
||||
|
||||
area(points) {
|
||||
const area = new Planimeter(points);
|
||||
this.data.push(area);
|
||||
return area;
|
||||
}
|
||||
|
||||
toString() {
|
||||
const string = this.data.map(ruler => ruler.toString()).join("; ");
|
||||
return string;
|
||||
|
|
@ -21,7 +33,7 @@ class Rulers {
|
|||
for (ruler of rulers) {
|
||||
const [type, pointsString] = ruler.split(": ");
|
||||
const points = pointsString.split(" ").map(el => el.split(",").map(n => +n));
|
||||
if (type === "linear") this.linear(points);
|
||||
this[type](points);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -41,12 +53,29 @@ class Rulers {
|
|||
}
|
||||
}
|
||||
|
||||
class LinearRuler {
|
||||
class Measurer {
|
||||
constructor(points) {
|
||||
this.points = points;
|
||||
this.id = rulers.data.length;
|
||||
}
|
||||
|
||||
drag() {
|
||||
const tr = parseTransform(this.getAttribute("transform"));
|
||||
const x = +tr[0] - d3.event.x, y = +tr[1] - d3.event.y;
|
||||
|
||||
d3.event.on("drag", function() {
|
||||
const transform = `translate(${(x + d3.event.x)},${(y + d3.event.y)})`;
|
||||
this.setAttribute("transform", transform);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class LinearRuler extends Measurer {
|
||||
constructor(points) {
|
||||
super(points);
|
||||
}
|
||||
|
||||
toString() {
|
||||
return "linear" + ": " + this.points.join(" ");
|
||||
}
|
||||
|
|
@ -74,7 +103,7 @@ class LinearRuler {
|
|||
const size = rn(1 / scale ** .3 * 2, 2);
|
||||
const dash = rn(30 / distanceScaleInput.value, 2);
|
||||
|
||||
const el = this.el = ruler.append("g").attr("class", "ruler").call(d3.drag().on("start", this.drag)).attr("font-size", 10 * size)
|
||||
const el = this.el = ruler.append("g").attr("class", "ruler").call(d3.drag().on("start", this.drag)).attr("font-size", 10 * size);
|
||||
el.append("polyline").attr("points", points).attr("class", "white").attr("stroke-width", size)
|
||||
.call(d3.drag().on("start", () => this.addControl(this)));
|
||||
el.append("polyline").attr("points", points).attr("class", "gray").attr("stroke-width", rn(size * 1.2, 2)).attr("stroke-dasharray", dash);
|
||||
|
|
@ -82,6 +111,7 @@ class LinearRuler {
|
|||
el.append("text").attr("dx", ".35em").attr("dy", "-.45em").on("click", () => rulers.remove(this.id));
|
||||
this.drawPoints(el);
|
||||
this.updateLabel();
|
||||
return this;
|
||||
}
|
||||
|
||||
drawPoints(el) {
|
||||
|
|
@ -125,16 +155,6 @@ class LinearRuler {
|
|||
return length;
|
||||
}
|
||||
|
||||
drag() {
|
||||
const tr = parseTransform(this.getAttribute("transform"));
|
||||
const x = +tr[0] - d3.event.x, y = +tr[1] - d3.event.y;
|
||||
|
||||
d3.event.on("drag", function() {
|
||||
const transform = `translate(${(x + d3.event.x)},${(y + d3.event.y)})`;
|
||||
this.setAttribute("transform", transform);
|
||||
});
|
||||
}
|
||||
|
||||
dragControl(context, pointId) {
|
||||
let edge = context.isEdge(pointId)
|
||||
let circle = context.el.select(`circle:nth-child(${pointId+1})`);
|
||||
|
|
@ -194,89 +214,184 @@ class LinearRuler {
|
|||
}
|
||||
}
|
||||
|
||||
function drawOpisometer() {
|
||||
class Opisometer extends Measurer {
|
||||
constructor(points) {
|
||||
super(points);
|
||||
}
|
||||
|
||||
toString() {
|
||||
return "curve" + ": " + this.points.join(" ");
|
||||
}
|
||||
|
||||
draw() {
|
||||
if (this.el) this.el.selectAll("*").remove();
|
||||
lineGen.curve(d3.curveBasis);
|
||||
const size = rn(1 / scale ** .3 * 2, 1);
|
||||
const dash = rn(30 / distanceScaleInput.value, 2);
|
||||
const p0 = d3.mouse(this);
|
||||
const points = [[p0[0], p0[1]]];
|
||||
let length = 0;
|
||||
const context = this;
|
||||
|
||||
const rulerNew = ruler.append("g").attr("class", "opisometer").call(d3.drag().on("start", dragRuler));
|
||||
const curve = rulerNew.append("path").attr("class", "white").attr("stroke-width", size);
|
||||
const curveGray = rulerNew.append("path").attr("class", "gray").attr("stroke-width", size).attr("stroke-dasharray", dash);
|
||||
const text = rulerNew.append("text").attr("dy", "-.3em").attr("font-size", 10 * size).on("click", removeParent);
|
||||
const start = rulerNew.append("circle").attr("r", 2 * size).attr("stroke-width", .5 * size).attr("data-edge", "start").call(d3.drag().on("start", dragOpisometerEnd));
|
||||
const end = rulerNew.append("circle").attr("r", 2 * size).attr("stroke-width", .5 * size).attr("data-edge", "end").call(d3.drag().on("start", dragOpisometerEnd));
|
||||
const el = this.el = ruler.append("g").attr("class", "opisometer").call(d3.drag().on("start", this.drag)).attr("font-size", 10 * size);
|
||||
el.append("path").attr("class", "white").attr("stroke-width", size);
|
||||
el.append("path").attr("class", "gray").attr("stroke-width", size).attr("stroke-dasharray", dash);
|
||||
const rulerPoints = el.append("g").attr("class", "rulerPoints").attr("stroke-width", .5 * size).attr("font-size", 2 * size);
|
||||
rulerPoints.append("circle").attr("r", "1em").call(d3.drag().on("start", function() {context.dragControl(context, 0)}));
|
||||
rulerPoints.append("circle").attr("r", "1em").call(d3.drag().on("start", function() {context.dragControl(context, 1)}));
|
||||
el.append("text").attr("dx", ".35em").attr("dy", "-.45em").on("click", () => rulers.remove(this.id));
|
||||
|
||||
this.updateCurve();
|
||||
this.updateLabel();
|
||||
return this;
|
||||
}
|
||||
|
||||
addPoint(point) {
|
||||
const MIN_DIST = d3.event.sourceEvent.shiftKey ? 9 : 100;
|
||||
const prev = last(this.points);
|
||||
point = [point[0] | 0, point[1] | 0];
|
||||
const dist2 = (prev[0] - point[0]) ** 2 + (prev[1] - point[1]) ** 2;
|
||||
if (dist2 < MIN_DIST) return;
|
||||
this.points.push(point);
|
||||
this.updateCurve();
|
||||
this.updateLabel();
|
||||
}
|
||||
|
||||
updateCurve() {
|
||||
const path = round(lineGen(this.points));
|
||||
this.el.selectAll("path").attr("d", path);
|
||||
|
||||
const left = this.points[0];
|
||||
const right = last(this.points);
|
||||
this.el.select(".rulerPoints > circle:first-child").attr("cx", left[0]).attr("cy", left[1]);
|
||||
this.el.select(".rulerPoints > circle:last-child").attr("cx", right[0]).attr("cy", right[1]);
|
||||
}
|
||||
|
||||
updateLabel() {
|
||||
const length = this.el.select("path").node().getTotalLength();
|
||||
const text = rn(length * distanceScaleInput.value) + " " + distanceUnitInput.value;
|
||||
const [x, y] = last(this.points);
|
||||
this.el.select("text").attr("x", x).attr("y", y).text(text);
|
||||
}
|
||||
|
||||
dragControl(context, rigth) {
|
||||
const MIN_DIST = d3.event.sourceEvent.shiftKey ? 9 : 100;
|
||||
let prev = rigth ? last(context.points) : context.points[0];
|
||||
|
||||
d3.event.on("drag", function() {
|
||||
const p = d3.mouse(this);
|
||||
const diff = Math.hypot(last(points)[0] - p[0], last(points)[1] - p[1]);
|
||||
if (diff > 3) points.push([p[0], p[1]]); else return;
|
||||
const point = [d3.event.x | 0, d3.event.y | 0];
|
||||
|
||||
const path = round(lineGen(points));
|
||||
curve.attr("d", path);
|
||||
curveGray.attr("d", path);
|
||||
length = curve.node().getTotalLength();
|
||||
const label = rn(length * distanceScaleInput.value) + " " + distanceUnitInput.value;
|
||||
text.attr("x", p[0]).attr("y", p[1]).text(label);
|
||||
const dist2 = (prev[0] - point[0]) ** 2 + (prev[1] - point[1]) ** 2;
|
||||
if (dist2 < MIN_DIST) return;
|
||||
|
||||
rigth ? context.points.push(point) : context.points.unshift(point);
|
||||
prev = point;
|
||||
|
||||
context.updateCurve();
|
||||
context.updateLabel();
|
||||
});
|
||||
|
||||
d3.event.on("end", function() {
|
||||
restoreDefaultEvents();
|
||||
clearMainTip();
|
||||
addOpisometer.classList.remove("pressed");
|
||||
|
||||
const c = curve.node().getPointAtLength(length / 2);
|
||||
const p = curve.node().getPointAtLength(length / 2 - 1);
|
||||
const atan = p.x > c.x ? Math.atan2(p.y - c.y, p.x - c.x) : Math.atan2(c.y - p.y, c.x - p.x);
|
||||
const angle = rn(atan * 180 / Math.PI, 3);
|
||||
const rotate = `rotate(${angle} ${c.x} ${c.y})`;
|
||||
|
||||
rulerNew.attr("data-points", JSON.stringify(points));
|
||||
text.attr("x", c.x).attr("y", c.y).attr("transform", rotate);
|
||||
start.attr("cx", points[0][0]).attr("cy", points[0][1]);
|
||||
end.attr("cx", last(points)[0]).attr("cy", last(points)[1]);
|
||||
if (!d3.event.sourceEvent.shiftKey) context.optimize();
|
||||
});
|
||||
}
|
||||
|
||||
optimize() {
|
||||
const MIN_DIST2 = 900;
|
||||
const optimized = [];
|
||||
|
||||
for (let i=0, p1 = this.points[0]; i < this.points.length; i++) {
|
||||
const p2 = this.points[i];
|
||||
const dist2 = !i || i === this.points.length-1 ? Infinity : (p2[0] - p1[0]) ** 2 + (p2[1] - p1[1]) ** 2;
|
||||
if (dist2 < MIN_DIST2) continue;
|
||||
optimized.push(p2);
|
||||
p1 = p2;
|
||||
}
|
||||
|
||||
this.points = optimized;
|
||||
this.updateCurve();
|
||||
this.updateLabel();
|
||||
}
|
||||
|
||||
undraw() {
|
||||
this.el.remove();
|
||||
}
|
||||
}
|
||||
|
||||
function dragOpisometerEnd() {
|
||||
const ruler = d3.select(this.parentNode);
|
||||
const curve = ruler.select(".white");
|
||||
const curveGray = ruler.select(".gray");
|
||||
const text = ruler.select("text");
|
||||
class Planimeter extends Measurer {
|
||||
constructor(points) {
|
||||
super(points);
|
||||
}
|
||||
|
||||
const points = JSON.parse(ruler.attr("data-points"));
|
||||
const x0 = +this.getAttribute("cx"), y0 = +this.getAttribute("cy");
|
||||
if (x0 === points[0][0] && y0 === points[0][1]) points.reverse();
|
||||
toString() {
|
||||
return "area" + ": " + this.points.join(" ");
|
||||
}
|
||||
|
||||
draw() {
|
||||
if (this.el) this.el.selectAll("*").remove();
|
||||
lineGen.curve(d3.curveBasis);
|
||||
let length = 0;
|
||||
const size = rn(1 / scale ** .3 * 2, 1);
|
||||
const dash = rn(30 / distanceScaleInput.value, 2);
|
||||
const context = this;
|
||||
|
||||
d3.event.on("drag", function() {
|
||||
const p = d3.mouse(this);
|
||||
d3.select(this).attr("cx", p[0]).attr("cy", p[1]);
|
||||
const el = this.el = ruler.append("g").attr("class", "opisometer").call(d3.drag().on("start", this.drag)).attr("font-size", 10 * size);
|
||||
el.append("path").attr("class", "white").attr("stroke-width", size);
|
||||
el.append("path").attr("class", "gray").attr("stroke-width", size).attr("stroke-dasharray", dash);
|
||||
const rulerPoints = el.append("g").attr("class", "rulerPoints").attr("stroke-width", .5 * size).attr("font-size", 2 * size);
|
||||
rulerPoints.append("circle").attr("r", "1em").call(d3.drag().on("start", function() {context.dragControl(context, 0)}));
|
||||
rulerPoints.append("circle").attr("r", "1em").call(d3.drag().on("start", function() {context.dragControl(context, 1)}));
|
||||
el.append("text").attr("dx", ".35em").attr("dy", "-.45em").on("click", () => rulers.remove(this.id));
|
||||
|
||||
const diff = Math.hypot(last(points)[0] - p[0], last(points)[1] - p[1]);
|
||||
if (diff > 3) points.push([p[0], p[1]]); else return;
|
||||
this.updateCurve();
|
||||
this.updateLabel();
|
||||
return this;
|
||||
}
|
||||
|
||||
const path = round(lineGen(points));
|
||||
curve.attr("d", path);
|
||||
curveGray.attr("d", path);
|
||||
length = curve.node().getTotalLength();
|
||||
const label = rn(length * distanceScaleInput.value) + " " + distanceUnitInput.value;
|
||||
text.text(label);
|
||||
});
|
||||
addPoint(point) {
|
||||
const MIN_DIST = d3.event.sourceEvent.shiftKey ? 9 : 100;
|
||||
const prev = last(this.points);
|
||||
point = [point[0] | 0, point[1] | 0];
|
||||
const dist2 = (prev[0] - point[0]) ** 2 + (prev[1] - point[1]) ** 2;
|
||||
if (dist2 < MIN_DIST) return;
|
||||
this.points.push(point);
|
||||
this.updateCurve();
|
||||
this.updateLabel();
|
||||
}
|
||||
|
||||
d3.event.on("end", function() {
|
||||
const c = curve.node().getPointAtLength(length / 2);
|
||||
const p = curve.node().getPointAtLength(length / 2 - 1);
|
||||
const atan = p.x > c.x ? Math.atan2(p.y - c.y, p.x - c.x) : Math.atan2(c.y - p.y, c.x - p.x);
|
||||
const angle = rn(atan * 180 / Math.PI, 3);
|
||||
const rotate = `rotate(${angle} ${c.x} ${c.y})`;
|
||||
updateCurve() {
|
||||
const path = round(lineGen(this.points));
|
||||
this.el.selectAll("path").attr("d", path);
|
||||
|
||||
ruler.attr("data-points", JSON.stringify(points));
|
||||
text.attr("x", c.x).attr("y", c.y).attr("transform", rotate);
|
||||
});
|
||||
const left = this.points[0];
|
||||
const right = last(this.points);
|
||||
this.el.select(".rulerPoints > circle:first-child").attr("cx", left[0]).attr("cy", left[1]);
|
||||
this.el.select(".rulerPoints > circle:last-child").attr("cx", right[0]).attr("cy", right[1]);
|
||||
}
|
||||
|
||||
updateLabel() {
|
||||
const length = this.el.select("path").node().getTotalLength();
|
||||
const text = rn(length * distanceScaleInput.value) + " " + distanceUnitInput.value;
|
||||
const [x, y] = last(this.points);
|
||||
this.el.select("text").attr("x", x).attr("y", y).text(text);
|
||||
}
|
||||
|
||||
optimize() {
|
||||
const MIN_DIST2 = 900;
|
||||
const optimized = [];
|
||||
|
||||
for (let i=0, p1 = this.points[0]; i < this.points.length; i++) {
|
||||
const p2 = this.points[i];
|
||||
const dist2 = !i || i === this.points.length-1 ? Infinity : (p2[0] - p1[0]) ** 2 + (p2[1] - p1[1]) ** 2;
|
||||
if (dist2 < MIN_DIST2) continue;
|
||||
optimized.push(p2);
|
||||
p1 = p2;
|
||||
}
|
||||
|
||||
this.points = optimized;
|
||||
this.updateCurve();
|
||||
this.updateLabel();
|
||||
}
|
||||
|
||||
undraw() {
|
||||
this.el.remove();
|
||||
}
|
||||
}
|
||||
|
||||
function drawPlanimeter() {
|
||||
|
|
|
|||
|
|
@ -219,10 +219,25 @@ function editUnits() {
|
|||
this.classList.remove("pressed");
|
||||
} else {
|
||||
if (!layerIsOn("toggleRulers")) toggleRulers();
|
||||
tip("Draw a curve to measure its length", true);
|
||||
tip("Draw a curve to measure length. Hold Shift to disallow path optimization", true);
|
||||
unitsBottom.querySelectorAll(".pressed").forEach(button => button.classList.remove("pressed"));
|
||||
this.classList.add("pressed");
|
||||
viewbox.style("cursor", "crosshair").call(d3.drag().on("start", drawOpisometer));
|
||||
viewbox.style("cursor", "crosshair").call(d3.drag().on("start", function() {
|
||||
const point = d3.mouse(this);
|
||||
const opisometer = rulers.curve([point]).draw();
|
||||
|
||||
d3.event.on("drag", function() {
|
||||
const point = d3.mouse(this);
|
||||
opisometer.addPoint(point);
|
||||
});
|
||||
|
||||
d3.event.on("end", function() {
|
||||
restoreDefaultEvents();
|
||||
clearMainTip();
|
||||
addOpisometer.classList.remove("pressed");
|
||||
if (!d3.event.sourceEvent.shiftKey) opisometer.optimize();
|
||||
});
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue