diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js index ac1f46f7..222e829e 100644 --- a/src/util/qpolygon-utils.js +++ b/src/util/qpolygon-utils.js @@ -2426,9 +2426,20 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { 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 + } + linesAnalysis.push({ - start: { x: midPoint.x, y: midPoint.y }, - end: { x: intersect.x, y: intersect.y }, + 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', @@ -2559,7 +2570,169 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { }) }) //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 + } + + 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)) + } else { + const midX = (roofPoints[0] + roofPoints[2]) / 2 + const midY = (roofPoints[1] + roofPoints[3]) / 2 + + 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 = getRidgeDrivePoints(point, prevLine, nextLine, baseLines, canvas) + + console.log('drivePoint : ', drivePoint) + 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) => {}) //각 선분의 교차점을 찾아 선을 그린다. const MAX_ITERATIONS = 1000 //무한루프 방지 @@ -2878,17 +3051,19 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { if (count === 1) points.push(point) }) - 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) } - proceedAnalysis.push(currentLine, partnerLine) } }) }) @@ -2903,6 +3078,106 @@ 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