mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-17 09:41:24 +01:00
v1.6.08 - Planimeter class
This commit is contained in:
parent
ca1acce48c
commit
130cc7ebaf
3 changed files with 98 additions and 160 deletions
2
main.js
2
main.js
|
|
@ -1028,7 +1028,7 @@ function drawCoastline() {
|
||||||
if (f === largestLand) {
|
if (f === largestLand) {
|
||||||
const from = points[d3.scan(points, (a, b) => a[0] - b[0])];
|
const from = points[d3.scan(points, (a, b) => a[0] - b[0])];
|
||||||
const to = points[d3.scan(points, (a, b) => b[0] - a[0])];
|
const to = points[d3.scan(points, (a, b) => b[0] - a[0])];
|
||||||
rulers.linear([from, to]);
|
rulers.create(Ruler, [from, to]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,39 +1,29 @@
|
||||||
// UI measurers: rulers (linear, curve, area) and Scale Bar
|
|
||||||
class Rulers {
|
class Rulers {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.data = [];
|
this.data = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
linear(points) {
|
create(Type, points) {
|
||||||
const ruler = new LinearRuler(points);
|
const ruler = new Type(points);
|
||||||
this.data.push(ruler);
|
this.data.push(ruler);
|
||||||
return ruler;
|
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() {
|
toString() {
|
||||||
const string = this.data.map(ruler => ruler.toString()).join("; ");
|
return this.data.map(ruler => ruler.toString()).join("; ");
|
||||||
return string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fromString(string) {
|
fromString(string) {
|
||||||
this.data = [];
|
this.data = [];
|
||||||
|
|
||||||
const rulers = string.split("; ");
|
const rulers = string.split("; ");
|
||||||
for (ruler of rulers) {
|
for (const rulerString of rulers) {
|
||||||
const [type, pointsString] = ruler.split(": ");
|
const [type, pointsString] = rulerString.split(": ");
|
||||||
const points = pointsString.split(" ").map(el => el.split(",").map(n => +n));
|
const points = pointsString.split(" ").map(el => el.split(",").map(n => +n));
|
||||||
this[type](points);
|
const Type = type === "Ruler" ? Ruler :
|
||||||
|
type === "Opisometer" ? Opisometer :
|
||||||
|
type === "Planimeter" ? Planimeter : null;
|
||||||
|
this.create(Type, points);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -46,6 +36,8 @@ class Rulers {
|
||||||
}
|
}
|
||||||
|
|
||||||
remove(id) {
|
remove(id) {
|
||||||
|
if (id === undefined) return;
|
||||||
|
|
||||||
const ruler = this.data.find(ruler => ruler.id === id);
|
const ruler = this.data.find(ruler => ruler.id === id);
|
||||||
ruler.undraw();
|
ruler.undraw();
|
||||||
const rulerIndex = this.data.indexOf(ruler);
|
const rulerIndex = this.data.indexOf(ruler);
|
||||||
|
|
@ -59,6 +51,18 @@ class Measurer {
|
||||||
this.id = rulers.data.length;
|
this.id = rulers.data.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
return this.constructor.name + ": " + this.points.join(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
getSize() {
|
||||||
|
return rn(1 / scale ** .3 * 2, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
getDash() {
|
||||||
|
return rn(30 / distanceScaleInput.value, 2);
|
||||||
|
}
|
||||||
|
|
||||||
drag() {
|
drag() {
|
||||||
const tr = parseTransform(this.getAttribute("transform"));
|
const tr = parseTransform(this.getAttribute("transform"));
|
||||||
const x = +tr[0] - d3.event.x, y = +tr[1] - d3.event.y;
|
const x = +tr[0] - d3.event.x, y = +tr[1] - d3.event.y;
|
||||||
|
|
@ -69,17 +73,45 @@ class Measurer {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
class LinearRuler extends Measurer {
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class Ruler extends Measurer {
|
||||||
constructor(points) {
|
constructor(points) {
|
||||||
super(points);
|
super(points);
|
||||||
}
|
}
|
||||||
|
|
||||||
toString() {
|
|
||||||
return "linear" + ": " + this.points.join(" ");
|
|
||||||
}
|
|
||||||
|
|
||||||
getPointsString() {
|
getPointsString() {
|
||||||
return this.points.join(" ");
|
return this.points.join(" ");
|
||||||
}
|
}
|
||||||
|
|
@ -92,7 +124,7 @@ class LinearRuler extends Measurer {
|
||||||
return this.points.findIndex(el => el[0] == x && el[1] == y);
|
return this.points.findIndex(el => el[0] == x && el[1] == y);
|
||||||
}
|
}
|
||||||
|
|
||||||
addPoint(i) {
|
pushPoint(i) {
|
||||||
const [x, y] = this.points[i];
|
const [x, y] = this.points[i];
|
||||||
i ? this.points.push([x, y]) : this.points.unshift([x, y]);
|
i ? this.points.push([x, y]) : this.points.unshift([x, y]);
|
||||||
}
|
}
|
||||||
|
|
@ -100,8 +132,8 @@ class LinearRuler extends Measurer {
|
||||||
draw() {
|
draw() {
|
||||||
if (this.el) this.el.selectAll("*").remove();
|
if (this.el) this.el.selectAll("*").remove();
|
||||||
const points = this.getPointsString();
|
const points = this.getPointsString();
|
||||||
const size = rn(1 / scale ** .3 * 2, 2);
|
const size = this.getSize();
|
||||||
const dash = rn(30 / distanceScaleInput.value, 2);
|
const dash = this.getDash();
|
||||||
|
|
||||||
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)
|
el.append("polyline").attr("points", points).attr("class", "white").attr("stroke-width", size)
|
||||||
|
|
@ -167,7 +199,7 @@ class LinearRuler extends Measurer {
|
||||||
d3.event.on("drag", function() {
|
d3.event.on("drag", function() {
|
||||||
if (edge) {
|
if (edge) {
|
||||||
if (d3.event.dx < .1 && d3.event.dy < .1) return;
|
if (d3.event.dx < .1 && d3.event.dy < .1) return;
|
||||||
context.addPoint(pointId);
|
context.pushPoint(pointId);
|
||||||
context.drawPoints(context.el);
|
context.drawPoints(context.el);
|
||||||
if (pointId) pointId++;
|
if (pointId) pointId++;
|
||||||
circle = context.el.select(`circle:nth-child(${pointId+1})`);
|
circle = context.el.select(`circle:nth-child(${pointId+1})`);
|
||||||
|
|
@ -219,15 +251,10 @@ class Opisometer extends Measurer {
|
||||||
super(points);
|
super(points);
|
||||||
}
|
}
|
||||||
|
|
||||||
toString() {
|
|
||||||
return "curve" + ": " + this.points.join(" ");
|
|
||||||
}
|
|
||||||
|
|
||||||
draw() {
|
draw() {
|
||||||
if (this.el) this.el.selectAll("*").remove();
|
if (this.el) this.el.selectAll("*").remove();
|
||||||
lineGen.curve(d3.curveBasis);
|
const size = this.getSize();
|
||||||
const size = rn(1 / scale ** .3 * 2, 1);
|
const dash = this.getDash();
|
||||||
const dash = rn(30 / distanceScaleInput.value, 2);
|
|
||||||
const context = this;
|
const context = this;
|
||||||
|
|
||||||
const el = this.el = ruler.append("g").attr("class", "opisometer").call(d3.drag().on("start", this.drag)).attr("font-size", 10 * size);
|
const el = this.el = ruler.append("g").attr("class", "opisometer").call(d3.drag().on("start", this.drag)).attr("font-size", 10 * size);
|
||||||
|
|
@ -243,18 +270,8 @@ class Opisometer extends Measurer {
|
||||||
return this;
|
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() {
|
updateCurve() {
|
||||||
|
lineGen.curve(d3.curveCatmullRom.alpha(.5));
|
||||||
const path = round(lineGen(this.points));
|
const path = round(lineGen(this.points));
|
||||||
this.el.selectAll("path").attr("d", path);
|
this.el.selectAll("path").attr("d", path);
|
||||||
|
|
||||||
|
|
@ -292,27 +309,6 @@ class Opisometer extends Measurer {
|
||||||
if (!d3.event.sourceEvent.shiftKey) context.optimize();
|
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Planimeter extends Measurer {
|
class Planimeter extends Measurer {
|
||||||
|
|
@ -320,111 +316,37 @@ class Planimeter extends Measurer {
|
||||||
super(points);
|
super(points);
|
||||||
}
|
}
|
||||||
|
|
||||||
toString() {
|
|
||||||
return "area" + ": " + this.points.join(" ");
|
|
||||||
}
|
|
||||||
|
|
||||||
draw() {
|
draw() {
|
||||||
if (this.el) this.el.selectAll("*").remove();
|
if (this.el) this.el.selectAll("*").remove();
|
||||||
lineGen.curve(d3.curveBasis);
|
const size = this.getSize();
|
||||||
const size = rn(1 / scale ** .3 * 2, 1);
|
|
||||||
const dash = rn(30 / distanceScaleInput.value, 2);
|
|
||||||
const context = this;
|
|
||||||
|
|
||||||
const el = this.el = ruler.append("g").attr("class", "opisometer").call(d3.drag().on("start", this.drag)).attr("font-size", 10 * size);
|
const el = this.el = ruler.append("g").attr("class", "planimeter").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", "planimeter").attr("stroke-width", size);
|
||||||
el.append("path").attr("class", "gray").attr("stroke-width", size).attr("stroke-dasharray", dash);
|
el.append("text").on("click", () => rulers.remove(this.id));
|
||||||
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.updateCurve();
|
||||||
this.updateLabel();
|
this.updateLabel();
|
||||||
return this;
|
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() {
|
updateCurve() {
|
||||||
|
lineGen.curve(d3.curveCatmullRomClosed.alpha(.5));
|
||||||
const path = round(lineGen(this.points));
|
const path = round(lineGen(this.points));
|
||||||
this.el.selectAll("path").attr("d", path);
|
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() {
|
updateLabel() {
|
||||||
const length = this.el.select("path").node().getTotalLength();
|
if (this.points.length < 3) return;
|
||||||
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 polygonArea = rn(Math.abs(d3.polygonArea(this.points)));
|
||||||
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() {
|
|
||||||
lineGen.curve(d3.curveBasisClosed);
|
|
||||||
const size = rn(1 / scale ** .3 * 2, 1);
|
|
||||||
const p0 = d3.mouse(this);
|
|
||||||
const points = [[p0[0], p0[1]]];
|
|
||||||
|
|
||||||
const rulerNew = ruler.append("g").attr("class", "planimeter").call(d3.drag().on("start", dragRuler));
|
|
||||||
const curve = rulerNew.append("path").attr("class", "planimeter").attr("stroke-width", size);
|
|
||||||
const text = rulerNew.append("text").attr("font-size", 10 * size).on("click", removeParent);
|
|
||||||
|
|
||||||
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 > 5) points.push([p[0], p[1]]); else return;
|
|
||||||
curve.attr("d", round(lineGen(points)));
|
|
||||||
});
|
|
||||||
|
|
||||||
d3.event.on("end", function() {
|
|
||||||
restoreDefaultEvents();
|
|
||||||
clearMainTip();
|
|
||||||
addPlanimeter.classList.remove("pressed");
|
|
||||||
|
|
||||||
const polygonArea = rn(Math.abs(d3.polygonArea(points)));
|
|
||||||
const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value;
|
const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value;
|
||||||
const area = si(polygonArea * distanceScaleInput.value ** 2) + " " + unit;
|
const area = si(polygonArea * distanceScaleInput.value ** 2) + " " + unit;
|
||||||
const c = polylabel([points], 1.0); // pole of inaccessibility
|
const c = polylabel([this.points], 1.0);
|
||||||
text.attr("x", c[0]).attr("y", c[1]).text(area);
|
this.el.select("text").attr("x", c[0]).attr("y", c[1]).text(area);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// draw scale bar
|
// Scale bar
|
||||||
function drawScaleBar() {
|
function drawScaleBar() {
|
||||||
if (scaleBar.style("display") === "none") return; // no need to re-draw hidden element
|
if (scaleBar.style("display") === "none") return; // no need to re-draw hidden element
|
||||||
scaleBar.selectAll("*").remove(); // fully redraw every time
|
scaleBar.selectAll("*").remove(); // fully redraw every time
|
||||||
|
|
|
||||||
|
|
@ -209,7 +209,7 @@ function editUnits() {
|
||||||
const dy = (rulers.data.length * 40) % (graphHeight / 2);
|
const dy = (rulers.data.length * 40) % (graphHeight / 2);
|
||||||
const from = [p.x-dx | 0, p.y+dy | 0];
|
const from = [p.x-dx | 0, p.y+dy | 0];
|
||||||
const to = [p.x+dx | 0, p.y+dy | 0];
|
const to = [p.x+dx | 0, p.y+dy | 0];
|
||||||
rulers.linear([from, to]).draw();
|
rulers.create(Ruler, [from, to]).draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleOpisometerMode() {
|
function toggleOpisometerMode() {
|
||||||
|
|
@ -224,7 +224,7 @@ function editUnits() {
|
||||||
this.classList.add("pressed");
|
this.classList.add("pressed");
|
||||||
viewbox.style("cursor", "crosshair").call(d3.drag().on("start", function() {
|
viewbox.style("cursor", "crosshair").call(d3.drag().on("start", function() {
|
||||||
const point = d3.mouse(this);
|
const point = d3.mouse(this);
|
||||||
const opisometer = rulers.curve([point]).draw();
|
const opisometer = rulers.create(Opisometer, [point]).draw();
|
||||||
|
|
||||||
d3.event.on("drag", function() {
|
d3.event.on("drag", function() {
|
||||||
const point = d3.mouse(this);
|
const point = d3.mouse(this);
|
||||||
|
|
@ -248,10 +248,26 @@ function editUnits() {
|
||||||
this.classList.remove("pressed");
|
this.classList.remove("pressed");
|
||||||
} else {
|
} else {
|
||||||
if (!layerIsOn("toggleRulers")) toggleRulers();
|
if (!layerIsOn("toggleRulers")) toggleRulers();
|
||||||
tip("Draw a line to measure its inner area", true);
|
tip("Draw a curve to measure its area. Hold Shift to disallow path optimization", true);
|
||||||
unitsBottom.querySelectorAll(".pressed").forEach(button => button.classList.remove("pressed"));
|
unitsBottom.querySelectorAll(".pressed").forEach(button => button.classList.remove("pressed"));
|
||||||
this.classList.add("pressed");
|
this.classList.add("pressed");
|
||||||
viewbox.style("cursor", "crosshair").call(d3.drag().on("start", drawPlanimeter));
|
viewbox.style("cursor", "crosshair").call(d3.drag().on("start", function() {
|
||||||
|
const point = d3.mouse(this);
|
||||||
|
const planimeter = rulers.create(Planimeter, [point]).draw();
|
||||||
|
|
||||||
|
d3.event.on("drag", function() {
|
||||||
|
const point = d3.mouse(this);
|
||||||
|
planimeter.addPoint(point);
|
||||||
|
});
|
||||||
|
|
||||||
|
d3.event.on("end", function() {
|
||||||
|
restoreDefaultEvents();
|
||||||
|
clearMainTip();
|
||||||
|
addPlanimeter.classList.remove("pressed");
|
||||||
|
if (!d3.event.sourceEvent.shiftKey) planimeter.optimize();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue