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:
2026-07-03 19:47:03 +02:00
parent eaf6d8ed7b
commit 6aaef8f46a
3 changed files with 175 additions and 66 deletions
+20 -10
View File
@@ -160,7 +160,7 @@ export class PlanGpuRenderer {
const mmToDevicePx = (paperScaleN / 1000) * PX_PER_M * meet;
this.drawFills(geometry);
this.drawLines(geometry, viewport, mmToDevicePx);
this.drawLines(geometry, viewport, mmToDevicePx, meet);
}
/** Gefüllte Polygone (Poché). */
@@ -191,11 +191,21 @@ export class PlanGpuRenderer {
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(
geometry: GpuGeometry,
viewport: { width: number; height: number },
mmToDevicePx: number,
meet: number,
): void {
const gl = this.gl;
const { vertexBuffer, indexBuffer, batches } = geometry.line;
@@ -210,10 +220,6 @@ export class PlanGpuRenderer {
if (prog.uniforms.viewportPx) {
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);
const stride = 6 * 4; // [x,y, nx,ny, side, miter]
@@ -239,13 +245,17 @@ export class PlanGpuRenderer {
}
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
// strokePx-Uniform trägt die ECHTE Papier-mm-Breite; der Shader multipliziert
// mit strokeScale (= mm→Geräte-px) und klemmt bei ~0.6 px (Haarlinie sichtbar,
// MSAA via antialias:true), damit sehr feine Gewichte beim Rauszoomen nicht
// verschwinden.
// strokePx-Uniform trägt die Breite (Papier-mm ODER Bildschirm-px, je nach
// `screenPx`); strokeScale wählt PRO BATCH den passenden Massstabsfaktor.
// Der Shader multipliziert beides und klemmt bei ~0.6 px (Haarlinie
// sichtbar, MSAA via antialias:true), damit sehr feine Gewichte beim
// Rauszoomen nicht verschwinden.
for (const b of batches) {
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.strokeScale) {
gl.uniform1f(prog.uniforms.strokeScale, b.screenPx ? meet : mmToDevicePx);
}
gl.drawElements(gl.TRIANGLES, b.indexCount, gl.UNSIGNED_INT, b.startIndex * 4);
}
if (pos !== undefined && pos >= 0) gl.disableVertexAttribArray(pos);