task(D2): spline tool - CV-based Catmull-Rom interpolation through all control points, ENTER commit from 3 CVs, controlPoints metadata
This commit is contained in:
@@ -558,15 +558,119 @@ export const ellipseTool: ToolExtensionV2 = {
|
||||
},
|
||||
};
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Task D2: Spline (CV-basiert) — Kontrollpunkt-Klick-Sequenz,
|
||||
// ENTER committet ab 3 CVs. Catmull-Rom-Interpolation (Kurve läuft
|
||||
// DURCH alle CVs, schwingt an Ecken = echte Glättung).
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
let splinePoints: Pt[] = [];
|
||||
|
||||
/**
|
||||
* Catmull-Rom-Spline-Interpolation: Für n CVs werden n-1 Segmente
|
||||
* mit je SEG Samples erzeugt. Tangenten aus Nachbarn (Endpunkte
|
||||
* geklemmt). Ergibt eine glatte Kurve durch alle CVs.
|
||||
*/
|
||||
function catmullRom(cvs: Pt[], seg = 16): Pt[] {
|
||||
if (cvs.length < 2) return [...cvs];
|
||||
const out: Pt[] = [];
|
||||
const n = cvs.length;
|
||||
for (let i = 0; i < n - 1; i++) {
|
||||
const p0 = cvs[Math.max(0, i - 1)];
|
||||
const p1 = cvs[i];
|
||||
const p2 = cvs[i + 1];
|
||||
const p3 = cvs[Math.min(n - 1, i + 2)];
|
||||
for (let s = (i === 0 ? 0 : 1); s <= seg; s++) {
|
||||
const t = s / seg;
|
||||
const t2 = t * t;
|
||||
const t3 = t2 * t;
|
||||
const x = 0.5 * ((2 * p1.x) + (-p0.x + p2.x) * t + (2 * p0.x - 5 * p1.x + 4 * p2.x - p3.x) * t2 + (-p0.x + 3 * p1.x - 3 * p2.x + p3.x) * t3);
|
||||
const y = 0.5 * ((2 * p1.y) + (-p0.y + p2.y) * t + (2 * p0.y - 5 * p1.y + 4 * p2.y - p3.y) * t2 + (-p0.y + 3 * p1.y - 3 * p2.y + p3.y) * t3);
|
||||
out.push({ x, y });
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function buildSpline(cvs: Pt[], ctx: FullToolContext): CADElement {
|
||||
const stroke = (ctx.options.strokeColor as string | undefined) ?? '#e0e0f0';
|
||||
const layerId = (ctx.options.layerId as string | undefined) ?? 'layer-0';
|
||||
const pts = catmullRom(cvs);
|
||||
const xs = pts.map((p) => p.x), ys = pts.map((p) => p.y);
|
||||
const minX = Math.min(...xs), minY = Math.min(...ys);
|
||||
const maxX = Math.max(...xs), maxY = Math.max(...ys);
|
||||
return {
|
||||
id: uid('spline'),
|
||||
type: 'polyline',
|
||||
layerId,
|
||||
x: minX + (maxX - minX) / 2,
|
||||
y: minY + (maxY - minY) / 2,
|
||||
width: maxX - minX,
|
||||
height: maxY - minY,
|
||||
properties: {
|
||||
points: pts,
|
||||
controlPoints: [...cvs],
|
||||
stroke,
|
||||
strokeWidth: 1,
|
||||
},
|
||||
} as unknown as CADElement;
|
||||
}
|
||||
|
||||
export const splineTool: ToolExtensionV2 = {
|
||||
manifest: {
|
||||
id: 'spline',
|
||||
label: 'Spline',
|
||||
icon: '\u2248',
|
||||
ribbonTab: 'canvas',
|
||||
tags: ['basic'],
|
||||
description: 'Spline zeichnen: Kontrollpunkte klicken, ENTER beendet',
|
||||
},
|
||||
optionsSchema: [{ key: 'strokeColor', label: 'Farbe', type: 'color' }],
|
||||
handlers: {
|
||||
down(e, ctx) {
|
||||
splinePoints.push(e.world);
|
||||
ctx.setStatus(`Spline: ${splinePoints.length} Kontrollpunkte – ENTER beendet`);
|
||||
},
|
||||
move(e, ctx) {
|
||||
const c = ctx as FullToolContext;
|
||||
if (splinePoints.length === 0) return;
|
||||
const pts = [...splinePoints, e.world];
|
||||
ctx.setPreview?.(buildSpline(pts, c));
|
||||
},
|
||||
up() { /* commit nur via ENTER */ },
|
||||
key(e, ctx) {
|
||||
if (e.key !== 'Enter' && e.key !== 'ENTER') return false;
|
||||
const c = ctx as FullToolContext;
|
||||
if (splinePoints.length < 3) {
|
||||
ctx.setStatus(`Spline benötigt mindestens 3 Kontrollpunkte (aktuell ${splinePoints.length})`);
|
||||
return true;
|
||||
}
|
||||
if (!c.doc) return false;
|
||||
const el = buildSpline(splinePoints, c);
|
||||
c.doc.transact(() => c.doc!.addElement(el));
|
||||
const count = splinePoints.length;
|
||||
splinePoints = [];
|
||||
ctx.setPreview?.(null);
|
||||
ctx.setStatus(`Spline erstellt (${count} CVs, ${el.id})`);
|
||||
return true;
|
||||
},
|
||||
cancel(ctx) {
|
||||
splinePoints = [];
|
||||
ctx.setPreview?.(null);
|
||||
ctx.setStatus('Spline abgebrochen');
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const coreDrawingPlugin: PluginV2 = {
|
||||
manifest: {
|
||||
id: 'core-drawing',
|
||||
name: 'Zeichnen (Kern)',
|
||||
version: '1.3.0',
|
||||
version: '1.4.0',
|
||||
author: 'web-cad team',
|
||||
description: 'Zeichenwerkzeuge V2: Linie, Rechteck, Kreis, Bogen, Polylinie, Polygon, RevCloud.',
|
||||
category: 'tools',
|
||||
enabledByDefault: true,
|
||||
},
|
||||
tools: [lineTool, rectTool, circleTool, arcTool, polylineTool, polygonTool, revcloudTool, ellipseTool],
|
||||
tools: [lineTool, rectTool, circleTool, arcTool, polylineTool, polygonTool, revcloudTool, ellipseTool, splineTool],
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user