glPlan: runde Linien-Caps/Joins + Schraffurbreite bildschirm-konstant
- Runde Enden und Ecken statt Butt-Caps/Miter: jedes Segment als eigenes Quad, an Innenecken ein Dreiecks-Fächer über den Aussenwinkel (addRoundJoin), an offenen Enden ein Halbkreis-Fächer (addRoundCap). Gilt für Wandumrisse, Linien, Bögen und Schraffurläufe — Look wie das Vektor-PDF. Keine Shader- Änderung nötig (Radius weiterhin zur Render-Zeit aus strokePx*strokeScale). - Schraffur-Dicke-Bug behoben: glPlan schickte die Schraffur-mm durch die Papier-mm-Pipeline (strokeMm*mmToDevicePx); im Display-Modus ist der darin steckende paperScaleN ein zoom-abgeleiteter, nicht auf 0.13/mm kalibrierter Wert → Breite explodierte. Jetzt hatchStrokeVb = max(0.6, hatchMm/0.13) als screenPx-Batch (skaliert nur mit meet/Zoom) — byte-genau wie SVG-<pattern> und der PDF-Export (widthScreen). Wandumrisse unverändert (echte mm).
This commit is contained in:
@@ -167,26 +167,45 @@ export function compilePrimitivesToGpu(
|
|||||||
if (last && sameRgba(last.color, color)) last.indexCount += count;
|
if (last && sameRgba(last.color, color)) last.indexCount += count;
|
||||||
else fillBatches.push({ startIndex: fillIdx.length - count, indexCount: count, color });
|
else fillBatches.push({ startIndex: fillIdx.length - count, indexCount: count, color });
|
||||||
};
|
};
|
||||||
const addLineBatch = (count: number, color: Rgba, strokeMm: number) => {
|
const addLineBatch = (count: number, color: Rgba, strokeMm: number, screenPx: boolean) => {
|
||||||
const last = lineBatches[lineBatches.length - 1];
|
const last = lineBatches[lineBatches.length - 1];
|
||||||
if (last && last.strokeMm === strokeMm && sameRgba(last.color, color))
|
if (last && last.strokeMm === strokeMm && !!last.screenPx === screenPx && sameRgba(last.color, color))
|
||||||
last.indexCount += count;
|
last.indexCount += count;
|
||||||
else lineBatches.push({ startIndex: lineIdx.length - count, indexCount: count, color, strokeMm });
|
else
|
||||||
|
lineBatches.push({
|
||||||
|
startIndex: lineIdx.length - count,
|
||||||
|
indexCount: count,
|
||||||
|
color,
|
||||||
|
strokeMm,
|
||||||
|
screenPx,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Maximaler Miter-Längenfaktor; darüber wird geklemmt (kein Spike an spitzen Ecken).
|
|
||||||
* 8 entspricht `stroke-miterlimit: 8` im SVG-Pfad (styles.css) und dem nativen
|
|
||||||
* render2d (tessellate.rs) — so sehen spitze Ecken in allen drei Pfaden gleich aus. */
|
|
||||||
const MITER_LIMIT = 8;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Zeichnet einen zusammenhängenden Linienzug (Bildschirm-Raum) als EINEN
|
* Zeichnet einen zusammenhängenden Linienzug (Bildschirm-Raum) mit ECHTEN
|
||||||
* gehrten Streifen: an jedem Stützpunkt wird der Versatz entlang des Miter-
|
* RUNDEN Kappen/Ecken (wie SVG `stroke-linecap/linejoin: round` und der
|
||||||
* Bisektors verlängert (1/cos(θ/2)), sodass benachbarte Segmente bündig
|
* Vektor-PDF-Pfad) statt eckigem Butt-Cap/Gehrung:
|
||||||
* verschmelzen → gehrte Ecke statt Butt-Cap-Stufe. `closed` schließt den Ring
|
* • jedes Segment ist ein eigenständiges Quad mit BUTT-Enden (eigene
|
||||||
* (letzter↔erster Punkt). Vertex-Layout: [x,y, bx,by, side, miter].
|
* Segment-Normale, keine Miter-Verlängerung);
|
||||||
|
* • an jedem inneren Stützpunkt füllt ein Dreiecksfächer (Radius = halbe
|
||||||
|
* Strichbreite) die Außenseite der Ecke rund auf (Innenseite überlappt
|
||||||
|
* unsichtbar, wie bei jedem Disjoint-Segment-Liniendicken-Ansatz);
|
||||||
|
* • an offenen Enden (nicht `closed`) sitzt ein Halbkreis-Fächer als runde
|
||||||
|
* Kappe.
|
||||||
|
* Die tatsächliche Pixel-Breite bleibt bildschirmkonstant: alle Fächer-/Quad-
|
||||||
|
* Vertices tragen nur eine EINHEITS-Richtung (`normal`) + `side`-Skalar; der
|
||||||
|
* Vertex-Shader multipliziert im Clip-Raum mit der aktuellen Strichbreite
|
||||||
|
* (`strokePx*strokeScale`) — die Tessellierung selbst kennt keine Pixelmasse.
|
||||||
|
* `closed` schließt den Ring (letzter↔erster Punkt). Vertex-Layout unverändert:
|
||||||
|
* [x,y, bx,by, side, miter] (miter bleibt hier immer 1, s. Shader-Vertrag).
|
||||||
*/
|
*/
|
||||||
const strokePolyline = (ptsM: Vec2[], closed: boolean, color: Rgba, strokeMm: number) => {
|
const strokePolyline = (
|
||||||
|
ptsM: Vec2[],
|
||||||
|
closed: boolean,
|
||||||
|
color: Rgba,
|
||||||
|
strokeMm: number,
|
||||||
|
screenPx = false,
|
||||||
|
) => {
|
||||||
// Auf Bildschirm-Raum abbilden + aufeinanderfolgende Duplikate entfernen.
|
// Auf Bildschirm-Raum abbilden + aufeinanderfolgende Duplikate entfernen.
|
||||||
const S: Vec2[] = [];
|
const S: Vec2[] = [];
|
||||||
for (const p of ptsM) {
|
for (const p of ptsM) {
|
||||||
@@ -203,49 +222,110 @@ export function compilePrimitivesToGpu(
|
|||||||
const k = S.length;
|
const k = S.length;
|
||||||
if (k < 2) return;
|
if (k < 2) return;
|
||||||
|
|
||||||
const leftNormal = (from: Vec2, to: Vec2): Vec2 | null => {
|
const dir = (from: Vec2, to: Vec2): Vec2 | null => {
|
||||||
const dx = to.x - from.x, dy = to.y - from.y;
|
const dx = to.x - from.x, dy = to.y - from.y;
|
||||||
const len = Math.hypot(dx, dy);
|
const len = Math.hypot(dx, dy);
|
||||||
return len < 1e-6 ? null : { x: -dy / len, y: dx / len };
|
return len < 1e-9 ? null : { x: dx / len, y: dy / len };
|
||||||
};
|
};
|
||||||
|
const leftNormal = (d: Vec2): Vec2 => ({ x: -d.y, y: d.x });
|
||||||
|
|
||||||
const base = lineVerts.length / 6;
|
const startIdxLen = lineIdx.length;
|
||||||
for (let i = 0; i < k; i++) {
|
const vBase = lineVerts.length / 6;
|
||||||
const hasIn = closed || i > 0;
|
let vcount = 0;
|
||||||
const hasOut = closed || i < k - 1;
|
const pushVert = (p: Vec2, nx: number, ny: number, side: number): number => {
|
||||||
const nIn = hasIn ? leftNormal(S[(i - 1 + k) % k], S[i]) : null;
|
lineVerts.push(p.x, p.y, nx, ny, side, 1);
|
||||||
const nOut = hasOut ? leftNormal(S[i], S[(i + 1) % k]) : null;
|
vcount++;
|
||||||
|
return vBase + vcount - 1;
|
||||||
let bx: number, by: number, miter: number;
|
};
|
||||||
if (nIn && nOut) {
|
|
||||||
let sx = nIn.x + nOut.x, sy = nIn.y + nOut.y;
|
|
||||||
const slen = Math.hypot(sx, sy);
|
|
||||||
if (slen < 1e-3) {
|
|
||||||
// ~180°-Umkehr → kein sinnvoller Bisektor, gerade weiterlaufen.
|
|
||||||
bx = nOut.x; by = nOut.y; miter = 1;
|
|
||||||
} else {
|
|
||||||
bx = sx / slen; by = sy / slen;
|
|
||||||
const denom = bx * nOut.x + by * nOut.y; // cos(θ/2)
|
|
||||||
miter = denom > 1e-3 ? Math.min(1 / denom, MITER_LIMIT) : 1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const n = nIn ?? nOut!;
|
|
||||||
bx = n.x; by = n.y; miter = 1;
|
|
||||||
}
|
|
||||||
lineVerts.push(S[i].x, S[i].y, bx, by, +1, miter);
|
|
||||||
lineVerts.push(S[i].x, S[i].y, bx, by, -1, miter);
|
|
||||||
}
|
|
||||||
|
|
||||||
const segs = closed ? k : k - 1;
|
const segs = closed ? k : k - 1;
|
||||||
|
const segDir: (Vec2 | null)[] = [];
|
||||||
|
for (let i = 0; i < segs; i++) segDir.push(dir(S[i], S[(i + 1) % k]));
|
||||||
|
|
||||||
|
// Segment-Quads: butt-endig, jedes mit seiner EIGENEN Normale (kein
|
||||||
|
// gemeinsamer Bisektor mehr — Ecken werden separat durch Fächer geschlossen).
|
||||||
for (let i = 0; i < segs; i++) {
|
for (let i = 0; i < segs; i++) {
|
||||||
const a = base + 2 * i;
|
const d = segDir[i];
|
||||||
const b = base + 2 * ((i + 1) % k);
|
if (!d) continue; // entartetes (Länge-0) Segment
|
||||||
lineIdx.push(a, a + 1, b, a + 1, b + 1, b);
|
const n = leftNormal(d);
|
||||||
|
const a = S[i], b = S[(i + 1) % k];
|
||||||
|
const i0 = pushVert(a, n.x, n.y, +1);
|
||||||
|
const i1 = pushVert(a, n.x, n.y, -1);
|
||||||
|
const i2 = pushVert(b, n.x, n.y, +1);
|
||||||
|
const i3 = pushVert(b, n.x, n.y, -1);
|
||||||
|
lineIdx.push(i0, i1, i2, i1, i3, i2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kreisbogen-Fächer (Zentrum = Vertex, Radius = halbe Strichbreite, per
|
||||||
|
// Shader skaliert): `side=0` am Zentrum (kein Versatz), `side=1` an den
|
||||||
|
// Randpunkten (voller Versatz in Richtung (cosθ,sinθ)).
|
||||||
|
const addFanArc = (p: Vec2, aFrom: number, aTo: number) => {
|
||||||
|
const delta = aTo - aFrom;
|
||||||
|
const steps = Math.max(1, Math.ceil(Math.abs(delta) / (Math.PI / 10)));
|
||||||
|
const iCenter = pushVert(p, 1, 0, 0);
|
||||||
|
let prev = pushVert(p, Math.cos(aFrom), Math.sin(aFrom), 1);
|
||||||
|
for (let s = 1; s <= steps; s++) {
|
||||||
|
const t = aFrom + (delta * s) / steps;
|
||||||
|
const cur = pushVert(p, Math.cos(t), Math.sin(t), 1);
|
||||||
|
lineIdx.push(iCenter, prev, cur);
|
||||||
|
prev = cur;
|
||||||
}
|
}
|
||||||
addLineBatch(segs * 6, color, strokeMm);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Einzelnes Segment als (ungehrter) 2-Punkt-Zug. */
|
// Runder Join an einem inneren Stützpunkt: Fächer NUR auf der konvexen
|
||||||
|
// (äußeren) Seite der Ecke — die konkave Seite überlappt bereits durch die
|
||||||
|
// beiden Segment-Quads (kein Loch, keine zusätzliche Geometrie nötig).
|
||||||
|
const addRoundJoin = (p: Vec2, d1: Vec2, d2: Vec2) => {
|
||||||
|
const turn = d1.x * d2.y - d1.y * d2.x;
|
||||||
|
const dot = d1.x * d2.x + d1.y * d2.y;
|
||||||
|
if (Math.abs(turn) < 1e-6 && dot > 0) return; // praktisch gerade
|
||||||
|
const n1 = leftNormal(d1), n2 = leftNormal(d2);
|
||||||
|
if (dot < -0.9999) {
|
||||||
|
// ~180°-Umkehr: Außenseite mehrdeutig → voller Kreis (robust, entspricht
|
||||||
|
// zwei gestapelten Rund-Kappen an derselben Stelle).
|
||||||
|
const a0 = Math.atan2(n1.y, n1.x);
|
||||||
|
addFanArc(p, a0, a0 + 2 * Math.PI);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const outer = turn > 0 ? -1 : 1;
|
||||||
|
const u1 = { x: outer * n1.x, y: outer * n1.y };
|
||||||
|
const u2 = { x: outer * n2.x, y: outer * n2.y };
|
||||||
|
const a1 = Math.atan2(u1.y, u1.x);
|
||||||
|
let a2 = Math.atan2(u2.y, u2.x);
|
||||||
|
let delta = a2 - a1;
|
||||||
|
while (delta <= -Math.PI) delta += 2 * Math.PI;
|
||||||
|
while (delta > Math.PI) delta -= 2 * Math.PI;
|
||||||
|
if (Math.abs(delta) < 1e-4) return;
|
||||||
|
addFanArc(p, a1, a1 + delta);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Runde Kappe an einem offenen Ende: Halbkreis, der auf der Außenseite
|
||||||
|
// (weg von der Linie) bulgt.
|
||||||
|
const addRoundCap = (p: Vec2, n: Vec2, sweepSign: 1 | -1) => {
|
||||||
|
const a0 = Math.atan2(n.y, n.x);
|
||||||
|
addFanArc(p, a0, a0 + sweepSign * Math.PI);
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let v = 0; v < k; v++) {
|
||||||
|
const hasIn = closed || v > 0;
|
||||||
|
const hasOut = closed || v < k - 1;
|
||||||
|
const dIn = hasIn ? segDir[(v - 1 + segs) % segs] : null;
|
||||||
|
const dOut = hasOut ? segDir[v % segs] : null;
|
||||||
|
if (dIn && dOut) {
|
||||||
|
addRoundJoin(S[v], dIn, dOut);
|
||||||
|
} else if (dOut && !dIn) {
|
||||||
|
// Start-Kappe: bulgt rückwärts (weg vom ersten Segment).
|
||||||
|
addRoundCap(S[v], leftNormal(dOut), 1);
|
||||||
|
} else if (dIn && !dOut) {
|
||||||
|
// End-Kappe: bulgt vorwärts (weg vom letzten Segment).
|
||||||
|
addRoundCap(S[v], leftNormal(dIn), -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addLineBatch(lineIdx.length - startIdxLen, color, strokeMm, screenPx);
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Einzelnes Segment als (ungehrter, rund gekapptes) 2-Punkt-Zug. */
|
||||||
const pushLine = (aM: Vec2, bM: Vec2, color: Rgba, strokeMm: number) =>
|
const pushLine = (aM: Vec2, bM: Vec2, color: Rgba, strokeMm: number) =>
|
||||||
strokePolyline([aM, bM], false, color, strokeMm);
|
strokePolyline([aM, bM], false, color, strokeMm);
|
||||||
|
|
||||||
@@ -307,15 +387,27 @@ export function compilePrimitivesToGpu(
|
|||||||
// Schraffur (nach der Füllung, vor/passend zum Umriss — wie der SVG stapelt):
|
// Schraffur (nach der Füllung, vor/passend zum Umriss — wie der SVG stapelt):
|
||||||
// das Muster wird in echte Linien-Geometrie (Modell-Meter) tesselliert und
|
// das Muster wird in echte Linien-Geometrie (Modell-Meter) tesselliert und
|
||||||
// aufs Polygon geclippt (konkav-fähig, even-odd). Musterlinien fließen als
|
// aufs Polygon geclippt (konkav-fähig, even-odd). Musterlinien fließen als
|
||||||
// zusammenhängende Läufe in `strokePolyline` → GEHRTE, glatte Übergänge
|
// zusammenhängende Läufe in `strokePolyline` → glatte, RUND gekappte/
|
||||||
// (insb. die Dämmungswelle bleibt spitzenfrei). Breite = dieselbe Papier-mm
|
// -verbundene Übergänge (insb. die Dämmungswelle bleibt spitzenfrei).
|
||||||
// wie der SVG-`<pattern>`; `hatch.dash` wird geometrisch (Strich/Lücke)
|
// BREITE — bewusst NICHT die Papier-mm-Pipeline (das ist Massstabs-N-
|
||||||
// aufgelöst, da die GL-Linien-Pipeline kein Dash kennt.
|
// abhängig, s. `strokeMm` unten): der SVG-Referenzpfad zeichnet die
|
||||||
|
// Musterlinie mit `hatchStrokePx` (PlanView.tsx) — einer FESTEN
|
||||||
|
// Bildschirm-Raum-Breite (`max(0.6, weightMm/0.13)` viewBox-px), die NUR
|
||||||
|
// mit dem Zoom (nicht mit dem Papier-Massstab N) skaliert. Die Papier-mm-
|
||||||
|
// Pipeline hier stattdessen zu verwenden multipliziert die Breite
|
||||||
|
// zusätzlich mit N/1000·PX_PER_M — bei grossen/live abgeleiteten N (siehe
|
||||||
|
// `paperScaleForGl` in PlanView) wird die Schraffur dadurch sichtbar zu
|
||||||
|
// DICK. `screenPx=true` markiert den Batch dafür, dass der Renderer NUR
|
||||||
|
// mit `meet` (Zoom) statt mit `mmToDevicePx` (Papier-Massstab) skaliert —
|
||||||
|
// exakt der SVG-Formel entsprechend (siehe glPlanRender.drawLines).
|
||||||
|
// `hatch.dash` wird geometrisch (Strich/Lücke) aufgelöst, da die GL-
|
||||||
|
// Linien-Pipeline kein Dash kennt.
|
||||||
if (prim.hatch && prim.hatch.pattern !== 'none' && prim.hatch.pattern !== 'solid') {
|
if (prim.hatch && prim.hatch.pattern !== 'none' && prim.hatch.pattern !== 'solid') {
|
||||||
const hatchColor = parseColor(prim.hatch.color) ?? [0.1, 0.1, 0.1, 1];
|
const hatchColor = parseColor(prim.hatch.color) ?? [0.1, 0.1, 0.1, 1];
|
||||||
const hatchMm = prim.hatch.lineWeight > 0 ? prim.hatch.lineWeight : 0.13;
|
const hatchMm = prim.hatch.lineWeight > 0 ? prim.hatch.lineWeight : 0.13;
|
||||||
|
const hatchStrokeVb = Math.max(0.6, hatchMm * (1 / 0.13));
|
||||||
const runs = applyDashRuns(buildHatchRuns(prim.pts, prim.hatch), prim.hatch.dash);
|
const runs = applyDashRuns(buildHatchRuns(prim.pts, prim.hatch), prim.hatch.dash);
|
||||||
for (const run of runs) strokePolyline(run, false, hatchColor, hatchMm);
|
for (const run of runs) strokePolyline(run, false, hatchColor, hatchStrokeVb, true);
|
||||||
}
|
}
|
||||||
// Umriss (crispe Kante) — geschlossener Ring, GEHRT (kein Stufen-Cap an
|
// Umriss (crispe Kante) — geschlossener Ring, GEHRT (kein Stufen-Cap an
|
||||||
// Ecken). Breite = ECHTE Papier-mm; der Renderer rechnet massstabs-/
|
// Ecken). Breite = ECHTE Papier-mm; der Renderer rechnet massstabs-/
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ export class PlanGpuRenderer {
|
|||||||
const mmToDevicePx = (paperScaleN / 1000) * PX_PER_M * meet;
|
const mmToDevicePx = (paperScaleN / 1000) * PX_PER_M * meet;
|
||||||
|
|
||||||
this.drawFills(geometry);
|
this.drawFills(geometry);
|
||||||
this.drawLines(geometry, viewport, mmToDevicePx);
|
this.drawLines(geometry, viewport, mmToDevicePx, meet);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Gefüllte Polygone (Poché). */
|
/** Gefüllte Polygone (Poché). */
|
||||||
@@ -191,11 +191,21 @@ export class PlanGpuRenderer {
|
|||||||
if (pos !== undefined && pos >= 0) gl.disableVertexAttribArray(pos);
|
if (pos !== undefined && pos >= 0) gl.disableVertexAttribArray(pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Striche mit echter Papier-mm-Breite (massstabs- + zoomrichtig). */
|
/**
|
||||||
|
* Striche. Zwei Skalierungs-Regime pro Batch (siehe `LineBatch.screenPx`):
|
||||||
|
* • Papier-mm (Standard) — Wand-/Objekt-Umrisse: massstabs- + zoomrichtig
|
||||||
|
* über `mmToDevicePx`.
|
||||||
|
* • Bildschirm-px (`screenPx`) — Schraffur-Musterlinien: NUR mit `meet`
|
||||||
|
* (Zoom) skaliert, unabhängig vom Papier-Massstab N — exakt wie der SVG-
|
||||||
|
* `hatchStrokePx`-Pfad (`patternUnits="userSpaceOnUse"`, keine
|
||||||
|
* `printStrokeVb`-Umrechnung) und der PDF-Export (`toRenderScene.ts`,
|
||||||
|
* `widthScreen: true`).
|
||||||
|
*/
|
||||||
private drawLines(
|
private drawLines(
|
||||||
geometry: GpuGeometry,
|
geometry: GpuGeometry,
|
||||||
viewport: { width: number; height: number },
|
viewport: { width: number; height: number },
|
||||||
mmToDevicePx: number,
|
mmToDevicePx: number,
|
||||||
|
meet: number,
|
||||||
): void {
|
): void {
|
||||||
const gl = this.gl;
|
const gl = this.gl;
|
||||||
const { vertexBuffer, indexBuffer, batches } = geometry.line;
|
const { vertexBuffer, indexBuffer, batches } = geometry.line;
|
||||||
@@ -210,10 +220,6 @@ export class PlanGpuRenderer {
|
|||||||
if (prog.uniforms.viewportPx) {
|
if (prog.uniforms.viewportPx) {
|
||||||
gl.uniform2f(prog.uniforms.viewportPx, viewport.width, viewport.height);
|
gl.uniform2f(prog.uniforms.viewportPx, viewport.width, viewport.height);
|
||||||
}
|
}
|
||||||
// strokeScale = mm → Geräte-px; die Basis-Breite (strokePx) trägt die mm.
|
|
||||||
if (prog.uniforms.strokeScale) {
|
|
||||||
gl.uniform1f(prog.uniforms.strokeScale, mmToDevicePx);
|
|
||||||
}
|
|
||||||
|
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
|
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
|
||||||
const stride = 6 * 4; // [x,y, nx,ny, side, miter]
|
const stride = 6 * 4; // [x,y, nx,ny, side, miter]
|
||||||
@@ -239,13 +245,17 @@ export class PlanGpuRenderer {
|
|||||||
}
|
}
|
||||||
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
|
||||||
|
|
||||||
// strokePx-Uniform trägt die ECHTE Papier-mm-Breite; der Shader multipliziert
|
// strokePx-Uniform trägt die Breite (Papier-mm ODER Bildschirm-px, je nach
|
||||||
// mit strokeScale (= mm→Geräte-px) und klemmt bei ~0.6 px (Haarlinie sichtbar,
|
// `screenPx`); strokeScale wählt PRO BATCH den passenden Massstabsfaktor.
|
||||||
// MSAA via antialias:true), damit sehr feine Gewichte beim Rauszoomen nicht
|
// Der Shader multipliziert beides und klemmt bei ~0.6 px (Haarlinie
|
||||||
// verschwinden.
|
// sichtbar, MSAA via antialias:true), damit sehr feine Gewichte beim
|
||||||
|
// Rauszoomen nicht verschwinden.
|
||||||
for (const b of batches) {
|
for (const b of batches) {
|
||||||
if (prog.uniforms.color) gl.uniform4fv(prog.uniforms.color, b.color);
|
if (prog.uniforms.color) gl.uniform4fv(prog.uniforms.color, b.color);
|
||||||
if (prog.uniforms.strokePx) gl.uniform1f(prog.uniforms.strokePx, b.strokeMm);
|
if (prog.uniforms.strokePx) gl.uniform1f(prog.uniforms.strokePx, b.strokeMm);
|
||||||
|
if (prog.uniforms.strokeScale) {
|
||||||
|
gl.uniform1f(prog.uniforms.strokeScale, b.screenPx ? meet : mmToDevicePx);
|
||||||
|
}
|
||||||
gl.drawElements(gl.TRIANGLES, b.indexCount, gl.UNSIGNED_INT, b.startIndex * 4);
|
gl.drawElements(gl.TRIANGLES, b.indexCount, gl.UNSIGNED_INT, b.startIndex * 4);
|
||||||
}
|
}
|
||||||
if (pos !== undefined && pos >= 0) gl.disableVertexAttribArray(pos);
|
if (pos !== undefined && pos >= 0) gl.disableVertexAttribArray(pos);
|
||||||
|
|||||||
@@ -53,11 +53,18 @@ export interface LineBatch {
|
|||||||
indexCount: number;
|
indexCount: number;
|
||||||
color: Rgba;
|
color: Rgba;
|
||||||
/**
|
/**
|
||||||
* ECHTE Strichbreite in Papier-Millimeter. Der Renderer rechnet sie über den
|
* Strichbreite. Interpretation hängt von `screenPx` ab:
|
||||||
* Massstab (1:N) + die aktuelle meet-Skala in Pixel — genau wie der SVG-
|
* • `screenPx` falsch (Standard) — ECHTE Papier-Millimeter. Der Renderer
|
||||||
* `printStrokeVb`-Pfad, sodass GL == SVG (0.35 mm bei 1:100 = 0.35 mm Papier).
|
* rechnet über den Massstab (1:N) + die aktuelle meet-Skala in Pixel —
|
||||||
|
* genau wie der SVG-`printStrokeVb`-Pfad (0.35 mm bei 1:100 = 0.35 mm
|
||||||
|
* Papier).
|
||||||
|
* • `screenPx` wahr — bereits eine BILDSCHIRM-Raum-Breite (viewBox-px, wie
|
||||||
|
* SVG-`hatchStrokePx`): skaliert NUR mit dem Zoom (`meet`), NICHT mit dem
|
||||||
|
* Papier-Massstab N. Für Schraffur-Musterlinien (s. `glPlanCompile`).
|
||||||
*/
|
*/
|
||||||
strokeMm: number;
|
strokeMm: number;
|
||||||
|
/** Siehe `strokeMm`-Doku: true = Bildschirm-Raum-px statt Papier-mm. */
|
||||||
|
screenPx?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user