qcast-front/.claude/memory/project_skeleton_utils_ridge_roof.md
Jaeyoung Lee 4644269852 docs: 지붕 생성 로직 분석 메모리 문서 추가
qpolygon-utils 지붕 생성 구조 / drawRoofByAttribute 내부 구조 /
skeleton-utils 용마루 두 경로 차이 분석. roofline-edit 구현 시 reference.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 09:52:17 +09:00

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). 전처리 파이프라인:

  1. roof.lines ↔ wall.lines index 매칭으로 wallLine id 주입
  2. baseLines = wall.baseLines.filter(planeSize>0), createOrderedBasePoints 로 roof.points 순서 정렬
  3. 45° 대각 확장 계산 (:1011-1066): contactData(baseLine→roofLine 방향벡터) → maxStep(전체 꼭짓점 중 min(|dx|,|dy|) 의 max) → centroid 보정 → changRoofLinePoints (maxContactDistance=√2·maxStep 확장)
  4. 마루이동(moveFlowLine/moveUpDown ≠ 0) 시 safeMovedPointsWithFallbackmovedPoints 로 대체 (:1071-1121)
  5. ⚠️ 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패스)
  6. 좌표 유효성/중복점/일직선/방향 뒤집힘(isOverDetected, cross product 부호 반전) 검출 (:1175-1236)
  7. roof.skeletonPoints 갱신 → SK 빌드 → createInnerLinesFromSkeleton

즉 45° 확장 코드는 계산만 하고 버려지는 상태(점진 리팩토링 흔적). 실입력은 baseLines corner.

3. drawSkeletonRidgeRoofFromBaseLines (오버 전용, :1363-1444)

헤더 주석(:1350-1362) 의 존재 이유: 일반 경로의 45° 확장/roof 경계 클램핑이 오버 상태에선 폴리곤 자가교차/경계 되돌림을 일으켜 엉뚱한 SK 를 만든다 → 전처리 다 건너뛰고 baseLines 그대로 입력.

처리:

  1. wall.baseLines 순차 끝점({x:bl.x1, y:bl.y1}) 수집, 확장·offset 없이 그대로. planeSize<1(SHOULDER_ABSORBED) 스킵
  2. rawPoints < 3 이면 중단
  3. roof.skeletonPoints = rawPoints (오버 좌표 그대로)
  4. perturbPolygonPointstoGeoJSONSkeletonBuilder.BuildFromGeoJSON
  5. createInnerLinesFromSkeleton(..., isOverDetected=true, rawPoints)
  6. canvas.skeletonStates[roofId]=true, canvas.skeletonLines 갱신
  7. 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. 수정 시 주의

  1. 45° 확장 코드(:1011-1066) 는 dead 입력SK_INPUT_USE_WALL_BASELINE_DIRECT 끄지 않는 한 결과 미사용. 만지기 전 플래그 확인.
  2. createInnerLinesFromSkeleton (:1455) 은 두 경로 공유 — 여기 바꾸면 양쪽 영향. processEavesEdge(처마) + processGableEdge(케라바 후처리) 호출.
  3. 오버 경로는 lastPoints 보존이 의도 — 무심코 갱신하면 누적 마루이동 기준 붕괴.
  4. 예외 시 보호 정책(:1341-1346): SK/innerLines 유지, skeletonStates[roofId]=false 만, skeletonLines 비우지 않음 (이전 성공 상태 보존).
  5. verifyMoveBoundary 는 QPolygon.drawHelpLine 과 skeletonBuilder(:1077) 두 곳에서 호출 — 'crossed' 시 경고 로그만, 흐름은 진행.