feat: v1.5 3D 门窗开合动画
- 扇推断逻辑抽取为共享模块 inferSashGlyphs(2D符号与3D动画共用) - WindoorThreed 重写: 扇面板独立网格组 平开扇绕铰链旋转(od定铰链侧, 门75°/平开32°/双向门40°) 推拉扇沿轨道平移(双扇前后轨交替) 区域归属扇的不再重复渲染静态玻璃 - 开合控制条: 开合度滑杆+自动演示+全开/全关, 平滑插值动画 - 组合画布 3D 预览自动获得开合能力
This commit is contained in:
@@ -37,7 +37,7 @@
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import {
|
||||
bboxOf, parseDrawing, regionPoints, replayShape, spAxis, spPos,
|
||||
bboxOf, inferSashGlyphs, parseDrawing, regionPoints, replayShape, spAxis, spPos,
|
||||
type DrawingData, type FrameShape, type Pt, type Segment, type SashShape, type SplitLine,
|
||||
} from '../types/drawing'
|
||||
|
||||
@@ -245,68 +245,8 @@ function drawFrame(
|
||||
ctx.strokeRect(x0 + inset, y0 + inset, (x1 - x0) - inset * 2, (y1 - y0) - inset * 2)
|
||||
}
|
||||
|
||||
// 扇(推断: 有效bd → polyId区域(双扇对半) → 帧bbox按扇数均分)
|
||||
const regionBBs = regions.map(pts => bboxOf(pts))
|
||||
type Glyph = { x0: number; y0: number; x1: number; y1: number; od: number; openToward?: string; isDoor?: boolean; wtf?: boolean }
|
||||
const glyphs: Glyph[] = []
|
||||
const bdRect = (bd: NonNullable<SashShape['bd']>) => {
|
||||
const r = {
|
||||
x0: Math.min(bd.from.x, bd.to.x), y0: Math.min(bd.from.y, bd.to.y),
|
||||
x1: Math.max(bd.from.x, bd.to.x), y1: Math.max(bd.from.y, bd.to.y),
|
||||
}
|
||||
return !bd.hidden && r.x1 - r.x0 > 10 && r.y1 - r.y0 > 10 ? r : null
|
||||
}
|
||||
const pushGlyphs = (r: { x0: number; y0: number; x1: number; y1: number }, entries: any[], sub: SashShape) => {
|
||||
for (const e of entries) {
|
||||
glyphs.push({
|
||||
x0: r.x0, y0: r.y0, x1: r.x1, y1: r.y1,
|
||||
od: (e.od ?? sub.od ?? 90) as number,
|
||||
openToward: (e.openToward ?? sub.openToward) as string | undefined,
|
||||
isDoor: Boolean(e.isDoor ?? sub.isDoor),
|
||||
wtf: Boolean(e.wtf ?? sub.wtf ?? sub.withTurningFrame),
|
||||
})
|
||||
}
|
||||
}
|
||||
const unmatched: { sub: SashShape; entries: any[] }[] = []
|
||||
for (const sub of (shape.sm ?? []) as SashShape[]) {
|
||||
const raw = (sub.sashes?.length ? sub.sashes : [sub]) as any[]
|
||||
const entries = raw.filter(e => e && e.enabled !== false)
|
||||
if (!entries.length) continue
|
||||
const rect = sub.bd ? bdRect(sub.bd) : null
|
||||
if (rect) { pushGlyphs(rect, entries, sub); continue }
|
||||
const idx = sub.polyId?.idx
|
||||
const rb = idx != null && regionBBs[idx] ? regionBBs[idx] : null
|
||||
if (rb) {
|
||||
if (entries.length === 2) {
|
||||
const w = rb.x1 - rb.x0, h = rb.y1 - rb.y0
|
||||
const halves = w >= h
|
||||
? [{ ...rb, x1: (rb.x0 + rb.x1) / 2 }, { ...rb, x0: (rb.x0 + rb.x1) / 2 }]
|
||||
: [{ ...rb, y1: (rb.y0 + rb.y1) / 2 }, { ...rb, y0: (rb.y0 + rb.y1) / 2 }]
|
||||
entries.forEach((e, k) => pushGlyphs(halves[k], [e], sub))
|
||||
} else {
|
||||
pushGlyphs(rb, entries, sub)
|
||||
}
|
||||
} else {
|
||||
unmatched.push({ sub, entries })
|
||||
}
|
||||
}
|
||||
if (unmatched.length && regions.length) {
|
||||
const fb = bboxOf(regions.flat())
|
||||
const K = unmatched.reduce((s, u) => s + u.entries.length, 0)
|
||||
const colW = (fb.x1 - fb.x0) / K
|
||||
let i = 0
|
||||
for (const u of unmatched) {
|
||||
for (const e of u.entries) {
|
||||
glyphs.push({
|
||||
x0: fb.x0 + i * colW, x1: fb.x0 + (i + 1) * colW, y0: fb.y0, y1: fb.y1,
|
||||
od: (e.od ?? u.sub.od ?? 90) as number,
|
||||
openToward: (e.openToward ?? u.sub.openToward) as string | undefined,
|
||||
isDoor: Boolean(e.isDoor ?? u.sub.isDoor),
|
||||
})
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
// 扇(推断: 有效bd → polyId区域(双扇对半) → 帧bbox按扇数均分) — 共享推断模块
|
||||
const glyphs = inferSashGlyphs(shape, rp.regions).sashes
|
||||
for (const g of glyphs) {
|
||||
const bx0 = tx(g.x0), bx1 = tx(g.x1), by0 = ty(g.y1), by1 = ty(g.y0)
|
||||
const w = bx1 - bx0, h = by1 - by0
|
||||
|
||||
Reference in New Issue
Block a user