Nordstern-3D: Live-Schnittebene mit schraffierten Schnittflaechen
Der 3D-Viewport kann das Modell jetzt live an einer vertikalen Ebene aufschneiden (Overlay-Button 'Schnittebene', MVP: Ebene durch die Modellmitte, Blick +Y): - Globals um section_plane vec4 erweitert (xyz=Normale, w=-n*p; Enable in mode.y); MESH_/GRID_WGSL discarden Fragmente vor der Ebene (world_pos als neuer Varying) - Flaechen, Kanten, Grid, Highlight. - section_fill.rs (neu): build_cut_caps ruft cut_section und trianguliert jedes (u,v)-Rechteck zurueck in Weltkoordinaten ([x,y,z,u,v]). - CAP_WGSL: prozedurale 45-Grad-Diagonalschraffur aus (u,v) in Modell- Metern (fwidth-AA), Tinte auf Papier wie die 2D-Konvention; eigene cap_pipeline (CullMode::None, Depth-Bias Richtung Kamera gegen Z-Fighting), gezeichnet nach den Flaechen, nicht geclippt. - web.rs cached walls/slabs; set_section_plane(active, p, n) aktualisiert Uniform + Caps; set_model baut Caps bei aktivem Schnitt neu. - useWasm3dRenderer.setSectionPlane + Toggle in Wasm3DViewport. Bekannte Luecken (dokumentiert): Oeffnungen im 3D-Solid nicht ausgespart (cut_section spart aus -> Mismatch bei Schnitt durchs Fenster; wird mit dem Boolean-Loecher-Slice geloest), nur vertikale Ebenen, ein globales Muster (per-Bauteil = Stufe 2). cargo 56 Tests + naga-WGSL-Validierung, 222 vitest gruen, WASM neu gebaut (pkg3d gitignored).
This commit is contained in:
@@ -42,7 +42,7 @@
|
||||
// Die Orbit-/Pan-Mathematik folgt den OrbitControls-Konventionen (Winkel je
|
||||
// Pixel an der Viewport-Höhe normiert), damit sich beide Pfade gleich anfühlen.
|
||||
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import type { Project } from "../model/types";
|
||||
import { projectToModel3d, pickGeometry } from "../plan/toWalls3d";
|
||||
import type { RenderMode, View3d } from "../ui/TopBar";
|
||||
@@ -220,10 +220,16 @@ export function Wasm3DViewport({
|
||||
setRenderStyle,
|
||||
applyViewPreset,
|
||||
setGroundGrid,
|
||||
setSectionPlane,
|
||||
setHighlightLines,
|
||||
ready,
|
||||
failed,
|
||||
} = useWasm3dRenderer(canvasRef, true);
|
||||
// Live-Schnitt-Zustand (lokaler Overlay-Umschalter, MVP): vertikale
|
||||
// Schnittebene durch die Modellmitte, Blick +Y (Grundriss-Y = world Z, s.
|
||||
// section.rs `looking_plus_y`: normal = [0,0,1]). Kein Store-/App-Prop —
|
||||
// reine Viewport-Ansichts-Option analog zum Grid-Overlay.
|
||||
const [sectionActive, setSectionActive] = useState(false);
|
||||
// Orbit-Zustand in einem Ref (kein React-State): Mausbewegungen zeichnen
|
||||
// direkt neu, ohne die Komponente je Frame zu re-rendern. Dient NUR der
|
||||
// freien Navigation NACH einem Praeset-Sprung (s. Datei-Kommentar).
|
||||
@@ -277,6 +283,18 @@ export function Wasm3DViewport({
|
||||
redrawRef.current();
|
||||
}, [ready, gridVisible, groundElevation, setGroundGrid]);
|
||||
|
||||
// Live-Schnitt: bei Bereitschaft, Umschalten (`sectionActive`) oder
|
||||
// Modelländerung neu setzen. MVP-Ebene = vertikal durch die Modellmitte
|
||||
// (`fitTargetDist`-Ziel), Blick nach +Y -> Normale [0,0,1] (Grundriss-Y =
|
||||
// world Z, s. section.rs `looking_plus_y`). Bei aktivem Schnitt baut Rust die
|
||||
// Schnittflaechen-Schraffur (Caps) aus den gecachten Waenden/Decken.
|
||||
useEffect(() => {
|
||||
if (!ready) return;
|
||||
const { target } = fitTargetDist(project);
|
||||
setSectionPlane(sectionActive, target, [0, 0, 1]);
|
||||
redrawRef.current();
|
||||
}, [ready, sectionActive, project, setSectionPlane]);
|
||||
|
||||
// Auswahl-Hervorhebung: bei Bereitschaft oder Änderung der Highlight-Linien
|
||||
// (Auswahl gewechselt/aufgehoben) neu setzen + zeichnen. Die Engine hält das
|
||||
// Highlight, bis es erneut gesetzt wird — Kamerabewegungen (Orbit/Pan/Zoom)
|
||||
@@ -504,6 +522,37 @@ export function Wasm3DViewport({
|
||||
Darstellungsart kommen von der Oberleiste (TopBar); das Grid-Overlay
|
||||
unten ist der einzige eigene Umschalter hier. */}
|
||||
<canvas ref={canvasRef} data-engine="render3d" />
|
||||
{/* Live-Schnitt-Umschalter (eigener Overlay-Button, MVP): vertikale
|
||||
Schnittebene durch die Modellmitte, Blick +Y. Sitzt ueber dem
|
||||
Grid-Button (falls vorhanden). Reine Ansichts-Option des Viewports. */}
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Schnittebene"
|
||||
onClick={() => setSectionActive((v) => !v)}
|
||||
title={sectionActive ? "Schnittebene aus" : "Schnittebene ein"}
|
||||
aria-pressed={sectionActive}
|
||||
style={{
|
||||
position: "absolute",
|
||||
right: 10,
|
||||
bottom: onToggleGrid ? 48 : 10,
|
||||
width: 30,
|
||||
height: 30,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
padding: 0,
|
||||
borderRadius: 7,
|
||||
border: "1px solid rgba(255,255,255,0.14)",
|
||||
background: sectionActive ? "rgba(255,255,255,0.14)" : "rgba(20,20,24,0.65)",
|
||||
color: sectionActive ? "#f2f2f2" : "#9a9a9a",
|
||||
cursor: "pointer",
|
||||
backdropFilter: "blur(4px)",
|
||||
}}
|
||||
>
|
||||
<span className="material-symbols-outlined" aria-hidden="true" style={{ fontSize: 18 }}>
|
||||
content_cut
|
||||
</span>
|
||||
</button>
|
||||
{onToggleGrid && (
|
||||
// Dezenter Overlay-Button (eigener DOM-Knoten neben dem Canvas, `.viewport`
|
||||
// ist `position: relative` — s. styles.css), stört die Maus-Navigation
|
||||
|
||||
@@ -53,6 +53,20 @@ interface WebModelRendererLike {
|
||||
extent_m: number,
|
||||
spacing_m: number,
|
||||
): void;
|
||||
/** Live-Schnitt ein-/ausschalten (Rust-seitig, `web::set_section_plane`).
|
||||
* `active=false` hebt den Schnitt auf. `p*` = Punkt auf der Ebene, `n*` =
|
||||
* Ebenennormale = Blickrichtung (world-Meter, Y-up; nur vertikale Ebenen).
|
||||
* Kappt das Modell per Shader-`discard` und fuellt die Schnittflaeche mit der
|
||||
* Diagonalschraffur (Caps). Wirkt erst beim naechsten `render`. */
|
||||
set_section_plane(
|
||||
active: boolean,
|
||||
px: number,
|
||||
py: number,
|
||||
pz: number,
|
||||
nx: number,
|
||||
ny: number,
|
||||
nz: number,
|
||||
): void;
|
||||
/**
|
||||
* Auswahl-Hervorhebung setzen: ein LineList-Vertex-Array (interleaved
|
||||
* `[px,py,pz, r,g,b, ...]`, world-Meter, je 2 Vertices = 1 Linie), das die
|
||||
@@ -187,6 +201,38 @@ export function useWasm3dRenderer(
|
||||
[],
|
||||
);
|
||||
|
||||
/**
|
||||
* Live-Schnitt setzen/aufheben (Overlay-Umschalter im Viewport, s.
|
||||
* Wasm3DViewport.tsx). `active=false` zeigt das Modell wieder komplett.
|
||||
* `point` = Punkt auf der Schnittebene, `normal` = Ebenennormale =
|
||||
* Blickrichtung (world-Meter, Y-up; nur vertikale Ebenen sinnvoll). Wirkt
|
||||
* erst beim nächsten `render`.
|
||||
*/
|
||||
const setSectionPlane = useCallback(
|
||||
(
|
||||
active: boolean,
|
||||
point: [number, number, number],
|
||||
normal: [number, number, number],
|
||||
) => {
|
||||
const r = rendererRef.current;
|
||||
if (!r) return;
|
||||
try {
|
||||
r.set_section_plane(
|
||||
active,
|
||||
point[0],
|
||||
point[1],
|
||||
point[2],
|
||||
normal[0],
|
||||
normal[1],
|
||||
normal[2],
|
||||
);
|
||||
} catch (e) {
|
||||
console.warn("render3d set_section_plane fehlgeschlagen:", e);
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
/**
|
||||
* Auswahl-Hervorhebung setzen (Umriss-Linien des gewählten Bauteils, immer
|
||||
* obenauf) — reicht das fertige LineList-Vertex-Array 1:1 an
|
||||
@@ -274,6 +320,7 @@ export function useWasm3dRenderer(
|
||||
setRenderStyle,
|
||||
applyViewPreset,
|
||||
setGroundGrid,
|
||||
setSectionPlane,
|
||||
setHighlightLines,
|
||||
ready,
|
||||
failed,
|
||||
|
||||
Reference in New Issue
Block a user