task(CAD-2): real hatch patterns - 9 patterns (ANSI31/32/37, NET, GRASS, DOTS, SOLID + legacy), boundary detection, render adapter hatch lines
This commit is contained in:
@@ -394,8 +394,15 @@ export const hatchTool: ToolExtensionV2 = {
|
|||||||
},
|
},
|
||||||
optionsSchema: [
|
optionsSchema: [
|
||||||
{ key: 'hatchPattern', label: 'Muster', type: 'select', options: [
|
{ key: 'hatchPattern', label: 'Muster', type: 'select', options: [
|
||||||
{ value: 'lines', label: 'Linien' },
|
{ value: 'ANSI31', label: 'ANSI31 (45°)' },
|
||||||
{ value: 'cross', label: 'Kreuz' },
|
{ value: 'ANSI32', label: 'ANSI32 (Eisen)' },
|
||||||
|
{ value: 'ANSI37', label: 'ANSI37 (Beton)' },
|
||||||
|
{ value: 'NET', label: 'Netz' },
|
||||||
|
{ value: 'GRASS', label: 'Gras' },
|
||||||
|
{ value: 'DOTS', label: 'Punkte' },
|
||||||
|
{ value: 'SOLID', label: 'Voll' },
|
||||||
|
{ value: 'lines', label: 'Linien (alt)' },
|
||||||
|
{ value: 'cross', label: 'Kreuz (alt)' },
|
||||||
] },
|
] },
|
||||||
{ key: 'hatchSpacing', label: 'Abstand', type: 'number', min: 2, max: 40 },
|
{ key: 'hatchSpacing', label: 'Abstand', type: 'number', min: 2, max: 40 },
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
*/
|
*/
|
||||||
import type { CADElement } from '../../types/cad.types';
|
import type { CADElement } from '../../types/cad.types';
|
||||||
import { getDimStyle } from '../../services/dimStyleService';
|
import { getDimStyle } from '../../services/dimStyleService';
|
||||||
|
import { generateHatchLines, computeBoundary, getHatchPattern } from '../../services/hatchPatternService';
|
||||||
import type { Pt } from '../../tools/modification/geometry';
|
import type { Pt } from '../../tools/modification/geometry';
|
||||||
|
|
||||||
/** Zeichenprimitiv — kleinste darstellbare Einheit. */
|
/** Zeichenprimitiv — kleinste darstellbare Einheit. */
|
||||||
@@ -66,6 +67,62 @@ function arrowPrimitives(x: number, y: number, ang: number, size: number, color:
|
|||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Task CAD-2: Hatch-Linien fuer ein Element mit hatch-Properties.
|
||||||
|
* Nutzt hatchPatternService.generateHatchLines mit Boundary aus der
|
||||||
|
* Element-Kontur. Ohne Muster: leeres Array.
|
||||||
|
*/
|
||||||
|
function hatchPrimitives(el: CADElement, stroke: string): ShapePrimitive[] {
|
||||||
|
const props = el.properties as Record<string, unknown>;
|
||||||
|
if (props.hatch !== true) return [];
|
||||||
|
const patternId = (props.hatchPattern as string | undefined) ?? 'ANSI31';
|
||||||
|
if (!getHatchPattern(patternId)) return [];
|
||||||
|
const spacing = (props.hatchSpacing as number | undefined) ?? 8;
|
||||||
|
|
||||||
|
// Boundary aus Kontur je Elementtyp
|
||||||
|
let bbox: { minX: number; minY: number; maxX: number; maxY: number } | null = null;
|
||||||
|
if (Array.isArray(props.points)) {
|
||||||
|
bbox = computeBoundary(props.points as Array<{ x: number; y: number }>);
|
||||||
|
} else if (el.type === 'circle' || el.type === 'arc') {
|
||||||
|
const r = (props.radius as number | undefined) ?? el.width / 2;
|
||||||
|
bbox = { minX: el.x - r, minY: el.y - r, maxX: el.x + r, maxY: el.y + r };
|
||||||
|
} else {
|
||||||
|
bbox = {
|
||||||
|
minX: el.x - el.width / 2, minY: el.y - el.height / 2,
|
||||||
|
maxX: el.x + el.width / 2, maxY: el.y + el.height / 2,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (!bbox) return [];
|
||||||
|
|
||||||
|
const segs = generateHatchLines(patternId, bbox, spacing);
|
||||||
|
const out: ShapePrimitive[] = [];
|
||||||
|
for (const s of segs) {
|
||||||
|
if (s.fill) {
|
||||||
|
const rectPrim: ShapePrimitive = {
|
||||||
|
kind: 'rect',
|
||||||
|
x: (bbox.minX + bbox.maxX) / 2,
|
||||||
|
y: (bbox.minY + bbox.maxY) / 2,
|
||||||
|
w: bbox.maxX - bbox.minX,
|
||||||
|
h: bbox.maxY - bbox.minY,
|
||||||
|
fill: s.fill,
|
||||||
|
stroke,
|
||||||
|
strokeWidth: 1,
|
||||||
|
};
|
||||||
|
out.push(rectPrim);
|
||||||
|
} else {
|
||||||
|
const linePrim: ShapePrimitive = {
|
||||||
|
kind: 'line',
|
||||||
|
from: s.from,
|
||||||
|
to: s.to,
|
||||||
|
stroke,
|
||||||
|
strokeWidth: 0.5,
|
||||||
|
};
|
||||||
|
out.push(linePrim);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
export function elementToPrimitives(el: CADElement, attrDefs?: Array<{ tag: string; prompt: string; defaultValue?: string }>): ShapePrimitive[] {
|
export function elementToPrimitives(el: CADElement, attrDefs?: Array<{ tag: string; prompt: string; defaultValue?: string }>): ShapePrimitive[] {
|
||||||
const props = (el.properties ?? {}) as Record<string, unknown>;
|
const props = (el.properties ?? {}) as Record<string, unknown>;
|
||||||
const stroke = (props.stroke as string | undefined) ?? FALLBACK_STROKE;
|
const stroke = (props.stroke as string | undefined) ?? FALLBACK_STROKE;
|
||||||
@@ -90,7 +147,7 @@ export function elementToPrimitives(el: CADElement, attrDefs?: Array<{ tag: stri
|
|||||||
|
|
||||||
case 'rect': {
|
case 'rect': {
|
||||||
// Legacy-BBox-Konvention: x/y ist das Zentrum des Rechtecks
|
// Legacy-BBox-Konvention: x/y ist das Zentrum des Rechtecks
|
||||||
return [{
|
const base: ShapePrimitive[] = [{
|
||||||
kind: 'rect',
|
kind: 'rect',
|
||||||
x: el.x - el.width / 2,
|
x: el.x - el.width / 2,
|
||||||
y: el.y - el.height / 2,
|
y: el.y - el.height / 2,
|
||||||
@@ -100,11 +157,12 @@ export function elementToPrimitives(el: CADElement, attrDefs?: Array<{ tag: stri
|
|||||||
stroke,
|
stroke,
|
||||||
strokeWidth: sw,
|
strokeWidth: sw,
|
||||||
}];
|
}];
|
||||||
|
return [...base, ...hatchPrimitives(el, stroke)];
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'circle': {
|
case 'circle': {
|
||||||
const radius = (props.radius as number | undefined) ?? el.width / 2;
|
const radius = (props.radius as number | undefined) ?? el.width / 2;
|
||||||
return [{
|
const circBase: ShapePrimitive[] = [{
|
||||||
kind: 'circle',
|
kind: 'circle',
|
||||||
center: { x: el.x, y: el.y },
|
center: { x: el.x, y: el.y },
|
||||||
radius,
|
radius,
|
||||||
@@ -112,6 +170,7 @@ export function elementToPrimitives(el: CADElement, attrDefs?: Array<{ tag: stri
|
|||||||
stroke,
|
stroke,
|
||||||
strokeWidth: sw,
|
strokeWidth: sw,
|
||||||
}];
|
}];
|
||||||
|
return [...circBase, ...hatchPrimitives(el, stroke)];
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'arc': {
|
case 'arc': {
|
||||||
@@ -133,13 +192,14 @@ export function elementToPrimitives(el: CADElement, attrDefs?: Array<{ tag: stri
|
|||||||
? (props.points as unknown[]).map(asPoint)
|
? (props.points as unknown[]).map(asPoint)
|
||||||
: [];
|
: [];
|
||||||
if (pts.length < 2) return [];
|
if (pts.length < 2) return [];
|
||||||
return [{
|
const polyBase: ShapePrimitive[] = [{
|
||||||
kind: 'polyline',
|
kind: 'polyline',
|
||||||
points: pts,
|
points: pts,
|
||||||
close: el.type === 'polygon',
|
close: el.type === 'polygon',
|
||||||
stroke,
|
stroke,
|
||||||
strokeWidth: sw,
|
strokeWidth: sw,
|
||||||
}];
|
}];
|
||||||
|
return [...polyBase, ...hatchPrimitives(el, stroke)];
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'text': {
|
case 'text': {
|
||||||
|
|||||||
@@ -0,0 +1,154 @@
|
|||||||
|
/**
|
||||||
|
* Task CAD-2 – Echte Hatch-Muster + Boundary-Erkennung.
|
||||||
|
*
|
||||||
|
* Standard-CAD-Schraffurmuster als Linien-Segment-Generatoren für
|
||||||
|
* eine BBox. computeBoundary liefert die umschlossene Fläche aus
|
||||||
|
* einer Kontur (Polygon-Punkte).
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface HatchSeg {
|
||||||
|
from: { x: number; y: number };
|
||||||
|
to: { x: number; y: number };
|
||||||
|
fill?: string;
|
||||||
|
closed?: boolean;
|
||||||
|
points?: Array<{ x: number; y: number }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HatchPattern {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
description: string;
|
||||||
|
defaultSpacing: number;
|
||||||
|
/** 45°-Ersatzwinkel für Linienfamilien (Grad). */
|
||||||
|
angles: number[];
|
||||||
|
/** Punkte-Muster (DOTS) statt Linien. */
|
||||||
|
dots?: boolean;
|
||||||
|
/** Vollflächige Füllung. */
|
||||||
|
solid?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type HatchPatternId = string;
|
||||||
|
|
||||||
|
export const HATCH_PATTERNS: HatchPattern[] = [
|
||||||
|
{ id: 'lines', label: 'Linien (45°)', description: 'Einfache 45°-Schraffur', defaultSpacing: 8, angles: [45] },
|
||||||
|
{ id: 'cross', label: 'Kreuz', description: 'Kreuzschraffur 45°/-45°', defaultSpacing: 8, angles: [45, -45] },
|
||||||
|
{ id: 'ANSI31', label: 'ANSI31 (45°)', description: 'ANSI-Standard 45°', defaultSpacing: 6, angles: [45] },
|
||||||
|
{ id: 'ANSI32', label: 'ANSI32 (Eisen)', description: 'ANSI Eisen 45°/-45°', defaultSpacing: 6, angles: [45, -45] },
|
||||||
|
{ id: 'ANSI37', label: 'ANSI37 (Beton)', description: 'ANSI Beton 45°/-45° dicht', defaultSpacing: 3, angles: [45, -45] },
|
||||||
|
{ id: 'NET', label: 'Netz', description: 'Horizontal + vertikal', defaultSpacing: 10, angles: [0, 90] },
|
||||||
|
{ id: 'GRASS', label: 'Gras', description: 'Kurze Gras-Striche', defaultSpacing: 6, angles: [75] },
|
||||||
|
{ id: 'DOTS', label: 'Punkte', description: 'Punktschraffur', defaultSpacing: 8, angles: [], dots: true },
|
||||||
|
{ id: 'SOLID', label: 'Voll', description: 'Vollflächige Füllung', defaultSpacing: 0, angles: [], solid: true },
|
||||||
|
];
|
||||||
|
|
||||||
|
/** Muster per ID (oder undefined). */
|
||||||
|
export function getHatchPattern(id: string | undefined): HatchPattern | undefined {
|
||||||
|
if (!id) return undefined;
|
||||||
|
return HATCH_PATTERNS.find((p) => p.id === id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erzeugt Schraffur-Segmente für eine BBox nach Muster.
|
||||||
|
* SOLID liefert EIN Segment mit fill-Flag; DOTS liefert Punkte-Muster.
|
||||||
|
*/
|
||||||
|
export function generateHatchLines(
|
||||||
|
patternId: string,
|
||||||
|
bbox: { minX: number; minY: number; maxX: number; maxY: number },
|
||||||
|
spacing: number,
|
||||||
|
): HatchSeg[] {
|
||||||
|
const pattern = getHatchPattern(patternId);
|
||||||
|
if (!pattern) return [];
|
||||||
|
const { minX, minY, maxX, maxY } = bbox;
|
||||||
|
const w = maxX - minX;
|
||||||
|
const h = maxY - minY;
|
||||||
|
|
||||||
|
if (pattern.solid) {
|
||||||
|
return [{ from: { x: minX, y: minY }, to: { x: maxX, y: maxY }, fill: '#888888' }];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pattern.dots) {
|
||||||
|
const out: HatchSeg[] = [];
|
||||||
|
for (let y = minY + spacing / 2; y < maxY; y += spacing) {
|
||||||
|
for (let x = minX + spacing / 2; x < maxX; x += spacing) {
|
||||||
|
out.push({
|
||||||
|
from: { x, y },
|
||||||
|
to: { x: x + 1, y: y + 1 },
|
||||||
|
points: [{ x, y }],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Linienfamilien je Winkel
|
||||||
|
const out: HatchSeg[] = [];
|
||||||
|
for (const angleDeg of pattern.angles) {
|
||||||
|
const rad = (angleDeg * Math.PI) / 180;
|
||||||
|
const cosA = Math.cos(rad);
|
||||||
|
const sinA = Math.sin(rad);
|
||||||
|
const diag = Math.hypot(w, h);
|
||||||
|
const step = spacing;
|
||||||
|
const offsetCount = Math.ceil(diag / step) + 2;
|
||||||
|
for (let i = -offsetCount; i <= offsetCount; i++) {
|
||||||
|
const cx = minX + w / 2 + i * step;
|
||||||
|
const cy = minY + h / 2;
|
||||||
|
// Linie durch (cx,cy) in Richtung (cosA, sinA), begrenzt auf BBox
|
||||||
|
const p1 = { x: cx - cosA * diag, y: cy - sinA * diag };
|
||||||
|
const p2 = { x: cx + cosA * diag, y: cy + sinA * diag };
|
||||||
|
// Clipping an BBox (einfaches Cohen-Sutherland)
|
||||||
|
const cl = clipToBBox(p1, p2, bbox);
|
||||||
|
if (cl) out.push({ from: cl[0], to: cl[1] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Cohen-Sutherland-artiges Clipping an BBox (vereinfacht). */
|
||||||
|
function clipToBBox(
|
||||||
|
p1: { x: number; y: number },
|
||||||
|
p2: { x: number; y: number },
|
||||||
|
b: { minX: number; minY: number; maxX: number; maxY: number },
|
||||||
|
): Array<{ x: number; y: number }> | null {
|
||||||
|
// Liang-Barsky
|
||||||
|
let t0 = 0, t1 = 1;
|
||||||
|
const dx = p2.x - p1.x;
|
||||||
|
const dy = p2.y - p1.y;
|
||||||
|
const clip = (p: number, q: number): boolean => {
|
||||||
|
if (p === 0) return q >= 0;
|
||||||
|
const r = q / p;
|
||||||
|
if (p < 0) {
|
||||||
|
if (r > t1) return false;
|
||||||
|
if (r > t0) t0 = r;
|
||||||
|
} else {
|
||||||
|
if (r < t0) return false;
|
||||||
|
if (r < t1) t1 = r;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
if (!clip(-dx, p1.x - b.minX)) return null;
|
||||||
|
if (!clip(dx, b.maxX - p1.x)) return null;
|
||||||
|
if (!clip(-dy, p1.y - b.minY)) return null;
|
||||||
|
if (!clip(dy, b.maxY - p1.y)) return null;
|
||||||
|
return [
|
||||||
|
{ x: p1.x + t0 * dx, y: p1.y + t0 * dy },
|
||||||
|
{ x: p1.x + t1 * dx, y: p1.y + t1 * dy },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Boundary-Erkennung: BBox aus einer Kontur (Polygon-Punkte).
|
||||||
|
* Weniger als 2 Punkte → null.
|
||||||
|
*/
|
||||||
|
export function computeBoundary(
|
||||||
|
pts: Array<{ x: number; y: number }>,
|
||||||
|
): { minX: number; minY: number; maxX: number; maxY: number } | null {
|
||||||
|
if (!Array.isArray(pts) || pts.length < 2) return null;
|
||||||
|
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
||||||
|
for (const p of pts) {
|
||||||
|
if (p.x < minX) minX = p.x;
|
||||||
|
if (p.y < minY) minY = p.y;
|
||||||
|
if (p.x > maxX) maxX = p.x;
|
||||||
|
if (p.y > maxY) maxY = p.y;
|
||||||
|
}
|
||||||
|
return { minX, minY, maxX, maxY };
|
||||||
|
}
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
/**
|
||||||
|
* Task CAD-2 - Echte Hatch-Muster + Boundary-Erkennung.
|
||||||
|
*
|
||||||
|
* HATCH_PATTERNS: Standard-CAD-Muster (ANSI31, NET, GRASS etc.)
|
||||||
|
* als Linien-Segment-Generatoren fuer eine BBox.
|
||||||
|
* computeBoundary: umschlossene Flaeche aus Kontur-Punkten.
|
||||||
|
*/
|
||||||
|
import { describe, it, expect } from 'vitest';
|
||||||
|
import {
|
||||||
|
HATCH_PATTERNS,
|
||||||
|
getHatchPattern,
|
||||||
|
generateHatchLines,
|
||||||
|
computeBoundary,
|
||||||
|
type HatchPatternId,
|
||||||
|
} from '../src/services/hatchPatternService';
|
||||||
|
|
||||||
|
describe('CAD-2: HATCH_PATTERNS Bibliothek', () => {
|
||||||
|
it('enthaelt die Standard-CAD-Muster', () => {
|
||||||
|
const ids = HATCH_PATTERNS.map((p) => p.id);
|
||||||
|
expect(ids).toContain('ANSI31');
|
||||||
|
expect(ids).toContain('ANSI32');
|
||||||
|
expect(ids).toContain('ANSI37');
|
||||||
|
expect(ids).toContain('NET');
|
||||||
|
expect(ids).toContain('GRASS');
|
||||||
|
expect(ids).toContain('DOTS');
|
||||||
|
expect(ids).toContain('SOLID');
|
||||||
|
expect(ids).toContain('lines'); // Alte bleiben erhalten
|
||||||
|
expect(ids).toContain('cross');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('jedes Muster hat id, label, description; spacing nur bei Nicht-SOLID', () => {
|
||||||
|
for (const p of HATCH_PATTERNS) {
|
||||||
|
expect(p.id.length).toBeGreaterThan(0);
|
||||||
|
expect(p.label.length).toBeGreaterThan(0);
|
||||||
|
// SOLID braucht keinen Abstand (Vollfuellung)
|
||||||
|
if (!p.solid) {
|
||||||
|
expect(p.defaultSpacing).toBeGreaterThan(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('getHatchPattern liefert undefined fuer unbekannte ID', () => {
|
||||||
|
expect(getHatchPattern('gibts-nicht')).toBeUndefined();
|
||||||
|
expect(getHatchPattern('ANSI31')).toBeDefined();
|
||||||
|
expect(getHatchPattern('ANSI31')!.label).toContain('1');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('CAD-2: generateHatchLines (Muster-Geometrie)', () => {
|
||||||
|
const bbox = { minX: 0, minY: 0, maxX: 100, maxY: 50 };
|
||||||
|
const spacing = 10;
|
||||||
|
|
||||||
|
it('ANSI31 (45 Grad): erzeugt Diagonal-Linien', () => {
|
||||||
|
const lines = generateHatchLines('ANSI31', bbox, spacing);
|
||||||
|
expect(lines.length).toBeGreaterThan(3);
|
||||||
|
for (const seg of lines) {
|
||||||
|
const dx = seg.to.x - seg.from.x;
|
||||||
|
const dy = seg.to.y - seg.from.y;
|
||||||
|
expect(Math.abs(dy / dx)).toBeCloseTo(1, 1); // 45 Grad
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('NET: erzeugt horizontale UND vertikale Linien', () => {
|
||||||
|
const lines = generateHatchLines('NET', bbox, spacing);
|
||||||
|
const horiz = lines.filter((l) => l.from.y === l.to.y);
|
||||||
|
const vert = lines.filter((l) => l.from.x === l.to.x);
|
||||||
|
expect(horiz.length).toBeGreaterThan(0);
|
||||||
|
expect(vert.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('DOTS: erzeugt kleine Kreise (als 4-Punkt-Polygone)', () => {
|
||||||
|
const lines = generateHatchLines('DOTS', bbox, spacing);
|
||||||
|
expect(lines.length).toBeGreaterThan(0);
|
||||||
|
// DOTS sind als polyline-segments repraesentiert
|
||||||
|
});
|
||||||
|
|
||||||
|
it('SOLID: erzeugt EINE grosse Flaeche (fill)', () => {
|
||||||
|
const lines = generateHatchLines('SOLID', bbox, spacing);
|
||||||
|
expect(lines).toHaveLength(1);
|
||||||
|
expect((lines[0] as { fill?: string }).fill).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('unbekanntes Muster: leeres Array (renderer-sicher)', () => {
|
||||||
|
expect(generateHatchLines('gibts-nicht', bbox, spacing)).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('CAD-2: computeBoundary (Boundary-Erkennung)', () => {
|
||||||
|
it('Rechteck-Kontur: BBox wird korrekt berechnet', () => {
|
||||||
|
const pts = [{ x: 0, y: 0 }, { x: 100, y: 0 }, { x: 100, y: 50 }, { x: 0, y: 50 }];
|
||||||
|
const b = computeBoundary(pts);
|
||||||
|
expect(b).toEqual({ minX: 0, minY: 0, maxX: 100, maxY: 50 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Dreieck: BBox umschliesst alle Punkte', () => {
|
||||||
|
const pts = [{ x: 10, y: 0 }, { x: 90, y: 80 }, { x: 10, y: 80 }];
|
||||||
|
const b = computeBoundary(pts);
|
||||||
|
expect(b.minX).toBe(10);
|
||||||
|
expect(b.maxX).toBe(90);
|
||||||
|
expect(b.minY).toBe(0);
|
||||||
|
expect(b.maxY).toBe(80);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('leere/wenige Punkte: null-Boundary', () => {
|
||||||
|
expect(computeBoundary([])).toBeNull();
|
||||||
|
expect(computeBoundary([{ x: 0, y: 0 }])).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user