Browser-BIM (cad): semantisches Modell, abgeleitete 2D/3D-Sichten, Zeichenwerkzeuge
Standalone-Browser-Port von DOSSIER. Enthaelt das semantische Modell mit Plan-/3D-Ableitung, Zeichen- und Editierwerkzeuge, Rhino-artiges Befehlssystem, dockbares Panel-System, Resource-Manager, DXF/.lin/.pat-Import, i18n (de/en) sowie Projektdokumentation und Probe-Harness.
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
// Geometrie-Helfer für das Gebäudemodell. Reine 2D/3D-Mathematik,
|
||||
// kein externer Kernel nötig (Phase 0).
|
||||
|
||||
import type { Vec2 } from "./types";
|
||||
|
||||
export const sub = (a: Vec2, b: Vec2): Vec2 => ({ x: a.x - b.x, y: a.y - b.y });
|
||||
export const add = (a: Vec2, b: Vec2): Vec2 => ({ x: a.x + b.x, y: a.y + b.y });
|
||||
export const scale = (a: Vec2, s: number): Vec2 => ({ x: a.x * s, y: a.y * s });
|
||||
export const len = (a: Vec2): number => Math.hypot(a.x, a.y);
|
||||
|
||||
export const normalize = (a: Vec2): Vec2 => {
|
||||
const l = len(a) || 1;
|
||||
return { x: a.x / l, y: a.y / l };
|
||||
};
|
||||
|
||||
/** Linke Normale (90° gegen den Uhrzeigersinn gedreht). */
|
||||
export const leftNormal = (a: Vec2): Vec2 => ({ x: -a.y, y: a.x });
|
||||
|
||||
/** Kreuzprodukt (Z-Komponente) zweier 2D-Vektoren. */
|
||||
export const cross = (p: Vec2, q: Vec2): number => p.x * q.y - p.y * q.x;
|
||||
|
||||
/** Eine unendliche Gerade als Stützpunkt + Richtung. */
|
||||
export interface Line {
|
||||
point: Vec2;
|
||||
dir: Vec2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schnittpunkt der Geraden (a + t·da) mit (b + s·db).
|
||||
* Liefert null bei (nahezu) parallelen Richtungen.
|
||||
*/
|
||||
export function lineIntersect(
|
||||
a: Vec2,
|
||||
da: Vec2,
|
||||
b: Vec2,
|
||||
db: Vec2,
|
||||
): Vec2 | null {
|
||||
const denom = cross(da, db);
|
||||
if (Math.abs(denom) < 1e-9) return null; // parallel → kein Schnitt
|
||||
const t = cross(sub(b, a), db) / denom;
|
||||
return add(a, scale(da, t));
|
||||
}
|
||||
|
||||
/** Punkt entlang einer Strecke p1→p2 im Abstand d vom Start. */
|
||||
export const along = (p1: Vec2, p2: Vec2, d: number): Vec2 =>
|
||||
add(p1, scale(normalize(sub(p2, p1)), d));
|
||||
|
||||
/**
|
||||
* Die vier Eckpunkte eines Bandes, das quer zur Normalen zwischen den
|
||||
* Offsets offA und offB liegt (gemessen entlang der linken Normalen n).
|
||||
* Reihenfolge: start+n*offA, end+n*offA, end+n*offB, start+n*offB.
|
||||
*/
|
||||
export function wallBand(
|
||||
start: Vec2,
|
||||
end: Vec2,
|
||||
offA: number,
|
||||
offB: number,
|
||||
): [Vec2, Vec2, Vec2, Vec2] {
|
||||
const u = normalize(sub(end, start));
|
||||
const n = leftNormal(u);
|
||||
return [
|
||||
add(start, scale(n, offA)),
|
||||
add(end, scale(n, offA)),
|
||||
add(end, scale(n, offB)),
|
||||
add(start, scale(n, offB)),
|
||||
];
|
||||
}
|
||||
|
||||
/** Die vier Eckpunkte einer Wand als Polygon (gegen den Uhrzeigersinn). */
|
||||
export function wallCorners(
|
||||
start: Vec2,
|
||||
end: Vec2,
|
||||
thickness: number,
|
||||
): [Vec2, Vec2, Vec2, Vec2] {
|
||||
const h = thickness / 2;
|
||||
return wallBand(start, end, h, -h);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wie {@link wallBand}, aber die Bandenden werden bei Bedarf an Gehrungslinien
|
||||
* (startCut / endCut) abgeschnitten statt rechtwinklig gekappt. Jede der beiden
|
||||
* Bandkanten (bei Offset offA bzw. offB, Richtung u) wird mit der jeweiligen
|
||||
* Schnittlinie verschnitten; fehlt eine Schnittlinie, bleibt das Ende
|
||||
* rechtwinklig (Punkt auf der Achssenkrechten).
|
||||
* Reihenfolge bleibt konsistent zu wallBand: [A.start, A.end, B.end, B.start].
|
||||
*/
|
||||
export function clippedBand(
|
||||
start: Vec2,
|
||||
end: Vec2,
|
||||
offA: number,
|
||||
offB: number,
|
||||
startCut: Line | null,
|
||||
endCut: Line | null,
|
||||
): [Vec2, Vec2, Vec2, Vec2] {
|
||||
const u = normalize(sub(end, start));
|
||||
const n = leftNormal(u);
|
||||
|
||||
const edgeStart = (off: number): Vec2 => {
|
||||
const base = add(start, scale(n, off));
|
||||
if (!startCut) return base;
|
||||
return lineIntersect(base, u, startCut.point, startCut.dir) ?? base;
|
||||
};
|
||||
const edgeEnd = (off: number): Vec2 => {
|
||||
const base = add(end, scale(n, off));
|
||||
if (!endCut) return base;
|
||||
return lineIntersect(base, u, endCut.point, endCut.dir) ?? base;
|
||||
};
|
||||
|
||||
return [edgeStart(offA), edgeEnd(offA), edgeEnd(offB), edgeStart(offB)];
|
||||
}
|
||||
Reference in New Issue
Block a user