qpolygon-utils 지붕 생성 구조 / drawRoofByAttribute 내부 구조 / skeleton-utils 용마루 두 경로 차이 분석. roofline-edit 구현 시 reference. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5.4 KiB
| name | description | type |
|---|---|---|
| skeleton-utils 용마루 지붕 두 경로 차이 | src/util/skeleton-utils.js 의 drawSkeletonRidgeRoof(일반) vs drawSkeletonRidgeRoofFromBaseLines(오버 전용) 차이 + skeletonBuilder 전처리 파이프라인 + SK_INPUT_USE_WALL_BASELINE_DIRECT 플래그. 용마루(전체 EAVES) 케이스 수정 전 reference. | project |
skeleton-utils 용마루 지붕 — 일반 vs 오버 두 경로
src/util/skeleton-utils.js. 용마루 지붕(라인 type 이 전부 EAVES) 케이스 전용. straight-skeleton(SkeletonBuilder.BuildFromGeoJSON)으로 용마루/추녀 내부선 생성. 박공/한쪽흐름/변별은 qpolygon-utils.js 가 담당 (별도 메모리 참조: qpolygon-utils 지붕 생성 구조).
1. 분기 (QPolygon.js:677-684)
drawHelpLine 에서 types.every(EAVES) 일 때:
verdict = verifyMoveBoundary(this.id, this.canvas)
if verdict === 'crossed': drawSkeletonRidgeRoofFromBaseLines ← 오버(경계 넘음) 전용
else: drawSkeletonRidgeRoof ← 일반
'crossed' = 마루/벽 이동이 roof 경계를 넘어선 "오버" 상태. wall.baseLines 가 이미 오버된 좌표로 mutate 됨.
2. drawSkeletonRidgeRoof (일반, :71-75)
export const drawSkeletonRidgeRoof = (roofId, canvas, textMode) => {
skeletonBuilder(roofId, canvas, textMode) // 얇은 래퍼
}
실체는 skeletonBuilder (:922). 전처리 파이프라인:
- roof.lines ↔ wall.lines index 매칭으로
wallLineid 주입 baseLines = wall.baseLines.filter(planeSize>0),createOrderedBasePoints로 roof.points 순서 정렬- 45° 대각 확장 계산 (:1011-1066): contactData(baseLine→roofLine 방향벡터) →
maxStep(전체 꼭짓점 중 min(|dx|,|dy|) 의 max) → centroid 보정 →changRoofLinePoints(maxContactDistance=√2·maxStep 확장) - 마루이동(
moveFlowLine/moveUpDown≠ 0) 시safeMovedPointsWithFallback의movedPoints로 대체 (:1071-1121) - ⚠️
SK_INPUT_USE_WALL_BASELINE_DIRECT = true(:36, :1128) → 위 3·4 결과를 전부 무시하고orderedBaseLinePoints(=wall.baseLines corner) 직접 사용. + 평행오버 corner 흡수(DUP_EPS=1.0, COLLINEAR_EPS=50.0, 최대 5패스) - 좌표 유효성/중복점/일직선/방향 뒤집힘(isOverDetected, cross product 부호 반전) 검출 (:1175-1236)
roof.skeletonPoints갱신 → SK 빌드 →createInnerLinesFromSkeleton
즉 45° 확장 코드는 계산만 하고 버려지는 상태(점진 리팩토링 흔적). 실입력은 baseLines corner.
3. drawSkeletonRidgeRoofFromBaseLines (오버 전용, :1363-1444)
헤더 주석(:1350-1362) 의 존재 이유: 일반 경로의 45° 확장/roof 경계 클램핑이 오버 상태에선 폴리곤 자가교차/경계 되돌림을 일으켜 엉뚱한 SK 를 만든다 → 전처리 다 건너뛰고 baseLines 그대로 입력.
처리:
wall.baseLines순차 끝점({x:bl.x1, y:bl.y1}) 수집, 확장·offset 없이 그대로.planeSize<1(SHOULDER_ABSORBED) 스킵- rawPoints < 3 이면 중단
roof.skeletonPoints = rawPoints(오버 좌표 그대로)perturbPolygonPoints→toGeoJSON→SkeletonBuilder.BuildFromGeoJSONcreateInnerLinesFromSkeleton(..., isOverDetected=true, rawPoints)canvas.skeletonStates[roofId]=true,canvas.skeletonLines갱신canvas.skeleton.lastPoints는 기존 값 보존 (__preservedLastPoints) — 오버 상태에서 새 누적 기준점 안 만듦
건드리지 않는 것: roof.points / roof.lines / outerLine / lengthText (주석 :1358 명시).
4. 핵심 차이 요약
| 항목 | drawSkeletonRidgeRoof (일반) | ...FromBaseLines (오버) |
|---|---|---|
| 트리거 | EAVES 전부 + verdict ≠ crossed | EAVES 전부 + verdict === crossed |
| 실체 | skeletonBuilder 래퍼 | 자체 SK 빌드 |
| SK 입력 | 전처리 파이프라인(but 플래그로 baseLine 직접) | baseLines 끝점 그대로 |
| roof.points/lines/outerLine/lengthText | 마루이동 시 갱신·검증 | 불변 (side-effect free) |
| lastPoints | 새로 계산/누적 | 보존 |
| isOverDetected | cross 부호 반전 검출로 산출 | 무조건 true 마킹 |
| 공통 | createInnerLinesFromSkeleton, planeSize<1 스킵 | 동일 |
현 시점 진짜 차이: 플래그(SK_INPUT_USE_WALL_BASELINE_DIRECT=true) 때문에 두 함수의 SK 입력 좌표는 거의 같아짐. 실질 차이는 주변 상태(roof.points/lastPoints 등) 를 mutate 하느냐 보존하느냐. 오버를 undo/redo 단축수단으로 쓸 때 history snapshot(wall.baseLines/roof.lines) 오염 없이 SK 만 재그리기 위함 (현재 feature/redo-undo 브랜치 맥락).
5. 수정 시 주의
- 45° 확장 코드(:1011-1066) 는 dead 입력 —
SK_INPUT_USE_WALL_BASELINE_DIRECT끄지 않는 한 결과 미사용. 만지기 전 플래그 확인. createInnerLinesFromSkeleton(:1455) 은 두 경로 공유 — 여기 바꾸면 양쪽 영향.processEavesEdge(처마) +processGableEdge(케라바 후처리) 호출.- 오버 경로는
lastPoints보존이 의도 — 무심코 갱신하면 누적 마루이동 기준 붕괴. - 예외 시 보호 정책(:1341-1346): SK/innerLines 유지,
skeletonStates[roofId]=false만,skeletonLines비우지 않음 (이전 성공 상태 보존). verifyMoveBoundary는 QPolygon.drawHelpLine 과 skeletonBuilder(:1077) 두 곳에서 호출 — 'crossed' 시 경고 로그만, 흐름은 진행.