mirror of
https://github.com/Matysh/houseplan-card
synced 2026-08-01 00:48:29 +00:00
room settings button: centroid pull breaks the plateau tie
The inscribed-circle criterion is FLAT along the long axis of any elongated room — every midline point fits the same circle — and a plain argmax took the first plateau sample: left of centre on the owner's kitchen-living room, above centre in the sauna. The score now subtracts a soft pull toward the area centroid (shoelace-weighted): on the plateau the nearest- to-centroid point wins, while real clearance differences still dominate, so the point never wanders into a thinner limb of an L. Verified on a replica of the owner's floor: kitchen slab centre within a few units, sauna dead-centre both axes. Units: wide and tall rectangles centre on both axes; the L keeps to its slab near the centroid x.
This commit is contained in:
+28
-5
@@ -250,6 +250,24 @@ export function poleOfInaccessibility(poly: number[][], steps = 24): number[] {
|
||||
const ys = poly.map((p) => p[1]);
|
||||
const minX = Math.min(...xs), maxX = Math.max(...xs);
|
||||
const minY = Math.min(...ys), maxY = Math.max(...ys);
|
||||
const span = Math.max(maxX - minX, maxY - minY) || 1;
|
||||
// Area centroid (shoelace-weighted). Clearance alone has a PLATEAU on any
|
||||
// elongated room — every point of the long midline fits the same circle —
|
||||
// and a plain argmax took the first plateau point: left of centre on the
|
||||
// owner's kitchen, above centre in the sauna. A soft pull toward the
|
||||
// centroid breaks the tie along the plateau without ever dragging the
|
||||
// point into a thinner limb (clearance differences dominate the score).
|
||||
let a2 = 0, cx = 0, cy = 0;
|
||||
for (let i = 0; i < poly.length; i++) {
|
||||
const p = poly[i], q = poly[(i + 1) % poly.length];
|
||||
const cross = p[0] * q[1] - q[0] * p[1];
|
||||
a2 += cross;
|
||||
cx += (p[0] + q[0]) * cross;
|
||||
cy += (p[1] + q[1]) * cross;
|
||||
}
|
||||
const centroid = Math.abs(a2) > 1e-9
|
||||
? [cx / (3 * a2), cy / (3 * a2)]
|
||||
: [(minX + maxX) / 2, (minY + maxY) / 2];
|
||||
const clearance = (x: number, y: number): number => {
|
||||
if (!pointInPolygon([x, y], poly)) return -Infinity;
|
||||
let d = Infinity;
|
||||
@@ -259,14 +277,19 @@ export function poleOfInaccessibility(poly: number[][], steps = 24): number[] {
|
||||
}
|
||||
return d;
|
||||
};
|
||||
const score = (x: number, y: number): number => {
|
||||
const d = clearance(x, y);
|
||||
if (d === -Infinity) return d;
|
||||
return d - 0.08 * Math.hypot(x - centroid[0], y - centroid[1]) - 0.0001 * span;
|
||||
};
|
||||
let best: number[] | null = null;
|
||||
let bestD = -Infinity;
|
||||
let bestS = -Infinity;
|
||||
for (let i = 1; i < steps; i++) {
|
||||
for (let j = 1; j < steps; j++) {
|
||||
const x = minX + ((maxX - minX) * i) / steps;
|
||||
const y = minY + ((maxY - minY) * j) / steps;
|
||||
const d = clearance(x, y);
|
||||
if (d > bestD) { bestD = d; best = [x, y]; }
|
||||
const sc = score(x, y);
|
||||
if (sc > bestS) { bestS = sc; best = [x, y]; }
|
||||
}
|
||||
}
|
||||
if (best) {
|
||||
@@ -275,8 +298,8 @@ export function poleOfInaccessibility(poly: number[][], steps = 24): number[] {
|
||||
for (let i = -4; i <= 4; i++) {
|
||||
for (let j = -4; j <= 4; j++) {
|
||||
const x = bx + (cw * i) / 4, y = by + (ch * j) / 4;
|
||||
const d = clearance(x, y);
|
||||
if (d > bestD) { bestD = d; best = [x, y]; }
|
||||
const sc = score(x, y);
|
||||
if (sc > bestS) { bestS = sc; best = [x, y]; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user