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