feat: v0.6 分割树架构——横梃竖梃完整编辑
- 逆向确认原站设计思路: 基底多边形+mm.sp分割树(polyId指向作用区域), 运行时重放得到最终窗格(254套样本验证重放语义) - 前端 replayShape: 分割树重放还原真实网格(平开1矩形→4-7格) - 中梃编辑: 点击选中高亮/拖动移动/删除(直接操作sp源真值后重放) - 加梃升级: polyId.idx 精确指向被点窗格 - Python报价算料同语义重放: 中梃长度精确到实际作用跨度 - 方案文档§3.3: 原站设计思路+编辑器完成设计
This commit is contained in:
@@ -119,3 +119,93 @@ export function bboxOf(points: Pt[]): { x0: number; y0: number; x1: number; y1:
|
||||
const ys = points.map(p => p.y)
|
||||
return { x0: Math.min(...xs), y0: Math.min(...ys), x1: Math.max(...xs), y1: Math.max(...ys) }
|
||||
}
|
||||
|
||||
/** 分割线轴向: v=竖梃(恒定x) h=横梃(恒定y) */
|
||||
export function spAxis(sp: SplitLine): 'v' | 'h' {
|
||||
const n = sp.line?.norm ?? { x: 1, y: 0 }
|
||||
return Math.abs(n.x) >= Math.abs(n.y) ? 'v' : 'h'
|
||||
}
|
||||
|
||||
export function spPos(sp: SplitLine): number {
|
||||
return spAxis(sp) === 'v' ? sp.line.pt.x : sp.line.pt.y
|
||||
}
|
||||
|
||||
/**
|
||||
* 分割树重放(逆向自原站 shapeManager 的分割模型):
|
||||
* polygon.polygon 为基底多边形, mm.sp[] 为分割树 — 每条 sp 通过 polyId.idx 指向
|
||||
* 其作用区域, 按顺序应用后得到最终窗格。重放时:
|
||||
* - idx 越界/为-1 → 作用于基底区域0
|
||||
* - 分割坐标落在目标区域边上(退化)或目标非凸 → 跳过(no-op), 该梃按目标bbox裁剪绘制
|
||||
*/
|
||||
export function replayShape(shape: FrameShape): {
|
||||
regions: Segment[][]
|
||||
clips: { sp: SplitLine; bb: { x0: number; y0: number; x1: number; y1: number } }[]
|
||||
} {
|
||||
const base = (shape.polygon?.polygon ?? []).map(r => r.map(s => ({ ps: { ...s.ps }, pe: { ...s.pe } })))
|
||||
const regions: Segment[][] = base.map(r => r)
|
||||
const clips: { sp: SplitLine; bb: { x0: number; y0: number; x1: number; y1: number } }[] = []
|
||||
for (const sp of (shape.mm?.sp ?? []) as SplitLine[]) {
|
||||
if (!sp?.line?.pt || !sp.line.norm) continue
|
||||
const axis = spAxis(sp)
|
||||
const pos = spPos(sp)
|
||||
let idx = sp.polyId?.idx ?? -1
|
||||
if (!Number.isInteger(idx) || idx < 0 || idx >= regions.length) idx = 0
|
||||
const target = regions[idx]
|
||||
if (!target || target.length < 3) { continue }
|
||||
const bb = bboxOf(regionPoints(target))
|
||||
const lo = axis === 'v' ? bb.x0 : bb.y0
|
||||
const hi = axis === 'v' ? bb.x1 : bb.y1
|
||||
if (pos <= lo + 0.5 || pos >= hi - 0.5) {
|
||||
clips.push({ sp, bb }) // 退化: 边线上的记录, 仅用于绘制
|
||||
continue
|
||||
}
|
||||
const split = splitPolySegments(target, axis, pos)
|
||||
if (!split) { clips.push({ sp, bb }); continue }
|
||||
regions.splice(idx, 1, split.a, split.b)
|
||||
clips.push({ sp, bb })
|
||||
}
|
||||
return { regions, clips }
|
||||
}
|
||||
|
||||
/** 两次交叉点法分割闭合多边形段链(凸区域), 失败返回 null */
|
||||
export function splitPolySegments(region: Segment[], axis: 'v' | 'h', pos: number):
|
||||
{ a: Segment[]; b: Segment[] } | null {
|
||||
const pts = regionPoints(region)
|
||||
const n = pts.length
|
||||
const get = (p: Pt) => (axis === 'v' ? p.x : p.y)
|
||||
const crossings: { i: number; p: Pt }[] = []
|
||||
for (let i = 0; i < n; i++) {
|
||||
const a = pts[i], b = pts[(i + 1) % n]
|
||||
const da = get(a) - pos, db = get(b) - pos
|
||||
if ((da < 0 && db > 0) || (da > 0 && db < 0)) {
|
||||
const t = da / (da - db)
|
||||
crossings.push({
|
||||
i,
|
||||
p: axis === 'v'
|
||||
? { x: pos, y: a.y + (b.y - a.y) * t }
|
||||
: { x: a.x + (b.x - a.x) * t, y: pos },
|
||||
})
|
||||
}
|
||||
}
|
||||
if (crossings.length !== 2) return null
|
||||
const [c1, c2] = crossings
|
||||
if (c1.i === c2.i) return null
|
||||
const collect = (from: typeof c1, to: typeof c2): Pt[] => {
|
||||
const ps: Pt[] = [from.p]
|
||||
for (let k = (from.i + 1) % n; ; k = (k + 1) % n) {
|
||||
ps.push(pts[k])
|
||||
if (k === to.i) break
|
||||
}
|
||||
ps.push(to.p)
|
||||
return ps.filter((p, i) => i === 0 || Math.abs(p.x - ps[i - 1].x) > 0.01 || Math.abs(p.y - ps[i - 1].y) > 0.01)
|
||||
}
|
||||
const toSegs = (ps: Pt[]): Segment[] => {
|
||||
const segs: Segment[] = []
|
||||
for (let i = 0; i < ps.length; i++) segs.push({ ps: ps[i], pe: ps[(i + 1) % ps.length], name: 'segment' })
|
||||
return segs
|
||||
}
|
||||
const polyA = collect(c1, c2)
|
||||
const polyB = collect(c2, c1)
|
||||
if (polyA.length < 3 || polyB.length < 3) return null
|
||||
return { a: toSegs(polyA), b: toSegs(polyB) }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user