qpolygon-utils 지붕 생성 구조 / drawRoofByAttribute 내부 구조 / skeleton-utils 용마루 두 경로 차이 분석. roofline-edit 구현 시 reference. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
23 KiB
| name | description | type |
|---|---|---|
| drawRoofByAttribute 내부 구조 (후반부 상세) | qpolygon-utils.js drawRoofByAttribute (1680-5455, 3777줄) 의 단계별 정밀 분석. 라인 분류→가선분(linesAnalysis) 누적→MAX_ITERATIONS 교점 처리→하단지붕 파생→최종 split. 수정 시 어느 stage 인지 식별 필수. | project |
drawRoofByAttribute 내부 구조 (후반부 상세)
src/util/qpolygon-utils.js:1680-5455 의 정밀 분해. catch-all 함수라 모든 비표준 케이스(요세무네/맨사드/형이동/벽취합/팔작/반절처/케라바 혼합 등)가 여기 모인다.
0. 데이터 흐름 한 장 요약
wall.baseLines → [zero merge] → baseLines (mutated copy)
↓
type 분류: sheds / hipAndGables / eaves / jerkinHeads / gables
↓
[stage1 처리] 각 type 별로:
- innerLines.push(drawHip/Ridge/RoofLine ...) ← 즉시 그림
- linesAnalysis.push({start, end, left, right, type, degree}) ← 가선분 누적
↓
[stage2 MAX_ITERATIONS 루프] linesAnalysis 끼리 교점 → drawHip/Ridge + newAnalysis 분기
↓
[stage3 가선분 잔여] 평행 라인 예외처리
↓
[stage4 벽취합 잔여] 벽-벽 라인 처리
↓
[stage5 하단지붕 파생] downRoofGable (A/B) + downRoofEaves
↓
[stage6 최종 split] roof.lines × innerLinesPoints 분할 → drawHip/RoofLine
↓
roof.innerLines = innerLines (덮어쓰기)
innerLines 와 linesAnalysis 는 다른 컨테이너:
innerLines: 실제 캔버스에 그려진 QLine 객체들 (drawHip/Ridge/RoofLine 반환값)linesAnalysis: 가상 선분 메타데이터 ({start, end, left, right, type, degree, gableId?, connectCnt?}) — 아직 캔버스에 안 그려짐, 교점 계산용
TYPES = { HIP, RIDGE, GABLE_LINE, NEW } 는 linesAnalysis 내부 enum (canvas 의 LINE_TYPE.SUBLINE 과 별개).
1. 전반부 (1680-1995) — 이미 파악된 영역
- 1680-1759 형이동 zero-merge:
wall.baseLines중planeSize<EPSILON인덱스를 prev/next 페어로 모아 같은 vector 라인 끼리 합쳐baseLines재구성. zero-merge 후엔wall.baseLines의 중간 라인이 사라진 짧은 형태가 됨. - 1761
checkWallPolygon:baseLines의 시작점만 모아 만든 임시 QPolygon. inPolygon 판정 전용. - 1766-1995 벽취합 corner extension: WALL+offset>0 라인의 prev/next 처리,
addRoofLine1/2추가.name='addRoofLine',attributes.type=WALLLINE.ETC.
2. analyzeLine 헬퍼 (2000-2133)
drawGableRoof 의 analyzeEavesLine 과 거의 동일하지만 모든 type 에 적용. return:
{ startPoint, endPoint, length, angleDegree, normalizedAngle,
isHorizontal, isVertical, isDiagonal,
directionVector: {x,y},
roofLine: 매칭된 roof.lines 의 한 라인,
roofVector: {x,y} (지붕 안쪽 방향, 단위벡터) }
핵심:
- tolerance 1° 로 H/V/Diagonal 분류
roof.lines중 directionVector 가 같은 것을checkRoofLines로 거름- offset=0: 좌표 직접 매칭. 실패 시
currentRoof = { roofLine: line }자기 자신 fallback (NPE 방지) - offset>0: midPoint 에서 roofVector 방향으로 offset 만큼 가선 그어서
edgesIntersection검색, intersect 길이 - offset < 0.1 으로 best match 선택
⚠️ roofLine 매칭 실패 시 currentRoof.roofLine 직접 접근 (2130) — fallback 로직이 있어서 안전하지만 추가 변경 시 NPE 주의.
3. getRidgeDrivePoint 헬퍼 (2142-2270)
마루(ridge)/이등분선 등이 반대편 케라바(GABLE) 또는 이중처마(EAVES-EAVES) 코너까지만 뻗도록 클램프하는 포인트를 계산.
- prevLine/nextLine 이 모두 EAVES 가 아니면
null(호출처에서 fallback 처리) - prevLine 이 EAVES 이고 그 prev 가 GABLE 이면 → GABLE 의 roofLine 과 testPoint 의 교점 (=GABLE 코너)
- prevLine 이 EAVES 이고 그 prev 도 EAVES 이면 → 두 EAVES 의 사잇각 vector 로 hip edge 그어서 testPoint 와의 교점
- nextLine 쪽도 대칭
- 둘 다 있으면 가까운 쪽
- prevHasGable / nextHasGable 우선순위:
prevLength <= nextLength면 prev, 아니면 next 의 GABLE 사용
호출처: hipAndGables, ridgeEaves, jerkinHeads, MAX_ITERATIONS 루프(NEW 가선분 분기 시)
4. 라인 분류 (2275-2300)
baseLines.forEach(switch on attributes.type):
SHED → sheds[]
HIPANDGABLE → hipAndGables[] (팔작지붕/이리모야)
EAVES → eaves[] (처마)
JERKINHEAD → jerkinHeads[] (반절처)
GABLE → gables[] (케라바/박공)
default → 무시 (WALL, ETC, DEFAULT 등)
linesAnalysis = [] 초기화 (2303).
5. 단계별 처리 (1~5)
5-1. sheds.forEach (2306-2467) — 한쪽흐름
가드: 양옆이 둘 다 GABLE 이어야 함. 아니면 return.
- 맞은편 EAVES 라인 찾기 → shedDegree 산출 (없으면 4寸 default)
- roofLine 양 끝점에서 지붕 안쪽으로의 vector 두 개 (
checkRoofVector1/2) 그어서 반대편 roof.lines 와의 교점 (intersectPoints1/2) - HIP 가선분 2개 push (양 끝점 → 교점),
left=beforePrevIndex/nextIndex,right=prevIndex/afterNextIndex,type=TYPES.HIP,degree=shedDegree
⚠️ intersectPoints1[0] 직접 인덱스 접근 (2441) — sort 후 비어있으면 NPE.
5-2. hipAndGables.forEach (2470-2699) — 팔작지붕
가드: 양옆이 둘 다 EAVES. 그리고 prevLine/nextLine 의 vector 가 같으면(같은 방향, U자) silent return.
lineWidth = min(currentLine.attributes.width, roofLength/2)— 팔작지붕 두께 클램프- prevRoofLine/nextRoofLine 와 currentRoofLine 의 사잇각 vector 로 prevHipPoint/nextHipPoint 산출 (지붕 안쪽 방향 정규화)
- HIP 2개 즉시 그림 (
innerLines.push(drawHipLine prev, next)) - 양옆이 모두 EAVES (gable 이 짧을 때): midPoint 에서 양쪽 gable hip 두개 추가 + roofVector 반대 방향으로 10000 길이 가선 그어서 반대편 roof 교점 →
getRidgeDrivePoint로 클램프 →oppLine.attributes.type별 offset 보정 (HIPANDGABLE=width, JERKINHEAD=width/2, WALL=0) → RIDGE 가선분 push - else (gable 길이 충분):
drawRoofLine(gablePoint)즉시 그림. 길이 ~0 이면 RIDGE 가선분만 push
5-3. eaves 처리 (2701-3143)
eaves 를 planeSize 오름차순 sort 후 두 그룹으로 분리:
ridgeEaves(2704-2723): 양옆 모두 EAVES + prev/next vector 가 다른 방향 + checkWallPolygon.inPolygon(midPoint+nextVector) — 즉 처마-처마-처마 코너에서 안쪽 들여진 모양hipedEaves(2725): 나머지 (= 옆이 GABLE 등)
ridgeEaves.forEach (2731-3036) — 가장 복잡한 블록
상태 누적기:
proceedEaves[]:{prev, current, point: {x1,y1,x2,y2}, length}— 처리된 처마 hipproceedRidges[]:{prev, next, point}— 처리된 ridge
알고리즘:
- pHipVector/nHipVector (양 옆과의 사잇각) 산출, inPolygon 으로 정규화
findRoofPoints(checkPoint)헬퍼: hipEdge 와 roof.lines 교점들을 forward/backward 로 분리 후 가까운 것 선택. forward+backward / backward 2개 / forward 2개 fallback- prevHipPoint/nextHipPoint 두 hip 의
edgesIntersection으로 정점 보정 - proceedRidges 루프: 같은 base index 영향권의 ridge 가 prev/next hip 와 교차하면
isRidgePrev/isRidgeNext플래그 → hip 좌표를 forward 로 되돌림(roof 정점까지) - proceedEaves 중복 처리:
alreadyPrev/Next가 있으면 길이가 짧은 쪽으로 교체 (또는 새 hip 좌표를 기존 것으로 덮음). isRidge 플래그 시 무조건 교체 - prevHipPoint.x2 와 nextHipPoint.x2 가 같으면(=정점 만남) ridge 생성:
- midPoint 에서 ridgeVector(nextLine 방향, inPolygon 정규화) 로 가선
- roof.lines 와의 가까운 교점 → ridge 끝점
getRidgeDrivePoint로 클램프isOverlapBefore/After(beforePrev/afterNext 가 currentLine 과 동일 좌표 범위) 시 oppLine.type 별 offset 보정 분기:- 양옆에 GABLE 있음(otherGable): drivePoint 우선
- 없음 + oppLine=EAVES + 양옆에 EAVES: 길이 같으면 drivePoint, 다르면 offset
- 없음 + 그 외: offset 만 적용
- ridge 중복 체크 후
proceedRidges.push
hipedEaves.forEach (3038-3143)
가드: prevLine 이 EAVES 여야 함. 아니면 return.
- currentLine 와 prevLine 의 사잇각 vector → hipVector (inPolygon 정규화)
currentLine.x1에서 hipVector ×analyze.length길이로 가선 → roof.lines 의 forward/backward 교점- HIP 가선분 push (linesAnalysis),
degree=currentDegree(currentLine.pitch)
proceedEaves → linesAnalysis 변환 (3146-3219)
proceedEaves.forEach((eaves, index)):
- jointEaves = 같은 끝점(point.x2,y2) 공유하는 다른 eaves 검색
- jointEaves.length === 1 + ridgeEaves.length > 1:
drawHipLine 즉시 그림 (innerLines.push)
- jointEaves.length === 2 (3-way 코너):
jointIndex 3개 모두 drawHipLine 즉시
proceedRidges 에서 해당 코너 ridge 제거
"denyVector" (반대쌍 없는 vector) 찾아서 roof 와의 교점 → HIP 가선분 push (uniqueLine 좌우)
- else:
HIP 가선분 push (linesAnalysis)
proceedRidges → linesAnalysis (3220-3229): 모두 RIDGE 가선분으로 push
5-4. jerkinHeads.forEach (3232-3445) — 반절처
가드: 양옆 EAVES. 양옆 vector 같으면 silent return.
gableWidth = currentLine.attributes.width,lineLength = (roofLength - gableWidth) / 2- roofLine 따라 4개 점 (
jerkinPoints): 시작 → +lineLength → +gableWidth → +lineLength=끝 gableWidth >= roofLength: drawRoofLine 즉시 (roofPoints 그대로)- 정상: drawHipLine(gable1, prevDegree) + drawRoofLine(gable2) + drawHipLine(gable3, nextDegree) 즉시
- 추가로 prevRoofLine/nextRoofLine 과의 사잇각 hipVector × gableWidth → prevHipEdge/nextHipEdge 교점 hipIntersection
- hipIntersection 있으면 drawHipLine prev/next 2개 즉시 + roofVector 반대 방향으로 10000 가선 → roof 교점 → drivePoint 클램프 → offset 보정 → RIDGE 가선분 push
5-5. gables.forEach (3447-4239) — 케라바/박공 (가장 큰 블록)
가드: 양옆 EAVES. 그리고 vector 다른 방향 + checkWallPolygon.inPolygon 통과해야 본 처리.
isOverlap 분기 (3502-3562)
isOverlapBefore/After (beforePrev 또는 afterNext 가 currentLine 좌표 범위 동일) + otherGable 없음 → isOverlap=true:
- mid-to-mid ridge: cMid + ridgeVectorcOffset → oMid - ridgeVectoroOffset, oppLine.type 별 추가 offset
- 중복 체크 후 RIDGE 가선분 push
일반 분기 (3563-4182)
stdLine 결정: prevLine/nextLine 의 거리 (prevDistance/nextDistance) 비교, 둘 다 GABLE 이면 긴 쪽, 아니면 짧은 쪽. stdFindOppVector 도 같이 결정.
stdPoints 좌표 보정:
- stdPrevVector === stdNextVector (U자 코너): isHorizontal/isVertical × stdVector × prev/next y/x 비교로 8가지 분기, GABLE 일 때만 offset 적용
- 다른 방향: 양 끝점에 prev/nextLine offset 적용
oppositeLine 검색: stdLine 의 반대편 (stdFindOppVector 방향) 평행 라인들 (overlap 케이스 포함)
oppositeLine.forEach(opposite) (3787-4083):
- ridgePoint 산출:
- oppPrev/oppNextVector 같으면 (U자): 평행 offset 두번 적용
- 다르면 (Y/L자): inPolygon 분기로 offset 부호 결정
- stdAnalyze 축에 맞춰 ridgePoint 의 한 축을 mid 좌표로 통일
- oppLine 양옆 EAVES/GABLE 혼합 케이스 분기 (3835-3976):
- oppPrev xor oppNext 가 EAVES: stdLine 길이 >= oppLine 길이일 때 hipEdge 와 ridgeEdge 교점 → ridgePoint 시작점 보정
- 짧은 stdLine + EAVES 짧은 stdPrev/Next: drivePoint 보정
- stdPrev/Next 가 EAVES: driveEdge 와 ridgeEdge 교점 → stdPoints 의 해당 끝점 좌표 갱신
- overlap 영역(stdMin/Max × ridgeMin/Max) 으로 ridgePoint 클램프, vector 정렬 보정
ridgePoints.push({point, left:stdLine, right:oppLine})
ridgePoints.forEach(r) (4085-4180):
- inPolygon1/2 체크, 밖이면 vector 내 가장 가까운 roof.line 으로 보정
- roof boundary 위에 있는 점이 1개만이면 그 점이 startPoint
- RIDGE 가선분 push (중복 체크)
4185-4231 — 양옆 vector 같음 (반절마루 불가) 케이스
prevLineVector === nextLineVector (U자): roofLine 직각 방향으로 가선 → 반대편 roof.line 교점 (가장 먼 것) → 시작점은 roofLine 양 끝 중 교점에서 먼 쪽 → GABLE_LINE 가선분 push (degree = prevLine.pitch, gableId=baseLines.findIndex(currentLine), connectCnt=0)
6. MAX_ITERATIONS 루프 (4242-4608) — 가선분 교점 처리
linesAnalysis 가 비거나 1000회 도달까지 반복:
6-1. intersections 수집 (4248-4297)
각 currLine 마다:
- length<EPSILON 무시
- GABLE_LINE + connectCnt>1 무시
- 다른 nextLine 과
lineIntersection(seg-seg)시도 - 가장 짧은 distance1 (+ 같은 GABLE_LINE pair gableId 같으면 skip) 의 partner 선택
- distance1==minDistance 일 때
isSameLine(left/right 공유) 우선
6-2. 단일 교점 보정 (4302-4305)
intersectPoints unique 가 1개고 intersections 가 ≥2 → 첫 두개를 서로 partner 로 강제.
6-3. for-of 처리 (4310-4597)
intersection 페어마다:
- mutual partner 아니거나 left/right 공통 안 되면 skip
- 각각 drawHip/Ridge 그림 (innerLines.push, alreadyPoints 중복 차단):
cLine.type === HIP→ drawHipLine (cLine.degree)RIDGE→ drawRidgeLineNEW→ diagonal 이면 hip, 아니면 ridgeGABLE_LINE→ degree>0 hip, 아니면 ridge
동일 교점에 다른 라인이 더 있으면 (otherIs)
- 관련 baseline 공유하는 라인은 추가 그리고 processed
- (GABLE_LINE 페어인 경우는 otherIs 처리 skip)
GABLE_LINE 분기 (4425-4467)
gableLine.connectCnt++- uniqueLines 2개면:
- connectCnt<2: GABLE_LINE 가선분 새로 push (intersect → end)
- connectCnt>=2: HIP 더미 (start==end) push — "가선분 발생만 시키는" trigger 용
일반 분기 (4468-4588) — NEW 가선분 생성
- uniqueLines 2개 → bisector 계산 (
isParallel면getBisectLines(평행 시 길이 보정 100), 아니면getBisectBaseLines) - bisector 방향으로 roof.lines 와 교점 → 가까운 것
- 이미 같은 교점 가진 미처리 라인 있으면 그것을 newAnalysis 로 교체 (재활용)
- 아니면 NEW 가선분 push (
type=NEW,degree= isDiagonal ? cLine.degree : 0) - drivePoint 로 ridge 길이 클램프 (NEW + horizontal/vertical 일 때만)
newAnalysis.length > 0 이면 break (한 페어 처리 후 즉시 재계산)
6-4. 루프 종료 후 (4599-4608)
linesAnalysis = newAnalysis ⊕ (linesAnalysis - processed). newAnalysis 비면 while 종료.
7. 가선분 잔여 (4610-4696)
남은 linesAnalysis 의 평행 페어 처리:
- isOpposite (반대 방향): start-start 사이 line, type 별 drawHip/Ridge
- 동일 방향: 4개 점 중 unique 2개로 line, isDiagonal 여부에 따라 drawHip/Ridge
- 처리된 라인은
proceedAnalysis로 마킹 후 linesAnalysis 에서 제외
8. 벽취합 잔여 (4698-4732)
linesAnalysis 에 남은 line 중:
- start/end 가 모두 roof.lines 위에 있으면:
- line.type=RIDGE + baseLines.length===4 + (start 또는 end 가 onLine): drawRidgeLine
- 그 외 (양쪽 다 onLine): degree===0 → drawRoofLine, 아니면 drawHipLine
- innerLines 에 같은 끝점이 3개 미만, 다른 끝점은 없을 때만 그림 (중복 차단)
9. 하단지붕 분류 (4734-4778)
baseLines 순회하며 3종류로 분류:
- downRoofGable type 'A' (4750-4757): prevVector === nextVector (U자) + currentLine=EAVES + 양옆 중 하나라도 GABLE
- downRoofGable type 'B' (4759-4766): prevVector ≠ nextVector + checkWallPolygon.inPolygon(mid+nextVector) + 양옆 둘 다 GABLE
- downRoofEaves (4768-4777): prevVector === nextVector + currentLine=EAVES + 양옆 중 하나라도 EAVES + originPoint ≠ 현재 좌표 (=형이동된 라인)
10. downRoofLines (4780-5158)
10-1. type='A' (4782-4971) — U자 케라바 사이 처마
gableLine= prev 또는 next (currVector × prev/nextVector 8가지 분기)stdPoint= currentLine 양 끝 (gableLine 쪽 그대로 + 반대쪽 -currVector × otherLine.offset)oppLines= baseLines 중 stdLine 반대쪽 평행 라인들innerRidge= innerLines 중 RIDGE 라인 + currVector 와 같은 축 + stdPoint 범위 안roofLinePoint= stdPoint 에 currOffset 적용 (currVector.x===0 이면 ±y, 아니면 ∓x)- drawRoofLine(roofLinePoint) push
- roofLinePoint 에서 oppFindVector 방향 가선 → innerRidge 와 교점 가장 가까운 것 → drawHipLine + drawRoofLine (intersect → 인접한 ridge 끝점)
10-2. type='B' (4974-5157) — 안쪽으로 들어간 케라바 양쪽
ridgeFindVector= nextVector (mid 가 polygon 안일 때) 또는 prevVectoroppLines= innerLines RIDGE 중 ridgeFindVector 방향 + currentLine 범위 안roofPoint= currentLine 좌표에 -nextVector × currOffset 적용- drawRoofLine(roofPoint) push
- orthogonalStart/EndPoint = currentLine 양 끝에서 oppLines 의 가까운 끝점에 직각으로 투영
- orthogonalStartLine === orthogonalEndLine: drawRoofLine(start→end) 1개
- 다르면: drawRoofLine 2개 (각 ridge 의 먼 끝점까지 연장)
- 추가로 drawHipLine 2개 (roofPoint 양 끝 → orthogonalPoint 양 끝)
11. downRoofEaves.forEach (5160-5376) — 처마 형이동 흡수 라인
핵심 데이터:
flowDistance= currentLine 과 originPoint 의 axis 거리isFlowInside= inPolygon 으로 안/밖 결정upLine(상단 지붕) = isFlowInside 면 otherLine, 아니면 currentLinedownLine(하단 지붕) = 반대joinPoint= upLine 과 downLine 이 만나는 모서리
상단 지붕 처리:
upRoofPoint= upRoofLine 끝점에 upRoofVector × addUpOffset(=flowDistance) 더한 좌표 (inPolygon 통과하는 방향)upHipStartPoint= upRoofLine 의 다른 끝upHipVector= upLine + downLine 사잇각 (inPolygon 정규화)joinHipLine= innerLines 중 joinPoint + (joinPoint+upHipVector) 둘 다 위에 있는 라인 (=기존에 그려진 추녀마루)
하단 지붕 처리:
5. downRoofPoint = downRoofLine 끝점 + downRoofVector × addDownOffset(=upLine.offset), inPolygon 분기
6. intersectJoin = upRoofEdge × joinHipEdge — 상단 지붕선과 추녀마루의 교점 → joinHipLine.set 으로 좌표 갱신 (mutated!)
upSideLine 분기 (upLine 의 다른 인접 라인):
- upSideLine = EAVES (= 처마 끼고 추녀마루 분기 가능):
- innerLines 중 joinHipLine 외에 upRoofEdge 와 교점 + minDistance > EPSILON: drawHipLine(connectPoint) + drawRoofLine(connectPoint→intersectJoin)
- 없음: drawRoofLine(upHipStartPoint→intersectJoin)
- 아니면: drawRoofLine(upHipStartPoint→intersectJoin)
각 그릴 때 roof.adjustRoofLines.push({point, roofIdx: upRoofLine.idx}) — 회로/모듈 배치에서 보정 참조용.
하단 지붕 추녀마루 (intersectDownJoin = downHipEdge × joinHipEdge):
- 있으면 drawRoofLine(downRoofPoint) + drawHipLine(downHip1) + drawHipLine(downHip2 → joinEndPoint) — 지붕선/추녀마루1/추녀마루2 트리오
12. 최종 split + finalize (5379-5454)
innerLines.push(...downRoofLines) // 하단 지붕 모두 합류
// roof.lines 각 라인을 innerLines 끝점들로 분할
innerLinesPoints = unique(innerLines.{x1,y1,x2,y2})
roof.lines.forEach((currentLine, idx) => {
if (innerLine 이 currentLine 을 완전 포함 또는 그 반대) skip // hasOverlapLine 가드
splitPoint = innerLinesPoints 중 currentLine 위에 있고 끝점 아닌 것
if (splitPoint > 0):
// 첫 분할 = drawHipLine(prevDegree)
// 중간 = drawRoofLine
// 마지막 = splitPoint===1 이면 drawRoofLine, 아니면 drawHipLine(nextDegree)
else:
// 통째로 drawRoofLine
})
roof.innerLines = innerLines // ← 덮어쓰기 (drawGableRoof 의 push 와 다름!)
canvas 의 'check' 객체 제거 + renderAll
13. 수정 작업 시 어느 stage 인지 식별 가이드
| 증상 | 의심 stage |
|---|---|
| 한쪽흐름 추녀가 안 그려짐 | 5-1 sheds (양옆 GABLE 가드) |
| 팔작 모서리 잘못 그려짐 | 5-2 hipAndGables, prev/nextHipVector inPolygon 분기 |
| 처마 hip 길이가 비대칭 | 5-3 ridgeEaves proceedEaves alreadyPrev/Next 길이 비교 |
| 마루가 코너 직전에서 끊김/넘침 | getRidgeDrivePoint 또는 isOverlapBefore/After offset 분기 |
| 반절처 추녀마루 안 생김 | 5-4 jerkinHeads hipIntersection 또는 width >= roofLength 가드 |
| 케라바 ridge 가 비뚤어짐 | 5-5 gables stdLine 결정/oppositeLine 보정 |
connectCnt/gableId 충돌 |
6-3 GABLE_LINE 분기 |
| 가짜 ridge 가 남음 | 7 가선분 잔여 또는 8 벽취합 잔여 |
| 형이동 처마 위로 추녀마루 누락 | 11 downRoofEaves joinHipLine 분기 |
| roof boundary 가 hip 으로 잘못 분류 | 12 최종 split splitPoint 0개 케이스 |
| 라인이 두 번 그려짐 | alreadyPoints(innerLines, point) 가드 누락 또는 splitPoint 의 hasOverlapLine 미작동 |
14. 함정 모음
linesAnalysis와innerLines혼동 금지. 가선분 메타와 그려진 QLine 은 동기화되지 않음. 어떤 코드는 즉시 그리고 어떤 코드는 가선분만 push 한다.drawRoofLine의 name 이 SUBLINE.HIP — 후처리(평균화) 에서 hip 로 분류됨. 이름 보고 type 구분 금지.- TYPES.NEW = 가선분 분기 결과 — bisector 로 만들어진 가상 ridge/hip. degree 는 isDiagonal 일 때만 cLine.degree, 아니면 0 (=plane).
- GABLE_LINE.connectCnt — 0/1/2 단계로 increment. 2 이상이면 더미 가선분만 발생, 본인은 더 이상 그리지 않음.
- roof.adjustRoofLines — downRoofEaves 단계에서만 push. 회로/모듈 배치 단계에서 참조. roof.innerLines 와 별도.
- MAX_ITERATIONS=1000 — 무한루프 방지지만 도달하면 silent 미완성 (경고 없음). 케이스 추가 시 수렴 보장 확인 필수.
joinHipLine.set(...)(5278) — 기존 라인을 mutate 한다. 즉 5단계 이전에 만들어진 hip 의 좌표가 바뀐다.checkWallPolygon(1761) —baseLines.map(line => ({x:line.x1, y:line.y1}))로 만듦. zero-merge 후의 baseLines 시작점만 사용. 전체 라인 좌표가 아님.- 여러 곳에서
intersectionsByRoof[0]/intersectPoints1[0]직접 접근 — 비어있을 가능성 가드 없음. 케이스 추가 시 NPE 위험. - proceedRidges/proceedEaves 는 ridgeEaves 루프 전용 누적기 — 다른 단계(hipedEaves, jerkinHeads, gables)에서 보지 않음. 즉 케이스 간 충돌 가능.