From d148077e6be1fa9716121044a2f845cbaf22490b Mon Sep 17 00:00:00 2001 From: Jaeyoung Lee Date: Wed, 12 Nov 2025 10:40:57 +0900 Subject: [PATCH 01/28] =?UTF-8?q?=EB=B3=80=EB=B3=84=EB=A1=9C=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EC=A4=91=EA=B0=84=20=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util/qpolygon-utils.js | 1617 +++++++++++++++++++++++++++++++++++- 1 file changed, 1602 insertions(+), 15 deletions(-) diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js index 33737856..3014e43e 100644 --- a/src/util/qpolygon-utils.js +++ b/src/util/qpolygon-utils.js @@ -6,9 +6,9 @@ import { QPolygon } from '@/components/fabric/QPolygon' import * as turf from '@turf/turf' import { LINE_TYPE, POLYGON_TYPE } from '@/common/common' import Big from 'big.js' -import { canvas } from 'framer-motion/m' const TWO_PI = Math.PI * 2 +const EPSILON = 1e-10 //좌표계산 시 최소 차이값 export const defineQPolygon = () => { fabric.QPolygon.fromObject = function (object, callback) { @@ -1492,6 +1492,1595 @@ 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) => { + let roof = canvas?.getObjects().find((object) => object.id === roofId) + const wall = canvas.getObjects().find((object) => object.name === POLYGON_TYPE.WALL && object.attributes.roofId === roofId) + + /*const polygonVertices = roof.points.map((point) => ({ x: point.x, y: point.y })) + const L = polygonVertices.length + + const temporaryLines = [] + for (let i = 0; i < wall.points.length; i++) { + const currentVertex = polygonVertices[i] + const prevVertex = polygonVertices[(i - 1 + L) % L] + const nextVertex = polygonVertices[(i + 1) % L] + + const checkCircle1 = new fabric.Circle({ + left: currentVertex.x, + top: currentVertex.y, + radius: 4, + fill: 'red', + name: 'check', + }) + const checkCircle2 = new fabric.Circle({ + left: prevVertex.x, + top: prevVertex.y, + radius: 4, + fill: 'blue', + name: 'check', + }) + const checkCircle3 = new fabric.Circle({ + left: nextVertex.x, + top: nextVertex.y, + radius: 4, + fill: 'green', + name: 'check', + }) + canvas.add(checkCircle1, checkCircle2, checkCircle3) + canvas.renderAll() + + const bisector = calculateAngleBisector(prevVertex, currentVertex, nextVertex, polygonVertices) + + const divisionLines = [] + // 가선분의 종점 계산 (각이등분선과 다각형 변 또는 분할선분의 교점) + const endPoint = findIntersectionPoint(currentVertex, bisector, polygonVertices, divisionLines) + + if (endPoint) { + const tempLine = { + index: i, + startPoint: { ...currentVertex }, + endPoint: { ...endPoint }, + leftEdge: (i - 1 + L) % L, + rightEdge: i, + isActive: true, + intersectedWith: null, + minDistance: Infinity, + } + + const checkLine = new fabric.Line([tempLine.startPoint.x, tempLine.startPoint.y, tempLine.endPoint.x, tempLine.endPoint.y], { + stroke: 'red', + strokeWidth: 2, + parentId: roof.id, + name: 'check', + selectable: false, + }) + canvas.add(checkLine) + canvas.renderAll() + temporaryLines.push(tempLine) + } + canvas + .getObjects() + .filter((object) => object.name === 'check') + .forEach((object) => canvas.remove(object)) + canvas.renderAll() + } + + canvas + .getObjects() + .filter((object) => object.name === 'check') + .forEach((object) => canvas.remove(object)) + canvas.renderAll() + + return*/ + + const baseLines = wall.baseLines.filter((line) => line.attributes.planeSize > 0) + const baseLinePoints = baseLines.map((line) => ({ x: line.x1, y: line.y1 })) + + const checkWallPolygon = new QPolygon(baseLinePoints, {}) + + let ridgeLines = [] + let innerLines = [] + + /** 벽취합이 있는 경우 소매가 있다면 지붕 형상을 변경해야 한다. */ + 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, + } + } + + /** + * 지붕의 모양을 판단하여 각 변에 맞는 라인을 처리 한다. + */ + 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) + + //지붕선분을 innseLines에 추가한다. + const roofPoints = [analyze.roofLine.x1, analyze.roofLine.y1, analyze.roofLine.x2, analyze.roofLine.y2] + innerLines.push(drawRoofLine([roofPoints[0], roofPoints[1], roofPoints[2], roofPoints[3]], canvas, roof, textMode)) + + //지붕선을 임시선분에 추가한다. + /*const roofPoints = [analyze.roofLine.x1, analyze.roofLine.y1, analyze.roofLine.x2, analyze.roofLine.y2] + linesAnalysis.push({ + start: { x: roofPoints[0], y: roofPoints[1] }, // 시작점 + end: { x: roofPoints[2], y: roofPoints[3] }, // 끝점 + left: baseLines.findIndex((line) => line === prevLine), //이전라인 index + right: baseLines.findIndex((line) => line === currentLine), //현재라인 index + type: 'roof', //라인 타입 roof:지붕선(각도없음), hip:추녀마루(각도있음), ridge:(각도없음) + degree: 0, //라인 각도 + })*/ + //양옆이 케라바가 아닐때 제외 + /*if (prevLine.attributes.type !== LINE_TYPE.WALLLINE.GABLE || nextLine.attributes.type !== LINE_TYPE.WALLLINE.GABLE) { + 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 line1 = drawHipLine(points1, canvas, roof, textMode, null, shedDegree, shedDegree) + const line2 = drawHipLine(points2, canvas, roof, textMode, null, shedDegree, shedDegree) + const line3 = drawRoofLine([analyze.roofLine.x1, analyze.roofLine.y1, analyze.roofLine.x2, analyze.roofLine.y2], canvas, roof, textMode) + innerLines.push(line1, line2, line3)*/ + linesAnalysis.push( + { + start: { x: points1[0], y: points1[1] }, + end: { x: points1[2], y: points1[3] }, + left: baseLines.findIndex((line) => line === prevLine), + right: baseLines.findIndex((line) => line === currentLine), + type: 'hip', + degree: shedDegree, + }, + { + start: { x: points2[0], y: points2[1] }, + end: { x: points2[2], y: points2[3] }, + left: baseLines.findIndex((line) => line === currentLine), + right: baseLines.findIndex((line) => line === nextLine), + type: '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) + + //지붕선을 innerLines에 추가한다. + const roofPoints = [analyze.roofLine.x1, analyze.roofLine.y1, analyze.roofLine.x2, analyze.roofLine.y2] + innerLines.push(drawRoofLine([roofPoints[0], roofPoints[1], roofPoints[2], roofPoints[3]], canvas, roof, textMode)) + + //양옆이 처마가 아닐때 패스 + /*if (prevLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES || nextLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES) { + return false + }*/ + + 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 + 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: '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: 'ridge', + degree: 0, + }) + } + } + } + }) + //3. 처마지붕 판단, 처마는 옆이 처마여야한다. + let eavesHipLines = [] + + // 처마지붕 사이의 추녀마루를 작성하기 위한 포인트를 계산. + eaves.forEach((currentLine) => { + let prevLine + baseLines.forEach((baseLine, index) => { + if (baseLine === currentLine) { + prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] + } + }) + + const analyze = analyzeLine(currentLine) + + //지붕선을 innerLines에 추가한다. + const roofPoints = [analyze.roofLine.x1, analyze.roofLine.y1, analyze.roofLine.x2, analyze.roofLine.y2] + innerLines.push(drawRoofLine([roofPoints[0], roofPoints[1], roofPoints[2], roofPoints[3]], canvas, roof, textMode)) + + // 옆이 처마가 아니면 패스 + if (prevLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES) { + return + } + + const currentDegree = getDegreeByChon(currentLine.attributes.pitch) + const checkVector = getHalfAngleVector(currentLine, prevLine) + const checkPoint = { + x: currentLine.x1 + (checkVector.x * analyze.length) / 2, + y: currentLine.y1 + (checkVector.y * analyze.length) / 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) { + 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) + + const hipPoint = [ + isBackwardPoints[0].intersect.x, + isBackwardPoints[0].intersect.y, + isForwardPoints[0].intersect.x, + isForwardPoints[0].intersect.y, + ] + + linesAnalysis.push({ + start: { x: hipPoint[0], y: hipPoint[1] }, + end: { x: hipPoint[2], y: hipPoint[3] }, + left: baseLines.findIndex((line) => line === prevLine), + right: baseLines.findIndex((line) => line === currentLine), + type: 'hip', + degree: currentDegree, + }) + }) + + /*//추녀마루 포인트중 겹치는 라인을 찾아 조정한다. + eavesHipLines.forEach((eavesHipLine) => { + const { hipPoint, line1, line2, degree } = eavesHipLine + const checkLine = new fabric.Line(hipPoint, { + stroke: 'blue', + strokeWidth: 2, + parentId: roofId, + name: 'check', + }) + canvas.add(checkLine) + canvas.renderAll() + + const hipEdge = { vertex1: { x: hipPoint[0], y: hipPoint[1] }, vertex2: { x: hipPoint[2], y: hipPoint[3] } } + const intersectPoints = [] + eavesHipLines + .filter((line) => eavesHipLine !== line) + .forEach((hip) => { + const checkPoint = hip.hipPoint + const checkLine1 = hip.line1 + const checkLine2 = hip.line2 + const checkEdge = { vertex1: { x: checkPoint[0], y: checkPoint[1] }, vertex2: { x: checkPoint[2], y: checkPoint[3] } } + const intersect = edgesIntersection(checkEdge, hipEdge) + const checkLine = new fabric.Line(checkPoint, { + stroke: 'green', + strokeWidth: 2, + parentId: roofId, + name: 'check', + }) + canvas.add(checkLine) + canvas.renderAll() + + // 기준선내 겹치는 경우, line1, line2 중에서 비교선의 line1, line2가 겹칠때만 겹쳐질 수 있는 라인으로 판단한다. (샤프논문 참조) + if ( + intersect && + isPointOnLineNew({ x1: hipPoint[0], y1: hipPoint[1], x2: hipPoint[2], y2: hipPoint[3] }, intersect) && + isPointOnLineNew({ x1: checkPoint[0], y1: checkPoint[1], x2: checkPoint[2], y2: checkPoint[3] }, intersect) && + (line1 === checkLine1 || line1 === checkLine2 || line2 === checkLine1 || line2 === checkLine2) + ) { + const dx = hipPoint[0] - intersect.x + const dy = hipPoint[1] - intersect.y + const length = Math.sqrt(dx * dx + dy * dy) + intersectPoints.push({ intersect, hip, length }) + } + }) + intersectPoints.sort((a, b) => a.length - b.length) + if (intersectPoints.length === 0) { + innerLines.push(drawHipLine(hipPoint, canvas, roof, textMode, null, degree, degree)) + } else { + const intersect = intersectPoints[0].intersect + const linePoint = [hipPoint[0], hipPoint[1], intersect.x, intersect.y] + innerLines.push(drawHipLine(linePoint, canvas, roof, textMode, null, degree, degree)) + + //다른라인에서 사용 되지 않게 조정 + eavesHipLines.find((line) => line === eavesHipLine).hipPoint = linePoint + eavesHipLines.find((line) => line === intersectPoints[0].hip).hipPoint = [ + intersectPoints[0].hip.hipPoint[0], + intersectPoints[0].hip.hipPoint[1], + intersect.x, + intersect.y, + ] + } + canvas + .getObjects() + .filter((object) => object.name === 'check') + .forEach((object) => canvas.remove(object)) + canvas.renderAll() + })*/ + //4. 반절처(반절마루) 판단, 반절마루는 양옆이 처마여야 한다. + //5. 케라바 판단, 케라바는 양옆이 처마여야 한다. + + //각 선분의 교차점을 찾아 선을 그린다. + const MAX_ITERATIONS = 1000 //무한루프 방지 + let iterations = 0 + + console.log('linesAnalysis', linesAnalysis) + while (linesAnalysis.length > 0 && iterations < MAX_ITERATIONS) { + iterations++ + + // 각 가선분의 최단 교점 찾기 + const intersections = [] + for (let i = 0; i < linesAnalysis.length; i++) { + let minDistance = Infinity //최단거리 + let intersectPoint = null //교점 좌표 + let intersectIndex = [] //교점 선분의 index + + for (let j = 0; j < linesAnalysis.length; j++) { + if (i === j) continue + + const intersection = lineIntersection(linesAnalysis[i].start, linesAnalysis[i].end, linesAnalysis[j].start, linesAnalysis[j].end) + if (intersection) { + const distance = Math.sqrt((intersection.x - linesAnalysis[i].start.x) ** 2 + (intersection.y - linesAnalysis[i].start.y) ** 2) + if (distance > EPSILON) { + if (distance < minDistance && !almostEqual(distance, minDistance)) { + // console.log('intersection :', intersection, intersectPoint) + // console.log('distance :', distance, minDistance, almostEqual(distance, minDistance)) + minDistance = distance + intersectPoint = intersection + intersectIndex = [j] + } else if (almostEqual(distance, minDistance)) { + intersectIndex.push(j) + } + } + } + } + if (intersectPoint) { + intersections.push({ index: i, intersectPoint, intersectIndex, distance: minDistance }) + } + } + + // 교점에 대한 적합 여부 판단 및 처리. + let newAnalysis = [] //신규 발생 선 + const processed = new Set() //처리된 선 + console.log('intersections : ', intersections) + for (const { index, intersectPoint, intersectIndex } of intersections) { + const check1 = linesAnalysis[index] + const check2 = linesAnalysis[intersectIndex] + 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 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 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: check2.start.x, top: check2.start.y, radius: 5, fill: 'red', parentId: roofId, name: 'check' }) + const checkCircle3 = new fabric.Circle({ left: check1.end.x, top: check1.end.y, radius: 5, fill: 'green', 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(checkCircle1, checkCircle2, checkCircle3, checkCircle4) + canvas.add(checkLine1, checkLine2) + canvas.renderAll() + + //교점이 없거나, 이미 처리된 선분이면 처리하지 않는다. + if (!intersectPoint || processed.has(index)) continue + + const partner = intersections.find((p) => p.index === intersectIndex) + //교점이 없거나, 교점 선분이 없으면 처리하지 않는다. + if (!partner || !partner.intersectPoint) continue + + //상호 최단 교점 여부 확인. + if (partner.intersectIndex === index) { + const line1 = linesAnalysis[index] + const line2 = linesAnalysis[intersectIndex] + + //좌,우 선 중 공통 선 존재 확인. + const isSameLine = line1.left === line2.left || line1.left === line2.right || line1.right === line2.left || line1.right === line2.right + + if (isSameLine) { + const point1 = [line1.start.x, line1.start.y, intersectPoint.x, intersectPoint.y] + const point2 = [line2.start.x, line2.start.y, intersectPoint.x, intersectPoint.y] + + if (line1.type === 'hip') { + innerLines.push(drawHipLine(point1, canvas, roof, textMode, null, line1.degree, line1.degree)) + } else if (line1.type === 'ridge') { + innerLines.push(drawRidgeLine(point1, canvas, roof, textMode)) + } else if (line1.type === '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)) + } + } + + if (line2.type === 'hip') { + innerLines.push(drawHipLine(point2, canvas, roof, textMode, null, line2.degree, line2.degree)) + } else if (line2.type === 'ridge') { + innerLines.push(drawRidgeLine(point2, canvas, roof, textMode)) + } else if (line2.type === '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)) + } + } + + // 연결점에서 새로운 가선분을 생성 + 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: 'green', + 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) { + const is = intersectionsByRoof[0].is + const checkNewLine = new fabric.Line([intersectPoint.x, intersectPoint.y, is.x, is.y], { + stroke: 'cyan', + strokeWidth: 4, + parentId: roofId, + name: 'check', + }) + canvas.add(checkNewLine) + canvas.renderAll() + newAnalysis.push({ + start: { x: intersectPoint.x, y: intersectPoint.y }, + end: { x: is.x, y: is.y }, + left: uniqueLines[0], + right: uniqueLines[1], + type: 'new', + degree: line1.degree, + }) + } + } + canvas + .getObjects() + .filter((object) => object.name === 'check') + .forEach((object) => canvas.remove(object)) + canvas.renderAll() + } + + processed.add(index) + processed.add(intersectIndex) + } + } + canvas + .getObjects() + .filter((object) => object.name === 'check') + .forEach((object) => canvas.remove(object)) + canvas.renderAll() + } + // 처리된 가선분 제외 + linesAnalysis = linesAnalysis.filter((_, index) => !processed.has(index)).concat(newAnalysis) + console.log('lineAnalysis: ', linesAnalysis) + /* + linesAnalysis.forEach((line) => { + const checkLine = new fabric.Line([line.start.x, line.start.y, line.end.x, line.end.y], { + stroke: 'red', + strokeWidth: 2, + parentId: roofId, + name: 'check', + }) + canvas.add(checkLine) + canvas.renderAll() + })*/ + + canvas + .getObjects() + .filter((object) => object.name === 'check') + .forEach((object) => canvas.remove(object)) + canvas.renderAll() + // 새로운 가선분이 없을때 종료 + if (newAnalysis.length === 0) break + } + + //지붕 innerLines에 추가. + roof.innerLines.push(...innerLines, ...ridgeLines) + + canvas + .getObjects() + .filter((object) => object.name === 'check') + .forEach((object) => canvas.remove(object)) + canvas.renderAll() +} + +/** + * 선분과 선분의 교점을 구한다. + * @param line1Start + * @param line1End + * @param line2Start + * @param line2End + */ +const lineIntersection = (line1Start, line1End, line2Start, line2End) => { + const denominator = (line2End.y - line2Start.y) * (line1End.x - line1Start.x) - (line2End.x - line2Start.x) * (line1End.y - line1Start.y) + if (Math.abs(denominator) < EPSILON) return null // 평행 + + const ua = ((line2End.x - line2Start.x) * (line1Start.y - line2Start.y) - (line2End.y - line2Start.y) * (line1Start.x - line2Start.x)) / denominator + const ub = ((line1End.x - line1Start.x) * (line1Start.y - line2Start.y) - (line1End.y - line1Start.y) * (line1Start.x - line2Start.x)) / denominator + + if (ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1) { + return { + x: line1Start.x + ua * (line1End.x - line1Start.x), + y: line1Start.y + ua * (line1End.y - line1Start.y), + } + } + return null +} + +/** + * 실제 각도를 계산한다 대각선인 경우 각도 조정 + * @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 + * @param polygon + */ +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, canvas) => { + const checkLine1 = new fabric.Line([line1.x1, line1.y1, line1.x2, line1.y2], { + stroke: 'red', + strokeWidth: 4, + name: 'check', + }) + const checkLine2 = new fabric.Line([line2.x1, line2.y1, line2.x2, line2.y2], { + stroke: 'blue', + strokeWidth: 4, + name: 'check', + }) + const checkCircle = new fabric.Circle({ + left: intersection.x, + top: intersection.y, + radius: 5, + fill: 'cyan', + name: 'check', + }) + canvas.add(checkLine1, checkLine2, checkCircle) + canvas.renderAll() + + 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) { + const checkCircle = new fabric.Circle({ + left: is.x, + top: is.y, + radius: 5, + fill: 'pink', + name: 'check', + }) + canvas.add(checkCircle) + canvas.renderAll() + bisector = { x: Math.sign(intersection.x - is.x), y: Math.sign(intersection.y - is.y) } + } + + canvas.remove(checkLine1, checkLine2, checkCircle) + canvas.renderAll() + + return bisector +} + +const almostEqual = (a, b, epsilon = 1e-10) => { + if (a === Infinity || b === Infinity) return false + const diff = Math.abs(a - b) + const larger = Math.max(Math.abs(a), Math.abs(b)) + + // 둘 다 0에 가까운 경우 + if (larger < epsilon) { + return diff < epsilon + } + + return diff <= larger * epsilon +} + +const getDirection = (segment, fromPoint) => { + // const dist1 = Math.sqrt(Math.pow(segment.x1 - fromPoint.x, 2) + Math.pow(segment.y1 - fromPoint.y, 2)) + // const dist2 = Math.sqrt(Math.pow(segment.x2 - fromPoint.x, 2) + Math.pow(segment.y2 - fromPoint.y, 2)) + + // const target = dist1 > dist2 ? { x: segment.x1, y: segment.y1 } : { x: segment.x2, y: segment.y2 } + 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 } +} /** * 마루가 있는 지붕을 그린다. * @param roofId @@ -4607,9 +6196,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 @@ -7579,12 +9165,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) @@ -9228,11 +10817,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({ From a201d65ebe0157dd4e70d16f3b20de984e8464d4 Mon Sep 17 00:00:00 2001 From: Jaeyoung Lee Date: Fri, 21 Nov 2025 14:15:52 +0900 Subject: [PATCH 02/28] =?UTF-8?q?=EB=B3=80=EB=B3=84=EB=A1=9C=20=EB=8C=80?= =?UTF-8?q?=EC=9D=91=20=EC=A4=91=EA=B0=84=20=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util/qpolygon-utils.js | 673 +++++++++++++++++++------------------ 1 file changed, 354 insertions(+), 319 deletions(-) diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js index 3014e43e..ac1f46f7 100644 --- a/src/util/qpolygon-utils.js +++ b/src/util/qpolygon-utils.js @@ -2389,7 +2389,7 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { 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)) + // innerLines.push(drawRoofLine(gablePoint, canvas, roof, textMode)) //양옆이 처마일경우 두개의 선, 아닐때 한개의 선, 좌우가 처마가 아닐때 안그려져야하는데 기존에 그려지는 경우가 있음 이유를 알 수 없음. if (prevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES && nextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) { @@ -2499,8 +2499,8 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { const currentDegree = getDegreeByChon(currentLine.attributes.pitch) const checkVector = getHalfAngleVector(currentLine, prevLine) const checkPoint = { - x: currentLine.x1 + (checkVector.x * analyze.length) / 2, - y: currentLine.y1 + (checkVector.y * analyze.length) / 2, + x: currentLine.x1 + (checkVector.x * 10) / 2, + y: currentLine.y1 + (checkVector.y * 10) / 2, } let hipVector @@ -2558,74 +2558,6 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { degree: currentDegree, }) }) - - /*//추녀마루 포인트중 겹치는 라인을 찾아 조정한다. - eavesHipLines.forEach((eavesHipLine) => { - const { hipPoint, line1, line2, degree } = eavesHipLine - const checkLine = new fabric.Line(hipPoint, { - stroke: 'blue', - strokeWidth: 2, - parentId: roofId, - name: 'check', - }) - canvas.add(checkLine) - canvas.renderAll() - - const hipEdge = { vertex1: { x: hipPoint[0], y: hipPoint[1] }, vertex2: { x: hipPoint[2], y: hipPoint[3] } } - const intersectPoints = [] - eavesHipLines - .filter((line) => eavesHipLine !== line) - .forEach((hip) => { - const checkPoint = hip.hipPoint - const checkLine1 = hip.line1 - const checkLine2 = hip.line2 - const checkEdge = { vertex1: { x: checkPoint[0], y: checkPoint[1] }, vertex2: { x: checkPoint[2], y: checkPoint[3] } } - const intersect = edgesIntersection(checkEdge, hipEdge) - const checkLine = new fabric.Line(checkPoint, { - stroke: 'green', - strokeWidth: 2, - parentId: roofId, - name: 'check', - }) - canvas.add(checkLine) - canvas.renderAll() - - // 기준선내 겹치는 경우, line1, line2 중에서 비교선의 line1, line2가 겹칠때만 겹쳐질 수 있는 라인으로 판단한다. (샤프논문 참조) - if ( - intersect && - isPointOnLineNew({ x1: hipPoint[0], y1: hipPoint[1], x2: hipPoint[2], y2: hipPoint[3] }, intersect) && - isPointOnLineNew({ x1: checkPoint[0], y1: checkPoint[1], x2: checkPoint[2], y2: checkPoint[3] }, intersect) && - (line1 === checkLine1 || line1 === checkLine2 || line2 === checkLine1 || line2 === checkLine2) - ) { - const dx = hipPoint[0] - intersect.x - const dy = hipPoint[1] - intersect.y - const length = Math.sqrt(dx * dx + dy * dy) - intersectPoints.push({ intersect, hip, length }) - } - }) - intersectPoints.sort((a, b) => a.length - b.length) - if (intersectPoints.length === 0) { - innerLines.push(drawHipLine(hipPoint, canvas, roof, textMode, null, degree, degree)) - } else { - const intersect = intersectPoints[0].intersect - const linePoint = [hipPoint[0], hipPoint[1], intersect.x, intersect.y] - innerLines.push(drawHipLine(linePoint, canvas, roof, textMode, null, degree, degree)) - - //다른라인에서 사용 되지 않게 조정 - eavesHipLines.find((line) => line === eavesHipLine).hipPoint = linePoint - eavesHipLines.find((line) => line === intersectPoints[0].hip).hipPoint = [ - intersectPoints[0].hip.hipPoint[0], - intersectPoints[0].hip.hipPoint[1], - intersect.x, - intersect.y, - ] - } - canvas - .getObjects() - .filter((object) => object.name === 'check') - .forEach((object) => canvas.remove(object)) - canvas.renderAll() - })*/ //4. 반절처(반절마루) 판단, 반절마루는 양옆이 처마여야 한다. //5. 케라바 판단, 케라바는 양옆이 처마여야 한다. @@ -2637,236 +2569,329 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { while (linesAnalysis.length > 0 && iterations < MAX_ITERATIONS) { iterations++ + linesAnalysis.forEach((line) => { + const checkLine = new fabric.Line([line.start.x, line.start.y, line.end.x, line.end.y], { + stroke: 'cyan', + strokeWidth: 3, + parentId: roofId, + name: 'checkAnalysis', + }) + canvas.add(checkLine) + canvas.renderAll() + }) + // 각 가선분의 최단 교점 찾기 const intersections = [] for (let i = 0; i < linesAnalysis.length; i++) { let minDistance = Infinity //최단거리 let intersectPoint = null //교점 좌표 - let intersectIndex = [] //교점 선분의 index + let partners = new Set() //교점 선분의 index for (let j = 0; j < linesAnalysis.length; j++) { if (i === j) continue - - const intersection = lineIntersection(linesAnalysis[i].start, linesAnalysis[i].end, linesAnalysis[j].start, linesAnalysis[j].end) + const intersection = lineIntersection(linesAnalysis[i].start, linesAnalysis[i].end, linesAnalysis[j].start, linesAnalysis[j].end, canvas) if (intersection) { const distance = Math.sqrt((intersection.x - linesAnalysis[i].start.x) ** 2 + (intersection.y - linesAnalysis[i].start.y) ** 2) - if (distance > EPSILON) { - if (distance < minDistance && !almostEqual(distance, minDistance)) { - // console.log('intersection :', intersection, intersectPoint) - // console.log('distance :', distance, minDistance, almostEqual(distance, minDistance)) - minDistance = distance - intersectPoint = intersection - intersectIndex = [j] - } else if (almostEqual(distance, minDistance)) { - intersectIndex.push(j) - } + if (distance < minDistance && !almostEqual(distance, minDistance)) { + minDistance = distance + intersectPoint = intersection + partners = new Set([j]) + } else if (almostEqual(distance, minDistance)) { + partners.add(j) } } } if (intersectPoint) { - intersections.push({ index: i, intersectPoint, intersectIndex, distance: minDistance }) + intersections.push({ index: i, intersectPoint, partners, distance: minDistance }) } } + // 제일 가까운 교점부터 처리 + intersections.sort((a, b) => a.distance - b.distance) + // 교점에 대한 적합 여부 판단 및 처리. let newAnalysis = [] //신규 발생 선 const processed = new Set() //처리된 선 - console.log('intersections : ', intersections) - for (const { index, intersectPoint, intersectIndex } of intersections) { - const check1 = linesAnalysis[index] - const check2 = linesAnalysis[intersectIndex] - 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 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 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: check2.start.x, top: check2.start.y, radius: 5, fill: 'red', parentId: roofId, name: 'check' }) - const checkCircle3 = new fabric.Circle({ left: check1.end.x, top: check1.end.y, radius: 5, fill: 'green', 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(checkCircle1, checkCircle2, checkCircle3, checkCircle4) - canvas.add(checkLine1, checkLine2) - canvas.renderAll() - - //교점이 없거나, 이미 처리된 선분이면 처리하지 않는다. - if (!intersectPoint || processed.has(index)) continue - - const partner = intersections.find((p) => p.index === intersectIndex) - //교점이 없거나, 교점 선분이 없으면 처리하지 않는다. - if (!partner || !partner.intersectPoint) continue - - //상호 최단 교점 여부 확인. - if (partner.intersectIndex === index) { - const line1 = linesAnalysis[index] - const line2 = linesAnalysis[intersectIndex] - - //좌,우 선 중 공통 선 존재 확인. - const isSameLine = line1.left === line2.left || line1.left === line2.right || line1.right === line2.left || line1.right === line2.right - - if (isSameLine) { - const point1 = [line1.start.x, line1.start.y, intersectPoint.x, intersectPoint.y] - const point2 = [line2.start.x, line2.start.y, intersectPoint.x, intersectPoint.y] - - if (line1.type === 'hip') { - innerLines.push(drawHipLine(point1, canvas, roof, textMode, null, line1.degree, line1.degree)) - } else if (line1.type === 'ridge') { - innerLines.push(drawRidgeLine(point1, canvas, roof, textMode)) - } else if (line1.type === '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)) - } - } - - if (line2.type === 'hip') { - innerLines.push(drawHipLine(point2, canvas, roof, textMode, null, line2.degree, line2.degree)) - } else if (line2.type === 'ridge') { - innerLines.push(drawRidgeLine(point2, canvas, roof, textMode)) - } else if (line2.type === '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)) - } - } - - // 연결점에서 새로운 가선분을 생성 - 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: 'green', - 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) { - const is = intersectionsByRoof[0].is - const checkNewLine = new fabric.Line([intersectPoint.x, intersectPoint.y, is.x, is.y], { - stroke: 'cyan', - strokeWidth: 4, - parentId: roofId, - name: 'check', - }) - canvas.add(checkNewLine) - canvas.renderAll() - newAnalysis.push({ - start: { x: intersectPoint.x, y: intersectPoint.y }, - end: { x: is.x, y: is.y }, - left: uniqueLines[0], - right: uniqueLines[1], - type: 'new', - degree: line1.degree, - }) - } - } - canvas - .getObjects() - .filter((object) => object.name === 'check') - .forEach((object) => canvas.remove(object)) - canvas.renderAll() - } - - processed.add(index) - processed.add(intersectIndex) - } - } + 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) { + // 현재 선이 처리 되었음을 표기 + isProceed = true + const point1 = [line1.start.x, line1.start.y, intersectPoint.x, intersectPoint.y] + const point2 = [line2.start.x, line2.start.y, intersectPoint.x, intersectPoint.y] + const length1 = Math.sqrt((point1[2] - point1[0]) ** 2 + (point1[3] - point1[1]) ** 2) + const length2 = Math.sqrt((point2[2] - point2[0]) ** 2 + (point2[3] - point2[1]) ** 2) + + if (length1 > 0 && !alreadyPoints(innerLines, point1)) { + if (line1.type === 'hip') { + innerLines.push(drawHipLine(point1, canvas, roof, textMode, null, line1.degree, line1.degree)) + } else if (line1.type === 'ridge') { + innerLines.push(drawRidgeLine(point1, canvas, roof, textMode)) + } else if (line1.type === '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)) + } + } + } + + if (length2 > 0 && !alreadyPoints(innerLines, point2)) { + if (line2.type === 'hip') { + innerLines.push(drawHipLine(point2, canvas, roof, textMode, null, line2.degree, line2.degree)) + } else if (line2.type === 'ridge') { + innerLines.push(drawRidgeLine(point2, canvas, roof, textMode)) + } else if (line2.type === '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)) + } + } + } + + // 연결점에서 새로운 가선분을 생성 + 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) { + const is = intersectionsByRoof[0].is + const checkNewLine = new fabric.Line([intersectPoint.x, intersectPoint.y, is.x, is.y], { + stroke: 'cyan', + strokeWidth: 4, + parentId: roofId, + name: 'check', + }) + canvas.add(checkNewLine) + canvas.renderAll() + newAnalysis.push({ + start: { x: intersectPoint.x, y: intersectPoint.y }, + end: { x: is.x, y: is.y }, + left: uniqueLines[0], + right: uniqueLines[1], + type: 'new', + degree: line1.degree, + }) + } + } + 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 = linesAnalysis.filter((_, index) => !processed.has(index)).concat(newAnalysis) + linesAnalysis = newAnalysis.concat(linesAnalysis.filter((_, index) => !processed.has(index))) console.log('lineAnalysis: ', linesAnalysis) - /* - linesAnalysis.forEach((line) => { - const checkLine = new fabric.Line([line.start.x, line.start.y, line.end.x, line.end.y], { - stroke: 'red', - strokeWidth: 2, - parentId: roofId, - name: 'check', - }) - canvas.add(checkLine) - canvas.renderAll() - })*/ canvas .getObjects() - .filter((object) => object.name === 'check') + .filter((object) => object.name === 'check' || object.name === 'checkAnalysis') .forEach((object) => canvas.remove(object)) canvas.renderAll() // 새로운 가선분이 없을때 종료 if (newAnalysis.length === 0) break } + console.log('lineAnalysis: end ', linesAnalysis) + + // 가선분 중 처리가 안되어있는 붙어있는 라인에 대한 예외처리. + const proceedAnalysis = [] + linesAnalysis.forEach((currentLine) => { + if (proceedAnalysis.find((p) => p === currentLine)) return + //현재와 같으면 제외, 이미 처리된 라인은 제외, 현재와 공통선분이 존재하지 않으면 제외 + linesAnalysis + .filter( + (partnerLine) => + partnerLine !== currentLine && + !proceedAnalysis.find((p) => p === partnerLine) && + (currentLine.left === partnerLine.left || + currentLine.left === partnerLine.right || + partnerLine.left === currentLine.left || + partnerLine.left === currentLine.right), + ) + .forEach((partnerLine) => { + const dx1 = currentLine.end.x - currentLine.start.x + const dy1 = currentLine.end.y - currentLine.start.y + const dx2 = partnerLine.end.x - partnerLine.start.x + const dy2 = partnerLine.end.y - partnerLine.start.y + const denominator = dy2 * dx1 - dx2 * dy1 + const isOpposite = dx1 * dx2 + dy1 * dy2 < 0 + //평행하지 않으면 제외 + if (Math.abs(denominator) > EPSILON) return + + const currentDegree = getDegreeByChon(baseLines[currentLine.left].attributes.pitch) + if (isOpposite) { + const points = [currentLine.start.x, currentLine.start.y, partnerLine.start.x, partnerLine.start.y] + const length = Math.sqrt((points[0] - points[2]) ** 2 + (points[1] - points[3]) ** 2) + + if (length > 0) { + if (currentLine.type === 'hip') { + innerLines.push(drawHipLine(points, canvas, roof, textMode, null, currentDegree, currentDegree)) + } else if (currentLine.type === 'ridge') { + innerLines.push(drawRidgeLine(points, canvas, roof, textMode)) + } else if (currentLine.type === 'new') { + const isDiagonal = Math.abs(points[0] - points[2]) >= 1 && Math.abs(points[1] - points[3]) >= 1 + if (isDiagonal) { + innerLines.push(drawHipLine(points, canvas, roof, textMode, null, currentDegree, currentDegree)) + } else { + 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) + }) + + 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) + } + }) + }) //지붕 innerLines에 추가. roof.innerLines.push(...innerLines, ...ridgeLines) @@ -2886,16 +2911,31 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { * @param line2End */ const lineIntersection = (line1Start, line1End, line2Start, line2End) => { - const denominator = (line2End.y - line2Start.y) * (line1End.x - line1Start.x) - (line2End.x - line2Start.x) * (line1End.y - line1Start.y) - if (Math.abs(denominator) < EPSILON) return null // 평행 + 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) { + console.log('평행') + const isOpposite = dx1 * dx2 + dy1 * dy2 < 0 - const ua = ((line2End.x - line2Start.x) * (line1Start.y - line2Start.y) - (line2End.y - line2Start.y) * (line1Start.x - line2Start.x)) / denominator - const ub = ((line1End.x - line1Start.x) * (line1Start.y - line2Start.y) - (line1End.y - line1Start.y) * (line1Start.x - line2Start.x)) / denominator + 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 + } // 평행 + + const ua = (dx2 * (line1Start.y - line2Start.y) - dy2 * (line1Start.x - line2Start.x)) / denominator + const ub = (dx1 * (line1Start.y - line2Start.y) - dy1 * (line1Start.x - line2Start.x)) / denominator if (ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1) { return { - x: line1Start.x + ua * (line1End.x - line1Start.x), - y: line1Start.y + ua * (line1End.y - line1Start.y), + x: line1Start.x + ua * dx1, + y: line1Start.y + ua * dy1, } } return null @@ -2964,7 +3004,6 @@ const isParallel = (line1, line2) => { * 두 라인의 방향에 따라 이등분선의 vector를 구한다. * @param line1 * @param line2 - * @param polygon */ const getBisectLines = (line1, line2) => { const bisector = { x: 0, y: 0 } @@ -3009,27 +3048,7 @@ const getBisectLines = (line1, line2) => { * @param line2 * @param intersection */ -const getBisectBaseLines = (line1, line2, intersection, canvas) => { - const checkLine1 = new fabric.Line([line1.x1, line1.y1, line1.x2, line1.y2], { - stroke: 'red', - strokeWidth: 4, - name: 'check', - }) - const checkLine2 = new fabric.Line([line2.x1, line2.y1, line2.x2, line2.y2], { - stroke: 'blue', - strokeWidth: 4, - name: 'check', - }) - const checkCircle = new fabric.Circle({ - left: intersection.x, - top: intersection.y, - radius: 5, - fill: 'cyan', - name: 'check', - }) - canvas.add(checkLine1, checkLine2, checkCircle) - canvas.renderAll() - +const getBisectBaseLines = (line1, line2, intersection) => { let bisector = { x: 0, y: 0 } if (isParallel(line1, line2)) return bisector @@ -3037,42 +3056,30 @@ const getBisectBaseLines = (line1, line2, intersection, canvas) => { const lineEdge2 = { vertex1: { x: line2.x1, y: line2.y1 }, vertex2: { x: line2.x2, y: line2.y2 } } const is = edgesIntersection(lineEdge1, lineEdge2) if (is) { - const checkCircle = new fabric.Circle({ - left: is.x, - top: is.y, - radius: 5, - fill: 'pink', - name: 'check', - }) - canvas.add(checkCircle) - canvas.renderAll() bisector = { x: Math.sign(intersection.x - is.x), y: Math.sign(intersection.y - is.y) } } - - canvas.remove(checkLine1, checkLine2, checkCircle) - canvas.renderAll() - 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 - const diff = Math.abs(a - b) - const larger = Math.max(Math.abs(a), Math.abs(b)) - - // 둘 다 0에 가까운 경우 - if (larger < epsilon) { - return diff < epsilon - } - - return diff <= larger * epsilon + return Math.abs(a - b) <= epsilon } +/** + * segment가 fromPoint에서 떨어진 방향을 구한다. + * @param segment + * @param fromPoint + * @returns {{x, y}} + */ const getDirection = (segment, fromPoint) => { - // const dist1 = Math.sqrt(Math.pow(segment.x1 - fromPoint.x, 2) + Math.pow(segment.y1 - fromPoint.y, 2)) - // const dist2 = Math.sqrt(Math.pow(segment.x2 - fromPoint.x, 2) + Math.pow(segment.y2 - fromPoint.y, 2)) - - // const target = dist1 > dist2 ? { x: segment.x1, y: segment.y1 } : { x: segment.x2, y: segment.y2 } const target = { x: segment.x2, y: segment.y2 } const dx = target.x - fromPoint.x @@ -3081,6 +3088,34 @@ const getDirection = (segment, fromPoint) => { 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 From 6c0cc73cb8026dfa58ec501e1349685388896fd6 Mon Sep 17 00:00:00 2001 From: Jaeyoung Lee Date: Mon, 24 Nov 2025 10:35:00 +0900 Subject: [PATCH 03/28] =?UTF-8?q?=EC=A4=91=EA=B0=84=20=EC=A0=80=EC=9E=A5(?= =?UTF-8?q?=EB=B0=98=EB=B0=95=EA=B3=B5=EC=A7=80=EB=B6=95=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util/qpolygon-utils.js | 299 +++++++++++++++++++++++++++++++++++-- 1 file changed, 287 insertions(+), 12 deletions(-) diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js index ac1f46f7..222e829e 100644 --- a/src/util/qpolygon-utils.js +++ b/src/util/qpolygon-utils.js @@ -2426,9 +2426,20 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { intersections.sort((a, b) => a.distance - b.distance) if (intersections.length > 0) { const intersect = intersections[0].intersect + const point = [midPoint.x, midPoint.y, intersect.x, intersect.y] + + //마루가 최대로 뻗어나갈수 있는 포인트. null일때는 맞은편 지붕선 까지로 판단한다. + const drivePoint = getRidgeDrivePoints(point, prevLine, nextLine, baseLines, canvas) + + console.log('drivePoint : ', drivePoint) + if (drivePoint) { + point[2] = drivePoint.x + point[3] = drivePoint.y + } + linesAnalysis.push({ - start: { x: midPoint.x, y: midPoint.y }, - end: { x: intersect.x, y: intersect.y }, + start: { x: point[0], y: point[1] }, + end: { x: point[2], y: point[3] }, left: baseLines.findIndex((line) => line === prevLine), right: baseLines.findIndex((line) => line === nextLine), type: 'ridge', @@ -2559,7 +2570,169 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { }) }) //4. 반절처(반절마루) 판단, 반절마루는 양옆이 처마여야 한다. + jerkinHeads.forEach((currentLine) => { + let prevLine, nextLine + baseLines.forEach((baseLine, index) => { + if (baseLine === currentLine) { + nextLine = baseLines[(index + 1) % baseLines.length] + prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] + } + }) + + //양옆이 처마가 아닐때 패스 + if (prevLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES || nextLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES) { + return + } + + const analyze = analyzeLine(currentLine) + const roofPoints = [analyze.roofLine.x1, analyze.roofLine.y1, analyze.roofLine.x2, analyze.roofLine.y2] + + const prevLineVector = { x: Math.sign(prevLine.x1 - prevLine.x2), y: Math.sign(prevLine.y1 - prevLine.y2) } + const nextLineVector = { x: Math.sign(nextLine.x1 - nextLine.x2), y: Math.sign(nextLine.y1 - nextLine.y2) } + if (prevLineVector.x === nextLineVector.x && prevLineVector.y === nextLineVector.y) { + //반절마루 생성불가이므로 지붕선만 추가하고 끝냄 + innerLines.push(drawRoofLine(roofPoints, canvas, roof, textMode)) + } else { + const midX = (roofPoints[0] + roofPoints[2]) / 2 + const midY = (roofPoints[1] + roofPoints[3]) / 2 + + const currentDegree = getDegreeByChon(currentLine.attributes.pitch) + const prevDegree = getDegreeByChon(prevLine.attributes.pitch) + const nextDegree = getDegreeByChon(nextLine.attributes.pitch) + const gableWidth = currentLine.attributes.width + + const roofVector = { x: Math.sign(roofPoints[2] - roofPoints[0]), y: Math.sign(roofPoints[3] - roofPoints[1]) } + const roofDx = roofPoints[0] - roofPoints[2] + const roofDy = roofPoints[1] - roofPoints[3] + const roofLength = Math.sqrt(roofDx * roofDx + roofDy * roofDy) + const lineLength = (roofLength - gableWidth) / 2 + const jerkinPoints = [] + jerkinPoints.push( + { x: roofPoints[0], y: roofPoints[1] }, + { x: roofPoints[0] + roofVector.x * lineLength, y: roofPoints[1] + roofVector.y * lineLength }, + ) + jerkinPoints.push({ x: jerkinPoints[1].x + roofVector.x * gableWidth, y: jerkinPoints[1].y + roofVector.y * gableWidth }) + jerkinPoints.push({ x: jerkinPoints[2].x + roofVector.x * lineLength, y: jerkinPoints[2].y + roofVector.y * lineLength }) + + if (gableWidth >= roofLength) { + innerLines.push(drawRoofLine(roofPoints, canvas, roof, textMode)) + } else if (jerkinPoints.length === 4) { + const gablePoint1 = [jerkinPoints[0].x, jerkinPoints[0].y, jerkinPoints[1].x, jerkinPoints[1].y] + const gablePoint2 = [jerkinPoints[1].x, jerkinPoints[1].y, jerkinPoints[2].x, jerkinPoints[2].y] + const gablePoint3 = [jerkinPoints[2].x, jerkinPoints[2].y, jerkinPoints[3].x, jerkinPoints[3].y] + const gableLength1 = Math.sqrt(Math.pow(gablePoint1[2] - gablePoint1[0], 2) + Math.pow(gablePoint1[3] - gablePoint1[1], 2)) + const gableLength2 = Math.sqrt(Math.pow(gablePoint2[2] - gablePoint2[0], 2) + Math.pow(gablePoint2[3] - gablePoint2[1], 2)) + const gableLength3 = Math.sqrt(Math.pow(gablePoint3[2] - gablePoint3[0], 2) + Math.pow(gablePoint3[3] - gablePoint3[1], 2)) + if (gableLength1 >= 1) { + innerLines.push(drawHipLine(gablePoint1, canvas, roof, textMode, null, prevDegree, prevDegree)) + } + if (gableLength2 >= 1) { + innerLines.push(drawRoofLine(gablePoint2, canvas, roof, textMode)) + } + if (gableLength3 >= 1) { + innerLines.push(drawHipLine(gablePoint3, canvas, roof, textMode, null, nextDegree, nextDegree)) + } + + const currentRoofLine = analyze.roofLine + let prevRoofLine, nextRoofLine + roof.lines.forEach((roofLine, index) => { + if (roofLine === currentRoofLine) { + nextRoofLine = roof.lines[(index + 1) % roof.lines.length] + prevRoofLine = roof.lines[(index - 1 + roof.lines.length) % roof.lines.length] + } + }) + + /** 이전, 다음라인의 사잇각의 vector를 구한다. */ + let prevVector = getHalfAngleVector(prevRoofLine, currentRoofLine) + let nextVector = getHalfAngleVector(currentRoofLine, nextRoofLine) + + /** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ + const prevCheckPoint = { + x: currentRoofLine.x1 + prevVector.x * 10, + y: currentRoofLine.y1 + prevVector.y * 10, + } + /** 다음 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ + const nextCheckPoint = { + x: currentRoofLine.x2 + nextVector.x * 10, + y: currentRoofLine.y2 + nextVector.y * 10, + } + + let prevHipVector, nextHipVector + + if (roof.inPolygon(prevCheckPoint)) { + prevHipVector = { x: prevVector.x, y: prevVector.y } + } else { + prevHipVector = { x: -prevVector.x, y: -prevVector.y } + } + + /** 다음 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ + if (roof.inPolygon(nextCheckPoint)) { + nextHipVector = { x: nextVector.x, y: nextVector.y } + } else { + nextHipVector = { x: -nextVector.x, y: -nextVector.y } + } + + const prevHipEdge = { + vertex1: { x: gablePoint2[0], y: gablePoint2[1] }, + vertex2: { x: gablePoint2[0] + prevHipVector.x * gableWidth, y: gablePoint2[1] + prevHipVector.y * gableWidth }, + } + const nextHipEdge = { + vertex1: { x: gablePoint2[2], y: gablePoint2[3] }, + vertex2: { x: gablePoint2[2] + nextHipVector.x * gableWidth, y: gablePoint2[3] + nextHipVector.y * gableWidth }, + } + + const hipIntersection = edgesIntersection(prevHipEdge, nextHipEdge) + if (hipIntersection) { + const prevHipPoint = [prevHipEdge.vertex1.x, prevHipEdge.vertex1.y, hipIntersection.x, hipIntersection.y] + const nextHipPoint = [nextHipEdge.vertex1.x, nextHipEdge.vertex1.y, hipIntersection.x, hipIntersection.y] + innerLines.push(drawHipLine(prevHipPoint, canvas, roof, textMode, null, currentDegree, currentDegree)) + innerLines.push(drawHipLine(nextHipPoint, canvas, roof, textMode, null, currentDegree, currentDegree)) + + const midPoint = { x: hipIntersection.x, y: hipIntersection.y } + const checkEdge = { + vertex1: { x: midPoint.x, y: midPoint.y }, + vertex2: { x: midPoint.x + -analyze.roofVector.x * 10000, y: midPoint.y + -analyze.roofVector.y * 10000 }, + } + const intersections = [] + roof.lines + .filter((line) => line !== currentRoofLine) + .forEach((line) => { + const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const intersect = edgesIntersection(lineEdge, checkEdge) + if (intersect && isPointOnLineNew(line, intersect)) { + const distance = Math.sqrt(Math.pow(midPoint.x - intersect.x, 2) + Math.pow(midPoint.y - intersect.y, 2)) + intersections.push({ intersect, distance }) + } + }) + intersections.sort((a, b) => a.distance - b.distance) + if (intersections.length > 0) { + const intersect = intersections[0].intersect + const point = [midPoint.x, midPoint.y, intersect.x, intersect.y] + + //마루가 최대로 뻗어나갈수 있는 포인트. null일때는 맞은편 지붕선 까지로 판단한다. + const drivePoint = getRidgeDrivePoints(point, prevLine, nextLine, baseLines, canvas) + + console.log('drivePoint : ', drivePoint) + if (drivePoint) { + point[2] = drivePoint.x + point[3] = drivePoint.y + } + + linesAnalysis.push({ + start: { x: point[0], y: point[1] }, + end: { x: point[2], y: point[3] }, + left: baseLines.findIndex((line) => line === prevLine), + right: baseLines.findIndex((line) => line === nextLine), + type: 'ridge', + degree: 0, + }) + } + } + } + } + }) //5. 케라바 판단, 케라바는 양옆이 처마여야 한다. + gables.forEach((currentLine) => {}) //각 선분의 교차점을 찾아 선을 그린다. const MAX_ITERATIONS = 1000 //무한루프 방지 @@ -2878,17 +3051,19 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { if (count === 1) points.push(point) }) - const length = Math.sqrt((points[0].x - points[1].x) ** 2 + (points[0].y - points[1].y) ** 2) - if (length < EPSILON) return - const isDiagonal = Math.abs(points[0].x - points[1].x) >= 1 && Math.abs(points[0].y - points[1].y) >= 1 - if (isDiagonal) { - innerLines.push( - drawHipLine([points[0].x, points[0].y, points[1].x, points[1].y], canvas, roof, textMode, null, currentDegree, currentDegree), - ) - } else { - innerLines.push(drawRidgeLine([points[0].x, points[0].y, points[1].x, points[1].y], canvas, roof, textMode)) + if (points.length === 2) { + const length = Math.sqrt((points[0].x - points[1].x) ** 2 + (points[0].y - points[1].y) ** 2) + if (length < EPSILON) return + const isDiagonal = Math.abs(points[0].x - points[1].x) >= 1 && Math.abs(points[0].y - points[1].y) >= 1 + if (isDiagonal) { + innerLines.push( + drawHipLine([points[0].x, points[0].y, points[1].x, points[1].y], canvas, roof, textMode, null, currentDegree, currentDegree), + ) + } else { + innerLines.push(drawRidgeLine([points[0].x, points[0].y, points[1].x, points[1].y], canvas, roof, textMode)) + } + proceedAnalysis.push(currentLine, partnerLine) } - proceedAnalysis.push(currentLine, partnerLine) } }) }) @@ -2903,6 +3078,106 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { canvas.renderAll() } +/** + * + * @param testPoint + * @param prevLine + * @param nextLine + * @param baseLines + * @param canvas + */ +const getRidgeDrivePoints = (testPoint = [], prevLine, nextLine, baseLines, canvas) => { + if (prevLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES && nextLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES) return null + let prevPoint = null, + nextPoint = null + + const baseLinePoints = baseLines.map((line) => ({ x: line.x1, y: line.y1 })) + const checkWallPolygon = new QPolygon(baseLinePoints, {}) + const checkEdge = { vertex1: { x: testPoint[0], y: testPoint[1] }, vertex2: { x: testPoint[2], y: testPoint[3] } } + + if (prevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) { + const index = baseLines.findIndex((line) => line === prevLine) + const beforePrevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] + if (!beforePrevLine || beforePrevLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES) { + prevPoint = null + } else { + let prevVector = getHalfAngleVector(beforePrevLine, prevLine) + + const prevCheckPoint = { + x: prevLine.x1 + prevVector.x * 10, + y: prevLine.y1 + prevVector.y * 10, + } + + let prevHipVector + + if (checkWallPolygon.inPolygon(prevCheckPoint)) { + prevHipVector = { x: prevVector.x, y: prevVector.y } + } else { + prevHipVector = { x: -prevVector.x, y: -prevVector.y } + } + + const prevHipEdge = { + vertex1: { x: prevLine.x1, y: prevLine.y1 }, + vertex2: { x: prevLine.x1 + prevHipVector.x * 10, y: prevLine.y1 + prevHipVector.y * 10 }, + } + + const intersection = edgesIntersection(prevHipEdge, checkEdge) + if (intersection) { + const checkVector = { x: Math.sign(prevLine.x1 - testPoint[0]), y: Math.sign(prevLine.y1 - testPoint[1]) } + const intersectVector = { x: Math.sign(prevLine.x1 - intersection.x), y: Math.sign(prevLine.y1 - intersection.y) } + if (checkVector.x === intersectVector.x && checkVector.y === intersectVector.y) { + const distance = Math.sqrt((intersection.x - testPoint[0]) ** 2 + (intersection.y - testPoint[1]) ** 2) + prevPoint = { intersection, distance } + } + } + } + } + + if (nextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) { + const index = baseLines.findIndex((line) => line === nextLine) + const afterNextLine = baseLines[(index + 1) % baseLines.length] + if (!afterNextLine || afterNextLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES) { + nextPoint = null + } else { + let nextVector = getHalfAngleVector(nextLine, afterNextLine) + const nextCheckPoint = { + x: nextLine.x2 + nextVector.x * 10, + y: nextLine.y2 + nextVector.y * 10, + } + let nextHipVector + if (checkWallPolygon.inPolygon(nextCheckPoint)) { + nextHipVector = { x: nextVector.x, y: nextVector.y } + } else { + nextHipVector = { x: -nextVector.x, y: -nextVector.y } + } + + const nextHipEdge = { + vertex1: { x: nextLine.x2, y: nextLine.y2 }, + vertex2: { x: nextLine.x2 + nextHipVector.x * 10, y: nextLine.y2 + nextHipVector.y * 10 }, + } + const intersection = edgesIntersection(nextHipEdge, checkEdge) + if (intersection) { + const checkVector = { x: Math.sign(nextLine.x2 - testPoint[0]), y: Math.sign(nextLine.y2 - testPoint[1]) } + const intersectVector = { x: Math.sign(nextLine.x2 - intersection.x), y: Math.sign(nextLine.y2 - intersection.y) } + if (checkVector.x === intersectVector.x && checkVector.y === intersectVector.y) { + const distance = Math.sqrt((intersection.x - testPoint[0]) ** 2 + (intersection.y - testPoint[1]) ** 2) + nextPoint = { intersection, distance } + } + } + } + } + + if (prevPoint && nextPoint) { + return prevPoint.distance < nextPoint.distance ? prevPoint.intersection : nextPoint.intersection + } else if (prevPoint) { + return prevPoint.intersection + } else if (nextPoint) { + return nextPoint.intersection + } else { + return null + } +} + /** * 선분과 선분의 교점을 구한다. * @param line1Start From f3cd10931dc90c4419d7d7ec99ee7f640a570416 Mon Sep 17 00:00:00 2001 From: Jaeyoung Lee Date: Thu, 11 Dec 2025 14:39:46 +0900 Subject: [PATCH 04/28] =?UTF-8?q?=EB=B3=80=EB=B3=84=EB=A1=9C=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EC=A4=91=EA=B0=84=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util/qpolygon-utils.js | 1934 ++++++++++++++++++++++++++---------- 1 file changed, 1430 insertions(+), 504 deletions(-) diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js index 222e829e..ea0254f7 100644 --- a/src/util/qpolygon-utils.js +++ b/src/util/qpolygon-utils.js @@ -1660,89 +1660,84 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { let roof = canvas?.getObjects().find((object) => object.id === roofId) const wall = canvas.getObjects().find((object) => object.name === POLYGON_TYPE.WALL && object.attributes.roofId === roofId) - /*const polygonVertices = roof.points.map((point) => ({ x: point.x, y: point.y })) - const L = polygonVertices.length - - const temporaryLines = [] - for (let i = 0; i < wall.points.length; i++) { - const currentVertex = polygonVertices[i] - const prevVertex = polygonVertices[(i - 1 + L) % L] - const nextVertex = polygonVertices[(i + 1) % L] - - const checkCircle1 = new fabric.Circle({ - left: currentVertex.x, - top: currentVertex.y, - radius: 4, - fill: 'red', - name: 'check', + const zeroLines = [] + wall.baseLines.forEach((line, index) => (line.attributes.planeSize < EPSILON ? zeroLines.push(index) : null)) + let baseLines = [] + if (zeroLines.length > 0) { + //형이동으로 인해 뭉쳐지는 라인을 찾는다. + const mergedLines = [] + zeroLines.forEach((zeroIndex) => { + const prevIndex = (zeroIndex - 1 + wall.baseLines.length) % wall.baseLines.length + const nextIndex = (zeroIndex + 1) % wall.baseLines.length + mergedLines.push({ prevIndex, nextIndex }) }) - const checkCircle2 = new fabric.Circle({ - left: prevVertex.x, - top: prevVertex.y, - radius: 4, - fill: 'blue', - name: 'check', - }) - const checkCircle3 = new fabric.Circle({ - left: nextVertex.x, - top: nextVertex.y, - radius: 4, - fill: 'green', - name: 'check', - }) - canvas.add(checkCircle1, checkCircle2, checkCircle3) - canvas.renderAll() - const bisector = calculateAngleBisector(prevVertex, currentVertex, nextVertex, polygonVertices) + const proceedPair = [] + wall.baseLines.forEach((line, index) => { + //뭉쳐지는 라인이면 하나로 합치고 baseLines에 추가. 아니면 baseLines에 바로 추가. + if (!zeroLines.includes(index) && !mergedLines.find((mergedLine) => mergedLine.prevIndex === index || mergedLine.nextIndex === index)) { + baseLines.push(line) + } else { + if (zeroLines.includes(index)) { + mergedLines.forEach((indexPair) => { + //이미 처리된 라인이편 패스 + if (proceedPair.includes(indexPair.prevIndex) || proceedPair.includes(indexPair.nextIndex)) return + let prevLine = wall.baseLines[indexPair.prevIndex] + const lineVector = { x: Math.sign(prevLine.x2 - prevLine.x1), y: Math.sign(prevLine.y2 - prevLine.y1) } - const divisionLines = [] - // 가선분의 종점 계산 (각이등분선과 다각형 변 또는 분할선분의 교점) - const endPoint = findIntersectionPoint(currentVertex, bisector, polygonVertices, divisionLines) + //중복되는 지붕선을 병합한다. 라인 vector가 같아야한다. + const indexList = [indexPair.prevIndex, indexPair.nextIndex] + mergedLines + .filter((pair) => pair !== indexPair) + .forEach((pair) => { + const pLine = wall.baseLines[pair.prevIndex] + const nLine = wall.baseLines[pair.nextIndex] + const pVector = { x: Math.sign(pLine.x2 - pLine.x1), y: Math.sign(pLine.y2 - pLine.y1) } + const nVector = { x: Math.sign(nLine.x2 - nLine.x1), y: Math.sign(nLine.y2 - nLine.y1) } + if ( + pVector.x === lineVector.x && + pVector.y === lineVector.y && + nVector.x === lineVector.x && + nVector.y === lineVector.y && + (indexList.includes(pair.prevIndex) || indexList.includes(pair.nextIndex)) + ) { + indexList.push(pair.prevIndex, pair.nextIndex) + } + }) - if (endPoint) { - const tempLine = { - index: i, - startPoint: { ...currentVertex }, - endPoint: { ...endPoint }, - leftEdge: (i - 1 + L) % L, - rightEdge: i, - isActive: true, - intersectedWith: null, - minDistance: Infinity, + const startLine = wall.baseLines[Math.min(...indexList)] + const endLine = wall.baseLines[Math.max(...indexList)] + const points = [startLine.x1, startLine.y1, endLine.x2, endLine.y2] + const size = calcLinePlaneSize({ x1: points[0], y1: points[1], x2: points[2], y2: points[3] }) + + //라인 좌표 조정. + startLine.set({ + x1: points[0], + y1: points[1], + x2: points[2], + y2: points[3], + startPoint: { x: points[0], y: points[1] }, + endPoint: { x: points[2], y: points[3] }, + }) + startLine.setCoords() + startLine.attributes.planeSize = size + startLine.attributes.actualSize = size + startLine.fire('setLength') + + //처리된 index 추가. + proceedPair.push(...indexList) + //조정된라인 baseLine에 추가. + baseLines.push(startLine) + }) + } } - - const checkLine = new fabric.Line([tempLine.startPoint.x, tempLine.startPoint.y, tempLine.endPoint.x, tempLine.endPoint.y], { - stroke: 'red', - strokeWidth: 2, - parentId: roof.id, - name: 'check', - selectable: false, - }) - canvas.add(checkLine) - canvas.renderAll() - temporaryLines.push(tempLine) - } - canvas - .getObjects() - .filter((object) => object.name === 'check') - .forEach((object) => canvas.remove(object)) - canvas.renderAll() + }) + } else { + baseLines = wall.baseLines } - - canvas - .getObjects() - .filter((object) => object.name === 'check') - .forEach((object) => canvas.remove(object)) - canvas.renderAll() - - return*/ - - const baseLines = wall.baseLines.filter((line) => line.attributes.planeSize > 0) const baseLinePoints = baseLines.map((line) => ({ x: line.x1, y: line.y1 })) - const checkWallPolygon = new QPolygon(baseLinePoints, {}) - let ridgeLines = [] let innerLines = [] /** 벽취합이 있는 경우 소매가 있다면 지붕 형상을 변경해야 한다. */ @@ -2094,6 +2089,136 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { } } + /** + * + * @param testPoint + * @param prevLine + * @param nextLine + * @param baseLines + */ + const getRidgeDrivePoint = (testPoint = [], prevLine, nextLine, baseLines) => { + if (prevLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES && nextLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES) return null + let prevPoint = null, + nextPoint = null + + const baseLinePoints = baseLines.map((line) => ({ x: line.x1, y: line.y1 })) + const checkWallPolygon = new QPolygon(baseLinePoints, {}) + const checkEdge = { vertex1: { x: testPoint[0], y: testPoint[1] }, vertex2: { x: testPoint[2], y: testPoint[3] } } + + let prevHasGable = false, + nextHasGable = false + if (prevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) { + const index = baseLines.findIndex((line) => line === prevLine) + const beforePrevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] + if (!beforePrevLine) { + prevPoint = null + } else if (beforePrevLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) { + const analyze = analyzeLine(beforePrevLine) + const roofEdge = { vertex1: { x: analyze.roofLine.x1, y: analyze.roofLine.y1 }, vertex2: { x: analyze.roofLine.x2, y: analyze.roofLine.y2 } } + const intersection = edgesIntersection(checkEdge, roofEdge) + if (intersection) { + prevHasGable = true + const distance = Math.sqrt((intersection.x - testPoint[0]) ** 2 + (intersection.y - testPoint[1]) ** 2) + prevPoint = { intersection, distance } + } + } else if (beforePrevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) { + let prevVector = getHalfAngleVector(beforePrevLine, prevLine) + + const prevCheckPoint = { + x: prevLine.x1 + prevVector.x * 10, + y: prevLine.y1 + prevVector.y * 10, + } + + let prevHipVector + + if (checkWallPolygon.inPolygon(prevCheckPoint)) { + prevHipVector = { x: prevVector.x, y: prevVector.y } + } else { + prevHipVector = { x: -prevVector.x, y: -prevVector.y } + } + + const prevHipEdge = { + vertex1: { x: prevLine.x1, y: prevLine.y1 }, + vertex2: { x: prevLine.x1 + prevHipVector.x * 10, y: prevLine.y1 + prevHipVector.y * 10 }, + } + + const intersection = edgesIntersection(prevHipEdge, checkEdge) + if (intersection) { + const checkVector = { x: Math.sign(prevLine.x1 - testPoint[0]), y: Math.sign(prevLine.y1 - testPoint[1]) } + const intersectVector = { x: Math.sign(prevLine.x1 - intersection.x), y: Math.sign(prevLine.y1 - intersection.y) } + if (checkVector.x === intersectVector.x && checkVector.y === intersectVector.y) { + const distance = Math.sqrt((intersection.x - testPoint[0]) ** 2 + (intersection.y - testPoint[1]) ** 2) + prevPoint = { intersection, distance } + } + } + } + } + + if (nextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) { + const index = baseLines.findIndex((line) => line === nextLine) + const afterNextLine = baseLines[(index + 1) % baseLines.length] + if (!afterNextLine) { + nextPoint = null + } else if (afterNextLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) { + const analyze = analyzeLine(afterNextLine) + const roofEdge = { vertex1: { x: analyze.roofLine.x1, y: analyze.roofLine.y1 }, vertex2: { x: analyze.roofLine.x2, y: analyze.roofLine.y2 } } + const intersection = edgesIntersection(checkEdge, roofEdge) + if (intersection) { + nextHasGable = true + const distance = Math.sqrt((intersection.x - testPoint[2]) ** 2 + (intersection.y - testPoint[3]) ** 2) + nextPoint = { intersection, distance } + } + } else if (afterNextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) { + let nextVector = getHalfAngleVector(nextLine, afterNextLine) + const nextCheckPoint = { + x: nextLine.x2 + nextVector.x * 10, + y: nextLine.y2 + nextVector.y * 10, + } + let nextHipVector + if (checkWallPolygon.inPolygon(nextCheckPoint)) { + nextHipVector = { x: nextVector.x, y: nextVector.y } + } else { + nextHipVector = { x: -nextVector.x, y: -nextVector.y } + } + + const nextHipEdge = { + vertex1: { x: nextLine.x2, y: nextLine.y2 }, + vertex2: { x: nextLine.x2 + nextHipVector.x * 10, y: nextLine.y2 + nextHipVector.y * 10 }, + } + const intersection = edgesIntersection(nextHipEdge, checkEdge) + if (intersection) { + const checkVector = { x: Math.sign(nextLine.x2 - testPoint[0]), y: Math.sign(nextLine.y2 - testPoint[1]) } + const intersectVector = { x: Math.sign(nextLine.x2 - intersection.x), y: Math.sign(nextLine.y2 - intersection.y) } + if (checkVector.x === intersectVector.x && checkVector.y === intersectVector.y) { + const distance = Math.sqrt((intersection.x - testPoint[0]) ** 2 + (intersection.y - testPoint[1]) ** 2) + nextPoint = { intersection, distance } + } + } + } + } + + if (prevHasGable || nextHasGable) { + const prevLength = Math.sqrt((prevLine.x1 - prevLine.x2) ** 2 + (prevLine.y1 - prevLine.y2) ** 2) + const nextLength = Math.sqrt((nextLine.x1 - nextLine.x2) ** 2 + (nextLine.y1 - nextLine.y2) ** 2) + if (prevPoint && prevHasGable && prevLength < nextLength) { + return prevPoint.intersection + } + if (nextPoint && nextHasGable && prevLength > nextLength) { + return nextPoint.intersection + } + } + + if (prevPoint && nextPoint) { + return prevPoint.distance < nextPoint.distance ? prevPoint.intersection : nextPoint.intersection + } else if (prevPoint) { + return prevPoint.intersection + } else if (nextPoint) { + return nextPoint.intersection + } else { + return null + } + } + /** * 지붕의 모양을 판단하여 각 변에 맞는 라인을 처리 한다. */ @@ -2139,24 +2264,10 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { const analyze = analyzeLine(currentLine) - //지붕선분을 innseLines에 추가한다. - const roofPoints = [analyze.roofLine.x1, analyze.roofLine.y1, analyze.roofLine.x2, analyze.roofLine.y2] - innerLines.push(drawRoofLine([roofPoints[0], roofPoints[1], roofPoints[2], roofPoints[3]], canvas, roof, textMode)) - - //지붕선을 임시선분에 추가한다. - /*const roofPoints = [analyze.roofLine.x1, analyze.roofLine.y1, analyze.roofLine.x2, analyze.roofLine.y2] - linesAnalysis.push({ - start: { x: roofPoints[0], y: roofPoints[1] }, // 시작점 - end: { x: roofPoints[2], y: roofPoints[3] }, // 끝점 - left: baseLines.findIndex((line) => line === prevLine), //이전라인 index - right: baseLines.findIndex((line) => line === currentLine), //현재라인 index - type: 'roof', //라인 타입 roof:지붕선(각도없음), hip:추녀마루(각도있음), ridge:(각도없음) - degree: 0, //라인 각도 - })*/ //양옆이 케라바가 아닐때 제외 - /*if (prevLine.attributes.type !== LINE_TYPE.WALLLINE.GABLE || nextLine.attributes.type !== LINE_TYPE.WALLLINE.GABLE) { + if (prevLine.attributes.type !== LINE_TYPE.WALLLINE.GABLE || nextLine.attributes.type !== LINE_TYPE.WALLLINE.GABLE) { return - }*/ + } const eavesLines = [] @@ -2281,24 +2392,25 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { const points1 = [analyze.roofLine.x1, analyze.roofLine.y1, intersectPoints1[0].intersect.x, intersectPoints1[0].intersect.y] const points2 = [analyze.roofLine.x2, analyze.roofLine.y2, intersectPoints2[0].intersect.x, intersectPoints2[0].intersect.y] - /*const line1 = drawHipLine(points1, canvas, roof, textMode, null, shedDegree, shedDegree) - const line2 = drawHipLine(points2, canvas, roof, textMode, null, shedDegree, shedDegree) - const line3 = drawRoofLine([analyze.roofLine.x1, analyze.roofLine.y1, analyze.roofLine.x2, analyze.roofLine.y2], canvas, roof, textMode) - innerLines.push(line1, line2, line3)*/ + const prevIndex = baseLines.findIndex((baseLine) => baseLine === prevLine) + const beforePrevIndex = baseLines.findIndex((baseLine) => baseLine === baseLines[(prevIndex - 1 + baseLines.length) % baseLines.length]) + const nextIndex = baseLines.findIndex((baseLine) => baseLine === nextLine) + const afterNextIndex = baseLines.findIndex((baseLine) => baseLine === baseLines[(nextIndex + 1) % baseLines.length]) + linesAnalysis.push( { start: { x: points1[0], y: points1[1] }, end: { x: points1[2], y: points1[3] }, - left: baseLines.findIndex((line) => line === prevLine), - right: baseLines.findIndex((line) => line === currentLine), + left: beforePrevIndex, + right: prevIndex, type: 'hip', degree: shedDegree, }, { start: { x: points2[0], y: points2[1] }, end: { x: points2[2], y: points2[3] }, - left: baseLines.findIndex((line) => line === currentLine), - right: baseLines.findIndex((line) => line === nextLine), + left: nextIndex, + right: afterNextIndex, type: 'hip', degree: shedDegree, }, @@ -2317,14 +2429,17 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { const analyze = analyzeLine(currentLine) - //지붕선을 innerLines에 추가한다. - const roofPoints = [analyze.roofLine.x1, analyze.roofLine.y1, analyze.roofLine.x2, analyze.roofLine.y2] - innerLines.push(drawRoofLine([roofPoints[0], roofPoints[1], roofPoints[2], roofPoints[3]], canvas, roof, textMode)) - //양옆이 처마가 아닐때 패스 - /*if (prevLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES || nextLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES) { - return false - }*/ + if (prevLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES || nextLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES) { + return + } + + const prevLineVector = { x: Math.sign(prevLine.x1 - prevLine.x2), y: Math.sign(prevLine.y1 - prevLine.y2) } + const nextLineVector = { x: Math.sign(nextLine.x1 - nextLine.x2), y: Math.sign(nextLine.y1 - nextLine.y2) } + //반절마루 생성불가이므로 지붕선만 추가하고 끝냄 + if (prevLineVector.x === nextLineVector.x && prevLineVector.y === nextLineVector.y) { + return + } const prevDegree = getDegreeByChon(prevLine.attributes.pitch) const nextDegree = getDegreeByChon(nextLine.attributes.pitch) @@ -2429,7 +2544,7 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { const point = [midPoint.x, midPoint.y, intersect.x, intersect.y] //마루가 최대로 뻗어나갈수 있는 포인트. null일때는 맞은편 지붕선 까지로 판단한다. - const drivePoint = getRidgeDrivePoints(point, prevLine, nextLine, baseLines, canvas) + const drivePoint = getRidgeDrivePoint(point, prevLine, nextLine, baseLines) console.log('drivePoint : ', drivePoint) if (drivePoint) { @@ -2485,9 +2600,6 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { } }) //3. 처마지붕 판단, 처마는 옆이 처마여야한다. - let eavesHipLines = [] - - // 처마지붕 사이의 추녀마루를 작성하기 위한 포인트를 계산. eaves.forEach((currentLine) => { let prevLine baseLines.forEach((baseLine, index) => { @@ -2498,10 +2610,6 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { const analyze = analyzeLine(currentLine) - //지붕선을 innerLines에 추가한다. - const roofPoints = [analyze.roofLine.x1, analyze.roofLine.y1, analyze.roofLine.x2, analyze.roofLine.y2] - innerLines.push(drawRoofLine([roofPoints[0], roofPoints[1], roofPoints[2], roofPoints[3]], canvas, roof, textMode)) - // 옆이 처마가 아니면 패스 if (prevLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES) { return @@ -2536,7 +2644,10 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { const intersect = edgesIntersection(lineEdge, hipEdge) if (intersect && isPointOnLineNew(roofLine, intersect)) { const intersectVector = { x: Math.sign(hipEdge.vertex1.x - intersect.x), y: Math.sign(hipEdge.vertex1.y - intersect.y) } - if (intersectVector.x === hipForwardVector.x && intersectVector.y === hipForwardVector.y) { + if ( + (intersectVector.x === hipForwardVector.x && intersectVector.y === hipForwardVector.y) || + (intersectVector.x === 0 && intersectVector.y === 0) + ) { const dx = hipEdge.vertex1.x - intersect.x const dy = hipEdge.vertex1.y - intersect.y const length = Math.sqrt(dx * dx + dy * dy) @@ -2553,12 +2664,22 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { isForwardPoints.sort((a, b) => a.length - b.length) isBackwardPoints.sort((a, b) => a.length - b.length) - const hipPoint = [ - isBackwardPoints[0].intersect.x, - isBackwardPoints[0].intersect.y, - isForwardPoints[0].intersect.x, - isForwardPoints[0].intersect.y, - ] + let hipPoint = [] + if (isForwardPoints.length > 0 && isBackwardPoints.length > 0) { + hipPoint = [isBackwardPoints[0].intersect.x, isBackwardPoints[0].intersect.y, isForwardPoints[0].intersect.x, isForwardPoints[0].intersect.y] + } else { + if (isBackwardPoints.length === 0 && isForwardPoints.length > 1) { + hipPoint = [isForwardPoints[0].intersect.x, isForwardPoints[0].intersect.y, isForwardPoints[1].intersect.x, isForwardPoints[1].intersect.y] + } + if (isForwardPoints.length === 0 && isBackwardPoints.length > 1) { + hipPoint = [ + isBackwardPoints[0].intersect.x, + isBackwardPoints[0].intersect.y, + isBackwardPoints[1].intersect.x, + isBackwardPoints[1].intersect.y, + ] + } + } linesAnalysis.push({ start: { x: hipPoint[0], y: hipPoint[1] }, @@ -2591,168 +2712,680 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { const nextLineVector = { x: Math.sign(nextLine.x1 - nextLine.x2), y: Math.sign(nextLine.y1 - nextLine.y2) } if (prevLineVector.x === nextLineVector.x && prevLineVector.y === nextLineVector.y) { //반절마루 생성불가이므로 지붕선만 추가하고 끝냄 + // innerLines.push(drawRoofLine(roofPoints, canvas, roof, textMode)) + return + } + const currentDegree = getDegreeByChon(currentLine.attributes.pitch) + const prevDegree = getDegreeByChon(prevLine.attributes.pitch) + const nextDegree = getDegreeByChon(nextLine.attributes.pitch) + const gableWidth = currentLine.attributes.width + + const roofVector = { x: Math.sign(roofPoints[2] - roofPoints[0]), y: Math.sign(roofPoints[3] - roofPoints[1]) } + const roofDx = roofPoints[0] - roofPoints[2] + const roofDy = roofPoints[1] - roofPoints[3] + const roofLength = Math.sqrt(roofDx * roofDx + roofDy * roofDy) + const lineLength = (roofLength - gableWidth) / 2 + const jerkinPoints = [] + jerkinPoints.push( + { x: roofPoints[0], y: roofPoints[1] }, + { x: roofPoints[0] + roofVector.x * lineLength, y: roofPoints[1] + roofVector.y * lineLength }, + ) + jerkinPoints.push({ x: jerkinPoints[1].x + roofVector.x * gableWidth, y: jerkinPoints[1].y + roofVector.y * gableWidth }) + jerkinPoints.push({ x: jerkinPoints[2].x + roofVector.x * lineLength, y: jerkinPoints[2].y + roofVector.y * lineLength }) + + if (gableWidth >= roofLength) { innerLines.push(drawRoofLine(roofPoints, canvas, roof, textMode)) - } else { - const midX = (roofPoints[0] + roofPoints[2]) / 2 - const midY = (roofPoints[1] + roofPoints[3]) / 2 + } else if (jerkinPoints.length === 4) { + const gablePoint1 = [jerkinPoints[0].x, jerkinPoints[0].y, jerkinPoints[1].x, jerkinPoints[1].y] + const gablePoint2 = [jerkinPoints[1].x, jerkinPoints[1].y, jerkinPoints[2].x, jerkinPoints[2].y] + const gablePoint3 = [jerkinPoints[2].x, jerkinPoints[2].y, jerkinPoints[3].x, jerkinPoints[3].y] + const gableLength1 = Math.sqrt(Math.pow(gablePoint1[2] - gablePoint1[0], 2) + Math.pow(gablePoint1[3] - gablePoint1[1], 2)) + const gableLength2 = Math.sqrt(Math.pow(gablePoint2[2] - gablePoint2[0], 2) + Math.pow(gablePoint2[3] - gablePoint2[1], 2)) + const gableLength3 = Math.sqrt(Math.pow(gablePoint3[2] - gablePoint3[0], 2) + Math.pow(gablePoint3[3] - gablePoint3[1], 2)) + if (gableLength1 >= 1) { + innerLines.push(drawHipLine(gablePoint1, canvas, roof, textMode, null, prevDegree, prevDegree)) + } + if (gableLength2 >= 1) { + innerLines.push(drawRoofLine(gablePoint2, canvas, roof, textMode)) + } + if (gableLength3 >= 1) { + innerLines.push(drawHipLine(gablePoint3, canvas, roof, textMode, null, nextDegree, nextDegree)) + } - const currentDegree = getDegreeByChon(currentLine.attributes.pitch) - const prevDegree = getDegreeByChon(prevLine.attributes.pitch) - const nextDegree = getDegreeByChon(nextLine.attributes.pitch) - const gableWidth = currentLine.attributes.width - - const roofVector = { x: Math.sign(roofPoints[2] - roofPoints[0]), y: Math.sign(roofPoints[3] - roofPoints[1]) } - const roofDx = roofPoints[0] - roofPoints[2] - const roofDy = roofPoints[1] - roofPoints[3] - const roofLength = Math.sqrt(roofDx * roofDx + roofDy * roofDy) - const lineLength = (roofLength - gableWidth) / 2 - const jerkinPoints = [] - jerkinPoints.push( - { x: roofPoints[0], y: roofPoints[1] }, - { x: roofPoints[0] + roofVector.x * lineLength, y: roofPoints[1] + roofVector.y * lineLength }, - ) - jerkinPoints.push({ x: jerkinPoints[1].x + roofVector.x * gableWidth, y: jerkinPoints[1].y + roofVector.y * gableWidth }) - jerkinPoints.push({ x: jerkinPoints[2].x + roofVector.x * lineLength, y: jerkinPoints[2].y + roofVector.y * lineLength }) - - if (gableWidth >= roofLength) { - innerLines.push(drawRoofLine(roofPoints, canvas, roof, textMode)) - } else if (jerkinPoints.length === 4) { - const gablePoint1 = [jerkinPoints[0].x, jerkinPoints[0].y, jerkinPoints[1].x, jerkinPoints[1].y] - const gablePoint2 = [jerkinPoints[1].x, jerkinPoints[1].y, jerkinPoints[2].x, jerkinPoints[2].y] - const gablePoint3 = [jerkinPoints[2].x, jerkinPoints[2].y, jerkinPoints[3].x, jerkinPoints[3].y] - const gableLength1 = Math.sqrt(Math.pow(gablePoint1[2] - gablePoint1[0], 2) + Math.pow(gablePoint1[3] - gablePoint1[1], 2)) - const gableLength2 = Math.sqrt(Math.pow(gablePoint2[2] - gablePoint2[0], 2) + Math.pow(gablePoint2[3] - gablePoint2[1], 2)) - const gableLength3 = Math.sqrt(Math.pow(gablePoint3[2] - gablePoint3[0], 2) + Math.pow(gablePoint3[3] - gablePoint3[1], 2)) - if (gableLength1 >= 1) { - innerLines.push(drawHipLine(gablePoint1, canvas, roof, textMode, null, prevDegree, prevDegree)) + const currentRoofLine = analyze.roofLine + let prevRoofLine, nextRoofLine + roof.lines.forEach((roofLine, index) => { + if (roofLine === currentRoofLine) { + nextRoofLine = roof.lines[(index + 1) % roof.lines.length] + prevRoofLine = roof.lines[(index - 1 + roof.lines.length) % roof.lines.length] } - if (gableLength2 >= 1) { - innerLines.push(drawRoofLine(gablePoint2, canvas, roof, textMode)) + }) + + /** 이전, 다음라인의 사잇각의 vector를 구한다. */ + let prevVector = getHalfAngleVector(prevRoofLine, currentRoofLine) + let nextVector = getHalfAngleVector(currentRoofLine, nextRoofLine) + + /** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ + const prevCheckPoint = { + x: currentRoofLine.x1 + prevVector.x * 10, + y: currentRoofLine.y1 + prevVector.y * 10, + } + /** 다음 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ + const nextCheckPoint = { + x: currentRoofLine.x2 + nextVector.x * 10, + y: currentRoofLine.y2 + nextVector.y * 10, + } + + let prevHipVector, nextHipVector + + if (roof.inPolygon(prevCheckPoint)) { + prevHipVector = { x: prevVector.x, y: prevVector.y } + } else { + prevHipVector = { x: -prevVector.x, y: -prevVector.y } + } + + /** 다음 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ + if (roof.inPolygon(nextCheckPoint)) { + nextHipVector = { x: nextVector.x, y: nextVector.y } + } else { + nextHipVector = { x: -nextVector.x, y: -nextVector.y } + } + + const prevHipEdge = { + vertex1: { x: gablePoint2[0], y: gablePoint2[1] }, + vertex2: { x: gablePoint2[0] + prevHipVector.x * gableWidth, y: gablePoint2[1] + prevHipVector.y * gableWidth }, + } + const nextHipEdge = { + vertex1: { x: gablePoint2[2], y: gablePoint2[3] }, + vertex2: { x: gablePoint2[2] + nextHipVector.x * gableWidth, y: gablePoint2[3] + nextHipVector.y * gableWidth }, + } + + const hipIntersection = edgesIntersection(prevHipEdge, nextHipEdge) + if (hipIntersection) { + const prevHipPoint = [prevHipEdge.vertex1.x, prevHipEdge.vertex1.y, hipIntersection.x, hipIntersection.y] + const nextHipPoint = [nextHipEdge.vertex1.x, nextHipEdge.vertex1.y, hipIntersection.x, hipIntersection.y] + innerLines.push(drawHipLine(prevHipPoint, canvas, roof, textMode, null, currentDegree, currentDegree)) + innerLines.push(drawHipLine(nextHipPoint, canvas, roof, textMode, null, currentDegree, currentDegree)) + + const midPoint = { x: hipIntersection.x, y: hipIntersection.y } + const checkEdge = { + vertex1: { x: midPoint.x, y: midPoint.y }, + vertex2: { x: midPoint.x + -analyze.roofVector.x * 10000, y: midPoint.y + -analyze.roofVector.y * 10000 }, } - if (gableLength3 >= 1) { - innerLines.push(drawHipLine(gablePoint3, canvas, roof, textMode, null, nextDegree, nextDegree)) - } - - const currentRoofLine = analyze.roofLine - let prevRoofLine, nextRoofLine - roof.lines.forEach((roofLine, index) => { - if (roofLine === currentRoofLine) { - nextRoofLine = roof.lines[(index + 1) % roof.lines.length] - prevRoofLine = roof.lines[(index - 1 + roof.lines.length) % roof.lines.length] - } - }) - - /** 이전, 다음라인의 사잇각의 vector를 구한다. */ - let prevVector = getHalfAngleVector(prevRoofLine, currentRoofLine) - let nextVector = getHalfAngleVector(currentRoofLine, nextRoofLine) - - /** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ - const prevCheckPoint = { - x: currentRoofLine.x1 + prevVector.x * 10, - y: currentRoofLine.y1 + prevVector.y * 10, - } - /** 다음 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ - const nextCheckPoint = { - x: currentRoofLine.x2 + nextVector.x * 10, - y: currentRoofLine.y2 + nextVector.y * 10, - } - - let prevHipVector, nextHipVector - - if (roof.inPolygon(prevCheckPoint)) { - prevHipVector = { x: prevVector.x, y: prevVector.y } - } else { - prevHipVector = { x: -prevVector.x, y: -prevVector.y } - } - - /** 다음 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/ - if (roof.inPolygon(nextCheckPoint)) { - nextHipVector = { x: nextVector.x, y: nextVector.y } - } else { - nextHipVector = { x: -nextVector.x, y: -nextVector.y } - } - - const prevHipEdge = { - vertex1: { x: gablePoint2[0], y: gablePoint2[1] }, - vertex2: { x: gablePoint2[0] + prevHipVector.x * gableWidth, y: gablePoint2[1] + prevHipVector.y * gableWidth }, - } - const nextHipEdge = { - vertex1: { x: gablePoint2[2], y: gablePoint2[3] }, - vertex2: { x: gablePoint2[2] + nextHipVector.x * gableWidth, y: gablePoint2[3] + nextHipVector.y * gableWidth }, - } - - const hipIntersection = edgesIntersection(prevHipEdge, nextHipEdge) - if (hipIntersection) { - const prevHipPoint = [prevHipEdge.vertex1.x, prevHipEdge.vertex1.y, hipIntersection.x, hipIntersection.y] - const nextHipPoint = [nextHipEdge.vertex1.x, nextHipEdge.vertex1.y, hipIntersection.x, hipIntersection.y] - innerLines.push(drawHipLine(prevHipPoint, canvas, roof, textMode, null, currentDegree, currentDegree)) - innerLines.push(drawHipLine(nextHipPoint, canvas, roof, textMode, null, currentDegree, currentDegree)) - - const midPoint = { x: hipIntersection.x, y: hipIntersection.y } - const checkEdge = { - vertex1: { x: midPoint.x, y: midPoint.y }, - vertex2: { x: midPoint.x + -analyze.roofVector.x * 10000, y: midPoint.y + -analyze.roofVector.y * 10000 }, - } - const intersections = [] - roof.lines - .filter((line) => line !== currentRoofLine) - .forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersect = edgesIntersection(lineEdge, checkEdge) - if (intersect && isPointOnLineNew(line, intersect)) { - const distance = Math.sqrt(Math.pow(midPoint.x - intersect.x, 2) + Math.pow(midPoint.y - intersect.y, 2)) - intersections.push({ intersect, distance }) - } - }) - intersections.sort((a, b) => a.distance - b.distance) - if (intersections.length > 0) { - const intersect = intersections[0].intersect - const point = [midPoint.x, midPoint.y, intersect.x, intersect.y] - - //마루가 최대로 뻗어나갈수 있는 포인트. null일때는 맞은편 지붕선 까지로 판단한다. - const drivePoint = getRidgeDrivePoints(point, prevLine, nextLine, baseLines, canvas) - - console.log('drivePoint : ', drivePoint) - if (drivePoint) { - point[2] = drivePoint.x - point[3] = drivePoint.y + const intersections = [] + roof.lines + .filter((line) => line !== currentRoofLine) + .forEach((line) => { + const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const intersect = edgesIntersection(lineEdge, checkEdge) + if (intersect && isPointOnLineNew(line, intersect)) { + const distance = Math.sqrt(Math.pow(midPoint.x - intersect.x, 2) + Math.pow(midPoint.y - intersect.y, 2)) + intersections.push({ intersect, distance }) } + }) + intersections.sort((a, b) => a.distance - b.distance) + if (intersections.length > 0) { + const intersect = intersections[0].intersect + const point = [midPoint.x, midPoint.y, intersect.x, intersect.y] - linesAnalysis.push({ - start: { x: point[0], y: point[1] }, - end: { x: point[2], y: point[3] }, - left: baseLines.findIndex((line) => line === prevLine), - right: baseLines.findIndex((line) => line === nextLine), - type: 'ridge', - degree: 0, - }) + //마루가 최대로 뻗어나갈수 있는 포인트. null일때는 맞은편 지붕선 까지로 판단한다. + const drivePoint = getRidgeDrivePoint(point, prevLine, nextLine, baseLines) + if (drivePoint) { + point[2] = drivePoint.x + point[3] = drivePoint.y } + + linesAnalysis.push({ + start: { x: point[0], y: point[1] }, + end: { x: point[2], y: point[3] }, + left: baseLines.findIndex((line) => line === prevLine), + right: baseLines.findIndex((line) => line === nextLine), + type: 'ridge', + degree: 0, + }) } } } }) //5. 케라바 판단, 케라바는 양옆이 처마여야 한다. - gables.forEach((currentLine) => {}) + gables.forEach((currentLine) => { + let prevLine, nextLine + baseLines.forEach((baseLine, index) => { + if (baseLine === currentLine) { + nextLine = baseLines[(index + 1) % baseLines.length] + prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] + } + }) + + //양옆이 처마가 아닐때 패스 + if (prevLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES || nextLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES) { + return + } + + const prevLineVector = { x: Math.sign(prevLine.x1 - prevLine.x2), y: Math.sign(prevLine.y1 - prevLine.y2) } + const nextLineVector = { x: Math.sign(nextLine.x1 - nextLine.x2), y: Math.sign(nextLine.y1 - nextLine.y2) } + + const inPolygonPoint = { + x: (currentLine.x1 + currentLine.x2) / 2 + Math.sign(nextLine.x2 - nextLine.x1), + y: (currentLine.y1 + currentLine.y2) / 2 + Math.sign(nextLine.y2 - nextLine.y1), + } + + const checkCurrLine = new fabric.Line([currentLine.x1, currentLine.y1, currentLine.x2, currentLine.y2], { + stroke: 'cyan', + strokeWidth: 4, + parentId: roofId, + name: 'check', + }) + canvas.add(checkCurrLine) + canvas.renderAll() + + //좌우 라인이 서로 다른방향일때 + if ((prevLineVector.x !== nextLineVector.x || prevLineVector.y !== nextLineVector.y) && checkWallPolygon.inPolygon(inPolygonPoint)) { + const analyze = analyzeLine(currentLine) + const roofLine = analyze.roofLine + + let beforePrevLine, afterNextLine + baseLines.forEach((baseLine, index) => { + if (baseLine === prevLine) { + beforePrevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] + } + if (baseLine === nextLine) { + afterNextLine = baseLines[(index + 1) % baseLines.length] + } + }) + + const currentVector = { x: Math.sign(currentLine.x1 - currentLine.x2), y: Math.sign(currentLine.y1 - currentLine.y2) } + // 마루 진행 최대 길이 + let prevDistance = 0, + nextDistance = 0 + + if (beforePrevLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) { + const beforeVector = { x: Math.sign(beforePrevLine.x1 - beforePrevLine.x2), y: Math.sign(beforePrevLine.y1 - beforePrevLine.y2) } + let distance = Math.sqrt(Math.pow(prevLine.x2 - prevLine.x1, 2) + Math.pow(prevLine.y2 - prevLine.y1, 2)) + currentLine.attributes.offset + if (beforeVector.x === currentVector.x && beforeVector.y === currentVector.y) { + prevDistance = distance - beforePrevLine.attributes.offset + } else { + prevDistance = distance + beforePrevLine.attributes.offset + } + } + if (afterNextLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) { + const afterVector = { x: Math.sign(afterNextLine.x1 - afterNextLine.x2), y: Math.sign(afterNextLine.y1 - afterNextLine.y2) } + let distance = Math.sqrt(Math.pow(nextLine.x2 - nextLine.x1, 2) + Math.pow(nextLine.y2 - nextLine.y1, 2)) + currentLine.attributes.offset + if (afterVector.x === currentVector.x && afterVector.y === currentVector.y) { + nextDistance = distance - afterNextLine.attributes.offset + } else { + nextDistance = distance + afterNextLine.attributes.offset + } + } + + //좌우 선분 중 긴 쪽이 기준선이 된다 + const stdLine = nextDistance <= prevDistance ? prevLine : nextLine + let stdDistance = nextDistance <= prevDistance ? prevDistance : nextDistance + const stdAnalyze = analyzeLine(stdLine) + let stdPoints = [] + + const stdPrevLine = baseLines[(baseLines.indexOf(stdLine) - 1 + baseLines.length) % baseLines.length] + const stdNextLine = baseLines[(baseLines.indexOf(stdLine) + 1) % baseLines.length] + const stdVector = { x: Math.sign(stdLine.x1 - stdLine.x2), y: Math.sign(stdLine.y1 - stdLine.y2) } + const stdPrevVector = { x: Math.sign(stdPrevLine.x1 - stdPrevLine.x2), y: Math.sign(stdPrevLine.y1 - stdPrevLine.y2) } + const stdNextVector = { x: Math.sign(stdNextLine.x1 - stdNextLine.x2), y: Math.sign(stdNextLine.y1 - stdNextLine.y2) } + if (stdPrevVector.x === stdNextVector.x && stdPrevVector.y === stdNextVector.y) { + stdPoints = [ + stdLine.x1 + stdVector.x * stdPrevLine.attributes.offset, + stdLine.y1 + stdVector.y * stdPrevLine.attributes.offset, + stdLine.x2 + stdVector.x * stdNextLine.attributes.offset, + stdLine.y2 + stdVector.y * stdNextLine.attributes.offset, + ] + } else { + stdPoints = [ + stdLine.x1 + stdVector.x * stdPrevLine.attributes.offset, + stdLine.y1 + stdVector.y * stdPrevLine.attributes.offset, + stdLine.x2 + -stdVector.x * stdNextLine.attributes.offset, + stdLine.y2 + -stdVector.y * stdNextLine.attributes.offset, + ] + } + const checkLine = new fabric.Line([stdLine.x1, stdLine.y1, stdLine.x2, stdLine.y2], { + stroke: 'red', + strokeWidth: 4, + parentId: roofId, + name: 'check', + }) + canvas.add(checkLine) + canvas.renderAll() + + //기준지붕선의 반대쪽선 + const oppositeLine = [] + + const startX = Math.min(stdLine.x1, stdLine.x2) + const endX = Math.max(stdLine.x1, stdLine.x2) + const startY = Math.min(stdLine.y1, stdLine.y2) + const endY = Math.max(stdLine.y1, stdLine.y2) + + baseLines + .filter((line) => line !== stdLine && line !== currentLine) + .filter((line) => { + const vector = { x: Math.sign(stdLine.x1 - stdLine.x2), y: Math.sign(stdLine.y1 - stdLine.y2) } + const lineVector = { x: Math.sign(line.x1 - line.x2), y: Math.sign(line.y1 - line.y2) } + return (vector.x === lineVector.x && vector.y !== lineVector.y) || (vector.x !== lineVector.x && vector.y === lineVector.y) + }) + .forEach((line) => { + const lineStartX = Math.min(line.x1, line.x2) + const lineEndX = Math.max(line.x1, line.x2) + const lineStartY = Math.min(line.y1, line.y2) + const lineEndY = Math.max(line.y1, line.y2) + + if (stdAnalyze.isHorizontal) { + //full overlap + if (lineStartX <= startX && endX <= lineEndX) { + oppositeLine.push({ line, distance: Math.abs(lineStartY - startY) }) + } + if ( + (startX < lineStartX && lineStartX < endX) || + (startX < lineStartX && lineEndX < endX) || + (lineStartX === startX && lineEndX <= endX) || + (lineEndX === endX && lineStartX <= startX) + ) { + oppositeLine.push({ line, distance: Math.abs(lineStartY - startY) }) + } + } + if (stdAnalyze.isVertical) { + //full overlap + if (lineStartY <= startY && endY <= lineEndY) { + oppositeLine.push({ line, distance: Math.abs(lineStartX - startX) }) + } + if ( + (startY < lineStartY && lineStartY < endY) || + (startY < lineEndY && lineEndY < endY) || + (lineStartY === startY && lineEndY <= endY) || + (lineEndY === endY && lineStartY <= startY) + ) { + oppositeLine.push({ line, distance: Math.abs(lineStartX - startX) }) + } + } + }) + + if (oppositeLine.length > 0) { + const ridgePoints = [] + //지붕선 출발 지점 확인을 위한 기준 포인트 + let ridgeStdPoint = { x: (currentLine.x1 + currentLine.x2) / 2, y: (currentLine.y1 + currentLine.y2) / 2 } + + oppositeLine.sort((a, b) => a.distance - b.distance) + oppositeLine.forEach((opposite) => { + const oppLine = opposite.line + const checkOppLine = new fabric.Line([oppLine.x1, oppLine.y1, oppLine.x2, oppLine.y2], { + stroke: 'yellow', + strokeW: 4, + parentId: roofId, + name: 'check', + }) + canvas.add(checkOppLine) + canvas.renderAll() + + const oppIndex = baseLines.findIndex((line) => line === oppLine) + //마주하는 라인의 이전 다음 라인. + const oppPrevLine = baseLines[(oppIndex - 1 + baseLines.length) % baseLines.length] + const oppNextLine = baseLines[(oppIndex + 1) % baseLines.length] + const oppAnalyze = analyzeLine(oppLine) + const oppVector = { x: Math.sign(oppLine.x2 - oppLine.x1), y: Math.sign(oppLine.y2 - oppLine.y1) } + let ridgePoint + + const oppPrevVector = { x: Math.sign(oppPrevLine.x1 - oppPrevLine.x2), y: Math.sign(oppPrevLine.y1 - oppPrevLine.y2) } + const oppNextVector = { x: Math.sign(oppNextLine.x1 - oppNextLine.x2), y: Math.sign(oppNextLine.y1 - oppNextLine.y2) } + if (oppPrevVector.x === oppNextVector.x && oppPrevVector.y === oppNextVector.y) { + const addOffsetX1 = oppPrevVector.y * oppPrevLine.attributes.offset + const addOffsetY1 = -oppPrevVector.x * oppPrevLine.attributes.offset + const addOffsetX2 = oppPrevVector.y * oppNextLine.attributes.offset + const addOffsetY2 = -oppPrevVector.x * oppNextLine.attributes.offset + ridgePoint = [oppLine.x1 + addOffsetX1, oppLine.y1 + addOffsetY1, oppLine.x2 + addOffsetX2, oppLine.y2 + addOffsetY2] + } else { + const inPolygonPoint = { + x: (oppLine.x1 + oppLine.x2) / 2 + Math.sign(oppNextLine.x2 - oppNextLine.x1), + y: (oppLine.y1 + oppLine.y2) / 2 + Math.sign(oppNextLine.y2 - oppNextLine.y1), + } + if (checkWallPolygon.inPolygon(inPolygonPoint)) { + ridgePoint = [ + oppLine.x1 + -oppVector.x * oppPrevLine.attributes.offset, + oppLine.y1 + -oppVector.y * oppPrevLine.attributes.offset, + oppLine.x2 + oppVector.x * oppNextLine.attributes.offset, + oppLine.y2 + oppVector.y * oppNextLine.attributes.offset, + ] + } else { + ridgePoint = [ + oppLine.x1 + oppVector.x * oppPrevLine.attributes.offset, + oppLine.y1 + oppVector.y * oppPrevLine.attributes.offset, + oppLine.x2 + -oppVector.x * oppNextLine.attributes.offset, + oppLine.y2 + -oppVector.y * oppNextLine.attributes.offset, + ] + } + } + if (stdAnalyze.isHorizontal) { + ridgePoint[1] = (stdLine.y1 + oppLine.y1) / 2 + ridgePoint[3] = (stdLine.y2 + oppLine.y2) / 2 + } + if (stdAnalyze.isVertical) { + ridgePoint[0] = (stdLine.x1 + oppLine.x1) / 2 + ridgePoint[2] = (stdLine.x2 + oppLine.x2) / 2 + } + + //지붕선 출발 지점과의 거리를 통해 가까운쪽이 start point로 처리. + // const distRidgeStandard1 = Math.sqrt(Math.pow(ridgeStdPoint.x - ridgePoint[0], 2) + Math.pow(ridgeStdPoint.y - ridgePoint[1], 2)) + // const distRidgeStandard2 = Math.sqrt(Math.pow(ridgeStdPoint.x - ridgePoint[2], 2) + Math.pow(ridgeStdPoint.y - ridgePoint[3], 2)) + // if (distRidgeStandard1 > distRidgeStandard2) { + // ridgePoint = [ridgePoint[2], ridgePoint[3], ridgePoint[0], ridgePoint[1]] + // } + + if ( + (oppPrevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES || oppNextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) && + !(oppPrevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES && oppNextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) + ) { + const oppLineLength = Math.sqrt(Math.pow(oppLine.x2 - oppLine.x1, 2) + Math.pow(oppLine.y2 - oppLine.y1, 2)) + const stdLineLength = Math.sqrt(Math.pow(stdLine.x2 - stdLine.x1, 2) + Math.pow(stdLine.y2 - stdLine.y1, 2)) + //기준선이 반대선보다 길이가 짧을때는 추녀마루에 대한 교점 처리 패스 + if (stdLineLength >= oppLineLength) { + if (oppPrevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) { + //처마라인이 아닌 반대쪽부터 마루선을 시작하도록 포인트 변경 + const oppNextAnalyze = analyzeLine(oppNextLine) + if (oppNextAnalyze.isHorizontal) { + const dist1 = Math.abs(oppNextLine.y1 - ridgePoint[1]) + const dist2 = Math.abs(oppNextLine.y1 - ridgePoint[3]) + if (dist1 > dist2) { + ridgePoint = [ridgePoint[2], ridgePoint[3], ridgePoint[0], ridgePoint[1]] + } + } + if (oppNextAnalyze.isVertical) { + const dist1 = Math.abs(oppNextLine.x1 - ridgePoint[0]) + const dist2 = Math.abs(oppNextLine.x1 - ridgePoint[2]) + if (dist1 > dist2) { + ridgePoint = [ridgePoint[2], ridgePoint[3], ridgePoint[0], ridgePoint[1]] + } + } + + const checkEdge = { vertex1: { x: ridgePoint[0], y: ridgePoint[1] }, vertex2: { x: ridgePoint[2], y: ridgePoint[3] } } + const checkVector = getHalfAngleVector(oppLine, oppPrevLine) + const checkPoint = { + x: oppLine.x1 + (checkVector.x * 10) / 2, + y: oppLine.y1 + (checkVector.y * 10) / 2, + } + + let hipVector + if (checkWallPolygon.inPolygon(checkPoint)) { + hipVector = { x: checkVector.x, y: checkVector.y } + } else { + hipVector = { x: -checkVector.x, y: -checkVector.y } + } + + const ridgeVector = { x: Math.sign(ridgePoint[0] - ridgePoint[2]), y: Math.sign(ridgePoint[1] - ridgePoint[3]) } + const hipEdge = { vertex1: { x: oppLine.x1, y: oppLine.y1 }, vertex2: { x: oppLine.x1 + hipVector.x, y: oppLine.y1 + hipVector.y } } + const intersect = edgesIntersection(hipEdge, checkEdge) + if (intersect) { + const intersectVector = { x: Math.sign(ridgePoint[0] - intersect.x), y: Math.sign(ridgePoint[1] - intersect.y) } + if (ridgeVector.x === intersectVector.x && ridgeVector.y === intersectVector.y) { + ridgePoint = [ridgePoint[0], ridgePoint[1], intersect.x, intersect.y] + } else { + ridgePoint = [ridgePoint[2], ridgePoint[3], ridgePoint[2], ridgePoint[3]] + } + } + } + if (oppNextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) { + //처마라인이 아닌 반대쪽부터 마루선을 시작하도록 포인트 변경 + const oppPrevAnalyze = analyzeLine(oppPrevLine) + if (oppPrevAnalyze.isHorizontal) { + const dist1 = Math.abs(oppPrevLine.y1 - ridgePoint[1]) + const dist2 = Math.abs(oppPrevLine.y1 - ridgePoint[3]) + if (dist1 > dist2) { + ridgePoint = [ridgePoint[2], ridgePoint[3], ridgePoint[0], ridgePoint[1]] + } + } + if (oppPrevAnalyze.isVertical) { + const dist1 = Math.abs(oppPrevLine.x1 - ridgePoint[0]) + const dist2 = Math.abs(oppPrevLine.x1 - ridgePoint[2]) + if (dist1 > dist2) { + ridgePoint = [ridgePoint[2], ridgePoint[3], ridgePoint[0], ridgePoint[1]] + } + } + const checkEdge = { vertex1: { x: ridgePoint[0], y: ridgePoint[1] }, vertex2: { x: ridgePoint[2], y: ridgePoint[3] } } + const checkVector = getHalfAngleVector(oppLine, oppNextLine) + const checkPoint = { x: oppLine.x2 + (checkVector.x * 10) / 2, y: oppLine.y2 + (checkVector.y * 10) / 2 } + + let hipVector + if (checkWallPolygon.inPolygon(checkPoint)) { + hipVector = { x: checkVector.x, y: checkVector.y } + } else { + hipVector = { x: -checkVector.x, y: -checkVector.y } + } + + const ridgeVector = { x: Math.sign(ridgePoint[0] - ridgePoint[2]), y: Math.sign(ridgePoint[1] - ridgePoint[3]) } + const hipEdge = { vertex1: { x: oppLine.x2, y: oppLine.y2 }, vertex2: { x: oppLine.x2 + hipVector.x, y: oppLine.y2 + hipVector.y } } + const intersect = edgesIntersection(hipEdge, checkEdge) + if (intersect) { + const intersectVector = { x: Math.sign(ridgePoint[0] - intersect.x), y: Math.sign(ridgePoint[1] - intersect.y) } + if (ridgeVector.x === intersectVector.x && ridgeVector.y === intersectVector.y) { + ridgePoint = [ridgePoint[0], ridgePoint[1], intersect.x, intersect.y] + } else { + ridgePoint = [ridgePoint[2], ridgePoint[3], ridgePoint[2], ridgePoint[3]] + } + } + } + } + } + + const stdMinX = Math.min(stdPoints[0], stdPoints[2]) + const stdMaxX = Math.max(stdPoints[0], stdPoints[2]) + const stdMinY = Math.min(stdPoints[1], stdPoints[3]) + const stdMaxY = Math.max(stdPoints[1], stdPoints[3]) + + const rMinX = Math.min(ridgePoint[0], ridgePoint[2]) + const rMaxX = Math.max(ridgePoint[0], ridgePoint[2]) + const rMinY = Math.min(ridgePoint[1], ridgePoint[3]) + const rMaxY = Math.max(ridgePoint[1], ridgePoint[3]) + + const overlapMinX = Math.max(stdMinX, rMinX) + const overlapMaxX = Math.min(stdMaxX, rMaxX) + const overlapMinY = Math.max(stdMinY, rMinY) + const overlapMaxY = Math.min(stdMaxY, rMaxY) + + if (stdAnalyze.isHorizontal) { + if (overlapMinX > overlapMaxX) { + return + } + ridgePoint = [overlapMinX, ridgePoint[1], overlapMaxX, ridgePoint[3]] + } + if (stdAnalyze.isVertical) { + if (overlapMinY > overlapMaxY) { + return + } + ridgePoint = [ridgePoint[0], overlapMinY, ridgePoint[2], overlapMaxY] + } + + ridgePoints.push({ point: ridgePoint, left: stdLine, right: oppLine }) + canvas + .getObjects() + .filter((obj) => obj.name === 'check') + .forEach((obj) => canvas.remove(obj)) + canvas.renderAll() + }) + + ridgePoints.forEach((r) => { + let point = r.point + const inPolygon1 = + roof.inPolygon({ x: point[0], y: point[1] }) || + roof.lines.find((line) => isPointOnLineNew(line, { x: point[0], y: point[1] })) !== undefined + const inPolygon2 = + roof.inPolygon({ x: point[2], y: point[3] }) || + roof.lines.find((line) => isPointOnLineNew(line, { x: point[2], y: point[3] })) !== undefined + + //시작점이 지붕 밖에 있을때 vector내의 가까운 지붕선으로 변경 + if (!inPolygon1) { + const checkVector = { x: Math.sign(point[0] - point[2]), y: Math.sign(point[1] - point[3]) } + const checkEdge = { vertex1: { x: point[0], y: point[1] }, vertex2: { x: point[2], y: point[3] } } + let minDistance = Infinity + let correctPoint + roof.lines.forEach((line) => { + const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const intersect = edgesIntersection(lineEdge, checkEdge) + if (intersect) { + const distance = Math.sqrt(Math.pow(intersect.x - point[0], 2) + Math.pow(intersect.y - point[1], 2)) + const intersectVector = { x: Math.sign(point[0] - intersect.x), y: Math.sign(point[1] - intersect.y) } + if (distance < minDistance && intersectVector.x === checkVector.x && intersectVector.y === checkVector.y) { + minDistance = distance + correctPoint = intersect + } + } + }) + if (correctPoint) { + point = [correctPoint.x, correctPoint.y, point[2], point[3]] + } + } + + //종료점이 지붕밖에 있을때 vector내의 가까운 지붕선으로 변경 + if (!inPolygon2) { + const checkVector = { x: Math.sign(point[2] - point[0]), y: Math.sign(point[3] - point[1]) } + const checkEdge = { vertex1: { x: point[2], y: point[3] }, vertex2: { x: point[0], y: point[1] } } + let minDistance = Infinity + let correctPoint + roof.lines.forEach((line) => { + const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const intersect = edgesIntersection(lineEdge, checkEdge) + if (intersect) { + const distance = Math.sqrt(Math.pow(intersect.x - point[2], 2) + Math.pow(intersect.y - point[3], 2)) + const intersectVector = { x: Math.sign(point[2] - intersect.x), y: Math.sign(point[3] - intersect.y) } + if (distance < minDistance && intersectVector.x === checkVector.x && intersectVector.y === checkVector.y) { + minDistance = distance + correctPoint = intersect + } + } + }) + if (correctPoint) { + point = [point[0], point[1], correctPoint.x, correctPoint.y] + } + } + + const ridgeLength = Math.sqrt(Math.pow(point[2] - point[0], 2) + Math.pow(point[3] - point[1], 2)) + + if (ridgeLength > EPSILON) { + linesAnalysis.push({ + start: { x: point[0], y: point[1] }, + end: { x: point[2], y: point[3] }, + left: baseLines.findIndex((line) => line === r.left), + right: baseLines.findIndex((line) => line === r.right), + type: 'ridge', + degree: 0, + }) + } + }) + } + } + //반절마루 생성불가이므로 지붕선 분기를 해야하는지 확인 후 처리. + if (prevLineVector.x === nextLineVector.x && prevLineVector.y === nextLineVector.y) { + const analyze = analyzeLine(currentLine) + const roofLine = analyze.roofLine + const checkRoof = new fabric.Line([roofLine.x1, roofLine.y1, roofLine.x2, roofLine.y2], { + stroke: 'red', + strokeWidth: 4, + parentId: roofId, + name: 'check', + }) + canvas.add(checkRoof) + const checkLine = new fabric.Line([currentLine.x1, currentLine.y1, currentLine.x2, currentLine.y2], { + stroke: 'blue', + strokeWidth: 4, + parentId: roofId, + name: 'check', + }) + canvas.add(checkLine) + canvas.renderAll() + + const checkVector = { x: Math.sign(prevLine.y2 - prevLine.y1), y: Math.sign(prevLine.x1 - prevLine.x2) } + const checkEdge = { vertex1: { x: roofLine.x1, y: roofLine.y1 }, vertex2: { x: roofLine.x2, y: roofLine.y2 } } + let maxDistance = 0 + let correctPoint + roof.lines + .filter((line) => { + const roofIndex = roof.lines.indexOf(roofLine) + const prevRoofLine = roof.lines[(roofIndex - 1 + roof.lines.length) % roof.lines.length] + const nextRoofLine = roof.lines[(roofIndex + 1) % roof.lines.length] + return line !== roofLine && line !== prevRoofLine && line !== nextRoofLine + }) + .forEach((line) => { + const checkLine = new fabric.Line([line.x1, line.y1, line.x2, line.y2], { + stroke: 'red', + strokeWidth: 4, + parentId: roofId, + name: 'check', + }) + canvas.add(checkLine).renderAll() + const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const intersect = edgesIntersection(lineEdge, checkEdge) + if (intersect) { + const checkCircle = new fabric.Circle({ + left: intersect.x, + top: intersect.y, + radius: 5, + fill: 'blue', + parentId: roofId, + name: 'check', + }) + console.log('isPointOnLineNew(line, intersect)', isPointOnLineNew(line, intersect)) + canvas.add(checkCircle).renderAll() + } + if (intersect && isPointOnLineNew(line, intersect)) { + intersect.x = almostEqual(intersect.x, roofLine.x1) ? roofLine.x1 : intersect.x + intersect.y = almostEqual(intersect.y, roofLine.y1) ? roofLine.y1 : intersect.y + const distance = Math.sqrt(Math.pow(intersect.x - roofLine.x1, 2) + Math.pow(intersect.y - roofLine.y1, 2)) + const vector = { x: Math.sign(intersect.x - roofLine.x1), y: Math.sign(intersect.y - roofLine.y1) } + console.log('vector', vector, 'checkVector', checkVector) + if (distance > maxDistance && vector.x === checkVector.x && vector.y === checkVector.y) { + maxDistance = distance + correctPoint = intersect + } + } + }) + if (correctPoint) { + const startPoint = + Math.sqrt(Math.pow(correctPoint.x - roofLine.x1, 2) + Math.pow(correctPoint.y - roofLine.y1, 2)) > + Math.sqrt(Math.pow(correctPoint.x - roofLine.x2, 2) + Math.pow(correctPoint.y - roofLine.y2, 2)) + ? { x: roofLine.x1, y: roofLine.y1 } + : { x: roofLine.x2, y: roofLine.y2 } + linesAnalysis.push({ + start: startPoint, + end: { x: correctPoint.x, y: correctPoint.y }, + left: baseLines.findIndex((line) => line === prevLine), + right: baseLines.findIndex((line) => line === nextLine), + type: 'gableLine', + degree: getDegreeByChon(prevLine.attributes.pitch), + gableId: baseLines.findIndex((line) => line === currentLine), + connectCnt: 0, + }) + } + } + + canvas + .getObjects() + .filter((obj) => obj.name === 'check') + .forEach((obj) => canvas.remove(obj)) + canvas.renderAll() + }) //각 선분의 교차점을 찾아 선을 그린다. const MAX_ITERATIONS = 1000 //무한루프 방지 let iterations = 0 + console.log('baseLines', baseLines) console.log('linesAnalysis', linesAnalysis) while (linesAnalysis.length > 0 && iterations < MAX_ITERATIONS) { iterations++ linesAnalysis.forEach((line) => { - const checkLine = new fabric.Line([line.start.x, line.start.y, line.end.x, line.end.y], { - stroke: 'cyan', - strokeWidth: 3, + const point = [line.start.x, line.start.y, line.end.x, line.end.y] + const checkLine = new fabric.Line(point, { + stroke: 'red', + strokeWidth: 2, parentId: roofId, - name: 'checkAnalysis', + name: 'check', }) canvas.add(checkLine) canvas.renderAll() }) - // 각 가선분의 최단 교점 찾기 const intersections = [] for (let i = 0; i < linesAnalysis.length; i++) { @@ -2760,28 +3393,66 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { let intersectPoint = null //교점 좌표 let partners = new Set() //교점 선분의 index + const lineI = linesAnalysis[i] + const checkLineI = new fabric.Line([lineI.start.x, lineI.start.y, lineI.end.x, lineI.end.y], { + stroke: 'blue', + strokeWidth: 2, + parentId: roofId, + name: 'check', + }) + canvas.add(checkLineI).renderAll() + + if (lineI.type === 'gableLine' && lineI.connectCnt > 1) continue + for (let j = 0; j < linesAnalysis.length; j++) { - if (i === j) continue - const intersection = lineIntersection(linesAnalysis[i].start, linesAnalysis[i].end, linesAnalysis[j].start, linesAnalysis[j].end, canvas) + const lineJ = linesAnalysis[j] + if (lineI === lineJ) continue + if (lineI.type === 'gableLine' && lineJ.type === 'gableLine' && i.gableId === lineJ.gableId) continue + if (lineJ.type === 'gableLine' && lineJ.connectCnt > 1) continue + + const checkLineJ = new fabric.Line([lineJ.start.x, lineJ.start.y, lineJ.end.x, lineJ.end.y], { + stroke: 'green', + strokeWidth: 2, + parentId: roofId, + name: 'check', + }) + canvas.add(checkLineJ).renderAll() + + const intersection = lineIntersection(lineI.start, lineI.end, lineJ.start, lineJ.end, canvas) if (intersection) { - const distance = Math.sqrt((intersection.x - linesAnalysis[i].start.x) ** 2 + (intersection.y - linesAnalysis[i].start.y) ** 2) - if (distance < minDistance && !almostEqual(distance, minDistance)) { + const checkCircle = new fabric.Circle({ + left: intersection.x, + top: intersection.y, + radius: 5, + fill: 'red', + parentId: roofId, + name: 'check', + }) + canvas.add(checkCircle).renderAll() + const distance = Math.sqrt((intersection.x - lineI.start.x) ** 2 + (intersection.y - lineI.start.y) ** 2) + const distance2 = Math.sqrt((intersection.x - lineJ.start.x) ** 2 + (intersection.y - lineJ.start.y) ** 2) + if (lineI.type === 'gableLine' && distance < EPSILON) continue + if (distance < minDistance && !almostEqual(distance, minDistance) && !(distance < EPSILON && distance2 < EPSILON)) { minDistance = distance intersectPoint = intersection partners = new Set([j]) } else if (almostEqual(distance, minDistance)) { partners.add(j) } + canvas.remove(checkCircle).renderAll() } + canvas.remove(checkLineJ).renderAll() } if (intersectPoint) { intersections.push({ index: i, intersectPoint, partners, distance: minDistance }) } + canvas.remove(checkLineI).renderAll() } // 제일 가까운 교점부터 처리 intersections.sort((a, b) => a.distance - b.distance) + console.log('intersections', intersections) // 교점에 대한 적합 여부 판단 및 처리. let newAnalysis = [] //신규 발생 선 const processed = new Set() //처리된 선 @@ -2836,11 +3507,22 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { const isSameLine = line1.left === line2.left || line1.left === line2.right || line1.right === line2.left || line1.right === line2.right if (isSameLine) { // 현재 선이 처리 되었음을 표기 + let point1 = [line1.start.x, line1.start.y, intersectPoint.x, intersectPoint.y] + let point2 = [line2.start.x, line2.start.y, intersectPoint.x, intersectPoint.y] + let length1 = Math.sqrt((point1[2] - point1[0]) ** 2 + (point1[3] - point1[1]) ** 2) + let length2 = Math.sqrt((point2[2] - point2[0]) ** 2 + (point2[3] - point2[1]) ** 2) + if (length1 < EPSILON && length2 < EPSILON) continue isProceed = true - const point1 = [line1.start.x, line1.start.y, intersectPoint.x, intersectPoint.y] - const point2 = [line2.start.x, line2.start.y, intersectPoint.x, intersectPoint.y] - const length1 = Math.sqrt((point1[2] - point1[0]) ** 2 + (point1[3] - point1[1]) ** 2) - const length2 = Math.sqrt((point2[2] - point2[0]) ** 2 + (point2[3] - point2[1]) ** 2) + + //gable라인과 붙는경우 length가 0에 가까우면 포인트를 뒤집는다. + if (line1.type === 'gableLine' && length2 < EPSILON) { + point2 = [line2.end.x, line2.end.y, intersectPoint.x, intersectPoint.y] + length2 = Math.sqrt((point2[2] - point2[0]) ** 2 + (point2[3] - point2[1]) ** 2) + } + if (line2.type === 'gableLine' && length1 < EPSILON) { + point1 = [line1.end.x, line1.end.y, intersectPoint.x, intersectPoint.y] + length1 = Math.sqrt((point1[2] - point1[0]) ** 2 + (point1[3] - point1[1]) ** 2) + } if (length1 > 0 && !alreadyPoints(innerLines, point1)) { if (line1.type === 'hip') { @@ -2854,6 +3536,12 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { } else { innerLines.push(drawRidgeLine(point1, canvas, roof, textMode)) } + } else if (line1.type === 'gableLine') { + if (line1.degree > 0) { + innerLines.push(drawHipLine(point1, canvas, roof, textMode, null, line1.degree, line1.degree)) + } else { + innerLines.push(drawRidgeLine(point1, canvas, roof, textMode)) + } } } @@ -2869,103 +3557,184 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { } else { innerLines.push(drawRidgeLine(point2, canvas, roof, textMode)) } + } else if (line2.type === 'gableLine') { + if (line2.degree > 0) { + innerLines.push(drawHipLine(point2, canvas, roof, textMode, null, line2.degree, line2.degree)) + } else { + innerLines.push(drawRidgeLine(point2, canvas, roof, textMode)) + } } } - // 연결점에서 새로운 가선분을 생성 - const relationBaseLines = [line1.left, line1.right, line2.left, line2.right] - const uniqueBaseLines = [...new Set(relationBaseLines)] - if (uniqueBaseLines.length === 3) { - const linesCounts = new Map() - relationBaseLines.forEach((e) => { - linesCounts.set(e, (linesCounts.get(e) || 0) + 1) + if (line1.type === 'gableLine' || line2.type === 'gableLine') { + console.log('gableLine newAnalyze start') + const gableLine = line1.type === 'gableLine' ? line1 : line2 + gableLine.connectCnt++ + + const checkLine = new fabric.Line([gableLine.start.x, gableLine.start.y, gableLine.end.x, gableLine.end.y], { + stroke: 'red', + strokeWidth: 4, + parentId: roofId, + name: 'check', }) + const checkCircle = new fabric.Circle({ + left: intersectPoint.x, + top: intersectPoint.y, + radius: 5, + parentId: roofId, + name: 'check', + }) + canvas.add(checkLine, checkCircle) + canvas.renderAll() - const uniqueLines = Array.from(linesCounts.entries()) - .filter(([_, count]) => count === 1) - .map(([line, _]) => line) - - if (uniqueLines.length === 2) { - // 두 변의 이등분선 방향 계산 - // uniqueLines.sort((a, b) => a - b) - console.log('uniqueLines : ', uniqueLines) - const baseLine1 = baseLines[uniqueLines[0]] - const baseLine2 = baseLines[uniqueLines[1]] - - const checkLine1 = new fabric.Line([baseLine1.x1, baseLine1.y1, baseLine1.x2, baseLine1.y2], { - stroke: 'yellow', - strokeWidth: 4, - parentId: roofId, - name: 'check', + const relationBaseLines = [line1.left, line1.right, line2.left, line2.right] + const uniqueBaseLines = [...new Set(relationBaseLines)] + if (uniqueBaseLines.length === 3) { + const linesCounts = new Map() + relationBaseLines.forEach((e) => { + linesCounts.set(e, (linesCounts.get(e) || 0) + 1) }) - const checkLine2 = new fabric.Line([baseLine2.x1, baseLine2.y1, baseLine2.x2, baseLine2.y2], { - stroke: 'blue', - strokeWidth: 4, - parentId: roofId, - name: 'check', - }) - canvas.add(checkLine1, checkLine2) - canvas.renderAll() - let bisector - console.log('isParallel(baseLine1, baseLine2)', isParallel(baseLine1, baseLine2)) - if (isParallel(baseLine1, baseLine2)) { - bisector = getBisectLines( - { x1: line1.start.x, x2: line1.end.x, y1: line1.start.y, y2: line1.end.y }, - { x1: line2.start.x, y1: line2.start.y, x2: line2.end.x, y2: line2.end.y }, - ) - } else { - //이등분선 - bisector = getBisectBaseLines(baseLine1, baseLine2, intersectPoint, canvas) - } + const uniqueLines = Array.from(linesCounts.entries()) + .filter(([_, count]) => count === 1) + .map(([line, _]) => line) - //마주하는 지붕선을 찾는다. - const intersectionsByRoof = [] - const checkEdge = { - vertex1: { x: intersectPoint.x, y: intersectPoint.y }, - vertex2: { x: intersectPoint.x + bisector.x, y: intersectPoint.y + bisector.y }, - } - const checkVector = { - x: Math.sign(checkEdge.vertex1.x - checkEdge.vertex2.x), - y: Math.sign(checkEdge.vertex1.y - checkEdge.vertex2.y), - } - roof.lines.forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const is = edgesIntersection(lineEdge, checkEdge) - if (is && isPointOnLineNew(line, is)) { - const distance = Math.sqrt((is.x - intersectPoint.x) ** 2 + (is.y - intersectPoint.y) ** 2) - const isVector = { x: Math.sign(intersectPoint.x - is.x), y: Math.sign(intersectPoint.y - is.y) } - if (isVector.x === checkVector.x && isVector.y === checkVector.y) { - intersectionsByRoof.push({ is, distance }) - } + if (uniqueLines.length === 2) { + if (gableLine.connectCnt < 2) { + newAnalysis.push({ + start: { x: intersectPoint.x, y: intersectPoint.y }, + end: { x: gableLine.end.x, y: gableLine.end.y }, + left: gableLine.left, + right: gableLine.right, + type: 'gableLine', + degree: getDegreeByChon(baseLines[gableLine.right].attributes.pitch), + gableId: gableLine.gableId, + connectCnt: gableLine.connectCnt, + }) } + if (gableLine.connectCnt >= 2) { + //가선분 발생만 시키는 더미 생성. + newAnalysis.push({ + start: { x: intersectPoint.x, y: intersectPoint.y }, + end: { x: intersectPoint.x, y: intersectPoint.y }, + left: gableLine.gableId, + right: gableLine.gableId, + type: 'hip', + degree: 0, + }) + } + } + } + console.log('gableLine newAnalyze end') + } else { + // 연결점에서 새로운 가선분을 생성 + const relationBaseLines = [line1.left, line1.right, line2.left, line2.right] + const uniqueBaseLines = [...new Set(relationBaseLines)] + if (uniqueBaseLines.length === 3) { + const linesCounts = new Map() + relationBaseLines.forEach((e) => { + linesCounts.set(e, (linesCounts.get(e) || 0) + 1) }) - intersectionsByRoof.sort((a, b) => a.distance - b.distance) - //지붕 선과의 교점이 존재 할때 - if (intersectionsByRoof.length > 0) { - const is = intersectionsByRoof[0].is - const checkNewLine = new fabric.Line([intersectPoint.x, intersectPoint.y, is.x, is.y], { - stroke: 'cyan', + + const uniqueLines = Array.from(linesCounts.entries()) + .filter(([_, count]) => count === 1) + .map(([line, _]) => line) + + if (uniqueLines.length === 2) { + // 두 변의 이등분선 방향 계산 + // uniqueLines.sort((a, b) => a - b) + console.log('uniqueLines : ', uniqueLines) + const baseLine1 = baseLines[uniqueLines[0]] + const baseLine2 = baseLines[uniqueLines[1]] + + const checkLine1 = new fabric.Line([baseLine1.x1, baseLine1.y1, baseLine1.x2, baseLine1.y2], { + stroke: 'yellow', strokeWidth: 4, parentId: roofId, name: 'check', }) - canvas.add(checkNewLine) - canvas.renderAll() - newAnalysis.push({ - start: { x: intersectPoint.x, y: intersectPoint.y }, - end: { x: is.x, y: is.y }, - left: uniqueLines[0], - right: uniqueLines[1], - type: 'new', - degree: line1.degree, + const checkLine2 = new fabric.Line([baseLine2.x1, baseLine2.y1, baseLine2.x2, baseLine2.y2], { + stroke: 'blue', + strokeWidth: 4, + parentId: roofId, + name: 'check', }) + canvas.add(checkLine1, checkLine2) + canvas.renderAll() + let bisector + console.log('isParallel(baseLine1, baseLine2)', isParallel(baseLine1, baseLine2)) + if (isParallel(baseLine1, baseLine2)) { + bisector = getBisectLines( + { x1: line1.start.x, x2: line1.end.x, y1: line1.start.y, y2: line1.end.y }, + { x1: line2.start.x, y1: line2.start.y, x2: line2.end.x, y2: line2.end.y }, + ) + } else { + //이등분선 + bisector = getBisectBaseLines(baseLine1, baseLine2, intersectPoint, canvas) + } + + //마주하는 지붕선을 찾는다. + const intersectionsByRoof = [] + const checkEdge = { + vertex1: { x: intersectPoint.x, y: intersectPoint.y }, + vertex2: { x: intersectPoint.x + bisector.x, y: intersectPoint.y + bisector.y }, + } + const checkVector = { + x: Math.sign(checkEdge.vertex1.x - checkEdge.vertex2.x), + y: Math.sign(checkEdge.vertex1.y - checkEdge.vertex2.y), + } + roof.lines.forEach((line) => { + const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const is = edgesIntersection(lineEdge, checkEdge) + if (is && isPointOnLineNew(line, is)) { + const distance = Math.sqrt((is.x - intersectPoint.x) ** 2 + (is.y - intersectPoint.y) ** 2) + const isVector = { x: Math.sign(intersectPoint.x - is.x), y: Math.sign(intersectPoint.y - is.y) } + if (isVector.x === checkVector.x && isVector.y === checkVector.y) { + intersectionsByRoof.push({ is, distance }) + } + } + }) + intersectionsByRoof.sort((a, b) => a.distance - b.distance) + //지붕 선과의 교점이 존재 할때 + if (intersectionsByRoof.length > 0) { + let is = intersectionsByRoof[0].is + let linePoint = [intersectPoint.x, intersectPoint.y, is.x, is.y] + const isDiagonal = Math.abs(is.x - intersectPoint.x) >= 1 && Math.abs(is.y - intersectPoint.y) >= 1 + const length = Math.sqrt((linePoint[2] - linePoint[0]) ** 2 + (linePoint[3] - linePoint[1]) ** 2) + if (!isDiagonal) { + const line1 = baseLines[uniqueLines[0]] + const line2 = baseLines[uniqueLines[1]] + const vector1 = { x: Math.sign(line1.x1 - line1.x2), y: Math.sign(line1.y1 - line1.y2) } + + const prevLine = checkVector.x === vector1.x && checkVector.y === vector1.y ? line2 : line1 + const nextLine = checkVector.x === vector1.x && checkVector.y === vector1.y ? line1 : line2 + const drivePoint = getRidgeDrivePoint(linePoint, prevLine, nextLine, baseLines) + if (drivePoint !== null) { + const driveLength = Math.sqrt((drivePoint.x - intersectPoint.x) ** 2 + (drivePoint.y - intersectPoint.y) ** 2) + + if (driveLength < length) { + linePoint = [intersectPoint.x, intersectPoint.y, drivePoint.x, drivePoint.y] + } + } + } + const checkNewLine = new fabric.Line(linePoint, { stroke: 'cyan', strokeWidth: 4, parentId: roofId, name: 'check' }) + canvas.add(checkNewLine).renderAll() + + newAnalysis.push({ + start: { x: linePoint[0], y: linePoint[1] }, + end: { x: linePoint[2], y: linePoint[3] }, + left: uniqueLines[0], + right: uniqueLines[1], + type: 'new', + degree: isDiagonal ? line1.degree : 0, + }) + } } + canvas + .getObjects() + .filter((object) => object.name === 'check') + .forEach((object) => canvas.remove(object)) + canvas.renderAll() } - canvas - .getObjects() - .filter((object) => object.name === 'check') - .forEach((object) => canvas.remove(object)) - canvas.renderAll() } processed.add(pIndex) } @@ -2991,85 +3760,333 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { .forEach((object) => canvas.remove(object)) canvas.renderAll() // 새로운 가선분이 없을때 종료 + console.log('newAnalysis.length : ', newAnalysis.length) if (newAnalysis.length === 0) break } console.log('lineAnalysis: end ', linesAnalysis) // 가선분 중 처리가 안되어있는 붙어있는 라인에 대한 예외처리. const proceedAnalysis = [] - linesAnalysis.forEach((currentLine) => { - if (proceedAnalysis.find((p) => p === currentLine)) return - //현재와 같으면 제외, 이미 처리된 라인은 제외, 현재와 공통선분이 존재하지 않으면 제외 - linesAnalysis - .filter( - (partnerLine) => - partnerLine !== currentLine && - !proceedAnalysis.find((p) => p === partnerLine) && - (currentLine.left === partnerLine.left || - currentLine.left === partnerLine.right || - partnerLine.left === currentLine.left || - partnerLine.left === currentLine.right), - ) - .forEach((partnerLine) => { - const dx1 = currentLine.end.x - currentLine.start.x - const dy1 = currentLine.end.y - currentLine.start.y - const dx2 = partnerLine.end.x - partnerLine.start.x - const dy2 = partnerLine.end.y - partnerLine.start.y - const denominator = dy2 * dx1 - dx2 * dy1 - const isOpposite = dx1 * dx2 + dy1 * dy2 < 0 - //평행하지 않으면 제외 - if (Math.abs(denominator) > EPSILON) return + linesAnalysis + .filter((line) => { + const dx = line.end.x - line.start.x + const dy = line.end.y - line.start.y + const length = Math.sqrt(dx ** 2 + dy ** 2) + return length > 0 + }) + .forEach((currentLine) => { + if (proceedAnalysis.find((p) => p === currentLine)) return + //현재와 같으면 제외, 이미 처리된 라인은 제외, 현재와 공통선분이 존재하지 않으면 제외 + linesAnalysis + .filter((line) => { + const dx = line.end.x - line.start.x + const dy = line.end.y - line.start.y + const length = Math.sqrt(dx ** 2 + dy ** 2) + return length > 0 + }) + .filter( + (partnerLine) => + partnerLine !== currentLine && + !proceedAnalysis.find((p) => p === partnerLine) && + (currentLine.left === partnerLine.left || + currentLine.left === partnerLine.right || + partnerLine.left === currentLine.left || + partnerLine.left === currentLine.right), + ) + .forEach((partnerLine) => { + const dx1 = currentLine.end.x - currentLine.start.x + const dy1 = currentLine.end.y - currentLine.start.y + const dx2 = partnerLine.end.x - partnerLine.start.x + const dy2 = partnerLine.end.y - partnerLine.start.y + const denominator = dy2 * dx1 - dx2 * dy1 + const isOpposite = dx1 * dx2 + dy1 * dy2 < 0 + //평행하지 않으면 제외 + if (Math.abs(denominator) > EPSILON) return - const currentDegree = getDegreeByChon(baseLines[currentLine.left].attributes.pitch) - if (isOpposite) { - const points = [currentLine.start.x, currentLine.start.y, partnerLine.start.x, partnerLine.start.y] - const length = Math.sqrt((points[0] - points[2]) ** 2 + (points[1] - points[3]) ** 2) + const currentDegree = getDegreeByChon(baseLines[currentLine.left].attributes.pitch) + if (isOpposite) { + const points = [currentLine.start.x, currentLine.start.y, partnerLine.start.x, partnerLine.start.y] + const length = Math.sqrt((points[0] - points[2]) ** 2 + (points[1] - points[3]) ** 2) - if (length > 0) { - if (currentLine.type === 'hip') { - innerLines.push(drawHipLine(points, canvas, roof, textMode, null, currentDegree, currentDegree)) - } else if (currentLine.type === 'ridge') { - innerLines.push(drawRidgeLine(points, canvas, roof, textMode)) - } else if (currentLine.type === 'new') { - const isDiagonal = Math.abs(points[0] - points[2]) >= 1 && Math.abs(points[1] - points[3]) >= 1 - if (isDiagonal) { + if (length > 0) { + if (currentLine.type === 'hip') { innerLines.push(drawHipLine(points, canvas, roof, textMode, null, currentDegree, currentDegree)) - } else { + } else if (currentLine.type === 'ridge') { innerLines.push(drawRidgeLine(points, canvas, roof, textMode)) + } else if (currentLine.type === 'new') { + const isDiagonal = Math.abs(points[0] - points[2]) >= 1 && Math.abs(points[1] - points[3]) >= 1 + if (isDiagonal && almostEqual(Math.abs(points[0] - points[2]), Math.abs(points[1] - points[3]))) { + innerLines.push(drawHipLine(points, canvas, roof, textMode, null, currentDegree, currentDegree)) + } + if (!isDiagonal) { + innerLines.push(drawRidgeLine(points, canvas, roof, textMode)) + } } + proceedAnalysis.push(currentLine, partnerLine) } - proceedAnalysis.push(currentLine, partnerLine) - } - } else { - const allPoints = [currentLine.start, currentLine.end, partnerLine.start, partnerLine.end] - let points = [] - allPoints.forEach((point) => { - let count = 0 - allPoints.forEach((p) => { - if (almostEqual(point.x, p.x) && almostEqual(point.y, p.y)) count++ + } else { + const allPoints = [currentLine.start, currentLine.end, partnerLine.start, partnerLine.end] + let points = [] + allPoints.forEach((point) => { + let count = 0 + allPoints.forEach((p) => { + if (almostEqual(point.x, p.x) && almostEqual(point.y, p.y)) count++ + }) + if (count === 1) points.push(point) }) - if (count === 1) points.push(point) - }) - if (points.length === 2) { - const length = Math.sqrt((points[0].x - points[1].x) ** 2 + (points[0].y - points[1].y) ** 2) - if (length < EPSILON) return - const isDiagonal = Math.abs(points[0].x - points[1].x) >= 1 && Math.abs(points[0].y - points[1].y) >= 1 - if (isDiagonal) { - innerLines.push( - drawHipLine([points[0].x, points[0].y, points[1].x, points[1].y], canvas, roof, textMode, null, currentDegree, currentDegree), - ) - } else { - innerLines.push(drawRidgeLine([points[0].x, points[0].y, points[1].x, points[1].y], canvas, roof, textMode)) + if (points.length === 2) { + const length = Math.sqrt((points[0].x - points[1].x) ** 2 + (points[0].y - points[1].y) ** 2) + if (length < EPSILON) return + const isDiagonal = Math.abs(points[0].x - points[1].x) >= 1 && Math.abs(points[0].y - points[1].y) >= 1 + if (isDiagonal) { + innerLines.push( + drawHipLine([points[0].x, points[0].y, points[1].x, points[1].y], canvas, roof, textMode, null, currentDegree, currentDegree), + ) + } else { + innerLines.push(drawRidgeLine([points[0].x, points[0].y, points[1].x, points[1].y], canvas, roof, textMode)) + } + proceedAnalysis.push(currentLine, partnerLine) + } + } + }) + }) + linesAnalysis = linesAnalysis.filter((line) => !proceedAnalysis.find((p) => p === line)) + + //하단 지붕 라인처리 + const downRoofLines = [] + baseLines.forEach((baseLine, index) => { + const nextLine = baseLines[(index + 1) % baseLines.length] + const prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] + + const prevLineVector = { x: Math.sign(prevLine.x1 - prevLine.x2), y: Math.sign(prevLine.y1 - prevLine.y2) } + const nextLineVector = { x: Math.sign(nextLine.x1 - nextLine.x2), y: Math.sign(nextLine.y1 - nextLine.y2) } + + //반절마루 생성불가이므로 지붕선 분기를 해야하는지 확인 후 처리. + if ( + prevLineVector.x === nextLineVector.x && + prevLineVector.y === nextLineVector.y && + (prevLine.attributes.type === LINE_TYPE.WALLLINE.GABLE || nextLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) + ) { + downRoofLines.push(index) + } + }) + + if (downRoofLines.length > 0) { + downRoofLines.forEach((index) => { + const currentLine = baseLines[index] + // const nextLine = baseLines[(index + 1) % baseLines.length] + // const prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] + + const analyze = analyzeLine(currentLine) + if (analyze.isDiagonal) { + return + } + + const roofLine = analyze.roofLine + let roofPoint = [analyze.startPoint.x, analyze.startPoint.y, analyze.endPoint.x, analyze.endPoint.y] + + if (analyze.isVertical) { + roofPoint[0] = roofLine.x1 + roofPoint[2] = roofLine.x2 + } + if (analyze.isHorizontal) { + roofPoint[1] = roofLine.y1 + roofPoint[3] = roofLine.y2 + } + + console.log('analyze: ', analyze) + const findRidgeVector = { x: 0, y: 0 } + if (analyze.isVertical) { + // noinspection JSSuspiciousNameCombination + findRidgeVector.x = analyze.directionVector.y + } + if (analyze.isHorizontal) { + // noinspection JSSuspiciousNameCombination + findRidgeVector.y = analyze.directionVector.x + } + + console.log('findRidgeVector: ', findRidgeVector) + innerLines + .filter((line) => { + if (line.name === LINE_TYPE.SUBLINE.RIDGE) { + if (analyze.isVertical) { + const signX = Math.sign(currentLine.x1 - line.x1) + console.log('signX: ', signX) + return signX === findRidgeVector.x + } + if (analyze.isHorizontal) { + const signY = Math.sign(currentLine.y1 - line.y1) + console.log('signY: ', signY) + return signY === findRidgeVector.y + } + return false + } + return false + }) + .forEach((line) => { + if (line.name === LINE_TYPE.SUBLINE.RIDGE) { + const checkLine = new fabric.Line([line.x1, line.y1, line.x2, line.y2], { + stroke: 'red', + strokeWidth: 4, + parentId: roofId, + name: 'check', + }) + canvas.add(checkLine) + canvas.renderAll() + } + }) + + /*const oppositeLines = [] + baseLines + .filter((line) => { + //평행한 반대방향 라인인지 확인. + const lineAnalyze = analyzeLine(line) + return ( + (analyze.isHorizontal && + lineAnalyze.directionVector.x !== analyze.directionVector.x && + lineAnalyze.directionVector.y === analyze.directionVector.y) || + (analyze.isVertical && + lineAnalyze.directionVector.x === analyze.directionVector.x && + lineAnalyze.directionVector.y !== analyze.directionVector.y) + ) + }) + .filter((line) => { + //라인이 현재라인에 overlap 되거나, 현재 라인을 full overlap하는지 확인. + if (analyze.isHorizontal) { + const currentMinX = Math.min(currentLine.x1, currentLine.x2) + const currentMaxX = Math.max(currentLine.x1, currentLine.x2) + const minX = Math.min(line.x1, line.x2) + const maxX = Math.max(line.x1, line.x2) + //full overlap + if (minX <= currentMinX && maxX >= currentMaxX) { + return true + } + //라인 포인트중 하나라도 현재 라인의 범위에 들어와있으면 overlap으로 판단. + if ((currentMinX <= minX && minX <= currentMaxX) || (currentMinX <= maxX && maxX <= currentMaxX)) { + return true + } + } + if (analyze.isVertical) { + const currentMinY = Math.min(currentLine.y1, currentLine.y2) + const currentMaxY = Math.max(currentLine.y1, currentLine.y2) + const minY = Math.min(line.y1, line.y2) + const maxY = Math.max(line.y1, line.y2) + //full overlap + if (minY <= currentMinY && maxY >= currentMaxY) { + return true + } + //라인 포인트중 하나라도 현재 라인의 범위에 들어와있으면 overlap으로 판단. + if ((currentMinY <= minY && minY <= currentMaxY) || (currentMinY <= maxY && maxY <= currentMaxY)) { + return true + } + } + return false + }) + .forEach((line, i) => { + const checkLine = new fabric.Line([line.x1, line.y1, line.x2, line.y2], { + stroke: 'green', + strokeWidth: 4, + parentId: roofId, + name: 'check', + }) + canvas.add(checkLine) + canvas.renderAll() + })*/ + + // innerLines.push(drawRoofLine(roofPoint, canvas, roof, textMode)) + }) + } + + if (linesAnalysis.length > 0) { + linesAnalysis + .filter((line) => { + const dx = line.end.x - line.start.x + const dy = line.end.y - line.start.y + const length = Math.sqrt(dx ** 2 + dy ** 2) + return length > 0 + }) + .forEach((line) => { + const startOnLine = roof.lines.find((l) => isPointOnLineNew(l, line.start)) + const endOnLine = roof.lines.find((l) => isPointOnLineNew(l, line.end)) + console.log('startOnLine, endOnLine: ', startOnLine, endOnLine) + const allLinesPoints = [] + innerLines.forEach((innerLine) => { + allLinesPoints.push({ x: innerLine.x1, y: innerLine.y1 }, { x: innerLine.x2, y: innerLine.y2 }) + }) + + if ( + allLinesPoints.filter((p) => almostEqual(p.x, line.start.x) && almostEqual(p.y, line.start.y)).length < 3 && + allLinesPoints.filter((p) => almostEqual(p.x, line.end.x) && almostEqual(p.y, line.end.y)).length === 0 + ) { + if (startOnLine || endOnLine) { + if (line.degree === 0) { + innerLines.push(drawRoofLine([line.start.x, line.start.y, line.end.x, line.end.y], canvas, roof, textMode)) + } else { + innerLines.push( + drawHipLine([line.start.x, line.start.y, line.end.x, line.end.y], canvas, roof, textMode, null, line.degree, line.degree), + ) } - proceedAnalysis.push(currentLine, partnerLine) } } }) + } + + //지붕선에 따라 라인추가 작업 처리. + const innerLinesPoints = [] + innerLines.forEach((line) => { + const hasCoord1 = innerLinesPoints.find((p) => p.x === line.x1 && p.y === line.y1) + const hasCoord2 = innerLinesPoints.find((p) => p.x === line.x2 && p.y === line.y2) + if (!hasCoord1) innerLinesPoints.push({ x: line.x1, y: line.y1 }) + if (!hasCoord2) innerLinesPoints.push({ x: line.x2, y: line.y2 }) + }) + roof.lines.forEach((line) => { + const splitPoint = [] + let hasOverlapLine = false + const minX = Math.min(line.x1, line.x2) + const maxX = Math.max(line.x1, line.x2) + const minY = Math.min(line.y1, line.y2) + const maxY = Math.max(line.y1, line.y2) + innerLines.forEach((innerLine) => { + const innerLineMinX = Math.min(innerLine.x1, innerLine.x2) + const innerLineMaxX = Math.max(innerLine.x1, innerLine.x2) + const innerLineMinY = Math.min(innerLine.y1, innerLine.y2) + const innerLineMaxY = Math.max(innerLine.y1, innerLine.y2) + if (innerLineMinX <= minX && innerLineMaxX >= maxX && innerLineMinY <= minY && innerLineMaxY >= maxY) { + hasOverlapLine = true + } + if (minX <= innerLineMinX && maxX >= innerLineMaxX && minY <= innerLineMinY && maxY >= innerLineMaxY) { + hasOverlapLine = true + } + }) + if (hasOverlapLine) return + + innerLinesPoints.forEach((point) => { + if ( + isPointOnLineNew(line, point) && + !(almostEqual(line.x1, point.x) && almostEqual(line.y1, point.y)) && + !(almostEqual(line.x2, point.x) && almostEqual(line.y2, point.y)) + ) { + const distance = Math.sqrt((point.x - line.x1) ** 2 + (point.y - line.y1) ** 2) + splitPoint.push({ point, distance }) + } + }) + if (splitPoint.length > 0) { + splitPoint.sort((a, b) => a[1] - b[1]) + let startPoint = { x: line.x1, y: line.y1 } + for (let i = 0; i < splitPoint.length; i++) { + const point = splitPoint[i].point + innerLines.push(drawRoofLine([startPoint.x, startPoint.y, point.x, point.y], canvas, roof, textMode)) + startPoint = point + } + innerLines.push(drawRoofLine([startPoint.x, startPoint.y, line.x2, line.y2], canvas, roof, textMode)) + } else { + innerLines.push(drawRoofLine([line.x1, line.y1, line.x2, line.y2], canvas, roof, textMode)) + } }) //지붕 innerLines에 추가. - roof.innerLines.push(...innerLines, ...ridgeLines) + roof.innerLines = innerLines canvas .getObjects() @@ -3078,106 +4095,6 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { canvas.renderAll() } -/** - * - * @param testPoint - * @param prevLine - * @param nextLine - * @param baseLines - * @param canvas - */ -const getRidgeDrivePoints = (testPoint = [], prevLine, nextLine, baseLines, canvas) => { - if (prevLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES && nextLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES) return null - let prevPoint = null, - nextPoint = null - - const baseLinePoints = baseLines.map((line) => ({ x: line.x1, y: line.y1 })) - const checkWallPolygon = new QPolygon(baseLinePoints, {}) - const checkEdge = { vertex1: { x: testPoint[0], y: testPoint[1] }, vertex2: { x: testPoint[2], y: testPoint[3] } } - - if (prevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) { - const index = baseLines.findIndex((line) => line === prevLine) - const beforePrevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] - if (!beforePrevLine || beforePrevLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES) { - prevPoint = null - } else { - let prevVector = getHalfAngleVector(beforePrevLine, prevLine) - - const prevCheckPoint = { - x: prevLine.x1 + prevVector.x * 10, - y: prevLine.y1 + prevVector.y * 10, - } - - let prevHipVector - - if (checkWallPolygon.inPolygon(prevCheckPoint)) { - prevHipVector = { x: prevVector.x, y: prevVector.y } - } else { - prevHipVector = { x: -prevVector.x, y: -prevVector.y } - } - - const prevHipEdge = { - vertex1: { x: prevLine.x1, y: prevLine.y1 }, - vertex2: { x: prevLine.x1 + prevHipVector.x * 10, y: prevLine.y1 + prevHipVector.y * 10 }, - } - - const intersection = edgesIntersection(prevHipEdge, checkEdge) - if (intersection) { - const checkVector = { x: Math.sign(prevLine.x1 - testPoint[0]), y: Math.sign(prevLine.y1 - testPoint[1]) } - const intersectVector = { x: Math.sign(prevLine.x1 - intersection.x), y: Math.sign(prevLine.y1 - intersection.y) } - if (checkVector.x === intersectVector.x && checkVector.y === intersectVector.y) { - const distance = Math.sqrt((intersection.x - testPoint[0]) ** 2 + (intersection.y - testPoint[1]) ** 2) - prevPoint = { intersection, distance } - } - } - } - } - - if (nextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) { - const index = baseLines.findIndex((line) => line === nextLine) - const afterNextLine = baseLines[(index + 1) % baseLines.length] - if (!afterNextLine || afterNextLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES) { - nextPoint = null - } else { - let nextVector = getHalfAngleVector(nextLine, afterNextLine) - const nextCheckPoint = { - x: nextLine.x2 + nextVector.x * 10, - y: nextLine.y2 + nextVector.y * 10, - } - let nextHipVector - if (checkWallPolygon.inPolygon(nextCheckPoint)) { - nextHipVector = { x: nextVector.x, y: nextVector.y } - } else { - nextHipVector = { x: -nextVector.x, y: -nextVector.y } - } - - const nextHipEdge = { - vertex1: { x: nextLine.x2, y: nextLine.y2 }, - vertex2: { x: nextLine.x2 + nextHipVector.x * 10, y: nextLine.y2 + nextHipVector.y * 10 }, - } - const intersection = edgesIntersection(nextHipEdge, checkEdge) - if (intersection) { - const checkVector = { x: Math.sign(nextLine.x2 - testPoint[0]), y: Math.sign(nextLine.y2 - testPoint[1]) } - const intersectVector = { x: Math.sign(nextLine.x2 - intersection.x), y: Math.sign(nextLine.y2 - intersection.y) } - if (checkVector.x === intersectVector.x && checkVector.y === intersectVector.y) { - const distance = Math.sqrt((intersection.x - testPoint[0]) ** 2 + (intersection.y - testPoint[1]) ** 2) - nextPoint = { intersection, distance } - } - } - } - } - - if (prevPoint && nextPoint) { - return prevPoint.distance < nextPoint.distance ? prevPoint.intersection : nextPoint.intersection - } else if (prevPoint) { - return prevPoint.intersection - } else if (nextPoint) { - return nextPoint.intersection - } else { - return null - } -} - /** * 선분과 선분의 교점을 구한다. * @param line1Start @@ -3204,8 +4121,17 @@ const lineIntersection = (line1Start, line1End, line2Start, line2End) => { return null } // 평행 - const ua = (dx2 * (line1Start.y - line2Start.y) - dy2 * (line1Start.x - line2Start.x)) / denominator - const ub = (dx1 * (line1Start.y - line2Start.y) - dy1 * (line1Start.x - line2Start.x)) / denominator + let ua = (dx2 * (line1Start.y - line2Start.y) - dy2 * (line1Start.x - line2Start.x)) / denominator + let ub = (dx1 * (line1Start.y - line2Start.y) - dy1 * (line1Start.x - line2Start.x)) / denominator + + // 경계값 보정용 헬퍼 + const clamp01 = (t) => { + if (almostEqual(t, 0, EPSILON)) return 0 + if (almostEqual(t, 1, EPSILON)) return 1 + return t + } + ua = clamp01(ua) + ub = clamp01(ub) if (ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1) { return { From 4eb89068c5325b469d8047e35e47563c4f582ba6 Mon Sep 17 00:00:00 2001 From: ysCha Date: Mon, 15 Dec 2025 17:58:00 +0900 Subject: [PATCH 05/28] =?UTF-8?q?skt=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util/skeleton-utils.js | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/util/skeleton-utils.js b/src/util/skeleton-utils.js index edab8b87..ce438072 100644 --- a/src/util/skeleton-utils.js +++ b/src/util/skeleton-utils.js @@ -990,10 +990,12 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { const rLineM = Big(wallBaseLine.x2).minus(roofLine.x2).abs().toNumber(); newPStart.y = Big(wallBaseLine.y1).minus(rLineM).abs().toNumber(); const inLine = findLineContainingPoint(innerLines, { y: newPStart.y, x: newPStart.x }) - if(inLine.x2 > inLine.x1 ) { - getAddLine({ y: newPStart.y, x: newPStart.x }, { y: inLine.y2, x: inLine.x2 }, 'purple') - }else{ - getAddLine({ y: inLine.y1, x: inLine.x1 }, { y: newPEnd.y, x: newPEnd.x } , 'purple') + if(inLine) { + if (inLine.x2 > inLine.x1) { + getAddLine({ y: newPStart.y, x: newPStart.x }, { y: inLine.y2, x: inLine.x2 }, 'purple') + } else { + getAddLine({ y: inLine.y1, x: inLine.x1 }, { y: newPEnd.y, x: newPEnd.x }, 'purple') + } } } @@ -1042,10 +1044,12 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { const rLineM = Big(wallBaseLine.x2).minus(roofLine.x2).abs().toNumber(); newPEnd.y = Big(wallBaseLine.y2).plus(rLineM).abs().toNumber(); const inLine = findLineContainingPoint(innerLines, { y: newPEnd.y, x: newPEnd.x }) - if(inLine.x2 > inLine.x1 ) { - getAddLine({ y: newPEnd.y, x: newPEnd.x }, { y: inLine.y2, x: inLine.x2 }, 'purple') - }else{ - getAddLine({ y: inLine.y1, x: inLine.x1 }, { y: newPEnd.y, x: newPEnd.x } , 'purple') + if(inLine) { + if (inLine.x2 > inLine.x1) { + getAddLine({ y: newPEnd.y, x: newPEnd.x }, { y: inLine.y2, x: inLine.x2 }, 'purple') + } else { + getAddLine({ y: inLine.y1, x: inLine.x1 }, { y: newPEnd.y, x: newPEnd.x }, 'purple') + } } } From b4a7bb2e48bf33faecb03130a44d567e12ae6123 Mon Sep 17 00:00:00 2001 From: ysCha Date: Mon, 15 Dec 2025 18:21:40 +0900 Subject: [PATCH 06/28] =?UTF-8?q?skt=EC=88=98=EC=A0=952?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util/skeleton-utils.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/util/skeleton-utils.js b/src/util/skeleton-utils.js index ce438072..b3a59e79 100644 --- a/src/util/skeleton-utils.js +++ b/src/util/skeleton-utils.js @@ -650,7 +650,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { canvas.renderAll(); }); - if((roof.moveUpDown??0 > 0) ) { + //if((roof.moveUpDown??0 > 0) ) { // 같은 라인이 없으므로 새 다각형 라인 생성 //라인 편집 @@ -1545,7 +1545,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { canvas.renderAll() }); - } + // } if (findPoints.length > 0) { // 모든 점에 대해 라인 업데이트를 누적 From b35ae4407a15f0b787acfcb8f993e1cbc763c723 Mon Sep 17 00:00:00 2001 From: ysCha Date: Tue, 16 Dec 2025 10:44:43 +0900 Subject: [PATCH 07/28] =?UTF-8?q?[1351]=EA=B2=BD=EC=82=AC=EB=8F=84=20?= =?UTF-8?q?=EA=B8=B0=EB=B3=B8=20=EC=88=98=EC=B9=98=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?inclBase?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../modal/placementShape/PlacementShapeSetting.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx b/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx index e283fff0..4ec45dd5 100644 --- a/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx +++ b/src/components/floor-plan/modal/placementShape/PlacementShapeSetting.jsx @@ -350,7 +350,7 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla label="" className="input-origin block" readOnly={currentRoof?.roofAngleSet !== item.value} - value={index === 0 ? currentRoof?.pitch || '0' : currentRoof?.angle || '0'} + value={index === 0 ? (currentRoof?.pitch ?? basicSetting?.inclBase ?? '0') : (currentRoof?.angle ?? '0')} onChange={(value) => { if (index === 0) { const pitch = value === '' ? '' : Number(value); @@ -372,7 +372,7 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla }} options={{ allowNegative: false, - allowDecimal: true //(index !== 0), + allowDecimal: true }} /> @@ -520,7 +520,7 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla label="" className="input-origin block" ref={roofRef.hajebichi} - value={currentRoof?.hajebichi || '0'} + value={currentRoof?.hajebichi ?? basicSetting?.roofPchBase ?? '0'} onChange={(value) => { const hajebichi = value === '' ? '' : Number(value); setCurrentRoof(prev => ({ From fdeb0dab9a4e01fc6712f212f8f37dec7cf0592d Mon Sep 17 00:00:00 2001 From: ysCha Date: Tue, 16 Dec 2025 11:09:05 +0900 Subject: [PATCH 08/28] skeleton open --- src/components/fabric/QPolygon.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/fabric/QPolygon.js b/src/components/fabric/QPolygon.js index 3ca095a8..d2f7db92 100644 --- a/src/components/fabric/QPolygon.js +++ b/src/components/fabric/QPolygon.js @@ -336,7 +336,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, { if (types.every((type) => type === LINE_TYPE.WALLLINE.EAVES)) { // 용마루 -- straight-skeleton console.log('용마루 지붕') - //drawRidgeRoof(this.id, this.canvas, textMode) + ///drawRidgeRoof(this.id, this.canvas, textMode) drawSkeletonRidgeRoof(this.id, this.canvas, textMode); } else if (isGableRoof(types)) { // A형, B형 박공 지붕 From 4a959ba8cfd0b2345f732a5ed1904db8d8026dd9 Mon Sep 17 00:00:00 2001 From: ysCha Date: Tue, 16 Dec 2025 11:19:54 +0900 Subject: [PATCH 09/28] skeleton open --- src/lib/authActions.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/authActions.js b/src/lib/authActions.js index 5e7055d0..4a5b2c94 100644 --- a/src/lib/authActions.js +++ b/src/lib/authActions.js @@ -49,6 +49,7 @@ export async function setSession(data) { session.custCd = data.custCd session.isLoggedIn = true session.builderNo = data.builderNo + session.custNm = data.custNm await session.save() } From b0b87bbd662c5e7014fa8e432ce1d9b3ebe4ef17 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Tue, 16 Dec 2025 13:34:52 +0900 Subject: [PATCH 10/28] =?UTF-8?q?=EC=A7=80=EB=B6=95=EB=A9=B4=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99=20=ED=9B=84=20=EC=84=A0=ED=83=9D=20=EC=A0=9C=EB=8C=80?= =?UTF-8?q?=EB=A1=9C=20=EC=95=88=EB=90=98=EB=8A=94=20=ED=98=84=EC=83=81=20?= =?UTF-8?q?=EC=88=98=EC=A0=95,=20=EC=A7=80=EB=B6=95=EB=A9=B4=20=ED=95=A0?= =?UTF-8?q?=EB=8B=B9=20=EC=8B=9C=20=EB=B0=A9=ED=96=A5=20=ED=99=94=EC=82=B4?= =?UTF-8?q?=ED=91=9C=20=EC=A0=84=EB=B6=80=20=EB=82=A8=EC=AA=BD=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=90=98=EB=8A=94=20=ED=98=84=EC=83=81=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/common.js | 1 + src/hooks/surface/useSurfaceShapeBatch.js | 10 +++- src/hooks/usePolygon.js | 72 ++++++++++++----------- 3 files changed, 46 insertions(+), 37 deletions(-) diff --git a/src/common/common.js b/src/common/common.js index 60a57c76..76632014 100644 --- a/src/common/common.js +++ b/src/common/common.js @@ -61,6 +61,7 @@ export const LINE_TYPE = { */ DEFAULT: 'default', EAVES: 'eaves', + EAVE_HELP_LINE: 'eaveHelpLine', GABLE: 'gable', GABLE_LEFT: 'gableLeft', //케라바 왼쪽 GABLE_RIGHT: 'gableRight', //케라바 오른쪽 diff --git a/src/hooks/surface/useSurfaceShapeBatch.js b/src/hooks/surface/useSurfaceShapeBatch.js index 54721d94..b26ee195 100644 --- a/src/hooks/surface/useSurfaceShapeBatch.js +++ b/src/hooks/surface/useSurfaceShapeBatch.js @@ -1,7 +1,13 @@ 'use client' import { useRecoilValue, useResetRecoilState } from 'recoil' -import { canvasSettingState, canvasState, currentCanvasPlanState, currentObjectState, globalPitchState } from '@/store/canvasAtom' +import { + canvasSettingState, + canvasState, + currentCanvasPlanState, + currentObjectState, + globalPitchState, +} from '@/store/canvasAtom' import { LINE_TYPE, MENU, POLYGON_TYPE } from '@/common/common' import { getIntersectionPoint } from '@/util/canvas-util' import { degreesToRadians } from '@turf/turf' @@ -879,7 +885,7 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) { } }) - // roof.fire('polygonMoved') + roof.fire('polygonMoved') roof.fire('modified') drawDirectionArrow(roof) changeCorridorDimensionText() diff --git a/src/hooks/usePolygon.js b/src/hooks/usePolygon.js index 5e21e902..fe8fbd78 100644 --- a/src/hooks/usePolygon.js +++ b/src/hooks/usePolygon.js @@ -856,7 +856,7 @@ export const usePolygon = () => { if (innerLine.attributes && polygonLine.attributes.type) { // innerLine이 polygonLine보다 긴 경우 polygonLine.need를 false로 변경 if (polygonLine.length < innerLine.length) { - if(polygonLine.lineName !== 'eaveHelpLine'){ + if (polygonLine.lineName !== 'eaveHelpLine') { polygonLine.need = false } } @@ -867,7 +867,6 @@ export const usePolygon = () => { // innerLine.attributes.isStart = true // innerLine.parentLine = polygonLine - // 매핑된 innerLine의 attributes를 변경 (교차점 계산 전에 적용) innerLineMapping.forEach((polygonLine, innerLine) => { innerLine.attributes.planeSize = innerLine.attributes.planeSize ?? polygonLine.attributes.planeSize @@ -877,7 +876,6 @@ export const usePolygon = () => { innerLine.attributes.isStart = true innerLine.parentLine = polygonLine }) - } } }) @@ -1387,7 +1385,7 @@ export const usePolygon = () => { let representLine // 지붕을 그리면서 기존 polygon의 line중 연결된 line을 찾는다. - [...polygonLines, ...innerLines].forEach((line) => { + ;[...polygonLines, ...innerLines].forEach((line) => { let startFlag = false let endFlag = false const startPoint = line.startPoint @@ -1402,7 +1400,10 @@ export const usePolygon = () => { }) if (startFlag && endFlag) { - if (!representLines.includes(line) && line.attributes.type === LINE_TYPE.WALLLINE.EAVES) { + if ( + !representLines.includes(line) && + (line.attributes.type === LINE_TYPE.WALLLINE.EAVES || line.attributes.type === LINE_TYPE.WALLLINE.EAVE_HELP_LINE) + ) { representLines.push(line) } else if (!representLines.includes(line) && line.attributes.type === LINE_TYPE.WALLLINE.HIPANDGABLE) { representLines.push(line) @@ -1630,79 +1631,80 @@ export const usePolygon = () => { // } function findShortestPath(start, end, graph, epsilon = 1) { - const startKey = pointToKey(start, epsilon); - const endKey = pointToKey(end, epsilon); + const startKey = pointToKey(start, epsilon) + const endKey = pointToKey(end, epsilon) // 거리와 이전 노드 추적 - const distances = { [startKey]: 0 }; - const previous = {}; - const visited = new Set(); + const distances = { [startKey]: 0 } + const previous = {} + const visited = new Set() // 우선순위 큐 (거리가 짧은 순으로 정렬) - const queue = [{ key: startKey, dist: 0 }]; + const queue = [{ key: startKey, dist: 0 }] // 모든 노드 초기화 for (const key in graph) { if (key !== startKey) { - distances[key] = Infinity; + distances[key] = Infinity } } // 우선순위 큐에서 다음 노드 선택 const getNextNode = () => { - if (queue.length === 0) return null; - queue.sort((a, b) => a.dist - b.dist); - return queue.shift(); - }; + if (queue.length === 0) return null + queue.sort((a, b) => a.dist - b.dist) + return queue.shift() + } - let current; + let current while ((current = getNextNode())) { - const currentKey = current.key; + const currentKey = current.key // 목적지에 도달하면 종료 - if (currentKey === endKey) break; + if (currentKey === endKey) break // 이미 방문한 노드는 건너뜀 - if (visited.has(currentKey)) continue; - visited.add(currentKey); + if (visited.has(currentKey)) continue + visited.add(currentKey) // 인접 노드 탐색 for (const neighbor of graph[currentKey] || []) { - const neighborKey = pointToKey(neighbor.point, epsilon); - if (visited.has(neighborKey)) continue; + const neighborKey = pointToKey(neighbor.point, epsilon) + if (visited.has(neighborKey)) continue - const alt = distances[currentKey] + neighbor.distance; + const alt = distances[currentKey] + neighbor.distance // 더 짧은 경로를 찾은 경우 업데이트 if (alt < (distances[neighborKey] || Infinity)) { - distances[neighborKey] = alt; - previous[neighborKey] = currentKey; + distances[neighborKey] = alt + previous[neighborKey] = currentKey // 우선순위 큐에 추가 - queue.push({ key: neighborKey, dist: alt }); + queue.push({ key: neighborKey, dist: alt }) } } } // 경로 재구성 - const path = []; - let currentKey = endKey; + const path = [] + let currentKey = endKey // 시작점에 도달할 때까지 역추적 while (previous[currentKey] !== undefined) { - const [x, y] = currentKey.split(',').map(Number); - path.unshift({ x, y }); - currentKey = previous[currentKey]; + const [x, y] = currentKey.split(',').map(Number) + path.unshift({ x, y }) + currentKey = previous[currentKey] } // 시작점 추가 if (path.length > 0) { - const [sx, sy] = startKey.split(',').map(Number); - path.unshift({ x: sx, y: sy }); + const [sx, sy] = startKey.split(',').map(Number) + path.unshift({ x: sx, y: sy }) } - return path.length > 0 ? path : null; + return path.length > 0 ? path : null } + // 최종 함수 function getPath(start, end, graph, epsilon = 1) { // startPoint와 arrivalPoint가 될 수 있는 점은 line.attributes.type이 'default' 혹은 null이 아닌 line인 경우에만 가능 From b76f6eb057b2bcd502bb7099545afcbcc639010d Mon Sep 17 00:00:00 2001 From: ysCha Date: Tue, 16 Dec 2025 14:12:44 +0900 Subject: [PATCH 11/28] =?UTF-8?q?[1346]=EB=AC=B8=EC=9D=98=EA=B2=8C?= =?UTF-8?q?=EC=8B=9C=ED=8C=90=20:=20cnustNm=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/layout.js | 3 ++- src/components/community/modal/QnaRegModal.jsx | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/app/layout.js b/src/app/layout.js index dbd6c9a1..e19c6bb7 100644 --- a/src/app/layout.js +++ b/src/app/layout.js @@ -58,7 +58,8 @@ export default async function RootLayout({ children }) { pwdInitYn: session.pwdInitYn, custCd: session.custCd, isLoggedIn: session.isLoggedIn, - builderNo: session.builderNo + builderNo: session.builderNo, + custNm: session.custNm } } if (!headerPathname.includes('/login') && !session.isLoggedIn) { diff --git a/src/components/community/modal/QnaRegModal.jsx b/src/components/community/modal/QnaRegModal.jsx index 00c444c6..280a1086 100644 --- a/src/components/community/modal/QnaRegModal.jsx +++ b/src/components/community/modal/QnaRegModal.jsx @@ -349,7 +349,7 @@ let fileCheck = false; {getMessage('qna.list.header.regNm')} - + E-Mail* Date: Tue, 16 Dec 2025 15:49:40 +0900 Subject: [PATCH 12/28] =?UTF-8?q?=EA=B0=9C=EA=B5=AC=20:=20=EA=B3=84?= =?UTF-8?q?=EC=82=B0=EA=B8=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../floor-plan/modal/object/DormerOffset.jsx | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/components/floor-plan/modal/object/DormerOffset.jsx b/src/components/floor-plan/modal/object/DormerOffset.jsx index 1881b18e..fd03f185 100644 --- a/src/components/floor-plan/modal/object/DormerOffset.jsx +++ b/src/components/floor-plan/modal/object/DormerOffset.jsx @@ -59,10 +59,10 @@ export default function DormerOffset(props) { name="" label="" className="input-origin block" - value={arrow1LengthRef.current.value ?? 0} + value={arrow1LengthRef.current?.value ?? 0} ref={arrow1LengthRef} - onChange={(value) => setArrow1Length(value)} - options={{ + onChange={() => {}} // No-op function to prevent error + options={{ allowNegative: false, allowDecimal: false, }} @@ -86,7 +86,20 @@ export default function DormerOffset(props) {
- + {/**/} + {}} // No-op function to prevent error + options={{ + allowNegative: false, + allowDecimal: false, + }} + />
mm
From 19bdaa52fd0136408d31aa1386e3fc1659ffc4b0 Mon Sep 17 00:00:00 2001 From: ysCha Date: Tue, 16 Dec 2025 16:07:18 +0900 Subject: [PATCH 13/28] =?UTF-8?q?=EB=B3=B4=EC=A1=B0=EC=84=A0=EB=A1=9C?= =?UTF-8?q?=EB=A7=8C=EB=93=A4=EA=B8=B0=20:=20=EA=B3=84=EC=82=B0=EA=B8=B0?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../modal/roofShape/passivity/Eaves.jsx | 15 ++++++++++++++- .../modal/roofShape/passivity/Gable.jsx | 16 +++++++++++++++- .../modal/roofShape/passivity/Shed.jsx | 16 +++++++++++++++- 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/src/components/floor-plan/modal/roofShape/passivity/Eaves.jsx b/src/components/floor-plan/modal/roofShape/passivity/Eaves.jsx index fac8701e..f3dd4052 100644 --- a/src/components/floor-plan/modal/roofShape/passivity/Eaves.jsx +++ b/src/components/floor-plan/modal/roofShape/passivity/Eaves.jsx @@ -43,7 +43,20 @@ export default function Eaves({ offsetRef, pitchRef, pitchText }) { {getMessage('eaves.offset')}
- + {/**/} + {}} + options={{ + allowNegative: false, + allowDecimal: false + }} + />
mm
diff --git a/src/components/floor-plan/modal/roofShape/passivity/Gable.jsx b/src/components/floor-plan/modal/roofShape/passivity/Gable.jsx index 6da1266e..fe04a65e 100644 --- a/src/components/floor-plan/modal/roofShape/passivity/Gable.jsx +++ b/src/components/floor-plan/modal/roofShape/passivity/Gable.jsx @@ -1,6 +1,7 @@ import { useMessage } from '@/hooks/useMessage' import { useRecoilValue } from 'recoil' import { currentAngleTypeSelector } from '@/store/canvasAtom' +import { CalculatorInput } from '@/components/common/input/CalcInput' export default function Gable({ offsetRef }) { const { getMessage } = useMessage() @@ -12,7 +13,20 @@ export default function Gable({ offsetRef }) { {getMessage('gable.offset')}
- + {/**/} + {}} + options={{ + allowNegative: false, + allowDecimal: false + }} + />
mm
diff --git a/src/components/floor-plan/modal/roofShape/passivity/Shed.jsx b/src/components/floor-plan/modal/roofShape/passivity/Shed.jsx index 474c2f60..67a47bb0 100644 --- a/src/components/floor-plan/modal/roofShape/passivity/Shed.jsx +++ b/src/components/floor-plan/modal/roofShape/passivity/Shed.jsx @@ -1,4 +1,5 @@ import { useMessage } from '@/hooks/useMessage' +import { CalculatorInput } from '@/components/common/input/CalcInput' export default function Shed({ offsetRef }) { const { getMessage } = useMessage() @@ -9,7 +10,20 @@ export default function Shed({ offsetRef }) { {getMessage('shed.width')}
- + {/**/} + {}} + options={{ + allowNegative: false, + allowDecimal: false + }} + />
mm From 4f483dbfee2518492a838e76cebfc35f8274e13d Mon Sep 17 00:00:00 2001 From: ysCha Date: Tue, 16 Dec 2025 16:20:26 +0900 Subject: [PATCH 14/28] =?UTF-8?q?=EC=99=B8=EB=B2=BD=ED=8E=B8=EC=A7=91?= =?UTF-8?q?=EC=98=A4=EB=B8=8C=EC=85=8B=20:=20=EA=B3=84=EC=82=B0=EA=B8=B0?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../modal/wallLineOffset/type/Offset.jsx | 16 ++++++++- .../modal/wallLineOffset/type/WallLine.jsx | 33 +++++++++++++++++-- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/src/components/floor-plan/modal/wallLineOffset/type/Offset.jsx b/src/components/floor-plan/modal/wallLineOffset/type/Offset.jsx index 76f09cc7..79ae0898 100644 --- a/src/components/floor-plan/modal/wallLineOffset/type/Offset.jsx +++ b/src/components/floor-plan/modal/wallLineOffset/type/Offset.jsx @@ -1,6 +1,7 @@ import { useMessage } from '@/hooks/useMessage' import { useEffect, useState } from 'react' import { useEvent } from '@/hooks/useEvent' +import { CalculatorInput } from '@/components/common/input/CalcInput' export default function Offset({ length1Ref, arrow1Ref, currentWallLineRef }) { const { getMessage } = useMessage() @@ -74,7 +75,20 @@ export default function Offset({ length1Ref, arrow1Ref, currentWallLineRef }) {
- + {/**/} + {}} + options={{ + allowNegative: false, + allowDecimal: false + }} + />
mm
diff --git a/src/components/floor-plan/modal/wallLineOffset/type/WallLine.jsx b/src/components/floor-plan/modal/wallLineOffset/type/WallLine.jsx index 92f6a10b..d78a202f 100644 --- a/src/components/floor-plan/modal/wallLineOffset/type/WallLine.jsx +++ b/src/components/floor-plan/modal/wallLineOffset/type/WallLine.jsx @@ -1,6 +1,7 @@ import { useMessage } from '@/hooks/useMessage' import { forwardRef, useEffect, useImperativeHandle, useState } from 'react' import { useEvent } from '@/hooks/useEvent' +import { CalculatorInput } from '@/components/common/input/CalcInput' export default forwardRef(function WallLine({ length1Ref, length2Ref, arrow1Ref, arrow2Ref, radioTypeRef, currentWallLineRef }, ref) { const { getMessage } = useMessage() @@ -46,7 +47,21 @@ export default forwardRef(function WallLine({ length1Ref, length2Ref, arrow1Ref,
- + {/**/} + {}} + readOnly={type !== 1} + options={{ + allowNegative: false, + allowDecimal: false + }} + />
mm
@@ -80,7 +95,21 @@ export default forwardRef(function WallLine({ length1Ref, length2Ref, arrow1Ref,
- + {/**/} + {}} + readOnly={type !== 2} + options={{ + allowNegative: false, + allowDecimal: false + }} + />
mm
From bbf2dfbe7c92cb62579c6691c608f7caee7fb9c3 Mon Sep 17 00:00:00 2001 From: ysCha Date: Tue, 16 Dec 2025 17:14:47 +0900 Subject: [PATCH 15/28] =?UTF-8?q?=EB=B3=B4=EC=A1=B0=EC=84=A0=EC=82=AC?= =?UTF-8?q?=EC=9D=B4=EC=A6=88=EB=B3=80=EA=B2=BD=20:=20=EA=B3=84=EC=82=B0?= =?UTF-8?q?=EA=B8=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../modal/auxiliary/AuxiliarySize.jsx | 38 ++++++++++++++++--- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/src/components/floor-plan/modal/auxiliary/AuxiliarySize.jsx b/src/components/floor-plan/modal/auxiliary/AuxiliarySize.jsx index 5a9cde6f..40fb9f76 100644 --- a/src/components/floor-plan/modal/auxiliary/AuxiliarySize.jsx +++ b/src/components/floor-plan/modal/auxiliary/AuxiliarySize.jsx @@ -8,19 +8,21 @@ import { useEffect, useState } from 'react' import Big from 'big.js' import { calcLineActualSize, calcLinePlaneSize } from '@/util/qpolygon-utils' import { normalizeDigits } from '@/util/input-utils' +import { CalculatorInput } from '@/components/common/input/CalcInput' export default function AuxiliarySize(props) { const contextPopupPosition = useRecoilValue(contextPopupPositionState) const { id, pos = contextPopupPosition } = props const [checkedRadio, setCheckedRadio] = useState(null) - const [value1, setValue1] = useState(null) - const [value2, setValue2] = useState(null) + const [value1, setValue1] = useState('') + const [value2, setValue2] = useState('') const [size, setSize] = useState(0) const { getMessage } = useMessage() const { closePopup } = usePopup() const currentObject = useRecoilValue(currentObjectState) const canvas = useRecoilValue(canvasState) + useEffect(() => { return () => { canvas?.discardActiveObject() @@ -37,7 +39,7 @@ export default function AuxiliarySize(props) { }, [currentObject]) const handleInput = (e) => { - let value = e.target.value.replace(/^0+/, '') + let value = e.replace(/^0+/, '') if (value === '') { if (checkedRadio === 1) setValue1(value) if (checkedRadio === 2) setValue2(value) @@ -130,7 +132,20 @@ export default function AuxiliarySize(props) {
{getMessage('length')}
- + {/**/} +
mm
@@ -149,7 +164,20 @@ export default function AuxiliarySize(props) {
{getMessage('length')}
- + {/**/} +
mm
From b81fc389f53b54bf2246aeef29f1d747e20ec790 Mon Sep 17 00:00:00 2001 From: ysCha Date: Tue, 16 Dec 2025 17:20:28 +0900 Subject: [PATCH 16/28] =?UTF-8?q?=EB=B3=B4=EC=A1=B0=EC=84=A0=EC=9D=B4?= =?UTF-8?q?=EB=8F=99=EB=B3=B5=EC=82=AC=20:=20=EA=B3=84=EC=82=B0=EA=B8=B0?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../modal/auxiliary/AuxiliaryEdit.jsx | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/components/floor-plan/modal/auxiliary/AuxiliaryEdit.jsx b/src/components/floor-plan/modal/auxiliary/AuxiliaryEdit.jsx index 938b5244..d7696a84 100644 --- a/src/components/floor-plan/modal/auxiliary/AuxiliaryEdit.jsx +++ b/src/components/floor-plan/modal/auxiliary/AuxiliaryEdit.jsx @@ -8,6 +8,7 @@ import { currentObjectState } from '@/store/canvasAtom' import { useAuxiliaryDrawing } from '@/hooks/roofcover/useAuxiliaryDrawing' import { useSwal } from '@/hooks/useSwal' import { normalizeDigits } from '@/util/input-utils' +import { CalculatorInput } from '@/components/common/input/CalcInput' export default function AuxiliaryEdit(props) { const contextPopupPosition = useRecoilValue(contextPopupPositionState) @@ -66,7 +67,19 @@ export default function AuxiliaryEdit(props) {

{getMessage('length')}

- setVerticalSize(normalizeDigits(e.target.value))} /> + {/* setVerticalSize(normalizeDigits(e.target.value))} />*/} + setVerticalSize(value)} + options={{ + allowNegative: false, + allowDecimal: false + }} + />
mm
@@ -88,7 +101,19 @@ export default function AuxiliaryEdit(props) {
- setHorizonSize(normalizeDigits(e.target.value))} /> + {/* setHorizonSize(normalizeDigits(e.target.value))} />*/} + setHorizonSize(value)} + options={{ + allowNegative: false, + allowDecimal: false + }} + />
mm
From 6f47b9dc540219b5292e004fb986a4c33c2d01b2 Mon Sep 17 00:00:00 2001 From: ysCha Date: Tue, 16 Dec 2025 17:39:28 +0900 Subject: [PATCH 17/28] =?UTF-8?q?=EB=A9=B4=ED=98=95=EC=83=81=EB=B0=B0?= =?UTF-8?q?=EC=B9=98=20:=20=EA=B3=84=EC=82=B0=EA=B8=B0=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../placementSurface/PlacementSurface.jsx | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/src/components/floor-plan/modal/placementSurface/PlacementSurface.jsx b/src/components/floor-plan/modal/placementSurface/PlacementSurface.jsx index 7ab89106..4ede1367 100644 --- a/src/components/floor-plan/modal/placementSurface/PlacementSurface.jsx +++ b/src/components/floor-plan/modal/placementSurface/PlacementSurface.jsx @@ -1,6 +1,7 @@ import Image from 'next/image' import { useMessage } from '@/hooks/useMessage' import { forwardRef, useState } from 'react' +import { CalculatorInput } from '@/components/common/input/CalcInput' const PlacementSurface = forwardRef((props, refs) => { const { getMessage } = useMessage() @@ -74,10 +75,32 @@ const PlacementSurface = forwardRef((props, refs) => {
- */} + + {}} ref={ line.isDiagonal ? lengthetc @@ -91,6 +114,10 @@ const PlacementSurface = forwardRef((props, refs) => { ? length4 : length5 } + options={{ + allowNegative: false, + allowDecimal: false + }} />
mm From 8eeff43b4c4824e377e8aad21313b8018ddb3f00 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Tue, 16 Dec 2025 17:52:41 +0900 Subject: [PATCH 18/28] =?UTF-8?q?=EC=A7=80=EB=B6=95=EC=9E=AC=20=ED=95=A0?= =?UTF-8?q?=EB=8B=B9=20=EC=A0=84=20=EC=A7=80=EB=B6=95=20=EC=98=AE=EA=B8=B8?= =?UTF-8?q?=20=EC=8B=9C=20outerLinePoints=20=EC=A2=8C=ED=91=9C=EB=8F=84=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=ED=95=84=EC=9A=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/common/useRoofFn.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/hooks/common/useRoofFn.js b/src/hooks/common/useRoofFn.js index eb640d76..1148eed3 100644 --- a/src/hooks/common/useRoofFn.js +++ b/src/hooks/common/useRoofFn.js @@ -1,4 +1,4 @@ -import { useRecoilValue, useResetRecoilState } from 'recoil' +import { useRecoilState, useRecoilValue, useResetRecoilState } from 'recoil' import { canvasState, currentObjectState } from '@/store/canvasAtom' import { selectedRoofMaterialSelector } from '@/store/settingAtom' import { ROOF_MATERIAL_LAYOUT } from '@/components/floor-plan/modal/placementShape/PlacementShapeSetting' @@ -25,6 +25,7 @@ export function useRoofFn() { const { addPitchText } = useLine() const { setPolygonLinesActualSize } = usePolygon() const { changeCorridorDimensionText } = useText() + const [outerLinePoints, setOuterLinePoints] = useRecoilState(outerLinePointsState) //면형상 선택 클릭시 지붕 패턴 입히기 function setSurfaceShapePattern(polygon, mode = 'onlyBorder', trestleMode = false, roofMaterial, isForceChange = false, isDisplay = false) { @@ -263,6 +264,9 @@ export function useRoofFn() { const deltaX = roof.left - originalRoofLeft const deltaY = roof.top - originalRoofTop + const originOuterLinePoints = [...outerLinePoints] + setOuterLinePoints(originOuterLinePoints.map((point) => ({ x: point.x + deltaX, y: point.y + deltaY }))) + // Move all related objects by the delta allRoofObject.forEach((obj) => { if (obj.points !== undefined) { From 7bf2db7479b4221ea749066b8755ca9aeb86b2ca Mon Sep 17 00:00:00 2001 From: ysCha Date: Tue, 16 Dec 2025 17:58:46 +0900 Subject: [PATCH 19/28] =?UTF-8?q?=EB=8F=99=EC=9D=B4=EB=8F=99=ED=98=84?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=20:=20=EA=B3=84=EC=82=B0=EA=B8=B0=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../modal/movement/type/FlowLine.jsx | 21 +++++++++++++++--- .../floor-plan/modal/movement/type/Updown.jsx | 22 ++++++++++++++++--- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/components/floor-plan/modal/movement/type/FlowLine.jsx b/src/components/floor-plan/modal/movement/type/FlowLine.jsx index 7c400fd4..d4267a27 100644 --- a/src/components/floor-plan/modal/movement/type/FlowLine.jsx +++ b/src/components/floor-plan/modal/movement/type/FlowLine.jsx @@ -2,6 +2,7 @@ import { useMessage } from '@/hooks/useMessage' import { useState } from 'react' import { currentObjectState } from '@/store/canvasAtom' import { useRecoilValue } from 'recoil' +import { CalculatorInput } from '@/components/common/input/CalcInput' const FLOW_LINE_TYPE = { DOWN_LEFT: 'downLeft', @@ -69,13 +70,27 @@ export default function FlowLine({ FLOW_LINE_REF }) {
{getMessage('modal.movement.flow.line.movement')}
- */} + {setFilledInput(value)}} + options={{ + allowNegative: false, + allowDecimal: false + }} />
mm diff --git a/src/components/floor-plan/modal/movement/type/Updown.jsx b/src/components/floor-plan/modal/movement/type/Updown.jsx index f03685e7..ab88bbbf 100644 --- a/src/components/floor-plan/modal/movement/type/Updown.jsx +++ b/src/components/floor-plan/modal/movement/type/Updown.jsx @@ -3,6 +3,7 @@ import { useState } from 'react' import { useRecoilValue } from 'recoil' import { currentObjectState } from '@/store/canvasAtom' import { normalizeDigits } from '@/util/input-utils' +import { CalculatorInput } from '@/components/common/input/CalcInput' const UP_DOWN_TYPE = { UP: 'up', @@ -35,6 +36,7 @@ export default function Updown({ UP_DOWN_REF }) { {getMessage('modal.movement.flow.line.position')}
+
@@ -68,13 +70,27 @@ export default function Updown({ UP_DOWN_REF }) {
{getMessage('modal.movement.flow.line.movement')}
- */} + {setFilledInput(value)}} + options={{ + allowNegative: false, + allowDecimal: false + }} />
mm From eb71af3799bcb7f289459a77846ce6c427ab304f Mon Sep 17 00:00:00 2001 From: ysCha Date: Wed, 17 Dec 2025 15:36:36 +0900 Subject: [PATCH 20/28] =?UTF-8?q?=EC=A4=91=EB=B3=B5=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/roofcover/useMovementSetting.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/hooks/roofcover/useMovementSetting.js b/src/hooks/roofcover/useMovementSetting.js index 2525cdf6..795086fb 100644 --- a/src/hooks/roofcover/useMovementSetting.js +++ b/src/hooks/roofcover/useMovementSetting.js @@ -575,7 +575,19 @@ export function useMovementSetting(id) { targetBaseLines.push({ line: target, distance: 0 }) } - targetBaseLines.sort((a, b) => a.distance - b.distance) + // Remove duplicate lines + const uniqueLines = new Map(); + targetBaseLines = targetBaseLines.filter(item => { + const key = `${item.line.x1},${item.line.y1},${item.line.x2},${item.line.y2}`; + if (!uniqueLines.has(key)) { + uniqueLines.set(key, true); + return true; + } + return false; + }); + + // Sort by distance + targetBaseLines.sort((a, b) => a.distance - b.distance); targetBaseLines = targetBaseLines.filter((line) => line.distance === targetBaseLines[0].distance) if (isGableRoof) { From d096aae6db494867fd1c18ad55f051ef404186ec Mon Sep 17 00:00:00 2001 From: ysCha Date: Wed, 17 Dec 2025 15:37:37 +0900 Subject: [PATCH 21/28] =?UTF-8?q?skt=EC=BB=A8=ED=85=90=EC=B8=A0=EB=A9=94?= =?UTF-8?q?=EB=89=B4=EB=85=B8=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useContextMenu.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/hooks/useContextMenu.js b/src/hooks/useContextMenu.js index 46ced154..a863fd01 100644 --- a/src/hooks/useContextMenu.js +++ b/src/hooks/useContextMenu.js @@ -162,6 +162,7 @@ export function useContextMenu() { case 'auxiliaryLine': case 'hip': case 'ridge': + case 'eaveHelpLine': if (selectedMenu === 'surface') { setContextMenu([ [ From e7d83b727a45eec1331c0124a3f2ce494ea239cd Mon Sep 17 00:00:00 2001 From: ysCha Date: Wed, 17 Dec 2025 15:51:31 +0900 Subject: [PATCH 22/28] =?UTF-8?q?=EB=8F=99->=ED=98=84=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util/skeleton-utils.js | 1661 ++++++++++++++++++------------------ 1 file changed, 807 insertions(+), 854 deletions(-) diff --git a/src/util/skeleton-utils.js b/src/util/skeleton-utils.js index b3a59e79..383c82e0 100644 --- a/src/util/skeleton-utils.js +++ b/src/util/skeleton-utils.js @@ -159,7 +159,7 @@ const movingLineFromSkeleton = (roofId, canvas) => { // console.log("3333linePosition:::::", result.position); const position = movePosition //result.position; - const absMove = Big(moveUpDown).times(1).div(10); + const moveUpDownLength = Big(moveUpDown).times(1).div(10); const modifiedStartPoints = []; // oldPoints를 복사해서 새로운 points 배열 생성 let newPoints = oldPoints.map(point => ({...point})); @@ -181,64 +181,22 @@ const movingLineFromSkeleton = (roofId, canvas) => { const offset = line.attributes.offset // 새로운 좌표 계산 let newStartPoint = {...originalStartPoint}; - let newEndPoint = {...originalEndPoint}; - - // 위치와 방향에 따라 좌표 조정 - /* - switch (position) { - case 'left': - if (moveDirection === 'up') { - newStartPoint.x = Big(line.startPoint.x).minus(absMove).toNumber(); - newEndPoint.x = Big(line.endPoint.x).minus(absMove).toNumber(); - } else if (moveDirection === 'down') { - newStartPoint.x = Big(line.startPoint.x).plus(absMove).toNumber(); - newEndPoint.x = Big(line.endPoint.x).plus(absMove).toNumber(); - } - break; - case 'right': - if (moveDirection === 'up') { - newStartPoint.x = Big(line.startPoint.x).plus(absMove).toNumber(); - newEndPoint.x = Big(line.endPoint.x).plus(absMove).toNumber(); - } else if (moveDirection === 'down') { - newStartPoint.x = Big(line.startPoint.x).minus(absMove).toNumber(); - newEndPoint.x = Big(line.endPoint.x).minus(absMove).toNumber(); - } - break; - case 'top': - if (moveDirection === 'up') { - newStartPoint.y = Big(line.startPoint.y).minus(absMove).toNumber(); - newEndPoint.y = Big(line.endPoint.y).minus(absMove).toNumber(); - } else if (moveDirection === 'down') { - newStartPoint.y = Big(line.startPoint.y).plus(absMove).toNumber(); - newEndPoint.y = Big(line.endPoint.y).plus(absMove).toNumber(); - } - break; - case 'bottom': - if (moveDirection === 'up') { - newStartPoint.y = Big(line.startPoint.y).plus(absMove).toNumber(); - newEndPoint.y = Big(line.endPoint.y).plus(absMove).toNumber(); - } else if (moveDirection === 'down') { - newStartPoint.y = Big(line.startPoint.y).minus(absMove).toNumber(); - newEndPoint.y = Big(line.endPoint.y).minus(absMove).toNumber(); - } - break; - } - */ + let newEndPoint = {...originalEndPoint} // 원본 라인 업데이트 // newPoints 배열에서 일치하는 포인트들을 찾아서 업데이트 - console.log('absMove::', absMove); + console.log('absMove::', moveUpDownLength); newPoints.forEach((point, index) => { if(position === 'bottom'){ if (moveDirection === 'in') { if(isSamePoint(roof.basePoints[index], originalStartPoint) || isSamePoint(roof.basePoints[index], originalEndPoint)) { - point.y = Big(point.y).minus(absMove).toNumber(); + point.y = Big(point.y).minus(moveUpDownLength).toNumber(); } // if (isSamePoint(roof.basePoints[index], originalEndPoint)) { // point.y = Big(point.y).minus(absMove).toNumber(); // } }else if (moveDirection === 'out'){ if(isSamePoint(roof.basePoints[index], originalStartPoint) || isSamePoint(roof.basePoints[index], originalEndPoint)) { - point.y = Big(point.y).plus(absMove).toNumber(); + point.y = Big(point.y).plus(moveUpDownLength).toNumber(); } // if (isSamePoint(roof.basePoints[index], originalEndPoint)) { // point.y = Big(point.y).plus(absMove).toNumber(); @@ -248,31 +206,31 @@ const movingLineFromSkeleton = (roofId, canvas) => { }else if (position === 'top'){ if(moveDirection === 'in'){ if(isSamePoint(roof.basePoints[index], originalStartPoint)) { - point.y = Big(point.y).plus(absMove).toNumber(); + point.y = Big(point.y).plus(moveUpDownLength).toNumber(); } if (isSamePoint(roof.basePoints[index], originalEndPoint)) { - point.y = Big(point.y).plus(absMove).toNumber(); + point.y = Big(point.y).plus(moveUpDownLength).toNumber(); } }else if(moveDirection === 'out'){ if(isSamePoint(roof.basePoints[index], originalStartPoint)) { - point.y = Big(point.y).minus(absMove).toNumber(); + point.y = Big(point.y).minus(moveUpDownLength).toNumber(); } if (isSamePoint(roof.basePoints[index], originalEndPoint)) { - point.y = Big(point.y).minus(absMove).toNumber(); + point.y = Big(point.y).minus(moveUpDownLength).toNumber(); } } }else if(position === 'left'){ if(moveDirection === 'in'){ if(isSamePoint(roof.basePoints[index], originalStartPoint) || isSamePoint(roof.basePoints[index], originalEndPoint)) { - point.x = Big(point.x).plus(absMove).toNumber(); + point.x = Big(point.x).plus(moveUpDownLength).toNumber(); } // if (isSamePoint(roof.basePoints[index], originalEndPoint)) { // point.x = Big(point.x).plus(absMove).toNumber(); // } }else if(moveDirection === 'out'){ if(isSamePoint(roof.basePoints[index], originalStartPoint) || isSamePoint(roof.basePoints[index], originalEndPoint)) { - point.x = Big(point.x).minus(absMove).toNumber(); + point.x = Big(point.x).minus(moveUpDownLength).toNumber(); } // if (isSamePoint(roof.basePoints[index], originalEndPoint)) { // point.x = Big(point.x).minus(absMove).toNumber(); @@ -282,17 +240,17 @@ const movingLineFromSkeleton = (roofId, canvas) => { }else if(position === 'right'){ if(moveDirection === 'in'){ if(isSamePoint(roof.basePoints[index], originalStartPoint)) { - point.x = Big(point.x).minus(absMove).toNumber(); + point.x = Big(point.x).minus(moveUpDownLength).toNumber(); } if (isSamePoint(roof.basePoints[index], originalEndPoint)) { - point.x = Big(point.x).minus(absMove).toNumber(); + point.x = Big(point.x).minus(moveUpDownLength).toNumber(); } }else if(moveDirection === 'out'){ if(isSamePoint(roof.basePoints[index], originalStartPoint)) { - point.x = Big(point.x).plus(absMove).toNumber(); + point.x = Big(point.x).plus(moveUpDownLength).toNumber(); } if (isSamePoint(roof.basePoints[index], originalEndPoint)) { - point.x = Big(point.x).plus(absMove).toNumber(); + point.x = Big(point.x).plus(moveUpDownLength).toNumber(); } } @@ -440,8 +398,8 @@ export const skeletonBuilder = (roofId, canvas, textMode) => { */ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { if (!skeleton?.Edges) return [] - let roof = canvas?.getObjects().find((object) => object.id === roofId) - let wall = canvas.getObjects().find((obj) => obj.name === POLYGON_TYPE.WALL && obj.attributes.roofId === roofId) + const roof = canvas?.getObjects().find((object) => object.id === roofId) + const wall = canvas.getObjects().find((obj) => obj.name === POLYGON_TYPE.WALL && obj.attributes.roofId === roofId) let skeletonLines = [] let findPoints = []; @@ -650,902 +608,897 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { canvas.renderAll(); }); - //if((roof.moveUpDown??0 > 0) ) { + if((roof.moveUpDown??0 > 0) || (roof.moveFlowLine??0 > 0) ) { - // 같은 라인이 없으므로 새 다각형 라인 생성 - //라인 편집 - // let i = 0 - const currentRoofLines = canvas.getObjects().filter((obj) => obj.lineName === 'roofLine' && obj.attributes.roofId === roofId) - let roofLineRects = canvas.getObjects().filter((obj) => obj.name === 'roofLineRect' && obj.roofId === roofId) + const getMoveUpDownLine = () => { + // 같은 라인이 없으므로 새 다각형 라인 생성 + //라인 편집 + // let i = 0 + const currentRoofLines = canvas.getObjects().filter((obj) => obj.lineName === 'roofLine' && obj.attributes.roofId === roofId) + let roofLineRects = canvas.getObjects().filter((obj) => obj.name === 'roofLineRect' && obj.roofId === roofId) - roofLineRects.forEach((roofLineRect) => { - canvas.remove(roofLineRect) - canvas.renderAll() - }) + roofLineRects.forEach((roofLineRect) => { + canvas.remove(roofLineRect) + canvas.renderAll() + }) - let helpLines = canvas.getObjects().filter((obj) => obj.lineName === 'helpLine' && obj.roofId === roofId) - helpLines.forEach((helpLine) => { - canvas.remove(helpLine) - canvas.renderAll() - }) + let helpLines = canvas.getObjects().filter((obj) => obj.lineName === 'helpLine' && obj.roofId === roofId) + helpLines.forEach((helpLine) => { + canvas.remove(helpLine) + canvas.renderAll() + }) - function sortCurrentRoofLines(lines) { - return [...lines].sort((a, b) => { - // Get all coordinates in a consistent order - const getCoords = (line) => { - const x1 = line.x1 ?? line.get('x1'); - const y1 = line.y1 ?? line.get('y1'); - const x2 = line.x2 ?? line.get('x2'); - const y2 = line.y2 ?? line.get('y2'); + function sortCurrentRoofLines(lines) { + return [...lines].sort((a, b) => { + // Get all coordinates in a consistent order + const getCoords = (line) => { + const x1 = line.x1 ?? line.get('x1'); + const y1 = line.y1 ?? line.get('y1'); + const x2 = line.x2 ?? line.get('x2'); + const y2 = line.y2 ?? line.get('y2'); - // Sort points left-to-right, then top-to-bottom - return x1 < x2 || (x1 === x2 && y1 < y2) - ? [x1, y1, x2, y2] - : [x2, y2, x1, y1]; - }; + // Sort points left-to-right, then top-to-bottom + return x1 < x2 || (x1 === x2 && y1 < y2) + ? [x1, y1, x2, y2] + : [x2, y2, x1, y1]; + }; - const aCoords = getCoords(a); - const bCoords = getCoords(b); + const aCoords = getCoords(a); + const bCoords = getCoords(b); - // Compare each coordinate in order - for (let i = 0; i < 4; i++) { - if (Math.abs(aCoords[i] - bCoords[i]) > 0.1) { - return aCoords[i] - bCoords[i]; - } + // Compare each coordinate in order + for (let i = 0; i < 4; i++) { + if (Math.abs(aCoords[i] - bCoords[i]) > 0.1) { + return aCoords[i] - bCoords[i]; } - return 0; - }); - } - - - // function sortCurrentRoofLines(lines) { - // return [...lines].sort((a, b) => { - // const aX = a.x1 ?? a.get('x1') - // const aY = a.y1 ?? a.get('y1') - // const bX = b.x1 ?? b.get('x1') - // const bY = b.y1 ?? b.get('y1') - - // if (aX !== bX) return aX - bX - // return aY - bY - // }) - // } - + } + return 0; + }); + } // 각 라인 집합 정렬 - // roofLines의 방향에 맞춰 currentRoofLines의 방향을 조정 - const alignLineDirection = (sourceLines, targetLines) => { - return sourceLines.map(sourceLine => { - // 가장 가까운 targetLine 찾기 - const nearestTarget = targetLines.reduce((nearest, targetLine) => { - const sourceCenter = { - x: (sourceLine.x1 + sourceLine.x2) / 2, - y: (sourceLine.y1 + sourceLine.y2) / 2 - }; - const targetCenter = { - x: (targetLine.x1 + targetLine.x2) / 2, - y: (targetLine.y1 + targetLine.y2) / 2 - }; - const distance = Math.hypot( - sourceCenter.x - targetCenter.x, - sourceCenter.y - targetCenter.y - ); - - return !nearest || distance < nearest.distance - ? { line: targetLine, distance } - : nearest; - }, null)?.line; - - if (!nearestTarget) return sourceLine; - - // 방향이 반대인지 확인 (벡터 내적을 사용) - const sourceVec = { - x: sourceLine.x2 - sourceLine.x1, - y: sourceLine.y2 - sourceLine.y1 + // roofLines의 방향에 맞춰 currentRoofLines의 방향을 조정 + const alignLineDirection = (sourceLines, targetLines) => { + return sourceLines.map(sourceLine => { + // 가장 가까운 targetLine 찾기 + const nearestTarget = targetLines.reduce((nearest, targetLine) => { + const sourceCenter = { + x: (sourceLine.x1 + sourceLine.x2) / 2, + y: (sourceLine.y1 + sourceLine.y2) / 2 }; - const targetVec = { - x: nearestTarget.x2 - nearestTarget.x1, - y: nearestTarget.y2 - nearestTarget.y1 + const targetCenter = { + x: (targetLine.x1 + targetLine.x2) / 2, + y: (targetLine.y1 + targetLine.y2) / 2 }; + const distance = Math.hypot( + sourceCenter.x - targetCenter.x, + sourceCenter.y - targetCenter.y + ); - const dotProduct = sourceVec.x * targetVec.x + sourceVec.y * targetVec.y; + return !nearest || distance < nearest.distance + ? { line: targetLine, distance } + : nearest; + }, null)?.line; - // 내적이 음수이면 방향이 반대이므로 뒤집기 - if (dotProduct < 0) { - return { - ...sourceLine, - x1: sourceLine.x2, - y1: sourceLine.y2, - x2: sourceLine.x1, - y2: sourceLine.y1 - }; - } + if (!nearestTarget) return sourceLine; - return sourceLine; - }); - }; + // 방향이 반대인지 확인 (벡터 내적을 사용) + const sourceVec = { + x: sourceLine.x2 - sourceLine.x1, + y: sourceLine.y2 - sourceLine.y1 + }; + const targetVec = { + x: nearestTarget.x2 - nearestTarget.x1, + y: nearestTarget.y2 - nearestTarget.y1 + }; - // const sortedWallLines = sortCurrentRoofLines(wall.lines); - // roofLines의 방향에 맞춰 currentRoofLines 조정 후 정렬 - const alignedCurrentRoofLines = alignLineDirection(currentRoofLines, roofLines); - const sortedCurrentRoofLines = sortCurrentRoofLines(alignedCurrentRoofLines); - // const sortedRoofLines = sortCurrentRoofLines(roofLines); - const sortedWallBaseLines = sortCurrentRoofLines(wall.baseLines); - // const sortedBaseLines = sortBaseLinesByWallLines(wall.baseLines, wallLines); - const sortRoofLines = sortBaseLinesByWallLines(roofLines, wallLines); + const dotProduct = sourceVec.x * targetVec.x + sourceVec.y * targetVec.y; - // 원본 wallLines를 복사하여 사용 - const sortedWallLines = [...wallLines]; - const sortedBaseLines = sortBaseLinesByWallLines(wall.baseLines, sortedWallLines); - const sortedRoofLines = sortBaseLinesByWallLines(roofLines, sortedWallLines); - - //wall.lines 는 기본 벽 라인 - //wall.baseLine은 움직인라인 - const movedLines = [] - - - wallLines.forEach((wallLine, index) => { - - - // const roofLine = sortedRoofLines[index]; - // const currentRoofLine = sortedCurrentRoofLines[index]; - // const moveLine = sortedWallBaseLines[index] - // const wallBaseLine = sortedWallBaseLines[index] - - const roofLine = sortRoofLines[index]; - - if(roofLine.attributes.wallLine !== wallLine.id || (roofLine.idx - 1) !== index ){ - console.log("wallLine2::::", wallLine.id) - console.log('roofLine:::',roofLine.attributes.wallLine) - console.log("w:::",wallLine.startPoint, wallLine.endPoint) - console.log("R:::",roofLine.startPoint, roofLine.endPoint) - console.log("not matching roofLine", roofLine); - return false - }//roofLines.find(line => line.attributes.wallLineId === wallLine.attributes.wallId); - - const currentRoofLine = currentRoofLines[index]; - const moveLine = wall.baseLines[index] - const wallBaseLine = wall.baseLines[index] - //console.log("wallBaseLine", wallBaseLine); - - //roofline 외곽선 설정 - console.log("index::::", index) - console.log('roofLine:::',roofLine) - console.log('wallLine', wallLine) - console.log('wallBaseLine', wallBaseLine) - - - const origin = moveLine.attributes?.originPoint - if (!origin) return - - if (isSamePoint(moveLine, wallLine)) { - - return false + // 내적이 음수이면 방향이 반대이므로 뒤집기 + if (dotProduct < 0) { + return { + ...sourceLine, + x1: sourceLine.x2, + y1: sourceLine.y2, + x2: sourceLine.x1, + y2: sourceLine.y1 + }; } - const movedStart = Math.abs(moveLine.x1 - wallLine.x1) > EPSILON || Math.abs(moveLine.y1 - origin.y1) > EPSILON - const movedEnd = Math.abs(moveLine.x2 - wallLine.x2) > EPSILON || Math.abs(moveLine.y2 - origin.y2) > EPSILON + return sourceLine; + }); + }; + + // const sortedWallLines = sortCurrentRoofLines(wall.lines); + // roofLines의 방향에 맞춰 currentRoofLines 조정 후 정렬 + const alignedCurrentRoofLines = alignLineDirection(currentRoofLines, roofLines); + const sortedCurrentRoofLines = sortCurrentRoofLines(alignedCurrentRoofLines); + // const sortedRoofLines = sortCurrentRoofLines(roofLines); + const sortedWallBaseLines = sortCurrentRoofLines(wall.baseLines); + // const sortedBaseLines = sortBaseLinesByWallLines(wall.baseLines, wallLines); + const sortRoofLines = sortBaseLinesByWallLines(roofLines, wallLines); + + // 원본 wallLines를 복사하여 사용 + const sortedWallLines = [...wallLines]; + const sortedBaseLines = sortBaseLinesByWallLines(wall.baseLines, sortedWallLines); + const sortedRoofLines = sortBaseLinesByWallLines(roofLines, sortedWallLines); + + //wall.lines 는 기본 벽 라인 + //wall.baseLine은 움직인라인 + const movedLines = [] + + // 조건에 맞는 라인들만 필터링 + const validWallLines = wallLines.filter((wallLine, index) => wallLine.idx - 1 === index); - const fullyMoved = movedStart && movedEnd + validWallLines.forEach((wallLine, index) => { + + const originalIndex = wallLines.indexOf(wallLine); + const roofLine = sortRoofLines[originalIndex]; + const currentRoofLine = currentRoofLines[originalIndex]; + const moveLine = wall.baseLines[originalIndex]; + const wallBaseLine = wall.baseLines[originalIndex]; + + // const roofLine = sortRoofLines[index]; + + if (roofLine.attributes.wallLine !== wallLine.id || (roofLine.idx - 1) !== index) { + console.log("wallLine2::::", wallLine.id) + console.log('roofLine:::', roofLine.attributes.wallLine) + console.log("w:::", wallLine.startPoint, wallLine.endPoint) + console.log("R:::", roofLine.startPoint, roofLine.endPoint) + console.log("not matching roofLine", roofLine); + return false + }//roofLines.find(line => line.attributes.wallLineId === wallLine.attributes.wallId); + + // const currentRoofLine = currentRoofLines[index]; + // const moveLine = wall.baseLines[index] + // const wallBaseLine = wall.baseLines[index] + //console.log("wallBaseLine", wallBaseLine); + + //roofline 외곽선 설정 + console.log("index::::", index) + console.log('roofLine:::', roofLine) + console.log('wallLine', wallLine) + console.log('wallBaseLine', wallBaseLine) + + + const origin = moveLine.attributes?.originPoint + if (!origin) return + + if (isSamePoint(moveLine, wallLine)) { + + return false + } + + const movedStart = Math.abs(moveLine.x1 - wallLine.x1) > EPSILON || Math.abs(moveLine.y1 - origin.y1) > EPSILON + const movedEnd = Math.abs(moveLine.x2 - wallLine.x2) > EPSILON || Math.abs(moveLine.y2 - origin.y2) > EPSILON + + + const fullyMoved = movedStart && movedEnd //반시계 방향 - let newPStart //= {x:roofLine.x1, y:roofLine.y1} - let newPEnd //= {x:movedLines.x2, y:movedLines.y2} + let newPStart //= {x:roofLine.x1, y:roofLine.y1} + let newPEnd //= {x:movedLines.x2, y:movedLines.y2} //현재 roof는 무조건 시계방향 - const getAddLine = (p1, p2, stroke = '') => { - movedLines.push({ index, p1, p2 }) + const getAddLine = (p1, p2, stroke = '') => { + movedLines.push({ index, p1, p2 }) // Usage: - // let mergeLines = mergeMovedLines(movedLines); - //console.log("mergeLines:::::::", mergeLines); - const line = new QLine([p1.x, p1.y, p2.x, p2.y], { - parentId : roof.id, - fontSize : roof.fontSize, - stroke : '#3FBAE6', - strokeWidth: 2, - name : 'eaveHelpLine', - lineName : 'eaveHelpLine', - selectable : true, - visible : true, - roofId : roofId, - attributes : { - type: 'eaveHelpLine', - isStart : true, - pitch: wallLine.attributes.pitch, - } - }); - //coordinateText(line) - canvas.add(line) - canvas.renderAll(); - return line - } + // let mergeLines = mergeMovedLines(movedLines); + //console.log("mergeLines:::::::", mergeLines); + const line = new QLine([p1.x, p1.y, p2.x, p2.y], { + parentId : roof.id, + fontSize : roof.fontSize, + stroke : '#3FBAE6', + strokeWidth: 4, + name : 'eaveHelpLine', + lineName : 'eaveHelpLine', + visible : true, + roofId : roofId, + selectable: true, + hoverCursor: 'pointer', + attributes : { + type : 'eaveHelpLine', + isStart: true, + pitch : wallLine.attributes.pitch, + } + }); - //getAddLine(roofLine.startPoint, roofLine.endPoint, ) //외곽선을 그린다 + //coordinateText(line) + canvas.add(line) + line.bringToFront() + canvas.renderAll(); + return line + } - newPStart = { x: roofLine.x1, y: roofLine.y1 } - newPEnd = { x: roofLine.x2, y: roofLine.y2 } + //getAddLine(roofLine.startPoint, roofLine.endPoint, ) //외곽선을 그린다 - const getInnerLines = (lines, point) => { + newPStart = { x: roofLine.x1, y: roofLine.y1 } + newPEnd = { x: roofLine.x2, y: roofLine.y2 } - } - let isIn = false - let isOut = false + const getInnerLines = (lines, point) => { + + } + let isIn = false + let isOut = false //두 포인트가 변경된 라인인 - if (fullyMoved ) { - //반시계방향향 + if (fullyMoved) { + //반시계방향향 - const mLine = getSelectLinePosition(wall, wallBaseLine) + const mLine = getSelectLinePosition(wall, wallBaseLine) - if (getOrientation(roofLine) === 'vertical') { + if (getOrientation(roofLine) === 'vertical') { - if (['left', 'right'].includes(mLine.position)) { - if(Math.abs(wallLine.x1 - wallBaseLine.x1) < 0.1 || Math.abs(wallLine.x2 - wallBaseLine.x2) < 0.1) { - return false + if (['left', 'right'].includes(mLine.position)) { + if (Math.abs(wallLine.x1 - wallBaseLine.x1) < 0.1 || Math.abs(wallLine.x2 - wallBaseLine.x2) < 0.1) { + return false + } + const positionType = + (mLine.position === 'left' && wallLine.x1 < wallBaseLine.x1) || + (mLine.position === 'right' && wallLine.x1 > wallBaseLine.x1) + ? 'in' : 'out'; + const condition = `${mLine.position}_${positionType}`; + let isStartEnd = findInteriorPoint(wallBaseLine, sortedBaseLines) + let sPoint, ePoint; + if (condition === 'left_in') { + isIn = true + + if (isStartEnd.start) { + newPEnd.y = roofLine.y2; + newPEnd.x = roofLine.x2; + + const moveDist = Big(wallBaseLine.x1).minus(wallLine.x1).abs().toNumber() + ePoint = { x: wallBaseLine.x1, y: wallBaseLine.y1 }; + newPStart.y = wallBaseLine.y1 + + findPoints.push({ x: ePoint.x, y: ePoint.y, position: 'left_in_start' }); + const newPointX = Big(roofLine.x1).plus(moveDist).abs().toNumber() + const pDist = Big(wallLine.x1).minus(roofLine.x1).abs().toNumber() + const pLineY = Big(roofLine.y1).minus(0).abs().toNumber() + let idx = (0 > index - 1) ? roofLines.length : index + const pLineX = roofLines[idx - 1].x1 + + getAddLine({ x: newPStart.x, y: newPStart.y }, { x: ePoint.x, y: ePoint.y }, 'blue') + getAddLine({ x: roofLine.x2, y: roofLine.y2 }, { x: newPointX, y: roofLine.y2 }, 'orange') + + if (Math.abs(wallBaseLine.y1 - wallLine.y1) < 0.1) { + getAddLine({ x: pLineX, y: pLineY }, { x: newPointX, y: pLineY }, 'green') + getAddLine({ x: newPointX, y: pLineY }, { x: ePoint.x, y: ePoint.y }, 'pink') + } } - const positionType = - (mLine.position === 'left' && wallLine.x1 < wallBaseLine.x1) || - (mLine.position === 'right' && wallLine.x1 > wallBaseLine.x1) - ? 'in' : 'out'; - const condition = `${mLine.position}_${positionType}`; - let isStartEnd = findInteriorPoint(wallBaseLine, sortedBaseLines) - let sPoint, ePoint; - if(condition === 'left_in') { - isIn = true - if (isStartEnd.start ) { - newPEnd.y = roofLine.y2; - newPEnd.x = roofLine.x2; + if (isStartEnd.end) { + newPStart.y = roofLine.y1; + newPStart.x = roofLine.x1; - const moveDist = Big(wallBaseLine.x1).minus(wallLine.x1).abs().toNumber() - ePoint = {x: wallBaseLine.x1, y: wallBaseLine.y1}; - newPStart.y = wallBaseLine.y1 + const moveDist = Big(wallBaseLine.x2).minus(wallLine.x2).abs().toNumber() + ePoint = { x: wallBaseLine.x2, y: wallBaseLine.y2 }; + newPEnd.y = wallBaseLine.y2 - findPoints.push({ x: ePoint.x, y: ePoint.y, position: 'left_in_start' }); - const newPointX = Big(roofLine.x1).plus(moveDist).abs().toNumber() - const pDist = Big(wallLine.x1).minus(roofLine.x1).abs().toNumber() - const pLineY = Big(roofLine.y1).minus(0).abs().toNumber() - let idx = (0 > index - 1)?roofLines.length:index - const pLineX = roofLines[idx-1].x1 + findPoints.push({ x: ePoint.x, y: ePoint.y, position: 'left_in_end' }); + const newPointX = Big(roofLine.x1).plus(moveDist).toNumber() + const pDist = Big(wallLine.x1).minus(roofLine.x1).abs().toNumber() + const pLineY = Big(roofLine.y2).minus(0).abs().toNumber() + let idx = (roofLines.length < index + 1) ? 0 : index + const pLineX = roofLines[idx + 1].x2 - getAddLine({ x: newPStart.x, y: newPStart.y }, { x: ePoint.x, y: ePoint.y }, 'blue') - getAddLine({ x: roofLine.x2, y: roofLine.y2 }, { x: newPointX, y: roofLine.y2 }, 'orange') + getAddLine({ x: newPEnd.x, y: newPEnd.y }, { x: ePoint.x, y: ePoint.y }, 'blue') + getAddLine({ x: roofLine.x1, y: roofLine.y1 }, { x: newPointX, y: roofLine.y1 }, 'orange') - if(Math.abs(wallBaseLine.y1 - wallLine.y1) < 0.1) { - getAddLine({ x: pLineX, y: pLineY }, { x: newPointX, y: pLineY }, 'green') - getAddLine({ x: newPointX, y: pLineY }, { x: ePoint.x, y: ePoint.y }, 'pink') - } + if (Math.abs(wallBaseLine.y2 - wallLine.y2) < 0.1) { + getAddLine({ x: pLineX, y: pLineY }, { x: newPointX, y: pLineY }, 'green') + getAddLine({ x: newPointX, y: pLineY }, { x: ePoint.x, y: ePoint.y }, 'pink') } + //getAddLine({ x: roofLine.x2, y: roofLine.y2 }, { x: newPointX, y: roofLine.y2 }, 'orange') + } - if(isStartEnd.end) { - newPStart.y = roofLine.y1; - newPStart.x = roofLine.x1; + } else if (condition === 'left_out') { + console.log("left_out::::isStartEnd:::::", isStartEnd); + if (isStartEnd.start) { - const moveDist = Big(wallBaseLine.x2).minus(wallLine.x2).abs().toNumber() - ePoint = {x: wallBaseLine.x2, y: wallBaseLine.y2}; - newPEnd.y = wallBaseLine.y2 + const moveDist = Big(wallLine.x1).minus(wallBaseLine.x1).abs().toNumber() + const aStartY = Big(roofLine.y1).minus(moveDist).abs().toNumber() + const bStartY = Big(wallLine.y1).minus(moveDist).abs().toNumber() + const inLine = findLineContainingPoint(innerLines, { y: aStartY, x: roofLine.x2 }) - findPoints.push({ x: ePoint.x, y: ePoint.y, position: 'left_in_end' }); - const newPointX = Big(roofLine.x1).plus(moveDist).toNumber() - const pDist = Big(wallLine.x1).minus(roofLine.x1).abs().toNumber() - const pLineY = Big(roofLine.y2).minus(0).abs().toNumber() - let idx = (roofLines.length < index + 1)?0:index - const pLineX = roofLines[idx+1].x2 - - getAddLine({ x: newPEnd.x, y: newPEnd.y }, { x: ePoint.x, y: ePoint.y }, 'blue') - getAddLine({ x: roofLine.x1, y: roofLine.y1 }, { x: newPointX, y: roofLine.y1 }, 'orange') - - if(Math.abs(wallBaseLine.y2 - wallLine.y2) < 0.1) { - getAddLine({ x: pLineX, y: pLineY }, { x: newPointX, y: pLineY }, 'green') - getAddLine({ x: newPointX, y: pLineY }, { x: ePoint.x, y: ePoint.y }, 'pink') - } - //getAddLine({ x: roofLine.x2, y: roofLine.y2 }, { x: newPointX, y: roofLine.y2 }, 'orange') - } - - }else if(condition === 'left_out') { - console.log("left_out::::isStartEnd:::::", isStartEnd); - if(isStartEnd.start){ - - const moveDist = Big(wallLine.x1).minus(wallBaseLine.x1).abs().toNumber() - const aStartY = Big(roofLine.y1).minus(moveDist).abs().toNumber() - const bStartY = Big(wallLine.y1).minus(moveDist).abs().toNumber() - const inLine = findLineContainingPoint(innerLines, { y: aStartY, x: roofLine.x2 }) - - const eLineY = Big(bStartY).minus(wallLine.y1).abs().toNumber() - newPStart.y = aStartY - newPEnd.y = roofLine.y2 //Big(roofLine.y2).minus(eLineY).toNumber() - let idx = (0 >= index - 1)?roofLines.length:index - const newLine = roofLines[idx-1]; - - if(Math.abs(wallBaseLine.y1 - wallLine.y1) < 0.1) { - if(inLine){ - if(inLine.x1 < inLine.x2) { - getAddLine({ y: bStartY, x: wallLine.x2 }, { y: inLine.y2, x: inLine.x2 }, 'pink') - }else{ - getAddLine({ y: inLine.y2, x: inLine.x2 },{ y: bStartY, x: wallLine.x2 }, 'pink') - } + const eLineY = Big(bStartY).minus(wallLine.y1).abs().toNumber() + newPStart.y = aStartY + newPEnd.y = roofLine.y2 //Big(roofLine.y2).minus(eLineY).toNumber() + let idx = (0 >= index - 1) ? roofLines.length : index + const newLine = roofLines[idx - 1]; + if (Math.abs(wallBaseLine.y1 - wallLine.y1) < 0.1) { + if (inLine) { + if (inLine.x1 < inLine.x2) { + getAddLine({ y: bStartY, x: wallLine.x2 }, { y: inLine.y2, x: inLine.x2 }, 'pink') + } else { + getAddLine({ y: inLine.y2, x: inLine.x2 }, { y: bStartY, x: wallLine.x2 }, 'pink') } - getAddLine({ y: bStartY, x: wallLine.x2 }, { y: roofLine.y1, x: wallLine.x1 }, 'magenta') - getAddLine({ y: newLine.y1, x: newLine.x1 }, { y: newLine.y2, x: wallLine.x2 }, 'Gray') - findPoints.push({ y: aStartY, x: newPStart.x, position: 'left_out_start' }); - }else{ - const cLineY = Big(wallBaseLine.x1).minus(wallLine.x1).abs().toNumber() - newPStart.y = Big(newPStart.y).minus(cLineY).toNumber(); + + } + getAddLine({ y: bStartY, x: wallLine.x2 }, { y: roofLine.y1, x: wallLine.x1 }, 'magenta') + getAddLine({ y: newLine.y1, x: newLine.x1 }, { y: newLine.y2, x: wallLine.x2 }, 'Gray') + findPoints.push({ y: aStartY, x: newPStart.x, position: 'left_out_start' }); + } else { + const cLineY = Big(wallBaseLine.x1).minus(wallLine.x1).abs().toNumber() + newPStart.y = Big(newPStart.y).minus(cLineY).toNumber(); + const inLine = findLineContainingPoint(innerLines, { y: newPStart.y, x: newPStart.x }) + if (inLine) { + if (inLine.x1 < inLine.x2) { + getAddLine({ y: newPStart.y, x: newPStart.x }, { y: inLine.y2, x: inLine.x2 }, 'purple') + } else { + getAddLine({ y: inLine.y1, x: inLine.x1 }, { y: newPStart.y, x: newPStart.x }, 'purple') + } + } else { + //newPStart.y = wallLine.y1; + //외곽 라인 그리기 + const rLineM = Big(wallBaseLine.x2).minus(roofLine.x2).abs().toNumber(); + newPStart.y = Big(wallBaseLine.y1).minus(rLineM).abs().toNumber(); const inLine = findLineContainingPoint(innerLines, { y: newPStart.y, x: newPStart.x }) - if(inLine){ - if(inLine.x1 < inLine.x2) { + if (inLine) { + if (inLine.x2 > inLine.x1) { getAddLine({ y: newPStart.y, x: newPStart.x }, { y: inLine.y2, x: inLine.x2 }, 'purple') - }else{ - getAddLine({ y: inLine.y1, x: inLine.x1},{ y: newPStart.y, x: newPStart.x }, 'purple') - } - }else { - //newPStart.y = wallLine.y1; - //외곽 라인 그리기 - const rLineM = Big(wallBaseLine.x2).minus(roofLine.x2).abs().toNumber(); - newPStart.y = Big(wallBaseLine.y1).minus(rLineM).abs().toNumber(); - const inLine = findLineContainingPoint(innerLines, { y: newPStart.y, x: newPStart.x }) - if(inLine) { - if (inLine.x2 > inLine.x1) { - getAddLine({ y: newPStart.y, x: newPStart.x }, { y: inLine.y2, x: inLine.x2 }, 'purple') - } else { - getAddLine({ y: inLine.y1, x: inLine.x1 }, { y: newPEnd.y, x: newPEnd.x }, 'purple') - } - } - } - - } - } - - - if(isStartEnd.end){ - const moveDist = Big(wallLine.x1).minus(wallBaseLine.x1).abs().toNumber() - const aStartY = Big(roofLine.y2).plus(moveDist).toNumber() - const bStartY = Big(wallLine.y2).plus(moveDist).toNumber() - const inLine = findLineContainingPoint(innerLines, { y: aStartY, x: roofLine.x1 }) - console.log("startLines:::::::", inLine); - const eLineY = Big(bStartY).minus(wallLine.y2).abs().toNumber() - newPEnd.y = aStartY - newPStart.y = roofLine.y1//Big(roofLine.y1).plus(eLineY).toNumber() - let idx = (roofLines.length < index + 1)?0:index - const newLine = roofLines[idx+1]; - - if(Math.abs(wallBaseLine.y2 - wallLine.y2) < 0.1) { - if(inLine){ - if(inLine.x1 < inLine.x2) { - getAddLine({ y: bStartY, x: wallLine.x1 }, { y: inLine.y2, x: inLine.x2 }, 'pink') - }else{ - getAddLine({ y: inLine.y1, x: inLine.x1 }, { y: bStartY, x: wallLine.x1 }, 'pink') - } - } - getAddLine({ y: bStartY, x: wallLine.x1 }, { y: roofLine.y2, x: wallLine.x2 }, 'magenta') - getAddLine({ y: newLine.y2, x: newLine.x2 }, { y: newLine.y1, x: wallLine.x1 }, 'Gray') - findPoints.push({ y: aStartY, x: newPEnd.x, position: 'left_out_end' }); - }else{ - const cLineY = Big(wallBaseLine.x2).minus(wallLine.x2).abs().toNumber() - newPEnd.y = Big(newPEnd.y).plus(cLineY).toNumber(); - const inLine = findLineContainingPoint(innerLines, { y: newPEnd.y, x: newPEnd.x }) - if(inLine){ - if(inLine.x1 < inLine.x2) { - getAddLine({ y: newPEnd.y, x: newPEnd.x }, { y: inLine.y2, x: inLine.x2 }, 'purple') - }else{ - getAddLine({ y: inLine.y1, x: inLine.x1 },{ y: newPEnd.y, x: newPEnd.x }, 'purple') - } - }else { - - // newPEnd.y = wallLine.y2 - - //외곽 라인 그리기 - const rLineM = Big(wallBaseLine.x2).minus(roofLine.x2).abs().toNumber(); - newPEnd.y = Big(wallBaseLine.y2).plus(rLineM).abs().toNumber(); - const inLine = findLineContainingPoint(innerLines, { y: newPEnd.y, x: newPEnd.x }) - if(inLine) { - if (inLine.x2 > inLine.x1) { - getAddLine({ y: newPEnd.y, x: newPEnd.x }, { y: inLine.y2, x: inLine.x2 }, 'purple') - } else { - getAddLine({ y: inLine.y1, x: inLine.x1 }, { y: newPEnd.y, x: newPEnd.x }, 'purple') - } - } - } - - } - findPoints.push({ y: newPStart.y, x: newPEnd.x, position: 'left_out_end' }); - } - }else if(condition === 'right_in') { - if (isStartEnd.start ) { - - newPEnd.y = roofLine.y2; - newPEnd.x = roofLine.x2; - - const moveDist = Big(wallBaseLine.x1).minus(wallLine.x1).abs().toNumber() - ePoint = {x: wallBaseLine.x1, y: wallBaseLine.y1}; - newPStart.y = wallBaseLine.y1 - - findPoints.push({ x: ePoint.x, y: ePoint.y, position: 'right_in_start'}); - const newPointX = Big(roofLine.x1).minus(moveDist).abs().toNumber() - const pDist = Big(wallLine.x1).minus(roofLine.x1).abs().toNumber() - const pLineY = Big(roofLine.y1).minus(0).abs().toNumber() - let idx = (0 >= index - 1)?roofLines.length:index - const pLineX = roofLines[idx-1].x1 - - getAddLine({ x: newPStart.x, y: newPStart.y }, { x: ePoint.x, y: ePoint.y }, 'blue') - //getAddLine({ x: roofLine.x2, y: roofLine.y2 }, { x: newPointX, y: roofLine.y2 }, 'orange') - - if(Math.abs(wallBaseLine.y1 - wallLine.y1) < 0.1) { - getAddLine({ x: pLineX, y: pLineY }, { x: newPointX, y: pLineY }, 'green') - getAddLine({ x: newPointX, y: pLineY }, { x: ePoint.x, y: ePoint.y }, 'pink') - } - } - - if(isStartEnd.end) { - newPStart.y = roofLine.y1; - newPStart.x = roofLine.x1; - - const moveDist = Big(wallBaseLine.x2).minus(wallLine.x2).abs().toNumber() - ePoint = {x: wallBaseLine.x2, y: wallBaseLine.y2}; - newPEnd.y = wallBaseLine.y2 - - findPoints.push({ x: ePoint.x, y: ePoint.y, position: 'right_in_end' }); - const newPointX = Big(roofLine.x1).minus(moveDist).toNumber() - const pDist = Big(wallLine.x1).minus(roofLine.x1).abs().toNumber() - const pLineY = Big(roofLine.y2).minus(0).abs().toNumber() - let idx = (roofLines.length < index + 1)?0:index - const pLineX = roofLines[idx+1].x2 - - getAddLine({ x: newPEnd.x, y: newPEnd.y }, { x: ePoint.x, y: ePoint.y }, 'blue') - getAddLine({ x: roofLine.x1, y: roofLine.y1 }, { x: newPointX, y: roofLine.y1 }, 'orange') - - if(Math.abs(wallBaseLine.y2 - wallLine.y2) < 0.1) { - getAddLine({ x: pLineX, y: pLineY }, { x: newPointX, y: pLineY }, 'green') - getAddLine({ x: newPointX, y: pLineY }, { x: ePoint.x, y: ePoint.y }, 'pink') - } - getAddLine({ x: roofLine.x2, y: roofLine.y2 }, { x: newPointX, y: roofLine.y2 }, 'orange') - } - - }else if(condition === 'right_out') { - console.log("right_out::::isStartEnd:::::", isStartEnd); - if (isStartEnd.start ) { //x1 inside - const moveDist = Big(wallLine.x1).minus(wallBaseLine.x1).abs().toNumber() - const aStartY = Big(roofLine.y1).plus(moveDist).abs().toNumber() - const bStartY = Big(wallLine.y1).plus(moveDist).abs().toNumber() - const inLine = findLineContainingPoint(innerLines, { y: aStartY, x: roofLine.x1 }) - console.log("startLines:::::::", inLine); - const eLineY = Big(bStartY).minus(wallLine.y1).abs().toNumber() - newPStart.y = aStartY - newPEnd.y = roofLine.y2//Big(roofLine.y2).plus(eLineY).toNumber() - let idx = (0 >= index - 1)?roofLines.length:index - const newLine = roofLines[idx-1]; - - if(Math.abs(wallBaseLine.y1 - wallLine.y1) < 0.1) { - if(inLine){ - if(inLine.x2 < inLine.x1) { - getAddLine({ y: bStartY, x: wallLine.x2 }, { y: inLine.y2, x: inLine.x2 }, 'pink') - }else{ - getAddLine({ y: inLine.y1, x: inLine.x1 },{ y: bStartY, x: wallLine.x2 }, 'pink') - } - } - getAddLine({ y: bStartY, x: wallLine.x2 }, { y: roofLine.y1, x: wallLine.x1 }, 'magenta') - getAddLine({ y: newLine.y1, x: newLine.x1 }, { y: newLine.y2, x: wallLine.x2 }, 'Gray') - findPoints.push({ y: aStartY, x: newPEnd.x, position: 'right_out_start' }); - }else{ - const cLineY = Big(wallBaseLine.x1).minus(wallLine.x1).abs().toNumber() - newPStart.y = Big(newPStart.y).plus(cLineY).toNumber(); - const inLine = findLineContainingPoint(innerLines, { y: newPStart.y, x: newPStart.x }) - if(inLine){ - if(inLine.x2 < inLine.x1 ) { - getAddLine({ y: newPStart.y, x: newPStart.x }, { y: inLine.y2, x: inLine.x2 }, 'purple') - }else{ - getAddLine({ y: inLine.y1, x: inLine.x1 },{ y: newPStart.y, x: newPStart.x }, 'purple') - } - }else { - //newPStart.y = wallLine.y1; - //외곽 라인 그리기 - const rLineM = Big(wallBaseLine.x1).minus(roofLine.x1).abs().toNumber(); - newPStart.y = Big(wallBaseLine.y1).plus(rLineM).abs().toNumber(); - const inLine = findLineContainingPoint(innerLines, { y: newPStart.y, x: newPStart.x }) - if(inLine){ - if(inLine.x2 > inLine.x1 ) { - getAddLine({ y: newPStart.y, x: newPStart.x }, { y: inLine.y1, x: inLine.x1 }, 'purple') - }else{ - getAddLine({ y: inLine.y2, x: inLine.x2 }, { y: newPStart.y, x: newPStart.x } , 'purple') - } - } - - } - - } - - } - - if(isStartEnd.end){ - const moveDist = Big(wallLine.x1).minus(wallBaseLine.x1).abs().toNumber() - const aStartY = Big(roofLine.y2).minus(moveDist).abs().toNumber() - const bStartY = Big(wallLine.y2).minus(moveDist).abs().toNumber() - const inLine = findLineContainingPoint(innerLines, { y: aStartY, x: roofLine.x1 }) - console.log("startLines:::::::", inLine); - const eLineY = Big(bStartY).minus(wallLine.y2).abs().toNumber() - newPEnd.y = aStartY - newPStart.y = roofLine.y1//Big(roofLine.y1).minus(eLineY).toNumber() - let idx = (roofLines.length < index + 1)?0:index - const newLine = roofLines[idx+1]; - if(inLine){ - if(inLine.x2 < inLine.x1) { - getAddLine({ y: bStartY, x: wallLine.x1 }, { y: inLine.y2, x: inLine.x2 }, 'pink') - }else{ - getAddLine({ y: inLine.y1, x: inLine.x1 },{ y: bStartY, x: wallLine.x1 }, 'pink') - } - } - if(Math.abs(wallBaseLine.y2 - wallLine.y2) < 0.1) { - getAddLine({ y: bStartY, x: wallLine.x1 }, { y: roofLine.y2, x: wallLine.x2 }, 'magenta') - getAddLine({ y: newLine.y2, x: newLine.x2 }, { y: newLine.y1, x: wallLine.x1 }, 'Gray') - findPoints.push({ y: aStartY, x: newPEnd.x, position: 'right_out_end' }); - }else{ - const cLineY = Big(wallBaseLine.x2).minus(wallLine.x2).abs().toNumber() - newPEnd.y = Big(newPEnd.y).minus(cLineY).toNumber(); - const inLine = findLineContainingPoint(innerLines, { y: newPEnd.y, x: newPEnd.x }) - if(inLine){ - if(inLine.x2 < inLine.x1) { - getAddLine({ y: newPEnd.y, x: newPEnd.x }, { y: inLine.y2, x: inLine.x2 }, 'purple') - }else{ + } else { getAddLine({ y: inLine.y1, x: inLine.x1 }, { y: newPEnd.y, x: newPEnd.x }, 'purple') } - }else { - //newPEnd.y = wallLine.y2; - - //외곽 라인 그리기 - const rLineM = Big(wallBaseLine.x2).minus(roofLine.x2).abs().toNumber(); - newPEnd.y = Big(wallBaseLine.y2).minus(rLineM).abs().toNumber(); - const inLine = findLineContainingPoint(innerLines, { y: newPEnd.y, x: newPEnd.x }) - if(inLine){ - if(inLine.x2 > inLine.x1 ) { - getAddLine({ y: newPEnd.y, x: newPEnd.x }, { y: inLine.y1, x: inLine.x1 }, 'purple') - }else{ - getAddLine({ y: inLine.y2, x: inLine.x2}, { y: newPEnd.y, x: newPEnd.x } , 'purple') - } - } - } - } + } } - } - } else if (getOrientation(roofLine) === 'horizontal') { //red - if (['top', 'bottom'].includes(mLine.position)) { - if(Math.abs(wallLine.y1 - wallBaseLine.y1) < 0.1 || Math.abs(wallLine.y2 - wallBaseLine.y2) < 0.1) { - return false + + if (isStartEnd.end) { + const moveDist = Big(wallLine.x1).minus(wallBaseLine.x1).abs().toNumber() + const aStartY = Big(roofLine.y2).plus(moveDist).toNumber() + const bStartY = Big(wallLine.y2).plus(moveDist).toNumber() + const inLine = findLineContainingPoint(innerLines, { y: aStartY, x: roofLine.x1 }) + console.log("startLines:::::::", inLine); + const eLineY = Big(bStartY).minus(wallLine.y2).abs().toNumber() + newPEnd.y = aStartY + newPStart.y = roofLine.y1//Big(roofLine.y1).plus(eLineY).toNumber() + let idx = (roofLines.length < index + 1) ? 0 : index + const newLine = roofLines[idx + 1]; + + if (Math.abs(wallBaseLine.y2 - wallLine.y2) < 0.1) { + if (inLine) { + if (inLine.x1 < inLine.x2) { + getAddLine({ y: bStartY, x: wallLine.x1 }, { y: inLine.y2, x: inLine.x2 }, 'pink') + } else { + getAddLine({ y: inLine.y1, x: inLine.x1 }, { y: bStartY, x: wallLine.x1 }, 'pink') + } + } + getAddLine({ y: bStartY, x: wallLine.x1 }, { y: roofLine.y2, x: wallLine.x2 }, 'magenta') + getAddLine({ y: newLine.y2, x: newLine.x2 }, { y: newLine.y1, x: wallLine.x1 }, 'Gray') + findPoints.push({ y: aStartY, x: newPEnd.x, position: 'left_out_end' }); + } else { + const cLineY = Big(wallBaseLine.x2).minus(wallLine.x2).abs().toNumber() + newPEnd.y = Big(newPEnd.y).plus(cLineY).toNumber(); + const inLine = findLineContainingPoint(innerLines, { y: newPEnd.y, x: newPEnd.x }) + if (inLine) { + if (inLine.x1 < inLine.x2) { + getAddLine({ y: newPEnd.y, x: newPEnd.x }, { y: inLine.y2, x: inLine.x2 }, 'purple') + } else { + getAddLine({ y: inLine.y1, x: inLine.x1 }, { y: newPEnd.y, x: newPEnd.x }, 'purple') + } + } else { + + // newPEnd.y = wallLine.y2 + + //외곽 라인 그리기 + const rLineM = Big(wallBaseLine.x2).minus(roofLine.x2).abs().toNumber(); + newPEnd.y = Big(wallBaseLine.y2).plus(rLineM).abs().toNumber(); + const inLine = findLineContainingPoint(innerLines, { y: newPEnd.y, x: newPEnd.x }) + if (inLine) { + if (inLine.x2 > inLine.x1) { + getAddLine({ y: newPEnd.y, x: newPEnd.x }, { y: inLine.y2, x: inLine.x2 }, 'purple') + } else { + getAddLine({ y: inLine.y1, x: inLine.x1 }, { y: newPEnd.y, x: newPEnd.x }, 'purple') + } + } + } + + } + findPoints.push({ y: newPStart.y, x: newPEnd.x, position: 'left_out_end' }); } - const positionType = - (mLine.position === 'top' && wallLine.y1 < wallBaseLine.y1) || - (mLine.position === 'bottom' && wallLine.y1 > wallBaseLine.y1) - ? 'in' : 'out'; + } else if (condition === 'right_in') { + if (isStartEnd.start) { - const condition = `${mLine.position}_${positionType}`; - let isStartEnd = findInteriorPoint(wallBaseLine, sortedBaseLines) + newPEnd.y = roofLine.y2; + newPEnd.x = roofLine.x2; - let sPoint, ePoint; + const moveDist = Big(wallBaseLine.x1).minus(wallLine.x1).abs().toNumber() + ePoint = { x: wallBaseLine.x1, y: wallBaseLine.y1 }; + newPStart.y = wallBaseLine.y1 - if(condition === 'top_in') { - if (isStartEnd.start ) { - const moveDist = Big(wallLine.y1).minus(wallBaseLine.y1).abs().toNumber() - sPoint = {x: wallBaseLine.x1, y: wallBaseLine.y1}; - newPStart.x = wallBaseLine.x1; + findPoints.push({ x: ePoint.x, y: ePoint.y, position: 'right_in_start' }); + const newPointX = Big(roofLine.x1).minus(moveDist).abs().toNumber() + const pDist = Big(wallLine.x1).minus(roofLine.x1).abs().toNumber() + const pLineY = Big(roofLine.y1).minus(0).abs().toNumber() + let idx = (0 >= index - 1) ? roofLines.length : index + const pLineX = roofLines[idx - 1].x1 + getAddLine({ x: newPStart.x, y: newPStart.y }, { x: ePoint.x, y: ePoint.y }, 'blue') + //getAddLine({ x: roofLine.x2, y: roofLine.y2 }, { x: newPointX, y: roofLine.y2 }, 'orange') - const newPointY = Big(roofLine.y2).plus(moveDist).toNumber() - - const pDist = Big(wallLine.y2).minus(roofLine.y2).abs().toNumber() - const pLineX = Big(roofLine.x1).minus(0).abs().toNumber() - let idx = (0 >= index - 1)?roofLines.length:index - const pLineY = roofLines[idx-1].y1 - getAddLine({ x: newPStart.x, y: newPStart.y }, { x: sPoint.x, y: sPoint.y }, 'blue') - findPoints.push({ x: sPoint.x, y: sPoint.y, position: 'top_in_start' }); - - if(Math.abs(wallBaseLine.x1 - wallLine.x1) < 0.1) { - getAddLine({ x: pLineX, y: pLineY }, { x: pLineX, y: newPointY }, 'green') - getAddLine({ x: pLineX, y: newPointY }, { x: sPoint.x, y: sPoint.y }, 'pink') - } - //getAddLine({ x: roofLine.x2, y: roofLine.y2 }, { x: roofLine.x2, y: newPointY }, 'orange') - + if (Math.abs(wallBaseLine.y1 - wallLine.y1) < 0.1) { + getAddLine({ x: pLineX, y: pLineY }, { x: newPointX, y: pLineY }, 'green') + getAddLine({ x: newPointX, y: pLineY }, { x: ePoint.x, y: ePoint.y }, 'pink') } + } - if(isStartEnd.end){ - const moveDist = Big(wallLine.y2).minus(wallBaseLine.y2).abs().toNumber() - sPoint = { x: wallBaseLine.x2, y: wallBaseLine.y2 } - newPEnd.x = wallBaseLine.x2 + if (isStartEnd.end) { + newPStart.y = roofLine.y1; + newPStart.x = roofLine.x1; - const newPointY = Big(roofLine.y1).plus(moveDist).toNumber() + const moveDist = Big(wallBaseLine.x2).minus(wallLine.x2).abs().toNumber() + ePoint = { x: wallBaseLine.x2, y: wallBaseLine.y2 }; + newPEnd.y = wallBaseLine.y2 - const pDist = Big(wallLine.y1).minus(roofLine.y1).abs().toNumber() - const pLineX = Big(roofLine.x2).minus(0).abs().toNumber() - let idx = roofLines.length < index + 1 ? 0 : index - const pLineY = roofLines[idx + 1].y2 - getAddLine({ x: newPEnd.x, y: newPEnd.y }, { x: sPoint.x, y: sPoint.y }, 'blue') - findPoints.push({ x: sPoint.x, y: sPoint.y, position: 'top_in_end' }); + findPoints.push({ x: ePoint.x, y: ePoint.y, position: 'right_in_end' }); + const newPointX = Big(roofLine.x1).minus(moveDist).toNumber() + const pDist = Big(wallLine.x1).minus(roofLine.x1).abs().toNumber() + const pLineY = Big(roofLine.y2).minus(0).abs().toNumber() + let idx = (roofLines.length < index + 1) ? 0 : index + const pLineX = roofLines[idx + 1].x2 - if (Math.abs(wallBaseLine.x2 - wallLine.x2) < 0.1) { - getAddLine({ x: pLineX, y: pLineY }, { x: pLineX, y: newPointY }, 'green') - getAddLine({ x: pLineX, y: newPointY }, { x: sPoint.x, y: sPoint.y }, 'pink') - } + getAddLine({ x: newPEnd.x, y: newPEnd.y }, { x: ePoint.x, y: ePoint.y }, 'blue') + getAddLine({ x: roofLine.x1, y: roofLine.y1 }, { x: newPointX, y: roofLine.y1 }, 'orange') - - //getAddLine({ x: roofLine.x1, y: roofLine.y1 }, { x: roofLine.x1, y: newPointY }, 'orange') + if (Math.abs(wallBaseLine.y2 - wallLine.y2) < 0.1) { + getAddLine({ x: pLineX, y: pLineY }, { x: newPointX, y: pLineY }, 'green') + getAddLine({ x: newPointX, y: pLineY }, { x: ePoint.x, y: ePoint.y }, 'pink') } + getAddLine({ x: roofLine.x2, y: roofLine.y2 }, { x: newPointX, y: roofLine.y2 }, 'orange') + } - }else if(condition === 'top_out') { - console.log("top_out isStartEnd:::::::", isStartEnd); + } else if (condition === 'right_out') { + console.log("right_out::::isStartEnd:::::", isStartEnd); + if (isStartEnd.start) { //x1 inside + const moveDist = Big(wallLine.x1).minus(wallBaseLine.x1).abs().toNumber() + const aStartY = Big(roofLine.y1).plus(moveDist).abs().toNumber() + const bStartY = Big(wallLine.y1).plus(moveDist).abs().toNumber() + const inLine = findLineContainingPoint(innerLines, { y: aStartY, x: roofLine.x1 }) + console.log("startLines:::::::", inLine); + const eLineY = Big(bStartY).minus(wallLine.y1).abs().toNumber() + newPStart.y = aStartY + newPEnd.y = roofLine.y2//Big(roofLine.y2).plus(eLineY).toNumber() + let idx = (0 >= index - 1) ? roofLines.length : index + const newLine = roofLines[idx - 1]; - if (isStartEnd.start ) { - const moveDist = Big(wallLine.y1).minus(wallBaseLine.y1).abs().toNumber() - const aStartX = Big(roofLine.x1).plus(moveDist).toNumber() - const bStartX = Big(wallLine.x1).plus(moveDist).toNumber() - const inLine = findLineContainingPoint(innerLines, { x: aStartX, y: newPEnd.y }) - - const eLineX = Big(bStartX).minus(wallLine.x1).abs().toNumber() - newPEnd.x = roofLine.x2 //Big(newPEnd.x).plus(eLineX).toNumber() - newPStart.x = aStartX - let idx = (0 > index - 1)?roofLines.length:index - const newLine = roofLines[idx-1]; - - if(Math.abs(wallBaseLine.x1 - wallLine.x1) < 0.1) { - if(inLine){ - if(inLine.y2 > inLine.y1 ) { - getAddLine({ x: bStartX, y: wallLine.y1 }, { x: inLine.x2, y: inLine.y2 }, 'pink') - }else{ - getAddLine({ x: inLine.x1, y: inLine.y1 }, { x: bStartX, y: wallLine.y1 }, 'pink') - } + if (Math.abs(wallBaseLine.y1 - wallLine.y1) < 0.1) { + if (inLine) { + if (inLine.x2 < inLine.x1) { + getAddLine({ y: bStartY, x: wallLine.x2 }, { y: inLine.y2, x: inLine.x2 }, 'pink') + } else { + getAddLine({ y: inLine.y1, x: inLine.x1 }, { y: bStartY, x: wallLine.x2 }, 'pink') } - getAddLine({ x: bStartX, y: wallLine.y1 }, { x: roofLine.x1, y: wallLine.y1 }, 'magenta') - getAddLine({ x: newLine.x1, y: newLine.y1 }, { x: newLine.x1, y: wallLine.y1 }, 'Gray') - findPoints.push({ x: aStartX, y: newPEnd.y, position: 'top_out_start' }); - }else{ - const cLineX = Big(wallBaseLine.y1).minus(wallLine.y1).abs().toNumber() - newPStart.x = Big(newPStart.x).plus(cLineX).toNumber(); + } + getAddLine({ y: bStartY, x: wallLine.x2 }, { y: roofLine.y1, x: wallLine.x1 }, 'magenta') + getAddLine({ y: newLine.y1, x: newLine.x1 }, { y: newLine.y2, x: wallLine.x2 }, 'Gray') + findPoints.push({ y: aStartY, x: newPEnd.x, position: 'right_out_start' }); + } else { + const cLineY = Big(wallBaseLine.x1).minus(wallLine.x1).abs().toNumber() + newPStart.y = Big(newPStart.y).plus(cLineY).toNumber(); + const inLine = findLineContainingPoint(innerLines, { y: newPStart.y, x: newPStart.x }) + if (inLine) { + if (inLine.x2 < inLine.x1) { + getAddLine({ y: newPStart.y, x: newPStart.x }, { y: inLine.y2, x: inLine.x2 }, 'purple') + } else { + getAddLine({ y: inLine.y1, x: inLine.x1 }, { y: newPStart.y, x: newPStart.x }, 'purple') + } + } else { + //newPStart.y = wallLine.y1; + //외곽 라인 그리기 + const rLineM = Big(wallBaseLine.x1).minus(roofLine.x1).abs().toNumber(); + newPStart.y = Big(wallBaseLine.y1).plus(rLineM).abs().toNumber(); const inLine = findLineContainingPoint(innerLines, { y: newPStart.y, x: newPStart.x }) - if(inLine){ - if(inLine.y2 > inLine.y1 ) { - getAddLine({ y: newPStart.y, x: newPStart.x }, { y: inLine.y2, x: inLine.x2 }, 'purple') - }else{ - getAddLine({ y: inLine.y1, x: inLine.x1 }, { y: newPStart.y, x: newPStart.x } , 'purple') - } - - }else { - //외곽 라인 그리기 - const rLineM = Big(wallBaseLine.y1).minus(roofLine.y1).abs().toNumber(); - newPStart.x = Big(wallBaseLine.x1).plus(rLineM).abs().toNumber(); - const inLine = findLineContainingPoint(innerLines, { y: newPStart.y, x: newPStart.x }) - if(inLine){ - if(inLine.y2 > inLine.y1 ) { - getAddLine({ y: newPStart.y, x: newPStart.x }, { y: inLine.y2, x: inLine.x2 }, 'purple') - }else{ - getAddLine({ y: inLine.y1, x: inLine.x1 }, { y: newPStart.y, x: newPStart.x } , 'purple') - } + if (inLine) { + if (inLine.x2 > inLine.x1) { + getAddLine({ y: newPStart.y, x: newPStart.x }, { y: inLine.y1, x: inLine.x1 }, 'purple') + } else { + getAddLine({ y: inLine.y2, x: inLine.x2 }, { y: newPStart.y, x: newPStart.x }, 'purple') } } } + } - if(isStartEnd.end){ - const moveDist = Big(wallLine.y1).minus(wallBaseLine.y1).abs().toNumber() - const aStartX = Big(roofLine.x2).minus(moveDist).abs().toNumber() - const bStartX = Big(wallLine.x2).minus(moveDist).abs().toNumber() - const inLine = findLineContainingPoint(innerLines, { x: aStartX, y: newPEnd.y }) - console.log("startLines:::::::", inLine); - const eLineX = Big(bStartX).minus(wallLine.x2).abs().toNumber() - newPStart.x = roofLine.x1;//Big(newPStart.x).minus(eLineX).abs().toNumber() - newPEnd.x = aStartX - let idx = (roofLines.length < index + 1)?0:index - const newLine = roofLines[idx+1]; - if(Math.abs(wallBaseLine.x2 - wallLine.x2) < 0.1) { - if(inLine){ - if(inLine.y2 > inLine.y1 ){ - getAddLine({ x: bStartX, y: wallLine.y1 }, { x: inLine.x2, y: inLine.y2 }, 'pink') - }else{ - getAddLine({ x: inLine.x1, y: inLine.y1 },{ x: bStartX, y: wallLine.y1 }, 'pink') - } + } + if (isStartEnd.end) { + const moveDist = Big(wallLine.x1).minus(wallBaseLine.x1).abs().toNumber() + const aStartY = Big(roofLine.y2).minus(moveDist).abs().toNumber() + const bStartY = Big(wallLine.y2).minus(moveDist).abs().toNumber() + const inLine = findLineContainingPoint(innerLines, { y: aStartY, x: roofLine.x1 }) + console.log("startLines:::::::", inLine); + const eLineY = Big(bStartY).minus(wallLine.y2).abs().toNumber() + newPEnd.y = aStartY + newPStart.y = roofLine.y1//Big(roofLine.y1).minus(eLineY).toNumber() + let idx = (roofLines.length < index + 1) ? 0 : index + const newLine = roofLines[idx + 1]; + if (inLine) { + if (inLine.x2 < inLine.x1) { + getAddLine({ y: bStartY, x: wallLine.x1 }, { y: inLine.y2, x: inLine.x2 }, 'pink') + } else { + getAddLine({ y: inLine.y1, x: inLine.x1 }, { y: bStartY, x: wallLine.x1 }, 'pink') + } + } + if (Math.abs(wallBaseLine.y2 - wallLine.y2) < 0.1) { + getAddLine({ y: bStartY, x: wallLine.x1 }, { y: roofLine.y2, x: wallLine.x2 }, 'magenta') + getAddLine({ y: newLine.y2, x: newLine.x2 }, { y: newLine.y1, x: wallLine.x1 }, 'Gray') + findPoints.push({ y: aStartY, x: newPEnd.x, position: 'right_out_end' }); + } else { + const cLineY = Big(wallBaseLine.x2).minus(wallLine.x2).abs().toNumber() + newPEnd.y = Big(newPEnd.y).minus(cLineY).toNumber(); + const inLine = findLineContainingPoint(innerLines, { y: newPEnd.y, x: newPEnd.x }) + if (inLine) { + if (inLine.x2 < inLine.x1) { + getAddLine({ y: newPEnd.y, x: newPEnd.x }, { y: inLine.y2, x: inLine.x2 }, 'purple') + } else { + getAddLine({ y: inLine.y1, x: inLine.x1 }, { y: newPEnd.y, x: newPEnd.x }, 'purple') } - getAddLine({ x: bStartX, y: wallLine.y1 }, { x: roofLine.x2, y: wallLine.y2 }, 'magenta') - getAddLine({ x: newLine.x2, y: newLine.y2 }, { x: newLine.x1, y: wallLine.y1 }, 'Gray') - findPoints.push({ x: aStartX, y: newPEnd.y, position: 'top_out_end' }); - }else{ - const cLineX = Big(wallLine.y2).minus(wallBaseLine.y2).abs().toNumber() - newPEnd.x = Big(newPEnd.x).minus(cLineX).toNumber(); + } else { + //newPEnd.y = wallLine.y2; + + //외곽 라인 그리기 + const rLineM = Big(wallBaseLine.x2).minus(roofLine.x2).abs().toNumber(); + newPEnd.y = Big(wallBaseLine.y2).minus(rLineM).abs().toNumber(); const inLine = findLineContainingPoint(innerLines, { y: newPEnd.y, x: newPEnd.x }) - if(inLine){ - if(inLine.y2 > inLine.y1 ) { - getAddLine({ y: newPEnd.y, x: newPEnd.x }, { y: inLine.y2, x: inLine.x2 }, 'purple') - }else{ - getAddLine({ y: inLine.y1, x: inLine.x1 },{ y: newPEnd.y, x: newPEnd.x }, 'purple') - } - }else { - //newPEnd.x = wallLine.x2; - //외곽 라인 그리기 - const rLineM = Big(wallBaseLine.y2).minus(roofLine.y2).abs().toNumber(); - newPEnd.x = Big(wallBaseLine.x2).minus(rLineM).abs().toNumber(); - const inLine = findLineContainingPoint(innerLines, { y: newPEnd.y, x: newPEnd.x }) - if(inLine){ - if(inLine.y1 > inLine.y2 ) { - getAddLine({ y: newPEnd.y, x: newPEnd.x }, { y: inLine.y1, x: inLine.x1 }, 'purple') - }else{ - getAddLine({ y: inLine.y2, x: inLine.x2 }, { y: newPEnd.y, x: newPEnd.x } , 'purple') - } + if (inLine) { + if (inLine.x2 > inLine.x1) { + getAddLine({ y: newPEnd.y, x: newPEnd.x }, { y: inLine.y1, x: inLine.x1 }, 'purple') + } else { + getAddLine({ y: inLine.y2, x: inLine.x2 }, { y: newPEnd.y, x: newPEnd.x }, 'purple') } } } - } - }else if(condition === 'bottom_in') { - if (isStartEnd.start ) { - const moveDist = Big(wallLine.y1).minus(wallBaseLine.y1).abs().toNumber() - sPoint = {x: wallBaseLine.x1, y: wallBaseLine.y1}; - newPStart.x = wallBaseLine.x1; - - const newPointY = Big(roofLine.y2).minus(moveDist).toNumber() - - const pDist = Big(wallLine.y2).minus(roofLine.y2).abs().toNumber() - const pLineX = Big(roofLine.x1).minus(0).abs().toNumber() - let idx = (0 > index - 1)?roofLines.length:index - const pLineY = roofLines[idx-1].y1 - getAddLine({ x: newPStart.x, y: newPStart.y }, { x: sPoint.x, y: sPoint.y }, 'blue') - findPoints.push({ x: sPoint.x, y: sPoint.y, position: 'bottom_in_start' }); - - if(Math.abs(wallBaseLine.x1 - wallLine.x1) < 0.1) { - getAddLine({ x: pLineX, y: pLineY }, { x: pLineX, y: newPointY }, 'green') - getAddLine({ x: pLineX, y: newPointY }, { x: sPoint.x, y: sPoint.y }, 'pink') - } - getAddLine({ x: roofLine.x2, y: roofLine.y2 }, { x: roofLine.x2, y: newPointY }, 'orange') - } - - if(isStartEnd.end){ - const moveDist = Big(wallLine.y2).minus(wallBaseLine.y2).abs().toNumber() - sPoint = {x: wallBaseLine.x2, y: wallBaseLine.y2}; - newPEnd.x = wallBaseLine.x2; - - - const newPointY = Big(roofLine.y1).minus(moveDist).toNumber() - - const pDist = Big(wallLine.y1).minus(roofLine.y1).abs().toNumber() - const pLineX = Big(roofLine.x2).minus(0).abs().toNumber() - let idx = (roofLines.length < index + 1)?0:index - const pLineY = roofLines[idx+1].y2 - getAddLine({ x: newPEnd.x, y: newPEnd.y }, { x: sPoint.x, y: sPoint.y }, 'blue') - findPoints.push({ x: sPoint.x, y: sPoint.y, position: 'bottom_in_end' }); - - if(Math.abs(wallBaseLine.x2 - wallLine.x2) < 0.1) { - getAddLine({ x: pLineX, y: pLineY }, { x: pLineX, y: newPointY }, 'green') - getAddLine({ x: pLineX, y: newPointY }, { x: sPoint.x, y: sPoint.y }, 'pink') - } - getAddLine({ x: roofLine.x1, y: roofLine.y1 }, { x: roofLine.x1, y: newPointY }, 'orange') - - } - }else if(condition === 'bottom_out') { - console.log("bottom_out isStartEnd:::::::", isStartEnd); - if (isStartEnd.start ) { - const moveDist = Big(wallLine.y1).minus(wallBaseLine.y1).abs().toNumber() - const aStartX = Big(roofLine.x1).minus(moveDist).abs().toNumber() - const bStartX = Big(wallLine.x1).minus(moveDist).abs().toNumber() - const inLine = findLineContainingPoint(innerLines, { x: aStartX, y: roofLine.y1 }) - console.log("startLines:::::::", inLine); - const eLineX = Big(bStartX).minus(wallLine.x1).abs().toNumber() - newPEnd.x = roofLine.x2//Big(roofLine.x2).minus(eLineX).toNumber() - newPStart.x = aStartX - let idx = (0 > index - 1)?roofLines.length:index - const newLine = roofLines[idx-1]; - - - if(Math.abs(wallBaseLine.x1 - wallLine.x1) < 0.1) { - if(inLine){ - if(inLine.y2 < inLine.y1 ) { - getAddLine({ x: bStartX, y: wallLine.y1 }, { x: inLine.x2, y: inLine.y2 }, 'pink') - }else{ - getAddLine({ x: inLine.x1, y: inLine.y1 },{ x: bStartX, y: wallLine.y1 }, 'pink') - } - } - getAddLine({ x: bStartX, y: wallLine.y1 }, { x: roofLine.x1, y: wallLine.y1 }, 'magenta') - getAddLine({ x: newLine.x1, y: newLine.y1 }, { x: newLine.x1, y: wallLine.y1 }, 'Gray') - findPoints.push({ x: aStartX, y: newPEnd.y, position: 'bottom_out_start' }); - }else{ - const cLineX = Big(wallBaseLine.y1).minus(wallLine.y1).abs().toNumber() - newPStart.x = Big(newPStart.x).minus(cLineX).toNumber(); - const inLine = findLineContainingPoint(innerLines, { y: newPStart.y, x: newPStart.x }) - if(inLine){ - if(inLine.y2 < inLine.y1 ) { - getAddLine({ y: newPStart.y, x: newPStart.x }, { y: inLine.y2, x: inLine.x2 }, 'purple') - }else{ - getAddLine({ y: inLine.y1, x: inLine.x1 }, { y: newPStart.y, x: newPStart.x }, 'purple') - } - }else{ - //newPStart.x = wallLine.x1; - //외곽 라인 그리기 - const rLineM = Big(wallBaseLine.y1).minus(roofLine.y1).abs().toNumber(); - newPStart.x = Big(wallBaseLine.x1).minus(rLineM).abs().toNumber(); - const inLine = findLineContainingPoint(innerLines, { y: newPStart.y, x: newPStart.x }) - if(inLine){ - if(inLine.y2 > inLine.y1 ) { - getAddLine({ y: newPStart.y, x: newPStart.x }, { y: inLine.y1, x: inLine.x1 }, 'purple') - }else{ - getAddLine({ y: inLine.y2, x: inLine.x2 }, { y: newPStart.y, x: newPStart.x } , 'purple') - } - } - } - - } - } - - if(isStartEnd.end){ - const moveDist = Big(wallLine.y1).minus(wallBaseLine.y1).abs().toNumber() - const aStartX = Big(roofLine.x2).plus(moveDist).toNumber() - const bStartX = Big(wallLine.x2).plus(moveDist).toNumber() - const inLine = findLineContainingPoint(innerLines, { x: aStartX, y: roofLine.y1 }) - console.log("startLines:::::::", inLine); - const eLineX = Big(bStartX).minus(wallLine.x2).abs().toNumber() - newPEnd.x = aStartX - newPStart.x = roofLine.x1;//Big(roofLine.x1).plus(eLineX).toNumber() - let idx = (roofLines.length < index + 1)?0:index - const newLine = roofLines[idx + 1]; - - if(Math.abs(wallBaseLine.x2 - wallLine.x2) < 0.1) { - if(inLine){ - if(inLine.y2 < inLine.y1 ) { - getAddLine({ x: bStartX, y: wallLine.y1 }, { x: inLine.x2, y: inLine.y2 }, 'pink') - }else{ - getAddLine({ x: inLine.x1, y: inLine.y1 }, { x: bStartX, y: wallLine.y1 }, 'pink') - } - } - getAddLine({ x: bStartX, y: wallLine.y1 }, { x: roofLine.x2, y: wallLine.y2 }, 'magenta') - getAddLine({ x: newLine.x2, y: newLine.y2 }, { x: newLine.x1, y: wallLine.y1 }, 'Gray') - findPoints.push({ x: aStartX, y: newPEnd.y, position: 'bottom_out_end' }); - }else{ - const cLineX = Big(wallBaseLine.y2).minus(wallLine.y2).abs().toNumber() - newPEnd.x = Big(newPEnd.x).plus(cLineX).toNumber(); - const inLine = findLineContainingPoint(innerLines, { y: newPEnd.y, x: newPEnd.x }) - if(inLine){ - if(inLine.y2 < inLine.y1 ) { - getAddLine({ y: newPEnd.y, x: newPEnd.x }, { y: inLine.y2, x: inLine.x2 }, 'purple') - }else{ - getAddLine({ y: inLine.y1, x: inLine.x1 },{ y: newPEnd.y, x: newPEnd.x }, 'purple') - } - }else{ - //newPEnd.x = wallLine.x2; - //외곽 라인 그리기 - const rLineM = Big(wallBaseLine.y2).minus(roofLine.y2).abs().toNumber(); - newPEnd.x = Big(wallBaseLine.x2).plus(rLineM).abs().toNumber(); - const inLine = findLineContainingPoint(innerLines, { y: newPEnd.y, x: newPEnd.x }) - if(inLine){ - if(inLine.y1 > inLine.y2 ) { - getAddLine({ y: newPEnd.y, x: newPEnd.x }, { y: inLine.y2, x: inLine.x2 }, 'purple') - }else{ - getAddLine({ y: inLine.y1, x: inLine.x1 }, { y: newPEnd.y, x: newPEnd.x } , 'purple') - } - } - } - - } } } } } + } else if (getOrientation(roofLine) === 'horizontal') { //red - getAddLine(newPStart, newPEnd, 'red') - //canvas.remove(roofLine) - }else{ - getAddLine(roofLine.startPoint, roofLine.endPoint, ) + if (['top', 'bottom'].includes(mLine.position)) { + if (Math.abs(wallLine.y1 - wallBaseLine.y1) < 0.1 || Math.abs(wallLine.y2 - wallBaseLine.y2) < 0.1) { + return false + } + const positionType = + (mLine.position === 'top' && wallLine.y1 < wallBaseLine.y1) || + (mLine.position === 'bottom' && wallLine.y1 > wallBaseLine.y1) + ? 'in' : 'out'; + + const condition = `${mLine.position}_${positionType}`; + let isStartEnd = findInteriorPoint(wallBaseLine, sortedBaseLines) + + let sPoint, ePoint; + + if (condition === 'top_in') { + if (isStartEnd.start) { + const moveDist = Big(wallLine.y1).minus(wallBaseLine.y1).abs().toNumber() + sPoint = { x: wallBaseLine.x1, y: wallBaseLine.y1 }; + newPStart.x = wallBaseLine.x1; + + + const newPointY = Big(roofLine.y2).plus(moveDist).toNumber() + + const pDist = Big(wallLine.y2).minus(roofLine.y2).abs().toNumber() + const pLineX = Big(roofLine.x1).minus(0).abs().toNumber() + let idx = (0 >= index - 1) ? roofLines.length : index + const pLineY = roofLines[idx - 1].y1 + getAddLine({ x: newPStart.x, y: newPStart.y }, { x: sPoint.x, y: sPoint.y }, 'blue') + findPoints.push({ x: sPoint.x, y: sPoint.y, position: 'top_in_start' }); + + if (Math.abs(wallBaseLine.x1 - wallLine.x1) < 0.1) { + getAddLine({ x: pLineX, y: pLineY }, { x: pLineX, y: newPointY }, 'green') + getAddLine({ x: pLineX, y: newPointY }, { x: sPoint.x, y: sPoint.y }, 'pink') + } + //getAddLine({ x: roofLine.x2, y: roofLine.y2 }, { x: roofLine.x2, y: newPointY }, 'orange') + + } + + if (isStartEnd.end) { + const moveDist = Big(wallLine.y2).minus(wallBaseLine.y2).abs().toNumber() + sPoint = { x: wallBaseLine.x2, y: wallBaseLine.y2 } + newPEnd.x = wallBaseLine.x2 + + const newPointY = Big(roofLine.y1).plus(moveDist).toNumber() + + const pDist = Big(wallLine.y1).minus(roofLine.y1).abs().toNumber() + const pLineX = Big(roofLine.x2).minus(0).abs().toNumber() + let idx = roofLines.length < index + 1 ? 0 : index + const pLineY = roofLines[idx + 1].y2 + getAddLine({ x: newPEnd.x, y: newPEnd.y }, { x: sPoint.x, y: sPoint.y }, 'blue') + findPoints.push({ x: sPoint.x, y: sPoint.y, position: 'top_in_end' }); + + if (Math.abs(wallBaseLine.x2 - wallLine.x2) < 0.1) { + getAddLine({ x: pLineX, y: pLineY }, { x: pLineX, y: newPointY }, 'green') + getAddLine({ x: pLineX, y: newPointY }, { x: sPoint.x, y: sPoint.y }, 'pink') + } + + + //getAddLine({ x: roofLine.x1, y: roofLine.y1 }, { x: roofLine.x1, y: newPointY }, 'orange') + } + + } else if (condition === 'top_out') { + console.log("top_out isStartEnd:::::::", isStartEnd); + + if (isStartEnd.start) { + const moveDist = Big(wallLine.y1).minus(wallBaseLine.y1).abs().toNumber() + const aStartX = Big(roofLine.x1).plus(moveDist).toNumber() + const bStartX = Big(wallLine.x1).plus(moveDist).toNumber() + const inLine = findLineContainingPoint(innerLines, { x: aStartX, y: newPEnd.y }) + + const eLineX = Big(bStartX).minus(wallLine.x1).abs().toNumber() + newPEnd.x = roofLine.x2 //Big(newPEnd.x).plus(eLineX).toNumber() + newPStart.x = aStartX + let idx = (0 > index - 1) ? roofLines.length : index + const newLine = roofLines[idx - 1]; + + if (Math.abs(wallBaseLine.x1 - wallLine.x1) < 0.1) { + if (inLine) { + if (inLine.y2 > inLine.y1) { + getAddLine({ x: bStartX, y: wallLine.y1 }, { x: inLine.x2, y: inLine.y2 }, 'pink') + } else { + getAddLine({ x: inLine.x1, y: inLine.y1 }, { x: bStartX, y: wallLine.y1 }, 'pink') + } + } + getAddLine({ x: bStartX, y: wallLine.y1 }, { x: roofLine.x1, y: wallLine.y1 }, 'magenta') + getAddLine({ x: newLine.x1, y: newLine.y1 }, { x: newLine.x1, y: wallLine.y1 }, 'Gray') + findPoints.push({ x: aStartX, y: newPEnd.y, position: 'top_out_start' }); + } else { + const cLineX = Big(wallBaseLine.y1).minus(wallLine.y1).abs().toNumber() + newPStart.x = Big(newPStart.x).plus(cLineX).toNumber(); + const inLine = findLineContainingPoint(innerLines, { y: newPStart.y, x: newPStart.x }) + if (inLine) { + if (inLine.y2 > inLine.y1) { + getAddLine({ y: newPStart.y, x: newPStart.x }, { y: inLine.y2, x: inLine.x2 }, 'purple') + } else { + getAddLine({ y: inLine.y1, x: inLine.x1 }, { y: newPStart.y, x: newPStart.x }, 'purple') + } + + } else { + //외곽 라인 그리기 + const rLineM = Big(wallBaseLine.y1).minus(roofLine.y1).abs().toNumber(); + newPStart.x = Big(wallBaseLine.x1).plus(rLineM).abs().toNumber(); + const inLine = findLineContainingPoint(innerLines, { y: newPStart.y, x: newPStart.x }) + if (inLine) { + if (inLine.y2 > inLine.y1) { + getAddLine({ y: newPStart.y, x: newPStart.x }, { y: inLine.y2, x: inLine.x2 }, 'purple') + } else { + getAddLine({ y: inLine.y1, x: inLine.x1 }, { y: newPStart.y, x: newPStart.x }, 'purple') + } + } + } + + } + } + if (isStartEnd.end) { + const moveDist = Big(wallLine.y1).minus(wallBaseLine.y1).abs().toNumber() + const aStartX = Big(roofLine.x2).minus(moveDist).abs().toNumber() + const bStartX = Big(wallLine.x2).minus(moveDist).abs().toNumber() + const inLine = findLineContainingPoint(innerLines, { x: aStartX, y: newPEnd.y }) + console.log("startLines:::::::", inLine); + const eLineX = Big(bStartX).minus(wallLine.x2).abs().toNumber() + newPStart.x = roofLine.x1;//Big(newPStart.x).minus(eLineX).abs().toNumber() + newPEnd.x = aStartX + let idx = (roofLines.length < index + 1) ? 0 : index + const newLine = roofLines[idx + 1]; + + if (Math.abs(wallBaseLine.x2 - wallLine.x2) < 0.1) { + if (inLine) { + if (inLine.y2 > inLine.y1) { + getAddLine({ x: bStartX, y: wallLine.y1 }, { x: inLine.x2, y: inLine.y2 }, 'pink') + } else { + getAddLine({ x: inLine.x1, y: inLine.y1 }, { x: bStartX, y: wallLine.y1 }, 'pink') + } + + } + getAddLine({ x: bStartX, y: wallLine.y1 }, { x: roofLine.x2, y: wallLine.y2 }, 'magenta') + getAddLine({ x: newLine.x2, y: newLine.y2 }, { x: newLine.x1, y: wallLine.y1 }, 'Gray') + findPoints.push({ x: aStartX, y: newPEnd.y, position: 'top_out_end' }); + } else { + const cLineX = Big(wallLine.y2).minus(wallBaseLine.y2).abs().toNumber() + newPEnd.x = Big(newPEnd.x).minus(cLineX).toNumber(); + const inLine = findLineContainingPoint(innerLines, { y: newPEnd.y, x: newPEnd.x }) + if (inLine) { + if (inLine.y2 > inLine.y1) { + getAddLine({ y: newPEnd.y, x: newPEnd.x }, { y: inLine.y2, x: inLine.x2 }, 'purple') + } else { + getAddLine({ y: inLine.y1, x: inLine.x1 }, { y: newPEnd.y, x: newPEnd.x }, 'purple') + } + } else { + //newPEnd.x = wallLine.x2; + //외곽 라인 그리기 + const rLineM = Big(wallBaseLine.y2).minus(roofLine.y2).abs().toNumber(); + newPEnd.x = Big(wallBaseLine.x2).minus(rLineM).abs().toNumber(); + const inLine = findLineContainingPoint(innerLines, { y: newPEnd.y, x: newPEnd.x }) + if (inLine) { + if (inLine.y1 > inLine.y2) { + getAddLine({ y: newPEnd.y, x: newPEnd.x }, { y: inLine.y1, x: inLine.x1 }, 'purple') + } else { + getAddLine({ y: inLine.y2, x: inLine.x2 }, { y: newPEnd.y, x: newPEnd.x }, 'purple') + } + } + } + + } + } + } else if (condition === 'bottom_in') { + if (isStartEnd.start) { + const moveDist = Big(wallLine.y1).minus(wallBaseLine.y1).abs().toNumber() + sPoint = { x: wallBaseLine.x1, y: wallBaseLine.y1 }; + newPStart.x = wallBaseLine.x1; + + + const newPointY = Big(roofLine.y2).minus(moveDist).toNumber() + + const pDist = Big(wallLine.y2).minus(roofLine.y2).abs().toNumber() + const pLineX = Big(roofLine.x1).minus(0).abs().toNumber() + let idx = (0 > index - 1) ? roofLines.length : index + const pLineY = roofLines[idx - 1].y1 + getAddLine({ x: newPStart.x, y: newPStart.y }, { x: sPoint.x, y: sPoint.y }, 'blue') + findPoints.push({ x: sPoint.x, y: sPoint.y, position: 'bottom_in_start' }); + + if (Math.abs(wallBaseLine.x1 - wallLine.x1) < 0.1) { + getAddLine({ x: pLineX, y: pLineY }, { x: pLineX, y: newPointY }, 'green') + getAddLine({ x: pLineX, y: newPointY }, { x: sPoint.x, y: sPoint.y }, 'pink') + } + getAddLine({ x: roofLine.x2, y: roofLine.y2 }, { x: roofLine.x2, y: newPointY }, 'orange') + } + + if (isStartEnd.end) { + const moveDist = Big(wallLine.y2).minus(wallBaseLine.y2).abs().toNumber() + sPoint = { x: wallBaseLine.x2, y: wallBaseLine.y2 }; + newPEnd.x = wallBaseLine.x2; + + + const newPointY = Big(roofLine.y1).minus(moveDist).toNumber() + + const pDist = Big(wallLine.y1).minus(roofLine.y1).abs().toNumber() + const pLineX = Big(roofLine.x2).minus(0).abs().toNumber() + let idx = (roofLines.length < index + 1) ? 0 : index + const pLineY = roofLines[idx + 1].y2 + getAddLine({ x: newPEnd.x, y: newPEnd.y }, { x: sPoint.x, y: sPoint.y }, 'blue') + findPoints.push({ x: sPoint.x, y: sPoint.y, position: 'bottom_in_end' }); + + if (Math.abs(wallBaseLine.x2 - wallLine.x2) < 0.1) { + getAddLine({ x: pLineX, y: pLineY }, { x: pLineX, y: newPointY }, 'green') + getAddLine({ x: pLineX, y: newPointY }, { x: sPoint.x, y: sPoint.y }, 'pink') + } + getAddLine({ x: roofLine.x1, y: roofLine.y1 }, { x: roofLine.x1, y: newPointY }, 'orange') + + } + } else if (condition === 'bottom_out') { + console.log("bottom_out isStartEnd:::::::", isStartEnd); + if (isStartEnd.start) { + const moveDist = Big(wallLine.y1).minus(wallBaseLine.y1).abs().toNumber() + const aStartX = Big(roofLine.x1).minus(moveDist).abs().toNumber() + const bStartX = Big(wallLine.x1).minus(moveDist).abs().toNumber() + const inLine = findLineContainingPoint(innerLines, { x: aStartX, y: roofLine.y1 }) + console.log("startLines:::::::", inLine); + const eLineX = Big(bStartX).minus(wallLine.x1).abs().toNumber() + newPEnd.x = roofLine.x2//Big(roofLine.x2).minus(eLineX).toNumber() + newPStart.x = aStartX + let idx = (0 > index - 1) ? roofLines.length : index + const newLine = roofLines[idx - 1]; + + + if (Math.abs(wallBaseLine.x1 - wallLine.x1) < 0.1) { + if (inLine) { + if (inLine.y2 < inLine.y1) { + getAddLine({ x: bStartX, y: wallLine.y1 }, { x: inLine.x2, y: inLine.y2 }, 'pink') + } else { + getAddLine({ x: inLine.x1, y: inLine.y1 }, { x: bStartX, y: wallLine.y1 }, 'pink') + } + } + getAddLine({ x: bStartX, y: wallLine.y1 }, { x: roofLine.x1, y: wallLine.y1 }, 'magenta') + getAddLine({ x: newLine.x1, y: newLine.y1 }, { x: newLine.x1, y: wallLine.y1 }, 'Gray') + findPoints.push({ x: aStartX, y: newPEnd.y, position: 'bottom_out_start' }); + } else { + const cLineX = Big(wallBaseLine.y1).minus(wallLine.y1).abs().toNumber() + newPStart.x = Big(newPStart.x).minus(cLineX).toNumber(); + const inLine = findLineContainingPoint(innerLines, { y: newPStart.y, x: newPStart.x }) + if (inLine) { + if (inLine.y2 < inLine.y1) { + getAddLine({ y: newPStart.y, x: newPStart.x }, { y: inLine.y2, x: inLine.x2 }, 'purple') + } else { + getAddLine({ y: inLine.y1, x: inLine.x1 }, { y: newPStart.y, x: newPStart.x }, 'purple') + } + } else { + //newPStart.x = wallLine.x1; + //외곽 라인 그리기 + const rLineM = Big(wallBaseLine.y1).minus(roofLine.y1).abs().toNumber(); + newPStart.x = Big(wallBaseLine.x1).minus(rLineM).abs().toNumber(); + const inLine = findLineContainingPoint(innerLines, { y: newPStart.y, x: newPStart.x }) + if (inLine) { + if (inLine.y2 > inLine.y1) { + getAddLine({ y: newPStart.y, x: newPStart.x }, { y: inLine.y1, x: inLine.x1 }, 'purple') + } else { + getAddLine({ y: inLine.y2, x: inLine.x2 }, { y: newPStart.y, x: newPStart.x }, 'purple') + } + } + } + + } + } + + if (isStartEnd.end) { + const moveDist = Big(wallLine.y1).minus(wallBaseLine.y1).abs().toNumber() + const aStartX = Big(roofLine.x2).plus(moveDist).toNumber() + const bStartX = Big(wallLine.x2).plus(moveDist).toNumber() + const inLine = findLineContainingPoint(innerLines, { x: aStartX, y: roofLine.y1 }) + console.log("startLines:::::::", inLine); + const eLineX = Big(bStartX).minus(wallLine.x2).abs().toNumber() + newPEnd.x = aStartX + newPStart.x = roofLine.x1;//Big(roofLine.x1).plus(eLineX).toNumber() + let idx = (roofLines.length < index + 1) ? 0 : index + const newLine = roofLines[idx + 1]; + + if (Math.abs(wallBaseLine.x2 - wallLine.x2) < 0.1) { + if (inLine) { + if (inLine.y2 < inLine.y1) { + getAddLine({ x: bStartX, y: wallLine.y1 }, { x: inLine.x2, y: inLine.y2 }, 'pink') + } else { + getAddLine({ x: inLine.x1, y: inLine.y1 }, { x: bStartX, y: wallLine.y1 }, 'pink') + } + } + getAddLine({ x: bStartX, y: wallLine.y1 }, { x: roofLine.x2, y: wallLine.y2 }, 'magenta') + getAddLine({ x: newLine.x2, y: newLine.y2 }, { x: newLine.x1, y: wallLine.y1 }, 'Gray') + findPoints.push({ x: aStartX, y: newPEnd.y, position: 'bottom_out_end' }); + } else { + const cLineX = Big(wallBaseLine.y2).minus(wallLine.y2).abs().toNumber() + newPEnd.x = Big(newPEnd.x).plus(cLineX).toNumber(); + const inLine = findLineContainingPoint(innerLines, { y: newPEnd.y, x: newPEnd.x }) + if (inLine) { + if (inLine.y2 < inLine.y1) { + getAddLine({ y: newPEnd.y, x: newPEnd.x }, { y: inLine.y2, x: inLine.x2 }, 'purple') + } else { + getAddLine({ y: inLine.y1, x: inLine.x1 }, { y: newPEnd.y, x: newPEnd.x }, 'purple') + } + } else { + //newPEnd.x = wallLine.x2; + //외곽 라인 그리기 + const rLineM = Big(wallBaseLine.y2).minus(roofLine.y2).abs().toNumber(); + newPEnd.x = Big(wallBaseLine.x2).plus(rLineM).abs().toNumber(); + const inLine = findLineContainingPoint(innerLines, { y: newPEnd.y, x: newPEnd.x }) + if (inLine) { + if (inLine.y1 > inLine.y2) { + getAddLine({ y: newPEnd.y, x: newPEnd.x }, { y: inLine.y2, x: inLine.x2 }, 'purple') + } else { + getAddLine({ y: inLine.y1, x: inLine.x1 }, { y: newPEnd.y, x: newPEnd.x }, 'purple') + } + } + } + + } + } + } + } } + getAddLine(newPStart, newPEnd, 'red') + //canvas.remove(roofLine) + } else { + getAddLine(roofLine.startPoint, roofLine.endPoint,) + } - canvas.renderAll() - }); - // } + canvas.renderAll() + }); +} + getMoveUpDownLine() + + } if (findPoints.length > 0) { // 모든 점에 대해 라인 업데이트를 누적 From 5e93c4d0e8df678db8880168ad95543a98870927 Mon Sep 17 00:00:00 2001 From: ysCha Date: Wed, 17 Dec 2025 16:07:51 +0900 Subject: [PATCH 23/28] =?UTF-8?q?qna=5F=EA=B3=A0=EA=B0=9D=EB=AA=85?= =?UTF-8?q?=EB=85=B8=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/community/modal/QnaRegModal.jsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/community/modal/QnaRegModal.jsx b/src/components/community/modal/QnaRegModal.jsx index 280a1086..3ab03e71 100644 --- a/src/components/community/modal/QnaRegModal.jsx +++ b/src/components/community/modal/QnaRegModal.jsx @@ -349,7 +349,7 @@ let fileCheck = false; {getMessage('qna.list.header.regNm')} - + E-Mail* {dayjs(new Date()).format('YYYY-MM-DD')} + Customer + {getMessage('qna.reg.header.regUserNm')}* setQnaData({...qnaData, regUserNm: e.target.value })} onBlur={(e) => setQnaData({ ...qnaData, regUserNm: e.target.value })} /> {getMessage('qna.reg.header.regUserTelNo')} - Date: Wed, 17 Dec 2025 17:33:16 +0900 Subject: [PATCH 24/28] =?UTF-8?q?=EB=B3=80=EB=B3=84=EB=A1=9C=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=201=EC=B0=A8=20=EC=9A=B4=EC=98=81=EC=84=9C=EB=B2=84?= =?UTF-8?q?=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/fabric/QPolygon.js | 21 +- src/util/qpolygon-utils.js | 2465 +++++++++++++++++++++++------ 2 files changed, 2013 insertions(+), 473 deletions(-) diff --git a/src/components/fabric/QPolygon.js b/src/components/fabric/QPolygon.js index 6021afc2..164462eb 100644 --- a/src/components/fabric/QPolygon.js +++ b/src/components/fabric/QPolygon.js @@ -2,11 +2,10 @@ 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' -import { drawSkeletonRidgeRoof } from '@/util/skeleton-utils' export const QPolygon = fabric.util.createClass(fabric.Polygon, { type: 'QPolygon', @@ -313,14 +312,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) @@ -346,7 +349,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, { drawShedRoof(this.id, this.canvas, textMode) } else { console.log('변별로 설정') - drawRidgeRoof(this.id, this.canvas, textMode) + drawRoofByAttribute(this.id, this.canvas, textMode) } }, diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js index ea0254f7..576926f0 100644 --- a/src/util/qpolygon-utils.js +++ b/src/util/qpolygon-utils.js @@ -971,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 @@ -1657,6 +1657,7 @@ const findIntersectionPoint = (startPoint, direction, polygonVertices, divisionL * @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) @@ -2144,12 +2145,16 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { const intersection = edgesIntersection(prevHipEdge, checkEdge) if (intersection) { - const checkVector = { x: Math.sign(prevLine.x1 - testPoint[0]), y: Math.sign(prevLine.y1 - testPoint[1]) } - const intersectVector = { x: Math.sign(prevLine.x1 - intersection.x), y: Math.sign(prevLine.y1 - intersection.y) } + 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 } + } } } } @@ -2187,12 +2192,15 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { } const intersection = edgesIntersection(nextHipEdge, checkEdge) if (intersection) { - const checkVector = { x: Math.sign(nextLine.x2 - testPoint[0]), y: Math.sign(nextLine.y2 - testPoint[1]) } - const intersectVector = { x: Math.sign(nextLine.x2 - intersection.x), y: Math.sign(nextLine.y2 - intersection.y) } + 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 } + } } } } @@ -2200,7 +2208,7 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { 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) { + if (prevPoint && prevHasGable && prevLength <= nextLength) { return prevPoint.intersection } if (nextPoint && nextHasGable && prevLength > nextLength) { @@ -2403,7 +2411,7 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { end: { x: points1[2], y: points1[3] }, left: beforePrevIndex, right: prevIndex, - type: 'hip', + type: TYPES.HIP, degree: shedDegree, }, { @@ -2411,7 +2419,7 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { end: { x: points2[2], y: points2[3] }, left: nextIndex, right: afterNextIndex, - type: 'hip', + type: TYPES.HIP, degree: shedDegree, }, ) @@ -2434,6 +2442,17 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { 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) } //반절마루 생성불가이므로 지붕선만 추가하고 끝냄 @@ -2546,8 +2565,44 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { //마루가 최대로 뻗어나갈수 있는 포인트. null일때는 맞은편 지붕선 까지로 판단한다. const drivePoint = getRidgeDrivePoint(point, prevLine, nextLine, baseLines) - console.log('drivePoint : ', drivePoint) - if (drivePoint) { + 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 } @@ -2557,7 +2612,7 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { end: { x: point[2], y: point[3] }, left: baseLines.findIndex((line) => line === prevLine), right: baseLines.findIndex((line) => line === nextLine), - type: 'ridge', + type: TYPES.RIDGE, degree: 0, }) } @@ -2592,15 +2647,375 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { end: { x: intersect.x, y: intersect.y }, left: baseLines.findIndex((line) => line === prevLine), right: baseLines.findIndex((line) => line === nextLine), - type: 'ridge', + type: TYPES.RIDGE, degree: 0, }) } } } }) + + eaves.sort((a, b) => a.attributes.planeSize - b.attributes.planeSize) //3. 처마지붕 판단, 처마는 옆이 처마여야한다. - eaves.forEach((currentLine) => { + + 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) { @@ -2664,32 +3079,146 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { isForwardPoints.sort((a, b) => a.length - b.length) isBackwardPoints.sort((a, b) => a.length - b.length) - let hipPoint = [] + let hipPoint if (isForwardPoints.length > 0 && isBackwardPoints.length > 0) { - hipPoint = [isBackwardPoints[0].intersect.x, isBackwardPoints[0].intersect.y, isForwardPoints[0].intersect.x, isForwardPoints[0].intersect.y] + 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 = [isForwardPoints[0].intersect.x, isForwardPoints[0].intersect.y, isForwardPoints[1].intersect.x, isForwardPoints[1].intersect.y] + 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 = [ - isBackwardPoints[0].intersect.x, - isBackwardPoints[0].intersect.y, - isBackwardPoints[1].intersect.x, - isBackwardPoints[1].intersect.y, - ] + hipPoint = { + x1: isBackwardPoints[0].intersect.x, + y1: isBackwardPoints[0].intersect.y, + x2: isBackwardPoints[1].intersect.x, + y2: isBackwardPoints[1].intersect.y, + } } } - linesAnalysis.push({ - start: { x: hipPoint[0], y: hipPoint[1] }, - end: { x: hipPoint[2], y: hipPoint[3] }, - left: baseLines.findIndex((line) => line === prevLine), - right: baseLines.findIndex((line) => line === currentLine), - type: 'hip', - degree: currentDegree, - }) + 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 @@ -2705,6 +3234,16 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { 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] @@ -2830,7 +3369,42 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { //마루가 최대로 뻗어나갈수 있는 포인트. null일때는 맞은편 지붕선 까지로 판단한다. const drivePoint = getRidgeDrivePoint(point, prevLine, nextLine, baseLines) - if (drivePoint) { + const isOverlapBefore = analyze.isHorizontal + ? almostEqual(Math.min(beforePrevLine.x1, beforePrevLine.x2), Math.min(currentLine.x1, currentLine.x2)) && + almostEqual(Math.max(beforePrevLine.x1, beforePrevLine.x2), Math.max(currentLine.x1, currentLine.x2)) + : almostEqual(Math.min(beforePrevLine.y1, beforePrevLine.y2), Math.min(currentLine.y1, currentLine.y2)) && + almostEqual(Math.max(beforePrevLine.y1, beforePrevLine.y2), Math.max(currentLine.y1, currentLine.y2)) + const isOverlapAfter = analyze.isHorizontal + ? almostEqual(Math.min(afterNextLine.x1, afterNextLine.x2), Math.min(currentLine.x1, currentLine.x2)) && + almostEqual(Math.max(afterNextLine.x1, afterNextLine.x2), Math.max(currentLine.x1, currentLine.x2)) + : almostEqual(Math.min(afterNextLine.y1, afterNextLine.y2), Math.min(currentLine.y1, currentLine.y2)) && + almostEqual(Math.max(afterNextLine.y1, afterNextLine.y2), Math.max(currentLine.y1, currentLine.y2)) + + if (isOverlapBefore || isOverlapAfter) { + const oppLine = isOverlapBefore ? beforePrevLine : afterNextLine + const otherGable = baseLines.find( + (l) => l !== currentLine && l !== prevLine && l !== nextLine && l !== oppLine && l.attributes.type === LINE_TYPE.WALLLINE.GABLE, + ) + if (!otherGable) { + const pointVector = { x: Math.sign(clamp01(point[0] - point[2])), y: Math.sign(clamp01(point[1] - point[3])) } + let offset = 0 + switch (oppLine.attributes.type) { + case LINE_TYPE.WALLLINE.HIPANDGABLE: + offset = oppLine.attributes.width + break + case LINE_TYPE.WALLLINE.JERKINHEAD: + offset = oppLine.attributes.width / 2 + break + default: + break + } + point[2] += pointVector.x * offset + point[3] += pointVector.y * offset + } else if (drivePoint) { + point[2] = drivePoint.x + point[3] = drivePoint.y + } + } else if (drivePoint) { point[2] = drivePoint.x point[3] = drivePoint.y } @@ -2840,7 +3414,7 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { end: { x: point[2], y: point[3] }, left: baseLines.findIndex((line) => line === prevLine), right: baseLines.findIndex((line) => line === nextLine), - type: 'ridge', + type: TYPES.RIDGE, degree: 0, }) } @@ -2862,22 +3436,22 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { return } - const prevLineVector = { x: Math.sign(prevLine.x1 - prevLine.x2), y: Math.sign(prevLine.y1 - prevLine.y2) } - const nextLineVector = { x: Math.sign(nextLine.x1 - nextLine.x2), y: Math.sign(nextLine.y1 - nextLine.y2) } + const 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], { + /* 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() + canvas.renderAll()*/ //좌우 라인이 서로 다른방향일때 if ((prevLineVector.x !== nextLineVector.x || prevLineVector.y !== nextLineVector.y) && checkWallPolygon.inPolygon(inPolygonPoint)) { @@ -2894,392 +3468,711 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { } }) - const currentVector = { x: Math.sign(currentLine.x1 - currentLine.x2), y: Math.sign(currentLine.y1 - currentLine.y2) } + const currentVector = { x: Math.sign(clamp01(currentLine.x1 - currentLine.x2)), y: Math.sign(clamp01(currentLine.y1 - currentLine.y2)) } // 마루 진행 최대 길이 let prevDistance = 0, nextDistance = 0 - if (beforePrevLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) { - const beforeVector = { x: Math.sign(beforePrevLine.x1 - beforePrevLine.x2), y: Math.sign(beforePrevLine.y1 - beforePrevLine.y2) } - let distance = Math.sqrt(Math.pow(prevLine.x2 - prevLine.x1, 2) + Math.pow(prevLine.y2 - prevLine.y1, 2)) + currentLine.attributes.offset - if (beforeVector.x === currentVector.x && beforeVector.y === currentVector.y) { - prevDistance = distance - beforePrevLine.attributes.offset - } else { - prevDistance = distance + beforePrevLine.attributes.offset - } - } - if (afterNextLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) { - const afterVector = { x: Math.sign(afterNextLine.x1 - afterNextLine.x2), y: Math.sign(afterNextLine.y1 - afterNextLine.y2) } - let distance = Math.sqrt(Math.pow(nextLine.x2 - nextLine.x1, 2) + Math.pow(nextLine.y2 - nextLine.y1, 2)) + currentLine.attributes.offset - if (afterVector.x === currentVector.x && afterVector.y === currentVector.y) { - nextDistance = distance - afterNextLine.attributes.offset - } else { - nextDistance = distance + afterNextLine.attributes.offset + // 반대쪽 라인이 특정조건일때 마루선을 반대쪽까지로 처리한다.. + const 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 } } - //좌우 선분 중 긴 쪽이 기준선이 된다 - const stdLine = nextDistance <= prevDistance ? prevLine : nextLine - let stdDistance = nextDistance <= prevDistance ? prevDistance : nextDistance - const stdAnalyze = analyzeLine(stdLine) - let stdPoints = [] - - const stdPrevLine = baseLines[(baseLines.indexOf(stdLine) - 1 + baseLines.length) % baseLines.length] - const stdNextLine = baseLines[(baseLines.indexOf(stdLine) + 1) % baseLines.length] - const stdVector = { x: Math.sign(stdLine.x1 - stdLine.x2), y: Math.sign(stdLine.y1 - stdLine.y2) } - const stdPrevVector = { x: Math.sign(stdPrevLine.x1 - stdPrevLine.x2), y: Math.sign(stdPrevLine.y1 - stdPrevLine.y2) } - const stdNextVector = { x: Math.sign(stdNextLine.x1 - stdNextLine.x2), y: Math.sign(stdNextLine.y1 - stdNextLine.y2) } - if (stdPrevVector.x === stdNextVector.x && stdPrevVector.y === stdNextVector.y) { - stdPoints = [ - stdLine.x1 + stdVector.x * stdPrevLine.attributes.offset, - stdLine.y1 + stdVector.y * stdPrevLine.attributes.offset, - stdLine.x2 + stdVector.x * stdNextLine.attributes.offset, - stdLine.y2 + stdVector.y * stdNextLine.attributes.offset, + 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 { - stdPoints = [ - stdLine.x1 + stdVector.x * stdPrevLine.attributes.offset, - stdLine.y1 + stdVector.y * stdPrevLine.attributes.offset, - stdLine.x2 + -stdVector.x * stdNextLine.attributes.offset, - stdLine.y2 + -stdVector.y * stdNextLine.attributes.offset, - ] - } - const checkLine = new fabric.Line([stdLine.x1, stdLine.y1, stdLine.x2, stdLine.y2], { - stroke: 'red', - strokeWidth: 4, - parentId: roofId, - name: 'check', - }) - canvas.add(checkLine) - canvas.renderAll() - - //기준지붕선의 반대쪽선 - const oppositeLine = [] - - const startX = Math.min(stdLine.x1, stdLine.x2) - const endX = Math.max(stdLine.x1, stdLine.x2) - const startY = Math.min(stdLine.y1, stdLine.y2) - const endY = Math.max(stdLine.y1, stdLine.y2) - - baseLines - .filter((line) => line !== stdLine && line !== currentLine) - .filter((line) => { - const vector = { x: Math.sign(stdLine.x1 - stdLine.x2), y: Math.sign(stdLine.y1 - stdLine.y2) } - const lineVector = { x: Math.sign(line.x1 - line.x2), y: Math.sign(line.y1 - line.y2) } - return (vector.x === lineVector.x && vector.y !== lineVector.y) || (vector.x !== lineVector.x && vector.y === lineVector.y) - }) - .forEach((line) => { - const lineStartX = Math.min(line.x1, line.x2) - const lineEndX = Math.max(line.x1, line.x2) - const lineStartY = Math.min(line.y1, line.y2) - const lineEndY = Math.max(line.y1, line.y2) - - if (stdAnalyze.isHorizontal) { - //full overlap - if (lineStartX <= startX && endX <= lineEndX) { - oppositeLine.push({ line, distance: Math.abs(lineStartY - startY) }) - } - if ( - (startX < lineStartX && lineStartX < endX) || - (startX < lineStartX && lineEndX < endX) || - (lineStartX === startX && lineEndX <= endX) || - (lineEndX === endX && lineStartX <= startX) - ) { - oppositeLine.push({ line, distance: Math.abs(lineStartY - startY) }) - } + if (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)), } - if (stdAnalyze.isVertical) { - //full overlap - if (lineStartY <= startY && endY <= lineEndY) { - oppositeLine.push({ line, distance: Math.abs(lineStartX - startX) }) - } - if ( - (startY < lineStartY && lineStartY < endY) || - (startY < lineEndY && lineEndY < endY) || - (lineStartY === startY && lineEndY <= endY) || - (lineEndY === endY && lineStartY <= startY) - ) { - oppositeLine.push({ line, distance: Math.abs(lineStartX - startX) }) - } - } - }) - - if (oppositeLine.length > 0) { - const ridgePoints = [] - //지붕선 출발 지점 확인을 위한 기준 포인트 - let ridgeStdPoint = { x: (currentLine.x1 + currentLine.x2) / 2, y: (currentLine.y1 + currentLine.y2) / 2 } - - oppositeLine.sort((a, b) => a.distance - b.distance) - oppositeLine.forEach((opposite) => { - const oppLine = opposite.line - const checkOppLine = new fabric.Line([oppLine.x1, oppLine.y1, oppLine.x2, oppLine.y2], { - stroke: 'yellow', - strokeW: 4, - parentId: roofId, - name: 'check', - }) - canvas.add(checkOppLine) - canvas.renderAll() - - const oppIndex = baseLines.findIndex((line) => line === oppLine) - //마주하는 라인의 이전 다음 라인. - const oppPrevLine = baseLines[(oppIndex - 1 + baseLines.length) % baseLines.length] - const oppNextLine = baseLines[(oppIndex + 1) % baseLines.length] - const oppAnalyze = analyzeLine(oppLine) - const oppVector = { x: Math.sign(oppLine.x2 - oppLine.x1), y: Math.sign(oppLine.y2 - oppLine.y1) } - let ridgePoint - - const oppPrevVector = { x: Math.sign(oppPrevLine.x1 - oppPrevLine.x2), y: Math.sign(oppPrevLine.y1 - oppPrevLine.y2) } - const oppNextVector = { x: Math.sign(oppNextLine.x1 - oppNextLine.x2), y: Math.sign(oppNextLine.y1 - oppNextLine.y2) } - if (oppPrevVector.x === oppNextVector.x && oppPrevVector.y === oppNextVector.y) { - const addOffsetX1 = oppPrevVector.y * oppPrevLine.attributes.offset - const addOffsetY1 = -oppPrevVector.x * oppPrevLine.attributes.offset - const addOffsetX2 = oppPrevVector.y * oppNextLine.attributes.offset - const addOffsetY2 = -oppPrevVector.x * oppNextLine.attributes.offset - ridgePoint = [oppLine.x1 + addOffsetX1, oppLine.y1 + addOffsetY1, oppLine.x2 + addOffsetX2, oppLine.y2 + addOffsetY2] + 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 { - 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, - ] + 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 { - 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, + 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 }, ] - } - } - 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]] + 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 } - if (oppNextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) { - //처마라인이 아닌 반대쪽부터 마루선을 시작하도록 포인트 변경 - const oppPrevAnalyze = analyzeLine(oppPrevLine) - if (oppPrevAnalyze.isHorizontal) { - const dist1 = Math.abs(oppPrevLine.y1 - ridgePoint[1]) - const dist2 = Math.abs(oppPrevLine.y1 - ridgePoint[3]) - if (dist1 > dist2) { - ridgePoint = [ridgePoint[2], ridgePoint[3], ridgePoint[0], ridgePoint[1]] - } - } - if (oppPrevAnalyze.isVertical) { - const dist1 = Math.abs(oppPrevLine.x1 - ridgePoint[0]) - const dist2 = Math.abs(oppPrevLine.x1 - ridgePoint[2]) - if (dist1 > dist2) { - ridgePoint = [ridgePoint[2], ridgePoint[3], ridgePoint[0], ridgePoint[1]] - } - } - const checkEdge = { vertex1: { x: ridgePoint[0], y: ridgePoint[1] }, vertex2: { x: ridgePoint[2], y: ridgePoint[3] } } - const checkVector = getHalfAngleVector(oppLine, oppNextLine) - const checkPoint = { x: oppLine.x2 + (checkVector.x * 10) / 2, y: oppLine.y2 + (checkVector.y * 10) / 2 } - let hipVector - if (checkWallPolygon.inPolygon(checkPoint)) { - hipVector = { x: checkVector.x, y: checkVector.y } - } else { - hipVector = { x: -checkVector.x, y: -checkVector.y } - } - - const ridgeVector = { x: Math.sign(ridgePoint[0] - ridgePoint[2]), y: Math.sign(ridgePoint[1] - ridgePoint[3]) } - const hipEdge = { vertex1: { x: oppLine.x2, y: oppLine.y2 }, vertex2: { x: oppLine.x2 + hipVector.x, y: oppLine.y2 + hipVector.y } } - const intersect = edgesIntersection(hipEdge, checkEdge) - if (intersect) { - const intersectVector = { x: Math.sign(ridgePoint[0] - intersect.x), y: Math.sign(ridgePoint[1] - intersect.y) } - if (ridgeVector.x === intersectVector.x && ridgeVector.y === intersectVector.y) { - ridgePoint = [ridgePoint[0], ridgePoint[1], intersect.x, intersect.y] - } else { - ridgePoint = [ridgePoint[2], ridgePoint[3], ridgePoint[2], ridgePoint[3]] - } - } + const 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 stdMinX = Math.min(stdPoints[0], stdPoints[2]) - const stdMaxX = Math.max(stdPoints[0], stdPoints[2]) - const stdMinY = Math.min(stdPoints[1], stdPoints[3]) - const stdMaxY = Math.max(stdPoints[1], stdPoints[3]) - - const rMinX = Math.min(ridgePoint[0], ridgePoint[2]) - const rMaxX = Math.max(ridgePoint[0], ridgePoint[2]) - const rMinY = Math.min(ridgePoint[1], ridgePoint[3]) - const rMaxY = Math.max(ridgePoint[1], ridgePoint[3]) - - const overlapMinX = Math.max(stdMinX, rMinX) - const overlapMaxX = Math.min(stdMaxX, rMaxX) - const overlapMinY = Math.max(stdMinY, rMinY) - const overlapMaxY = Math.min(stdMaxY, rMaxY) - - if (stdAnalyze.isHorizontal) { - if (overlapMinX > overlapMaxX) { - return - } - ridgePoint = [overlapMinX, ridgePoint[1], overlapMaxX, ridgePoint[3]] - } - if (stdAnalyze.isVertical) { - if (overlapMinY > overlapMaxY) { - return - } - ridgePoint = [ridgePoint[0], overlapMinY, ridgePoint[2], overlapMaxY] - } - - ridgePoints.push({ point: ridgePoint, left: stdLine, right: oppLine }) - canvas - .getObjects() - .filter((obj) => obj.name === 'check') - .forEach((obj) => canvas.remove(obj)) - canvas.renderAll() - }) - - ridgePoints.forEach((r) => { - let point = r.point - const inPolygon1 = - roof.inPolygon({ x: point[0], y: point[1] }) || - roof.lines.find((line) => isPointOnLineNew(line, { x: point[0], y: point[1] })) !== undefined - const inPolygon2 = - roof.inPolygon({ x: point[2], y: point[3] }) || - roof.lines.find((line) => isPointOnLineNew(line, { x: point[2], y: point[3] })) !== undefined - - //시작점이 지붕 밖에 있을때 vector내의 가까운 지붕선으로 변경 - if (!inPolygon1) { - const checkVector = { x: Math.sign(point[0] - point[2]), y: Math.sign(point[1] - point[3]) } - const checkEdge = { vertex1: { x: point[0], y: point[1] }, vertex2: { x: point[2], y: point[3] } } - let minDistance = Infinity - let correctPoint - roof.lines.forEach((line) => { - const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } - const intersect = edgesIntersection(lineEdge, checkEdge) + 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) { - 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 (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 } } - }) - 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 - } + /* 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]] } - }) - 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)) + ridgePoints.push({ point: ridgePoint, left: stdLine, right: oppLine }) + canvas + .getObjects() + .filter((obj) => obj.name === 'check') + .forEach((obj) => canvas.remove(obj)) + canvas.renderAll() + }) - if (ridgeLength > EPSILON) { - linesAnalysis.push({ - start: { x: point[0], y: point[1] }, - end: { x: point[2], y: point[3] }, - left: baseLines.findIndex((line) => line === r.left), - right: baseLines.findIndex((line) => line === r.right), - type: 'ridge', - degree: 0, - }) - } - }) + 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], { + /*const checkRoof = new fabric.Line([roofLine.x1, roofLine.y1, roofLine.x2, roofLine.y2], { stroke: 'red', strokeWidth: 4, parentId: roofId, @@ -3293,7 +4186,7 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { name: 'check', }) canvas.add(checkLine) - canvas.renderAll() + 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 } } @@ -3307,16 +4200,16 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { return line !== roofLine && line !== prevRoofLine && line !== nextRoofLine }) .forEach((line) => { - const checkLine = new fabric.Line([line.x1, line.y1, line.x2, line.y2], { + /*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() + 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) { + /* if (intersect) { const checkCircle = new fabric.Circle({ left: intersect.x, top: intersect.y, @@ -3327,7 +4220,7 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { }) 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 @@ -3351,7 +4244,7 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { end: { x: correctPoint.x, y: correctPoint.y }, left: baseLines.findIndex((line) => line === prevLine), right: baseLines.findIndex((line) => line === nextLine), - type: 'gableLine', + type: TYPES.GABLE_LINE, degree: getDegreeByChon(prevLine.attributes.pitch), gableId: baseLines.findIndex((line) => line === currentLine), connectCnt: 0, @@ -3372,9 +4265,647 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { console.log('baseLines', baseLines) console.log('linesAnalysis', linesAnalysis) + while (linesAnalysis.length > 0 && iterations < MAX_ITERATIONS) { iterations++ + /*linesAnalysis.forEach((line) => { + const point = [line.start.x, line.start.y, line.end.x, line.end.y] + const checkLine = new fabric.Line(point, { + stroke: 'red', + strokeWidth: 2, + parentId: roofId, + name: 'check', + }) + canvas.add(checkLine).renderAll() + // canvas.remove(checkLine).renderAll() + })*/ + + const intersections = [] + linesAnalysis.forEach((currLine, i) => { + let minDistance = Infinity + let intersectPoint = null + let linePoint = null + let partner = null + + /*const checkCLine = new fabric.Line([currLine.start.x, currLine.start.y, currLine.end.x, currLine.end.y], { + stroke: 'red', + strokeWidth: 4, + parentId: roofId, + name: 'check', + }) + canvas.add(checkCLine).renderAll()*/ + + const cLength = Math.sqrt(Math.pow(currLine.end.x - currLine.start.x, 2) + Math.pow(currLine.end.y - currLine.start.y, 2)) + //남은 길이가 0이면 무시 + if (cLength < EPSILON) return + // 하단레벨 케라바 라인인데 연결점이 2개 이상이면 무시 + if (currLine.type === TYPES.GABLE_LINE && currLine.connectCnt > 1) return + + linesAnalysis.forEach((nextLine, j) => { + if (i === j) return + if (currLine.type === TYPES.GABLE_LINE && nextLine.type === TYPES.GABLE_LINE && currLine.gableId === nextLine.gableId) return + if (nextLine.type === TYPES.GABLE_LINE && nextLine.connectCnt > 1) return + /*const checkNLine = new fabric.Line([nextLine.start.x, nextLine.start.y, nextLine.end.x, nextLine.end.y], { + stroke: 'green', + strokeWidth: 4, + parentId: roofId, + name: 'check', + }) + canvas.add(checkNLine).renderAll()*/ + + const intersect = lineIntersection(currLine.start, currLine.end, nextLine.start, nextLine.end, canvas) + if (intersect) { + const checkCircle = new fabric.Circle({ left: intersect.x, top: intersect.y, radius: 5, fill: 'blue', parentId: roofId, name: 'check' }) + canvas.add(checkCircle).renderAll() + let distance1 = Math.sqrt(Math.pow(intersect.x - currLine.start.x, 2) + Math.pow(intersect.y - currLine.start.y, 2)) + let distance2 = Math.sqrt(Math.pow(intersect.x - nextLine.start.x, 2) + Math.pow(intersect.y - nextLine.start.y, 2)) + let point = [currLine.start.x, currLine.start.y, intersect.x, intersect.y] + if (distance1 < EPSILON) { + distance1 = Math.sqrt(Math.pow(intersect.x - currLine.end.x, 2) + Math.pow(intersect.y - currLine.end.y, 2)) + distance2 = Math.sqrt(Math.pow(intersect.x - nextLine.end.x, 2) + Math.pow(intersect.y - nextLine.end.y, 2)) + point = [currLine.end.x, currLine.end.y, intersect.x, intersect.y] + } + // 하단레벨 케라바 라인인데 남은 길이가 없는 경우 무시 + if (currLine.type === TYPES.GABLE_LINE && distance1 < EPSILON) return + //교점까지의 길이가 최소 길이보다 작을 경우, 최소 길이와 교점을 교체 + if (distance1 < minDistance && !almostEqual(distance1, minDistance) && !(distance1 < EPSILON && distance2 < EPSILON)) { + minDistance = distance1 + intersectPoint = intersect + linePoint = point + partner = j + } else if (almostEqual(distance1, minDistance)) { + const pLine = linesAnalysis[partner] + const isSameLine = + currLine.left === pLine.left || currLine.left === pLine.right || currLine.right === pLine.left || currLine.right === pLine.right + if (!isSameLine) { + partner = j + } + } + canvas.remove(checkCircle).renderAll() + } + canvas.remove(checkNLine).renderAll() + }) + canvas.remove(checkCLine).renderAll() + if (intersectPoint) { + intersections.push({ index: i, intersect: intersectPoint, linePoint, partner }) + } + }) + console.log('intersections', intersections) + const intersectPoints = intersections + .map((item) => item.intersect) + .filter((point, index, self) => self.findIndex((p) => almostEqual(p.x, point.x) && almostEqual(p.y, point.y)) === index) + console.log('intersectPoints', intersectPoints) + + if (intersectPoints.length === 1 && intersections.length > 1) { + intersections[0].partner = intersections[1].index + intersections[1].partner = intersections[0].index + } + + let newAnalysis = [] //신규 발생 선 + const processed = new Set() //처리된 선 + + for (const { index, intersect, linePoint, partner } of intersections) { + const cLine = linesAnalysis[index] + const pLine = linesAnalysis[partner] + + //교점이 없거나, 이미 처리된 선분이면 처리하지 않는다. + if (!intersect || processed.has(index)) continue + + //교점이 없거나, 교점 선분이 없으면 처리하지 않는다. + const pIntersection = intersections.find((p) => p.index === partner) + if (!pIntersection || !pIntersection.intersect) continue + + //상호 최단 교점이 아닐경우 처리하지 않는다. + if (pIntersection.partner !== index) continue + + //공통선분이 없으면 처리하지 않는다. + const isSameLine = cLine.left === pLine.left || cLine.left === pLine.right || cLine.right === pLine.left || cLine.right === pLine.right + if (!isSameLine) continue + + //처리 된 라인으로 설정 + processed.add(index).add(partner) + + let point1 = linePoint + let point2 = pIntersection.linePoint + let length1 = Math.sqrt(Math.pow(point1[2] - point1[0], 2) + Math.pow(point1[3] - point1[1], 2)) + let length2 = Math.sqrt(Math.pow(point2[2] - point2[0], 2) + Math.pow(point2[3] - point2[1], 2)) + + //gable라인과 붙는경우 length가 0에 가까우면 포인트를 뒤집는다. + if (cLine.type === TYPES.GABLE_LINE && length2 < EPSILON) { + point2 = [pLine.end.x, pLine.end.y, intersect.x, intersect.y] + length2 = Math.sqrt((point2[2] - point2[0]) ** 2 + (point2[3] - point2[1]) ** 2) + } + if (pLine.type === TYPES.GABLE_LINE && length1 < EPSILON) { + point1 = [cLine.end.x, cLine.end.y, intersect.x, intersect.y] + length1 = Math.sqrt((point1[2] - point1[0]) ** 2 + (point1[3] - point1[1]) ** 2) + } + + if (length1 > 0 && !alreadyPoints(innerLines, point1)) { + if (cLine.type === TYPES.HIP) { + innerLines.push(drawHipLine(point1, canvas, roof, textMode, null, cLine.degree, cLine.degree)) + } else if (cLine.type === TYPES.RIDGE) { + innerLines.push(drawRidgeLine(point1, canvas, roof, textMode)) + } else if (cLine.type === TYPES.NEW) { + const isDiagonal = Math.abs(point1[0] - point1[2]) >= 1 && Math.abs(point1[1] - point1[3]) >= 1 + if (isDiagonal) { + innerLines.push(drawHipLine(point1, canvas, roof, textMode, null, cLine.degree, cLine.degree)) + } else { + innerLines.push(drawRidgeLine(point1, canvas, roof, textMode)) + } + } else if (cLine.type === TYPES.GABLE_LINE) { + if (cLine.degree > 0) { + innerLines.push(drawHipLine(point1, canvas, roof, textMode, null, cLine.degree, cLine.degree)) + } else { + innerLines.push(drawRidgeLine(point1, canvas, roof, textMode)) + } + } + } + + if (length2 > 0 && !alreadyPoints(innerLines, point2)) { + if (pLine.type === TYPES.HIP) { + innerLines.push(drawHipLine(point2, canvas, roof, textMode, null, pLine.degree, pLine.degree)) + } else if (pLine.type === TYPES.RIDGE) { + innerLines.push(drawRidgeLine(point2, canvas, roof, textMode)) + } else if (pLine.type === TYPES.NEW) { + const isDiagonal = Math.abs(point2[0] - point2[2]) >= 1 && Math.abs(point2[1] - point2[3]) >= 1 + if (isDiagonal) { + innerLines.push(drawHipLine(point2, canvas, roof, textMode, null, pLine.degree, pLine.degree)) + } else { + innerLines.push(drawRidgeLine(point2, canvas, roof, textMode)) + } + } else if (pLine.type === TYPES.GABLE_LINE) { + if (pLine.degree > 0) { + innerLines.push(drawHipLine(point2, canvas, roof, textMode, null, pLine.degree, pLine.degree)) + } else { + innerLines.push(drawRidgeLine(point2, canvas, roof, textMode)) + } + } + } + const otherIs = intersections + .filter((is) => is.index !== index && is.index !== partner) + .filter((is) => almostEqual(is.intersect.x, intersect.x) && almostEqual(is.intersect.y, intersect.y)) + + let relationBaseLines = [cLine.left, cLine.right, pLine.left, pLine.right] + if (otherIs.length > 0 && cLine.type !== TYPES.GABLE_LINE && pLine.type !== TYPES.GABLE_LINE) { + for (let i = 0; i < otherIs.length; i++) { + const oLine = linesAnalysis[otherIs[i].index] + if (oLine.type === TYPES.RIDGE && linesAnalysis.length > 3) continue + const isSameLine = relationBaseLines.includes(oLine.left) || relationBaseLines.includes(oLine.right) + if (isSameLine) { + let oPoint = otherIs[i].linePoint + let oLength = Math.sqrt(Math.pow(oPoint[2] - oPoint[0], 2) + Math.pow(oPoint[3] - oPoint[1], 2)) + if (oLength > 0 && !alreadyPoints(innerLines, oPoint)) { + if (oLine.type === TYPES.HIP) { + innerLines.push(drawHipLine(oPoint, canvas, roof, textMode, null, oLine.degree, oLine.degree)) + } else if (oLine.type === TYPES.RIDGE) { + innerLines.push(drawRidgeLine(oPoint, canvas, roof, textMode)) + } else if (oLine.type === TYPES.NEW) { + const isDiagonal = Math.abs(oPoint[0] - oPoint[2]) >= 1 && Math.abs(oPoint[1] - oPoint[3]) >= 1 + if (isDiagonal) { + innerLines.push(drawHipLine(oPoint, canvas, roof, textMode, null, oLine.degree, oLine.degree)) + } else { + innerLines.push(drawRidgeLine(oPoint, canvas, roof, textMode)) + } + } else if (oLine.type === TYPES.GABLE_LINE) { + if (oLine.degree > 0) { + innerLines.push(drawHipLine(oPoint, canvas, roof, textMode, null, oLine.degree, oLine.degree)) + } else { + innerLines.push(drawRidgeLine(oPoint, canvas, roof, textMode)) + } + } + } + processed.add(otherIs[i].index) + relationBaseLines.push(oLine.left, oLine.right) + } + } + } + if (cLine.type === TYPES.GABLE_LINE || pLine.type === TYPES.GABLE_LINE) { + relationBaseLines = [cLine.left, cLine.right, pLine.left, pLine.right] + console.log('gableLine newAnalyze start') + const gableLine = cLine.type === TYPES.GABLE_LINE ? cLine : pLine + gableLine.connectCnt++ + + /*const checkLine = new fabric.Line([gableLine.start.x, gableLine.start.y, gableLine.end.x, gableLine.end.y], { + stroke: 'red', + strokeWidth: 4, + parentId: roofId, + name: 'check', + }) + const checkCircle = new fabric.Circle({ + left: intersect.x, + top: intersect.y, + radius: 5, + parentId: roofId, + name: 'check', + }) + canvas.add(checkLine, checkCircle) + canvas.renderAll()*/ + + const uniqueBaseLines = [...new Set(relationBaseLines)] + // 연결점에서 새로운 가선분을 생성 + if (uniqueBaseLines.length > 2) { + const linesCounts = new Map() + relationBaseLines.forEach((e) => { + linesCounts.set(e, (linesCounts.get(e) || 0) + 1) + }) + + const uniqueLines = Array.from(linesCounts.entries()) + .filter(([_, count]) => count === 1) + .map(([line, _]) => line) + + if (uniqueLines.length === 2) { + if (gableLine.connectCnt < 2) { + newAnalysis.push({ + start: { x: intersect.x, y: intersect.y }, + end: { x: gableLine.end.x, y: gableLine.end.y }, + left: gableLine.left, + right: gableLine.right, + type: TYPES.GABLE_LINE, + degree: getDegreeByChon(baseLines[gableLine.right].attributes.pitch), + gableId: gableLine.gableId, + connectCnt: gableLine.connectCnt, + }) + } + if (gableLine.connectCnt >= 2) { + //가선분 발생만 시키는 더미 생성. + newAnalysis.push({ + start: { x: intersect.x, y: intersect.y }, + end: { x: intersect.x, y: intersect.y }, + left: gableLine.gableId, + right: gableLine.gableId, + type: TYPES.HIP, + degree: 0, + }) + } + } + } + console.log('gableLine newAnalyze end') + } else { + const uniqueBaseLines = [...new Set(relationBaseLines)] + // 연결점에서 새로운 가선분을 생성 + if (uniqueBaseLines.length > 2) { + const linesCounts = new Map() + relationBaseLines.forEach((e) => { + linesCounts.set(e, (linesCounts.get(e) || 0) + 1) + }) + + const uniqueLines = Array.from(linesCounts.entries()) + .filter(([_, count]) => count === 1) + .map(([line, _]) => line) + + console.log('uniqueLines', uniqueLines) + if (uniqueLines.length === 2) { + // 두 변의 이등분선 방향 계산 + // uniqueLines.sort((a, b) => a - b) + console.log('uniqueLines : ', uniqueLines) + const baseLine1 = baseLines[uniqueLines[0]] + const baseLine2 = baseLines[uniqueLines[1]] + + let bisector + console.log('isParallel(baseLine1, baseLine2)', isParallel(baseLine1, baseLine2)) + if (isParallel(baseLine1, baseLine2)) { + bisector = getBisectLines( + { x1: cLine.start.x, x2: cLine.end.x, y1: cLine.start.y, y2: cLine.end.y }, + { x1: pLine.start.x, y1: pLine.start.y, x2: pLine.end.x, y2: pLine.end.y }, + ) + } else { + bisector = getBisectBaseLines(baseLine1, baseLine2, intersect, canvas) + } + + //마주하는 지붕선을 찾는다. + const intersectionsByRoof = [] + const checkEdge = { + vertex1: { x: intersect.x, y: intersect.y }, + vertex2: { x: intersect.x + bisector.x, y: intersect.y + bisector.y }, + } + const checkVector = { + x: Math.sign(checkEdge.vertex1.x - checkEdge.vertex2.x), + y: Math.sign(checkEdge.vertex1.y - checkEdge.vertex2.y), + } + roof.lines.forEach((line) => { + const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } + const is = edgesIntersection(lineEdge, checkEdge) + if (is && isPointOnLineNew(line, is)) { + const distance = Math.sqrt((is.x - intersect.x) ** 2 + (is.y - intersect.y) ** 2) + const isVector = { x: Math.sign(intersect.x - is.x), y: Math.sign(intersect.y - is.y) } + if (isVector.x === checkVector.x && isVector.y === checkVector.y) { + intersectionsByRoof.push({ is, distance }) + } + } + }) + intersectionsByRoof.sort((a, b) => a.distance - b.distance) + console.log('intersectionsByRoof : ', intersectionsByRoof) + + //기 존재하는 analyze 라인이 있으면 newAnalyzeLine을 생성하지 않고 교체한다. + const otherIs = intersections.filter( + (is) => !processed.has(is.index) && almostEqual(is.intersect.x, intersect.x) && almostEqual(is.intersect.y, intersect.y), + ) + + console.log('otherIs', otherIs) + if (otherIs.length > 0) { + const analyze = linesAnalysis[otherIs[0].index] + processed.add(otherIs[0].index) + newAnalysis.push({ + start: { x: intersect.x, y: intersect.y }, + end: { x: analyze.end.x, y: analyze.end.y }, + left: analyze.left, + right: analyze.right, + type: analyze.type, + degree: analyze.degree, + gableId: analyze.gableId, + connectCnt: analyze.connectCnt, + }) + } else if (intersectionsByRoof.length > 0) { + //지붕선과 교점이 발생할때 + let is = intersectionsByRoof[0].is + let linePoint = [intersect.x, intersect.y, is.x, is.y] + const isDiagonal = Math.abs(is.x - intersect.x) >= 1 && Math.abs(is.y - intersect.y) >= 1 + const length = Math.sqrt((linePoint[2] - linePoint[0]) ** 2 + (linePoint[3] - linePoint[1]) ** 2) + + const line1 = baseLines[uniqueLines[0]] + const line2 = baseLines[uniqueLines[1]] + const vector1 = { x: Math.sign(line1.x1 - line1.x2), y: Math.sign(line1.y1 - line1.y2) } + + const prevLine = checkVector.x === vector1.x && checkVector.y === vector1.y ? line2 : line1 + const nextLine = checkVector.x === vector1.x && checkVector.y === vector1.y ? line1 : line2 + + /*const checkPrevLine = new fabric.Line([prevLine.x1, prevLine.y1, prevLine.x2, prevLine.y2], { + stroke: 'yellow', + strokeWidth: 4, + parentId: roofId, + name: 'check', + }) + const checkNextLine = new fabric.Line([nextLine.x1, nextLine.y1, nextLine.x2, nextLine.y2], { + stroke: 'red', + strokeWidth: 4, + parentId: roofId, + name: 'check', + }) + canvas.add(checkPrevLine, checkNextLine).renderAll()*/ + + if (!isDiagonal) { + const drivePoint = getRidgeDrivePoint(linePoint, prevLine, nextLine, baseLines) + if (drivePoint !== null) { + const driveLength = Math.sqrt((drivePoint.x - intersect.x) ** 2 + (drivePoint.y - intersect.y) ** 2) + if (driveLength < length) { + linePoint = [intersect.x, intersect.y, drivePoint.x, drivePoint.y] + } + } + } + const checkNewLine = new fabric.Line(linePoint, { stroke: 'cyan', strokeWidth: 4, parentId: roofId, name: 'check' }) + canvas.add(checkNewLine).renderAll() + + newAnalysis.push({ + start: { x: linePoint[0], y: linePoint[1] }, + end: { x: linePoint[2], y: linePoint[3] }, + left: uniqueLines[0], + right: uniqueLines[1], + type: TYPES.NEW, + degree: isDiagonal ? cLine.degree : 0, + }) + } + } + } + } + canvas + .getObjects() + .filter((object) => object.name === 'check' || object.name === 'checkAnalysis') + .forEach((object) => canvas.remove(object)) + canvas.renderAll() + + if (newAnalysis.length > 0) break + } + + // 처리된 가선분 제외 + linesAnalysis = newAnalysis.concat(linesAnalysis.filter((_, index) => !processed.has(index))) + console.log('lineAnalysis: ', linesAnalysis) + + canvas + .getObjects() + .filter((object) => object.name === 'check' || object.name === 'checkAnalysis') + .forEach((object) => canvas.remove(object)) + canvas.renderAll() + if (newAnalysis.length === 0) break + } + + // 가선분 중 처리가 안되어있는 붙어있는 라인에 대한 예외처리. + const proceedAnalysis = [] + linesAnalysis + .filter((line) => { + const dx = line.end.x - line.start.x + const dy = line.end.y - line.start.y + const length = Math.sqrt(dx ** 2 + dy ** 2) + return length > 0 + }) + .forEach((currentLine) => { + if (proceedAnalysis.find((p) => p === currentLine)) return + //현재와 같으면 제외, 이미 처리된 라인은 제외, 현재와 공통선분이 존재하지 않으면 제외 + linesAnalysis + .filter((line) => { + const dx = line.end.x - line.start.x + const dy = line.end.y - line.start.y + const length = Math.sqrt(dx ** 2 + dy ** 2) + return length > 0 + }) + .filter( + (partnerLine) => + partnerLine !== currentLine && + !proceedAnalysis.find((p) => p === partnerLine) && + (currentLine.left === partnerLine.left || + currentLine.left === partnerLine.right || + partnerLine.left === currentLine.left || + partnerLine.left === currentLine.right), + ) + .forEach((partnerLine) => { + const dx1 = currentLine.end.x - currentLine.start.x + const dy1 = currentLine.end.y - currentLine.start.y + const dx2 = partnerLine.end.x - partnerLine.start.x + const dy2 = partnerLine.end.y - partnerLine.start.y + const denominator = dy2 * dx1 - dx2 * dy1 + const isOpposite = dx1 * dx2 + dy1 * dy2 < 0 + //평행하지 않으면 제외 + if (Math.abs(denominator) > EPSILON) return + + const currentDegree = getDegreeByChon(baseLines[currentLine.left].attributes.pitch) + if (isOpposite) { + const points = [currentLine.start.x, currentLine.start.y, partnerLine.start.x, partnerLine.start.y] + const length = Math.sqrt((points[0] - points[2]) ** 2 + (points[1] - points[3]) ** 2) + + if (length > 0) { + if (currentLine.type === TYPES.HIP) { + innerLines.push(drawHipLine(points, canvas, roof, textMode, null, currentDegree, currentDegree)) + } else if (currentLine.type === TYPES.RIDGE) { + innerLines.push(drawRidgeLine(points, canvas, roof, textMode)) + } else if (currentLine.type === TYPES.NEW) { + const isDiagonal = Math.abs(points[0] - points[2]) >= 1 && Math.abs(points[1] - points[3]) >= 1 + if (isDiagonal && almostEqual(Math.abs(points[0] - points[2]), Math.abs(points[1] - points[3]))) { + innerLines.push(drawHipLine(points, canvas, roof, textMode, null, currentDegree, currentDegree)) + } + if (!isDiagonal) { + innerLines.push(drawRidgeLine(points, canvas, roof, textMode)) + } + } + proceedAnalysis.push(currentLine, partnerLine) + } + } else { + const allPoints = [currentLine.start, currentLine.end, partnerLine.start, partnerLine.end] + let points = [] + allPoints.forEach((point) => { + let count = 0 + allPoints.forEach((p) => { + if (almostEqual(point.x, p.x) && almostEqual(point.y, p.y)) count++ + }) + if (count === 1) points.push(point) + }) + + if (points.length === 2) { + const length = Math.sqrt((points[0].x - points[1].x) ** 2 + (points[0].y - points[1].y) ** 2) + if (length < EPSILON) return + const isDiagonal = Math.abs(points[0].x - points[1].x) >= 1 && Math.abs(points[0].y - points[1].y) >= 1 + if (isDiagonal) { + innerLines.push( + drawHipLine([points[0].x, points[0].y, points[1].x, points[1].y], canvas, roof, textMode, null, currentDegree, currentDegree), + ) + } else { + innerLines.push(drawRidgeLine([points[0].x, points[0].y, points[1].x, points[1].y], canvas, roof, textMode)) + } + proceedAnalysis.push(currentLine, partnerLine) + } + } + }) + }) + linesAnalysis = linesAnalysis.filter((line) => !proceedAnalysis.find((p) => p === line)) + + // 최종 라인중 벽에서 시작해서 벽에서 끝나는 라인이 남을 경우(벽취함) + if (linesAnalysis.length > 0) { + linesAnalysis + .filter((line) => { + const dx = line.end.x - line.start.x + const dy = line.end.y - line.start.y + const length = Math.sqrt(dx ** 2 + dy ** 2) + return length > 0 + }) + .forEach((line) => { + const startOnLine = roof.lines.find((l) => isPointOnLineNew(l, line.start)) + const endOnLine = roof.lines.find((l) => isPointOnLineNew(l, line.end)) + console.log('startOnLine, endOnLine: ', startOnLine, endOnLine) + const allLinesPoints = [] + innerLines.forEach((innerLine) => { + allLinesPoints.push({ x: innerLine.x1, y: innerLine.y1 }, { x: innerLine.x2, y: innerLine.y2 }) + }) + + if ( + allLinesPoints.filter((p) => almostEqual(p.x, line.start.x) && almostEqual(p.y, line.start.y)).length < 3 && + allLinesPoints.filter((p) => almostEqual(p.x, line.end.x) && almostEqual(p.y, line.end.y)).length === 0 + ) { + if (startOnLine && endOnLine) { + if (line.degree === 0) { + innerLines.push(drawRoofLine([line.start.x, line.start.y, line.end.x, line.end.y], canvas, roof, textMode)) + } else { + innerLines.push( + drawHipLine([line.start.x, line.start.y, line.end.x, line.end.y], canvas, roof, textMode, null, line.degree, line.degree), + ) + } + } + } + }) + } + + //하단 지붕 라인처리 + const downRoofLines = [] + baseLines.forEach((baseLine, index) => { + const nextLine = baseLines[(index + 1) % baseLines.length] + const prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] + + const prevLineVector = { x: Math.sign(prevLine.x1 - prevLine.x2), y: Math.sign(prevLine.y1 - prevLine.y2) } + const nextLineVector = { x: Math.sign(nextLine.x1 - nextLine.x2), y: Math.sign(nextLine.y1 - nextLine.y2) } + + //반절마루 생성불가이므로 지붕선 분기를 해야하는지 확인 후 처리. + if ( + prevLineVector.x === nextLineVector.x && + prevLineVector.y === nextLineVector.y && + (prevLine.attributes.type === LINE_TYPE.WALLLINE.GABLE || nextLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) + ) { + downRoofLines.push(index) + } + }) + + //지붕선에 따라 라인추가 작업 처리. + const innerLinesPoints = [] + innerLines.forEach((line) => { + const hasCoord1 = innerLinesPoints.find((p) => p.x === line.x1 && p.y === line.y1) + const hasCoord2 = innerLinesPoints.find((p) => p.x === line.x2 && p.y === line.y2) + if (!hasCoord1) innerLinesPoints.push({ x: line.x1, y: line.y1 }) + if (!hasCoord2) innerLinesPoints.push({ x: line.x2, y: line.y2 }) + }) + roof.lines.forEach((currentLine, index) => { + const prevLine = roof.lines[(index - 1 + roof.lines.length) % roof.lines.length] + const nextLine = roof.lines[(index + 1) % roof.lines.length] + const prevDegree = getDegreeByChon(prevLine.attributes.pitch) + const nextDegree = getDegreeByChon(nextLine.attributes.pitch) + console.log('prevDegree, nextDegree: ', prevDegree, nextDegree) + + const splitPoint = [] + let hasOverlapLine = false + const minX = Math.min(currentLine.x1, currentLine.x2) + const maxX = Math.max(currentLine.x1, currentLine.x2) + const minY = Math.min(currentLine.y1, currentLine.y2) + const maxY = Math.max(currentLine.y1, currentLine.y2) + innerLines.forEach((innerLine) => { + const innerLineMinX = Math.min(innerLine.x1, innerLine.x2) + const innerLineMaxX = Math.max(innerLine.x1, innerLine.x2) + const innerLineMinY = Math.min(innerLine.y1, innerLine.y2) + const innerLineMaxY = Math.max(innerLine.y1, innerLine.y2) + if (innerLineMinX <= minX && innerLineMaxX >= maxX && innerLineMinY <= minY && innerLineMaxY >= maxY) { + hasOverlapLine = true + } + if (minX <= innerLineMinX && maxX >= innerLineMaxX && minY <= innerLineMinY && maxY >= innerLineMaxY) { + hasOverlapLine = true + } + }) + if (hasOverlapLine) return + + innerLinesPoints.forEach((point) => { + if ( + isPointOnLineNew(currentLine, point) && + !(almostEqual(currentLine.x1, point.x) && almostEqual(currentLine.y1, point.y)) && + !(almostEqual(currentLine.x2, point.x) && almostEqual(currentLine.y2, point.y)) + ) { + const distance = Math.sqrt((point.x - currentLine.x1) ** 2 + (point.y - currentLine.y1) ** 2) + splitPoint.push({ point, distance }) + } + }) + if (splitPoint.length > 0) { + splitPoint.sort((a, b) => a[1] - b[1]) + let startPoint = { x: currentLine.x1, y: currentLine.y1 } + for (let i = 0; i < splitPoint.length; i++) { + const point = splitPoint[i].point + if (i === 0) { + innerLines.push(drawHipLine([startPoint.x, startPoint.y, point.x, point.y], canvas, roof, textMode, null, prevDegree, prevDegree)) + } else { + innerLines.push(drawRoofLine([startPoint.x, startPoint.y, point.x, point.y], canvas, roof, textMode)) + } + startPoint = point + } + innerLines.push(drawHipLine([startPoint.x, startPoint.y, currentLine.x2, currentLine.y2], canvas, roof, textMode, null, nextDegree, nextDegree)) + } else { + innerLines.push(drawRoofLine([currentLine.x1, currentLine.y1, currentLine.x2, currentLine.y2], canvas, roof, textMode)) + } + }) + + //지붕 innerLines에 추가. + roof.innerLines = innerLines + + canvas + .getObjects() + .filter((object) => object.name === 'check') + .forEach((object) => canvas.remove(object)) + canvas.renderAll() + //return + /*while (linesAnalysis.length > 0 && iterations < MAX_ITERATIONS) { + iterations++ + linesAnalysis.forEach((line) => { const point = [line.start.x, line.start.y, line.end.x, line.end.y] const checkLine = new fabric.Line(point, { @@ -3394,6 +4925,8 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { 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, @@ -3402,36 +4935,47 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { }) canvas.add(checkLineI).renderAll() - if (lineI.type === 'gableLine' && lineI.connectCnt > 1) continue + 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 === 'gableLine' && lineJ.type === 'gableLine' && i.gableId === lineJ.gableId) continue - if (lineJ.type === 'gableLine' && lineJ.connectCnt > 1) continue - - const checkLineJ = new fabric.Line([lineJ.start.x, lineJ.start.y, lineJ.end.x, lineJ.end.y], { - stroke: 'green', - strokeWidth: 2, - parentId: roofId, - name: 'check', - }) - canvas.add(checkLineJ).renderAll() + 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 checkCircle = new fabric.Circle({ - left: intersection.x, - top: intersection.y, - radius: 5, - fill: 'red', - parentId: roofId, - name: 'check', - }) - canvas.add(checkCircle).renderAll() const distance = Math.sqrt((intersection.x - lineI.start.x) ** 2 + (intersection.y - lineI.start.y) ** 2) const distance2 = Math.sqrt((intersection.x - lineJ.start.x) ** 2 + (intersection.y - lineJ.start.y) ** 2) - if (lineI.type === 'gableLine' && distance < EPSILON) continue + if (lineI.type === TYPES.GABLE_LINE && distance < EPSILON) continue if (distance < minDistance && !almostEqual(distance, minDistance) && !(distance < EPSILON && distance2 < EPSILON)) { minDistance = distance intersectPoint = intersection @@ -3439,9 +4983,7 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { } else if (almostEqual(distance, minDistance)) { partners.add(j) } - canvas.remove(checkCircle).renderAll() } - canvas.remove(checkLineJ).renderAll() } if (intersectPoint) { intersections.push({ index: i, intersectPoint, partners, distance: minDistance }) @@ -3515,28 +5057,28 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { isProceed = true //gable라인과 붙는경우 length가 0에 가까우면 포인트를 뒤집는다. - if (line1.type === 'gableLine' && length2 < EPSILON) { + 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 === 'gableLine' && length1 < EPSILON) { + 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 === 'hip') { + if (line1.type === TYPES.HIP) { innerLines.push(drawHipLine(point1, canvas, roof, textMode, null, line1.degree, line1.degree)) - } else if (line1.type === 'ridge') { + } else if (line1.type === TYPES.RIDGE) { innerLines.push(drawRidgeLine(point1, canvas, roof, textMode)) - } else if (line1.type === 'new') { + } 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 === 'gableLine') { + } else if (line1.type === TYPES.GABLE_LINE) { if (line1.degree > 0) { innerLines.push(drawHipLine(point1, canvas, roof, textMode, null, line1.degree, line1.degree)) } else { @@ -3546,18 +5088,18 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { } if (length2 > 0 && !alreadyPoints(innerLines, point2)) { - if (line2.type === 'hip') { + if (line2.type === TYPES.HIP) { innerLines.push(drawHipLine(point2, canvas, roof, textMode, null, line2.degree, line2.degree)) - } else if (line2.type === 'ridge') { + } else if (line2.type === TYPES.RIDGE) { innerLines.push(drawRidgeLine(point2, canvas, roof, textMode)) - } else if (line2.type === 'new') { + } 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 === 'gableLine') { + } else if (line2.type === TYPES.GABLE_LINE) { if (line2.degree > 0) { innerLines.push(drawHipLine(point2, canvas, roof, textMode, null, line2.degree, line2.degree)) } else { @@ -3566,9 +5108,9 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { } } - if (line1.type === 'gableLine' || line2.type === 'gableLine') { + if (line1.type === TYPES.GABLE_LINE || line2.type === TYPES.GABLE_LINE) { console.log('gableLine newAnalyze start') - const gableLine = line1.type === 'gableLine' ? line1 : line2 + 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], { @@ -3605,7 +5147,7 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { end: { x: gableLine.end.x, y: gableLine.end.y }, left: gableLine.left, right: gableLine.right, - type: 'gableLine', + type: TYPES.GABLE_LINE, degree: getDegreeByChon(baseLines[gableLine.right].attributes.pitch), gableId: gableLine.gableId, connectCnt: gableLine.connectCnt, @@ -3618,7 +5160,7 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { end: { x: intersectPoint.x, y: intersectPoint.y }, left: gableLine.gableId, right: gableLine.gableId, - type: 'hip', + type: TYPES.HIP, degree: 0, }) } @@ -3724,7 +5266,7 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { end: { x: linePoint[2], y: linePoint[3] }, left: uniqueLines[0], right: uniqueLines[1], - type: 'new', + type: TYPES.NEW, degree: isDiagonal ? line1.degree : 0, }) } @@ -3763,10 +5305,10 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { console.log('newAnalysis.length : ', newAnalysis.length) if (newAnalysis.length === 0) break } - console.log('lineAnalysis: end ', linesAnalysis) + console.log('lineAnalysis: end ', linesAnalysis)*/ // 가선분 중 처리가 안되어있는 붙어있는 라인에 대한 예외처리. - const proceedAnalysis = [] + /* const proceedAnalysis = [] linesAnalysis .filter((line) => { const dx = line.end.x - line.start.x @@ -3809,11 +5351,11 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { const length = Math.sqrt((points[0] - points[2]) ** 2 + (points[1] - points[3]) ** 2) if (length > 0) { - if (currentLine.type === 'hip') { + if (currentLine.type === TYPES.HIP) { innerLines.push(drawHipLine(points, canvas, roof, textMode, null, currentDegree, currentDegree)) - } else if (currentLine.type === 'ridge') { + } else if (currentLine.type === TYPES.RIDGE) { innerLines.push(drawRidgeLine(points, canvas, roof, textMode)) - } else if (currentLine.type === 'new') { + } 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)) @@ -3851,10 +5393,10 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { } }) }) - linesAnalysis = linesAnalysis.filter((line) => !proceedAnalysis.find((p) => p === line)) + linesAnalysis = linesAnalysis.filter((line) => !proceedAnalysis.find((p) => p === line))*/ //하단 지붕 라인처리 - const downRoofLines = [] + /* const downRoofLines = [] baseLines.forEach((baseLine, index) => { const nextLine = baseLines[(index + 1) % baseLines.length] const prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] @@ -3870,9 +5412,9 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { ) { downRoofLines.push(index) } - }) + })*/ - if (downRoofLines.length > 0) { + /*if (downRoofLines.length > 0) { downRoofLines.forEach((index) => { const currentLine = baseLines[index] // const nextLine = baseLines[(index + 1) % baseLines.length] @@ -3937,7 +5479,7 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { } }) - /*const oppositeLines = [] + /!*const oppositeLines = [] baseLines .filter((line) => { //평행한 반대방향 라인인지 확인. @@ -3992,13 +5534,13 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { }) canvas.add(checkLine) canvas.renderAll() - })*/ + })*!/ // innerLines.push(drawRoofLine(roofPoint, canvas, roof, textMode)) }) - } + }*/ - if (linesAnalysis.length > 0) { + /*if (linesAnalysis.length > 0) { linesAnalysis .filter((line) => { const dx = line.end.x - line.start.x @@ -4030,10 +5572,10 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { } } }) - } + }*/ //지붕선에 따라 라인추가 작업 처리. - const innerLinesPoints = [] + /*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) @@ -4083,16 +5625,7 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { } else { innerLines.push(drawRoofLine([line.x1, line.y1, line.x2, line.y2], canvas, roof, textMode)) } - }) - - //지붕 innerLines에 추가. - roof.innerLines = innerLines - - canvas - .getObjects() - .filter((object) => object.name === 'check') - .forEach((object) => canvas.remove(object)) - canvas.renderAll() + })*/ } /** @@ -4110,7 +5643,6 @@ const lineIntersection = (line1Start, line1End, line2Start, line2End) => { // 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) { - console.log('평행') 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 }) @@ -4124,12 +5656,6 @@ const lineIntersection = (line1Start, line1End, line2Start, line2End) => { let ua = (dx2 * (line1Start.y - line2Start.y) - dy2 * (line1Start.x - line2Start.x)) / denominator let ub = (dx1 * (line1Start.y - line2Start.y) - dy1 * (line1Start.x - line2Start.x)) / denominator - // 경계값 보정용 헬퍼 - const clamp01 = (t) => { - if (almostEqual(t, 0, EPSILON)) return 0 - if (almostEqual(t, 1, EPSILON)) return 1 - return t - } ua = clamp01(ua) ub = clamp01(ub) @@ -4142,6 +5668,17 @@ const lineIntersection = (line1Start, line1End, line2Start, line2End) => { 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 From 86d595ef5242f4521e445c0d66fd94314dbf45ea Mon Sep 17 00:00:00 2001 From: Jaeyoung Lee Date: Wed, 17 Dec 2025 17:50:18 +0900 Subject: [PATCH 25/28] =?UTF-8?q?=EC=A3=BC=EC=84=9D=EC=B2=98=EB=A6=AC=20?= =?UTF-8?q?=EC=95=88=EB=90=9C=EA=B3=B3=20=EC=B2=98=EB=A6=AC.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/fabric/QPolygon.js | 3 ++- src/util/qpolygon-utils.js | 10 +++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/components/fabric/QPolygon.js b/src/components/fabric/QPolygon.js index fd7352eb..c87d161f 100644 --- a/src/components/fabric/QPolygon.js +++ b/src/components/fabric/QPolygon.js @@ -6,6 +6,7 @@ import { calculateAngle, drawGableRoof, drawRoofByAttribute, drawShedRoof, toGeo import * as turf from '@turf/turf' import { LINE_TYPE, POLYGON_TYPE } from '@/common/common' import Big from 'big.js' +import { drawSkeletonRidgeRoof } from '@/util/skeleton-utils' export const QPolygon = fabric.util.createClass(fabric.Polygon, { type: 'QPolygon', @@ -340,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('패턴 지붕') diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js index 9eb774e1..3a38edd8 100644 --- a/src/util/qpolygon-utils.js +++ b/src/util/qpolygon-utils.js @@ -4342,11 +4342,11 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { partner = j } } - canvas.remove(checkCircle).renderAll() + // canvas.remove(checkCircle).renderAll() } - canvas.remove(checkNLine).renderAll() + // canvas.remove(checkNLine).renderAll() }) - canvas.remove(checkCLine).renderAll() + // canvas.remove(checkCLine).renderAll() if (intersectPoint) { intersections.push({ index: i, intersect: intersectPoint, linePoint, partner }) } @@ -9617,8 +9617,8 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { return } - canvas.remove(prevGableLine) - canvas.remove(nextGableLine) + // canvas.remove(prevGableLine) + // canvas.remove(nextGableLine) baseHipLines = baseHipLines.filter((base) => base.line !== prevGableLine && base.line !== nextGableLine) const points = [prevGableLine.x1, prevGableLine.y1, nextGableLine.x1, nextGableLine.y1] From f345b0dbbef30bc9293164610c78787d2cb338df Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Thu, 18 Dec 2025 10:12:34 +0900 Subject: [PATCH 26/28] =?UTF-8?q?=EC=B2=98=EB=A7=88/=EC=BC=80=EB=9D=BC?= =?UTF-8?q?=EB=B0=94=20=EB=B3=80=EA=B2=BD=20=ED=83=AD=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=EC=8B=9C=20=EB=B2=84=ED=8A=BC=20=EA=B0=92=20=EC=B4=88=EA=B8=B0?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/roofcover/useEavesGableEdit.js | 1 + 1 file changed, 1 insertion(+) 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) => { From 013fd963b37454eb826b06716530fda8a2dda319 Mon Sep 17 00:00:00 2001 From: Jaeyoung Lee Date: Thu, 18 Dec 2025 11:27:56 +0900 Subject: [PATCH 27/28] =?UTF-8?q?=EB=B3=80=EB=B3=84=EB=A1=9C=204=EA=B0=81?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EB=A7=88=EB=A3=A8=EC=84=A0=20=EA=B7=B8?= =?UTF-8?q?=EB=A0=A4=EC=A7=80=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EA=B2=BD?= =?UTF-8?q?=EC=9A=B0=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util/qpolygon-utils.js | 43 +++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js index 3a38edd8..f8c011f0 100644 --- a/src/util/qpolygon-utils.js +++ b/src/util/qpolygon-utils.js @@ -2957,11 +2957,12 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { ridgePoint.x2 += pointVector.x * offset ridgePoint.y2 += pointVector.y * offset } - } 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 @@ -3398,8 +3399,18 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { default: break } - point[2] += pointVector.x * offset - point[3] += pointVector.y * offset + 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 @@ -4316,8 +4327,8 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { 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() + /*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] @@ -4799,13 +4810,17 @@ export const drawRoofByAttribute = (roofId, canvas, textMode) => { 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), - ) + 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), + ) + } } } } From 03a629b9c8963cfd94ddf0f21047a96f401cfafc Mon Sep 17 00:00:00 2001 From: ysCha Date: Thu, 18 Dec 2025 13:40:08 +0900 Subject: [PATCH 28/28] =?UTF-8?q?=EC=98=A4=EB=B8=8C=EC=A0=9D=ED=8A=B8=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5:=20=EA=B3=84=EC=82=B0=EA=B8=B0=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/management/StuffDetail.jsx | 108 ++++++++++++++++------ 1 file changed, 80 insertions(+), 28 deletions(-) diff --git a/src/components/management/StuffDetail.jsx b/src/components/management/StuffDetail.jsx index e0843b5d..dfcc547c 100644 --- a/src/components/management/StuffDetail.jsx +++ b/src/components/management/StuffDetail.jsx @@ -23,6 +23,7 @@ import { QcastContext } from '@/app/QcastProvider' import { useCanvasMenu } from '@/hooks/common/useCanvasMenu' import { useSwal } from '@/hooks/useSwal' import { sanitizeIntegerInputEvent } from '@/util/input-utils' +import { CalculatorInput } from '@/components/common/input/CalcInput' export default function StuffDetail() { const [stuffSearch, setStuffSearch] = useRecoilState(stuffSearchState) @@ -1204,8 +1205,10 @@ export default function StuffDetail() { } // 저장 - const onValid = async () => { - const formData = form.getValues() + const onValid = async (actionType) => { + const formData = form.getValues(); + if(actionType !== 'save') return false + console.log('Action type:', actionType); // 'save' 또는 'tempSave' let errors = {} let fieldNm @@ -1715,7 +1718,7 @@ export default function StuffDetail() { - - - - - -