diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js index 222e829e..ea0254f7 100644 --- a/src/util/qpolygon-utils.js +++ b/src/util/qpolygon-utils.js @@ -1660,89 +1660,84 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { 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 polygonVertices = roof.points.map((point) => ({ x: point.x, y: point.y })) - const L = polygonVertices.length - - const temporaryLines = [] - for (let i = 0; i < wall.points.length; i++) { - const currentVertex = polygonVertices[i] - const prevVertex = polygonVertices[(i - 1 + L) % L] - const nextVertex = polygonVertices[(i + 1) % L] - - const checkCircle1 = new fabric.Circle({ - left: currentVertex.x, - top: currentVertex.y, - radius: 4, - fill: 'red', - name: 'check', + 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 checkCircle2 = new fabric.Circle({ - left: prevVertex.x, - top: prevVertex.y, - radius: 4, - fill: 'blue', - name: 'check', - }) - const checkCircle3 = new fabric.Circle({ - left: nextVertex.x, - top: nextVertex.y, - radius: 4, - fill: 'green', - name: 'check', - }) - canvas.add(checkCircle1, checkCircle2, checkCircle3) - canvas.renderAll() - const bisector = calculateAngleBisector(prevVertex, currentVertex, nextVertex, polygonVertices) + 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) } - const divisionLines = [] - // 가선분의 종점 계산 (각이등분선과 다각형 변 또는 분할선분의 교점) - const endPoint = findIntersectionPoint(currentVertex, bisector, polygonVertices, divisionLines) + //중복되는 지붕선을 병합한다. 라인 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) + } + }) - if (endPoint) { - const tempLine = { - index: i, - startPoint: { ...currentVertex }, - endPoint: { ...endPoint }, - leftEdge: (i - 1 + L) % L, - rightEdge: i, - isActive: true, - intersectedWith: null, - minDistance: Infinity, + 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) + }) + } } - - const checkLine = new fabric.Line([tempLine.startPoint.x, tempLine.startPoint.y, tempLine.endPoint.x, tempLine.endPoint.y], { - stroke: 'red', - strokeWidth: 2, - parentId: roof.id, - name: 'check', - selectable: false, - }) - canvas.add(checkLine) - canvas.renderAll() - temporaryLines.push(tempLine) - } - canvas - .getObjects() - .filter((object) => object.name === 'check') - .forEach((object) => canvas.remove(object)) - canvas.renderAll() + }) + } else { + baseLines = wall.baseLines } - - canvas - .getObjects() - .filter((object) => object.name === 'check') - .forEach((object) => canvas.remove(object)) - canvas.renderAll() - - return*/ - - const baseLines = wall.baseLines.filter((line) => line.attributes.planeSize > 0) const baseLinePoints = baseLines.map((line) => ({ x: line.x1, y: line.y1 })) - const checkWallPolygon = new QPolygon(baseLinePoints, {}) - let ridgeLines = [] let innerLines = [] /** 벽취합이 있는 경우 소매가 있다면 지붕 형상을 변경해야 한다. */ @@ -2094,6 +2089,136 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { } } + /** + * + * @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(prevLine.x1 - testPoint[0]), y: Math.sign(prevLine.y1 - testPoint[1]) } + const intersectVector = { x: Math.sign(prevLine.x1 - intersection.x), y: Math.sign(prevLine.y1 - 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 (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(nextLine.x2 - testPoint[0]), y: Math.sign(nextLine.y2 - testPoint[1]) } + const intersectVector = { x: Math.sign(nextLine.x2 - intersection.x), y: Math.sign(nextLine.y2 - 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 (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 + } + } + /** * 지붕의 모양을 판단하여 각 변에 맞는 라인을 처리 한다. */ @@ -2139,24 +2264,10 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { const analyze = analyzeLine(currentLine) - //지붕선분을 innseLines에 추가한다. - const roofPoints = [analyze.roofLine.x1, analyze.roofLine.y1, analyze.roofLine.x2, analyze.roofLine.y2] - innerLines.push(drawRoofLine([roofPoints[0], roofPoints[1], roofPoints[2], roofPoints[3]], canvas, roof, textMode)) - - //지붕선을 임시선분에 추가한다. - /*const roofPoints = [analyze.roofLine.x1, analyze.roofLine.y1, analyze.roofLine.x2, analyze.roofLine.y2] - linesAnalysis.push({ - start: { x: roofPoints[0], y: roofPoints[1] }, // 시작점 - end: { x: roofPoints[2], y: roofPoints[3] }, // 끝점 - left: baseLines.findIndex((line) => line === prevLine), //이전라인 index - right: baseLines.findIndex((line) => line === currentLine), //현재라인 index - type: 'roof', //라인 타입 roof:지붕선(각도없음), hip:추녀마루(각도있음), ridge:(각도없음) - degree: 0, //라인 각도 - })*/ //양옆이 케라바가 아닐때 제외 - /*if (prevLine.attributes.type !== LINE_TYPE.WALLLINE.GABLE || nextLine.attributes.type !== LINE_TYPE.WALLLINE.GABLE) { + if (prevLine.attributes.type !== LINE_TYPE.WALLLINE.GABLE || nextLine.attributes.type !== LINE_TYPE.WALLLINE.GABLE) { return - }*/ + } const eavesLines = [] @@ -2281,24 +2392,25 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { 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 line1 = drawHipLine(points1, canvas, roof, textMode, null, shedDegree, shedDegree) - const line2 = drawHipLine(points2, canvas, roof, textMode, null, shedDegree, shedDegree) - const line3 = drawRoofLine([analyze.roofLine.x1, analyze.roofLine.y1, analyze.roofLine.x2, analyze.roofLine.y2], canvas, roof, textMode) - innerLines.push(line1, line2, line3)*/ + 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: baseLines.findIndex((line) => line === prevLine), - right: baseLines.findIndex((line) => line === currentLine), + left: beforePrevIndex, + right: prevIndex, type: 'hip', degree: shedDegree, }, { start: { x: points2[0], y: points2[1] }, end: { x: points2[2], y: points2[3] }, - left: baseLines.findIndex((line) => line === currentLine), - right: baseLines.findIndex((line) => line === nextLine), + left: nextIndex, + right: afterNextIndex, type: 'hip', degree: shedDegree, }, @@ -2317,14 +2429,17 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { const analyze = analyzeLine(currentLine) - //지붕선을 innerLines에 추가한다. - const roofPoints = [analyze.roofLine.x1, analyze.roofLine.y1, analyze.roofLine.x2, analyze.roofLine.y2] - innerLines.push(drawRoofLine([roofPoints[0], roofPoints[1], roofPoints[2], roofPoints[3]], canvas, roof, textMode)) - //양옆이 처마가 아닐때 패스 - /*if (prevLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES || nextLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES) { - return false - }*/ + if (prevLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES || nextLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES) { + return + } + + 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) @@ -2429,7 +2544,7 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { const point = [midPoint.x, midPoint.y, intersect.x, intersect.y] //마루가 최대로 뻗어나갈수 있는 포인트. null일때는 맞은편 지붕선 까지로 판단한다. - const drivePoint = getRidgeDrivePoints(point, prevLine, nextLine, baseLines, canvas) + const drivePoint = getRidgeDrivePoint(point, prevLine, nextLine, baseLines) console.log('drivePoint : ', drivePoint) if (drivePoint) { @@ -2485,9 +2600,6 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { } }) //3. 처마지붕 판단, 처마는 옆이 처마여야한다. - let eavesHipLines = [] - - // 처마지붕 사이의 추녀마루를 작성하기 위한 포인트를 계산. eaves.forEach((currentLine) => { let prevLine baseLines.forEach((baseLine, index) => { @@ -2498,10 +2610,6 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { const analyze = analyzeLine(currentLine) - //지붕선을 innerLines에 추가한다. - const roofPoints = [analyze.roofLine.x1, analyze.roofLine.y1, analyze.roofLine.x2, analyze.roofLine.y2] - innerLines.push(drawRoofLine([roofPoints[0], roofPoints[1], roofPoints[2], roofPoints[3]], canvas, roof, textMode)) - // 옆이 처마가 아니면 패스 if (prevLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES) { return @@ -2536,7 +2644,10 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { 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) { + 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) @@ -2553,12 +2664,22 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { isForwardPoints.sort((a, b) => a.length - b.length) isBackwardPoints.sort((a, b) => a.length - b.length) - const hipPoint = [ - isBackwardPoints[0].intersect.x, - isBackwardPoints[0].intersect.y, - isForwardPoints[0].intersect.x, - isForwardPoints[0].intersect.y, - ] + let hipPoint = [] + if (isForwardPoints.length > 0 && isBackwardPoints.length > 0) { + hipPoint = [isBackwardPoints[0].intersect.x, isBackwardPoints[0].intersect.y, isForwardPoints[0].intersect.x, isForwardPoints[0].intersect.y] + } else { + if (isBackwardPoints.length === 0 && isForwardPoints.length > 1) { + hipPoint = [isForwardPoints[0].intersect.x, isForwardPoints[0].intersect.y, isForwardPoints[1].intersect.x, isForwardPoints[1].intersect.y] + } + if (isForwardPoints.length === 0 && isBackwardPoints.length > 1) { + hipPoint = [ + isBackwardPoints[0].intersect.x, + isBackwardPoints[0].intersect.y, + isBackwardPoints[1].intersect.x, + isBackwardPoints[1].intersect.y, + ] + } + } linesAnalysis.push({ start: { x: hipPoint[0], y: hipPoint[1] }, @@ -2591,168 +2712,680 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { 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 { - const midX = (roofPoints[0] + roofPoints[2]) / 2 - const midY = (roofPoints[1] + roofPoints[3]) / 2 + } 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 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)) + 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] } - if (gableLength2 >= 1) { - innerLines.push(drawRoofLine(gablePoint2, canvas, roof, textMode)) + }) + + /** 이전, 다음라인의 사잇각의 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 }, } - 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 = getRidgeDrivePoints(point, prevLine, nextLine, baseLines, canvas) - - console.log('drivePoint : ', drivePoint) - if (drivePoint) { - point[2] = drivePoint.x - point[3] = drivePoint.y + 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] - 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: 'ridge', - degree: 0, - }) + //마루가 최대로 뻗어나갈수 있는 포인트. null일때는 맞은편 지붕선 까지로 판단한다. + const drivePoint = getRidgeDrivePoint(point, prevLine, nextLine, baseLines) + 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: 'ridge', + degree: 0, + }) } } } }) //5. 케라바 판단, 케라바는 양옆이 처마여야 한다. - gables.forEach((currentLine) => {}) + 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(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 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(currentLine.x1 - currentLine.x2), y: Math.sign(currentLine.y1 - currentLine.y2) } + // 마루 진행 최대 길이 + let prevDistance = 0, + nextDistance = 0 + + if (beforePrevLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) { + const beforeVector = { x: Math.sign(beforePrevLine.x1 - beforePrevLine.x2), y: Math.sign(beforePrevLine.y1 - beforePrevLine.y2) } + let distance = 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 = distance - beforePrevLine.attributes.offset + } else { + prevDistance = distance + beforePrevLine.attributes.offset + } + } + if (afterNextLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) { + const afterVector = { x: Math.sign(afterNextLine.x1 - afterNextLine.x2), y: Math.sign(afterNextLine.y1 - afterNextLine.y2) } + let distance = 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 = distance - afterNextLine.attributes.offset + } else { + nextDistance = distance + afterNextLine.attributes.offset + } + } + + //좌우 선분 중 긴 쪽이 기준선이 된다 + const stdLine = nextDistance <= prevDistance ? prevLine : nextLine + 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(stdLine.x1 - stdLine.x2), y: Math.sign(stdLine.y1 - stdLine.y2) } + const stdPrevVector = { x: Math.sign(stdPrevLine.x1 - stdPrevLine.x2), y: Math.sign(stdPrevLine.y1 - stdPrevLine.y2) } + const stdNextVector = { x: Math.sign(stdNextLine.x1 - stdNextLine.x2), y: Math.sign(stdNextLine.y1 - stdNextLine.y2) } + if (stdPrevVector.x === stdNextVector.x && stdPrevVector.y === stdNextVector.y) { + stdPoints = [ + stdLine.x1 + stdVector.x * stdPrevLine.attributes.offset, + stdLine.y1 + stdVector.y * stdPrevLine.attributes.offset, + stdLine.x2 + stdVector.x * stdNextLine.attributes.offset, + stdLine.y2 + stdVector.y * stdNextLine.attributes.offset, + ] + } else { + stdPoints = [ + stdLine.x1 + stdVector.x * stdPrevLine.attributes.offset, + stdLine.y1 + stdVector.y * stdPrevLine.attributes.offset, + stdLine.x2 + -stdVector.x * stdNextLine.attributes.offset, + stdLine.y2 + -stdVector.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', + }) + canvas.add(checkLine) + canvas.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) + + baseLines + .filter((line) => line !== stdLine && line !== currentLine) + .filter((line) => { + const vector = { x: Math.sign(stdLine.x1 - stdLine.x2), y: Math.sign(stdLine.y1 - stdLine.y2) } + const lineVector = { x: Math.sign(line.x1 - line.x2), y: Math.sign(line.y1 - line.y2) } + return (vector.x === lineVector.x && vector.y !== lineVector.y) || (vector.x !== lineVector.x && vector.y === lineVector.y) + }) + .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) }) + } + 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) }) + } + 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) }) + } + } + }) + + 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', + strokeW: 4, + 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]] + } + } + } + } + } + + 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 + } + ridgePoint = [overlapMinX, ridgePoint[1], overlapMaxX, ridgePoint[3]] + } + if (stdAnalyze.isVertical) { + if (overlapMinY > overlapMaxY) { + return + } + ridgePoint = [ridgePoint[0], overlapMinY, ridgePoint[2], overlapMaxY] + } + + 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: '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: 'gableLine', + 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 checkLine = new fabric.Line([line.start.x, line.start.y, line.end.x, line.end.y], { - stroke: 'cyan', - strokeWidth: 3, + 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: 'checkAnalysis', + name: 'check', }) canvas.add(checkLine) canvas.renderAll() }) - // 각 가선분의 최단 교점 찾기 const intersections = [] for (let i = 0; i < linesAnalysis.length; i++) { @@ -2760,28 +3393,66 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { let intersectPoint = null //교점 좌표 let partners = new Set() //교점 선분의 index + const lineI = linesAnalysis[i] + 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 === 'gableLine' && lineI.connectCnt > 1) continue + for (let j = 0; j < linesAnalysis.length; j++) { - if (i === j) continue - const intersection = lineIntersection(linesAnalysis[i].start, linesAnalysis[i].end, linesAnalysis[j].start, linesAnalysis[j].end, canvas) + const lineJ = linesAnalysis[j] + if (lineI === lineJ) continue + if (lineI.type === 'gableLine' && lineJ.type === 'gableLine' && i.gableId === lineJ.gableId) continue + if (lineJ.type === 'gableLine' && lineJ.connectCnt > 1) continue + + const checkLineJ = new fabric.Line([lineJ.start.x, lineJ.start.y, lineJ.end.x, lineJ.end.y], { + stroke: 'green', + strokeWidth: 2, + parentId: roofId, + name: 'check', + }) + canvas.add(checkLineJ).renderAll() + + const intersection = lineIntersection(lineI.start, lineI.end, lineJ.start, lineJ.end, canvas) if (intersection) { - const distance = Math.sqrt((intersection.x - linesAnalysis[i].start.x) ** 2 + (intersection.y - linesAnalysis[i].start.y) ** 2) - if (distance < minDistance && !almostEqual(distance, minDistance)) { + const checkCircle = new fabric.Circle({ + left: intersection.x, + top: intersection.y, + radius: 5, + fill: 'red', + parentId: roofId, + name: 'check', + }) + canvas.add(checkCircle).renderAll() + 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 === 'gableLine' && 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) } + canvas.remove(checkCircle).renderAll() } + canvas.remove(checkLineJ).renderAll() } 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() //처리된 선 @@ -2836,11 +3507,22 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { 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 - const point1 = [line1.start.x, line1.start.y, intersectPoint.x, intersectPoint.y] - const point2 = [line2.start.x, line2.start.y, intersectPoint.x, intersectPoint.y] - const length1 = Math.sqrt((point1[2] - point1[0]) ** 2 + (point1[3] - point1[1]) ** 2) - const length2 = Math.sqrt((point2[2] - point2[0]) ** 2 + (point2[3] - point2[1]) ** 2) + + //gable라인과 붙는경우 length가 0에 가까우면 포인트를 뒤집는다. + if (line1.type === 'gableLine' && 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 === 'gableLine' && 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 === 'hip') { @@ -2854,6 +3536,12 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { } else { innerLines.push(drawRidgeLine(point1, canvas, roof, textMode)) } + } else if (line1.type === 'gableLine') { + if (line1.degree > 0) { + innerLines.push(drawHipLine(point1, canvas, roof, textMode, null, line1.degree, line1.degree)) + } else { + innerLines.push(drawRidgeLine(point1, canvas, roof, textMode)) + } } } @@ -2869,103 +3557,184 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { } else { innerLines.push(drawRidgeLine(point2, canvas, roof, textMode)) } + } else if (line2.type === 'gableLine') { + if (line2.degree > 0) { + innerLines.push(drawHipLine(point2, canvas, roof, textMode, null, line2.degree, line2.degree)) + } else { + innerLines.push(drawRidgeLine(point2, canvas, roof, textMode)) + } } } - // 연결점에서 새로운 가선분을 생성 - 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) + if (line1.type === 'gableLine' || line2.type === 'gableLine') { + console.log('gableLine newAnalyze start') + const gableLine = line1.type === 'gableLine' ? 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 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 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 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 uniqueLines = Array.from(linesCounts.entries()) + .filter(([_, count]) => count === 1) + .map(([line, _]) => line) - //마주하는 지붕선을 찾는다. - 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 }) - } + 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: 'gableLine', + 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: '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) }) - intersectionsByRoof.sort((a, b) => a.distance - b.distance) - //지붕 선과의 교점이 존재 할때 - if (intersectionsByRoof.length > 0) { - const is = intersectionsByRoof[0].is - const checkNewLine = new fabric.Line([intersectPoint.x, intersectPoint.y, is.x, is.y], { - stroke: 'cyan', + + 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', }) - canvas.add(checkNewLine) - canvas.renderAll() - newAnalysis.push({ - start: { x: intersectPoint.x, y: intersectPoint.y }, - end: { x: is.x, y: is.y }, - left: uniqueLines[0], - right: uniqueLines[1], - type: 'new', - degree: line1.degree, + 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: 'new', + degree: isDiagonal ? line1.degree : 0, + }) + } } + canvas + .getObjects() + .filter((object) => object.name === 'check') + .forEach((object) => canvas.remove(object)) + canvas.renderAll() } - canvas - .getObjects() - .filter((object) => object.name === 'check') - .forEach((object) => canvas.remove(object)) - canvas.renderAll() } processed.add(pIndex) } @@ -2991,85 +3760,333 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { .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.forEach((currentLine) => { - if (proceedAnalysis.find((p) => p === currentLine)) return - //현재와 같으면 제외, 이미 처리된 라인은 제외, 현재와 공통선분이 존재하지 않으면 제외 - linesAnalysis - .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 + 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) + 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 === 'hip') { - innerLines.push(drawHipLine(points, canvas, roof, textMode, null, currentDegree, currentDegree)) - } else if (currentLine.type === 'ridge') { - innerLines.push(drawRidgeLine(points, canvas, roof, textMode)) - } else if (currentLine.type === 'new') { - const isDiagonal = Math.abs(points[0] - points[2]) >= 1 && Math.abs(points[1] - points[3]) >= 1 - if (isDiagonal) { + if (length > 0) { + if (currentLine.type === 'hip') { innerLines.push(drawHipLine(points, canvas, roof, textMode, null, currentDegree, currentDegree)) - } else { + } else if (currentLine.type === 'ridge') { innerLines.push(drawRidgeLine(points, canvas, roof, textMode)) + } else if (currentLine.type === '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) } - 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++ + } 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 (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)) + 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), + ) } - proceedAnalysis.push(currentLine, partnerLine) } } }) + } + + //지붕선에 따라 라인추가 작업 처리. + 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)) + } }) //지붕 innerLines에 추가. - roof.innerLines.push(...innerLines, ...ridgeLines) + roof.innerLines = innerLines canvas .getObjects() @@ -3078,106 +4095,6 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { canvas.renderAll() } -/** - * - * @param testPoint - * @param prevLine - * @param nextLine - * @param baseLines - * @param canvas - */ -const getRidgeDrivePoints = (testPoint = [], prevLine, nextLine, baseLines, canvas) => { - 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] } } - - 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 || beforePrevLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES) { - prevPoint = null - } else { - 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(prevLine.x1 - testPoint[0]), y: Math.sign(prevLine.y1 - testPoint[1]) } - const intersectVector = { x: Math.sign(prevLine.x1 - intersection.x), y: Math.sign(prevLine.y1 - 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 (nextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) { - const index = baseLines.findIndex((line) => line === nextLine) - const afterNextLine = baseLines[(index + 1) % baseLines.length] - if (!afterNextLine || afterNextLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES) { - nextPoint = null - } else { - 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(nextLine.x2 - testPoint[0]), y: Math.sign(nextLine.y2 - testPoint[1]) } - const intersectVector = { x: Math.sign(nextLine.x2 - intersection.x), y: Math.sign(nextLine.y2 - 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 (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 - } -} - /** * 선분과 선분의 교점을 구한다. * @param line1Start @@ -3204,8 +4121,17 @@ const lineIntersection = (line1Start, line1End, line2Start, line2End) => { return null } // 평행 - const ua = (dx2 * (line1Start.y - line2Start.y) - dy2 * (line1Start.x - line2Start.x)) / denominator - const ub = (dx1 * (line1Start.y - line2Start.y) - dy1 * (line1Start.x - line2Start.x)) / denominator + 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 + + // 경계값 보정용 헬퍼 + const clamp01 = (t) => { + if (almostEqual(t, 0, EPSILON)) return 0 + if (almostEqual(t, 1, EPSILON)) return 1 + return t + } + ua = clamp01(ua) + ub = clamp01(ub) if (ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1) { return {