import { ANGLE_TYPE, canvasState, currentAngleTypeSelector, fontFamilyState, fontSizeState, pitchTextSelector } from '@/store/canvasAtom' import { useRecoilValue } from 'recoil' import { fabric } from 'fabric' import { findAndRemoveClosestPoint, getDegreeByChon, getDegreeInOrientation, getDirectionByPoint, isPointOnLine } from '@/util/canvas-util' import { QPolygon } from '@/components/fabric/QPolygon' import { isSamePoint, removeDuplicatePolygons } from '@/util/qpolygon-utils' import { flowDisplaySelector } from '@/store/settingAtom' import { fontSelector } from '@/store/fontAtom' import { QLine } from '@/components/fabric/QLine' import { POLYGON_TYPE } from '@/common/common' export const usePolygon = () => { const canvas = useRecoilValue(canvasState) const isFlowDisplay = useRecoilValue(flowDisplaySelector) const flowFontOptions = useRecoilValue(fontSelector('flowText')) const lengthTextFontOptions = useRecoilValue(fontSelector('lengthText')) const currentAngleType = useRecoilValue(currentAngleTypeSelector) const pitchText = useRecoilValue(pitchTextSelector) const addPolygon = (points, options) => { const polygon = new QPolygon(points, { ...options, fontSize: lengthTextFontOptions.fontSize.value, fill: options.fill || 'transparent', stroke: options.stroke || '#000000', selectable: true, }) canvas?.add(polygon) addLengthText(polygon) return polygon } const addPolygonByLines = (lines, options) => { //lines의 idx를 정렬한다. lines.sort((a, b) => a.idx - b.idx) const points = createPolygonPointsFromOuterLines(lines) return addPolygon(points, { ...options, }) } const addLengthText = (polygon) => { const lengthTexts = canvas.getObjects().filter((obj) => obj.name === 'lengthText' && obj.parentId === polygon.id) lengthTexts.forEach((text) => { canvas.remove(text) }) const lines = polygon.lines lines.forEach((line, i) => { const length = line.getLength() const { planeSize, actualSize } = line.attributes const scaleX = line.scaleX const scaleY = line.scaleY const x1 = line.left const y1 = line.top const x2 = line.left + line.width * scaleX const y2 = line.top + line.height * scaleY let left, top if (line.direction === 'left' || line.direction === 'right') { left = (x1 + x2) / 2 top = (y1 + y2) / 2 + 10 } else if (line.direction === 'top' || line.direction === 'bottom') { left = (x1 + x2) / 2 + 10 top = (y1 + y2) / 2 } const minX = line.left const maxX = line.left + line.width const minY = line.top const maxY = line.top + line.length const degree = (Math.atan2(y2 - y1, x2 - x1) * 180) / Math.PI const text = new fabric.Textbox(planeSize ? planeSize.toString() : length.toString(), { left: left, top: top, fontSize: lengthTextFontOptions.fontSize.value, minX, maxX, minY, maxY, parentDirection: line.direction, parentDegree: degree, parentId: polygon.id, planeSize: planeSize ?? length, actualSize: actualSize ?? length, editable: false, selectable: true, lockRotation: true, lockScalingX: true, lockScalingY: true, parent: polygon, name: 'lengthText', }) canvas.add(text) }) /*const points = polygon.get('points') points.forEach((start, i) => { const end = points[(i + 1) % points.length] const dx = end.x - start.x const dy = end.y - start.y const length = Number(Math.sqrt(dx * dx + dy * dy).toFixed(1)) * 10 const midPoint = new fabric.Point((start.x + end.x) / 2, (start.y + end.y) / 2) const degree = (Math.atan2(dy, dx) * 180) / Math.PI // Create new text object if it doesn't exist const text = new fabric.Text(length.toString(), { left: midPoint.x, top: midPoint.y, parentId: polygon.id, fontSize: lengthTextFontOptions.fontSize.value, minX: Math.min(start.x, end.x), maxX: Math.max(start.x, end.x), minY: Math.min(start.y, end.y), maxY: Math.max(start.y, end.y), parentDirection: getDirectionByPoint(start, end), parentDegree: degree, dirty: true, editable: true, selectable: true, lockRotation: true, lockScalingX: true, lockScalingY: true, idx: i, name: 'lengthText', parent: polygon, }) // this.texts.push(text) canvas.add(text) })*/ canvas.renderAll() } const createPolygonPointsFromOuterLines = (outerLines) => { if (!outerLines || outerLines.length === 0) { return [] } // Extract points from outerLines return outerLines.map((line) => ({ x: line.x1, y: line.y1, })) } const removePolygon = (polygon) => { const texts = canvas.getObjects().filter((obj) => obj.parentId === polygon.id) texts.forEach((text) => { canvas.remove(text) }) canvas.remove(polygon) canvas.renderAll() } /** * poolygon의 방향에 따라 화살표를 추가한다. * @param polygon */ const drawDirectionArrow = (polygon) => { const direction = polygon.direction if (!direction) { return } //동일 아이디가 있으면 일단 지우고 다시 그린다 const existArrow = polygon.canvas.getObjects().filter((obj) => obj.name === 'arrow' && obj.parentId === polygon.id) if (existArrow.length > 0) { polygon.canvas.remove(...existArrow) } polygon.canvas .getObjects() .filter((obj) => obj.name === 'flowText' && obj.parentId === polygon.arrow?.id) .forEach((obj) => polygon.canvas.remove(obj)) let arrow = null let points = [] if (polygon.arrow) { polygon.canvas.remove(polygon.arrow) } let centerPoint = { x: polygon.left, y: polygon.top } const { width, height } = polygon let stickeyPoint const polygonMaxX = Math.max(...polygon.getCurrentPoints().map((point) => point.x)) const polygonMinX = Math.min(...polygon.getCurrentPoints().map((point) => point.x)) const polygonMaxY = Math.max(...polygon.getCurrentPoints().map((point) => point.y)) const polygonMinY = Math.min(...polygon.getCurrentPoints().map((point) => point.y)) switch (direction) { case 'north': points = [ { x: centerPoint.x - width / 2, y: polygonMinY - 50 }, { x: centerPoint.x + 20 - width / 2, y: polygonMinY - 50 }, { x: centerPoint.x + 20 - width / 2, y: polygonMinY - 80 }, { x: centerPoint.x + 50 - width / 2, y: polygonMinY - 80 }, { x: centerPoint.x - width / 2, y: polygonMinY - 110 }, { x: centerPoint.x - 50 - width / 2, y: polygonMinY - 80 }, { x: centerPoint.x - 20 - width / 2, y: polygonMinY - 80 }, { x: centerPoint.x - 20 - width / 2, y: polygonMinY - 50 }, ] stickeyPoint = { x: centerPoint.x - width / 2, y: polygonMinY - 110 } break case 'south': points = [ { x: centerPoint.x + width / 2, y: polygonMaxY + 50 }, { x: centerPoint.x + 20 + width / 2, y: polygonMaxY + 50 }, { x: centerPoint.x + 20 + width / 2, y: polygonMaxY + 80 }, { x: centerPoint.x + 50 + width / 2, y: polygonMaxY + 80 }, { x: centerPoint.x + width / 2, y: polygonMaxY + 110 }, { x: centerPoint.x - 50 + width / 2, y: polygonMaxY + 80 }, { x: centerPoint.x - 20 + width / 2, y: polygonMaxY + 80 }, { x: centerPoint.x - 20 + width / 2, y: polygonMaxY + 50 }, ] stickeyPoint = { x: centerPoint.x + width / 2, y: polygonMaxY + 110 } break case 'west': points = [ { x: polygonMinX - 50, y: centerPoint.y - height / 2 }, { x: polygonMinX - 50, y: centerPoint.y + 20 - height / 2 }, { x: polygonMinX - 80, y: centerPoint.y + 20 - height / 2 }, { x: polygonMinX - 80, y: centerPoint.y + 50 - height / 2 }, { x: polygonMinX - 110, y: centerPoint.y - height / 2 }, { x: polygonMinX - 80, y: centerPoint.y - 50 - height / 2 }, { x: polygonMinX - 80, y: centerPoint.y - 20 - height / 2 }, { x: polygonMinX - 50, y: centerPoint.y - 20 - height / 2 }, ] stickeyPoint = { x: polygonMinX - 110, y: centerPoint.y - height / 2 } break case 'east': points = [ { x: polygonMaxX + 50, y: centerPoint.y + height / 2 }, { x: polygonMaxX + 50, y: centerPoint.y + 20 + height / 2 }, { x: polygonMaxX + 80, y: centerPoint.y + 20 + height / 2 }, { x: polygonMaxX + 80, y: centerPoint.y + 50 + height / 2 }, { x: polygonMaxX + 110, y: centerPoint.y + height / 2 }, { x: polygonMaxX + 80, y: centerPoint.y - 50 + height / 2 }, { x: polygonMaxX + 80, y: centerPoint.y - 20 + height / 2 }, { x: polygonMaxX + 50, y: centerPoint.y - 20 + height / 2 }, ] stickeyPoint = { x: polygonMaxX + 110, y: centerPoint.y + height / 2 } break } arrow = new QPolygon(points, { selectable: false, name: 'arrow', fill: 'transparent', stroke: 'black', direction: direction, parent: polygon, stickeyPoint: stickeyPoint, surfaceCompass: polygon.surfaceCompass, moduleCompass: polygon.moduleCompass, visible: isFlowDisplay, pitch: polygon.roofMaterial?.pitch ?? 4, parentId: polygon.id, }) arrow.setViewLengthText(false) polygon.arrow = arrow polygon.canvas.add(arrow) polygon.canvas.renderAll() drawDirectionStringToArrow2(polygon) // drawDirectionStringToArrow() } //arrow의 compass 값으로 방향 글자 설정 필요 const drawDirectionStringToArrow2 = (polygon) => { const { direction, surfaceCompass, moduleCompass, arrow } = polygon if (moduleCompass === null || moduleCompass === undefined) { const textObj = new fabric.Text(`${currentAngleType === ANGLE_TYPE.SLOPE ? arrow.pitch : getDegreeByChon(arrow.pitch)}${pitchText}`, { fontFamily: flowFontOptions.fontFamily.value, fontWeight: flowFontOptions.fontWeight.value.toLowerCase().includes('bold') ? 'bold' : 'normal', fontStyle: flowFontOptions.fontWeight.value.toLowerCase().includes('italic') ? 'italic' : 'normal', fontSize: flowFontOptions.fontSize.value, fill: flowFontOptions.fontColor.value, originX: 'center', originY: 'center', pitch: arrow.pitch, name: 'flowText', selectable: false, left: arrow.stickeyPoint.x, top: arrow.stickeyPoint.y, parent: arrow, parentId: arrow.id, visible: isFlowDisplay, }) polygon.canvas.add(textObj) return } let text = '' const compassType = (375 - getDegreeInOrientation(moduleCompass)) / 15 if ([1, 25].includes(compassType)) { direction === 'north' ? (text = '北') : direction === 'south' ? (text = '南') : direction === 'west' ? (text = '西') : (text = '東') } else if ([2, 3].includes(compassType)) { direction === 'north' ? (text = '北北東') : direction === 'south' ? (text = '南南西') : direction === 'west' ? (text = '西北西') : (text = '東南東') } else if ([4].includes(compassType)) { direction === 'north' ? (text = '北東') : direction === 'south' ? (text = '南西') : direction === 'west' ? (text = '北西') : (text = '南東') } else if ([5, 6].includes(compassType)) { direction === 'north' ? (text = '東北東') : direction === 'south' ? (text = '西南西') : direction === 'west' ? (text = '北北西') : (text = '南南東') } else if ([7].includes(compassType)) { direction === 'north' ? (text = '東') : direction === 'south' ? (text = '西') : direction === 'west' ? (text = '北') : (text = '南') } else if ([8, 9].includes(compassType)) { direction === 'north' ? (text = '東南東') : direction === 'south' ? (text = '西北西') : direction === 'west' ? (text = '北北東') : (text = '南南西') } else if ([10].includes(compassType)) { direction === 'north' ? (text = '南東') : direction === 'south' ? (text = '北西') : direction === 'west' ? (text = '南西') : (text = '北東') } else if ([11, 12].includes(compassType)) { direction === 'north' ? (text = '南南東') : direction === 'south' ? (text = '北北西') : direction === 'west' ? (text = '東北東') : (text = '西南西') } else if ([13].includes(compassType)) { direction === 'north' ? (text = '南') : direction === 'south' ? (text = '北') : direction === 'west' ? (text = '東') : (text = '西') } else if ([14, 15].includes(compassType)) { direction === 'north' ? (text = '南南西') : direction === 'south' ? (text = '北北東') : direction === 'west' ? (text = '東南東') : (text = '西北西') } else if ([16].includes(compassType)) { direction === 'north' ? (text = '南西') : direction === 'south' ? (text = '北東') : direction === 'west' ? (text = '南東') : (text = '北西') } else if ([17, 18].includes(compassType)) { direction === 'north' ? (text = '西南西') : direction === 'south' ? (text = '東北東') : direction === 'west' ? (text = '南南東') : (text = '北北西') } else if ([19].includes(compassType)) { direction === 'north' ? (text = '西') : direction === 'south' ? (text = '東') : direction === 'west' ? (text = '南') : (text = '北') } else if ([20, 21].includes(compassType)) { direction === 'north' ? (text = '西北西') : direction === 'south' ? (text = '東南東') : direction === 'west' ? (text = '南南西') : (text = '北北東') } else if ([22].includes(compassType)) { direction === 'north' ? (text = '北西') : direction === 'south' ? (text = '南東') : direction === 'west' ? (text = '南西') : (text = '北東') } else if ([23, 24].includes(compassType)) { direction === 'north' ? (text = '北北西') : direction === 'south' ? (text = '南南東') : direction === 'west' ? (text = '西南西') : (text = '東北東') } // 東,西,南,北 if ([360].includes(surfaceCompass)) { text = '南' } else if ([345, 330].includes(surfaceCompass)) { text = '南南東' } else if ([315].includes(surfaceCompass)) { text = '南東' } else if ([300, 285].includes(surfaceCompass)) { text = '東南東' } else if ([270].includes(surfaceCompass)) { text = '東' } else if ([255, 240].includes(surfaceCompass)) { text = '東北東' } else if ([225].includes(surfaceCompass)) { text = '北東' } else if ([210, 195].includes(surfaceCompass)) { text = '北北東' } else if ([180].includes(surfaceCompass)) { text = '北' } else if ([165, 150].includes(surfaceCompass)) { text = '北北西' } else if ([135].includes(surfaceCompass)) { text = '北西' } else if ([120, 105].includes(surfaceCompass)) { text = '西北西' } else if ([90].includes(surfaceCompass)) { text = '西' } else if ([75, 60].includes(surfaceCompass)) { text = '西南西' } else if ([45].includes(surfaceCompass)) { text = '西南' } else if ([30, 15].includes(surfaceCompass)) { text = '西西南' } const sameDirectionCnt = canvas.getObjects().filter((obj) => { const onlyStrDirection = obj.directionText?.replace(/[0-9]/g, '') return obj.name === POLYGON_TYPE.ROOF && obj !== polygon && onlyStrDirection === text }) text = text + (sameDirectionCnt.length + 1) polygon.set('directionText', text) const textObj = new fabric.Text(`${text} (${currentAngleType === ANGLE_TYPE.SLOPE ? arrow.pitch : getDegreeByChon(arrow.pitch)}${pitchText})`, { fontFamily: flowFontOptions.fontFamily.value, fontWeight: flowFontOptions.fontWeight.value.toLowerCase().includes('bold') ? 'bold' : 'normal', fontStyle: flowFontOptions.fontWeight.value.toLowerCase().includes('italic') ? 'italic' : 'normal', fontSize: flowFontOptions.fontSize.value, fill: flowFontOptions.fontColor.value, pitch: arrow.pitch, originX: 'center', originY: 'center', name: 'flowText', originText: text, selectable: false, left: arrow.stickeyPoint.x, top: arrow.stickeyPoint.y, parent: arrow, parentId: arrow.id, visible: isFlowDisplay, }) polygon.canvas.add(textObj) } /** * 방향을 나타낸 화살표에 각도에 따라 글씨 추가 * @param canvas * @param compass */ const drawDirectionStringToArrow = (canvas, compass = 0) => { const arrows = canvas?.getObjects().filter((obj) => obj.name === 'arrow') if (arrows.length === 0) { return } const eastArrows = arrows.filter((arrow) => arrow.direction === 'east') const westArrows = arrows.filter((arrow) => arrow.direction === 'west') const northArrows = arrows.filter((arrow) => arrow.direction === 'north') const southArrows = arrows.filter((arrow) => arrow.direction === 'south') let southText = '南' let eastText = '東' let westText = '西' let northText = '北' if (compass === 0 || compass === 360) { // 남,동,서 가능 // 그대로 } else if (compass < 45) { //남(남남동),동(동북동),서(서남서) 가능 //북(북북서) southText = '南南東' eastText = '東北東' westText = '西南西' northText = '北北西' } else if (compass === 45) { // 남, 서 가능 // 남(남동) // 서(남서) // 북(북서) // 동(북동) southText = '南東' westText = '南西' northText = '北西' eastText = '北東' } else if (compass < 90) { // 북(서북서) // 동 (북북동) // 남(동남동) // 서(남남서) northText = '北西北' eastText = '北北東' southText = '東南東' westText = '南南西' } else if (compass === 90) { // 동(북) // 서(남) // 남(동) // 북(서) eastText = '北' westText = '南' southText = '東' northText = '西' } else if (compass < 135) { // 남,서,북 가능 // 동(북북서) // 서(남남동) // 남(동북동) // 북(서남서) eastText = '北北西' westText = '南南東' southText = '東北東' northText = '西南西' } else if (compass === 135) { // 서,북 가능 // 서(남동) // 북(남서) // 남(북동) // 동(북서) westText = '南東' northText = '南西' southText = '北東' eastText = '北西' } else if (compass < 180) { // 북,동,서 가능 // 북(남남서) // 동(서북서) // 남(북북동) // 서(동남동) northText = '南南西' eastText = '西北西' southText = '北北東' westText = '東南東' } else if (compass === 180) { // 북,동,서 가능 // 북(남) // 동(서) // 남(북) // 서(동) northText = '南' eastText = '西' southText = '北' westText = '東' } else if (compass < 225) { // 서,북,동 가능 // 북(남남동) // 동(서남서) // 남(북북서) // 서(동남동) northText = '南南東' eastText = '西南西' southText = '北北西' westText = '東南東' } else if (compass === 225) { // 북,동 가능 // 북(남동) // 동(남서) // 남(북서) // 서(북동) northText = '南東' eastText = '南西' southText = '北西' westText = '北東' } else if (compass < 270) { // 북동남 가능 // 북(동남동) // 동(남남서) // 남(서북서) // 서(북북동) northText = '東南東' eastText = '南南西' southText = '西北西' westText = '北北東' } else if (compass === 270) { // 북동남 가능 // 북(동) // 동(남) // 남(서) // 서(북) northText = '東' eastText = '南' southText = '西' westText = '北' } else if (compass < 315) { // 북,동,남 가능 // 북(동북동) // 동(남남동) // 남(서남서) // 서(북북서) northText = '東北東' eastText = '南南東' southText = '西南西' westText = '北北西' } else if (compass === 315) { // 동,남 가능 // 북(북동) // 동(남동) // 남(남서) // 서(북서) northText = '北東' eastText = '南東' southText = '南西' westText = '北西' } else if (compass < 360) { // 남,동,서 가능 // 북(북북동) // 동(동남동) // 남(남남서) // 서(서북서) northText = '北北東' eastText = '東南東' southText = '南南西' westText = '西北西' } clearFlowText(canvas) addTextByArrows(eastArrows, eastText, canvas) addTextByArrows(westArrows, westText, canvas) addTextByArrows(northArrows, northText, canvas) addTextByArrows(southArrows, southText, canvas) } const clearFlowText = (canvas) => { const texts = canvas.getObjects().filter((obj) => obj.name === 'flowText') texts.forEach((text) => { canvas.remove(text) }) } const addTextByArrows = (arrows, txt, canvas) => { arrows.forEach((arrow, index) => { // const textStr = `${txt}${index + 1} (${currentAngleType === ANGLE_TYPE.SLOPE ? arrow.pitch : getDegreeByChon(arrow.pitch)}${pitchText})` const textStr = `${txt} (${currentAngleType === ANGLE_TYPE.SLOPE ? arrow.pitch : getDegreeByChon(arrow.pitch)}${pitchText})` const text = new fabric.Text(`${textStr}`, { fontFamily: flowFontOptions.fontFamily.value, fontWeight: flowFontOptions.fontWeight.value.toLowerCase().includes('bold') ? 'bold' : 'normal', fontStyle: flowFontOptions.fontWeight.value.toLowerCase().includes('italic') ? 'italic' : 'normal', fontSize: flowFontOptions.fontSize.value, fill: flowFontOptions.fontColor.value, pitch: arrow.pitch, originX: 'center', originY: 'center', name: 'flowText', originText: `${txt}${index + 1}`, selectable: false, left: arrow.stickeyPoint.x, top: arrow.stickeyPoint.y, parent: arrow, parentId: arrow.id, visible: isFlowDisplay, }) canvas.add(text) }) } const splitPolygonWithLines = (polygon) => { polygon.set({ visible: false }) let innerLines = [...polygon.innerLines] canvas.renderAll() let polygonLines = [...polygon.lines] const roofs = [] //polygonLines를 순회하며 innerLines와 교차하는 점을 line의 속성에 배열로 저장한다. polygonLines.forEach((line) => { let startPoint // 시작점 let endPoint // 끝점 if (line.x1 < line.x2) { startPoint = { x: line.x1, y: line.y1 } endPoint = { x: line.x2, y: line.y2 } } else if (line.x1 > line.x2) { startPoint = { x: line.x2, y: line.y2 } endPoint = { x: line.x1, y: line.y1 } } else { if (line.y1 < line.y2) { startPoint = { x: line.x1, y: line.y1 } endPoint = { x: line.x2, y: line.y2 } } else { startPoint = { x: line.x2, y: line.y2 } endPoint = { x: line.x1, y: line.y1 } } } line.startPoint = startPoint line.endPoint = endPoint }) innerLines.forEach((line) => { let startPoint // 시작점 let endPoint // 끝점 if (line.x1 < line.x2) { startPoint = { x: line.x1, y: line.y1 } endPoint = { x: line.x2, y: line.y2 } } else if (line.x1 > line.x2) { startPoint = { x: line.x2, y: line.y2 } endPoint = { x: line.x1, y: line.y1 } } else { if (line.y1 < line.y2) { startPoint = { x: line.x1, y: line.y1 } endPoint = { x: line.x2, y: line.y2 } } else { startPoint = { x: line.x2, y: line.y2 } endPoint = { x: line.x1, y: line.y1 } } } line.startPoint = startPoint line.endPoint = endPoint }) polygonLines.forEach((line) => { line.set({ strokeWidth: 10 }) canvas.add(line) }) canvas.renderAll() polygonLines.forEach((line) => { const intersections = [] innerLines.forEach((innerLine) => { if (isPointOnLine(line, innerLine.startPoint)) { if (isSamePoint(line.startPoint, innerLine.startPoint) || isSamePoint(line.endPoint, innerLine.startPoint)) { return } intersections.push(innerLine.startPoint) } if (isPointOnLine(line, innerLine.endPoint)) { if (isSamePoint(line.startPoint, innerLine.endPoint) || isSamePoint(line.endPoint, innerLine.endPoint)) { return } intersections.push(innerLine.endPoint) } }) line.set({ intersections }) }) const divideLines = polygonLines.filter((line) => line.intersections.length > 0) let newLines = [] divideLines.forEach((line) => { const { intersections, startPoint, endPoint } = line if (intersections.length === 1) { // 한 점만 교차하는 경우 const newLinePoint1 = [line.x1, line.y1, intersections[0].x, intersections[0].y] const newLinePoint2 = [intersections[0].x, intersections[0].y, line.x2, line.y2] const newLine1 = new QLine(newLinePoint1, { stroke: 'blue', strokeWidth: 3, fontSize: polygon.fontSize, attributes: line.attributes, }) const newLine2 = new QLine(newLinePoint2, { stroke: 'blue', strokeWidth: 3, fontSize: polygon.fontSize, attributes: line.attributes, }) newLine1.attributes = { ...line.attributes, planeSize: Math.round(Math.sqrt(Math.pow(newLine1.x1 - newLine1.x2, 2) + Math.pow(newLine1.y1 - newLine1.y2, 2))) * 10, actualSize: Math.round(Math.sqrt(Math.pow(newLine1.x1 - newLine1.x2, 2) + Math.pow(newLine1.y1 - newLine1.y2, 2))) * 10, } newLine2.attributes = { ...line.attributes, planeSize: Math.round(Math.sqrt(Math.pow(newLine2.x1 - newLine2.x2, 2) + Math.pow(newLine2.y1 - newLine2.y2, 2))) * 10, actualSize: Math.round(Math.sqrt(Math.pow(newLine2.x1 - newLine2.x2, 2) + Math.pow(newLine2.y1 - newLine2.y2, 2))) * 10, } newLines.push(newLine1) newLines.push(newLine2) } else { // 두 점 이상 교차하는 경우 //1. intersections중에 startPoint와 가장 가까운 점을 찾는다. //2. 가장 가까운 점을 기준으로 line을 나눈다. let currentPoint = startPoint while (intersections.length !== 0) { const minDistancePoint = findAndRemoveClosestPoint(currentPoint, intersections) const newLinePoint = [currentPoint.x, currentPoint.y, minDistancePoint.x, minDistancePoint.y] const newLine = new QLine(newLinePoint, { stroke: 'blue', strokeWidth: 3, fontSize: polygon.fontSize, attributes: line.attributes, }) newLine.attributes = { ...line.attributes, planeSize: Math.round(Math.sqrt(Math.pow(newLine.x1 - newLine.x2, 2) + Math.pow(newLine.y1 - newLine.y2, 2))) * 10, actualSize: Math.round(Math.sqrt(Math.pow(newLine.x1 - newLine.x2, 2) + Math.pow(newLine.y1 - newLine.y2, 2))) * 10, } newLines.push(newLine) currentPoint = minDistancePoint } const newLinePoint = [currentPoint.x, currentPoint.y, endPoint.x, endPoint.y] const newLine = new QLine(newLinePoint, { stroke: 'blue', strokeWidth: 3, fontSize: polygon.fontSize, attributes: line.attributes, }) newLine.attributes = { ...line.attributes, planeSize: Math.round(Math.sqrt(Math.pow(newLine.x1 - newLine.x2, 2) + Math.pow(newLine.y1 - newLine.y2, 2))) * 10, actualSize: Math.round(Math.sqrt(Math.pow(newLine.x1 - newLine.x2, 2) + Math.pow(newLine.y1 - newLine.y2, 2))) * 10, } newLines.push(newLine) } }) //polygonLines에서 divideLines를 제거하고 newLines를 추가한다. polygonLines = polygonLines.filter((line) => !divideLines.includes(line)) polygonLines = [...polygonLines, ...newLines] const allLines = [...polygonLines, ...innerLines] /** * 왼쪽 상단을 startPoint로 전부 변경 */ allLines.forEach((line) => { let startPoint // 시작점 let endPoint // 끝점 if (line.x1 < line.x2) { startPoint = { x: line.x1, y: line.y1 } endPoint = { x: line.x2, y: line.y2 } } else if (line.x1 > line.x2) { startPoint = { x: line.x2, y: line.y2 } endPoint = { x: line.x1, y: line.y1 } } else { if (line.y1 < line.y2) { startPoint = { x: line.x1, y: line.y1 } endPoint = { x: line.x2, y: line.y2 } } else { startPoint = { x: line.x2, y: line.y2 } endPoint = { x: line.x1, y: line.y1 } } } line.startPoint = startPoint line.endPoint = endPoint }) // polygon line에서 각각 출발한다. polygonLines.forEach((line) => { /*line.set({ strokeWidth: 5, stroke: 'green' }) canvas.add(line) canvas.renderAll()*/ const startPoint = line.startPoint // 시작점 let arrivalPoint = line.endPoint // 도착점 let currentPoint = startPoint const roofPoints = [startPoint] const startLine = line const visitPoints = [startPoint] const visitLines = [startLine] let notVisitedLines = [] let cnt = 0 while (!isSamePoint(currentPoint, arrivalPoint)) { //현재 점으로 부터 갈 수 있는 다른 라인을 찾는다. let nextLines = allLines.filter( (line2) => (isSamePoint(line2.startPoint, currentPoint) || isSamePoint(line2.endPoint, currentPoint)) && line !== line2 && innerLines.includes(line2) && !visitLines.includes(line2), ) if (nextLines.length === 0) { nextLines = allLines.filter( (line2) => (isSamePoint(line2.startPoint, currentPoint) || isSamePoint(line2.endPoint, currentPoint)) && line !== line2 && !visitLines.includes(line2), ) } if (nextLines.length === 0) { //아직 안갔던 line중 0번째를 선택한다. if (notVisitedLines.length === 0) { break } else { // nextLines = [...notVisitedLines.shift().line] } } let comparisonPoints = [] nextLines.forEach((nextLine) => { if (isSamePoint(nextLine.startPoint, currentPoint)) { comparisonPoints.push(nextLine.endPoint) } else { comparisonPoints.push(nextLine.startPoint) } }) comparisonPoints = comparisonPoints.filter((point) => !visitPoints.some((visitPoint) => isSamePoint(visitPoint, point))) comparisonPoints = comparisonPoints.filter((point) => !isSamePoint(point, currentPoint)) const minDistancePoint = comparisonPoints.reduce((prev, current) => { const prevDistance = Math.sqrt(Math.pow(prev.x - arrivalPoint.x, 2) + Math.pow(prev.y - arrivalPoint.y, 2)) const currentDistance = Math.sqrt(Math.pow(current.x - arrivalPoint.x, 2) + Math.pow(current.y - arrivalPoint.y, 2)) return prevDistance < currentDistance ? prev : current }, comparisonPoints[0]) nextLines.forEach((nextLine) => { if (isSamePoint(nextLine.startPoint, minDistancePoint) || isSamePoint(nextLine.endPoint, minDistancePoint)) { visitLines.push(nextLine) } else { notVisitedLines.push({ line: nextLine, endPoint: nextLine.endPoint, startPoint: nextLine.startPoint, currentPoint: { ...currentPoint }, roofPoints: [...roofPoints], }) } }) currentPoint = { ...minDistancePoint } roofPoints.push(currentPoint) cnt++ if (cnt > 100) { break } } roofs.push(roofPoints) canvas.remove(line) canvas.renderAll() }) const newRoofs = removeDuplicatePolygons(roofs.filter((roof) => roof.length < 100)) newRoofs.forEach((roofPoint, index) => { let defense, pitch let representLines = [] let representLine // 지붕을 그리면서 기존 polygon의 line중 연결된 line을 찾는다. polygonLines.forEach((line) => { let startFlag = false let endFlag = false const startPoint = line.startPoint const endPoint = line.endPoint roofPoint.forEach((point, index) => { if (isSamePoint(point, startPoint)) { startFlag = true } if (isSamePoint(point, endPoint)) { endFlag = true } }) if (startFlag && endFlag) { if (!representLines.includes(line)) { representLines.push(line) } } }) // representLines중 가장 긴 line을 찾는다. representLines.forEach((line) => { if (!representLine) { representLine = line } else { if (representLine.length < line.length) { representLine = line } } }) const direction = polygon.direction ?? representLine.direction const polygonDirection = polygon.direction switch (direction) { case 'top': defense = 'east' break case 'right': defense = 'south' break case 'bottom': defense = 'west' break case 'left': defense = 'north' break } pitch = polygon.lines[index].attributes?.pitch ?? 0 const roof = new QPolygon(roofPoint, { fontSize: polygon.fontSize, stroke: 'black', fill: 'transparent', strokeWidth: 3, name: POLYGON_TYPE.ROOF, originX: 'center', originY: 'center', selectable: true, defense: defense, direction: polygonDirection ?? defense, pitch: pitch, }) //allLines중 생성된 roof와 관련있는 line을 찾는다. roof.lines = [...polygonLines, ...polygon.innerLines].filter((line) => { let startFlag = false let endFlag = false const startPoint = line.startPoint const endPoint = line.endPoint roofPoint.forEach((point, index) => { if (isSamePoint(point, startPoint)) { startFlag = true } if (isSamePoint(point, endPoint)) { endFlag = true } }) return startFlag && endFlag }) canvas.add(roof) addLengthText(roof) canvas.remove(polygon) canvas.renderAll() }) } const splitPolygonWithSeparate = (separates) => { separates.forEach((separate) => { const points = separate.lines.map((line) => { return { x: line.x1, y: line.y1 } }) let defense = '' switch (separate.attributes.direction) { case 'top': defense = 'east' break case 'right': defense = 'south' break case 'bottom': defense = 'west' break case 'left': defense = 'north' break } const roof = new QPolygon(points, { fontSize: separate.fontSize, stroke: 'black', fill: 'transparent', strokeWidth: 3, name: POLYGON_TYPE.ROOF, originX: 'center', originY: 'center', selectable: true, defense: defense, pitch: separate.attributes.pitch, direction: defense, }) canvas.add(roof) }) canvas.renderAll() } return { addPolygon, addPolygonByLines, removePolygon, drawDirectionArrow, addLengthText, splitPolygonWithLines, splitPolygonWithSeparate, } }