Kantenverschieben faengt an aktiven Geometrie-Snaps

Beim Verschieben einer Kante ueber den Dreiecks-Griff wird der Cursor jetzt
durch die Snap-Engine (snapFor) gefuehrt: bei einem Geometrie-Snap
(Endpunkt/Mittelpunkt/Schnittpunkt) wird der gefangene Punkt statt des rohen
Cursors auf die Kanten-Normale projiziert — die Kante rastet an reale
Geometrie statt nur ans Raster. Ohne Geometrie-Snap bleibt die bisherige
Raster-Rundung. Der freie Delta-Fall bleibt unveraendert.
This commit is contained in:
2026-07-04 00:17:28 +02:00
parent 7b65f3bce6
commit bf3e8bf9fc
+24 -9
View File
@@ -1294,10 +1294,17 @@ export default function App() {
// Cursor-Delta — beide Endpunkte wandern gleich, die geteilten Ecken ziehen // Cursor-Delta — beide Endpunkte wandern gleich, die geteilten Ecken ziehen
// die Nachbar-Segmente nach. Shift = Ortho (H/V) auf das Gesamt-Delta. // die Nachbar-Segmente nach. Shift = Ortho (H/V) auf das Gesamt-Delta.
// • sonst (rect/geschlossene Polylinie/Wand): NUR die Komponente senkrecht // • sonst (rect/geschlossene Polylinie/Wand): NUR die Komponente senkrecht
// zur Kante (Seite verschiebt sich parallel nach außen/innen), aufs Raster // zur Kante (Seite verschiebt sich parallel nach außen/innen). Fängt an
// gerundet — wie bisher. // eingeschalteten Geometrie-Snaps (Endpunkt/Mittelpunkt/Schnittpunkt/…);
onEdgeMove: (edge: EdgeGrip, raw: Vec2, _pxPerMeter: number, mods: ToolMods) => { // ohne Geometrie-Snap wird der Offset wie bisher aufs Raster gerundet.
onEdgeMove: (edge: EdgeGrip, raw: Vec2, pxPerMeter: number, mods: ToolMods) => {
if (edge.free) { if (edge.free) {
// Freier Fall arbeitet mit einem inkrementellen Cursor-DELTA (nicht
// einem absoluten Punkt) — eine Geometrie-Snap-Projektion auf die
// Normale ergibt hier keinen Sinn, da es keine feste Kanten-Normale
// gibt, an die sich ein gefangener Punkt anlehnen könnte. Bleibt beim
// bisherigen Raster-Delta-Verhalten.
//
// Inkrementelles 2D-Delta gegen den letzten rohen Cursor. Erster Event // Inkrementelles 2D-Delta gegen den letzten rohen Cursor. Erster Event
// verankert nur (kein Sprung, da der Griff am Kanten-Mittelpunkt sitzt). // verankert nur (kein Sprung, da der Griff am Kanten-Mittelpunkt sitzt).
const last = edgeFreeLastRef.current; const last = edgeFreeLastRef.current;
@@ -1325,13 +1332,21 @@ export default function App() {
setDraft({ preview: [], vertices: [], snap: null }); setDraft({ preview: [], vertices: [], snap: null });
return; return;
} }
// Senkrechte Projektion von (raw Grab-Mittelpunkt) auf die Außennormale. // Cursor zuerst durch die Snap-Engine schicken (Ctrl unterdrückt Snap
const rel = sub(raw, edge.mid); // wie überall sonst, siehe computeSnap). Ein GEOMETRIE-Snap (Endpunkt/
// Mittelpunkt/Schnittpunkt/…, nicht Raster/Ortho) liefert den gefangenen
// Punkt, der dann auf die Außennormale projiziert wird — so rastet die
// Kante an reale Geometrie statt nur ans Raster.
const snapResult = snapFor(raw, pxPerMeter, mods);
const isGeometrySnap = !!snapResult && snapResult.kind !== "grid" && snapResult.kind !== "ortho";
const cursor = isGeometrySnap ? snapResult!.point : raw;
// Senkrechte Projektion von (Cursor Grab-Mittelpunkt) auf die Außennormale.
const rel = sub(cursor, edge.mid);
let offset = rel.x * edge.normal.x + rel.y * edge.normal.y; let offset = rel.x * edge.normal.x + rel.y * edge.normal.y;
// Raster-Schritt (Snap) anwenden, sofern Snap+Raster aktiv und nicht per // Raster-Schritt (Snap) nur, wenn kein Geometrie-Snap gefangen wurde —
// Ctrl unterdrückt sind. // sonst gewinnt der gefangene Punkt.
const step = snap.gridSize; const step = snap.gridSize;
if (!mods.ctrl && snap.enabled && snap.grid && step > 0) { if (!isGeometrySnap && !mods.ctrl && snap.enabled && snap.grid && step > 0) {
offset = Math.round(offset / step) * step; offset = Math.round(offset / step) * step;
} }
// INKREMENTELL + Grab-Anker: moveEdge verschiebt additiv, daher nur die // INKREMENTELL + Grab-Anker: moveEdge verschiebt additiv, daher nur die
@@ -1343,7 +1358,7 @@ export default function App() {
const inc = offset - prev; const inc = offset - prev;
if (inc === 0) return; if (inc === 0) return;
applyEdge(edge.aIndex, edge.bIndex, scale(edge.normal, inc)); applyEdge(edge.aIndex, edge.bIndex, scale(edge.normal, inc));
setDraft({ preview: [], vertices: [], snap: null }); setDraft({ preview: [], vertices: [], snap: isGeometrySnap ? snapResult : null });
}, },
onGripEnd: () => { onGripEnd: () => {
setDraft(null); setDraft(null);