+ | Customer |
+ |
{getMessage('qna.reg.header.regUserNm')}* |
setQnaData({...qnaData, regUserNm: e.target.value })}
onBlur={(e) => setQnaData({ ...qnaData, regUserNm: e.target.value })} /> |
{getMessage('qna.reg.header.regUserTelNo')} |
- line.attributes?.type === LINE_TYPE.WALLLINE.EAVES)
-
const referenceAngle = calculateAngle(shedLines[0].startPoint, shedLines[0].endPoint)
-
- return eavesLines.filter((line) => {
- const eavesAngle = calculateAngle(line.startPoint, line.endPoint)
- return Math.abs(referenceAngle - eavesAngle) === 180
+ const otherSideLines = lines.filter((line) => {
+ const lineAngle = calculateAngle(line.startPoint, line.endPoint)
+ return Math.abs(referenceAngle - lineAngle) === 180
})
+ const containNotEaves = otherSideLines.filter((line) => line.attributes?.type !== LINE_TYPE.WALLLINE.EAVES)
+
+ if (containNotEaves.length === 0) {
+ return otherSideLines
+ } else {
+ return []
+ }
}
const parallelEaves = getParallelEavesLines(shedLines, lines)
@@ -337,7 +341,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
// 용마루 -- straight-skeleton
console.log('용마루 지붕')
///drawRidgeRoof(this.id, this.canvas, textMode)
- drawSkeletonRidgeRoof(this.id, this.canvas, textMode);
+ drawSkeletonRidgeRoof(this.id, this.canvas, textMode)
} else if (isGableRoof(types)) {
// A형, B형 박공 지붕
console.log('패턴 지붕')
@@ -347,7 +351,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
drawShedRoof(this.id, this.canvas, textMode)
} else {
console.log('변별로 설정')
- drawRidgeRoof(this.id, this.canvas, textMode)
+ drawRoofByAttribute(this.id, this.canvas, textMode)
}
},
diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js
index a4f885a4..3a38edd8 100644
--- a/src/util/qpolygon-utils.js
+++ b/src/util/qpolygon-utils.js
@@ -8,6 +8,7 @@ import { LINE_TYPE, POLYGON_TYPE } from '@/common/common'
import Big from 'big.js'
const TWO_PI = Math.PI * 2
+const EPSILON = 1e-10 //좌표계산 시 최소 차이값
export const defineQPolygon = () => {
fabric.QPolygon.fromObject = function (object, callback) {
@@ -970,7 +971,7 @@ export const drawGableRoof = (roofId, canvas, textMode) => {
}
if (analyze.isVertical) {
const overlapY1 = Math.min(overlapAnalyze.startPoint.y, overlapAnalyze.endPoint.y)
- const overlapY2 = (overlapAnalyze.startPoint.y, overlapAnalyze.endPoint.y)
+ const overlapY2 = Math.max(overlapAnalyze.startPoint.y, overlapAnalyze.endPoint.y)
// 각 라인 사이의 길이를 구해서 각도에 대한 중간 지점으로 마루를 생성.
const currentMidX = (currentLine.x1 + currentLine.x2) / 2
@@ -1491,6 +1492,4368 @@ export const drawShedRoof = (roofId, canvas, textMode) => {
canvas.renderAll()
}
+const getInwardNormal = (v1, v2, isCCW) => {
+ const dx = v2.x - v1.x
+ const dy = v2.y - v1.y
+ const length = Math.sqrt(dx * dx + dy * dy)
+
+ if (length === 0) return { x: 0, y: 0 }
+
+ if (isCCW) {
+ return { x: -dy / length, y: dx / length }
+ } else {
+ return { x: dy / length, y: -dx / length }
+ }
+}
+
+const isCounterClockwise = (vertices) => {
+ let sum = 0
+ for (let i = 0; i < vertices.length; i++) {
+ const v1 = vertices[i]
+ const v2 = vertices[(i + 1) % vertices.length]
+ sum += (v2.x - v1.x) * (v2.y + v1.y)
+ }
+ return sum < 0
+}
+
+const isPointInPolygon = (point, polygon) => {
+ let inside = false
+
+ for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
+ const xi = polygon[i].x,
+ yi = polygon[i].y
+ const xj = polygon[j].x,
+ yj = polygon[j].y
+
+ const intersect = yi > point.y !== yj > point.y && point.x < ((xj - xi) * (point.y - yi)) / (yj - yi) + xi
+
+ if (intersect) inside = !inside
+ }
+
+ return inside
+}
+
+const calculateAngleBisector = (prevVertex, currentVertex, nextVertex, polygonVertices) => {
+ const isCCW = isCounterClockwise(polygonVertices)
+
+ // 이전 변의 내향 법선
+ const norm1 = getInwardNormal(prevVertex, currentVertex, isCCW)
+
+ // 다음 변의 내향 법선
+ const norm2 = getInwardNormal(currentVertex, nextVertex, isCCW)
+
+ // 이등분선 계산
+ let bisectorX = norm1.x + norm2.x
+ let bisectorY = norm1.y + norm2.y
+
+ const length = Math.sqrt(bisectorX * bisectorX + bisectorY * bisectorY)
+
+ if (length < 1e-10) {
+ // 180도인 경우
+ bisectorX = norm1.x
+ bisectorY = norm1.y
+ } else {
+ bisectorX /= length
+ bisectorY /= length
+ }
+
+ const testPoint = {
+ x: currentVertex.x + bisectorX * 0.1,
+ y: currentVertex.y + bisectorY * 0.1,
+ }
+
+ if (isPointInPolygon(testPoint, polygonVertices)) {
+ // 방향이 외부를 향하면 반전
+ bisectorX = -bisectorX
+ bisectorY = -bisectorY
+ }
+
+ return { x: bisectorX, y: bisectorY }
+}
+
+const lineSegmentIntersection = (p1, p2, p3, p4) => {
+ const x1 = p1.x,
+ y1 = p1.y
+ const x2 = p2.x,
+ y2 = p2.y
+ const x3 = p3.x,
+ y3 = p3.y
+ const x4 = p4.x,
+ y4 = p4.y
+
+ const denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)
+
+ if (Math.abs(denom) < EPSILON) {
+ return null // 평행
+ }
+
+ const t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / denom
+ const u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / denom
+
+ if (t >= 0 && t <= 1 && u >= 0 && u <= 1) {
+ return {
+ x: Number((x1 + t * (x2 - x1)).toFixed(1)),
+ y: Number((y1 + t * (y2 - y1)).toFixed(1)),
+ }
+ }
+
+ return null
+}
+
+/**
+ * 두 점 사이의 거리 계산
+ */
+const distanceBetweenPoints = (p1, p2) => {
+ const dx = p2.x - p1.x
+ const dy = p2.y - p1.y
+ return Math.sqrt(dx * dx + dy * dy)
+}
+
+const findIntersectionPoint = (startPoint, direction, polygonVertices, divisionLines) => {
+ const rayEnd = {
+ x: startPoint.x + direction.x * 10000,
+ y: startPoint.y + direction.y * 10000,
+ }
+
+ let closestIntersection = null
+ let minDistance = Infinity
+
+ // 다각형 변과의 교점
+ for (let i = 0; i < polygonVertices.length; i++) {
+ const v1 = polygonVertices[i]
+ const v2 = polygonVertices[(i + 1) % polygonVertices.length]
+
+ const intersection = lineSegmentIntersection(startPoint, rayEnd, v1, v2)
+
+ if (intersection) {
+ const dist = distanceBetweenPoints(startPoint, intersection)
+ if (dist > 0.1 && dist < minDistance) {
+ minDistance = dist
+ closestIntersection = intersection
+ }
+ }
+ }
+
+ // 분할선분과의 교점
+ for (const divLine of divisionLines) {
+ const intersection = lineSegmentIntersection(startPoint, rayEnd, { x: divLine.x1, y: divLine.y1 }, { x: divLine.x2, y: divLine.y2 })
+
+ if (intersection) {
+ const dist = distanceBetweenPoints(startPoint, intersection)
+ if (dist > 0.1 && dist < minDistance) {
+ minDistance = dist
+ closestIntersection = intersection
+ }
+ }
+ }
+
+ return closestIntersection
+}
+
+/**
+ * 변별로 설정된 지붕을 그린다.
+ * @param roofId
+ * @param canvas
+ * @param textMode
+ */
+export const drawRoofByAttribute = (roofId, canvas, textMode) => {
+ const TYPES = { HIP: 'hip', RIDGE: 'ridge', GABLE_LINE: 'gableLine', NEW: 'new' }
+ let roof = canvas?.getObjects().find((object) => object.id === roofId)
+ const wall = canvas.getObjects().find((object) => object.name === POLYGON_TYPE.WALL && object.attributes.roofId === roofId)
+
+ const zeroLines = []
+ wall.baseLines.forEach((line, index) => (line.attributes.planeSize < EPSILON ? zeroLines.push(index) : null))
+ let baseLines = []
+ if (zeroLines.length > 0) {
+ //형이동으로 인해 뭉쳐지는 라인을 찾는다.
+ const mergedLines = []
+ zeroLines.forEach((zeroIndex) => {
+ const prevIndex = (zeroIndex - 1 + wall.baseLines.length) % wall.baseLines.length
+ const nextIndex = (zeroIndex + 1) % wall.baseLines.length
+ mergedLines.push({ prevIndex, nextIndex })
+ })
+
+ const proceedPair = []
+ wall.baseLines.forEach((line, index) => {
+ //뭉쳐지는 라인이면 하나로 합치고 baseLines에 추가. 아니면 baseLines에 바로 추가.
+ if (!zeroLines.includes(index) && !mergedLines.find((mergedLine) => mergedLine.prevIndex === index || mergedLine.nextIndex === index)) {
+ baseLines.push(line)
+ } else {
+ if (zeroLines.includes(index)) {
+ mergedLines.forEach((indexPair) => {
+ //이미 처리된 라인이편 패스
+ if (proceedPair.includes(indexPair.prevIndex) || proceedPair.includes(indexPair.nextIndex)) return
+ let prevLine = wall.baseLines[indexPair.prevIndex]
+ const lineVector = { x: Math.sign(prevLine.x2 - prevLine.x1), y: Math.sign(prevLine.y2 - prevLine.y1) }
+
+ //중복되는 지붕선을 병합한다. 라인 vector가 같아야한다.
+ const indexList = [indexPair.prevIndex, indexPair.nextIndex]
+ mergedLines
+ .filter((pair) => pair !== indexPair)
+ .forEach((pair) => {
+ const pLine = wall.baseLines[pair.prevIndex]
+ const nLine = wall.baseLines[pair.nextIndex]
+ const pVector = { x: Math.sign(pLine.x2 - pLine.x1), y: Math.sign(pLine.y2 - pLine.y1) }
+ const nVector = { x: Math.sign(nLine.x2 - nLine.x1), y: Math.sign(nLine.y2 - nLine.y1) }
+ if (
+ pVector.x === lineVector.x &&
+ pVector.y === lineVector.y &&
+ nVector.x === lineVector.x &&
+ nVector.y === lineVector.y &&
+ (indexList.includes(pair.prevIndex) || indexList.includes(pair.nextIndex))
+ ) {
+ indexList.push(pair.prevIndex, pair.nextIndex)
+ }
+ })
+
+ const startLine = wall.baseLines[Math.min(...indexList)]
+ const endLine = wall.baseLines[Math.max(...indexList)]
+ const points = [startLine.x1, startLine.y1, endLine.x2, endLine.y2]
+ const size = calcLinePlaneSize({ x1: points[0], y1: points[1], x2: points[2], y2: points[3] })
+
+ //라인 좌표 조정.
+ startLine.set({
+ x1: points[0],
+ y1: points[1],
+ x2: points[2],
+ y2: points[3],
+ startPoint: { x: points[0], y: points[1] },
+ endPoint: { x: points[2], y: points[3] },
+ })
+ startLine.setCoords()
+ startLine.attributes.planeSize = size
+ startLine.attributes.actualSize = size
+ startLine.fire('setLength')
+
+ //처리된 index 추가.
+ proceedPair.push(...indexList)
+ //조정된라인 baseLine에 추가.
+ baseLines.push(startLine)
+ })
+ }
+ }
+ })
+ } else {
+ baseLines = wall.baseLines
+ }
+ const baseLinePoints = baseLines.map((line) => ({ x: line.x1, y: line.y1 }))
+ const checkWallPolygon = new QPolygon(baseLinePoints, {})
+
+ let innerLines = []
+
+ /** 벽취합이 있는 경우 소매가 있다면 지붕 형상을 변경해야 한다. */
+ baseLines
+ .filter((line) => line.attributes.type === LINE_TYPE.WALLLINE.WALL && line.attributes.offset > 0)
+ .forEach((currentLine) => {
+ const prevLine = baseLines.find((line) => line.x2 === currentLine.x1 && line.y2 === currentLine.y1)
+ const nextLine = baseLines.find((line) => line.x1 === currentLine.x2 && line.y1 === currentLine.y2)
+
+ const currentMidX = Big(currentLine.x1).plus(Big(currentLine.x2)).div(2).toNumber()
+ const currentMidY = Big(currentLine.y1).plus(Big(currentLine.y2)).div(2).toNumber()
+ const currentVectorX = Math.sign(currentLine.x2 - currentLine.x1)
+ const currentVectorY = Math.sign(currentLine.y2 - currentLine.y1)
+
+ /** 현재 라인의 지붕 라인을 찾는다. */
+ const intersectionRoofs = []
+ let currentRoof
+ if (currentVectorX === 0) {
+ const checkEdge = {
+ vertex1: { x: prevLine.x1, y: currentMidY },
+ vertex2: { x: currentMidX, y: currentMidY },
+ }
+ roof.lines
+ .filter((line) => Math.sign(line.x2 - line.x1) === currentVectorX && Math.sign(line.y2 - line.y1) === currentVectorY)
+ .forEach((line) => {
+ const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
+ const intersection = edgesIntersection(checkEdge, lineEdge)
+ if (intersection) {
+ if (isPointOnLine(line, intersection)) {
+ intersectionRoofs.push({
+ line,
+ intersection,
+ size: Big(intersection.x).minus(currentMidX).abs().pow(2).plus(Big(intersection.y).minus(currentMidY).abs().pow(2)).sqrt(),
+ })
+ }
+ }
+ })
+ } else {
+ const checkEdge = {
+ vertex1: { x: currentMidX, y: prevLine.y1 },
+ vertex2: { x: currentMidX, y: currentMidY },
+ }
+ roof.lines
+ .filter((line) => Math.sign(line.x2 - line.x1) === currentVectorX && Math.sign(line.y2 - line.y1) === currentVectorY)
+ .forEach((line) => {
+ const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
+ const intersection = edgesIntersection(checkEdge, lineEdge)
+ if (intersection) {
+ if (isPointOnLine(line, intersection)) {
+ intersectionRoofs.push({
+ line,
+ intersection,
+ size: Big(intersection.x).minus(currentMidX).abs().pow(2).plus(Big(intersection.y).minus(currentMidY).abs().pow(2)).sqrt(),
+ })
+ }
+ }
+ })
+ }
+ if (intersectionRoofs.length > 0) {
+ currentRoof = intersectionRoofs.sort((a, b) => a.size - b.size)[0].line
+ }
+ if (currentRoof) {
+ const prevRoof = roof.lines.find((line) => line.x2 === currentRoof.x1 && line.y2 === currentRoof.y1)
+ const nextRoof = roof.lines.find((line) => line.x1 === currentRoof.x2 && line.y1 === currentRoof.y2)
+
+ const prevOffset = prevLine.attributes.offset
+ const nextOffset = nextLine.attributes.offset
+
+ currentRoof.set({ x1: currentLine.x1, y1: currentLine.y1, x2: currentLine.x2, y2: currentLine.y2 })
+
+ if (prevLine.attributes.type !== LINE_TYPE.WALLLINE.WALL && prevOffset > 0) {
+ const addPoint1 = []
+ const addPoint2 = []
+ if (Math.sign(prevLine.y2 - prevLine.y1) === 0) {
+ addPoint1.push(prevRoof.x2, prevRoof.y2, prevRoof.x2, currentRoof.y1)
+ addPoint2.push(addPoint1[2], addPoint1[3], currentRoof.x1, currentRoof.y1)
+ } else {
+ addPoint1.push(prevRoof.x2, prevRoof.y2, currentRoof.x1, prevRoof.y2)
+ addPoint2.push(addPoint1[2], addPoint1[3], currentRoof.x1, currentRoof.y1)
+ }
+ const addRoofLine1 = new QLine(addPoint1, {
+ name: 'addRoofLine',
+ parentId: roof.id,
+ fontSize: roof.fontSize,
+ stroke: '#1083E3',
+ strokeWidth: 2,
+ textMode: textMode,
+ attributes: {
+ roofId: roofId,
+ type: LINE_TYPE.WALLLINE.ETC,
+ planeSize: calcLinePlaneSize({
+ x1: addPoint1[0],
+ y1: addPoint1[1],
+ x2: addPoint1[2],
+ y2: addPoint1[3],
+ }),
+ actualSize: calcLinePlaneSize({
+ x1: addPoint1[0],
+ y1: addPoint1[1],
+ x2: addPoint1[2],
+ y2: addPoint1[3],
+ }),
+ },
+ })
+
+ const addRoofLine2 = new QLine(addPoint2, {
+ name: 'addRoofLine',
+ parentId: roof.id,
+ fontSize: roof.fontSize,
+ stroke: '#1083E3',
+ strokeWidth: 2,
+ textMode: textMode,
+ attributes: {
+ roofId: roofId,
+ type: LINE_TYPE.WALLLINE.ETC,
+ planeSize: calcLinePlaneSize({
+ x1: addPoint2[0],
+ y1: addPoint2[1],
+ x2: addPoint2[2],
+ y2: addPoint2[3],
+ }),
+ actualSize: calcLinePlaneSize({
+ x1: addPoint2[0],
+ y1: addPoint2[1],
+ x2: addPoint2[2],
+ y2: addPoint2[3],
+ }),
+ },
+ })
+ canvas.add(addRoofLine1, addRoofLine2)
+ canvas.renderAll()
+
+ const prevIndex = roof.lines.indexOf(prevRoof)
+ if (prevIndex === roof.lines.length - 1) {
+ roof.lines.unshift(addRoofLine1, addRoofLine2)
+ } else {
+ roof.lines.splice(prevIndex + 1, 0, addRoofLine1, addRoofLine2)
+ }
+ } else if (prevLine.attributes.type === LINE_TYPE.WALLLINE.WALL && prevOffset > 0) {
+ if (Math.sign(prevLine.y2 - prevLine.y1) === 0) {
+ prevRoof.set({ x2: currentLine.x1, y2: prevRoof.y1 })
+ } else {
+ prevRoof.set({ x2: prevRoof.x1, y2: currentLine.y1 })
+ }
+ currentRoof.set({ x1: prevRoof.x2, y1: prevRoof.y2 })
+ } else if (prevLine.attributes.type === LINE_TYPE.WALLLINE.WALL || prevOffset === 0) {
+ prevRoof.set({ x2: currentLine.x1, y2: currentLine.y1 })
+ }
+ if (nextLine.attributes.type !== LINE_TYPE.WALLLINE.WALL && nextOffset > 0) {
+ const addPoint1 = []
+ const addPoint2 = []
+ if (Math.sign(nextLine.y2 - nextLine.y1) === 0) {
+ addPoint1.push(currentRoof.x2, currentRoof.y2, nextRoof.x1, currentRoof.y2)
+ addPoint2.push(addPoint1[2], addPoint1[3], nextRoof.x1, nextRoof.y1)
+ } else {
+ addPoint1.push(currentRoof.x2, currentRoof.y2, currentRoof.x2, nextRoof.y1)
+ addPoint2.push(addPoint1[2], addPoint1[3], nextRoof.x1, nextRoof.y1)
+ }
+
+ const addRoofLine1 = new QLine(addPoint1, {
+ name: 'addRoofLine',
+ parentId: roof.id,
+ fontSize: roof.fontSize,
+ stroke: '#1083E3',
+ strokeWidth: 2,
+ textMode: textMode,
+ attributes: {
+ roofId: roofId,
+ type: LINE_TYPE.WALLLINE.ETC,
+ planeSize: calcLinePlaneSize({
+ x1: addPoint1[0],
+ y1: addPoint1[1],
+ x2: addPoint1[2],
+ y2: addPoint1[3],
+ }),
+ actualSize: calcLinePlaneSize({
+ x1: addPoint1[0],
+ y1: addPoint1[1],
+ x2: addPoint1[2],
+ y2: addPoint1[3],
+ }),
+ },
+ })
+
+ const addRoofLine2 = new QLine(addPoint2, {
+ name: 'addRoofLine',
+ parentId: roof.id,
+ fontSize: roof.fontSize,
+ stroke: '#1083E3',
+ strokeWidth: 2,
+ textMode: textMode,
+ attributes: {
+ roofId: roofId,
+ type: LINE_TYPE.WALLLINE.ETC,
+ planeSize: calcLinePlaneSize({
+ x1: addPoint2[0],
+ y1: addPoint2[1],
+ x2: addPoint2[2],
+ y2: addPoint2[3],
+ }),
+ actualSize: calcLinePlaneSize({
+ x1: addPoint2[0],
+ y1: addPoint2[1],
+ x2: addPoint2[2],
+ y2: addPoint2[3],
+ }),
+ },
+ })
+ canvas.add(addRoofLine1, addRoofLine2)
+ canvas.renderAll()
+
+ const nextIndex = roof.lines.indexOf(nextRoof)
+ if (nextIndex === 0) {
+ roof.lines.push(addRoofLine1, addRoofLine2)
+ } else {
+ roof.lines.splice(nextIndex, 0, addRoofLine1, addRoofLine2)
+ }
+ } else if (nextLine.attributes.type === LINE_TYPE.WALLLINE.WALL && nextOffset > 0) {
+ if (Math.sign(nextLine.y2 - nextLine.y1) === 0) {
+ nextRoof.set({ x1: currentLine.x2, y1: nextRoof.y1 })
+ } else {
+ nextRoof.set({ x1: nextRoof.x1, y1: currentLine.y2 })
+ }
+ currentRoof.set({ x2: nextRoof.x1, y2: nextRoof.y1 })
+ } else if (nextLine.attributes.type === LINE_TYPE.WALLLINE.WALL || prevOffset === 0) {
+ nextRoof.set({ x1: currentLine.x2, y1: currentLine.y2 })
+ }
+
+ roof = reDrawPolygon(roof, canvas)
+ }
+ })
+
+ /**
+ * 라인의 속성을 분석한다.
+ * @param line
+ * @returns {{startPoint: {x: number, y}, endPoint: {x: number, y}, length: number, angleDegree: number, normalizedAngle: number, isHorizontal: boolean, isVertical: boolean, isDiagonal: boolean, directionVector: {x: number, y: number}, roofLine: *, roofVector: {x: number, y: number}}}
+ */
+ const analyzeLine = (line) => {
+ const tolerance = 1
+ const dx = Big(line.x2).minus(Big(line.x1)).toNumber()
+ const dy = Big(line.y2).minus(Big(line.y1)).toNumber()
+ const length = Math.sqrt(dx * dx + dy * dy)
+ const angleDegree = (Math.atan2(dy, dx) * 180) / Math.PI
+ const normalizedAngle = Math.abs((Math.atan2(dy, dx) * 180) / Math.PI) % 180
+ const directionVector = { x: dx / length, y: dy / length }
+ let isHorizontal = false,
+ isVertical = false,
+ isDiagonal = false
+ if (normalizedAngle < tolerance || normalizedAngle >= 180 - tolerance) {
+ isHorizontal = true
+ } else if (Math.abs(normalizedAngle - 90) <= tolerance) {
+ isVertical = true
+ } else {
+ isDiagonal = true
+ }
+
+ const originPoint = line.attributes.originPoint
+ const midX = (originPoint.x1 + originPoint.x2) / 2
+ const midY = (originPoint.y1 + originPoint.y2) / 2
+ const offset = line.attributes.offset
+
+ const checkRoofLines = roof.lines.filter((roof) => {
+ const roofDx = Big(roof.x2).minus(Big(roof.x1)).toNumber()
+ const roofDy = Big(roof.y2).minus(Big(roof.y1)).toNumber()
+ const roofLength = Math.sqrt(roofDx * roofDx + roofDy * roofDy)
+ const roofVector = { x: roofDx / roofLength, y: roofDy / roofLength }
+ return directionVector.x === roofVector.x && directionVector.y === roofVector.y
+ })
+
+ let roofVector = { x: 0, y: 0 }
+ if (isHorizontal) {
+ const checkPoint = { x: midX, y: midY + offset }
+ if (wall.inPolygon(checkPoint)) {
+ roofVector = { x: 0, y: -1 }
+ } else {
+ roofVector = { x: 0, y: 1 }
+ }
+ }
+ if (isVertical) {
+ const checkPoint = { x: midX + offset, y: midY }
+ if (wall.inPolygon(checkPoint)) {
+ roofVector = { x: -1, y: 0 }
+ } else {
+ roofVector = { x: 1, y: 0 }
+ }
+ }
+
+ const findEdge = { vertex1: { x: midX, y: midY }, vertex2: { x: midX + roofVector.x * offset, y: midY + roofVector.y * offset } }
+ const edgeDx =
+ Big(findEdge.vertex2.x).minus(Big(findEdge.vertex1.x)).abs().toNumber() < 0.1
+ ? 0
+ : Big(findEdge.vertex2.x).minus(Big(findEdge.vertex1.x)).toNumber()
+ const edgeDy =
+ Big(findEdge.vertex2.y).minus(Big(findEdge.vertex1.y)).abs().toNumber() < 0.1
+ ? 0
+ : Big(findEdge.vertex2.y).minus(Big(findEdge.vertex1.y)).toNumber()
+ const edgeLength = Math.sqrt(edgeDx * edgeDx + edgeDy * edgeDy)
+ const edgeVector = { x: edgeDx / edgeLength, y: edgeDy / edgeLength }
+
+ const intersectRoofLines = []
+ checkRoofLines.forEach((roofLine) => {
+ const lineEdge = { vertex1: { x: roofLine.x1, y: roofLine.y1 }, vertex2: { x: roofLine.x2, y: roofLine.y2 } }
+ const intersect = edgesIntersection(lineEdge, findEdge)
+ if (intersect) {
+ const intersectDx =
+ Big(intersect.x).minus(Big(findEdge.vertex1.x)).abs().toNumber() < 0.1 ? 0 : Big(intersect.x).minus(Big(findEdge.vertex1.x)).toNumber()
+ const intersectDy =
+ Big(intersect.y).minus(Big(findEdge.vertex1.y)).abs().toNumber() < 0.1 ? 0 : Big(intersect.y).minus(Big(findEdge.vertex1.y)).toNumber()
+ const intersectLength = Math.sqrt(intersectDx * intersectDx + intersectDy * intersectDy)
+ const intersectVector = { x: intersectDx / intersectLength, y: intersectDy / intersectLength }
+ if (edgeVector.x === intersectVector.x && edgeVector.y === intersectVector.y) {
+ intersectRoofLines.push({ roofLine, intersect, length: intersectLength })
+ }
+ }
+ })
+
+ intersectRoofLines.sort((a, b) => a.length - b.length)
+ let currentRoof = intersectRoofLines.find((roof) => isPointOnLineNew(roof.roofLine, roof.intersect) && roof.length - offset < 0.1)
+ if (!currentRoof) {
+ currentRoof = intersectRoofLines[0]
+ }
+
+ let startPoint, endPoint
+ if (isHorizontal) {
+ startPoint = { x: Math.min(line.x1, line.x2, currentRoof.roofLine.x1, currentRoof.roofLine.x2), y: line.y1 }
+ endPoint = { x: Math.max(line.x1, line.x2, currentRoof.roofLine.x1, currentRoof.roofLine.x2), y: line.y2 }
+ }
+ if (isVertical) {
+ startPoint = { x: line.x1, y: Math.min(line.y1, line.y2, currentRoof.roofLine.y1, currentRoof.roofLine.y2) }
+ endPoint = { x: line.x2, y: Math.max(line.y1, line.y2, currentRoof.roofLine.y1, currentRoof.roofLine.y2) }
+ }
+ if (isDiagonal) {
+ startPoint = { x: line.x1, y: line.y1 }
+ endPoint = { x: line.x2, y: line.y2 }
+ }
+
+ return {
+ startPoint,
+ endPoint,
+ length,
+ angleDegree,
+ normalizedAngle,
+ isHorizontal,
+ isVertical,
+ isDiagonal,
+ directionVector: { x: dx / length, y: dy / length },
+ roofLine: currentRoof.roofLine,
+ roofVector,
+ }
+ }
+
+ /**
+ *
+ * @param testPoint
+ * @param prevLine
+ * @param nextLine
+ * @param baseLines
+ */
+ const getRidgeDrivePoint = (testPoint = [], prevLine, nextLine, baseLines) => {
+ if (prevLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES && nextLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES) return null
+ let prevPoint = null,
+ nextPoint = null
+
+ const baseLinePoints = baseLines.map((line) => ({ x: line.x1, y: line.y1 }))
+ const checkWallPolygon = new QPolygon(baseLinePoints, {})
+ const checkEdge = { vertex1: { x: testPoint[0], y: testPoint[1] }, vertex2: { x: testPoint[2], y: testPoint[3] } }
+
+ let prevHasGable = false,
+ nextHasGable = false
+ if (prevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) {
+ const index = baseLines.findIndex((line) => line === prevLine)
+ const beforePrevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length]
+ if (!beforePrevLine) {
+ prevPoint = null
+ } else if (beforePrevLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) {
+ const analyze = analyzeLine(beforePrevLine)
+ const roofEdge = { vertex1: { x: analyze.roofLine.x1, y: analyze.roofLine.y1 }, vertex2: { x: analyze.roofLine.x2, y: analyze.roofLine.y2 } }
+ const intersection = edgesIntersection(checkEdge, roofEdge)
+ if (intersection) {
+ prevHasGable = true
+ const distance = Math.sqrt((intersection.x - testPoint[0]) ** 2 + (intersection.y - testPoint[1]) ** 2)
+ prevPoint = { intersection, distance }
+ }
+ } else if (beforePrevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) {
+ let prevVector = getHalfAngleVector(beforePrevLine, prevLine)
+
+ const prevCheckPoint = {
+ x: prevLine.x1 + prevVector.x * 10,
+ y: prevLine.y1 + prevVector.y * 10,
+ }
+
+ let prevHipVector
+
+ if (checkWallPolygon.inPolygon(prevCheckPoint)) {
+ prevHipVector = { x: prevVector.x, y: prevVector.y }
+ } else {
+ prevHipVector = { x: -prevVector.x, y: -prevVector.y }
+ }
+
+ const prevHipEdge = {
+ vertex1: { x: prevLine.x1, y: prevLine.y1 },
+ vertex2: { x: prevLine.x1 + prevHipVector.x * 10, y: prevLine.y1 + prevHipVector.y * 10 },
+ }
+
+ const intersection = edgesIntersection(prevHipEdge, checkEdge)
+ if (intersection) {
+ const checkVector = { x: Math.sign(testPoint[0] - testPoint[2]), y: Math.sign(testPoint[1] - testPoint[3]) }
+ const intersectVector = { x: Math.sign(testPoint[0] - intersection.x), y: Math.sign(testPoint[1] - intersection.y) }
+ if (checkVector.x === intersectVector.x && checkVector.y === intersectVector.y) {
+ const distance = Math.sqrt((intersection.x - testPoint[0]) ** 2 + (intersection.y - testPoint[1]) ** 2)
+ prevPoint = { intersection, distance }
+ }
+
+ if (almostEqual(intersection.x, testPoint[0]) && almostEqual(intersection.y, testPoint[1])) {
+ prevPoint = { intersection, distance: 0 }
+ }
+ }
+ }
+ }
+
+ if (nextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) {
+ const index = baseLines.findIndex((line) => line === nextLine)
+ const afterNextLine = baseLines[(index + 1) % baseLines.length]
+ if (!afterNextLine) {
+ nextPoint = null
+ } else if (afterNextLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) {
+ const analyze = analyzeLine(afterNextLine)
+ const roofEdge = { vertex1: { x: analyze.roofLine.x1, y: analyze.roofLine.y1 }, vertex2: { x: analyze.roofLine.x2, y: analyze.roofLine.y2 } }
+ const intersection = edgesIntersection(checkEdge, roofEdge)
+ if (intersection) {
+ nextHasGable = true
+ const distance = Math.sqrt((intersection.x - testPoint[2]) ** 2 + (intersection.y - testPoint[3]) ** 2)
+ nextPoint = { intersection, distance }
+ }
+ } else if (afterNextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) {
+ let nextVector = getHalfAngleVector(nextLine, afterNextLine)
+ const nextCheckPoint = {
+ x: nextLine.x2 + nextVector.x * 10,
+ y: nextLine.y2 + nextVector.y * 10,
+ }
+ let nextHipVector
+ if (checkWallPolygon.inPolygon(nextCheckPoint)) {
+ nextHipVector = { x: nextVector.x, y: nextVector.y }
+ } else {
+ nextHipVector = { x: -nextVector.x, y: -nextVector.y }
+ }
+
+ const nextHipEdge = {
+ vertex1: { x: nextLine.x2, y: nextLine.y2 },
+ vertex2: { x: nextLine.x2 + nextHipVector.x * 10, y: nextLine.y2 + nextHipVector.y * 10 },
+ }
+ const intersection = edgesIntersection(nextHipEdge, checkEdge)
+ if (intersection) {
+ const checkVector = { x: Math.sign(testPoint[0] - testPoint[2]), y: Math.sign(testPoint[1] - testPoint[3]) }
+ const intersectVector = { x: Math.sign(testPoint[0] - intersection.x), y: Math.sign(testPoint[1] - intersection.y) }
+ if (checkVector.x === intersectVector.x && checkVector.y === intersectVector.y) {
+ const distance = Math.sqrt((intersection.x - testPoint[0]) ** 2 + (intersection.y - testPoint[1]) ** 2)
+ nextPoint = { intersection, distance }
+ }
+ if (almostEqual(intersection.x, testPoint[0]) && almostEqual(intersection.y, testPoint[1])) {
+ nextPoint = { intersection, distance: 0 }
+ }
+ }
+ }
+ }
+
+ if (prevHasGable || nextHasGable) {
+ const prevLength = Math.sqrt((prevLine.x1 - prevLine.x2) ** 2 + (prevLine.y1 - prevLine.y2) ** 2)
+ const nextLength = Math.sqrt((nextLine.x1 - nextLine.x2) ** 2 + (nextLine.y1 - nextLine.y2) ** 2)
+ if (prevPoint && prevHasGable && prevLength <= nextLength) {
+ return prevPoint.intersection
+ }
+ if (nextPoint && nextHasGable && prevLength > nextLength) {
+ return nextPoint.intersection
+ }
+ }
+
+ if (prevPoint && nextPoint) {
+ return prevPoint.distance < nextPoint.distance ? prevPoint.intersection : nextPoint.intersection
+ } else if (prevPoint) {
+ return prevPoint.intersection
+ } else if (nextPoint) {
+ return nextPoint.intersection
+ } else {
+ return null
+ }
+ }
+
+ /**
+ * 지붕의 모양을 판단하여 각 변에 맞는 라인을 처리 한다.
+ */
+ const sheds = []
+ const hipAndGables = []
+ const eaves = []
+ const jerkinHeads = []
+ const gables = []
+ baseLines.forEach((baseLine) => {
+ switch (baseLine.attributes.type) {
+ case LINE_TYPE.WALLLINE.SHED:
+ sheds.push(baseLine)
+ break
+ case LINE_TYPE.WALLLINE.HIPANDGABLE:
+ hipAndGables.push(baseLine)
+ break
+ case LINE_TYPE.WALLLINE.EAVES:
+ eaves.push(baseLine)
+ break
+ case LINE_TYPE.WALLLINE.JERKINHEAD:
+ jerkinHeads.push(baseLine)
+ break
+ case LINE_TYPE.WALLLINE.GABLE:
+ gables.push(baseLine)
+ break
+ default:
+ break
+ }
+ })
+
+ //지붕선 내부에 보조선을 그리기 위한 analysis
+ let linesAnalysis = []
+
+ //1. 한쪽흐름(단면경사) 판단, 단면경사는 양옆이 케라바(일반)여야 한다. 맞은편 지붕이 처마여야 한다.
+ sheds.forEach((currentLine) => {
+ let prevLine, nextLine
+ baseLines.forEach((baseLine, index) => {
+ if (baseLine === currentLine) {
+ nextLine = baseLines[(index + 1) % baseLines.length]
+ prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length]
+ }
+ })
+
+ const analyze = analyzeLine(currentLine)
+
+ //양옆이 케라바가 아닐때 제외
+ if (prevLine.attributes.type !== LINE_TYPE.WALLLINE.GABLE || nextLine.attributes.type !== LINE_TYPE.WALLLINE.GABLE) {
+ return
+ }
+
+ const eavesLines = []
+
+ // 맞은편 처마 지붕을 찾는다. 흐름 각도를 확인하기 위함.
+ baseLines
+ .filter((baseLine) => baseLine.attributes.type === LINE_TYPE.WALLLINE.EAVES)
+ .forEach((baseLine) => {
+ const lineAnalyze = analyzeLine(baseLine)
+ //방향이 맞지 않으면 패스
+ if (
+ analyze.isHorizontal !== lineAnalyze.isHorizontal ||
+ analyze.isVertical !== lineAnalyze.isVertical ||
+ analyze.isDiagonal ||
+ lineAnalyze.isDiagonal
+ ) {
+ return false
+ }
+
+ // 수평 일때
+ if (
+ analyze.isHorizontal &&
+ Math.min(baseLine.x1, baseLine.x2) <= Math.min(currentLine.x1, currentLine.x2) &&
+ Math.max(baseLine.x1, baseLine.x2) >= Math.max(currentLine.x1, currentLine.x2)
+ ) {
+ eavesLines.push(baseLine)
+ }
+
+ //수직 일때
+ if (
+ analyze.isVertical &&
+ Math.min(baseLine.y1, baseLine.y2) <= Math.min(currentLine.y1, currentLine.y2) &&
+ Math.max(baseLine.y1, baseLine.y2) >= Math.max(currentLine.y1, currentLine.y2)
+ ) {
+ eavesLines.push(baseLine)
+ }
+ })
+
+ let shedDegree = getDegreeByChon(4)
+
+ if (eavesLines.length > 0) {
+ shedDegree = getDegreeByChon(eavesLines[0].attributes.pitch)
+ }
+
+ //지붕좌표1에서 지붕 안쪽으로의 확인 방향
+ const checkRoofVector1 = {
+ vertex1: { x: analyze.roofLine.x1, y: analyze.roofLine.y1 },
+ vertex2: { x: analyze.roofLine.x1 + analyze.roofVector.x * -1, y: analyze.roofLine.y1 + analyze.roofVector.y * -1 },
+ }
+ //지붕좌표2에서 지붕 안쪽으로의 확인 방향
+ const checkRoofVector2 = {
+ vertex1: { x: analyze.roofLine.x2, y: analyze.roofLine.y2 },
+ vertex2: { x: analyze.roofLine.x2 + analyze.roofVector.x * -1, y: analyze.roofLine.y2 + analyze.roofVector.y * -1 },
+ }
+
+ //좌표1에서의 교차점
+ const intersectPoints1 = []
+ //좌료2에서의 교차점
+ const intersectPoints2 = []
+ roof.lines
+ .filter((roofLine) => roofLine !== analyze.roofLine) // 같은 지붕선 제외
+ .filter((roofLine) => {
+ const tolerance = 1
+ const dx = Big(roofLine.x2).minus(Big(roofLine.x1)).toNumber()
+ const dy = Big(roofLine.y2).minus(Big(roofLine.y1)).toNumber()
+ const length = Math.sqrt(dx * dx + dy * dy)
+ const angleDegree = (Math.atan2(dy, dx) * 180) / Math.PI
+ const normalizedAngle = Math.abs((Math.atan2(dy, dx) * 180) / Math.PI) % 180
+ let isHorizontal = false,
+ isVertical = false,
+ isDiagonal = false
+ if (normalizedAngle < tolerance || normalizedAngle >= 180 - tolerance) {
+ isHorizontal = true
+ } else if (Math.abs(normalizedAngle - 90) <= tolerance) {
+ isVertical = true
+ } else {
+ isDiagonal = true
+ }
+ let otherSide = false
+
+ // 수평 일때 반대쪽 라인이 현재 라인을 포함하는지 확인.
+ if (
+ analyze.isHorizontal &&
+ ((Math.min(roofLine.x1, roofLine.x2) <= Math.min(analyze.roofLine.x1, analyze.roofLine.x2) &&
+ Math.max(roofLine.x1, roofLine.x2) >= Math.max(analyze.roofLine.x1, analyze.roofLine.x2)) ||
+ (Math.min(analyze.roofLine.x1, analyze.roofLine.x2) <= Math.min(roofLine.x1, roofLine.x2) &&
+ Math.max(analyze.roofLine.x1, analyze.roofLine.x2) >= Math.max(roofLine.x1, roofLine.x2))) &&
+ angleDegree !== analyze.angleDegree
+ ) {
+ otherSide = true
+ }
+
+ //수직 일때 반대쪽 라인이 현재 라인을 포함하는지 확인.
+ if (
+ analyze.isVertical &&
+ ((Math.min(roofLine.y1, roofLine.y2) <= Math.min(analyze.roofLine.y1, analyze.roofLine.y2) &&
+ Math.max(roofLine.y1, roofLine.y2) >= Math.max(analyze.roofLine.y1, analyze.roofLine.y2)) ||
+ (Math.min(analyze.roofLine.y1, analyze.roofLine.y2) <= Math.min(roofLine.y1, roofLine.y2) &&
+ Math.max(analyze.roofLine.y1, analyze.roofLine.y2) >= Math.max(roofLine.y1, roofLine.y2))) &&
+ angleDegree !== analyze.angleDegree
+ ) {
+ otherSide = true
+ }
+
+ return analyze.isHorizontal === isHorizontal && analyze.isVertical === isVertical && analyze.isDiagonal === isDiagonal && otherSide
+ })
+ .forEach((roofLine) => {
+ const lineEdge = { vertex1: { x: roofLine.x1, y: roofLine.y1 }, vertex2: { x: roofLine.x2, y: roofLine.y2 } }
+ const intersect1 = edgesIntersection(lineEdge, checkRoofVector1)
+ const intersect2 = edgesIntersection(lineEdge, checkRoofVector2)
+ if (intersect1 && isPointOnLineNew(roofLine, intersect1)) {
+ const size = Math.sqrt(Math.pow(analyze.roofLine.x1 - intersect1.x, 2) + Math.pow(analyze.roofLine.y1 - intersect1.y, 2))
+ intersectPoints1.push({ intersect: intersect1, size })
+ }
+ if (intersect2 && isPointOnLineNew(roofLine, intersect2)) {
+ const size = Math.sqrt(Math.pow(analyze.roofLine.x2 - intersect2.x, 2) + Math.pow(analyze.roofLine.y2 - intersect2.y, 2))
+ intersectPoints2.push({ intersect: intersect2, size })
+ }
+ })
+ intersectPoints1.sort((a, b) => b.size - a.size)
+ intersectPoints2.sort((a, b) => b.size - a.size)
+
+ const points1 = [analyze.roofLine.x1, analyze.roofLine.y1, intersectPoints1[0].intersect.x, intersectPoints1[0].intersect.y]
+ const points2 = [analyze.roofLine.x2, analyze.roofLine.y2, intersectPoints2[0].intersect.x, intersectPoints2[0].intersect.y]
+
+ const prevIndex = baseLines.findIndex((baseLine) => baseLine === prevLine)
+ const beforePrevIndex = baseLines.findIndex((baseLine) => baseLine === baseLines[(prevIndex - 1 + baseLines.length) % baseLines.length])
+ const nextIndex = baseLines.findIndex((baseLine) => baseLine === nextLine)
+ const afterNextIndex = baseLines.findIndex((baseLine) => baseLine === baseLines[(nextIndex + 1) % baseLines.length])
+
+ linesAnalysis.push(
+ {
+ start: { x: points1[0], y: points1[1] },
+ end: { x: points1[2], y: points1[3] },
+ left: beforePrevIndex,
+ right: prevIndex,
+ type: TYPES.HIP,
+ degree: shedDegree,
+ },
+ {
+ start: { x: points2[0], y: points2[1] },
+ end: { x: points2[2], y: points2[3] },
+ left: nextIndex,
+ right: afterNextIndex,
+ type: TYPES.HIP,
+ degree: shedDegree,
+ },
+ )
+ })
+
+ //2. 팔작지붕(이리모야) 판단, 팔작지붕은 양옆이 처마여야만 한다.
+ hipAndGables.forEach((currentLine) => {
+ let prevLine, nextLine
+ baseLines.forEach((baseLine, index) => {
+ if (baseLine === currentLine) {
+ nextLine = baseLines[(index + 1) % baseLines.length]
+ prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length]
+ }
+ })
+
+ const analyze = analyzeLine(currentLine)
+
+ //양옆이 처마가 아닐때 패스
+ if (prevLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES || nextLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES) {
+ return
+ }
+
+ let beforePrevLine, afterNextLine
+ baseLines.forEach((baseLine, index) => {
+ if (baseLine === prevLine) {
+ beforePrevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length]
+ }
+ if (baseLine === nextLine) {
+ afterNextLine = baseLines[(index + 1) % baseLines.length]
+ }
+ })
+
+ const currentVector = { x: Math.sign(clamp01(currentLine.x1 - currentLine.x2)), y: Math.sign(clamp01(currentLine.y1 - currentLine.y2)) }
+ const prevLineVector = { x: Math.sign(prevLine.x1 - prevLine.x2), y: Math.sign(prevLine.y1 - prevLine.y2) }
+ const nextLineVector = { x: Math.sign(nextLine.x1 - nextLine.x2), y: Math.sign(nextLine.y1 - nextLine.y2) }
+ //반절마루 생성불가이므로 지붕선만 추가하고 끝냄
+ if (prevLineVector.x === nextLineVector.x && prevLineVector.y === nextLineVector.y) {
+ return
+ }
+
+ const prevDegree = getDegreeByChon(prevLine.attributes.pitch)
+ const nextDegree = getDegreeByChon(nextLine.attributes.pitch)
+
+ const currentRoofLine = analyze.roofLine
+ let prevRoofLine, nextRoofLine
+ roof.lines.forEach((roofLine, index) => {
+ if (roofLine === currentRoofLine) {
+ nextRoofLine = roof.lines[(index + 1) % roof.lines.length]
+ prevRoofLine = roof.lines[(index - 1 + roof.lines.length) % roof.lines.length]
+ }
+ })
+ /** 팔작지붕 두께*/
+ const roofDx = currentRoofLine.x2 - currentRoofLine.x1
+ const roofDy = currentRoofLine.y2 - currentRoofLine.y1
+ const roofLength = Math.sqrt(roofDx * roofDx + roofDy * roofDy)
+ const lineWidth = currentLine.attributes.width <= roofLength / 2 ? currentLine.attributes.width : roofLength / 2
+
+ /** 이전, 다음라인의 사잇각의 vector를 구한다. */
+ let prevVector = getHalfAngleVector(prevRoofLine, currentRoofLine)
+ let nextVector = getHalfAngleVector(currentRoofLine, nextRoofLine)
+
+ /** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/
+ const prevCheckPoint = {
+ x: currentRoofLine.x1 + prevVector.x * lineWidth,
+ y: currentRoofLine.y1 + prevVector.y * lineWidth,
+ }
+ /** 다음 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/
+ const nextCheckPoint = {
+ x: currentRoofLine.x2 + nextVector.x * lineWidth,
+ y: currentRoofLine.y2 + nextVector.y * lineWidth,
+ }
+
+ let prevHipVector, nextHipVector
+
+ if (roof.inPolygon(prevCheckPoint)) {
+ prevHipVector = { x: prevVector.x, y: prevVector.y }
+ } else {
+ prevHipVector = { x: -prevVector.x, y: -prevVector.y }
+ }
+
+ /** 다음 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/
+ if (roof.inPolygon(nextCheckPoint)) {
+ nextHipVector = { x: nextVector.x, y: nextVector.y }
+ } else {
+ nextHipVector = { x: -nextVector.x, y: -nextVector.y }
+ }
+
+ const prevHipPoint = [
+ currentRoofLine.x1,
+ currentRoofLine.y1,
+ currentRoofLine.x1 + prevHipVector.x * lineWidth,
+ currentRoofLine.y1 + prevHipVector.y * lineWidth,
+ ]
+ const nextHipPoint = [
+ currentRoofLine.x2,
+ currentRoofLine.y2,
+ currentRoofLine.x2 + nextHipVector.x * lineWidth,
+ currentRoofLine.y2 + nextHipVector.y * lineWidth,
+ ]
+ const gablePoint = [prevHipPoint[2], prevHipPoint[3], nextHipPoint[2], nextHipPoint[3]]
+
+ innerLines.push(drawHipLine(prevHipPoint, canvas, roof, textMode, null, prevDegree, prevDegree))
+ innerLines.push(drawHipLine(nextHipPoint, canvas, roof, textMode, null, nextDegree, nextDegree))
+ // innerLines.push(drawRoofLine(gablePoint, canvas, roof, textMode))
+
+ //양옆이 처마일경우 두개의 선, 아닐때 한개의 선, 좌우가 처마가 아닐때 안그려져야하는데 기존에 그려지는 경우가 있음 이유를 알 수 없음.
+ if (prevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES && nextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) {
+ const midPoint = { x: (prevHipPoint[2] + nextHipPoint[2]) / 2, y: (prevHipPoint[3] + nextHipPoint[3]) / 2 }
+ const prevGablePoint = [gablePoint[0], gablePoint[1], midPoint.x, midPoint.y]
+ const nextGablePoint = [gablePoint[2], gablePoint[3], midPoint.x, midPoint.y]
+ const prevDx = prevGablePoint[0] - prevGablePoint[2]
+ const prevDy = prevGablePoint[1] - prevGablePoint[3]
+ const prevGableLength = Math.sqrt(prevDx * prevDx + prevDy * prevDy)
+ const nextDx = nextGablePoint[0] - nextGablePoint[2]
+ const nextDy = nextGablePoint[1] - nextGablePoint[3]
+ const nextGableLength = Math.sqrt(nextDx * nextDx + nextDy * nextDy)
+ if (prevGableLength >= 1) {
+ innerLines.push(drawHipLine(prevGablePoint, canvas, roof, textMode, null, prevDegree, prevDegree))
+ }
+ if (nextGableLength >= 1) {
+ innerLines.push(drawHipLine(nextGablePoint, canvas, roof, textMode, null, nextDegree, nextDegree))
+ }
+ const checkEdge = {
+ vertex1: { x: midPoint.x, y: midPoint.y },
+ vertex2: { x: midPoint.x + -analyze.roofVector.x * 10000, y: midPoint.y + -analyze.roofVector.y * 10000 },
+ }
+ const intersections = []
+ roof.lines
+ .filter((line) => line !== currentRoofLine)
+ .forEach((line) => {
+ const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
+ const intersect = edgesIntersection(lineEdge, checkEdge)
+ if (intersect && isPointOnLineNew(line, intersect)) {
+ const distance = Math.sqrt(Math.pow(midPoint.x - intersect.x, 2) + Math.pow(midPoint.y - intersect.y, 2))
+ intersections.push({ intersect, distance })
+ }
+ })
+ intersections.sort((a, b) => a.distance - b.distance)
+ if (intersections.length > 0) {
+ const intersect = intersections[0].intersect
+ const point = [midPoint.x, midPoint.y, intersect.x, intersect.y]
+
+ //마루가 최대로 뻗어나갈수 있는 포인트. null일때는 맞은편 지붕선 까지로 판단한다.
+ const drivePoint = getRidgeDrivePoint(point, prevLine, nextLine, baseLines)
+
+ const isOverlapBefore = analyze.isHorizontal
+ ? almostEqual(Math.min(beforePrevLine.x1, beforePrevLine.x2), Math.min(currentLine.x1, currentLine.x2)) &&
+ almostEqual(Math.max(beforePrevLine.x1, beforePrevLine.x2), Math.max(currentLine.x1, currentLine.x2))
+ : almostEqual(Math.min(beforePrevLine.y1, beforePrevLine.y2), Math.min(currentLine.y1, currentLine.y2)) &&
+ almostEqual(Math.max(beforePrevLine.y1, beforePrevLine.y2), Math.max(currentLine.y1, currentLine.y2))
+ const isOverlapAfter = analyze.isHorizontal
+ ? almostEqual(Math.min(afterNextLine.x1, afterNextLine.x2), Math.min(currentLine.x1, currentLine.x2)) &&
+ almostEqual(Math.max(afterNextLine.x1, afterNextLine.x2), Math.max(currentLine.x1, currentLine.x2))
+ : almostEqual(Math.min(afterNextLine.y1, afterNextLine.y2), Math.min(currentLine.y1, currentLine.y2)) &&
+ almostEqual(Math.max(afterNextLine.y1, afterNextLine.y2), Math.max(currentLine.y1, currentLine.y2))
+
+ if (isOverlapBefore || isOverlapAfter) {
+ const oppLine = isOverlapBefore ? beforePrevLine : afterNextLine
+ const otherGable = baseLines.find(
+ (l) => l !== currentLine && l !== prevLine && l !== nextLine && l !== oppLine && l.attributes.type === LINE_TYPE.WALLLINE.GABLE,
+ )
+ const pointVector = { x: Math.sign(clamp01(point[0] - point[2])), y: Math.sign(clamp01(point[1] - point[3])) }
+ if (!otherGable) {
+ let offset = 0
+ switch (oppLine.attributes.type) {
+ case LINE_TYPE.WALLLINE.HIPANDGABLE:
+ offset = oppLine.attributes.width
+ break
+ case LINE_TYPE.WALLLINE.JERKINHEAD:
+ offset = oppLine.attributes.width / 2
+ break
+ default:
+ break
+ }
+ point[2] += pointVector.x * offset
+ point[3] += pointVector.y * offset
+ } else {
+ if (drivePoint) {
+ point[2] = drivePoint.x
+ point[3] = drivePoint.y
+ }
+ }
+ } else if (drivePoint) {
+ point[2] = drivePoint.x
+ point[3] = drivePoint.y
+ }
+
+ linesAnalysis.push({
+ start: { x: point[0], y: point[1] },
+ end: { x: point[2], y: point[3] },
+ left: baseLines.findIndex((line) => line === prevLine),
+ right: baseLines.findIndex((line) => line === nextLine),
+ type: TYPES.RIDGE,
+ degree: 0,
+ })
+ }
+ } else {
+ const gableDx = gablePoint[0] - gablePoint[2]
+ const gableDy = gablePoint[1] - gablePoint[3]
+ const gableLength = Math.sqrt(gableDx * gableDx + gableDy * gableDy)
+ if (gableLength >= 1) {
+ innerLines.push(drawRoofLine(gablePoint, canvas, roof, textMode))
+ } else {
+ const midPoint = { x: gablePoint[2], y: gablePoint[3] }
+ const checkEdge = {
+ vertex1: { x: midPoint.x, y: midPoint.y },
+ vertex2: { x: midPoint.x + -analyze.roofVector.x * 10000, y: midPoint.y + -analyze.roofVector.y * 10000 },
+ }
+ const intersections = []
+ roof.lines
+ .filter((line) => line !== currentRoofLine)
+ .forEach((line) => {
+ const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
+ const intersect = edgesIntersection(lineEdge, checkEdge)
+ if (intersect && isPointOnLineNew(line, intersect)) {
+ const distance = Math.sqrt(Math.pow(midPoint.x - intersect.x, 2) + Math.pow(midPoint.y - intersect.y, 2))
+ intersections.push({ intersect, distance })
+ }
+ })
+ intersections.sort((a, b) => a.distance - b.distance)
+ if (intersections.length > 0) {
+ const intersect = intersections[0].intersect
+ linesAnalysis.push({
+ start: { x: midPoint.x, y: midPoint.y },
+ end: { x: intersect.x, y: intersect.y },
+ left: baseLines.findIndex((line) => line === prevLine),
+ right: baseLines.findIndex((line) => line === nextLine),
+ type: TYPES.RIDGE,
+ degree: 0,
+ })
+ }
+ }
+ }
+ })
+
+ eaves.sort((a, b) => a.attributes.planeSize - b.attributes.planeSize)
+ //3. 처마지붕 판단, 처마는 옆이 처마여야한다.
+
+ const ridgeEaves = eaves.filter((currentLine) => {
+ let prevLine, nextLine
+ baseLines.forEach((baseLine, index) => {
+ if (baseLine === currentLine) {
+ nextLine = baseLines[(index + 1) % baseLines.length]
+ prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length]
+ }
+ })
+ const prevLineVector = { x: Math.sign(prevLine.x1 - prevLine.x2), y: Math.sign(prevLine.y1 - prevLine.y2) }
+ const nextLineVector = { x: Math.sign(nextLine.x1 - nextLine.x2), y: Math.sign(nextLine.y1 - nextLine.y2) }
+ const inPolygonPoint = {
+ x: (currentLine.x1 + currentLine.x2) / 2 + Math.sign(nextLine.x2 - nextLine.x1),
+ y: (currentLine.y1 + currentLine.y2) / 2 + Math.sign(nextLine.y2 - nextLine.y1),
+ }
+
+ //좌우 라인이 서로 다른방향이고 지붕 안쪽으로 들어가지 않을때
+ const isAbleShape = (prevLineVector.x !== nextLineVector.x || prevLineVector.y !== nextLineVector.y) && checkWallPolygon.inPolygon(inPolygonPoint)
+ const isAbleAttribute = prevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES && nextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES
+ return isAbleAttribute && isAbleShape
+ })
+
+ const hipedEaves = eaves.filter((line) => !ridgeEaves.includes(line))
+
+ ridgeEaves.sort((a, b) => a.attributes.planeSize - b.attributes.planeSize)
+
+ let proceedEaves = [] // left: 이전, right:다음, point:그려지는 포인트, length:길이
+ let proceedRidges = [] // left: 이전, right:다음, point:그려지는 포인트, length:길이
+ let hipLines = []
+ ridgeEaves.forEach((currentLine) => {
+ /*const checkLine = new fabric.Line([currentLine.x1, currentLine.y1, currentLine.x2, currentLine.y2], {
+ stroke: 'red',
+ strokeWidth: 4,
+ parentId: roofId,
+ name: 'check',
+ })
+ canvas.add(checkLine).renderAll()*/
+ let prevLine, nextLine, currentI, prevI, nextI
+ baseLines.forEach((baseLine, index) => {
+ if (baseLine === currentLine) {
+ currentI = index
+ prevI = (index - 1 + baseLines.length) % baseLines.length
+ nextI = (index + 1) % baseLines.length
+ }
+ })
+ prevLine = baseLines[prevI]
+ nextLine = baseLines[nextI]
+
+ let beforePrevLine, afterNextLine
+ baseLines.forEach((baseLine, index) => {
+ if (baseLine === prevLine) {
+ beforePrevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length]
+ }
+ if (baseLine === nextLine) {
+ afterNextLine = baseLines[(index + 1) % baseLines.length]
+ }
+ })
+
+ const analyze = analyzeLine(currentLine)
+ const currentDegree = getDegreeByChon(currentLine.attributes.pitch)
+ let pHipVector = getHalfAngleVector(currentLine, prevLine)
+ let nHipVector = getHalfAngleVector(currentLine, nextLine)
+ const pCheckPoint = {
+ x: currentLine.x1 + (pHipVector.x * 10) / 2,
+ y: currentLine.y1 + (pHipVector.y * 10) / 2,
+ }
+ const nCheckPoint = {
+ x: currentLine.x2 + (nHipVector.x * 10) / 2,
+ y: currentLine.y2 + (nHipVector.y * 10) / 2,
+ }
+
+ if (!checkWallPolygon.inPolygon(pCheckPoint)) {
+ pHipVector = { x: -pHipVector.x, y: -pHipVector.y }
+ }
+ if (!checkWallPolygon.inPolygon(nCheckPoint)) {
+ nHipVector = { x: -nHipVector.x, y: -nHipVector.y }
+ }
+
+ const prevCheckPoint = [currentLine.x1, currentLine.y1, currentLine.x1 + pHipVector.x * 1000, currentLine.y1 + pHipVector.y * 1000]
+ const nextCheckPoint = [currentLine.x2, currentLine.y2, currentLine.x2 + nHipVector.x * 1000, currentLine.y2 + nHipVector.y * 1000]
+
+ const findRoofPoints = (points) => {
+ const hipEdge = { vertex1: { x: points[0], y: points[1] }, vertex2: { x: points[2], y: points[3] } }
+ const hipForwardVector = { x: Math.sign(hipEdge.vertex1.x - hipEdge.vertex2.x), y: Math.sign(hipEdge.vertex1.y - hipEdge.vertex2.y) }
+ const hipBackwardVector = { x: -hipForwardVector.x, y: -hipForwardVector.y }
+ const isForwardPoints = []
+ const isBackwardPoints = []
+
+ roof.lines.forEach((roofLine) => {
+ const lineEdge = { vertex1: { x: roofLine.x1, y: roofLine.y1 }, vertex2: { x: roofLine.x2, y: roofLine.y2 } }
+ const intersect = edgesIntersection(lineEdge, hipEdge)
+ if (intersect && isPointOnLineNew(roofLine, intersect)) {
+ const intersectVector = { x: Math.sign(hipEdge.vertex1.x - intersect.x), y: Math.sign(hipEdge.vertex1.y - intersect.y) }
+ if (
+ (intersectVector.x === hipForwardVector.x && intersectVector.y === hipForwardVector.y) ||
+ (intersectVector.x === 0 && intersectVector.y === 0)
+ ) {
+ const dx = hipEdge.vertex1.x - intersect.x
+ const dy = hipEdge.vertex1.y - intersect.y
+ const length = Math.sqrt(dx * dx + dy * dy)
+ isForwardPoints.push({ intersect, length })
+ }
+ if (intersectVector.x === hipBackwardVector.x && intersectVector.y === hipBackwardVector.y) {
+ const dx = hipEdge.vertex2.x - intersect.x
+ const dy = hipEdge.vertex2.y - intersect.y
+ const length = Math.sqrt(dx * dx + dy * dy)
+ isBackwardPoints.push({ intersect, length })
+ }
+ }
+ })
+ isForwardPoints.sort((a, b) => a.length - b.length)
+ isBackwardPoints.sort((a, b) => a.length - b.length)
+ return { forward: isForwardPoints[0].intersect, backward: isBackwardPoints[0].intersect }
+ }
+
+ const pRoofPoints = findRoofPoints(prevCheckPoint)
+ const nRoofPoints = findRoofPoints(nextCheckPoint)
+
+ let prevHipPoint = { x1: pRoofPoints.backward.x, y1: pRoofPoints.backward.y, x2: pRoofPoints.forward.x, y2: pRoofPoints.forward.y }
+ let nextHipPoint = { x1: nRoofPoints.backward.x, y1: nRoofPoints.backward.y, x2: nRoofPoints.forward.x, y2: nRoofPoints.forward.y }
+
+ const prevEdge = { vertex1: { x: prevHipPoint.x1, y: prevHipPoint.y1 }, vertex2: { x: prevHipPoint.x2, y: prevHipPoint.y2 } }
+ const nextEdge = { vertex1: { x: nextHipPoint.x1, y: nextHipPoint.y1 }, vertex2: { x: nextHipPoint.x2, y: nextHipPoint.y2 } }
+ const intersect = edgesIntersection(prevEdge, nextEdge)
+ if (intersect && isPointOnLineNew(prevHipPoint, intersect) && isPointOnLineNew(nextHipPoint, intersect)) {
+ prevHipPoint.x2 = intersect.x
+ prevHipPoint.y2 = intersect.y
+ nextHipPoint.x2 = intersect.x
+ nextHipPoint.y2 = intersect.y
+ }
+
+ let isRidgePrev, isRidgeNext
+ let minPrevDist = Infinity
+ let minNextDist = Infinity
+ proceedRidges.forEach((ridge) => {
+ console.log('ridge : ', ridge)
+ const ridgeEdge = { vertex1: { x: ridge.point.x1, y: ridge.point.y1 }, vertex2: { x: ridge.point.x2, y: ridge.point.y2 } }
+ const isPrev = edgesIntersection(ridgeEdge, prevEdge)
+ const isNext = edgesIntersection(ridgeEdge, nextEdge)
+ if (
+ isPrev &&
+ isPointOnLineNew(prevHipPoint, isPrev) &&
+ (ridge.prev === prevI || ridge.prev === nextI || ridge.next === prevI || ridge.next === nextI)
+ ) {
+ const distance = Math.sqrt((isPrev.x - prevHipPoint.x1) ** 2 + (isPrev.y - prevHipPoint.y1) ** 2)
+ if (distance < minPrevDist) {
+ minPrevDist = distance
+ isRidgePrev = isPrev
+ }
+ }
+ if (
+ isNext &&
+ isPointOnLineNew(nextHipPoint, isNext) &&
+ (ridge.prev === prevI || ridge.prev === nextI || ridge.next === prevI || ridge.next === nextI)
+ ) {
+ const distance = Math.sqrt((isNext.x - nextHipPoint.x1) ** 2 + (isNext.y - nextHipPoint.y1) ** 2)
+ if (distance < minNextDist) {
+ minNextDist = distance
+ isRidgeNext = isNext
+ }
+ }
+ })
+
+ //접하는 라인에서 파생된 마루선이 겹칠경우 라인보정을 종료
+ if (isRidgePrev) {
+ prevHipPoint = { x1: pRoofPoints.backward.x, y1: pRoofPoints.backward.y, x2: pRoofPoints.forward.x, y2: pRoofPoints.forward.y }
+ }
+ if (isRidgeNext) {
+ nextHipPoint = { x1: nRoofPoints.backward.x, y1: nRoofPoints.backward.y, x2: nRoofPoints.forward.x, y2: nRoofPoints.forward.y }
+ }
+
+ let prevHipLength = Math.sqrt((prevHipPoint.x2 - prevHipPoint.x1) ** 2 + (prevHipPoint.y2 - prevHipPoint.y1) ** 2)
+ let nextHipLength = Math.sqrt((nextHipPoint.x2 - nextHipPoint.x1) ** 2 + (nextHipPoint.y2 - nextHipPoint.y1) ** 2)
+
+ const alreadyPrev = proceedEaves.filter((line) => almostEqual(line.point.x1, prevHipPoint.x1) && almostEqual(line.point.y1, prevHipPoint.y1))
+ const alreadyNext = proceedEaves.filter((line) => almostEqual(line.point.x1, nextHipPoint.x1) && almostEqual(line.point.y1, nextHipPoint.y1))
+ if (alreadyPrev.length) {
+ alreadyPrev.sort((a, b) => a.length - b.length)
+ const alrPrev = alreadyPrev[0]
+ if (isRidgePrev) {
+ alrPrev.prev = prevI
+ alrPrev.current = currentI
+ alrPrev.point = prevHipPoint
+ alrPrev.length = prevHipLength
+ } else {
+ if (prevHipLength < alrPrev.length) {
+ //겹치는데 지금 만들어지는 라인이 더 짧은경우 다른 라인제거 하고 현재 라인을 추가.
+ alrPrev.prev = prevI
+ alrPrev.current = currentI
+ alrPrev.point = prevHipPoint
+ alrPrev.length = prevHipLength
+ } else {
+ prevHipPoint = alrPrev.point
+ }
+ }
+ } else {
+ proceedEaves.push({ prev: prevI, current: currentI, point: prevHipPoint, length: prevHipLength })
+ }
+ if (alreadyNext.length) {
+ alreadyNext.sort((a, b) => a.length - b.length)
+ const alrNext = alreadyNext[0]
+ if (isRidgeNext) {
+ alrNext.prev = currentI
+ alrNext.current = nextI
+ alrNext.point = nextHipPoint
+ alrNext.length = nextHipLength
+ } else {
+ if (nextHipLength < alrNext.length) {
+ //겹치는데 지금 만들어지는 라인이 더 짧은경우 다른 라인제거 하고 현재 라인을 추가.
+ alrNext.prev = currentI
+ alrNext.current = nextI
+ alrNext.point = nextHipPoint
+ alrNext.length = nextHipLength
+ } else {
+ nextHipPoint = alrNext.point
+ }
+ }
+ } else {
+ proceedEaves.push({ prev: currentI, current: nextI, point: nextHipPoint, length: nextHipLength })
+ }
+
+ let ridgePoint
+ if (almostEqual(prevHipPoint.x2, nextHipPoint.x2) && almostEqual(prevHipPoint.y2, nextHipPoint.y2)) {
+ const ridgeStartPoint = { x: prevHipPoint.x2, y: prevHipPoint.y2 }
+
+ let ridgeVector = { x: Math.sign(clamp01(nextLine.x2 - nextLine.x1)), y: Math.sign(clamp01(nextLine.y2 - nextLine.y1)) }
+ const midX = (currentLine.x1 + currentLine.x2) / 2
+ const midY = (currentLine.y1 + currentLine.y2) / 2
+ let checkPoint = { x: midX + ridgeVector.x, y: midY + ridgeVector.y }
+ if (!checkWallPolygon.inPolygon(checkPoint)) {
+ ridgeVector = { x: -ridgeVector.x, y: -ridgeVector.y }
+ }
+ ridgePoint = { x1: ridgeStartPoint.x, y1: ridgeStartPoint.y, x2: ridgeStartPoint.x + ridgeVector.x, y2: ridgeStartPoint.y + ridgeVector.y }
+ const ridgeEdge = { vertex1: { x: ridgePoint.x1, y: ridgePoint.y1 }, vertex2: { x: ridgePoint.x2, y: ridgePoint.y2 } }
+
+ let roofIs
+ let minDistance = Infinity
+ roof.lines.forEach((line) => {
+ const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
+ const intersect = edgesIntersection(lineEdge, ridgeEdge)
+ if (intersect && isPointOnLineNew(line, intersect)) {
+ const isVector = { x: Math.sign(clamp01(intersect.x - ridgeStartPoint.x)), y: Math.sign(clamp01(intersect.y - ridgeStartPoint.y)) }
+ const distance = Math.sqrt(Math.pow(ridgeStartPoint.x - intersect.x, 2) + Math.pow(ridgeStartPoint.y - intersect.y, 2))
+ if (distance < minDistance && isVector.x === ridgeVector.x && isVector.y === ridgeVector.y) {
+ minDistance = distance
+ roofIs = intersect
+ }
+ }
+ })
+ if (roofIs) {
+ ridgePoint.x2 = roofIs.x
+ ridgePoint.y2 = roofIs.y
+ }
+
+ const drivePoint = getRidgeDrivePoint([ridgePoint.x1, ridgePoint.y1, ridgePoint.x2, ridgePoint.y2], prevLine, nextLine, baseLines)
+ const isOverlapBefore = analyze.isHorizontal
+ ? almostEqual(Math.min(beforePrevLine.x1, beforePrevLine.x2), Math.min(currentLine.x1, currentLine.x2)) &&
+ almostEqual(Math.max(beforePrevLine.x1, beforePrevLine.x2), Math.max(currentLine.x1, currentLine.x2))
+ : almostEqual(Math.min(beforePrevLine.y1, beforePrevLine.y2), Math.min(currentLine.y1, currentLine.y2)) &&
+ almostEqual(Math.max(beforePrevLine.y1, beforePrevLine.y2), Math.max(currentLine.y1, currentLine.y2))
+ const isOverlapAfter = analyze.isHorizontal
+ ? almostEqual(Math.min(afterNextLine.x1, afterNextLine.x2), Math.min(currentLine.x1, currentLine.x2)) &&
+ almostEqual(Math.max(afterNextLine.x1, afterNextLine.x2), Math.max(currentLine.x1, currentLine.x2))
+ : almostEqual(Math.min(afterNextLine.y1, afterNextLine.y2), Math.min(currentLine.y1, currentLine.y2)) &&
+ almostEqual(Math.max(afterNextLine.y1, afterNextLine.y2), Math.max(currentLine.y1, currentLine.y2))
+ if (isOverlapBefore || isOverlapAfter) {
+ const oppLine = isOverlapBefore ? beforePrevLine : afterNextLine
+ const otherGable = baseLines.find(
+ (l) => l !== currentLine && l !== prevLine && l !== nextLine && l !== oppLine && l.attributes.type === LINE_TYPE.WALLLINE.GABLE,
+ )
+ const pointVector = { x: Math.sign(clamp01(ridgePoint.x1 - ridgePoint.x2)), y: Math.sign(clamp01(ridgePoint.y1 - ridgePoint.y2)) }
+ let offset = 0
+ switch (oppLine.attributes.type) {
+ case LINE_TYPE.WALLLINE.HIPANDGABLE:
+ offset = oppLine.attributes.width
+ break
+ case LINE_TYPE.WALLLINE.JERKINHEAD:
+ offset = oppLine.attributes.width / 2
+ break
+ default:
+ break
+ }
+ if (!otherGable) {
+ if (oppLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) {
+ const oppIndex = baseLines.findIndex((l) => l === oppLine)
+ const oppPrevLine = baseLines[(oppIndex - 1 + baseLines.length) % baseLines.length]
+ const oppNextLine = baseLines[(oppIndex + 1) % baseLines.length]
+
+ if (oppPrevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES || oppNextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) {
+ const currentLength = Math.sqrt(Math.pow(currentLine.x1 - currentLine.x2, 2) + Math.pow(currentLine.y1 - currentLine.y2, 2))
+ const oppLength = Math.sqrt(Math.pow(oppLine.x1 - oppLine.x2, 2) + Math.pow(oppLine.y1 - oppLine.y2, 2))
+ if (almostEqual(currentLength, oppLength)) {
+ if (drivePoint) {
+ ridgePoint.x2 = drivePoint.x
+ ridgePoint.y2 = drivePoint.y
+ }
+ } else {
+ ridgePoint.x2 += pointVector.x * offset
+ ridgePoint.y2 += pointVector.y * offset
+ }
+ } else {
+ ridgePoint.x2 += pointVector.x * offset
+ ridgePoint.y2 += pointVector.y * offset
+ }
+ }
+ } else if (drivePoint) {
+ ridgePoint.x2 = drivePoint.x
+ ridgePoint.y2 = drivePoint.y
+ }
+ } else if (drivePoint) {
+ ridgePoint.x2 = drivePoint.x
+ ridgePoint.y2 = drivePoint.y
+ }
+ }
+ if (ridgePoint) {
+ const alreadyRidge = proceedRidges.find(
+ (line) =>
+ almostEqual(line.point.x1, ridgePoint.x1) &&
+ almostEqual(line.point.y1, ridgePoint.y1) &&
+ almostEqual(line.point.x2, ridgePoint.x2) &&
+ almostEqual(line.point.y2, ridgePoint.y2),
+ )
+ if (!alreadyRidge) {
+ proceedRidges.push({ prev: prevI, next: nextI, point: ridgePoint })
+ }
+
+ /*const checkRidgeStart = new fabric.Circle({ left: ridgePoint.x1, top: ridgePoint.y1, radius: 4, parentId: roofId, name: 'check' })
+ const checkRidgeLine = new fabric.Line([ridgePoint.x1, ridgePoint.y1, ridgePoint.x2, ridgePoint.y2], {
+ stroke: 'yellow',
+ strokeWidth: 4,
+ parentId: roofId,
+ name: 'check',
+ })
+ canvas.add(checkRidgeLine, checkRidgeStart).renderAll()*/
+ }
+
+ /*const checkLine1 = new fabric.Line([prevHipPoint.x1, prevHipPoint.y1, prevHipPoint.x2, prevHipPoint.y2], {
+ stroke: 'red',
+ strokeWidth: 4,
+ parentId: roofId,
+ name: 'check',
+ })
+ const checkLine2 = new fabric.Line([nextHipPoint.x1, nextHipPoint.y1, nextHipPoint.x2, nextHipPoint.y2], {
+ stroke: 'blue',
+ strokeWidth: 4,
+ parentId: roofId,
+ name: 'check',
+ })
+ canvas.add(checkLine1, checkLine2).renderAll()
+*/
+ canvas
+ .getObjects()
+ .filter((o) => o.name === 'check')
+ .forEach((o) => canvas.remove(o))
+ canvas.renderAll()
+ })
+
+ console.log('hipedEaves', hipedEaves)
+ hipedEaves.forEach((currentLine) => {
+ let prevLine
+ baseLines.forEach((baseLine, index) => {
+ if (baseLine === currentLine) {
+ prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length]
+ }
+ })
+
+ const analyze = analyzeLine(currentLine)
+
+ // 옆이 처마가 아니면 패스
+ if (prevLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES) {
+ return
+ }
+
+ const currentDegree = getDegreeByChon(currentLine.attributes.pitch)
+ const checkVector = getHalfAngleVector(currentLine, prevLine)
+ const checkPoint = {
+ x: currentLine.x1 + (checkVector.x * 10) / 2,
+ y: currentLine.y1 + (checkVector.y * 10) / 2,
+ }
+
+ let hipVector
+ if (checkWallPolygon.inPolygon(checkPoint)) {
+ hipVector = { x: checkVector.x, y: checkVector.y }
+ } else {
+ hipVector = { x: -checkVector.x, y: -checkVector.y }
+ }
+
+ const hipEdge = {
+ vertex1: { x: currentLine.x1, y: currentLine.y1 },
+ vertex2: { x: currentLine.x1 + hipVector.x * analyze.length, y: currentLine.y1 + hipVector.y * analyze.length },
+ }
+
+ const hipForwardVector = { x: Math.sign(hipEdge.vertex1.x - hipEdge.vertex2.x), y: Math.sign(hipEdge.vertex1.y - hipEdge.vertex2.y) }
+ const hipBackwardVector = { x: -hipForwardVector.x, y: -hipForwardVector.y }
+ const isForwardPoints = []
+ const isBackwardPoints = []
+
+ roof.lines.forEach((roofLine) => {
+ const lineEdge = { vertex1: { x: roofLine.x1, y: roofLine.y1 }, vertex2: { x: roofLine.x2, y: roofLine.y2 } }
+ const intersect = edgesIntersection(lineEdge, hipEdge)
+ if (intersect && isPointOnLineNew(roofLine, intersect)) {
+ const intersectVector = { x: Math.sign(hipEdge.vertex1.x - intersect.x), y: Math.sign(hipEdge.vertex1.y - intersect.y) }
+ if (
+ (intersectVector.x === hipForwardVector.x && intersectVector.y === hipForwardVector.y) ||
+ (intersectVector.x === 0 && intersectVector.y === 0)
+ ) {
+ const dx = hipEdge.vertex1.x - intersect.x
+ const dy = hipEdge.vertex1.y - intersect.y
+ const length = Math.sqrt(dx * dx + dy * dy)
+ isForwardPoints.push({ intersect, length })
+ }
+ if (intersectVector.x === hipBackwardVector.x && intersectVector.y === hipBackwardVector.y) {
+ const dx = hipEdge.vertex2.x - intersect.x
+ const dy = hipEdge.vertex2.y - intersect.y
+ const length = Math.sqrt(dx * dx + dy * dy)
+ isBackwardPoints.push({ intersect, length })
+ }
+ }
+ })
+ isForwardPoints.sort((a, b) => a.length - b.length)
+ isBackwardPoints.sort((a, b) => a.length - b.length)
+
+ let hipPoint
+ if (isForwardPoints.length > 0 && isBackwardPoints.length > 0) {
+ hipPoint = {
+ x1: isBackwardPoints[0].intersect.x,
+ y1: isBackwardPoints[0].intersect.y,
+ x2: isForwardPoints[0].intersect.x,
+ y2: isForwardPoints[0].intersect.y,
+ }
+ } else {
+ if (isBackwardPoints.length === 0 && isForwardPoints.length > 1) {
+ hipPoint = {
+ x1: isForwardPoints[0].intersect.x,
+ y1: isForwardPoints[0].intersect.y,
+ x2: isForwardPoints[1].intersect.x,
+ y2: isForwardPoints[1].intersect.y,
+ }
+ }
+ if (isForwardPoints.length === 0 && isBackwardPoints.length > 1) {
+ hipPoint = {
+ x1: isBackwardPoints[0].intersect.x,
+ y1: isBackwardPoints[0].intersect.y,
+ x2: isBackwardPoints[1].intersect.x,
+ y2: isBackwardPoints[1].intersect.y,
+ }
+ }
+ }
+
+ if (hipPoint) {
+ const hipLength = Math.sqrt((hipPoint.x2 - hipPoint.x1) ** 2 + (hipPoint.y2 - hipPoint.y1) ** 2)
+ const alreadyLine = proceedEaves.filter((line) => almostEqual(line.point.x1, hipPoint.x1) && almostEqual(line.point.y1, hipPoint.y1))
+ //겹쳐지는 라인이 있는경우 조정한다.
+ if (alreadyLine.length === 0) {
+ linesAnalysis.push({
+ start: { x: hipPoint.x1, y: hipPoint.y1 },
+ end: { x: hipPoint.x2, y: hipPoint.y2 },
+ left: baseLines.findIndex((line) => line === prevLine),
+ right: baseLines.findIndex((line) => line === currentLine),
+ type: TYPES.HIP,
+ degree: currentDegree,
+ })
+ }
+ }
+ })
+
+ //작성된 라인을 analyze에 추가한다.
+ const pIndexEaves = []
+ proceedEaves.forEach((eaves, index) => {
+ if (pIndexEaves.includes(index)) return
+ const { prev, current, point } = eaves
+ const currentDegree = getDegreeByChon(baseLines[current].attributes.pitch)
+ const jointEaves = proceedEaves
+ .filter((e) => e !== eaves)
+ .filter((e) => almostEqual(e.point.x2, eaves.point.x2) && almostEqual(e.point.y2, eaves.point.y2))
+ if (jointEaves.length === 1 && ridgeEaves.length > 1) {
+ innerLines.push(drawHipLine([point.x1, point.y1, point.x2, point.y2], canvas, roof, textMode, null, currentDegree, currentDegree))
+ pIndexEaves.push(index)
+ } else if (jointEaves.length === 2) {
+ console.log('jointEaves : ', jointEaves)
+ const jointIndex = [index]
+ let jointLines = []
+ jointEaves.forEach((e) => jointIndex.push(proceedEaves.findIndex((p) => p === e)))
+ const jointVectors = []
+ //연결된 라인 생성
+ jointIndex.forEach((i) => {
+ const ev = proceedEaves[i]
+ const degree = getDegreeByChon(baseLines[ev.current].attributes.pitch)
+ innerLines.push(drawHipLine([ev.point.x1, ev.point.y1, ev.point.x2, ev.point.y2], canvas, roof, textMode, null, degree, degree))
+ pIndexEaves.push(i)
+ jointLines.push(ev.prev, ev.current)
+ jointVectors.push({ x: Math.sign(ev.point.x2 - ev.point.x1), y: Math.sign(ev.point.y2 - ev.point.y1) })
+ })
+ //연결된 지점에서 파생된 마루선 제거
+ const removeRidge = proceedRidges.filter((ridge) => almostEqual(ridge.point.x1, eaves.point.x2) && almostEqual(ridge.point.y1, eaves.point.y2))
+ proceedRidges = proceedRidges.filter((ridge) => !removeRidge.includes(ridge))
+ console.log('pIndexEaves : ', pIndexEaves)
+ console.log('jointLines : ', jointLines)
+ console.log('jointVectors : ', jointVectors)
+ let dneVector = jointVectors.find((v) => !jointVectors.find((v2) => v2.x === -v.x && v2.y === -v.y))
+ console.log('dneVector : ', dneVector)
+ const findRoofEdge = {
+ vertex1: { x: eaves.point.x2, y: eaves.point.y2 },
+ vertex2: { x: eaves.point.x2 + dneVector.x, y: eaves.point.y2 + dneVector.y },
+ }
+ let minDistance = Infinity
+ let isPoint
+ roof.lines.forEach((line) => {
+ const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
+ const intersect = edgesIntersection(lineEdge, findRoofEdge)
+ if (intersect && isPointOnLineNew(line, intersect)) {
+ const distance = Math.sqrt(Math.pow(intersect.x - eaves.point.x2, 2) + Math.pow(intersect.y - eaves.point.y2, 2))
+ if (distance < minDistance) {
+ minDistance = distance
+ isPoint = intersect
+ }
+ }
+ })
+ if (isPoint) {
+ const countMap = new Map()
+ jointLines.forEach((value) => {
+ countMap.set(value, (countMap.get(value) || 0) + 1)
+ })
+
+ const uniqueLine = jointLines.filter((value) => countMap.get(value) === 1)
+ console.log('uniqueLine : ', uniqueLine)
+ linesAnalysis.push({
+ start: { x: eaves.point.x2, y: eaves.point.y2 },
+ end: { x: isPoint.x, y: isPoint.y },
+ left: uniqueLine[0],
+ right: uniqueLine[1],
+ type: TYPES.HIP,
+ degree: currentDegree,
+ })
+ }
+ console.log('=============')
+ } else {
+ linesAnalysis.push({
+ start: { x: point.x1, y: point.y1 },
+ end: { x: point.x2, y: point.y2 },
+ left: prev,
+ right: current,
+ type: TYPES.HIP,
+ degree: currentDegree,
+ })
+ pIndexEaves.push(index)
+ }
+ })
+ proceedRidges.forEach(({ prev, next, point }) =>
+ linesAnalysis.push({
+ start: { x: point.x1, y: point.y1 },
+ end: { x: point.x2, y: point.y2 },
+ left: prev,
+ right: next,
+ type: TYPES.RIDGE,
+ degree: 0,
+ }),
+ )
+
+ console.log('proceedEaves :', proceedEaves)
+ console.log('proceedRidges :', proceedRidges)
+
+ //4. 반절처(반절마루) 판단, 반절마루는 양옆이 처마여야 한다.
+ jerkinHeads.forEach((currentLine) => {
+ let prevLine, nextLine
+ baseLines.forEach((baseLine, index) => {
+ if (baseLine === currentLine) {
+ nextLine = baseLines[(index + 1) % baseLines.length]
+ prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length]
+ }
+ })
+
+ //양옆이 처마가 아닐때 패스
+ if (prevLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES || nextLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES) {
+ return
+ }
+
+ let beforePrevLine, afterNextLine
+ baseLines.forEach((baseLine, index) => {
+ if (baseLine === prevLine) {
+ beforePrevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length]
+ }
+ if (baseLine === nextLine) {
+ afterNextLine = baseLines[(index + 1) % baseLines.length]
+ }
+ })
+
+ const analyze = analyzeLine(currentLine)
+ const roofPoints = [analyze.roofLine.x1, analyze.roofLine.y1, analyze.roofLine.x2, analyze.roofLine.y2]
+
+ const prevLineVector = { x: Math.sign(prevLine.x1 - prevLine.x2), y: Math.sign(prevLine.y1 - prevLine.y2) }
+ const nextLineVector = { x: Math.sign(nextLine.x1 - nextLine.x2), y: Math.sign(nextLine.y1 - nextLine.y2) }
+ if (prevLineVector.x === nextLineVector.x && prevLineVector.y === nextLineVector.y) {
+ //반절마루 생성불가이므로 지붕선만 추가하고 끝냄
+ // innerLines.push(drawRoofLine(roofPoints, canvas, roof, textMode))
+ return
+ }
+ const currentDegree = getDegreeByChon(currentLine.attributes.pitch)
+ const prevDegree = getDegreeByChon(prevLine.attributes.pitch)
+ const nextDegree = getDegreeByChon(nextLine.attributes.pitch)
+ const gableWidth = currentLine.attributes.width
+
+ const roofVector = { x: Math.sign(roofPoints[2] - roofPoints[0]), y: Math.sign(roofPoints[3] - roofPoints[1]) }
+ const roofDx = roofPoints[0] - roofPoints[2]
+ const roofDy = roofPoints[1] - roofPoints[3]
+ const roofLength = Math.sqrt(roofDx * roofDx + roofDy * roofDy)
+ const lineLength = (roofLength - gableWidth) / 2
+ const jerkinPoints = []
+ jerkinPoints.push(
+ { x: roofPoints[0], y: roofPoints[1] },
+ { x: roofPoints[0] + roofVector.x * lineLength, y: roofPoints[1] + roofVector.y * lineLength },
+ )
+ jerkinPoints.push({ x: jerkinPoints[1].x + roofVector.x * gableWidth, y: jerkinPoints[1].y + roofVector.y * gableWidth })
+ jerkinPoints.push({ x: jerkinPoints[2].x + roofVector.x * lineLength, y: jerkinPoints[2].y + roofVector.y * lineLength })
+
+ if (gableWidth >= roofLength) {
+ innerLines.push(drawRoofLine(roofPoints, canvas, roof, textMode))
+ } else if (jerkinPoints.length === 4) {
+ const gablePoint1 = [jerkinPoints[0].x, jerkinPoints[0].y, jerkinPoints[1].x, jerkinPoints[1].y]
+ const gablePoint2 = [jerkinPoints[1].x, jerkinPoints[1].y, jerkinPoints[2].x, jerkinPoints[2].y]
+ const gablePoint3 = [jerkinPoints[2].x, jerkinPoints[2].y, jerkinPoints[3].x, jerkinPoints[3].y]
+ const gableLength1 = Math.sqrt(Math.pow(gablePoint1[2] - gablePoint1[0], 2) + Math.pow(gablePoint1[3] - gablePoint1[1], 2))
+ const gableLength2 = Math.sqrt(Math.pow(gablePoint2[2] - gablePoint2[0], 2) + Math.pow(gablePoint2[3] - gablePoint2[1], 2))
+ const gableLength3 = Math.sqrt(Math.pow(gablePoint3[2] - gablePoint3[0], 2) + Math.pow(gablePoint3[3] - gablePoint3[1], 2))
+ if (gableLength1 >= 1) {
+ innerLines.push(drawHipLine(gablePoint1, canvas, roof, textMode, null, prevDegree, prevDegree))
+ }
+ if (gableLength2 >= 1) {
+ innerLines.push(drawRoofLine(gablePoint2, canvas, roof, textMode))
+ }
+ if (gableLength3 >= 1) {
+ innerLines.push(drawHipLine(gablePoint3, canvas, roof, textMode, null, nextDegree, nextDegree))
+ }
+
+ const currentRoofLine = analyze.roofLine
+ let prevRoofLine, nextRoofLine
+ roof.lines.forEach((roofLine, index) => {
+ if (roofLine === currentRoofLine) {
+ nextRoofLine = roof.lines[(index + 1) % roof.lines.length]
+ prevRoofLine = roof.lines[(index - 1 + roof.lines.length) % roof.lines.length]
+ }
+ })
+
+ /** 이전, 다음라인의 사잇각의 vector를 구한다. */
+ let prevVector = getHalfAngleVector(prevRoofLine, currentRoofLine)
+ let nextVector = getHalfAngleVector(currentRoofLine, nextRoofLine)
+
+ /** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/
+ const prevCheckPoint = {
+ x: currentRoofLine.x1 + prevVector.x * 10,
+ y: currentRoofLine.y1 + prevVector.y * 10,
+ }
+ /** 다음 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/
+ const nextCheckPoint = {
+ x: currentRoofLine.x2 + nextVector.x * 10,
+ y: currentRoofLine.y2 + nextVector.y * 10,
+ }
+
+ let prevHipVector, nextHipVector
+
+ if (roof.inPolygon(prevCheckPoint)) {
+ prevHipVector = { x: prevVector.x, y: prevVector.y }
+ } else {
+ prevHipVector = { x: -prevVector.x, y: -prevVector.y }
+ }
+
+ /** 다음 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/
+ if (roof.inPolygon(nextCheckPoint)) {
+ nextHipVector = { x: nextVector.x, y: nextVector.y }
+ } else {
+ nextHipVector = { x: -nextVector.x, y: -nextVector.y }
+ }
+
+ const prevHipEdge = {
+ vertex1: { x: gablePoint2[0], y: gablePoint2[1] },
+ vertex2: { x: gablePoint2[0] + prevHipVector.x * gableWidth, y: gablePoint2[1] + prevHipVector.y * gableWidth },
+ }
+ const nextHipEdge = {
+ vertex1: { x: gablePoint2[2], y: gablePoint2[3] },
+ vertex2: { x: gablePoint2[2] + nextHipVector.x * gableWidth, y: gablePoint2[3] + nextHipVector.y * gableWidth },
+ }
+
+ const hipIntersection = edgesIntersection(prevHipEdge, nextHipEdge)
+ if (hipIntersection) {
+ const prevHipPoint = [prevHipEdge.vertex1.x, prevHipEdge.vertex1.y, hipIntersection.x, hipIntersection.y]
+ const nextHipPoint = [nextHipEdge.vertex1.x, nextHipEdge.vertex1.y, hipIntersection.x, hipIntersection.y]
+ innerLines.push(drawHipLine(prevHipPoint, canvas, roof, textMode, null, currentDegree, currentDegree))
+ innerLines.push(drawHipLine(nextHipPoint, canvas, roof, textMode, null, currentDegree, currentDegree))
+
+ const midPoint = { x: hipIntersection.x, y: hipIntersection.y }
+ const checkEdge = {
+ vertex1: { x: midPoint.x, y: midPoint.y },
+ vertex2: { x: midPoint.x + -analyze.roofVector.x * 10000, y: midPoint.y + -analyze.roofVector.y * 10000 },
+ }
+ const intersections = []
+ roof.lines
+ .filter((line) => line !== currentRoofLine)
+ .forEach((line) => {
+ const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
+ const intersect = edgesIntersection(lineEdge, checkEdge)
+ if (intersect && isPointOnLineNew(line, intersect)) {
+ const distance = Math.sqrt(Math.pow(midPoint.x - intersect.x, 2) + Math.pow(midPoint.y - intersect.y, 2))
+ intersections.push({ intersect, distance })
+ }
+ })
+ intersections.sort((a, b) => a.distance - b.distance)
+ if (intersections.length > 0) {
+ const intersect = intersections[0].intersect
+ const point = [midPoint.x, midPoint.y, intersect.x, intersect.y]
+
+ //마루가 최대로 뻗어나갈수 있는 포인트. null일때는 맞은편 지붕선 까지로 판단한다.
+ const drivePoint = getRidgeDrivePoint(point, prevLine, nextLine, baseLines)
+ const isOverlapBefore = analyze.isHorizontal
+ ? almostEqual(Math.min(beforePrevLine.x1, beforePrevLine.x2), Math.min(currentLine.x1, currentLine.x2)) &&
+ almostEqual(Math.max(beforePrevLine.x1, beforePrevLine.x2), Math.max(currentLine.x1, currentLine.x2))
+ : almostEqual(Math.min(beforePrevLine.y1, beforePrevLine.y2), Math.min(currentLine.y1, currentLine.y2)) &&
+ almostEqual(Math.max(beforePrevLine.y1, beforePrevLine.y2), Math.max(currentLine.y1, currentLine.y2))
+ const isOverlapAfter = analyze.isHorizontal
+ ? almostEqual(Math.min(afterNextLine.x1, afterNextLine.x2), Math.min(currentLine.x1, currentLine.x2)) &&
+ almostEqual(Math.max(afterNextLine.x1, afterNextLine.x2), Math.max(currentLine.x1, currentLine.x2))
+ : almostEqual(Math.min(afterNextLine.y1, afterNextLine.y2), Math.min(currentLine.y1, currentLine.y2)) &&
+ almostEqual(Math.max(afterNextLine.y1, afterNextLine.y2), Math.max(currentLine.y1, currentLine.y2))
+
+ if (isOverlapBefore || isOverlapAfter) {
+ const oppLine = isOverlapBefore ? beforePrevLine : afterNextLine
+ const otherGable = baseLines.find(
+ (l) => l !== currentLine && l !== prevLine && l !== nextLine && l !== oppLine && l.attributes.type === LINE_TYPE.WALLLINE.GABLE,
+ )
+ if (!otherGable) {
+ const pointVector = { x: Math.sign(clamp01(point[0] - point[2])), y: Math.sign(clamp01(point[1] - point[3])) }
+ let offset = 0
+ switch (oppLine.attributes.type) {
+ case LINE_TYPE.WALLLINE.HIPANDGABLE:
+ offset = oppLine.attributes.width
+ break
+ case LINE_TYPE.WALLLINE.JERKINHEAD:
+ offset = oppLine.attributes.width / 2
+ break
+ default:
+ break
+ }
+ point[2] += pointVector.x * offset
+ point[3] += pointVector.y * offset
+ } else if (drivePoint) {
+ point[2] = drivePoint.x
+ point[3] = drivePoint.y
+ }
+ } else if (drivePoint) {
+ point[2] = drivePoint.x
+ point[3] = drivePoint.y
+ }
+
+ linesAnalysis.push({
+ start: { x: point[0], y: point[1] },
+ end: { x: point[2], y: point[3] },
+ left: baseLines.findIndex((line) => line === prevLine),
+ right: baseLines.findIndex((line) => line === nextLine),
+ type: TYPES.RIDGE,
+ degree: 0,
+ })
+ }
+ }
+ }
+ })
+ //5. 케라바 판단, 케라바는 양옆이 처마여야 한다.
+ gables.forEach((currentLine) => {
+ let prevLine, nextLine
+ baseLines.forEach((baseLine, index) => {
+ if (baseLine === currentLine) {
+ nextLine = baseLines[(index + 1) % baseLines.length]
+ prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length]
+ }
+ })
+
+ //양옆이 처마가 아닐때 패스
+ if (prevLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES || nextLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES) {
+ return
+ }
+
+ const prevLineVector = { x: Math.sign(clamp01(prevLine.x1 - prevLine.x2)), y: Math.sign(clamp01(prevLine.y1 - prevLine.y2)) }
+ const nextLineVector = { x: Math.sign(clamp01(nextLine.x1 - nextLine.x2)), y: Math.sign(clamp01(nextLine.y1 - nextLine.y2)) }
+
+ const inPolygonPoint = {
+ x: (currentLine.x1 + currentLine.x2) / 2 + Math.sign(nextLine.x2 - nextLine.x1),
+ y: (currentLine.y1 + currentLine.y2) / 2 + Math.sign(nextLine.y2 - nextLine.y1),
+ }
+
+ /* const checkCurrLine = new fabric.Line([currentLine.x1, currentLine.y1, currentLine.x2, currentLine.y2], {
+ stroke: 'cyan',
+ strokeWidth: 4,
+ parentId: roofId,
+ name: 'check',
+ })
+ canvas.add(checkCurrLine)
+ canvas.renderAll()*/
+
+ //좌우 라인이 서로 다른방향일때
+ if ((prevLineVector.x !== nextLineVector.x || prevLineVector.y !== nextLineVector.y) && checkWallPolygon.inPolygon(inPolygonPoint)) {
+ const analyze = analyzeLine(currentLine)
+ const roofLine = analyze.roofLine
+
+ let beforePrevLine, afterNextLine
+ baseLines.forEach((baseLine, index) => {
+ if (baseLine === prevLine) {
+ beforePrevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length]
+ }
+ if (baseLine === nextLine) {
+ afterNextLine = baseLines[(index + 1) % baseLines.length]
+ }
+ })
+
+ const currentVector = { x: Math.sign(clamp01(currentLine.x1 - currentLine.x2)), y: Math.sign(clamp01(currentLine.y1 - currentLine.y2)) }
+ // 마루 진행 최대 길이
+ let prevDistance = 0,
+ nextDistance = 0
+
+ // 반대쪽 라인이 특정조건일때 마루선을 반대쪽까지로 처리한다..
+ const isOverlapBefore = analyze.isHorizontal
+ ? almostEqual(Math.min(beforePrevLine.x1, beforePrevLine.x2), Math.min(currentLine.x1, currentLine.x2)) &&
+ almostEqual(Math.max(beforePrevLine.x1, beforePrevLine.x2), Math.max(currentLine.x1, currentLine.x2))
+ : almostEqual(Math.min(beforePrevLine.y1, beforePrevLine.y2), Math.min(currentLine.y1, currentLine.y2)) &&
+ almostEqual(Math.max(beforePrevLine.y1, beforePrevLine.y2), Math.max(currentLine.y1, currentLine.y2))
+ const isOverlapAfter = analyze.isHorizontal
+ ? almostEqual(Math.min(afterNextLine.x1, afterNextLine.x2), Math.min(currentLine.x1, currentLine.x2)) &&
+ almostEqual(Math.max(afterNextLine.x1, afterNextLine.x2), Math.max(currentLine.x1, currentLine.x2))
+ : almostEqual(Math.min(afterNextLine.y1, afterNextLine.y2), Math.min(currentLine.y1, currentLine.y2)) &&
+ almostEqual(Math.max(afterNextLine.y1, afterNextLine.y2), Math.max(currentLine.y1, currentLine.y2))
+
+ let isOverlap = false
+ if (isOverlapBefore || isOverlapAfter) {
+ const oppLine = isOverlapBefore ? beforePrevLine : afterNextLine
+ const otherGable = baseLines.find(
+ (l) => l !== currentLine && l !== prevLine && l !== nextLine && l !== oppLine && l.attributes.type === LINE_TYPE.WALLLINE.GABLE,
+ )
+ if (!otherGable) {
+ isOverlap = true
+ }
+ }
+
+ console.log('isOverlap : ', isOverlap)
+ if (isOverlap) {
+ const oppLine = isOverlapBefore ? beforePrevLine : afterNextLine
+ const cMidX = (currentLine.x1 + currentLine.x2) / 2
+ const cMidY = (currentLine.y1 + currentLine.y2) / 2
+ const oMidX = (oppLine.x1 + oppLine.x2) / 2
+ const oMidY = (oppLine.y1 + oppLine.y2) / 2
+ const cOffset = currentLine.attributes.offset
+ const oOffset = oppLine.attributes.offset
+ const ridgeVector = { x: Math.sign(clamp01(cMidX - oMidX)), y: Math.sign(clamp01(cMidY - oMidY)) }
+ const ridgePoint = [
+ cMidX + ridgeVector.x * cOffset,
+ cMidY + ridgeVector.y * cOffset,
+ oMidX + -ridgeVector.x * oOffset,
+ oMidY + -ridgeVector.y * oOffset,
+ ]
+ let offset = 0
+ switch (oppLine.attributes.type) {
+ case LINE_TYPE.WALLLINE.HIPANDGABLE:
+ offset = oppLine.attributes.width
+ break
+ case LINE_TYPE.WALLLINE.JERKINHEAD:
+ offset = oppLine.attributes.width / 2
+ break
+ default:
+ break
+ }
+ ridgePoint[2] += ridgeVector.x * offset
+ ridgePoint[3] += ridgeVector.y * offset
+
+ linesAnalysis.push({
+ start: { x: ridgePoint[0], y: ridgePoint[1] },
+ end: { x: ridgePoint[2], y: ridgePoint[3] },
+ left: baseLines.findIndex((line) => line === prevLine),
+ right: baseLines.findIndex((line) => line === nextLine),
+ type: TYPES.RIDGE,
+ degree: 0,
+ })
+ } else {
+ if (beforePrevLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) {
+ const beforeVector = {
+ x: Math.sign(clamp01(beforePrevLine.x1 - beforePrevLine.x2)),
+ y: Math.sign(clamp01(beforePrevLine.y1 - beforePrevLine.y2)),
+ }
+ prevDistance = Math.sqrt(Math.pow(prevLine.x2 - prevLine.x1, 2) + Math.pow(prevLine.y2 - prevLine.y1, 2)) + currentLine.attributes.offset
+ if (beforeVector.x === currentVector.x && beforeVector.y === currentVector.y) {
+ prevDistance = prevDistance - beforePrevLine.attributes.offset
+ } else {
+ prevDistance = prevDistance + beforePrevLine.attributes.offset
+ }
+ } else {
+ prevDistance =
+ Math.sqrt(Math.pow(prevLine.x2 - prevLine.x1, 2) + Math.pow(prevLine.y2 - prevLine.y1, 2)) +
+ currentLine.attributes.offset +
+ beforePrevLine.attributes.offset
+ }
+
+ if (afterNextLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) {
+ const afterVector = {
+ x: Math.sign(clamp01(afterNextLine.x1 - afterNextLine.x2)),
+ y: Math.sign(clamp01(afterNextLine.y1 - afterNextLine.y2)),
+ }
+ nextDistance = Math.sqrt(Math.pow(nextLine.x2 - nextLine.x1, 2) + Math.pow(nextLine.y2 - nextLine.y1, 2)) + currentLine.attributes.offset
+ if (afterVector.x === currentVector.x && afterVector.y === currentVector.y) {
+ nextDistance = nextDistance - afterNextLine.attributes.offset
+ } else {
+ nextDistance = nextDistance + afterNextLine.attributes.offset
+ }
+ } else {
+ nextDistance =
+ Math.sqrt(Math.pow(nextLine.x2 - nextLine.x1, 2) + Math.pow(nextLine.y2 - nextLine.y1, 2)) +
+ currentLine.attributes.offset +
+ afterNextLine.attributes.offset
+ }
+
+ //좌우 선분 의 이전 다음 선이 케라바인경우 둘 중 긴 쪽이 기준선이 된다. 아닌경우 짧은쪽
+ let stdLine
+ let stdFindOppVector
+ if (beforePrevLine.attributes.type === LINE_TYPE.WALLLINE.GABLE && afterNextLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) {
+ if (nextDistance <= prevDistance) {
+ stdLine = prevLine
+ if (prevLineVector.x === 0) {
+ stdFindOppVector = { x: Math.sign(clamp01(prevLine.x1 - nextLine.x1)), y: 0 }
+ } else {
+ stdFindOppVector = { x: 0, y: Math.sign(clamp01(prevLine.y1 - nextLine.y1)) }
+ }
+ } else {
+ stdLine = nextLine
+ if (nextLineVector.x === 0) {
+ stdFindOppVector = { x: Math.sign(clamp01(nextLine.x1 - prevLine.x1)), y: 0 }
+ } else {
+ stdFindOppVector = { x: 0, y: Math.sign(clamp01(nextLine.y1 - prevLine.y1)) }
+ }
+ }
+ } else {
+ if (nextDistance <= prevDistance) {
+ stdLine = nextLine
+ if (nextLineVector.x === 0) {
+ stdFindOppVector = { x: Math.sign(clamp01(nextLine.x1 - prevLine.x1)), y: 0 }
+ } else {
+ stdFindOppVector = { x: 0, y: Math.sign(clamp01(nextLine.y1 - prevLine.y1)) }
+ }
+ } else {
+ stdLine = prevLine
+ if (prevLineVector.x === 0) {
+ stdFindOppVector = { x: Math.sign(clamp01(prevLine.x1 - nextLine.x1)), y: 0 }
+ } else {
+ stdFindOppVector = { x: 0, y: Math.sign(clamp01(prevLine.y1 - nextLine.y1)) }
+ }
+ }
+ }
+ let stdDistance = nextDistance <= prevDistance ? prevDistance : nextDistance
+ const stdAnalyze = analyzeLine(stdLine)
+ let stdPoints = []
+
+ const stdPrevLine = baseLines[(baseLines.indexOf(stdLine) - 1 + baseLines.length) % baseLines.length]
+ const stdNextLine = baseLines[(baseLines.indexOf(stdLine) + 1) % baseLines.length]
+ const stdVector = { x: Math.sign(clamp01(stdLine.x1 - stdLine.x2)), y: Math.sign(clamp01(stdLine.y1 - stdLine.y2)) }
+ const stdPrevVector = { x: Math.sign(clamp01(stdPrevLine.x1 - stdPrevLine.x2)), y: Math.sign(clamp01(stdPrevLine.y1 - stdPrevLine.y2)) }
+ const stdNextVector = { x: Math.sign(clamp01(stdNextLine.x1 - stdNextLine.x2)), y: Math.sign(clamp01(stdNextLine.y1 - stdNextLine.y2)) }
+ let stdAdjustVector = { x: 0, y: 0 }
+
+ if (stdPrevVector.x === stdNextVector.x && stdPrevVector.y === stdNextVector.y) {
+ if (stdAnalyze.isHorizontal) {
+ if (stdVector.x === 1) {
+ if (stdPrevLine.y1 > stdNextLine.y1) {
+ stdPoints.push(stdLine.x1 + stdPrevLine.attributes.offset, stdLine.y1)
+ if (stdNextLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) {
+ stdPoints.push(stdLine.x2 + stdNextLine.attributes.offset, stdLine.y2)
+ } else {
+ stdPoints.push(stdLine.x2, stdLine.y2)
+ }
+ } else {
+ if (stdPrevLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) {
+ stdPoints.push(stdLine.x1 - stdPrevLine.attributes.offset, stdLine.y1)
+ } else {
+ stdPoints.push(stdLine.x1, stdLine.y1)
+ }
+ stdPoints.push(stdLine.x2 - stdNextLine.attributes.offset, stdLine.y2)
+ }
+ } else {
+ if (stdPrevLine.y1 < stdNextLine.y1) {
+ stdPoints.push(stdLine.x1 - stdPrevLine.attributes.offset, stdLine.y1)
+ if (stdNextLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) {
+ stdPoints.push(stdLine.x2 - stdNextLine.attributes.offset, stdLine.y2)
+ } else {
+ stdPoints.push(stdLine.x2, stdLine.y2)
+ }
+ } else {
+ if (stdPrevLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) {
+ stdPoints.push(stdLine.x1 + stdPrevLine.attributes.offset, stdLine.y1)
+ } else {
+ stdPoints.push(stdLine.x1, stdLine.y1)
+ }
+ stdPoints.push(stdLine.x2 + stdNextLine.attributes.offset, stdLine.y2)
+ }
+ }
+ }
+ if (stdAnalyze.isVertical) {
+ if (stdVector.y === 1) {
+ if (stdPrevLine.x1 > stdNextLine.x1) {
+ if (stdPrevLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) {
+ stdPoints.push(stdLine.x1, stdLine.y1 - stdPrevLine.attributes.offset)
+ } else {
+ stdPoints.push(stdLine.x1, stdLine.y1)
+ }
+ stdPoints.push(stdLine.x2, stdLine.y2 - stdNextLine.attributes.offset)
+ } else {
+ stdPoints.push(stdLine.x1, stdLine.y1 + stdPrevLine.attributes.offset)
+ if (stdNextLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) {
+ stdPoints.push(stdLine.x2, stdLine.y2 + stdNextLine.attributes.offset)
+ } else {
+ stdPoints.push(stdLine.x2, stdLine.y2)
+ }
+ }
+ } else {
+ if (stdPrevLine.x1 < stdNextLine.x1) {
+ if (stdPrevLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) {
+ stdPoints.push(stdLine.x1, stdLine.y1 + stdPrevLine.attributes.offset)
+ } else {
+ stdPoints.push(stdLine.x1, stdLine.y1)
+ }
+ stdPoints.push(stdLine.x2, stdLine.y2 + stdNextLine.attributes.offset)
+ } else {
+ stdPoints.push(stdLine.x1, stdLine.y1 - stdPrevLine.attributes.offset)
+ if (stdNextLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) {
+ stdPoints.push(stdLine.x2, stdLine.y2 - stdNextLine.attributes.offset)
+ } else {
+ stdPoints.push(stdLine.x2, stdLine.y2)
+ }
+ }
+ }
+ }
+ console.log('stdAdjustVector', stdAdjustVector)
+ } else {
+ const stdAddPrevVector = { x: Math.sign(clamp01(stdLine.x1 - stdLine.x2)), y: Math.sign(clamp01(stdLine.y1 - stdLine.y2)) }
+ const stdAddNextVector = { x: Math.sign(clamp01(stdLine.x2 - stdLine.x1)), y: Math.sign(clamp01(stdLine.y2 - stdLine.y1)) }
+ console.log('stdAddPrevVector', stdAddPrevVector)
+ stdPoints = [
+ stdLine.x1 + stdAddPrevVector.x * stdPrevLine.attributes.offset,
+ stdLine.y1 + stdAddPrevVector.y * stdPrevLine.attributes.offset,
+ stdLine.x2 + stdAddNextVector.x * stdNextLine.attributes.offset,
+ stdLine.y2 + stdAddNextVector.y * stdNextLine.attributes.offset,
+ ]
+ }
+ /*const checkLine = new fabric.Line([stdLine.x1, stdLine.y1, stdLine.x2, stdLine.y2], {
+ stroke: 'red',
+ strokeWidth: 4,
+ parentId: roofId,
+ name: 'check',
+ })
+ const checkLine2 = new fabric.Line(stdPoints, {
+ stroke: 'green',
+ strokeWidth: 4,
+ parentId: roofId,
+ name: 'check',
+ })
+ canvas.add(checkLine, checkLine2).renderAll()
+*/
+ //기준지붕선의 반대쪽선
+ const oppositeLine = []
+
+ const startX = Math.min(stdLine.x1, stdLine.x2)
+ const endX = Math.max(stdLine.x1, stdLine.x2)
+ const startY = Math.min(stdLine.y1, stdLine.y2)
+ const endY = Math.max(stdLine.y1, stdLine.y2)
+ console.log('stdFindOppVector', stdFindOppVector)
+ baseLines
+ .filter((line) => line !== stdLine && line !== currentLine && line.attributes.type !== LINE_TYPE.WALLLINE.SHED)
+ .filter((line) => {
+ const lineVector = { x: Math.sign(clamp01(line.x1 - line.x2)), y: Math.sign(clamp01(line.y1 - line.y2)) }
+ let oppVector = { x: 0, y: 0 }
+ if (stdVector.x === 0) {
+ oppVector = { x: Math.sign(clamp01(stdLine.x1 - line.x1)), y: 0 }
+ }
+ if (stdVector.y === 0) {
+ oppVector = { x: 0, y: Math.sign(clamp01(stdLine.y1 - line.y1)) }
+ }
+ const rightDirection =
+ (stdVector.x === lineVector.x && stdVector.y !== lineVector.y) || (stdVector.x !== lineVector.x && stdVector.y === lineVector.y)
+ const rightOpp = stdFindOppVector.x === oppVector.x && stdFindOppVector.y === oppVector.y
+ return rightDirection && rightOpp
+ })
+ .forEach((line) => {
+ const lineStartX = Math.min(line.x1, line.x2)
+ const lineEndX = Math.max(line.x1, line.x2)
+ const lineStartY = Math.min(line.y1, line.y2)
+ const lineEndY = Math.max(line.y1, line.y2)
+
+ if (stdAnalyze.isHorizontal) {
+ //full overlap
+ if (lineStartX <= startX && endX <= lineEndX) {
+ oppositeLine.push({ line, distance: Math.abs(lineStartY - startY) })
+ } else if (
+ (startX < lineStartX && lineStartX < endX) ||
+ (startX < lineStartX && lineEndX < endX) ||
+ (lineStartX === startX && lineEndX <= endX) ||
+ (lineEndX === endX && lineStartX <= startX)
+ ) {
+ oppositeLine.push({ line, distance: Math.abs(lineStartY - startY) })
+ }
+ }
+ if (stdAnalyze.isVertical) {
+ //full overlap
+ if (lineStartY <= startY && endY <= lineEndY) {
+ oppositeLine.push({ line, distance: Math.abs(lineStartX - startX) })
+ } else if (
+ (startY < lineStartY && lineStartY < endY) ||
+ (startY < lineEndY && lineEndY < endY) ||
+ (lineStartY === startY && lineEndY <= endY) ||
+ (lineEndY === endY && lineStartY <= startY)
+ ) {
+ oppositeLine.push({ line, distance: Math.abs(lineStartX - startX) })
+ }
+ }
+ })
+ console.log('oppositeLine', oppositeLine)
+ if (oppositeLine.length > 0) {
+ const ridgePoints = []
+ //지붕선 출발 지점 확인을 위한 기준 포인트
+ let ridgeStdPoint = { x: (currentLine.x1 + currentLine.x2) / 2, y: (currentLine.y1 + currentLine.y2) / 2 }
+
+ oppositeLine.sort((a, b) => a.distance - b.distance)
+ oppositeLine.forEach((opposite) => {
+ const oppLine = opposite.line
+ /*const checkOppLine = new fabric.Line([oppLine.x1, oppLine.y1, oppLine.x2, oppLine.y2], {
+ stroke: 'yellow',
+ strokeWidth: 6,
+ parentId: roofId,
+ name: 'check',
+ })
+ canvas.add(checkOppLine)
+ canvas.renderAll()*/
+
+ const oppIndex = baseLines.findIndex((line) => line === oppLine)
+ //마주하는 라인의 이전 다음 라인.
+ const oppPrevLine = baseLines[(oppIndex - 1 + baseLines.length) % baseLines.length]
+ const oppNextLine = baseLines[(oppIndex + 1) % baseLines.length]
+ const oppAnalyze = analyzeLine(oppLine)
+ const oppVector = { x: Math.sign(oppLine.x2 - oppLine.x1), y: Math.sign(oppLine.y2 - oppLine.y1) }
+ let ridgePoint
+
+ const oppPrevVector = { x: Math.sign(oppPrevLine.x1 - oppPrevLine.x2), y: Math.sign(oppPrevLine.y1 - oppPrevLine.y2) }
+ const oppNextVector = { x: Math.sign(oppNextLine.x1 - oppNextLine.x2), y: Math.sign(oppNextLine.y1 - oppNextLine.y2) }
+ if (oppPrevVector.x === oppNextVector.x && oppPrevVector.y === oppNextVector.y) {
+ const addOffsetX1 = oppPrevVector.y * oppPrevLine.attributes.offset
+ const addOffsetY1 = -oppPrevVector.x * oppPrevLine.attributes.offset
+ const addOffsetX2 = oppPrevVector.y * oppNextLine.attributes.offset
+ const addOffsetY2 = -oppPrevVector.x * oppNextLine.attributes.offset
+ ridgePoint = [oppLine.x1 + addOffsetX1, oppLine.y1 + addOffsetY1, oppLine.x2 + addOffsetX2, oppLine.y2 + addOffsetY2]
+ } else {
+ const inPolygonPoint = {
+ x: (oppLine.x1 + oppLine.x2) / 2 + Math.sign(oppNextLine.x2 - oppNextLine.x1),
+ y: (oppLine.y1 + oppLine.y2) / 2 + Math.sign(oppNextLine.y2 - oppNextLine.y1),
+ }
+ if (checkWallPolygon.inPolygon(inPolygonPoint)) {
+ ridgePoint = [
+ oppLine.x1 + -oppVector.x * oppPrevLine.attributes.offset,
+ oppLine.y1 + -oppVector.y * oppPrevLine.attributes.offset,
+ oppLine.x2 + oppVector.x * oppNextLine.attributes.offset,
+ oppLine.y2 + oppVector.y * oppNextLine.attributes.offset,
+ ]
+ } else {
+ ridgePoint = [
+ oppLine.x1 + oppVector.x * oppPrevLine.attributes.offset,
+ oppLine.y1 + oppVector.y * oppPrevLine.attributes.offset,
+ oppLine.x2 + -oppVector.x * oppNextLine.attributes.offset,
+ oppLine.y2 + -oppVector.y * oppNextLine.attributes.offset,
+ ]
+ }
+ }
+ if (stdAnalyze.isHorizontal) {
+ ridgePoint[1] = (stdLine.y1 + oppLine.y1) / 2
+ ridgePoint[3] = (stdLine.y2 + oppLine.y2) / 2
+ }
+ if (stdAnalyze.isVertical) {
+ ridgePoint[0] = (stdLine.x1 + oppLine.x1) / 2
+ ridgePoint[2] = (stdLine.x2 + oppLine.x2) / 2
+ }
+
+ // 지붕선 출발 지점과의 거리를 통해 가까운쪽이 start point로 처리.
+ /*const distRidgeStandard1 = Math.sqrt(Math.pow(ridgeStdPoint.x - ridgePoint[0], 2) + Math.pow(ridgeStdPoint.y - ridgePoint[1], 2))
+ const distRidgeStandard2 = Math.sqrt(Math.pow(ridgeStdPoint.x - ridgePoint[2], 2) + Math.pow(ridgeStdPoint.y - ridgePoint[3], 2))
+ if (distRidgeStandard1 > distRidgeStandard2) {
+ ridgePoint = [ridgePoint[2], ridgePoint[3], ridgePoint[0], ridgePoint[1]]
+ }*/
+
+ if (
+ (oppPrevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES || oppNextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) &&
+ !(oppPrevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES && oppNextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES)
+ ) {
+ const oppLineLength = Math.sqrt(Math.pow(oppLine.x2 - oppLine.x1, 2) + Math.pow(oppLine.y2 - oppLine.y1, 2))
+ const stdLineLength = Math.sqrt(Math.pow(stdLine.x2 - stdLine.x1, 2) + Math.pow(stdLine.y2 - stdLine.y1, 2))
+ //기준선이 반대선보다 길이가 짧을때는 추녀마루에 대한 교점 처리 패스
+ if (stdLineLength >= oppLineLength) {
+ if (oppPrevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) {
+ //처마라인이 아닌 반대쪽부터 마루선을 시작하도록 포인트 변경
+ const oppNextAnalyze = analyzeLine(oppNextLine)
+ if (oppNextAnalyze.isHorizontal) {
+ const dist1 = Math.abs(oppNextLine.y1 - ridgePoint[1])
+ const dist2 = Math.abs(oppNextLine.y1 - ridgePoint[3])
+ if (dist1 > dist2) {
+ ridgePoint = [ridgePoint[2], ridgePoint[3], ridgePoint[0], ridgePoint[1]]
+ }
+ }
+ if (oppNextAnalyze.isVertical) {
+ const dist1 = Math.abs(oppNextLine.x1 - ridgePoint[0])
+ const dist2 = Math.abs(oppNextLine.x1 - ridgePoint[2])
+ if (dist1 > dist2) {
+ ridgePoint = [ridgePoint[2], ridgePoint[3], ridgePoint[0], ridgePoint[1]]
+ }
+ }
+
+ const checkEdge = { vertex1: { x: ridgePoint[0], y: ridgePoint[1] }, vertex2: { x: ridgePoint[2], y: ridgePoint[3] } }
+ const checkVector = getHalfAngleVector(oppLine, oppPrevLine)
+ const checkPoint = {
+ x: oppLine.x1 + (checkVector.x * 10) / 2,
+ y: oppLine.y1 + (checkVector.y * 10) / 2,
+ }
+
+ let hipVector
+ if (checkWallPolygon.inPolygon(checkPoint)) {
+ hipVector = { x: checkVector.x, y: checkVector.y }
+ } else {
+ hipVector = { x: -checkVector.x, y: -checkVector.y }
+ }
+
+ const ridgeVector = { x: Math.sign(ridgePoint[0] - ridgePoint[2]), y: Math.sign(ridgePoint[1] - ridgePoint[3]) }
+ const hipEdge = { vertex1: { x: oppLine.x1, y: oppLine.y1 }, vertex2: { x: oppLine.x1 + hipVector.x, y: oppLine.y1 + hipVector.y } }
+ const intersect = edgesIntersection(hipEdge, checkEdge)
+ if (intersect) {
+ const intersectVector = { x: Math.sign(ridgePoint[0] - intersect.x), y: Math.sign(ridgePoint[1] - intersect.y) }
+ if (ridgeVector.x === intersectVector.x && ridgeVector.y === intersectVector.y) {
+ ridgePoint = [ridgePoint[0], ridgePoint[1], intersect.x, intersect.y]
+ } else {
+ ridgePoint = [ridgePoint[2], ridgePoint[3], ridgePoint[2], ridgePoint[3]]
+ }
+ }
+ }
+ if (oppNextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) {
+ //처마라인이 아닌 반대쪽부터 마루선을 시작하도록 포인트 변경
+ const oppPrevAnalyze = analyzeLine(oppPrevLine)
+ if (oppPrevAnalyze.isHorizontal) {
+ const dist1 = Math.abs(oppPrevLine.y1 - ridgePoint[1])
+ const dist2 = Math.abs(oppPrevLine.y1 - ridgePoint[3])
+ if (dist1 > dist2) {
+ ridgePoint = [ridgePoint[2], ridgePoint[3], ridgePoint[0], ridgePoint[1]]
+ }
+ }
+ if (oppPrevAnalyze.isVertical) {
+ const dist1 = Math.abs(oppPrevLine.x1 - ridgePoint[0])
+ const dist2 = Math.abs(oppPrevLine.x1 - ridgePoint[2])
+ if (dist1 > dist2) {
+ ridgePoint = [ridgePoint[2], ridgePoint[3], ridgePoint[0], ridgePoint[1]]
+ }
+ }
+ const checkEdge = { vertex1: { x: ridgePoint[0], y: ridgePoint[1] }, vertex2: { x: ridgePoint[2], y: ridgePoint[3] } }
+ const checkVector = getHalfAngleVector(oppLine, oppNextLine)
+ const checkPoint = { x: oppLine.x2 + (checkVector.x * 10) / 2, y: oppLine.y2 + (checkVector.y * 10) / 2 }
+
+ let hipVector
+ if (checkWallPolygon.inPolygon(checkPoint)) {
+ hipVector = { x: checkVector.x, y: checkVector.y }
+ } else {
+ hipVector = { x: -checkVector.x, y: -checkVector.y }
+ }
+
+ const ridgeVector = { x: Math.sign(ridgePoint[0] - ridgePoint[2]), y: Math.sign(ridgePoint[1] - ridgePoint[3]) }
+ const hipEdge = { vertex1: { x: oppLine.x2, y: oppLine.y2 }, vertex2: { x: oppLine.x2 + hipVector.x, y: oppLine.y2 + hipVector.y } }
+ const intersect = edgesIntersection(hipEdge, checkEdge)
+ if (intersect) {
+ const intersectVector = { x: Math.sign(ridgePoint[0] - intersect.x), y: Math.sign(ridgePoint[1] - intersect.y) }
+ if (ridgeVector.x === intersectVector.x && ridgeVector.y === intersectVector.y) {
+ ridgePoint = [ridgePoint[0], ridgePoint[1], intersect.x, intersect.y]
+ } else {
+ ridgePoint = [ridgePoint[2], ridgePoint[3], ridgePoint[2], ridgePoint[3]]
+ }
+ }
+ }
+ } else {
+ if (stdPrevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES || stdNextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) {
+ const vectorLine = stdPrevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES ? stdPrevLine : stdNextLine
+ let driveVector = getHalfAngleVector(vectorLine, stdLine)
+ const allPoints = [
+ { x: stdLine.x1, y: stdLine.y1 },
+ { x: stdLine.x2, y: stdLine.y2 },
+ { x: vectorLine.x1, y: vectorLine.y1 },
+ { x: vectorLine.x2, y: vectorLine.y2 },
+ ]
+ let startPoint
+ for (let i = 0; i < allPoints.length; i++) {
+ const point = allPoints[i]
+ for (let j = i + 1; j < allPoints.length; j++) {
+ if (i === j) continue
+ const point2 = allPoints[j]
+ if (almostEqual(point.x, point2.x) && almostEqual(point.y, point2.y)) {
+ startPoint = point2
+ break
+ }
+ }
+ if (startPoint) break
+ }
+
+ const checkDrivePoint = { x: startPoint.x + driveVector.x * 5, y: startPoint.y + driveVector.y * 5 }
+ if (!checkWallPolygon.inPolygon(checkDrivePoint)) {
+ driveVector = { x: -driveVector.x, y: -driveVector.y }
+ }
+
+ const driveEdge = {
+ vertex1: { x: startPoint.x, y: startPoint.y },
+ vertex2: { x: startPoint.x + driveVector.x, y: startPoint.y + driveVector.y },
+ }
+ const ridgeEdge = { vertex1: { x: ridgePoint[0], y: ridgePoint[1] }, vertex2: { x: ridgePoint[2], y: ridgePoint[3] } }
+ const intersect = edgesIntersection(driveEdge, ridgeEdge)
+ const rLength = Math.sqrt(Math.pow(ridgePoint[2] - ridgePoint[0], 2) + Math.pow(ridgePoint[3] - ridgePoint[1], 2))
+ if (intersect) {
+ const length1 = Math.sqrt(Math.pow(intersect.x - ridgePoint[0], 2) + Math.pow(intersect.y - ridgePoint[1], 2))
+ const length2 = Math.sqrt(Math.pow(intersect.x - ridgePoint[2], 2) + Math.pow(intersect.y - ridgePoint[3], 2))
+ if (rLength < length1 && rLength < length2) {
+ if (length1 >= length2) {
+ ridgePoint = [ridgePoint[0], ridgePoint[1], intersect.x, intersect.y]
+ } else {
+ ridgePoint = [ridgePoint[2], ridgePoint[3], intersect.x, intersect.y]
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (stdPrevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES || stdNextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) {
+ const vectorLine = stdPrevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES ? stdPrevLine : stdNextLine
+ let driveVector = getHalfAngleVector(vectorLine, stdLine)
+ const allPoints = [
+ { x: stdLine.x1, y: stdLine.y1 },
+ { x: stdLine.x2, y: stdLine.y2 },
+ { x: vectorLine.x1, y: vectorLine.y1 },
+ { x: vectorLine.x2, y: vectorLine.y2 },
+ ]
+ let startPoint
+ for (let i = 0; i < allPoints.length; i++) {
+ const point = allPoints[i]
+ for (let j = i + 1; j < allPoints.length; j++) {
+ if (i === j) continue
+ const point2 = allPoints[j]
+ if (almostEqual(point.x, point2.x) && almostEqual(point.y, point2.y)) {
+ startPoint = point2
+ break
+ }
+ }
+ if (startPoint) break
+ }
+
+ const checkDrivePoint = { x: startPoint.x + driveVector.x * 5, y: startPoint.y + driveVector.y * 5 }
+ if (!checkWallPolygon.inPolygon(checkDrivePoint)) {
+ driveVector = { x: -driveVector.x, y: -driveVector.y }
+ }
+
+ const driveEdge = {
+ vertex1: { x: startPoint.x, y: startPoint.y },
+ vertex2: { x: startPoint.x + driveVector.x, y: startPoint.y + driveVector.y },
+ }
+ const ridgeEdge = { vertex1: { x: ridgePoint[0], y: ridgePoint[1] }, vertex2: { x: ridgePoint[2], y: ridgePoint[3] } }
+ const intersect = edgesIntersection(driveEdge, ridgeEdge)
+ if (intersect) {
+ if (almostEqual(stdLine.x1, startPoint.x) && stdAnalyze.isHorizontal) {
+ stdPoints[0] = intersect.x
+ }
+ if (almostEqual(stdLine.y1, startPoint.y) && stdAnalyze.isVertical) {
+ stdPoints[1] = intersect.y
+ }
+ if (almostEqual(stdLine.x2, startPoint.x) && stdAnalyze.isHorizontal) {
+ stdPoints[2] = intersect.x
+ }
+ if (almostEqual(stdLine.y2, startPoint.y) && stdAnalyze.isVertical) {
+ stdPoints[3] = intersect.y
+ }
+ }
+ }
+
+ /* const checkLine1 = new fabric.Line(stdPoints, { stroke: 'red', strokeWidth: 4, parentId: roofId, name: 'check' })
+ const checkLine2 = new fabric.Line(ridgePoint, { stroke: 'cyan', strokeWidth: 4, parentId: roofId, name: 'check' })
+ canvas.add(checkLine1, checkLine2).renderAll()*/
+
+ const stdMinX = Math.min(stdPoints[0], stdPoints[2])
+ const stdMaxX = Math.max(stdPoints[0], stdPoints[2])
+ const stdMinY = Math.min(stdPoints[1], stdPoints[3])
+ const stdMaxY = Math.max(stdPoints[1], stdPoints[3])
+
+ const rMinX = Math.min(ridgePoint[0], ridgePoint[2])
+ const rMaxX = Math.max(ridgePoint[0], ridgePoint[2])
+ const rMinY = Math.min(ridgePoint[1], ridgePoint[3])
+ const rMaxY = Math.max(ridgePoint[1], ridgePoint[3])
+
+ const overlapMinX = Math.max(stdMinX, rMinX)
+ const overlapMaxX = Math.min(stdMaxX, rMaxX)
+ const overlapMinY = Math.max(stdMinY, rMinY)
+ const overlapMaxY = Math.min(stdMaxY, rMaxY)
+
+ if (stdAnalyze.isHorizontal) {
+ if (overlapMinX > overlapMaxX) {
+ return
+ }
+ const dist1 = Math.abs(ridgePoint[0] - overlapMinX)
+ const dist2 = Math.abs(ridgePoint[0] - overlapMaxX)
+ if (dist1 < dist2) {
+ ridgePoint = [overlapMinX, ridgePoint[1], overlapMaxX, ridgePoint[3]]
+ } else {
+ ridgePoint = [overlapMaxX, ridgePoint[1], overlapMinX, ridgePoint[3]]
+ }
+
+ const ridgeVector = Math.sign(clamp01(ridgePoint[0] - ridgePoint[2]))
+ if (stdVector.x !== ridgeVector) {
+ ridgePoint = [ridgePoint[2], ridgePoint[3], ridgePoint[0], ridgePoint[1]]
+ }
+ }
+ if (stdAnalyze.isVertical) {
+ if (overlapMinY > overlapMaxY) {
+ return
+ }
+ const dist1 = Math.abs(ridgePoint[1] - overlapMinY)
+ const dist2 = Math.abs(ridgePoint[1] - overlapMaxY)
+ if (dist1 < dist2) {
+ ridgePoint = [ridgePoint[0], overlapMinY, ridgePoint[2], overlapMaxY]
+ } else {
+ ridgePoint = [ridgePoint[0], overlapMaxY, ridgePoint[2], overlapMinY]
+ }
+ const ridgeVector = Math.sign(clamp01(ridgePoint[1] - ridgePoint[3]))
+ if (stdVector.y !== ridgeVector) {
+ ridgePoint = [ridgePoint[2], ridgePoint[3], ridgePoint[0], ridgePoint[1]]
+ }
+ }
+
+ ridgePoints.push({ point: ridgePoint, left: stdLine, right: oppLine })
+ canvas
+ .getObjects()
+ .filter((obj) => obj.name === 'check')
+ .forEach((obj) => canvas.remove(obj))
+ canvas.renderAll()
+ })
+
+ ridgePoints.forEach((r) => {
+ let point = r.point
+ const inPolygon1 =
+ roof.inPolygon({ x: point[0], y: point[1] }) ||
+ roof.lines.find((line) => isPointOnLineNew(line, { x: point[0], y: point[1] })) !== undefined
+ const inPolygon2 =
+ roof.inPolygon({ x: point[2], y: point[3] }) ||
+ roof.lines.find((line) => isPointOnLineNew(line, { x: point[2], y: point[3] })) !== undefined
+
+ //시작점이 지붕 밖에 있을때 vector내의 가까운 지붕선으로 변경
+ if (!inPolygon1) {
+ const checkVector = { x: Math.sign(point[0] - point[2]), y: Math.sign(point[1] - point[3]) }
+ const checkEdge = { vertex1: { x: point[0], y: point[1] }, vertex2: { x: point[2], y: point[3] } }
+ let minDistance = Infinity
+ let correctPoint
+ roof.lines.forEach((line) => {
+ const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
+ const intersect = edgesIntersection(lineEdge, checkEdge)
+ if (intersect) {
+ const distance = Math.sqrt(Math.pow(intersect.x - point[0], 2) + Math.pow(intersect.y - point[1], 2))
+ const intersectVector = { x: Math.sign(point[0] - intersect.x), y: Math.sign(point[1] - intersect.y) }
+ if (distance < minDistance && intersectVector.x === checkVector.x && intersectVector.y === checkVector.y) {
+ minDistance = distance
+ correctPoint = intersect
+ }
+ }
+ })
+ if (correctPoint) {
+ point = [correctPoint.x, correctPoint.y, point[2], point[3]]
+ }
+ }
+
+ //종료점이 지붕밖에 있을때 vector내의 가까운 지붕선으로 변경
+ if (!inPolygon2) {
+ const checkVector = { x: Math.sign(point[2] - point[0]), y: Math.sign(point[3] - point[1]) }
+ const checkEdge = { vertex1: { x: point[2], y: point[3] }, vertex2: { x: point[0], y: point[1] } }
+ let minDistance = Infinity
+ let correctPoint
+ roof.lines.forEach((line) => {
+ const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
+ const intersect = edgesIntersection(lineEdge, checkEdge)
+ if (intersect) {
+ const distance = Math.sqrt(Math.pow(intersect.x - point[2], 2) + Math.pow(intersect.y - point[3], 2))
+ const intersectVector = { x: Math.sign(point[2] - intersect.x), y: Math.sign(point[3] - intersect.y) }
+ if (distance < minDistance && intersectVector.x === checkVector.x && intersectVector.y === checkVector.y) {
+ minDistance = distance
+ correctPoint = intersect
+ }
+ }
+ })
+ if (correctPoint) {
+ point = [point[0], point[1], correctPoint.x, correctPoint.y]
+ }
+ }
+
+ const ridgeLength = Math.sqrt(Math.pow(point[2] - point[0], 2) + Math.pow(point[3] - point[1], 2))
+
+ if (ridgeLength > EPSILON) {
+ linesAnalysis.push({
+ start: { x: point[0], y: point[1] },
+ end: { x: point[2], y: point[3] },
+ left: baseLines.findIndex((line) => line === r.left),
+ right: baseLines.findIndex((line) => line === r.right),
+ type: TYPES.RIDGE,
+ degree: 0,
+ })
+ }
+ })
+ }
+ }
+ }
+ //반절마루 생성불가이므로 지붕선 분기를 해야하는지 확인 후 처리.
+ if (prevLineVector.x === nextLineVector.x && prevLineVector.y === nextLineVector.y) {
+ const analyze = analyzeLine(currentLine)
+ const roofLine = analyze.roofLine
+ /*const checkRoof = new fabric.Line([roofLine.x1, roofLine.y1, roofLine.x2, roofLine.y2], {
+ stroke: 'red',
+ strokeWidth: 4,
+ parentId: roofId,
+ name: 'check',
+ })
+ canvas.add(checkRoof)
+ const checkLine = new fabric.Line([currentLine.x1, currentLine.y1, currentLine.x2, currentLine.y2], {
+ stroke: 'blue',
+ strokeWidth: 4,
+ parentId: roofId,
+ name: 'check',
+ })
+ canvas.add(checkLine)
+ canvas.renderAll()*/
+
+ const checkVector = { x: Math.sign(prevLine.y2 - prevLine.y1), y: Math.sign(prevLine.x1 - prevLine.x2) }
+ const checkEdge = { vertex1: { x: roofLine.x1, y: roofLine.y1 }, vertex2: { x: roofLine.x2, y: roofLine.y2 } }
+ let maxDistance = 0
+ let correctPoint
+ roof.lines
+ .filter((line) => {
+ const roofIndex = roof.lines.indexOf(roofLine)
+ const prevRoofLine = roof.lines[(roofIndex - 1 + roof.lines.length) % roof.lines.length]
+ const nextRoofLine = roof.lines[(roofIndex + 1) % roof.lines.length]
+ return line !== roofLine && line !== prevRoofLine && line !== nextRoofLine
+ })
+ .forEach((line) => {
+ /*const checkLine = new fabric.Line([line.x1, line.y1, line.x2, line.y2], {
+ stroke: 'red',
+ strokeWidth: 4,
+ parentId: roofId,
+ name: 'check',
+ })
+ canvas.add(checkLine).renderAll()*/
+ const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
+ const intersect = edgesIntersection(lineEdge, checkEdge)
+ /* if (intersect) {
+ const checkCircle = new fabric.Circle({
+ left: intersect.x,
+ top: intersect.y,
+ radius: 5,
+ fill: 'blue',
+ parentId: roofId,
+ name: 'check',
+ })
+ console.log('isPointOnLineNew(line, intersect)', isPointOnLineNew(line, intersect))
+ canvas.add(checkCircle).renderAll()
+ }*/
+ if (intersect && isPointOnLineNew(line, intersect)) {
+ intersect.x = almostEqual(intersect.x, roofLine.x1) ? roofLine.x1 : intersect.x
+ intersect.y = almostEqual(intersect.y, roofLine.y1) ? roofLine.y1 : intersect.y
+ const distance = Math.sqrt(Math.pow(intersect.x - roofLine.x1, 2) + Math.pow(intersect.y - roofLine.y1, 2))
+ const vector = { x: Math.sign(intersect.x - roofLine.x1), y: Math.sign(intersect.y - roofLine.y1) }
+ console.log('vector', vector, 'checkVector', checkVector)
+ if (distance > maxDistance && vector.x === checkVector.x && vector.y === checkVector.y) {
+ maxDistance = distance
+ correctPoint = intersect
+ }
+ }
+ })
+ if (correctPoint) {
+ const startPoint =
+ Math.sqrt(Math.pow(correctPoint.x - roofLine.x1, 2) + Math.pow(correctPoint.y - roofLine.y1, 2)) >
+ Math.sqrt(Math.pow(correctPoint.x - roofLine.x2, 2) + Math.pow(correctPoint.y - roofLine.y2, 2))
+ ? { x: roofLine.x1, y: roofLine.y1 }
+ : { x: roofLine.x2, y: roofLine.y2 }
+ linesAnalysis.push({
+ start: startPoint,
+ end: { x: correctPoint.x, y: correctPoint.y },
+ left: baseLines.findIndex((line) => line === prevLine),
+ right: baseLines.findIndex((line) => line === nextLine),
+ type: TYPES.GABLE_LINE,
+ degree: getDegreeByChon(prevLine.attributes.pitch),
+ gableId: baseLines.findIndex((line) => line === currentLine),
+ connectCnt: 0,
+ })
+ }
+ }
+
+ canvas
+ .getObjects()
+ .filter((obj) => obj.name === 'check')
+ .forEach((obj) => canvas.remove(obj))
+ canvas.renderAll()
+ })
+
+ //각 선분의 교차점을 찾아 선을 그린다.
+ const MAX_ITERATIONS = 1000 //무한루프 방지
+ let iterations = 0
+
+ console.log('baseLines', baseLines)
+ console.log('linesAnalysis', linesAnalysis)
+
+ while (linesAnalysis.length > 0 && iterations < MAX_ITERATIONS) {
+ iterations++
+
+ /*linesAnalysis.forEach((line) => {
+ const point = [line.start.x, line.start.y, line.end.x, line.end.y]
+ const checkLine = new fabric.Line(point, {
+ stroke: 'red',
+ strokeWidth: 2,
+ parentId: roofId,
+ name: 'check',
+ })
+ canvas.add(checkLine).renderAll()
+ // canvas.remove(checkLine).renderAll()
+ })*/
+
+ const intersections = []
+ linesAnalysis.forEach((currLine, i) => {
+ let minDistance = Infinity
+ let intersectPoint = null
+ let linePoint = null
+ let partner = null
+
+ /*const checkCLine = new fabric.Line([currLine.start.x, currLine.start.y, currLine.end.x, currLine.end.y], {
+ stroke: 'red',
+ strokeWidth: 4,
+ parentId: roofId,
+ name: 'check',
+ })
+ canvas.add(checkCLine).renderAll()*/
+
+ const cLength = Math.sqrt(Math.pow(currLine.end.x - currLine.start.x, 2) + Math.pow(currLine.end.y - currLine.start.y, 2))
+ //남은 길이가 0이면 무시
+ if (cLength < EPSILON) return
+ // 하단레벨 케라바 라인인데 연결점이 2개 이상이면 무시
+ if (currLine.type === TYPES.GABLE_LINE && currLine.connectCnt > 1) return
+
+ linesAnalysis.forEach((nextLine, j) => {
+ if (i === j) return
+ if (currLine.type === TYPES.GABLE_LINE && nextLine.type === TYPES.GABLE_LINE && currLine.gableId === nextLine.gableId) return
+ if (nextLine.type === TYPES.GABLE_LINE && nextLine.connectCnt > 1) return
+ /*const checkNLine = new fabric.Line([nextLine.start.x, nextLine.start.y, nextLine.end.x, nextLine.end.y], {
+ stroke: 'green',
+ strokeWidth: 4,
+ parentId: roofId,
+ name: 'check',
+ })
+ canvas.add(checkNLine).renderAll()*/
+
+ const intersect = lineIntersection(currLine.start, currLine.end, nextLine.start, nextLine.end, canvas)
+ if (intersect) {
+ const checkCircle = new fabric.Circle({ left: intersect.x, top: intersect.y, radius: 5, fill: 'blue', parentId: roofId, name: 'check' })
+ canvas.add(checkCircle).renderAll()
+ let distance1 = Math.sqrt(Math.pow(intersect.x - currLine.start.x, 2) + Math.pow(intersect.y - currLine.start.y, 2))
+ let distance2 = Math.sqrt(Math.pow(intersect.x - nextLine.start.x, 2) + Math.pow(intersect.y - nextLine.start.y, 2))
+ let point = [currLine.start.x, currLine.start.y, intersect.x, intersect.y]
+ if (distance1 < EPSILON) {
+ distance1 = Math.sqrt(Math.pow(intersect.x - currLine.end.x, 2) + Math.pow(intersect.y - currLine.end.y, 2))
+ distance2 = Math.sqrt(Math.pow(intersect.x - nextLine.end.x, 2) + Math.pow(intersect.y - nextLine.end.y, 2))
+ point = [currLine.end.x, currLine.end.y, intersect.x, intersect.y]
+ }
+ // 하단레벨 케라바 라인인데 남은 길이가 없는 경우 무시
+ if (currLine.type === TYPES.GABLE_LINE && distance1 < EPSILON) return
+ //교점까지의 길이가 최소 길이보다 작을 경우, 최소 길이와 교점을 교체
+ if (distance1 < minDistance && !almostEqual(distance1, minDistance) && !(distance1 < EPSILON && distance2 < EPSILON)) {
+ minDistance = distance1
+ intersectPoint = intersect
+ linePoint = point
+ partner = j
+ } else if (almostEqual(distance1, minDistance)) {
+ const pLine = linesAnalysis[partner]
+ const isSameLine =
+ currLine.left === pLine.left || currLine.left === pLine.right || currLine.right === pLine.left || currLine.right === pLine.right
+ if (!isSameLine) {
+ partner = j
+ }
+ }
+ // canvas.remove(checkCircle).renderAll()
+ }
+ // canvas.remove(checkNLine).renderAll()
+ })
+ // canvas.remove(checkCLine).renderAll()
+ if (intersectPoint) {
+ intersections.push({ index: i, intersect: intersectPoint, linePoint, partner })
+ }
+ })
+ console.log('intersections', intersections)
+ const intersectPoints = intersections
+ .map((item) => item.intersect)
+ .filter((point, index, self) => self.findIndex((p) => almostEqual(p.x, point.x) && almostEqual(p.y, point.y)) === index)
+ console.log('intersectPoints', intersectPoints)
+
+ if (intersectPoints.length === 1 && intersections.length > 1) {
+ intersections[0].partner = intersections[1].index
+ intersections[1].partner = intersections[0].index
+ }
+
+ let newAnalysis = [] //신규 발생 선
+ const processed = new Set() //처리된 선
+
+ for (const { index, intersect, linePoint, partner } of intersections) {
+ const cLine = linesAnalysis[index]
+ const pLine = linesAnalysis[partner]
+
+ //교점이 없거나, 이미 처리된 선분이면 처리하지 않는다.
+ if (!intersect || processed.has(index)) continue
+
+ //교점이 없거나, 교점 선분이 없으면 처리하지 않는다.
+ const pIntersection = intersections.find((p) => p.index === partner)
+ if (!pIntersection || !pIntersection.intersect) continue
+
+ //상호 최단 교점이 아닐경우 처리하지 않는다.
+ if (pIntersection.partner !== index) continue
+
+ //공통선분이 없으면 처리하지 않는다.
+ const isSameLine = cLine.left === pLine.left || cLine.left === pLine.right || cLine.right === pLine.left || cLine.right === pLine.right
+ if (!isSameLine) continue
+
+ //처리 된 라인으로 설정
+ processed.add(index).add(partner)
+
+ let point1 = linePoint
+ let point2 = pIntersection.linePoint
+ let length1 = Math.sqrt(Math.pow(point1[2] - point1[0], 2) + Math.pow(point1[3] - point1[1], 2))
+ let length2 = Math.sqrt(Math.pow(point2[2] - point2[0], 2) + Math.pow(point2[3] - point2[1], 2))
+
+ //gable라인과 붙는경우 length가 0에 가까우면 포인트를 뒤집는다.
+ if (cLine.type === TYPES.GABLE_LINE && length2 < EPSILON) {
+ point2 = [pLine.end.x, pLine.end.y, intersect.x, intersect.y]
+ length2 = Math.sqrt((point2[2] - point2[0]) ** 2 + (point2[3] - point2[1]) ** 2)
+ }
+ if (pLine.type === TYPES.GABLE_LINE && length1 < EPSILON) {
+ point1 = [cLine.end.x, cLine.end.y, intersect.x, intersect.y]
+ length1 = Math.sqrt((point1[2] - point1[0]) ** 2 + (point1[3] - point1[1]) ** 2)
+ }
+
+ if (length1 > 0 && !alreadyPoints(innerLines, point1)) {
+ if (cLine.type === TYPES.HIP) {
+ innerLines.push(drawHipLine(point1, canvas, roof, textMode, null, cLine.degree, cLine.degree))
+ } else if (cLine.type === TYPES.RIDGE) {
+ innerLines.push(drawRidgeLine(point1, canvas, roof, textMode))
+ } else if (cLine.type === TYPES.NEW) {
+ const isDiagonal = Math.abs(point1[0] - point1[2]) >= 1 && Math.abs(point1[1] - point1[3]) >= 1
+ if (isDiagonal) {
+ innerLines.push(drawHipLine(point1, canvas, roof, textMode, null, cLine.degree, cLine.degree))
+ } else {
+ innerLines.push(drawRidgeLine(point1, canvas, roof, textMode))
+ }
+ } else if (cLine.type === TYPES.GABLE_LINE) {
+ if (cLine.degree > 0) {
+ innerLines.push(drawHipLine(point1, canvas, roof, textMode, null, cLine.degree, cLine.degree))
+ } else {
+ innerLines.push(drawRidgeLine(point1, canvas, roof, textMode))
+ }
+ }
+ }
+
+ if (length2 > 0 && !alreadyPoints(innerLines, point2)) {
+ if (pLine.type === TYPES.HIP) {
+ innerLines.push(drawHipLine(point2, canvas, roof, textMode, null, pLine.degree, pLine.degree))
+ } else if (pLine.type === TYPES.RIDGE) {
+ innerLines.push(drawRidgeLine(point2, canvas, roof, textMode))
+ } else if (pLine.type === TYPES.NEW) {
+ const isDiagonal = Math.abs(point2[0] - point2[2]) >= 1 && Math.abs(point2[1] - point2[3]) >= 1
+ if (isDiagonal) {
+ innerLines.push(drawHipLine(point2, canvas, roof, textMode, null, pLine.degree, pLine.degree))
+ } else {
+ innerLines.push(drawRidgeLine(point2, canvas, roof, textMode))
+ }
+ } else if (pLine.type === TYPES.GABLE_LINE) {
+ if (pLine.degree > 0) {
+ innerLines.push(drawHipLine(point2, canvas, roof, textMode, null, pLine.degree, pLine.degree))
+ } else {
+ innerLines.push(drawRidgeLine(point2, canvas, roof, textMode))
+ }
+ }
+ }
+ const otherIs = intersections
+ .filter((is) => is.index !== index && is.index !== partner)
+ .filter((is) => almostEqual(is.intersect.x, intersect.x) && almostEqual(is.intersect.y, intersect.y))
+
+ let relationBaseLines = [cLine.left, cLine.right, pLine.left, pLine.right]
+ if (otherIs.length > 0 && cLine.type !== TYPES.GABLE_LINE && pLine.type !== TYPES.GABLE_LINE) {
+ for (let i = 0; i < otherIs.length; i++) {
+ const oLine = linesAnalysis[otherIs[i].index]
+ if (oLine.type === TYPES.RIDGE && linesAnalysis.length > 3) continue
+ const isSameLine = relationBaseLines.includes(oLine.left) || relationBaseLines.includes(oLine.right)
+ if (isSameLine) {
+ let oPoint = otherIs[i].linePoint
+ let oLength = Math.sqrt(Math.pow(oPoint[2] - oPoint[0], 2) + Math.pow(oPoint[3] - oPoint[1], 2))
+ if (oLength > 0 && !alreadyPoints(innerLines, oPoint)) {
+ if (oLine.type === TYPES.HIP) {
+ innerLines.push(drawHipLine(oPoint, canvas, roof, textMode, null, oLine.degree, oLine.degree))
+ } else if (oLine.type === TYPES.RIDGE) {
+ innerLines.push(drawRidgeLine(oPoint, canvas, roof, textMode))
+ } else if (oLine.type === TYPES.NEW) {
+ const isDiagonal = Math.abs(oPoint[0] - oPoint[2]) >= 1 && Math.abs(oPoint[1] - oPoint[3]) >= 1
+ if (isDiagonal) {
+ innerLines.push(drawHipLine(oPoint, canvas, roof, textMode, null, oLine.degree, oLine.degree))
+ } else {
+ innerLines.push(drawRidgeLine(oPoint, canvas, roof, textMode))
+ }
+ } else if (oLine.type === TYPES.GABLE_LINE) {
+ if (oLine.degree > 0) {
+ innerLines.push(drawHipLine(oPoint, canvas, roof, textMode, null, oLine.degree, oLine.degree))
+ } else {
+ innerLines.push(drawRidgeLine(oPoint, canvas, roof, textMode))
+ }
+ }
+ }
+ processed.add(otherIs[i].index)
+ relationBaseLines.push(oLine.left, oLine.right)
+ }
+ }
+ }
+ if (cLine.type === TYPES.GABLE_LINE || pLine.type === TYPES.GABLE_LINE) {
+ relationBaseLines = [cLine.left, cLine.right, pLine.left, pLine.right]
+ console.log('gableLine newAnalyze start')
+ const gableLine = cLine.type === TYPES.GABLE_LINE ? cLine : pLine
+ gableLine.connectCnt++
+
+ /*const checkLine = new fabric.Line([gableLine.start.x, gableLine.start.y, gableLine.end.x, gableLine.end.y], {
+ stroke: 'red',
+ strokeWidth: 4,
+ parentId: roofId,
+ name: 'check',
+ })
+ const checkCircle = new fabric.Circle({
+ left: intersect.x,
+ top: intersect.y,
+ radius: 5,
+ parentId: roofId,
+ name: 'check',
+ })
+ canvas.add(checkLine, checkCircle)
+ canvas.renderAll()*/
+
+ const uniqueBaseLines = [...new Set(relationBaseLines)]
+ // 연결점에서 새로운 가선분을 생성
+ if (uniqueBaseLines.length > 2) {
+ const linesCounts = new Map()
+ relationBaseLines.forEach((e) => {
+ linesCounts.set(e, (linesCounts.get(e) || 0) + 1)
+ })
+
+ const uniqueLines = Array.from(linesCounts.entries())
+ .filter(([_, count]) => count === 1)
+ .map(([line, _]) => line)
+
+ if (uniqueLines.length === 2) {
+ if (gableLine.connectCnt < 2) {
+ newAnalysis.push({
+ start: { x: intersect.x, y: intersect.y },
+ end: { x: gableLine.end.x, y: gableLine.end.y },
+ left: gableLine.left,
+ right: gableLine.right,
+ type: TYPES.GABLE_LINE,
+ degree: getDegreeByChon(baseLines[gableLine.right].attributes.pitch),
+ gableId: gableLine.gableId,
+ connectCnt: gableLine.connectCnt,
+ })
+ }
+ if (gableLine.connectCnt >= 2) {
+ //가선분 발생만 시키는 더미 생성.
+ newAnalysis.push({
+ start: { x: intersect.x, y: intersect.y },
+ end: { x: intersect.x, y: intersect.y },
+ left: gableLine.gableId,
+ right: gableLine.gableId,
+ type: TYPES.HIP,
+ degree: 0,
+ })
+ }
+ }
+ }
+ console.log('gableLine newAnalyze end')
+ } else {
+ const uniqueBaseLines = [...new Set(relationBaseLines)]
+ // 연결점에서 새로운 가선분을 생성
+ if (uniqueBaseLines.length > 2) {
+ const linesCounts = new Map()
+ relationBaseLines.forEach((e) => {
+ linesCounts.set(e, (linesCounts.get(e) || 0) + 1)
+ })
+
+ const uniqueLines = Array.from(linesCounts.entries())
+ .filter(([_, count]) => count === 1)
+ .map(([line, _]) => line)
+
+ console.log('uniqueLines', uniqueLines)
+ if (uniqueLines.length === 2) {
+ // 두 변의 이등분선 방향 계산
+ // uniqueLines.sort((a, b) => a - b)
+ console.log('uniqueLines : ', uniqueLines)
+ const baseLine1 = baseLines[uniqueLines[0]]
+ const baseLine2 = baseLines[uniqueLines[1]]
+
+ let bisector
+ console.log('isParallel(baseLine1, baseLine2)', isParallel(baseLine1, baseLine2))
+ if (isParallel(baseLine1, baseLine2)) {
+ bisector = getBisectLines(
+ { x1: cLine.start.x, x2: cLine.end.x, y1: cLine.start.y, y2: cLine.end.y },
+ { x1: pLine.start.x, y1: pLine.start.y, x2: pLine.end.x, y2: pLine.end.y },
+ )
+ } else {
+ bisector = getBisectBaseLines(baseLine1, baseLine2, intersect, canvas)
+ }
+
+ //마주하는 지붕선을 찾는다.
+ const intersectionsByRoof = []
+ const checkEdge = {
+ vertex1: { x: intersect.x, y: intersect.y },
+ vertex2: { x: intersect.x + bisector.x, y: intersect.y + bisector.y },
+ }
+ const checkVector = {
+ x: Math.sign(checkEdge.vertex1.x - checkEdge.vertex2.x),
+ y: Math.sign(checkEdge.vertex1.y - checkEdge.vertex2.y),
+ }
+ roof.lines.forEach((line) => {
+ const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
+ const is = edgesIntersection(lineEdge, checkEdge)
+ if (is && isPointOnLineNew(line, is)) {
+ const distance = Math.sqrt((is.x - intersect.x) ** 2 + (is.y - intersect.y) ** 2)
+ const isVector = { x: Math.sign(intersect.x - is.x), y: Math.sign(intersect.y - is.y) }
+ if (isVector.x === checkVector.x && isVector.y === checkVector.y) {
+ intersectionsByRoof.push({ is, distance })
+ }
+ }
+ })
+ intersectionsByRoof.sort((a, b) => a.distance - b.distance)
+ console.log('intersectionsByRoof : ', intersectionsByRoof)
+
+ //기 존재하는 analyze 라인이 있으면 newAnalyzeLine을 생성하지 않고 교체한다.
+ const otherIs = intersections.filter(
+ (is) => !processed.has(is.index) && almostEqual(is.intersect.x, intersect.x) && almostEqual(is.intersect.y, intersect.y),
+ )
+
+ console.log('otherIs', otherIs)
+ if (otherIs.length > 0) {
+ const analyze = linesAnalysis[otherIs[0].index]
+ processed.add(otherIs[0].index)
+ newAnalysis.push({
+ start: { x: intersect.x, y: intersect.y },
+ end: { x: analyze.end.x, y: analyze.end.y },
+ left: analyze.left,
+ right: analyze.right,
+ type: analyze.type,
+ degree: analyze.degree,
+ gableId: analyze.gableId,
+ connectCnt: analyze.connectCnt,
+ })
+ } else if (intersectionsByRoof.length > 0) {
+ //지붕선과 교점이 발생할때
+ let is = intersectionsByRoof[0].is
+ let linePoint = [intersect.x, intersect.y, is.x, is.y]
+ const isDiagonal = Math.abs(is.x - intersect.x) >= 1 && Math.abs(is.y - intersect.y) >= 1
+ const length = Math.sqrt((linePoint[2] - linePoint[0]) ** 2 + (linePoint[3] - linePoint[1]) ** 2)
+
+ const line1 = baseLines[uniqueLines[0]]
+ const line2 = baseLines[uniqueLines[1]]
+ const vector1 = { x: Math.sign(line1.x1 - line1.x2), y: Math.sign(line1.y1 - line1.y2) }
+
+ const prevLine = checkVector.x === vector1.x && checkVector.y === vector1.y ? line2 : line1
+ const nextLine = checkVector.x === vector1.x && checkVector.y === vector1.y ? line1 : line2
+
+ /*const checkPrevLine = new fabric.Line([prevLine.x1, prevLine.y1, prevLine.x2, prevLine.y2], {
+ stroke: 'yellow',
+ strokeWidth: 4,
+ parentId: roofId,
+ name: 'check',
+ })
+ const checkNextLine = new fabric.Line([nextLine.x1, nextLine.y1, nextLine.x2, nextLine.y2], {
+ stroke: 'red',
+ strokeWidth: 4,
+ parentId: roofId,
+ name: 'check',
+ })
+ canvas.add(checkPrevLine, checkNextLine).renderAll()*/
+
+ if (!isDiagonal) {
+ const drivePoint = getRidgeDrivePoint(linePoint, prevLine, nextLine, baseLines)
+ if (drivePoint !== null) {
+ const driveLength = Math.sqrt((drivePoint.x - intersect.x) ** 2 + (drivePoint.y - intersect.y) ** 2)
+ if (driveLength < length) {
+ linePoint = [intersect.x, intersect.y, drivePoint.x, drivePoint.y]
+ }
+ }
+ }
+ const checkNewLine = new fabric.Line(linePoint, { stroke: 'cyan', strokeWidth: 4, parentId: roofId, name: 'check' })
+ canvas.add(checkNewLine).renderAll()
+
+ newAnalysis.push({
+ start: { x: linePoint[0], y: linePoint[1] },
+ end: { x: linePoint[2], y: linePoint[3] },
+ left: uniqueLines[0],
+ right: uniqueLines[1],
+ type: TYPES.NEW,
+ degree: isDiagonal ? cLine.degree : 0,
+ })
+ }
+ }
+ }
+ }
+ canvas
+ .getObjects()
+ .filter((object) => object.name === 'check' || object.name === 'checkAnalysis')
+ .forEach((object) => canvas.remove(object))
+ canvas.renderAll()
+
+ if (newAnalysis.length > 0) break
+ }
+
+ // 처리된 가선분 제외
+ linesAnalysis = newAnalysis.concat(linesAnalysis.filter((_, index) => !processed.has(index)))
+ console.log('lineAnalysis: ', linesAnalysis)
+
+ canvas
+ .getObjects()
+ .filter((object) => object.name === 'check' || object.name === 'checkAnalysis')
+ .forEach((object) => canvas.remove(object))
+ canvas.renderAll()
+ if (newAnalysis.length === 0) break
+ }
+
+ // 가선분 중 처리가 안되어있는 붙어있는 라인에 대한 예외처리.
+ const proceedAnalysis = []
+ linesAnalysis
+ .filter((line) => {
+ const dx = line.end.x - line.start.x
+ const dy = line.end.y - line.start.y
+ const length = Math.sqrt(dx ** 2 + dy ** 2)
+ return length > 0
+ })
+ .forEach((currentLine) => {
+ if (proceedAnalysis.find((p) => p === currentLine)) return
+ //현재와 같으면 제외, 이미 처리된 라인은 제외, 현재와 공통선분이 존재하지 않으면 제외
+ linesAnalysis
+ .filter((line) => {
+ const dx = line.end.x - line.start.x
+ const dy = line.end.y - line.start.y
+ const length = Math.sqrt(dx ** 2 + dy ** 2)
+ return length > 0
+ })
+ .filter(
+ (partnerLine) =>
+ partnerLine !== currentLine &&
+ !proceedAnalysis.find((p) => p === partnerLine) &&
+ (currentLine.left === partnerLine.left ||
+ currentLine.left === partnerLine.right ||
+ partnerLine.left === currentLine.left ||
+ partnerLine.left === currentLine.right),
+ )
+ .forEach((partnerLine) => {
+ const dx1 = currentLine.end.x - currentLine.start.x
+ const dy1 = currentLine.end.y - currentLine.start.y
+ const dx2 = partnerLine.end.x - partnerLine.start.x
+ const dy2 = partnerLine.end.y - partnerLine.start.y
+ const denominator = dy2 * dx1 - dx2 * dy1
+ const isOpposite = dx1 * dx2 + dy1 * dy2 < 0
+ //평행하지 않으면 제외
+ if (Math.abs(denominator) > EPSILON) return
+
+ const currentDegree = getDegreeByChon(baseLines[currentLine.left].attributes.pitch)
+ if (isOpposite) {
+ const points = [currentLine.start.x, currentLine.start.y, partnerLine.start.x, partnerLine.start.y]
+ const length = Math.sqrt((points[0] - points[2]) ** 2 + (points[1] - points[3]) ** 2)
+
+ if (length > 0) {
+ if (currentLine.type === TYPES.HIP) {
+ innerLines.push(drawHipLine(points, canvas, roof, textMode, null, currentDegree, currentDegree))
+ } else if (currentLine.type === TYPES.RIDGE) {
+ innerLines.push(drawRidgeLine(points, canvas, roof, textMode))
+ } else if (currentLine.type === TYPES.NEW) {
+ const isDiagonal = Math.abs(points[0] - points[2]) >= 1 && Math.abs(points[1] - points[3]) >= 1
+ if (isDiagonal && almostEqual(Math.abs(points[0] - points[2]), Math.abs(points[1] - points[3]))) {
+ innerLines.push(drawHipLine(points, canvas, roof, textMode, null, currentDegree, currentDegree))
+ }
+ if (!isDiagonal) {
+ innerLines.push(drawRidgeLine(points, canvas, roof, textMode))
+ }
+ }
+ proceedAnalysis.push(currentLine, partnerLine)
+ }
+ } else {
+ const allPoints = [currentLine.start, currentLine.end, partnerLine.start, partnerLine.end]
+ let points = []
+ allPoints.forEach((point) => {
+ let count = 0
+ allPoints.forEach((p) => {
+ if (almostEqual(point.x, p.x) && almostEqual(point.y, p.y)) count++
+ })
+ if (count === 1) points.push(point)
+ })
+
+ if (points.length === 2) {
+ const length = Math.sqrt((points[0].x - points[1].x) ** 2 + (points[0].y - points[1].y) ** 2)
+ if (length < EPSILON) return
+ const isDiagonal = Math.abs(points[0].x - points[1].x) >= 1 && Math.abs(points[0].y - points[1].y) >= 1
+ if (isDiagonal) {
+ innerLines.push(
+ drawHipLine([points[0].x, points[0].y, points[1].x, points[1].y], canvas, roof, textMode, null, currentDegree, currentDegree),
+ )
+ } else {
+ innerLines.push(drawRidgeLine([points[0].x, points[0].y, points[1].x, points[1].y], canvas, roof, textMode))
+ }
+ proceedAnalysis.push(currentLine, partnerLine)
+ }
+ }
+ })
+ })
+ linesAnalysis = linesAnalysis.filter((line) => !proceedAnalysis.find((p) => p === line))
+
+ // 최종 라인중 벽에서 시작해서 벽에서 끝나는 라인이 남을 경우(벽취함)
+ if (linesAnalysis.length > 0) {
+ linesAnalysis
+ .filter((line) => {
+ const dx = line.end.x - line.start.x
+ const dy = line.end.y - line.start.y
+ const length = Math.sqrt(dx ** 2 + dy ** 2)
+ return length > 0
+ })
+ .forEach((line) => {
+ const startOnLine = roof.lines.find((l) => isPointOnLineNew(l, line.start))
+ const endOnLine = roof.lines.find((l) => isPointOnLineNew(l, line.end))
+ console.log('startOnLine, endOnLine: ', startOnLine, endOnLine)
+ const allLinesPoints = []
+ innerLines.forEach((innerLine) => {
+ allLinesPoints.push({ x: innerLine.x1, y: innerLine.y1 }, { x: innerLine.x2, y: innerLine.y2 })
+ })
+
+ if (
+ allLinesPoints.filter((p) => almostEqual(p.x, line.start.x) && almostEqual(p.y, line.start.y)).length < 3 &&
+ allLinesPoints.filter((p) => almostEqual(p.x, line.end.x) && almostEqual(p.y, line.end.y)).length === 0
+ ) {
+ if (startOnLine && endOnLine) {
+ if (line.degree === 0) {
+ innerLines.push(drawRoofLine([line.start.x, line.start.y, line.end.x, line.end.y], canvas, roof, textMode))
+ } else {
+ innerLines.push(
+ drawHipLine([line.start.x, line.start.y, line.end.x, line.end.y], canvas, roof, textMode, null, line.degree, line.degree),
+ )
+ }
+ }
+ }
+ })
+ }
+
+ //하단 지붕 라인처리
+ const downRoofLines = []
+ baseLines.forEach((baseLine, index) => {
+ const nextLine = baseLines[(index + 1) % baseLines.length]
+ const prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length]
+
+ const prevLineVector = { x: Math.sign(prevLine.x1 - prevLine.x2), y: Math.sign(prevLine.y1 - prevLine.y2) }
+ const nextLineVector = { x: Math.sign(nextLine.x1 - nextLine.x2), y: Math.sign(nextLine.y1 - nextLine.y2) }
+
+ //반절마루 생성불가이므로 지붕선 분기를 해야하는지 확인 후 처리.
+ if (
+ prevLineVector.x === nextLineVector.x &&
+ prevLineVector.y === nextLineVector.y &&
+ (prevLine.attributes.type === LINE_TYPE.WALLLINE.GABLE || nextLine.attributes.type === LINE_TYPE.WALLLINE.GABLE)
+ ) {
+ downRoofLines.push(index)
+ }
+ })
+
+ //지붕선에 따라 라인추가 작업 처리.
+ const innerLinesPoints = []
+ innerLines.forEach((line) => {
+ const hasCoord1 = innerLinesPoints.find((p) => p.x === line.x1 && p.y === line.y1)
+ const hasCoord2 = innerLinesPoints.find((p) => p.x === line.x2 && p.y === line.y2)
+ if (!hasCoord1) innerLinesPoints.push({ x: line.x1, y: line.y1 })
+ if (!hasCoord2) innerLinesPoints.push({ x: line.x2, y: line.y2 })
+ })
+ roof.lines.forEach((currentLine, index) => {
+ const prevLine = roof.lines[(index - 1 + roof.lines.length) % roof.lines.length]
+ const nextLine = roof.lines[(index + 1) % roof.lines.length]
+ const prevDegree = getDegreeByChon(prevLine.attributes.pitch)
+ const nextDegree = getDegreeByChon(nextLine.attributes.pitch)
+ console.log('prevDegree, nextDegree: ', prevDegree, nextDegree)
+
+ const splitPoint = []
+ let hasOverlapLine = false
+ const minX = Math.min(currentLine.x1, currentLine.x2)
+ const maxX = Math.max(currentLine.x1, currentLine.x2)
+ const minY = Math.min(currentLine.y1, currentLine.y2)
+ const maxY = Math.max(currentLine.y1, currentLine.y2)
+ innerLines.forEach((innerLine) => {
+ const innerLineMinX = Math.min(innerLine.x1, innerLine.x2)
+ const innerLineMaxX = Math.max(innerLine.x1, innerLine.x2)
+ const innerLineMinY = Math.min(innerLine.y1, innerLine.y2)
+ const innerLineMaxY = Math.max(innerLine.y1, innerLine.y2)
+ if (innerLineMinX <= minX && innerLineMaxX >= maxX && innerLineMinY <= minY && innerLineMaxY >= maxY) {
+ hasOverlapLine = true
+ }
+ if (minX <= innerLineMinX && maxX >= innerLineMaxX && minY <= innerLineMinY && maxY >= innerLineMaxY) {
+ hasOverlapLine = true
+ }
+ })
+ if (hasOverlapLine) return
+
+ innerLinesPoints.forEach((point) => {
+ if (
+ isPointOnLineNew(currentLine, point) &&
+ !(almostEqual(currentLine.x1, point.x) && almostEqual(currentLine.y1, point.y)) &&
+ !(almostEqual(currentLine.x2, point.x) && almostEqual(currentLine.y2, point.y))
+ ) {
+ const distance = Math.sqrt((point.x - currentLine.x1) ** 2 + (point.y - currentLine.y1) ** 2)
+ splitPoint.push({ point, distance })
+ }
+ })
+ if (splitPoint.length > 0) {
+ splitPoint.sort((a, b) => a[1] - b[1])
+ let startPoint = { x: currentLine.x1, y: currentLine.y1 }
+ for (let i = 0; i < splitPoint.length; i++) {
+ const point = splitPoint[i].point
+ if (i === 0) {
+ innerLines.push(drawHipLine([startPoint.x, startPoint.y, point.x, point.y], canvas, roof, textMode, null, prevDegree, prevDegree))
+ } else {
+ innerLines.push(drawRoofLine([startPoint.x, startPoint.y, point.x, point.y], canvas, roof, textMode))
+ }
+ startPoint = point
+ }
+ innerLines.push(drawHipLine([startPoint.x, startPoint.y, currentLine.x2, currentLine.y2], canvas, roof, textMode, null, nextDegree, nextDegree))
+ } else {
+ innerLines.push(drawRoofLine([currentLine.x1, currentLine.y1, currentLine.x2, currentLine.y2], canvas, roof, textMode))
+ }
+ })
+
+ //지붕 innerLines에 추가.
+ roof.innerLines = innerLines
+
+ canvas
+ .getObjects()
+ .filter((object) => object.name === 'check')
+ .forEach((object) => canvas.remove(object))
+ canvas.renderAll()
+ //return
+ /*while (linesAnalysis.length > 0 && iterations < MAX_ITERATIONS) {
+ iterations++
+
+ linesAnalysis.forEach((line) => {
+ const point = [line.start.x, line.start.y, line.end.x, line.end.y]
+ const checkLine = new fabric.Line(point, {
+ stroke: 'red',
+ strokeWidth: 2,
+ parentId: roofId,
+ name: 'check',
+ })
+ canvas.add(checkLine)
+ canvas.renderAll()
+ })
+ // 각 가선분의 최단 교점 찾기
+ const intersections = []
+ for (let i = 0; i < linesAnalysis.length; i++) {
+ let minDistance = Infinity //최단거리
+ let intersectPoint = null //교점 좌표
+ let partners = new Set() //교점 선분의 index
+
+ const lineI = linesAnalysis[i]
+ const lengthI = Math.sqrt(Math.pow(lineI.end.x - lineI.start.x, 2) + Math.pow(lineI.end.y - lineI.start.y, 2))
+ console.log('lengthI', lengthI)
+ const checkLineI = new fabric.Line([lineI.start.x, lineI.start.y, lineI.end.x, lineI.end.y], {
+ stroke: 'blue',
+ strokeWidth: 2,
+ parentId: roofId,
+ name: 'check',
+ })
+ canvas.add(checkLineI).renderAll()
+
+ if (lineI.type === TYPES.GABLE_LINE && lineI.connectCnt > 1) continue
+
+ if (lengthI > EPSILON) {
+ const otherLines = linesAnalysis.filter((j) => j !== lineI)
+ const zeroLines = linesAnalysis.filter((j) => Math.sqrt(Math.pow(j.end.x - j.start.x, 2) + Math.pow(j.end.y - j.start.y, 2)) < EPSILON)
+ if (otherLines.length === zeroLines.length) {
+ zeroLines.forEach((j) => {
+ const jIndex = linesAnalysis.indexOf(j)
+ if (isPointOnLineNew({ x1: lineI.start.x, y1: lineI.start.y, x2: lineI.end.x, y2: lineI.end.y }, { x: j.start.x, y: j.start.y })) {
+ const distance = Math.sqrt(Math.pow(j.start.x - lineI.start.x, 2) + Math.pow(j.start.y - lineI.start.y, 2))
+ if (distance < minDistance) {
+ minDistance = distance
+ intersectPoint = { x: j.start.x, y: j.start.y }
+ partners = new Set([jIndex])
+ } else if (almostEqual(distance, minDistance)) {
+ partners.add(jIndex)
+ }
+ }
+ })
+ if (intersectPoint) {
+ intersections.push({ index: i, intersectPoint, partners, distance: minDistance })
+ partners.forEach((j) => {
+ const p = new Set([i])
+ intersections.push({ index: j, intersectPoint, partners: p, distance: 0 })
+ })
+ continue
+ }
+ }
+ }
+
+ for (let j = 0; j < linesAnalysis.length; j++) {
+ const lineJ = linesAnalysis[j]
+ if (lineI === lineJ) continue
+ if (lineI.type === TYPES.GABLE_LINE && lineJ.type === TYPES.GABLE_LINE && i.gableId === lineJ.gableId) continue
+ if (lineJ.type === TYPES.GABLE_LINE && lineJ.connectCnt > 1) continue
+
+ const intersection = lineIntersection(lineI.start, lineI.end, lineJ.start, lineJ.end, canvas)
+ if (intersection) {
+ const distance = Math.sqrt((intersection.x - lineI.start.x) ** 2 + (intersection.y - lineI.start.y) ** 2)
+ const distance2 = Math.sqrt((intersection.x - lineJ.start.x) ** 2 + (intersection.y - lineJ.start.y) ** 2)
+ if (lineI.type === TYPES.GABLE_LINE && distance < EPSILON) continue
+ if (distance < minDistance && !almostEqual(distance, minDistance) && !(distance < EPSILON && distance2 < EPSILON)) {
+ minDistance = distance
+ intersectPoint = intersection
+ partners = new Set([j])
+ } else if (almostEqual(distance, minDistance)) {
+ partners.add(j)
+ }
+ }
+ }
+ if (intersectPoint) {
+ intersections.push({ index: i, intersectPoint, partners, distance: minDistance })
+ }
+ canvas.remove(checkLineI).renderAll()
+ }
+
+ // 제일 가까운 교점부터 처리
+ intersections.sort((a, b) => a.distance - b.distance)
+
+ console.log('intersections', intersections)
+ // 교점에 대한 적합 여부 판단 및 처리.
+ let newAnalysis = [] //신규 발생 선
+ const processed = new Set() //처리된 선
+ for (const { index, intersectPoint, partners } of intersections) {
+ canvas
+ .getObjects()
+ .filter((object) => object.name === 'check')
+ .forEach((object) => canvas.remove(object))
+ canvas.renderAll()
+ let isProceed = false
+ for (const pIndex of partners) {
+ console.log('pIndex : ', pIndex)
+ const check1 = linesAnalysis[index]
+ const checkLine1 = new fabric.Line([check1.start.x, check1.start.y, check1.end.x, check1.end.y], {
+ stroke: 'red',
+ strokeWidth: 2,
+ parentId: roofId,
+ name: 'check',
+ })
+ const checkCircle1 = new fabric.Circle({ left: check1.start.x, top: check1.start.y, radius: 5, fill: 'red', parentId: roofId, name: 'check' })
+ const checkCircle2 = new fabric.Circle({ left: check1.end.x, top: check1.end.y, radius: 5, fill: 'green', parentId: roofId, name: 'check' })
+ canvas.add(checkLine1, checkCircle1, checkCircle2)
+ canvas.renderAll()
+
+ const check2 = linesAnalysis[pIndex]
+ const checkLine2 = new fabric.Line([check2.start.x, check2.start.y, check2.end.x, check2.end.y], {
+ stroke: 'green',
+ strokeWidth: 2,
+ parentId: roofId,
+ name: 'check',
+ })
+
+ const checkCircle3 = new fabric.Circle({ left: check2.start.x, top: check2.start.y, radius: 5, fill: 'red', parentId: roofId, name: 'check' })
+ const checkCircle4 = new fabric.Circle({ left: check2.end.x, top: check2.end.y, radius: 5, fill: 'green', parentId: roofId, name: 'check' })
+ canvas.add(checkLine2, checkCircle3, checkCircle4)
+ canvas.renderAll()
+
+ console.log('!intersectPoint || processed.has(index)', !intersectPoint, processed.has(index))
+ //교점이 없거나, 이미 처리된 선분이면 처리하지 않는다.
+ if (!intersectPoint || processed.has(index)) continue
+
+ const partner = intersections.find((p) => p.index === pIndex)
+ //교점이 없거나, 교점 선분이 없으면 처리하지 않는다.
+ if (!partner || !partner.intersectPoint) continue
+
+ //상호 최단 교점 여부 확인.
+ if (partner.partners.has(index)) {
+ const line1 = linesAnalysis[index]
+ const line2 = linesAnalysis[pIndex]
+
+ //좌,우 선 중 공통 선 존재 확인.
+ const isSameLine = line1.left === line2.left || line1.left === line2.right || line1.right === line2.left || line1.right === line2.right
+ if (isSameLine) {
+ // 현재 선이 처리 되었음을 표기
+ let point1 = [line1.start.x, line1.start.y, intersectPoint.x, intersectPoint.y]
+ let point2 = [line2.start.x, line2.start.y, intersectPoint.x, intersectPoint.y]
+ let length1 = Math.sqrt((point1[2] - point1[0]) ** 2 + (point1[3] - point1[1]) ** 2)
+ let length2 = Math.sqrt((point2[2] - point2[0]) ** 2 + (point2[3] - point2[1]) ** 2)
+ if (length1 < EPSILON && length2 < EPSILON) continue
+ isProceed = true
+
+ //gable라인과 붙는경우 length가 0에 가까우면 포인트를 뒤집는다.
+ if (line1.type === TYPES.GABLE_LINE && length2 < EPSILON) {
+ point2 = [line2.end.x, line2.end.y, intersectPoint.x, intersectPoint.y]
+ length2 = Math.sqrt((point2[2] - point2[0]) ** 2 + (point2[3] - point2[1]) ** 2)
+ }
+ if (line2.type === TYPES.GABLE_LINE && length1 < EPSILON) {
+ point1 = [line1.end.x, line1.end.y, intersectPoint.x, intersectPoint.y]
+ length1 = Math.sqrt((point1[2] - point1[0]) ** 2 + (point1[3] - point1[1]) ** 2)
+ }
+
+ if (length1 > 0 && !alreadyPoints(innerLines, point1)) {
+ if (line1.type === TYPES.HIP) {
+ innerLines.push(drawHipLine(point1, canvas, roof, textMode, null, line1.degree, line1.degree))
+ } else if (line1.type === TYPES.RIDGE) {
+ innerLines.push(drawRidgeLine(point1, canvas, roof, textMode))
+ } else if (line1.type === TYPES.NEW) {
+ const isDiagonal = Math.abs(point1[0] - point1[2]) >= 1 && Math.abs(point1[1] - point1[3]) >= 1
+ if (isDiagonal) {
+ innerLines.push(drawHipLine(point1, canvas, roof, textMode, null, line1.degree, line1.degree))
+ } else {
+ innerLines.push(drawRidgeLine(point1, canvas, roof, textMode))
+ }
+ } else if (line1.type === TYPES.GABLE_LINE) {
+ if (line1.degree > 0) {
+ innerLines.push(drawHipLine(point1, canvas, roof, textMode, null, line1.degree, line1.degree))
+ } else {
+ innerLines.push(drawRidgeLine(point1, canvas, roof, textMode))
+ }
+ }
+ }
+
+ if (length2 > 0 && !alreadyPoints(innerLines, point2)) {
+ if (line2.type === TYPES.HIP) {
+ innerLines.push(drawHipLine(point2, canvas, roof, textMode, null, line2.degree, line2.degree))
+ } else if (line2.type === TYPES.RIDGE) {
+ innerLines.push(drawRidgeLine(point2, canvas, roof, textMode))
+ } else if (line2.type === TYPES.NEW) {
+ const isDiagonal = Math.abs(point2[0] - point2[2]) >= 1 && Math.abs(point2[1] - point2[3]) >= 1
+ if (isDiagonal) {
+ innerLines.push(drawHipLine(point2, canvas, roof, textMode, null, line2.degree, line2.degree))
+ } else {
+ innerLines.push(drawRidgeLine(point2, canvas, roof, textMode))
+ }
+ } else if (line2.type === TYPES.GABLE_LINE) {
+ if (line2.degree > 0) {
+ innerLines.push(drawHipLine(point2, canvas, roof, textMode, null, line2.degree, line2.degree))
+ } else {
+ innerLines.push(drawRidgeLine(point2, canvas, roof, textMode))
+ }
+ }
+ }
+
+ if (line1.type === TYPES.GABLE_LINE || line2.type === TYPES.GABLE_LINE) {
+ console.log('gableLine newAnalyze start')
+ const gableLine = line1.type === TYPES.GABLE_LINE ? line1 : line2
+ gableLine.connectCnt++
+
+ const checkLine = new fabric.Line([gableLine.start.x, gableLine.start.y, gableLine.end.x, gableLine.end.y], {
+ stroke: 'red',
+ strokeWidth: 4,
+ parentId: roofId,
+ name: 'check',
+ })
+ const checkCircle = new fabric.Circle({
+ left: intersectPoint.x,
+ top: intersectPoint.y,
+ radius: 5,
+ parentId: roofId,
+ name: 'check',
+ })
+ canvas.add(checkLine, checkCircle)
+ canvas.renderAll()
+
+ const relationBaseLines = [line1.left, line1.right, line2.left, line2.right]
+ const uniqueBaseLines = [...new Set(relationBaseLines)]
+ if (uniqueBaseLines.length === 3) {
+ const linesCounts = new Map()
+ relationBaseLines.forEach((e) => {
+ linesCounts.set(e, (linesCounts.get(e) || 0) + 1)
+ })
+ const uniqueLines = Array.from(linesCounts.entries())
+ .filter(([_, count]) => count === 1)
+ .map(([line, _]) => line)
+
+ if (uniqueLines.length === 2) {
+ if (gableLine.connectCnt < 2) {
+ newAnalysis.push({
+ start: { x: intersectPoint.x, y: intersectPoint.y },
+ end: { x: gableLine.end.x, y: gableLine.end.y },
+ left: gableLine.left,
+ right: gableLine.right,
+ type: TYPES.GABLE_LINE,
+ degree: getDegreeByChon(baseLines[gableLine.right].attributes.pitch),
+ gableId: gableLine.gableId,
+ connectCnt: gableLine.connectCnt,
+ })
+ }
+ if (gableLine.connectCnt >= 2) {
+ //가선분 발생만 시키는 더미 생성.
+ newAnalysis.push({
+ start: { x: intersectPoint.x, y: intersectPoint.y },
+ end: { x: intersectPoint.x, y: intersectPoint.y },
+ left: gableLine.gableId,
+ right: gableLine.gableId,
+ type: TYPES.HIP,
+ degree: 0,
+ })
+ }
+ }
+ }
+ console.log('gableLine newAnalyze end')
+ } else {
+ // 연결점에서 새로운 가선분을 생성
+ const relationBaseLines = [line1.left, line1.right, line2.left, line2.right]
+ const uniqueBaseLines = [...new Set(relationBaseLines)]
+ if (uniqueBaseLines.length === 3) {
+ const linesCounts = new Map()
+ relationBaseLines.forEach((e) => {
+ linesCounts.set(e, (linesCounts.get(e) || 0) + 1)
+ })
+
+ const uniqueLines = Array.from(linesCounts.entries())
+ .filter(([_, count]) => count === 1)
+ .map(([line, _]) => line)
+
+ if (uniqueLines.length === 2) {
+ // 두 변의 이등분선 방향 계산
+ // uniqueLines.sort((a, b) => a - b)
+ console.log('uniqueLines : ', uniqueLines)
+ const baseLine1 = baseLines[uniqueLines[0]]
+ const baseLine2 = baseLines[uniqueLines[1]]
+
+ const checkLine1 = new fabric.Line([baseLine1.x1, baseLine1.y1, baseLine1.x2, baseLine1.y2], {
+ stroke: 'yellow',
+ strokeWidth: 4,
+ parentId: roofId,
+ name: 'check',
+ })
+ const checkLine2 = new fabric.Line([baseLine2.x1, baseLine2.y1, baseLine2.x2, baseLine2.y2], {
+ stroke: 'blue',
+ strokeWidth: 4,
+ parentId: roofId,
+ name: 'check',
+ })
+ canvas.add(checkLine1, checkLine2)
+ canvas.renderAll()
+ let bisector
+ console.log('isParallel(baseLine1, baseLine2)', isParallel(baseLine1, baseLine2))
+ if (isParallel(baseLine1, baseLine2)) {
+ bisector = getBisectLines(
+ { x1: line1.start.x, x2: line1.end.x, y1: line1.start.y, y2: line1.end.y },
+ { x1: line2.start.x, y1: line2.start.y, x2: line2.end.x, y2: line2.end.y },
+ )
+ } else {
+ //이등분선
+ bisector = getBisectBaseLines(baseLine1, baseLine2, intersectPoint, canvas)
+ }
+
+ //마주하는 지붕선을 찾는다.
+ const intersectionsByRoof = []
+ const checkEdge = {
+ vertex1: { x: intersectPoint.x, y: intersectPoint.y },
+ vertex2: { x: intersectPoint.x + bisector.x, y: intersectPoint.y + bisector.y },
+ }
+ const checkVector = {
+ x: Math.sign(checkEdge.vertex1.x - checkEdge.vertex2.x),
+ y: Math.sign(checkEdge.vertex1.y - checkEdge.vertex2.y),
+ }
+ roof.lines.forEach((line) => {
+ const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
+ const is = edgesIntersection(lineEdge, checkEdge)
+ if (is && isPointOnLineNew(line, is)) {
+ const distance = Math.sqrt((is.x - intersectPoint.x) ** 2 + (is.y - intersectPoint.y) ** 2)
+ const isVector = { x: Math.sign(intersectPoint.x - is.x), y: Math.sign(intersectPoint.y - is.y) }
+ if (isVector.x === checkVector.x && isVector.y === checkVector.y) {
+ intersectionsByRoof.push({ is, distance })
+ }
+ }
+ })
+ intersectionsByRoof.sort((a, b) => a.distance - b.distance)
+ //지붕 선과의 교점이 존재 할때
+ if (intersectionsByRoof.length > 0) {
+ let is = intersectionsByRoof[0].is
+ let linePoint = [intersectPoint.x, intersectPoint.y, is.x, is.y]
+ const isDiagonal = Math.abs(is.x - intersectPoint.x) >= 1 && Math.abs(is.y - intersectPoint.y) >= 1
+ const length = Math.sqrt((linePoint[2] - linePoint[0]) ** 2 + (linePoint[3] - linePoint[1]) ** 2)
+ if (!isDiagonal) {
+ const line1 = baseLines[uniqueLines[0]]
+ const line2 = baseLines[uniqueLines[1]]
+ const vector1 = { x: Math.sign(line1.x1 - line1.x2), y: Math.sign(line1.y1 - line1.y2) }
+
+ const prevLine = checkVector.x === vector1.x && checkVector.y === vector1.y ? line2 : line1
+ const nextLine = checkVector.x === vector1.x && checkVector.y === vector1.y ? line1 : line2
+ const drivePoint = getRidgeDrivePoint(linePoint, prevLine, nextLine, baseLines)
+ if (drivePoint !== null) {
+ const driveLength = Math.sqrt((drivePoint.x - intersectPoint.x) ** 2 + (drivePoint.y - intersectPoint.y) ** 2)
+
+ if (driveLength < length) {
+ linePoint = [intersectPoint.x, intersectPoint.y, drivePoint.x, drivePoint.y]
+ }
+ }
+ }
+ const checkNewLine = new fabric.Line(linePoint, { stroke: 'cyan', strokeWidth: 4, parentId: roofId, name: 'check' })
+ canvas.add(checkNewLine).renderAll()
+
+ newAnalysis.push({
+ start: { x: linePoint[0], y: linePoint[1] },
+ end: { x: linePoint[2], y: linePoint[3] },
+ left: uniqueLines[0],
+ right: uniqueLines[1],
+ type: TYPES.NEW,
+ degree: isDiagonal ? line1.degree : 0,
+ })
+ }
+ }
+ canvas
+ .getObjects()
+ .filter((object) => object.name === 'check')
+ .forEach((object) => canvas.remove(object))
+ canvas.renderAll()
+ }
+ }
+ processed.add(pIndex)
+ }
+ }
+ canvas
+ .getObjects()
+ .filter((object) => object.name === 'check')
+ .forEach((object) => canvas.remove(object))
+ canvas.renderAll()
+ }
+ if (isProceed) {
+ processed.add(index)
+ break
+ }
+ }
+ // 처리된 가선분 제외
+ linesAnalysis = newAnalysis.concat(linesAnalysis.filter((_, index) => !processed.has(index)))
+ console.log('lineAnalysis: ', linesAnalysis)
+
+ canvas
+ .getObjects()
+ .filter((object) => object.name === 'check' || object.name === 'checkAnalysis')
+ .forEach((object) => canvas.remove(object))
+ canvas.renderAll()
+ // 새로운 가선분이 없을때 종료
+ console.log('newAnalysis.length : ', newAnalysis.length)
+ if (newAnalysis.length === 0) break
+ }
+ console.log('lineAnalysis: end ', linesAnalysis)*/
+
+ // 가선분 중 처리가 안되어있는 붙어있는 라인에 대한 예외처리.
+ /* const proceedAnalysis = []
+ linesAnalysis
+ .filter((line) => {
+ const dx = line.end.x - line.start.x
+ const dy = line.end.y - line.start.y
+ const length = Math.sqrt(dx ** 2 + dy ** 2)
+ return length > 0
+ })
+ .forEach((currentLine) => {
+ if (proceedAnalysis.find((p) => p === currentLine)) return
+ //현재와 같으면 제외, 이미 처리된 라인은 제외, 현재와 공통선분이 존재하지 않으면 제외
+ linesAnalysis
+ .filter((line) => {
+ const dx = line.end.x - line.start.x
+ const dy = line.end.y - line.start.y
+ const length = Math.sqrt(dx ** 2 + dy ** 2)
+ return length > 0
+ })
+ .filter(
+ (partnerLine) =>
+ partnerLine !== currentLine &&
+ !proceedAnalysis.find((p) => p === partnerLine) &&
+ (currentLine.left === partnerLine.left ||
+ currentLine.left === partnerLine.right ||
+ partnerLine.left === currentLine.left ||
+ partnerLine.left === currentLine.right),
+ )
+ .forEach((partnerLine) => {
+ const dx1 = currentLine.end.x - currentLine.start.x
+ const dy1 = currentLine.end.y - currentLine.start.y
+ const dx2 = partnerLine.end.x - partnerLine.start.x
+ const dy2 = partnerLine.end.y - partnerLine.start.y
+ const denominator = dy2 * dx1 - dx2 * dy1
+ const isOpposite = dx1 * dx2 + dy1 * dy2 < 0
+ //평행하지 않으면 제외
+ if (Math.abs(denominator) > EPSILON) return
+
+ const currentDegree = getDegreeByChon(baseLines[currentLine.left].attributes.pitch)
+ if (isOpposite) {
+ const points = [currentLine.start.x, currentLine.start.y, partnerLine.start.x, partnerLine.start.y]
+ const length = Math.sqrt((points[0] - points[2]) ** 2 + (points[1] - points[3]) ** 2)
+
+ if (length > 0) {
+ if (currentLine.type === TYPES.HIP) {
+ innerLines.push(drawHipLine(points, canvas, roof, textMode, null, currentDegree, currentDegree))
+ } else if (currentLine.type === TYPES.RIDGE) {
+ innerLines.push(drawRidgeLine(points, canvas, roof, textMode))
+ } else if (currentLine.type === TYPES.NEW) {
+ const isDiagonal = Math.abs(points[0] - points[2]) >= 1 && Math.abs(points[1] - points[3]) >= 1
+ if (isDiagonal && almostEqual(Math.abs(points[0] - points[2]), Math.abs(points[1] - points[3]))) {
+ innerLines.push(drawHipLine(points, canvas, roof, textMode, null, currentDegree, currentDegree))
+ }
+ if (!isDiagonal) {
+ innerLines.push(drawRidgeLine(points, canvas, roof, textMode))
+ }
+ }
+ proceedAnalysis.push(currentLine, partnerLine)
+ }
+ } else {
+ const allPoints = [currentLine.start, currentLine.end, partnerLine.start, partnerLine.end]
+ let points = []
+ allPoints.forEach((point) => {
+ let count = 0
+ allPoints.forEach((p) => {
+ if (almostEqual(point.x, p.x) && almostEqual(point.y, p.y)) count++
+ })
+ if (count === 1) points.push(point)
+ })
+
+ if (points.length === 2) {
+ const length = Math.sqrt((points[0].x - points[1].x) ** 2 + (points[0].y - points[1].y) ** 2)
+ if (length < EPSILON) return
+ const isDiagonal = Math.abs(points[0].x - points[1].x) >= 1 && Math.abs(points[0].y - points[1].y) >= 1
+ if (isDiagonal) {
+ innerLines.push(
+ drawHipLine([points[0].x, points[0].y, points[1].x, points[1].y], canvas, roof, textMode, null, currentDegree, currentDegree),
+ )
+ } else {
+ innerLines.push(drawRidgeLine([points[0].x, points[0].y, points[1].x, points[1].y], canvas, roof, textMode))
+ }
+ proceedAnalysis.push(currentLine, partnerLine)
+ }
+ }
+ })
+ })
+ linesAnalysis = linesAnalysis.filter((line) => !proceedAnalysis.find((p) => p === line))*/
+
+ //하단 지붕 라인처리
+ /* const downRoofLines = []
+ baseLines.forEach((baseLine, index) => {
+ const nextLine = baseLines[(index + 1) % baseLines.length]
+ const prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length]
+
+ const prevLineVector = { x: Math.sign(prevLine.x1 - prevLine.x2), y: Math.sign(prevLine.y1 - prevLine.y2) }
+ const nextLineVector = { x: Math.sign(nextLine.x1 - nextLine.x2), y: Math.sign(nextLine.y1 - nextLine.y2) }
+
+ //반절마루 생성불가이므로 지붕선 분기를 해야하는지 확인 후 처리.
+ if (
+ prevLineVector.x === nextLineVector.x &&
+ prevLineVector.y === nextLineVector.y &&
+ (prevLine.attributes.type === LINE_TYPE.WALLLINE.GABLE || nextLine.attributes.type === LINE_TYPE.WALLLINE.GABLE)
+ ) {
+ downRoofLines.push(index)
+ }
+ })*/
+
+ /*if (downRoofLines.length > 0) {
+ downRoofLines.forEach((index) => {
+ const currentLine = baseLines[index]
+ // const nextLine = baseLines[(index + 1) % baseLines.length]
+ // const prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length]
+
+ const analyze = analyzeLine(currentLine)
+ if (analyze.isDiagonal) {
+ return
+ }
+
+ const roofLine = analyze.roofLine
+ let roofPoint = [analyze.startPoint.x, analyze.startPoint.y, analyze.endPoint.x, analyze.endPoint.y]
+
+ if (analyze.isVertical) {
+ roofPoint[0] = roofLine.x1
+ roofPoint[2] = roofLine.x2
+ }
+ if (analyze.isHorizontal) {
+ roofPoint[1] = roofLine.y1
+ roofPoint[3] = roofLine.y2
+ }
+
+ console.log('analyze: ', analyze)
+ const findRidgeVector = { x: 0, y: 0 }
+ if (analyze.isVertical) {
+ // noinspection JSSuspiciousNameCombination
+ findRidgeVector.x = analyze.directionVector.y
+ }
+ if (analyze.isHorizontal) {
+ // noinspection JSSuspiciousNameCombination
+ findRidgeVector.y = analyze.directionVector.x
+ }
+
+ console.log('findRidgeVector: ', findRidgeVector)
+ innerLines
+ .filter((line) => {
+ if (line.name === LINE_TYPE.SUBLINE.RIDGE) {
+ if (analyze.isVertical) {
+ const signX = Math.sign(currentLine.x1 - line.x1)
+ console.log('signX: ', signX)
+ return signX === findRidgeVector.x
+ }
+ if (analyze.isHorizontal) {
+ const signY = Math.sign(currentLine.y1 - line.y1)
+ console.log('signY: ', signY)
+ return signY === findRidgeVector.y
+ }
+ return false
+ }
+ return false
+ })
+ .forEach((line) => {
+ if (line.name === LINE_TYPE.SUBLINE.RIDGE) {
+ const checkLine = new fabric.Line([line.x1, line.y1, line.x2, line.y2], {
+ stroke: 'red',
+ strokeWidth: 4,
+ parentId: roofId,
+ name: 'check',
+ })
+ canvas.add(checkLine)
+ canvas.renderAll()
+ }
+ })
+
+ /!*const oppositeLines = []
+ baseLines
+ .filter((line) => {
+ //평행한 반대방향 라인인지 확인.
+ const lineAnalyze = analyzeLine(line)
+ return (
+ (analyze.isHorizontal &&
+ lineAnalyze.directionVector.x !== analyze.directionVector.x &&
+ lineAnalyze.directionVector.y === analyze.directionVector.y) ||
+ (analyze.isVertical &&
+ lineAnalyze.directionVector.x === analyze.directionVector.x &&
+ lineAnalyze.directionVector.y !== analyze.directionVector.y)
+ )
+ })
+ .filter((line) => {
+ //라인이 현재라인에 overlap 되거나, 현재 라인을 full overlap하는지 확인.
+ if (analyze.isHorizontal) {
+ const currentMinX = Math.min(currentLine.x1, currentLine.x2)
+ const currentMaxX = Math.max(currentLine.x1, currentLine.x2)
+ const minX = Math.min(line.x1, line.x2)
+ const maxX = Math.max(line.x1, line.x2)
+ //full overlap
+ if (minX <= currentMinX && maxX >= currentMaxX) {
+ return true
+ }
+ //라인 포인트중 하나라도 현재 라인의 범위에 들어와있으면 overlap으로 판단.
+ if ((currentMinX <= minX && minX <= currentMaxX) || (currentMinX <= maxX && maxX <= currentMaxX)) {
+ return true
+ }
+ }
+ if (analyze.isVertical) {
+ const currentMinY = Math.min(currentLine.y1, currentLine.y2)
+ const currentMaxY = Math.max(currentLine.y1, currentLine.y2)
+ const minY = Math.min(line.y1, line.y2)
+ const maxY = Math.max(line.y1, line.y2)
+ //full overlap
+ if (minY <= currentMinY && maxY >= currentMaxY) {
+ return true
+ }
+ //라인 포인트중 하나라도 현재 라인의 범위에 들어와있으면 overlap으로 판단.
+ if ((currentMinY <= minY && minY <= currentMaxY) || (currentMinY <= maxY && maxY <= currentMaxY)) {
+ return true
+ }
+ }
+ return false
+ })
+ .forEach((line, i) => {
+ const checkLine = new fabric.Line([line.x1, line.y1, line.x2, line.y2], {
+ stroke: 'green',
+ strokeWidth: 4,
+ parentId: roofId,
+ name: 'check',
+ })
+ canvas.add(checkLine)
+ canvas.renderAll()
+ })*!/
+
+ // innerLines.push(drawRoofLine(roofPoint, canvas, roof, textMode))
+ })
+ }*/
+
+ /*if (linesAnalysis.length > 0) {
+ linesAnalysis
+ .filter((line) => {
+ const dx = line.end.x - line.start.x
+ const dy = line.end.y - line.start.y
+ const length = Math.sqrt(dx ** 2 + dy ** 2)
+ return length > 0
+ })
+ .forEach((line) => {
+ const startOnLine = roof.lines.find((l) => isPointOnLineNew(l, line.start))
+ const endOnLine = roof.lines.find((l) => isPointOnLineNew(l, line.end))
+ console.log('startOnLine, endOnLine: ', startOnLine, endOnLine)
+ const allLinesPoints = []
+ innerLines.forEach((innerLine) => {
+ allLinesPoints.push({ x: innerLine.x1, y: innerLine.y1 }, { x: innerLine.x2, y: innerLine.y2 })
+ })
+
+ if (
+ allLinesPoints.filter((p) => almostEqual(p.x, line.start.x) && almostEqual(p.y, line.start.y)).length < 3 &&
+ allLinesPoints.filter((p) => almostEqual(p.x, line.end.x) && almostEqual(p.y, line.end.y)).length === 0
+ ) {
+ if (startOnLine || endOnLine) {
+ if (line.degree === 0) {
+ innerLines.push(drawRoofLine([line.start.x, line.start.y, line.end.x, line.end.y], canvas, roof, textMode))
+ } else {
+ innerLines.push(
+ drawHipLine([line.start.x, line.start.y, line.end.x, line.end.y], canvas, roof, textMode, null, line.degree, line.degree),
+ )
+ }
+ }
+ }
+ })
+ }*/
+
+ //지붕선에 따라 라인추가 작업 처리.
+ /*const innerLinesPoints = []
+ innerLines.forEach((line) => {
+ const hasCoord1 = innerLinesPoints.find((p) => p.x === line.x1 && p.y === line.y1)
+ const hasCoord2 = innerLinesPoints.find((p) => p.x === line.x2 && p.y === line.y2)
+ if (!hasCoord1) innerLinesPoints.push({ x: line.x1, y: line.y1 })
+ if (!hasCoord2) innerLinesPoints.push({ x: line.x2, y: line.y2 })
+ })
+ roof.lines.forEach((line) => {
+ const splitPoint = []
+ let hasOverlapLine = false
+ const minX = Math.min(line.x1, line.x2)
+ const maxX = Math.max(line.x1, line.x2)
+ const minY = Math.min(line.y1, line.y2)
+ const maxY = Math.max(line.y1, line.y2)
+ innerLines.forEach((innerLine) => {
+ const innerLineMinX = Math.min(innerLine.x1, innerLine.x2)
+ const innerLineMaxX = Math.max(innerLine.x1, innerLine.x2)
+ const innerLineMinY = Math.min(innerLine.y1, innerLine.y2)
+ const innerLineMaxY = Math.max(innerLine.y1, innerLine.y2)
+ if (innerLineMinX <= minX && innerLineMaxX >= maxX && innerLineMinY <= minY && innerLineMaxY >= maxY) {
+ hasOverlapLine = true
+ }
+ if (minX <= innerLineMinX && maxX >= innerLineMaxX && minY <= innerLineMinY && maxY >= innerLineMaxY) {
+ hasOverlapLine = true
+ }
+ })
+ if (hasOverlapLine) return
+
+ innerLinesPoints.forEach((point) => {
+ if (
+ isPointOnLineNew(line, point) &&
+ !(almostEqual(line.x1, point.x) && almostEqual(line.y1, point.y)) &&
+ !(almostEqual(line.x2, point.x) && almostEqual(line.y2, point.y))
+ ) {
+ const distance = Math.sqrt((point.x - line.x1) ** 2 + (point.y - line.y1) ** 2)
+ splitPoint.push({ point, distance })
+ }
+ })
+ if (splitPoint.length > 0) {
+ splitPoint.sort((a, b) => a[1] - b[1])
+ let startPoint = { x: line.x1, y: line.y1 }
+ for (let i = 0; i < splitPoint.length; i++) {
+ const point = splitPoint[i].point
+ innerLines.push(drawRoofLine([startPoint.x, startPoint.y, point.x, point.y], canvas, roof, textMode))
+ startPoint = point
+ }
+ innerLines.push(drawRoofLine([startPoint.x, startPoint.y, line.x2, line.y2], canvas, roof, textMode))
+ } else {
+ innerLines.push(drawRoofLine([line.x1, line.y1, line.x2, line.y2], canvas, roof, textMode))
+ }
+ })*/
+}
+
+/**
+ * 선분과 선분의 교점을 구한다.
+ * @param line1Start
+ * @param line1End
+ * @param line2Start
+ * @param line2End
+ */
+const lineIntersection = (line1Start, line1End, line2Start, line2End) => {
+ const dx1 = line1End.x - line1Start.x
+ const dy1 = line1End.y - line1Start.y
+ const dx2 = line2End.x - line2Start.x
+ const dy2 = line2End.y - line2Start.y
+ // const denominator = (line2End.y - line2Start.y) * (line1End.x - line1Start.x) - (line2End.x - line2Start.x) * (line1End.y - line1Start.y)
+ const denominator = dy2 * dx1 - dx2 * dy1
+ if (Math.abs(denominator) < EPSILON) {
+ const isOpposite = dx1 * dx2 + dy1 * dy2 < 0
+
+ const isOverlap = isPointOnLineNew({ x1: line1Start.x, y1: line1Start.y, x2: line1End.x, y2: line1End.y }, { x: line2Start.x, y: line2Start.y })
+
+ if (isOpposite && isOverlap) {
+ return { x: line2Start.x, y: line2Start.y }
+ }
+ return null
+ } // 평행
+
+ let ua = (dx2 * (line1Start.y - line2Start.y) - dy2 * (line1Start.x - line2Start.x)) / denominator
+ let ub = (dx1 * (line1Start.y - line2Start.y) - dy1 * (line1Start.x - line2Start.x)) / denominator
+
+ ua = clamp01(ua)
+ ub = clamp01(ub)
+
+ if (ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1) {
+ return {
+ x: line1Start.x + ua * dx1,
+ y: line1Start.y + ua * dy1,
+ }
+ }
+ return null
+}
+
+/**
+ * 경계값 보정용 헬퍼
+ * @param t
+ * @returns {*|number}
+ */
+const clamp01 = (t) => {
+ if (almostEqual(t, 0, EPSILON)) return 0
+ if (almostEqual(t, 1, EPSILON)) return 1
+ return t
+}
+
+/**
+ * 실제 각도를 계산한다 대각선인 경우 각도 조정
+ * @param points
+ * @param degree
+ */
+const getRealDegree = (points, degree) => {
+ const deltaX = Math.abs(points[2] - points[0])
+ const deltaY = Math.abs(points[3] - points[1])
+ if (deltaX < 1 || deltaY < 1) {
+ return degree
+ }
+
+ const hypotenuse = Math.sqrt(deltaX ** 2 + deltaY ** 2)
+ const adjacent = Math.sqrt(Math.pow(hypotenuse, 2) / 2)
+ const height = adjacent * Math.tan((degree * Math.PI) / 180)
+ return Math.atan2(height, hypotenuse) * (180 / Math.PI)
+}
+
+/**
+ * 두 라인이 평행한지 확인한다.
+ * @param line1
+ * @param line2
+ * @returns {boolean}
+ */
+const isParallel = (line1, line2) => {
+ // 벡터 계산
+ const v1x = line1.x2 - line1.x1
+ const v1y = line1.y2 - line1.y1
+ const v2x = line2.x2 - line2.x1
+ const v2y = line2.y2 - line2.y1
+
+ // 벡터의 크기 계산
+ const length1 = Math.sqrt(v1x ** 2 + v1y ** 2)
+ const length2 = Math.sqrt(v2x ** 2 + v2y ** 2)
+
+ // 벡터가 너무 작으면 평행 판단 불가
+ if (length1 < EPSILON || length2 < EPSILON) {
+ return false
+ }
+
+ // 정규화된 벡터
+ const norm1x = v1x / length1
+ const norm1y = v1y / length1
+ const norm2x = v2x / length2
+ const norm2y = v2y / length2
+
+ // 외적(cross product)을 이용한 평행 판단
+ // 외적의 크기가 0에 가까우면 평행
+ const crossProduct = Math.abs(norm1x * norm2y - norm1y * norm2x)
+ const isParallel = crossProduct < EPSILON
+
+ // 또는 내적(dot product)을 이용한 방법
+ // 내적의 절대값이 1에 가까우면 평행 또는 반평행
+ const dotProduct = norm1x * norm2x + norm1y * norm2y
+ const isParallelOrAntiParallel = Math.abs(Math.abs(dotProduct) - 1) < EPSILON
+
+ return isParallel || isParallelOrAntiParallel
+}
+
+/**
+ * 두 라인의 방향에 따라 이등분선의 vector를 구한다.
+ * @param line1
+ * @param line2
+ */
+const getBisectLines = (line1, line2) => {
+ const bisector = { x: 0, y: 0 }
+
+ // 벡터 계산
+ const v1x = line1.x2 - line1.x1
+ const v1y = line1.y2 - line1.y1
+ const v2x = line2.x2 - line2.x1
+ const v2y = line2.y2 - line2.y1
+
+ // 벡터의 크기 계산
+ const length1 = Math.sqrt(v1x ** 2 + v1y ** 2)
+ const length2 = Math.sqrt(v2x ** 2 + v2y ** 2)
+
+ // 벡터가 너무 작으면 평행 판단 불가
+ if (length1 < EPSILON || length2 < EPSILON) {
+ return bisector
+ }
+
+ const lineEdge1 = { vertex1: { x: line1.x1, y: line1.y1 }, vertex2: { x: line1.x2, y: line1.y2 } }
+ const lineEdge2 = { vertex1: { x: line2.x1, y: line2.y1 }, vertex2: { x: line2.x2, y: line2.y2 } }
+ const is = edgesIntersection(lineEdge1, lineEdge2)
+ if (is) {
+ const dir1 = getDirection(line1, is)
+ const dir2 = getDirection(line2, is)
+
+ // 이등분선 계산
+ const bisectorX = dir1.x + dir2.x
+ const bisectorY = dir1.y + dir2.y
+ const length = Math.sqrt(bisectorX * bisectorX + bisectorY * bisectorY)
+
+ bisector.x = bisectorX / length
+ bisector.y = bisectorY / length
+ }
+
+ return bisector
+}
+
+/**
+ * BaseLine의 교차상태에서의 bisect를 구한다.
+ * @param line1
+ * @param line2
+ * @param intersection
+ */
+const getBisectBaseLines = (line1, line2, intersection) => {
+ let bisector = { x: 0, y: 0 }
+ if (isParallel(line1, line2)) return bisector
+
+ const lineEdge1 = { vertex1: { x: line1.x1, y: line1.y1 }, vertex2: { x: line1.x2, y: line1.y2 } }
+ const lineEdge2 = { vertex1: { x: line2.x1, y: line2.y1 }, vertex2: { x: line2.x2, y: line2.y2 } }
+ const is = edgesIntersection(lineEdge1, lineEdge2)
+ if (is) {
+ bisector = { x: Math.sign(intersection.x - is.x), y: Math.sign(intersection.y - is.y) }
+ }
+ return bisector
+}
+
+/**
+ * a 와 b 가 epsilon내 오차인지 확인한다.
+ * @param a
+ * @param b
+ * @param epsilon
+ * @returns {boolean}
+ */
+const almostEqual = (a, b, epsilon = 1e-10) => {
+ if (a === Infinity || b === Infinity) return false
+ return Math.abs(a - b) <= epsilon
+}
+
+/**
+ * segment가 fromPoint에서 떨어진 방향을 구한다.
+ * @param segment
+ * @param fromPoint
+ * @returns {{x, y}}
+ */
+const getDirection = (segment, fromPoint) => {
+ const target = { x: segment.x2, y: segment.y2 }
+
+ const dx = target.x - fromPoint.x
+ const dy = target.y - fromPoint.y
+ const length = Math.sqrt(dx * dx + dy * dy)
+
+ return { x: dx / length, y: dy / length }
+}
+
+/**
+ * lines중 points가 이미 존재하는지 확인한다.
+ * @param lines
+ * @param points
+ * @returns {boolean}
+ */
+const alreadyPoints = (lines, points) => {
+ let has = false
+
+ lines.forEach((line) => {
+ const linePoints = [line.x1, line.y1, line.x2, line.y2]
+ if (
+ (almostEqual(linePoints[0], points[0], 1) &&
+ almostEqual(linePoints[1], points[1], 1) &&
+ almostEqual(linePoints[2], points[2], 1) &&
+ almostEqual(linePoints[3], points[3], 1)) ||
+ (almostEqual(linePoints[0], points[2], 1) &&
+ almostEqual(linePoints[1], points[3], 1) &&
+ almostEqual(linePoints[2], points[0], 1) &&
+ almostEqual(linePoints[3], points[1], 1))
+ ) {
+ has = true
+ }
+ })
+
+ return has
+}
/**
* 마루가 있는 지붕을 그린다.
* @param roofId
@@ -4606,9 +8969,6 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => {
.filter((line) => (ridgeVectorX === 0 ? line.x1 !== line.x2 : line.y1 !== line.y2))
.filter((line) => (line.x1 === ridge.x2 && line.y1 === ridge.y2) || (line.x2 === ridge.x2 && line.y2 === ridge.y2))
.forEach((line) => secondGableLines.push(line))
-
- baseHipLines
- .filter((line) => (ridgeVectorX === 0 ? line.x1 !== line.x2 : line.y1 !== line.y2))
.filter((line) => (line.x1 === ridge.x1 && line.y1 === ridge.y1) || (line.x2 === ridge.x1 && line.y2 === ridge.y1))
.forEach((line) => firstGableLines.push(line))
baseHipLines
@@ -5257,8 +9617,8 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => {
return
}
- canvas.remove(prevGableLine)
- canvas.remove(nextGableLine)
+ // canvas.remove(prevGableLine)
+ // canvas.remove(nextGableLine)
baseHipLines = baseHipLines.filter((base) => base.line !== prevGableLine && base.line !== nextGableLine)
const points = [prevGableLine.x1, prevGableLine.y1, nextGableLine.x1, nextGableLine.y1]
@@ -7578,12 +11938,15 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => {
hipBasePoint = { x1: line.x1, y1: line.y1, x2: line.x2, y2: line.y2 }
point = [mergePoint[0].x, mergePoint[0].y, mergePoint[3].x, mergePoint[3].y]
- const theta = Big(Math.acos(Big(line.line.attributes.planeSize).div(
- line.line.attributes.actualSize === 0 ||
- line.line.attributes.actualSize === '' ||
- line.line.attributes.actualSize === undefined ?
- line.line.attributes.planeSize : line.line.attributes.actualSize
- )))
+ const theta = Big(
+ Math.acos(
+ Big(line.line.attributes.planeSize).div(
+ line.line.attributes.actualSize === 0 || line.line.attributes.actualSize === '' || line.line.attributes.actualSize === undefined
+ ? line.line.attributes.planeSize
+ : line.line.attributes.actualSize,
+ ),
+ ),
+ )
.times(180)
.div(Math.PI)
.round(1)
@@ -9231,11 +13594,9 @@ const getSortedPoint = (points, lines) => {
const reCalculateSize = (line) => {
const oldPlaneSize = line.attributes.planeSize
const oldActualSize = line.attributes.actualSize
- const theta = Big(Math.acos(Big(oldPlaneSize).div(
- oldActualSize === 0 || oldActualSize === '' || oldActualSize === undefined ?
- oldPlaneSize :
- oldActualSize
- )))
+ const theta = Big(
+ Math.acos(Big(oldPlaneSize).div(oldActualSize === 0 || oldActualSize === '' || oldActualSize === undefined ? oldPlaneSize : oldActualSize)),
+ )
.times(180)
.div(Math.PI)
const planeSize = calcLinePlaneSize({
|