mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-02 04:38:55 +00:00
perf: reuse wall union across projection toggles
Issue: #89 User-Visible: no
This commit is contained in:
File diff suppressed because one or more lines are too long
+174
-174
File diff suppressed because one or more lines are too long
Vendored
+174
-174
File diff suppressed because one or more lines are too long
+24
-6
@@ -55,7 +55,8 @@ import {
|
||||
import {
|
||||
degradeWalls, rekeyWallsAfterMove,
|
||||
setWallThickness, setWallThicknessForRoom, cmToField, wallCmToUnits,
|
||||
wallEdgeBodies, wallBodiesGeometry, wallBodiesUnionPath, paperRoomShapesWithWalls,
|
||||
wallEdgeBodies, wallBodiesGeometry, wallBodiesPathFromGeometry, wallBodiesUnionPath,
|
||||
paperRoomShapesWithWalls,
|
||||
innerContourForRoom, roomWallProfile, outsetContour,
|
||||
openingInnerFaceOffsetFromIndex, openingTunnelGeometriesFromIndex,
|
||||
openingWallIndex as buildOpeningWallIndex, applyWallThicknessToNewRoom,
|
||||
@@ -4418,7 +4419,15 @@ class HouseplanCard extends LitElement {
|
||||
}
|
||||
|
||||
/** The rectangle "fit to screen" fits — always the content (docs/CANVAS.md). */
|
||||
private _isoSource(): { key: string; build: () => any } {
|
||||
private _wallUnionKey(): string {
|
||||
return `${this._space}|${this._cfgEpoch}|${this._spaceModel().rooms.length}`;
|
||||
}
|
||||
|
||||
private _isoSource(): {
|
||||
key: string;
|
||||
wallKey: string;
|
||||
build: () => { geom: any; depthUnits: number };
|
||||
} {
|
||||
const space = this._spaceModel();
|
||||
const walls = this._spaceWalls;
|
||||
const openCuts = this._openPairs().flatMap((pair) => pair.segs);
|
||||
@@ -4436,6 +4445,7 @@ class HouseplanCard extends LitElement {
|
||||
})}`;
|
||||
return {
|
||||
key,
|
||||
wallKey: this._wallUnionKey(),
|
||||
build: () => {
|
||||
const extras = physicalBodies(space, this._cellCm, this._gridPitch);
|
||||
const united = walls.length || extras.length
|
||||
@@ -4443,9 +4453,9 @@ class HouseplanCard extends LitElement {
|
||||
space.rooms, walls, openCuts, openings,
|
||||
this._wallKeyPitch, this._cellCm, this._gridPitch, NORM_W, extras,
|
||||
)
|
||||
: { geom: [] };
|
||||
: { geom: [], depthUnits: 0 };
|
||||
if (!united) throw new Error('wall boolean geometry failed');
|
||||
return united.geom;
|
||||
return united;
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -4461,7 +4471,15 @@ class HouseplanCard extends LitElement {
|
||||
if (cached) return { key: source.key, ...cached };
|
||||
const flat = this._frameOf().rect;
|
||||
const frame = projectedFrame({ rect: flat, wallHeight: ISO_WALL_HEIGHT });
|
||||
const geometry = buildIsoWallGeometry(source.build());
|
||||
const united = source.build();
|
||||
const geometry = buildIsoWallGeometry(united.geom);
|
||||
// Flat and iso are two projections of the same canonical union. Seed the
|
||||
// existing one-entry flat cache while that union is already in hand so a
|
||||
// Flat -> Volumetric toggle does not repeat the polygon boolean pass.
|
||||
this._wallUnionCache = {
|
||||
key: source.wallKey,
|
||||
value: wallBodiesPathFromGeometry(united),
|
||||
};
|
||||
const value = { geometry, frame };
|
||||
lruWrite(this._isoGeometryCache, source.key, value, 8);
|
||||
return { key: source.key, ...value };
|
||||
@@ -10018,7 +10036,7 @@ class HouseplanCard extends LitElement {
|
||||
angle: Number(o.angle) || 0,
|
||||
length: (Number(o.length) > 0 ? Number(o.length) : 0.9) * NORM_W,
|
||||
}));
|
||||
const unionKey = `${this._space}|${this._cfgEpoch}|${this._spaceModel().rooms.length}`;
|
||||
const unionKey = this._wallUnionKey();
|
||||
if (!this._wallUnionCache || this._wallUnionCache.key !== unionKey) {
|
||||
this._wallUnionCache = {
|
||||
key: unionKey,
|
||||
|
||||
+16
-2
@@ -1225,6 +1225,20 @@ function polyclipToPathD(geom: any): string {
|
||||
return d;
|
||||
}
|
||||
|
||||
/**
|
||||
* Project an already computed canonical wall union into the flat SVG path.
|
||||
* Isometric rendering and the ordinary flat wall layer share the same boolean
|
||||
* result; keeping this conversion separate prevents a projection toggle from
|
||||
* repeating the expensive polygon union merely to obtain its path string.
|
||||
*/
|
||||
export function wallBodiesPathFromGeometry(
|
||||
united: { geom: any; depthUnits: number } | null,
|
||||
): { d: string; depthUnits: number; fillRule: 'evenodd' } | null {
|
||||
if (!united) return null;
|
||||
const d = polyclipToPathD(united.geom);
|
||||
return d ? { d, depthUnits: united.depthUnits, fillRule: 'evenodd' } : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mitre patches at an endpoint where a virtual stretch meets real walls that
|
||||
* belong to different room contours.
|
||||
@@ -1462,8 +1476,8 @@ export function wallBodiesUnionPath(
|
||||
const united = wallBodiesGeometry(
|
||||
rooms, walls, openCuts, openings, pitch, cellCm, gridPitch, coordScale, extraBodies,
|
||||
);
|
||||
const d = united ? polyclipToPathD(united.geom) : '';
|
||||
if (united && d) return { d, depthUnits: united.depthUnits, fillRule: 'evenodd' };
|
||||
const projected = wallBodiesPathFromGeometry(united);
|
||||
if (projected) return projected;
|
||||
if (united) return null; // successful empty result: do not resurrect raw rings
|
||||
// fall back to evenodd rings concatenated
|
||||
const rings = wallBodyRings(rooms, walls, openCuts, pitch, cellCm, gridPitch, coordScale);
|
||||
|
||||
@@ -6,7 +6,8 @@ import {
|
||||
setWallThickness, setWallThicknessForRoom, applyWallThicknessToNewRoom,
|
||||
drawWallPreviewD, DRAW_WALL_DEFAULT_CM, clampWallCm, cmToField, fieldToCm,
|
||||
wallCmToUnits, insetContour, inwardNormal, edgeKinds, wallEdgeBodies,
|
||||
wallBodyRings, wallBodiesUnionPath, innerContourForRoom,
|
||||
wallBodyRings, wallBodiesGeometry, wallBodiesPathFromGeometry, wallBodiesUnionPath,
|
||||
innerContourForRoom,
|
||||
paperRoomShapesWithWalls, WALL_MIN_CM, WALL_MAX_CM, MITRE_LIMIT,
|
||||
atomicPolyForRoom, insetOffsetsForRoom, wallIntervals, materializeWallIntervals,
|
||||
normalizeWallIntervals,
|
||||
@@ -669,6 +670,19 @@ test('wallBodiesUnionPath: single fully-thick room keeps a floor hole', () => {
|
||||
assert.ok((united.d.match(/M/g) || []).length >= 2, united.d);
|
||||
});
|
||||
|
||||
test('wallBodiesPathFromGeometry reuses the canonical union without changing its flat path', () => {
|
||||
const room = { id: 'shared', poly: [[100, 100], [300, 100], [300, 300], [100, 300]] };
|
||||
const walls = applyWallThicknessToNewRoom([], [room], 'shared', 15, 0.01, [], 1000);
|
||||
const geometry = wallBodiesGeometry(
|
||||
[room], walls, [], [], 0.01, cellCm, GRID_PITCH, 1000,
|
||||
);
|
||||
const reused = wallBodiesPathFromGeometry(geometry);
|
||||
const direct = wallBodiesUnionPath(
|
||||
[room], walls, [], [], 0.01, cellCm, GRID_PITCH, 1000,
|
||||
);
|
||||
assert.deepEqual(reused, direct);
|
||||
});
|
||||
|
||||
test('wallBodiesUnionPath: a parent floor never erases a nested room wall', () => {
|
||||
const scale = 1000;
|
||||
const rooms = [
|
||||
|
||||
Reference in New Issue
Block a user