Commit ca2f6db2 authored by Henrik Tramberend's avatar Henrik Tramberend
Browse files

Add infinite dashed ruler lines

parent 7152903b
......@@ -93,7 +93,7 @@ g.renderSvg(anchor, 600, 400, segment);
## {.right .fragment}
![$\mathbf{\overrightarrow{d}}_r=-\mathbf{\overrightarrow{d}}_i+2(\mathbf{\overrightarrow{d}}_i\cdot{\mathbf{\overrightarrow{n}}})\mathbf{\overrightarrow{n}}$](./unfold.js){.run
![$\mathbf{d}_r=-\mathbf{d}_i+2(\mathbf{d}_i\cdot{\mathbf{n}})\mathbf{n}$](./unfold.js){.run
width="100%"}
------------------------------------------------------------------------
......@@ -165,3 +165,16 @@ g.renderSvg(anchor, 1200, 500, g.group(
g.label(s, "HHHH", "nw"),
));
```
# Infinite Line
``` {.javascript .run}
import * as g from "./static/geometry.js";
let p = g.point(100,150, "drag");
let q = g.point(400,150, "drag");
g.renderSvg(anchor, 500, 300, g.group(
g.line(p, q, "infinite")
));
```
......@@ -37,27 +37,23 @@
let center = point(150, 150, "drag");
let intersection = intersect(
line(upperLeft, point(240, 60, "drag"), {
end: "arrow",
}),
line(upperLeft, point(240, 60, "drag"), "arrow"),
circle(center, 60)
);
// let surfaceNormal = vector(150, 440, 0, -120);
let surfaceAnchor = point(150, 440, "drag");
let surfaceNormalTip = point(150, 320, "invisible");
let surfaceNormal = vector(surfaceAnchor, 0, -120, {
end: "vec-arrow",
});
let surfaceNormal = vector(surfaceAnchor, 0, -120);
let wi = point(60, 360, "drag");
let mwi = label(mirror(surfaceAnchor, wi), "-ωᵢ", "ne");
let projection = line(
surfaceAnchor,
label(project(surfaceNormal.p1, surfaceNormal.p2, wi), "b"),
{ end: "arrow" }
"arrow"
);
let proj1 = sum(mwi, projection, { end: "arrow" });
let proj2 = sum(proj1.p2, projection, { end: "arrow" });
let proj1 = sum(mwi, projection, "arrow");
let proj2 = sum(proj1.p2, projection, "arrow");
renderSvg(
600,
......@@ -77,13 +73,13 @@
group(
surface(surfaceAnchor, 200),
surfaceNormal,
line(surfaceAnchor, wi, { end: "arrow" }),
line(surfaceAnchor, wi, "arrow"),
label(
unfold(
60,
320,
projection,
line(surfaceAnchor, mwi, { end: "arrow" }),
line(surfaceAnchor, mwi, "arrow"),
proj1,
proj2,
line(surfaceAnchor, label(proj2.p2, "ωᵣ", "ne"), {
......
......@@ -12,7 +12,6 @@ let root = g.group(
intersection.p2,
intersection.n2,
intersection.p1,
intersection,
g.line(center, intersection.p1),
g.swtch(
intersection.p1,
......
import * as g from "./static/geometry.js";
let surfaceAnchor = g.point(300, 230);
let surfaceNormal = g.vector(surfaceAnchor, 0, -200, {
end: "vec-arrow",
});
let surfaceNormal = g.vector(surfaceAnchor, 0, -200);
let wi = g.point(80, 100, "drag");
let mwi = g.mirror(surfaceAnchor, wi);
let projection = g.line(
surfaceAnchor,
g.project(surfaceNormal.p1, surfaceNormal.p2, wi),
{ end: "arrow" }
g.project(surfaceNormal.p1, surfaceNormal.p2, wi, "line"),
"arrow"
);
let proj1 = g.sum(mwi, projection, { end: "arrow" });
let proj2 = g.sum(proj1.p2, projection, { end: "arrow" });
let proj1 = g.sum(mwi, projection, "arrow");
let proj2 = g.sum(proj1.p2, projection, "arrow");
let root = g.group(
g.surface(surfaceAnchor, 500),
surfaceNormal,
g.line(surfaceAnchor, wi, { end: "arrow" }),
g.line(surfaceAnchor, wi, "arrow"),
projection,
g.line(surfaceAnchor, mwi, { end: "arrow" }),
g.line(surfaceAnchor, mwi, "arrow"),
proj1,
proj2,
g.line(surfaceAnchor, proj2.p2, {
end: "arrow",
})
g.line(wi, projection.p2, "infinite"),
g.line(surfaceAnchor, proj2.p2, "arrow")
);
g.renderSvg(anchor, 600, 400, root);
// A fast Skala line clipper https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.302.7789&rep=rep1&type=pdf
// Adapted from https://github.com/oberbichler/skalaclip-js
export { Clipper };
function Point(x, y) {
return { x, y };
}
function Vector(x, y, z) {
return { x, y, z };
}
function pointCross(a, b) {
return Vector(a.y - b.y, b.x - a.x, a.x * b.y - a.y * b.x);
}
function cross(u, v) {
const z = u.x * v.y - u.y * v.x;
return Point((u.y * v.z - u.z * v.y) / z, (u.z * v.x - u.x * v.z) / z);
}
function dot(u, v) {
return u.x * v.x + u.y * v.y + u.z * v.z;
}
const mask = [0, 1, 2, 2, 4, 0, 4, 4, 8, 1, 0, 2, 8, 1, 8, 0];
const tab1 = [4, 3, 0, 3, 1, 4, 0, 3, 2, 2, 4, 2, 1, 1, 0, 4];
const tab2 = [4, 0, 1, 1, 2, 4, 2, 2, 3, 0, 4, 1, 3, 0, 3, 4];
class Clipper {
constructor(a, b) {
if (a.x < b.x) {
this.x_min = a.x;
this.x_max = b.x;
} else {
this.x_min = b.x;
this.x_max = a.x;
}
if (a.y < b.y) {
this.y_min = a.y;
this.y_max = b.y;
} else {
this.y_min = b.y;
this.y_max = a.y;
}
this.x = [
Vector(this.x_min, this.y_min, 1),
Vector(this.x_max, this.y_min, 1),
Vector(this.x_max, this.y_max, 1),
Vector(this.x_min, this.y_max, 1),
];
this.e = [
Vector(0, 1, -this.y_min),
Vector(1, 0, -this.x_max),
Vector(0, 1, -this.y_max),
Vector(1, 0, -this.x_min),
];
}
code(pt) {
let c = 0;
if (pt.x < this.x_min) {
c = 8;
} else if (pt.x > this.x_max) {
c = 2;
}
if (pt.y < this.y_min) {
c |= 1;
} else if (pt.y > this.y_max) {
c |= 4;
}
return c;
}
clipLine(xA, xB) {
const cA = this.code(xA);
const cB = this.code(xB);
// if ((cA | cB) === 0) {
// return [0, xA, xB];
// }
if ((cA & cB) !== 0) {
return [-1, xA, xB];
}
const p = pointCross(xA, xB);
let c = 0;
for (let k = 0; k < 4; k += 1) {
if (dot(p, this.x[k]) <= 0) {
c |= 1 << k;
}
}
if (c === 0 || c === 15) {
return [-1, xA, xB];
}
const i = tab1[c];
const j = tab2[c];
if (true || cA !== 0 && cB !== 0) {
const newXA = cross(p, this.e[i]);
const newXB = cross(p, this.e[j]);
return [3, newXA, newXB];
}
if (cA === 0) {
let newXB = null;
if ((cB & mask[c]) === 0) {
newXB = cross(p, this.e[i]);
} else {
newXB = cross(p, this.e[j]);
}
return [2, xA, newXB];
}
if (cB === 0) {
let newXA = null;
if ((cA & mask[c]) === 0) {
newXA = cross(p, this.e[i]);
} else {
newXA = cross(p, this.e[j]);
}
return [1, newXA, xB];
}
}
}
:root {
--stroke-width: 4px;
--stroke-width-handle: calc(var(--stroke-width) / 2);
}
#drawing {
margin: 0;
padding: 0;
......@@ -12,17 +17,17 @@ svg.geometry {
}
marker[id="arrow"] {
fill: black;
fill: black; /* SVG 2: context-fill*/
}
marker[id="vec-arrow"] {
fill: darkblue;
fill: darkblue; /* SVG 2: context-fill*/
}
circle.handle {
stroke: none;
stroke-dasharray: "5,5";
stroke-width: 2px;
stroke-width: var(--stroke-width-handle);
fill: white;
fill-opacity: 0;
}
......@@ -34,13 +39,13 @@ circle.handle:hover {
circle.handle:active {
stroke: gray;
stroke-width: 4px;
stroke-width: var(--stroke-width);
/* cursor: grab; */
}
circle.point {
stroke: black;
stroke-width: 6px;
stroke-width: var(--stroke-width);
fill: lightgray;
}
......@@ -52,26 +57,49 @@ circle.point.drag {
stroke: darkgreen;
}
line[id].line {
circle.point.red {
stroke: red;
}
circle.point.strong {
stroke-width: calc(var(--stroke-width) * 1.5);
}
line.line {
stroke: black;
stroke-width: 6px;
stroke-width: var(--stroke-width);
}
line[id].vector {
line.line.infinite {
stroke: black;
stroke-width: calc(var(--stroke-width) / 2);
stroke-linecap: round;
stroke-dasharray: calc(var(--stroke-width) * 2);
}
line.vector {
stroke: darkblue;
stroke-width: 6px;
stroke-width: var(--stroke-width);
}
line.line.computed {
stroke-width: calc(var(--stroke-width) * 1.5);
}
line.vector.computed {
stroke-width: calc(var(--stroke-width) * 1.5);
}
path[id].bezier {
stroke: darkred;
stroke-width: 9px;
stroke-width: calc(var(--stroke-width) * 1.5);
fill: none;
}
path[id].play {
fill: lightgray;
stroke: white;
stroke-width: 2;
stroke-width: var(--stroke-width-handle);
}
path[id].play:hover {
......@@ -84,7 +112,7 @@ path[id].play:active {
circle[id].circle {
stroke: black;
stroke-width: 6px;
stroke-width: var(--stroke-width);
fill: none;
}
......@@ -102,7 +130,7 @@ text.label {
g[id].surface line {
stroke: black;
stroke-width: 6px;
stroke-width: var(--stroke-width);
}
g[id].surface rect {
......
......@@ -20,20 +20,27 @@ export {
};
import "./d3.v6.min.js";
import { Clipper } from "./clip.js";
const defaults = {
point: { r: 9, opts: ["static"] }, // one of ["static", "drag", "computed"]
point: { r: 8, opts: [] }, // one of [, "drag", "computed"]
line: {
kind: "segment",
start: null,
end: null,
opts: [],
},
vector: {
end: "arrow",
opts: ["arrow"],
},
circle: {
opts: [],
},
surface: {
opts: [],
},
project: {
opts: ["line"],
},
circle: {},
unit: 60,
arrow: { w: 5, h: 5 },
arrow: { w: 5, h: 4 },
};
let nextId = 0;
......@@ -45,9 +52,12 @@ class Shape {
this.complete = true;
this.id = nextId++;
this.zIndex = z;
this.parent = null;
}
evaluate() {
// Make sure to evaluate a possible parent.
if (this.parent) this.parent.evaluate();
return this.complete;
}
......@@ -55,8 +65,12 @@ class Shape {
return dependencies.reduce((a, s) => a && s.evaluate(), true);
}
// Include the parents flat rep here, because it might not be in the tree
// already, for example a computing node where only the results are part of
// the tree.
flat() {
return this.complete ? [this] : [];
let pflat = this.parent ? this.parent.flat() : [];
return this.complete ? [this, ...pflat] : pflat;
}
}
......@@ -81,6 +95,7 @@ class Point extends Shape {
}
svg() {
let classes = this.opts.concat("point").join(" ");
let g = d3
.create("svg:g")
.attr("id", this.id)
......@@ -89,19 +104,20 @@ class Point extends Shape {
if (this.opts.includes("drag")) {
g.attr("class", "handle");
g.append(() => v.node()).attr("class", "point drag");
g.append(() => v.node()).attr("class", classes);
g.append("svg:circle")
.attr("class", "handle")
.attr("r", this.r * 2);
} else if (this.opts.includes("computed")) {
g.append(() => v.node()).attr("class", "point computed");
g.append(() => v.node()).attr("class", classes);
} else if (this.opts.includes("hidden")) {
g.attr("class", "handle");
g.append("svg:circle")
.attr("class", "handle")
.attr("r", this.r * 2);
} else if (this.opts.includes("static")) {
g.append(() => v.node()).attr("class", "point view");
} else if (this.opts.includes("invisible")) {
} else {
g.append(() => v.node()).attr("class", classes);
}
return g.node();
}
......@@ -112,12 +128,12 @@ function point(...args) {
}
class Text extends Shape {
constructor(x, y, text, opts = {}) {
constructor(x, y, text, ...opts) {
super();
this.x = x;
this.y = y;
this.text = text;
this.opts = { ...defaults.point, ...opts };
this.opts = opts.length == 0 ? defaults.text.opts : opts;
}
update(element) {
......@@ -141,11 +157,11 @@ function text(...args) {
}
class Line extends Shape {
constructor(p1, p2, opts = {}) {
constructor(p1, p2, ...opts) {
super();
this.p1 = p1;
this.p2 = p2;
this.opts = { ...defaults.line, ...opts };
this.opts = opts.length == 0 ? defaults.line.opts : opts;
this.zIndex = 10;
}
......@@ -175,27 +191,42 @@ class Line extends Shape {
}
update(element) {
d3.select(element)
.attr("x1", this.p1.x)
.attr("y1", this.p1.y)
.attr("x2", this.p2.x)
.attr("y2", this.p2.y);
if (this.c) {
let [n, xp1, xp2] = this.c.clipLine(this.p1, this.p2);
d3.select(element)
.attr("x1", xp1.x)
.attr("y1", xp1.y)
.attr("x2", xp2.x)
.attr("y2", xp2.y);
} else {
d3.select(element)
.attr("x1", this.p1.x)
.attr("y1", this.p1.y)
.attr("x2", this.p2.x)
.attr("y2", this.p2.y);
}
}
svg(w, h) {
let line = d3
.create("svg:line")
.attr("id", this.id)
.attr("class", "line")
.attr("x1", this.p1.x)
.attr("y1", this.p1.y)
.attr("x2", this.p2.x)
.attr("y2", this.p2.y);
if (this.opts.kind === "infinite") {
}
if (this.opts.end) {
line.attr("marker-end", `url(#${this.opts.end})`);
let classes = this.opts.concat("line").join(" ");
let line = d3.create("svg:line").attr("id", this.id).attr("class", classes);
if (this.opts.includes("infinite")) {
this.c = new Clipper({ x: 0, y: 0 }, { x: w, y: h });
let [n, xp1, xp2] = this.c.clipLine(this.p1, this.p2);
line
.attr("x1", xp1.x)
.attr("y1", xp1.y)
.attr("x2", xp2.x)
.attr("y2", xp2.y);
} else {
line
.attr("x1", this.p1.x)
.attr("y1", this.p1.y)
.attr("x2", this.p2.x)
.attr("y2", this.p2.y);
if (this.opts.includes("arrow")) {
line.attr("marker-end", `url(#arrow)`);
}
}
return line.node();
}
......@@ -206,11 +237,11 @@ function line(...args) {
}
class Surface extends Shape {
constructor(p, w, opts = {}) {
constructor(p, w, ...opts) {
super();
this.p = p;
this.w = w;
this.opts = { ...defaults.line, ...opts };
this.opts = opts.length == 0 ? defaults.surface.opts : opts;
this.zIndex = 1;
}
......@@ -254,13 +285,13 @@ function surface(...args) {
}
class Vector extends Shape {
constructor(p, nx, ny, opts = {}) {
constructor(p, nx, ny, ...opts) {
super();
this.p = p;
this._p = null;
this.nx = nx;
this.ny = ny;
this.opts = { ...defaults.vector, ...opts };
this.opts = opts.length == 0 ? defaults.vector.opts : opts;
this.zIndex = 10;
}
......@@ -292,7 +323,7 @@ class Vector extends Shape {
}
addP2(line) {
if (this.opts.end) {