import { useCallback, useEffect, useRef, useState } from 'react' import { calculateIntersection, distanceBetweenPoints, findClosestPoint, getCenterPoint, getClosestHorizontalLine, getClosestVerticalLine, getDirection, getStartIndex, rearrangeArray, } from '@/util/canvas-util' import { useRecoilState, useRecoilValue } from 'recoil' import { canvasSizeState, compassState, drewRoofCellsState, fontSizeState, modeState, roofPolygonArrayState, roofPolygonPatternArrayState, roofState, sortedPolygonArray, templateTypeState, wallState, guideLineState, horiGuideLinesState, vertGuideLinesState, } from '@/store/canvasAtom' import { QLine } from '@/components/fabric/QLine' import { fabric } from 'fabric' import { QPolygon } from '@/components/fabric/QPolygon' import offsetPolygon from '@/util/qpolygon-utils' import { isObjectNotEmpty } from '@/util/common-utils' import * as turf from '@turf/turf' import { Mode } from '@/common/common' export function useMode() { const [mode, setMode] = useRecoilState(modeState) const points = useRef([]) const historyPoints = useRef([]) const historyLines = useRef([]) const startPoint = useRef() const [canvas, setCanvas] = useState(null) const [zoom, setZoom] = useState(100) const [fontSize] = useRecoilState(fontSizeState) const [sortedArray, setSortedArray] = useRecoilState(sortedPolygonArray) const [roof, setRoof] = useRecoilState(roofState) const [wall, setWall] = useRecoilState(wallState) const [endPoint, setEndPoint] = useState(null) const pointCount = useRef(0) const [roofPolygonPattern, setRoofPolygonPattern] = useRecoilState(roofPolygonPatternArrayState) const [roofPolygonArray, setRoofPolygonArray] = useRecoilState(roofPolygonArrayState) const [templateType, setTemplateType] = useRecoilState(templateTypeState) const [canvasSize] = useRecoilState(canvasSizeState) const [selectedCellRoofArray, setSelectedCellRoofArray] = useState([]) const [drewRoofCells, setDrewRoofCells] = useRecoilState(drewRoofCellsState) const [roofStyle, setRoofStyle] = useState(1) //기본 지붕 패턴 const [templateCenterLine, setTemplateCenterLine] = useState([]) const compass = useRecoilValue(compassState) const [isCellCenter, setIsCellCenter] = useState(false) const [guideLineInfo, setGuideLineInfo] = useRecoilState(guideLineState) const [guideLineMode, setGuideLineMode] = useState(false) const [guideDotMode, setGuideDotMode] = useState(false) const [horiGuideLines, setHoriGuideLines] = useRecoilState(horiGuideLinesState) const [vertGuideLines, setVertGuideLines] = useRecoilState(vertGuideLinesState) useEffect(() => { // if (!canvas) { // canvas?.setZoom(0.8) // return // } if (!canvas) return setCanvas(canvas) canvas?.off('mouse:out', removeMouseLines) canvas?.on('mouse:out', removeMouseLines) canvas?.off('mouse:move') canvas?.on('mouse:move', drawMouseLines) }, [canvas, zoom]) // 빈 배열을 전달하여 컴포넌트가 마운트될 때만 실행되도록 함 useEffect(() => { if (canvas?.getObjects().find((obj) => obj.name === 'connectLine')) { canvas?.remove(canvas?.getObjects().find((obj) => obj.name === 'connectLine')) } canvas?.off('mouse:move', (e) => addLineEndPointToMousePoint(e, endPoint)) canvas?.off('mouse:move') canvas?.on('mouse:move', drawMouseLines) canvas?.off('mouse:out', removeMouseLines) canvas?.on('mouse:out', removeMouseLines) if (!endPoint) { return } canvas?.on('mouse:move', (e) => addLineEndPointToMousePoint(e, endPoint)) }, [endPoint]) useEffect(() => { canvas?.off('mouse:out', removeMouseLines) canvas?.on('mouse:out', removeMouseLines) changeMode(canvas, mode) canvas?.off('mouse:move') canvas?.on('mouse:move', drawMouseLines) }, [mode, horiGuideLines, vertGuideLines]) useEffect(() => { setGuideLineMode(false) setGuideDotMode(false) if (isObjectNotEmpty(guideLineInfo)) { const guideLineState = guideLineInfo.filter((item) => item.guideMode === 'guideLine') const guideDotState = guideLineInfo.filter((item) => item.guideMode === 'guideDot') setGuideLineMode(guideLineState.length > 0) setGuideDotMode(guideDotState.length > 0) } }, [guideLineInfo]) // 마우스 보조선 가로선, 세로선 그리기 const drawMouseLines = (e) => { let isGuideLineMode = false, isGuideDotMode = false let guideDotLength, guideLineLengthHori, guideLineLengthVert, horizontalLineArray, verticalLineArray if (isObjectNotEmpty(guideLineInfo)) { const guideLineState = guideLineInfo.filter((item) => item.guideMode === 'guideLine') const guideDotState = guideLineInfo.filter((item) => item.guideMode === 'guideDot') setGuideLineMode(guideLineState.length > 0) setGuideDotMode(guideDotState.length > 0) isGuideLineMode = guideLineState.length > 0 isGuideDotMode = guideDotState.length > 0 if (isGuideDotMode) { guideLineLengthHori = Number(guideDotState[0].moduleHoriLength) guideLineLengthVert = Number(guideDotState[0].moduleVertLength) } if (isGuideLineMode) { horizontalLineArray = [...horiGuideLines] verticalLineArray = [...vertGuideLines] guideLineLengthHori = Number(guideLineState[0].moduleHoriLength) guideLineLengthVert = Number(guideLineState[0].moduleVertLength) } } // 현재 마우스 포인터의 위치를 가져옵니다. const pointer = canvas?.getPointer(e.e) // 기존에 그려진 가이드라인을 제거합니다. removeMouseLines() let newX = pointer.x let newY = pointer.y //흡착점 있는지 확인 const adsorptionPointList = canvas?._objects.filter((obj) => obj.name === 'adsorptionPoint') if (mode === Mode.EDIT || mode === Mode.ADSORPTION_POINT) { let adsorptionPoint = adsorptionPointList.length > 0 ? findClosestPoint(pointer, adsorptionPointList) : null if ((horiGuideLines.length > 0 || vertGuideLines.length > 0) && guideDotMode) { const closestHorizontalLine = getClosestHorizontalLine(pointer, horiGuideLines) const closetVerticalLine = getClosestVerticalLine(pointer, vertGuideLines) let intersection = null let intersectionDistance = Infinity if (closestHorizontalLine && closetVerticalLine) { intersection = calculateIntersection(closestHorizontalLine, closetVerticalLine) if (intersection) { intersectionDistance = distanceBetweenPoints(pointer, intersection) } } let xDiff, yDiff if (closetVerticalLine) { xDiff = Math.abs(pointer.x - closetVerticalLine.x1) } if (closestHorizontalLine) { yDiff = Math.abs(pointer.y - closestHorizontalLine.y1) } const x = pointer.x - guideLineLengthHori * Math.floor(pointer.x / guideLineLengthHori) const y = pointer.y - guideLineLengthVert * Math.floor(pointer.y / guideLineLengthVert) const xRate = x / guideLineLengthHori const yRate = y / guideLineLengthVert const isAttachX = xRate >= 0.4 && xRate <= 0.7 const isAttachY = yRate >= 0.4 && yRate <= 0.7 if (isAttachX && isAttachY) { newX = Math.floor(pointer.x / guideLineLengthHori) * guideLineLengthHori + guideLineLengthHori / 2 newY = Math.floor(pointer.y / guideLineLengthVert) * guideLineLengthVert + guideLineLengthVert / 2 } else { if (intersection && intersectionDistance < 20) { newX = intersection.x newY = intersection.y } else { if (Math.min(xDiff, yDiff) <= 20) { if (xDiff < yDiff) { newX = closetVerticalLine.x1 newY = pointer.y } else { newX = pointer.x newY = closestHorizontalLine.y1 } } } } } else if (guideDotMode) { const x = pointer.x - guideLineLengthHori * Math.floor(pointer.x / guideLineLengthHori) const y = pointer.y - guideLineLengthVert * Math.floor(pointer.y / guideLineLengthVert) const xRate = x / guideLineLengthHori const yRate = y / guideLineLengthVert const isAttachX = xRate >= 0.4 && xRate <= 0.7 const isAttachY = yRate >= 0.4 && yRate <= 0.7 if (isAttachX && isAttachY) { newX = Math.floor(pointer.x / guideLineLengthHori) * guideLineLengthHori + guideLineLengthHori / 2 newY = Math.floor(pointer.y / guideLineLengthVert) * guideLineLengthVert + guideLineLengthVert / 2 } } else if (horiGuideLines.length > 0 || vertGuideLines.length > 0) { const closestHorizontalLine = getClosestHorizontalLine(pointer, horiGuideLines) const closetVerticalLine = getClosestVerticalLine(pointer, vertGuideLines) let intersection = null let intersectionDistance = Infinity if (closestHorizontalLine && closetVerticalLine) { intersection = calculateIntersection(closestHorizontalLine, closetVerticalLine) if (intersection) { intersectionDistance = distanceBetweenPoints(pointer, intersection) } } let xDiff, yDiff if (closetVerticalLine) { xDiff = Math.abs(pointer.x - closetVerticalLine.x1) } if (closestHorizontalLine) { yDiff = Math.abs(pointer.y - closestHorizontalLine.y1) } const x = pointer.x - guideLineLengthHori * Math.floor(pointer.x / guideLineLengthHori) const y = pointer.y - guideLineLengthVert * Math.floor(pointer.y / guideLineLengthVert) const xRate = x / guideLineLengthHori const yRate = y / guideLineLengthVert const isAttachX = xRate >= 0.4 && xRate <= 0.7 const isAttachY = yRate >= 0.4 && yRate <= 0.7 if (isAttachX && isAttachY) { newX = Math.floor(pointer.x / guideLineLengthHori) * guideLineLengthHori + guideLineLengthHori / 2 newY = Math.floor(pointer.y / guideLineLengthVert) * guideLineLengthVert + guideLineLengthVert / 2 } else { if (intersection && intersectionDistance < 20) { newX = intersection.x newY = intersection.y } else { if (Math.min(xDiff, yDiff) <= 20) { if (xDiff < yDiff) { newX = closetVerticalLine.x1 newY = pointer.y } else { newX = pointer.x newY = closestHorizontalLine.y1 } } } } } if (adsorptionPoint && distanceBetweenPoints(pointer, adsorptionPoint) < 20) { newX = adsorptionPoint.left newY = adsorptionPoint.top } } // 가로선을 그립니다. const horizontalLine = new fabric.Line([0, newY, 2 * canvas.width, newY], { stroke: 'red', strokeWidth: 1, selectable: false, name: 'mouseLine', }) // 세로선을 그립니다. const verticalLine = new fabric.Line([newX, 0, newX, 2 * canvas.height], { stroke: 'red', strokeWidth: 1, selectable: false, name: 'mouseLine', }) // 선들을 캔버스에 추가합니다. canvas?.add(horizontalLine, verticalLine) // 캔버스를 다시 그립니다. canvas?.renderAll() } useEffect(() => { if (pointCount.current <= 2) { removeGuideLines() return } drawGuideLines() }, [pointCount.current]) const removeGuideLines = () => { const guideLines = canvas?._objects.filter((obj) => obj.name === 'helpGuideLine') guideLines?.forEach((item) => canvas?.remove(item)) } const drawGuideLines = () => { // 이름이 guideLine인 가이드라인을 제거합니다. removeGuideLines() const arrivalX = startPoint.current?.left const arrivalY = startPoint.current?.top const lastX = endPoint?.left const lastY = endPoint?.top if (lastX === arrivalX || lastY === arrivalY) { // 둘중 하나라도 같으면 guideLine은 한개만 생성 const guideLine = new QLine([lastX, lastY, arrivalX, arrivalY], { fontSize: fontSize, stroke: 'black', strokeWidth: 1, strokeDashArray: [1, 1, 1], }) guideLine.name = 'helpGuideLine' canvas?.add(guideLine) } else { const guideLine1 = new QLine([lastX, lastY, lastX, arrivalY], { fontSize: fontSize, stroke: 'black', strokeWidth: 1, strokeDashArray: [1, 1, 1], }) const guideLine2 = new QLine([guideLine1.x2, guideLine1.y2, arrivalX, arrivalY], { fontSize: fontSize, stroke: 'black', strokeWidth: 1, strokeDashArray: [1, 1, 1], }) guideLine1.name = 'helpGuideLine' guideLine2.name = 'helpGuideLine' canvas?.add(guideLine1) canvas?.add(guideLine2) } } /** * 마우스 포인터의 가이드라인을 제거합니다. */ const removeMouseLines = () => { if (canvas?._objects.length > 0) { const mouseLines = canvas?._objects.filter((obj) => obj.name === 'mouseLine') mouseLines.forEach((item) => canvas?.remove(item)) } canvas?.renderAll() } const addLineEndPointToMousePoint = (e, endPoint) => { if (canvas?.getObjects().find((obj) => obj.name === 'connectLine')) { canvas?.remove(canvas?.getObjects().find((obj) => obj.name === 'connectLine')) } if (!endPoint) { return } const pointer = canvas?.getPointer(e.e) let newX, newY newX = pointer.x newY = pointer.y // 마우스 포인터 위치랑 endPoint를 연결하는 line 생성 const line = new fabric.Line([endPoint.left, endPoint.top, newX, newY], { stroke: 'black', strokeWidth: 1, selectable: false, }) line.set({ name: 'connectLine' }) canvas?.add(line) canvas?.renderAll() } // 모드에 따른 마우스 이벤트 변경 const changeMouseEvent = (mode) => { switch (mode) { case 'drawLine': canvas?.on('mouse:down', mouseEvent.drawLineModeLeftClick) document.addEventListener('contextmenu', mouseEvent.drawLineModeRightClick) break case 'edit': canvas?.on('mouse:down', mouseEvent.editMode) break case 'textbox': canvas?.on('mouse:down', mouseEvent.textboxMode) break case 'drawRect': canvas?.on('mouse:down', mouseEvent.drawRectMode) break case 'drawHelpLine': canvas?.off('selection:created', addSelectCreatedEvent) canvas?.off('selection:cleared', addSelectClearedEvent) canvas?.on('selection:created', addSelectCreatedEvent) canvas?.on('selection:cleared', addSelectClearedEvent) break case 'adsorptionPoint': canvas?.on('mouse:down', mouseEvent.adsorptionPoint) break case 'default': canvas?.off('mouse:down') break } } const keyValid = () => { if (points.current.length === 0) { alert('시작점을 선택해주세요') return false } return true } const drawCircleAndLine = (verticalLength, horizontalLength) => { const circle = new fabric.Circle({ radius: 5, fill: 'transparent', // 원 안을 비웁니다. stroke: 'black', // 원 테두리 색상을 검은색으로 설정합니다. left: points.current[points.current.length - 1].left + horizontalLength - 5, top: points.current[points.current.length - 1].top + verticalLength - 5, originX: 'center', originY: 'center', selectable: false, }) historyPoints.current.push(circle) points.current.push(circle) canvas?.add(circle) canvas?.renderAll() // length 값이 숫자가 아닌 경우 if (isNaN(length) || Math.max(Math.abs(verticalLength), Math.abs(horizontalLength)) === 0) { //마지막 추가 된 points 제거합니다. const lastPoint = historyPoints.current[historyPoints.current.length - 1] canvas?.remove(lastPoint) historyPoints.current.pop() points.current.pop() return } const line = new QLine( [points.current[0].left, points.current[0].top, points.current[0].left + horizontalLength, points.current[0].top + verticalLength], { stroke: 'black', strokeWidth: 2, selectable: false, viewLengthText: true, direction: getDirection(points.current[0], points.current[1]), fontSize: fontSize, }, ) pushHistoryLine(line) // 라인의 끝에 점을 추가합니다. const endPointCircle = new fabric.Circle({ radius: 1, fill: 'transparent', // 원 안을 비웁니다. stroke: 'black', // 원 테두리 색상을 검은색으로 설정합니다. left: points.current[0].left + horizontalLength, top: points.current[0].top + verticalLength, originX: 'center', originY: 'center', selectable: false, }) canvas?.add(endPointCircle) historyPoints.current.push(endPointCircle) points.current.forEach((point) => { canvas?.remove(point) }) setEndPoint(endPointCircle) pointCount.current = pointCount.current + 1 points.current = [endPointCircle] canvas?.renderAll() } const addSelectCreatedEvent = (e) => { const target = e.selected[0] if (target.name === 'helpPoint') { canvas?.on('mouse:move', helpPointEvent.mouseMove) } } const helpPointEvent = { mouseMove: (e) => { const target = canvas?.getActiveObject() const pointer = canvas?.getPointer(e.e) const point = { x: target.left + target.radius, y: target.top + target.radius } const angle = Math.atan2(pointer.y - point.y, pointer.x - point.x) const degree = fabric.util.radiansToDegrees(angle) const min = [0, 45, 90, -0, -90, -45, 135, -135, 180, -180].reduce((prev, curr) => { return Math.abs(curr - degree) < Math.abs(prev - degree) ? curr : prev }) // Calculate the center point of the target object const centerX = target.left + target.width / 2 const centerY = target.top + target.height / 2 const length = distanceBetweenPoints(point, { x: pointer.x, y: pointer.y }) // min의 각도와 pointer의 위치를 이용하여 새로운 점을 구한다. const newPoint = { x: centerX + length * Math.cos(fabric.util.degreesToRadians(min)), y: centerY + length * Math.sin(fabric.util.degreesToRadians(min)), } const line = new fabric.Line([point.x, point.y, newPoint.x, newPoint.y], { stroke: 'black', strokeWidth: 1, selectable: false, name: 'beforeHelpLine', helpPoint: target, }) const helpLines = canvas?._objects.filter((obj) => obj.name === 'beforeHelpLine') helpLines.forEach((item) => canvas?.remove(item)) canvas?.add(line) }, } const addSelectClearedEvent = (e) => { const target = e.deselected[0] if (target.name === 'helpPoint') { const beforeHelpLines = canvas?._objects.filter((obj) => obj.name === 'beforeHelpLine' && obj.helpPoint === target) const helpLines = canvas?._objects.filter((obj) => obj.name === 'helpLine' && obj.helpPoint === target) beforeHelpLines.forEach((item) => canvas?.remove(item)) helpLines.forEach((item) => canvas?.remove(item)) const newPoint = { x: beforeHelpLines[0].x2, y: beforeHelpLines[0].y2 } const helpLine = new fabric.Line([target.left + target.radius, target.top + target.radius, newPoint.x, newPoint.y], { stroke: 'red', strokeWidth: 1, selectable: false, name: 'helpLine', helpPoint: target, }) canvas?.add(helpLine) canvas?.renderAll() canvas?.off('mouse:move', helpPointEvent.mouseMove) } } const mouseAndkeyboardEventClear = () => { canvas?.off('mouse:down') Object.keys(mouseEvent).forEach((key) => { canvas?.off('mouse:down', mouseEvent[key]) document.removeEventListener('contextmenu', mouseEvent[key]) }) Object.keys(keyboardEvent).forEach((key) => { document.removeEventListener('keydown', keyboardEvent[key]) }) } const keyboardEvent = { // rerendering을 막기 위해 useCallback 사용 editMode: useCallback( (e) => { e.preventDefault() switch (e.key) { case 'ArrowDown': { if (!keyValid()) { return } const verticalLength = Number(prompt('길이를 입력하세요:')) const horizontalLength = 0 drawCircleAndLine(verticalLength, horizontalLength) break } case 'ArrowUp': { if (!keyValid()) { return } const verticalLength = -Number(prompt('길이를 입력하세요:')) const horizontalLength = 0 drawCircleAndLine(verticalLength, horizontalLength) break } case 'ArrowLeft': { if (!keyValid()) { return } const verticalLength = 0 const horizontalLength = -Number(prompt('길이를 입력하세요:')) drawCircleAndLine(verticalLength, horizontalLength) break } case 'ArrowRight': { if (!keyValid()) { return } const verticalLength = 0 const horizontalLength = Number(prompt('길이를 입력하세요:')) drawCircleAndLine(verticalLength, horizontalLength) break } case 'Enter': { const result = prompt('입력하세요 (a(A패턴), b(B패턴), t(지붕), e(변별))') switch (result) { case 'a': applyTemplateA() break case 'b': applyTemplateB() break case 't': templateMode() break case 'e': templateSideMode() } } } }, [canvas], ), } const changeMode = (canvas, mode) => { mouseAndkeyboardEventClear() setMode(mode) setCanvas(canvas) // mode별 이벤트 변경 changeMouseEvent(mode) changeKeyboardEvent(mode) switch (mode) { case 'template': templateMode() break case 'patterna': applyTemplateA() break case 'patternb': applyTemplateB() break case 'roofPattern': makeRoofPatternPolygon() break case 'roofTrestle': makeRoofTrestle() break case 'fillCells': makeRoofFillCells() break case 'cellPowercon': makeCellPowercon() break case 'drawHelpLine': drawHelpLineMode() break case 'default': clearEditMode() break } } const changeKeyboardEvent = (mode) => { if (mode === Mode.EDIT) { switch (mode) { case 'edit': document.addEventListener('keydown', keyboardEvent.editMode) break } } } const mouseEvent = { drawLineModeLeftClick: (options) => { if (mode !== Mode.DRAW_LINE) { return } const pointer = canvas?.getPointer(options.e) const line = new QLine( [pointer.x, 0, pointer.x, canvasSize.vertical], // y축에 1자 선을 그립니다. { stroke: 'gray', strokeWidth: 1, selectable: true, lockMovementX: true, lockMovementY: true, lockRotation: true, lockScalingX: true, lockScalingY: true, name: 'guideLine', direction: 'vertical', }, ) canvas?.add(line) canvas?.renderAll() const newVerticalLineArray = [...vertGuideLines] newVerticalLineArray.push(line) setVertGuideLines(newVerticalLineArray) }, drawLineModeRightClick: useCallback( (options) => { document.removeEventListener('contextmenu', mouseEvent.drawLineModeRightClick) if (mode !== Mode.DRAW_LINE) { return } const line = new fabric.Line( [0, options.offsetY, canvasSize.horizontal, options.offsetY], // y축에 1자 선을 그립니다. { stroke: 'gray', strokeWidth: 1, selectable: true, lockMovementX: true, lockMovementY: true, lockRotation: true, lockScalingX: true, lockScalingY: true, name: 'guideLine', direction: 'horizontal', }, ) canvas?.add(line) canvas?.renderAll() const newHorizontalLineArray = [...horiGuideLines] newHorizontalLineArray.push(line) setHoriGuideLines(newHorizontalLineArray) }, [canvas, mode, horiGuideLines], ), editMode: (options) => { if (mode !== Mode.EDIT) { return } let pointer = canvas?.getPointer(options.e) if (getInterSectPointByMouseLine()) { pointer = getInterSectPointByMouseLine() } const circle = new fabric.Circle({ radius: 5, fill: 'transparent', // 원 안을 비웁니다. stroke: 'red', // 원 테두리 색상을 검은색으로 설정합니다. left: pointer.x, top: pointer.y, x: pointer.x, y: pointer.y, originX: 'center', originY: 'center', selectable: false, }) if (!startPoint.current) { startPoint.current = circle pointCount.current = pointCount.current + 1 } let prevEndPoint setEndPoint((prev) => { prevEndPoint = prev return circle }) historyPoints.current.push(circle) points.current.push(circle) canvas?.add(circle) if (points.current.length === 2) { if (guideLineMode || guideDotMode) { const vector = { x: points.current[1].left - points.current[0].left, y: points.current[1].top - points.current[0].top, } const slope = Math.abs(vector.y / vector.x) // 기울기 계산 let scaledVector if (slope >= 1) { // 기울기가 1 이상이면 x축 방향으로 그림 scaledVector = { x: 0, y: pointer.y - prevEndPoint?.top, } } else { // 기울기가 1 미만이면 y축 방향으로 그림 scaledVector = { x: pointer.x - prevEndPoint?.left, y: 0, } } const verticalLength = scaledVector.y const horizontalLength = scaledVector.x drawCircleAndLine(verticalLength, horizontalLength) canvas?.renderAll() return } const length = Number(prompt('길이를 입력하세요:')) // length 값이 숫자가 아닌 경우 if (isNaN(length) || length === 0) { //마지막 추가 된 points 제거합니다. const lastPoint = historyPoints.current[historyPoints.current.length - 1] canvas?.remove(lastPoint) setEndPoint(prevEndPoint) historyPoints.current.pop() points.current.pop() return } if (length) { const vector = { x: points.current[1].left - points.current[0].left, y: points.current[1].top - points.current[0].top, } const slope = Math.abs(vector.y / vector.x) // 기울기 계산 let scaledVector if (slope >= 1) { // 기울기가 1 이상이면 x축 방향으로 그림 scaledVector = { x: 0, y: vector.y >= 0 ? Number(length) : -Number(length), } } else { // 기울기가 1 미만이면 y축 방향으로 그림 scaledVector = { x: vector.x >= 0 ? Number(length) : -Number(length), y: 0, } } const verticalLength = scaledVector.y const horizontalLength = scaledVector.x drawCircleAndLine(verticalLength, horizontalLength) } } canvas?.renderAll() }, textboxMode: (options) => { if (mode !== Mode.TEXTBOX) return if (canvas?.getActiveObject()?.type === 'textbox') return const pointer = canvas?.getPointer(options.e) const textbox = new fabric.Textbox('텍스트를 입력하세요', { left: pointer.x, top: pointer.y, width: 150, // 텍스트박스의 너비를 설정합니다. fontSize: fontSize, // 텍스트의 크기를 설정합니다. }) canvas?.add(textbox) canvas?.setActiveObject(textbox) // 생성된 텍스트박스를 활성 객체로 설정합니다. canvas?.renderAll() // textbox가 active가 풀린 경우 editing mode로 변경 textbox?.on('editing:exited', function () { changeMode(canvas, Mode.EDIT) }) }, drawRectMode: (o) => { if (mode !== Mode.DRAW_RECT) return let rect, isDown, origX, origY isDown = true const pointer = canvas.getPointer(o.e) origX = pointer.x origY = pointer.y rect = new fabric.Rect({ left: origX, top: origY, originX: 'left', originY: 'top', width: pointer.x - origX, height: pointer.y - origY, angle: 0, fill: 'white', stroke: 'black', transparentCorners: false, }) canvas.add(rect) canvas.on('mouse:move', function (e) { if (!isDown) return const pointer = canvas.getPointer(e.e) if (origX > pointer.x) { rect.set({ left: Math.abs(pointer.x) }) } if (origY > pointer.y) { rect.set({ top: Math.abs(pointer.y) }) } rect.set({ width: Math.abs(origX - pointer.x) }) rect.set({ height: Math.abs(origY - pointer.y) }) }) canvas.on('mouse:up', function (o) { isDown = false canvas.off('mouse:move') canvas.off('mouse:up') setMode(Mode.DEFAULT) }) }, // 흡착점 추가 adsorptionPoint: (o) => { if (mode !== Mode.ADSORPTION_POINT) return const pointer = canvas.getPointer(o.e) let newX = pointer.x let newY = pointer.y if (getInterSectPointByMouseLine()) { const interSectPoint = getInterSectPointByMouseLine() newX = interSectPoint.x newY = interSectPoint.y } const circle = new fabric.Circle({ radius: 5, fill: 'transparent', // 원 안을 비웁니다. stroke: 'black', // 원 테두리 색상을 검은색으로 설정합니다. left: newX, top: newY, originX: 'center', originY: 'center', x: newX - 5, y: newY - 5, selectable: false, name: 'adsorptionPoint', }) canvas.add(circle) canvas.renderAll() }, } const getInterSectPointByMouseLine = () => { const mouseLines = canvas?._objects.filter((obj) => obj.name === 'mouseLine') if (mouseLines.length !== 2) { return null } return calculateIntersection(mouseLines[0], mouseLines[1]) } const pushHistoryLine = (line) => { if (historyLines.current.length > 0 && historyLines.current[historyLines.current.length - 1].direction === line.direction) { // 같은 방향의 선이 두 번 연속으로 그려지면 이전 선을 제거하고, 새로운 선과 merge한다. const lastLine = historyLines.current.pop() canvas?.remove(lastLine) const mergedLine = new QLine([lastLine.x1, lastLine.y1, line.x2, line.y2], { stroke: 'black', strokeWidth: 2, selectable: false, viewLengthText: true, direction: lastLine.direction, fontSize: fontSize, }) historyLines.current.push(mergedLine) canvas?.add(mergedLine) } else { historyLines.current.push(line) canvas?.add(line) } } /** * 마우스로 그린 점 기준으로 외벽선을 완성시켜준다. * makePolygon 함수에 포함되어있던 내용을 다른 템플릿 적용에서도 사용할수 있도록 함수로 대체 */ const drawWallPolygon = (sort = true) => { const firstPoint = historyPoints.current[0] const lastPoint = historyPoints.current[historyPoints.current.length - 1] historyPoints.current.forEach((point) => { canvas?.remove(point) }) drawLineWithLength(lastPoint, firstPoint) points.current = [] historyPoints.current = [] const wall = makePolygon(null, sort) wall.set({ name: 'wall' }) return wall } const templateMode = () => { changeMode(canvas, Mode.EDIT) if (historyPoints.current.length >= 4) { const wall = drawWallPolygon() setWall(wall) handleOuterlinesTest2(wall) setTemplateType(1) } } const templateSideMode = () => { changeMode(canvas, Mode.EDIT) if (historyPoints.current.length >= 4) { const wall = drawWallPolygon() setWall(wall) console.log('sideWall', wall) } } /** * 두 점을 연결하는 선과 길이를 그립니다. * a : 시작점, b : 끝점 */ const drawLineWithLength = (a, b) => { if (!a || !b) { return } const line = new QLine([a.left, a.top, b.left, b.top], { stroke: 'black', strokeWidth: 2, selectable: false, viewLengthText: true, direction: getDirection(a, b), fontSize: fontSize, }) pushHistoryLine(line) canvas?.renderAll() } const makePolygon = (otherLines, sort = true) => { // 캔버스에서 모든 라인 객체를 찾습니다. const lines = otherLines || historyLines.current historyLines.current = [] // 각 라인의 시작점과 끝점을 사용하여 다각형의 점 배열을 생성합니다. const points = lines.map((line) => ({ x: line.x1, y: line.y1 })) // 모든 라인 객체를 캔버스에서 제거합니다. lines.forEach((line) => { canvas?.remove(line) }) // 점 배열을 사용하여 새로운 다각형 객체를 생성합니다. const polygon = new QPolygon( points, { stroke: 'black', fill: 'transparent', viewLengthText: true, fontSize: fontSize, sort: sort, selectable: false, }, canvas, ) // 새로운 다각형 객체를 캔버스에 추가합니다. canvas.add(polygon) // 캔버스를 다시 그립니다. // polygon.fillCell() canvas?.renderAll() // polygon.setViewLengthText(false) setMode(Mode.DEFAULT) return polygon } /** * 해당 캔버스를 비운다. */ const handleClear = () => { canvas?.clear() startPoint.current = null setEndPoint(null) pointCount.current = 0 setTemplateType(0) points.current = [] historyPoints.current = [] historyLines.current = [] setRoof(null) setWall(null) setSelectedCellRoofArray([]) //셀 그린거 삭제 } const clearEditMode = () => { startPoint.current = null setEndPoint(null) pointCount.current = 0 points.current = [] historyPoints.current = [] historyLines.current = [] } const zoomIn = () => { if (canvas.getZoom() + 0.1 > 1.6) { return } canvas?.setZoom(canvas.getZoom() + 0.1) setZoom(Math.round(zoom + 10)) } const zoomOut = () => { if (canvas.getZoom() - 0.1 < 0.5) { return } canvas?.setZoom(canvas.getZoom() - 0.1) setZoom(Math.ceil(zoom - 10)) } /** *벽 지붕 외곽선 생성 */ const handleOuterlinesTest = (polygon, offsetInputX, offsetInputY = 0) => { let offsetPoints = [] const originalMax = 71 const transformedMax = 100 offsetInputY = offsetInputY !== 0 ? offsetInputY : offsetInputX const offsetX = (offsetInputX / transformedMax) * originalMax * 2 const offsetY = (offsetInputY / transformedMax) * originalMax * 2 const sortedIndex = getStartIndex(polygon.lines) let tmpArraySorted = rearrangeArray(polygon.lines, sortedIndex) if (tmpArraySorted[0].direction === 'right') { //시계방향 tmpArraySorted = tmpArraySorted.reverse() //그럼 배열을 거꾸로 만들어서 무조건 반시계방향으로 배열 보정 } setSortedArray(tmpArraySorted) //recoil에 넣음 const points = tmpArraySorted.map((line) => ({ x: line.x1, y: line.y1, })) for (var i = 0; i < points.length; i++) { var prev = points[(i - 1 + points.length) % points.length] var current = points[i] var next = points[(i + 1) % points.length] // 두 벡터 계산 (prev -> current, current -> next) var vector1 = { x: current.x - prev.x, y: current.y - prev.y } var vector2 = { x: next.x - current.x, y: next.y - current.y } // 벡터의 길이 계산 var length1 = Math.sqrt(vector1.x * vector1.x + vector1.y * vector1.y) var length2 = Math.sqrt(vector2.x * vector2.x + vector2.y * vector2.y) // 벡터를 단위 벡터로 정규화 var unitVector1 = { x: vector1.x / length1, y: vector1.y / length1 } var unitVector2 = { x: vector2.x / length2, y: vector2.y / length2 } // 법선 벡터 계산 (왼쪽 방향) var normal1 = { x: -unitVector1.y, y: unitVector1.x } var normal2 = { x: -unitVector2.y, y: unitVector2.x } // 법선 벡터 평균 계산 var averageNormal = { x: (normal1.x + normal2.x) / 2, y: (normal1.y + normal2.y) / 2, } // 평균 법선 벡터를 단위 벡터로 정규화 var lengthNormal = Math.sqrt(averageNormal.x * averageNormal.x + averageNormal.y * averageNormal.y) var unitNormal = { x: averageNormal.x / lengthNormal, y: averageNormal.y / lengthNormal, } // 오프셋 적용 var offsetPoint = { x1: current.x + unitNormal.x * offsetX, y1: current.y + unitNormal.y * offsetY, } offsetPoints.push(offsetPoint) } return makePolygon(offsetPoints, false) } /** *벽 지붕 외곽선 생성 polygon을 입력받아 만들기 */ const handleOuterlinesTest2 = (polygon, offset = 50) => { const offsetPoints = offsetPolygon(polygon.points, offset) const roof = makePolygon( offsetPoints.map((point) => { return { x1: point.x, y1: point.y } }), ) roof.setWall(polygon) setRoof(roof) setWall(polygon) roof.drawHelpLine() roof.divideLine() } const drawRoofPolygon = (wall, offset = 50) => { console.log(wall) let points = wall.points, expandedPoints = [] let minX = points[0].x, minY = points[0].y, maxX = points[0].x, maxY = points[0].y points.forEach((point) => { if (point.x < minX) minX = point.x if (point.y < minY) minY = point.y if (point.x > maxX) maxX = point.x if (point.y > maxY) maxY = point.y }) console.log(points) points.forEach((point, index) => { const prevIndex = index === 0 ? points.length - 1 : index - 1 const nextIndex = index === points.length - 1 ? 0 : index + 1 point.direction = getDirectionByPoint(point, points[nextIndex]) point.length = Math.abs(point.x - points[nextIndex].x) === 0 ? Math.abs(point.y - points[nextIndex].y) : Math.abs(point.x - points[nextIndex].x) // point.degree = Math.round(getDegreeBetweenTwoLines(points[prevIndex], point, points[nextIndex])) }) console.log('points : ', points) points.forEach((currentWall, index) => { let prevWall = points[index === 0 ? points.length - 1 : index - 1] let nextWall = points[index === points.length - 1 ? 0 : index + 1] let isStartPointIn = minX < currentWall.x && currentWall.x < maxX && minY < currentWall.y && currentWall.y < maxY // let isEndPointIn = minX < currentWall.x2 && currentWall.x2 < maxX && minY < currentWall.y2 && currentWall.y2 < maxY if (prevWall.direction !== nextWall.direction) { if (currentWall.direction === 'top') { console.log('prevWall degree : ', 45) if (prevWall.direction === 'right') { let addLength = getLineOffsetPoint(prevWall, currentWall, nextWall, offset) expandedPoints.push({ x: currentWall.x + addLength, y: currentWall.y + addLength, }) } if (prevWall.direction === 'left') { console.log('prevWall degree : ', 225) expandedPoints.push({ x: currentWall.x - offset, y: currentWall.y + offset, }) } } if (currentWall.direction === 'bottom') { if (prevWall.direction === 'right') { console.log('prevWall degree : ', 45) let addLength = getLineOffsetPoint(prevWall, currentWall, nextWall, offset) console.log(currentWall.x, '-', offset, '+', addLength) console.log('addLength : ', addLength) expandedPoints.push({ x: currentWall.x + addLength, y: currentWall.y - addLength, }) } if (prevWall.direction === 'left') { console.log('prevWall degree : ', 315) let addLength = getLineOffsetPoint(prevWall, currentWall, nextWall, offset) console.log(currentWall.x, '-', offset, '+', addLength) console.log('addLength : ', addLength) expandedPoints.push({ x: currentWall.x - offset, y: currentWall.y - offset, }) } } if (currentWall.direction === 'right') { if (prevWall.direction === 'top') { if (isStartPointIn) { console.log('prevWall degree : ', 135) expandedPoints.push({ x: currentWall.x + offset, y: currentWall.y + offset, }) } else { console.log('prevWall degree : ', 315) expandedPoints.push({ x: currentWall.x - offset, y: currentWall.y - offset, }) } } if (prevWall.direction === 'bottom') { if (isStartPointIn) { console.log('prevWall degree : ', 45) expandedPoints.push({ x: currentWall.x + offset, y: currentWall.y - offset, }) } else { console.log('prevWall degree : ', 225) let addLength = getLineOffsetPoint(prevWall, currentWall, nextWall, offset) console.log('addLength : ', addLength) expandedPoints.push({ x: currentWall.x - offset, y: currentWall.y + offset, }) } } } if (currentWall.direction === 'left') { if (prevWall.direction === 'top') { if (isStartPointIn) { console.log('prevWall degree : ', 225) let addLength = getLineOffsetPoint(prevWall, currentWall, nextWall, offset) console.log('addLength : ', addLength) expandedPoints.push({ x: currentWall.x - offset, y: currentWall.y + offset, }) } else { console.log('prevWall degree : ', 45) let addLength = getLineOffsetPoint(prevWall, currentWall, nextWall, offset) expandedPoints.push({ x: currentWall.x + offset, y: currentWall.y - offset, }) } } if (prevWall.direction === 'bottom') { if (isStartPointIn) { console.log('prevWall degree : ', 315) expandedPoints.push({ x: currentWall.x - offset, y: currentWall.y - offset, }) } else { console.log('prevWall degree : ', 135) expandedPoints.push({ x: currentWall.x + offset, y: currentWall.y + offset, }) } } } } else { console.log('else :::: ') if (currentWall.direction === 'top') { if (prevWall.direction === 'right') { if (isStartPointIn) { console.log('prevWall degree : ', 315) expandedPoints.push({ x: currentWall.x - offset, y: currentWall.y - offset, }) } else { console.log('prevWall degree : ', 135) expandedPoints.push({ x: currentWall.x + offset, y: currentWall.y + offset, }) } } if (prevWall.direction === 'left') { if (isStartPointIn) { console.log('prevWall degree : ', 45) expandedPoints.push({ x: currentWall.x + offset, y: currentWall.y - offset, }) } else { console.log('prevWall degree : ', 225) expandedPoints.push({ x: currentWall.x - offset, y: currentWall.y + offset, }) } } } if (currentWall.direction === 'bottom') { if (prevWall.direction === 'right') { if (isStartPointIn) { console.log('prevWall degree : ', 225) expandedPoints.push({ x: currentWall.x - offset, y: currentWall.y + offset, }) } else { console.log('prevWall degree : ', 45) expandedPoints.push({ x: currentWall.x + offset, y: currentWall.y - offset, }) } } } if (currentWall.direction === 'right') { if (prevWall.direction === 'top') { if (isStartPointIn) { console.log('prevWall degree : ', 135) expandedPoints.push({ x: currentWall.x + offset, y: currentWall.y + offset, }) } else { console.log('prevWall degree : ', 315) expandedPoints.push({ x: currentWall.x - offset, y: currentWall.y - offset, }) } } if (prevWall.direction === 'bottom') { if (isStartPointIn) { console.log('prevWall degree : ', 225) expandedPoints.push({ x: currentWall.x - offset, y: currentWall.y + offset, }) } else { console.log('prevWall degree : ', 45) expandedPoints.push({ x: currentWall.x + offset, y: currentWall.y - offset, }) } } } if (currentWall.direction === 'left') { if (prevWall.direction === 'top') { if (isStartPointIn) { console.log('prevWall degree : ', 225) expandedPoints.push({ x: currentWall.x - offset, y: currentWall.y + offset, }) } else { console.log('prevWall degree : ', 45) expandedPoints.push({ x: currentWall.x + offset, y: currentWall.y - offset, }) } } if (prevWall.direction === 'bottom') { if (isStartPointIn) { console.log('prevWall degree : ', 135) expandedPoints.push({ x: currentWall.x + offset, y: currentWall.y + offset, }) } else { console.log('prevWall degree : ', 315) expandedPoints.push({ x: currentWall.x - offset, y: currentWall.y - offset, }) } } } } }) console.log('expandedPoints : ', expandedPoints) /*const roof = new fabric.Polygon(expandedPoints, { fill: 'transparent', stroke: 'red', strokeWidth: 1, selectable: true, fontSize: fontSize, name: 'QPolygon1', }) roof.wall = wall canvas?.add(roof)*/ // roof.drawHelpLine() } /** * 구하려는 라인의 x1,y1좌표가 기준 * @param line1 이전 라인 * @param line2 현재 라인 * @param line3 다음 라인 * @param offset * @returns {number} */ const getLineOffsetPoint = (line1, line2, line3, offset) => { //밑변 let a = Math.abs(line1.x - line2.x) //빗변 let c = Math.sqrt(Math.abs(line1.x - line2.x) ** 2 + Math.abs(line1.y - line2.y) ** 2) console.log(a, c) //밑변과 빗변사이의 각도 let alphaDegree = getDegreeBetweenTwoLines(line1, line2, line3) alphaDegree = alphaDegree <= 90 ? alphaDegree : 180 - alphaDegree alphaDegree = 90 - alphaDegree console.log('alphaDegree : ', alphaDegree) // console.log('Math.tan(alphaDegree * (Math.PI / 180)) : ', Math.tan(alphaDegree * (Math.PI / 180))) console.log(Math.round(offset * Math.tan(alphaDegree * (Math.PI / 180)))) const angle = getDegreeBetweenTwoLines(line1, line2, line3) const side1 = line1.length const side2 = line2.length const side3 = Math.sqrt(side1 ** 2 + side2 ** 2 - 2 * side1 * side2 * Math.cos(angle * (Math.PI / 180))) const beta = Math.round(Math.asin((side2 * Math.sin(angle * (Math.PI / 180))) / side3) * (180 / Math.PI) * 10) / 10 const alpha = 180 - angle - beta console.log('angle : ', angle, 'alpha : ', alpha, 'beta : ', beta) const h = side2 * Math.sin(alpha * (Math.PI / 180)) const h_new = h + offset console.log('빗변까지 길이 : ', h, 'offset 적용된 빗변까지 길이 : ', h_new) const newAdjacent = h_new / Math.sin(angle * (Math.PI / 180)) console.log('offset 적용된 밑변 : ', newAdjacent) // offset = Math.sqrt(diffAdjacent ** 2 + diffAdjacent ** 2) / 2 console.log('offset : ', offset) return offset } /** * 구하려는 라인의 x1,y1좌표가 기준 * @param line1 이전 라인 * @param line2 현재 라인 * @param line3 다음 라인 * @returns {number} */ const getDegreeBetweenTwoLines = (line1, line2, line3) => { console.log('getDegreeBetweenTwoLines 확인 ==========') console.log(line1, line2, line3) let x1 = line1.x, x2 = line2.x, x3 = line3.x let y1 = line1.y, y2 = line2.y, y3 = line3.y // 각 점 사이의 벡터 계산 const vector1 = { x: x1 - x2, y: y1 - y2 } const vector2 = { x: x3 - x2, y: y3 - y2 } // 벡터의 길이 계산 const magnitude1 = Math.sqrt(vector1.x * vector1.x + vector1.y * vector1.y) const magnitude2 = Math.sqrt(vector2.x * vector2.x + vector2.y * vector2.y) // 내적 계산 const dotProduct = vector1.x * vector2.x + vector1.y * vector2.y // 각도 계산 (라디안에서도 0에서 PI 라디안 사이의 각도로 변환) let angle = Math.acos(dotProduct / (magnitude1 * magnitude2)) // 라디안에서 도 단위로 변환 angle = angle * (180 / Math.PI) console.log('angel : ', angle) return angle } const calculateParallelPoint = (x1, y1, x2, y2, distance) => { // 원래 선의 dx, dy 계산 const dx = x2 - x1 const dy = y2 - y1 // 법선 벡터 정규화 const norm = Math.sqrt(dx * dx + dy * dy) const unitVectorX = dy / norm const unitVectorY = -dx / norm // 원하는 거리만큼 평행 이동 const offsetX = distance * unitVectorX const offsetY = distance * unitVectorY // 새로운 평행선의 두 점 계산 const newX1 = x1 + offsetX const newY1 = y1 + offsetY const newX2 = x2 + offsetX const newY2 = y2 + offsetY return { newPoint1: { x: newX1, y: newY1 }, newPoint2: { x: newX2, y: newY2 } } } const togglePolygonLine = (obj) => { const rtnLines = [] if (obj.type === 'QPolygon') { const points = obj.getCurrentPoints() points.forEach((point, index) => { const nextPoint = points[(index + 1) % points.length] // 마지막 점이면 첫 번째 점으로 연결 const line = new QLine([point.x, point.y, nextPoint.x, nextPoint.y], { stroke: 'black', strokeWidth: 2, selectable: false, fontSize: fontSize, // fontSize는 필요에 따라 조정 parent: obj, }) obj.visible = false canvas.add(line) rtnLines.push(line) }) canvas?.renderAll() } if (obj.type === 'QLine') { const parent = obj.parent canvas ?.getObjects() .filter((obj) => obj.parent === parent) .forEach((obj) => { rtnLines.push(obj) canvas.remove(obj) }) parent.visible = true canvas?.renderAll() } return rtnLines } const applyTemplateA = () => { if (historyPoints.current.length === 0) { changeMode(canvas, Mode.EDIT) return } changeMode(canvas, Mode.EDIT) const polygon = drawWallPolygon(false) // handleClear() if (polygon.lines.length === 4) { //4각형 handleOuterLineTemplateA4Points(polygon) } else if (polygon.lines.length === 6) { //6각형 handleOuterLineTemplateA6Points(polygon) } else if (polygon.lines.length === 8) { handleOuterLineTemplateA8Points(polygon) } setTemplateType(2) } const handleOuterLineTemplateA4Points = (polygon, offsetInputX = 20, offsetInputY = 50) => { const edge = offsetInputX const eaves = offsetInputY // 폴리곤의 각 변을 선으로 생성 const createLine = (start, end, stroke, property) => new QLine([start.x, start.y, end.x, end.y], { stroke, strokeWidth: 1, property, fontSize: 14, }) const lines = polygon.points.map((start, i) => { const end = polygon.points[(i + 1) % polygon.points.length] const line = createLine(start, end, '#A0D468', 'normal') canvas.add(line) return line }) let edgeIndexArray = [] let normalIndexArray = [] lines.forEach((line, i) => { if (i % 2 === 0) { line.set('stroke', 'skyblue').set('property', 'edge') edgeIndexArray.push(i) } else { normalIndexArray.push(i) } canvas.add(line) }) const centerPointX = (lines[1].x1 + lines[1].x2) / 2 const centerPointY = (lines[0].y1 + lines[0].y2) / 2 const horiCenterHalfLine = (lines[1].x2 - lines[1].x1) / 2 const createCenterLine = (x1, y1, x2, y2, stroke, strokeWidth, property, dashArray = []) => new QLine([x1, y1, x2, y2], { stroke, strokeWidth, property, fontSize: 14, strokeDashArray: dashArray, }) const vertCenterLine = createCenterLine(centerPointX, lines[0].y1 - edge, centerPointX, lines[0].y2 + edge, 'blue', 1, 'center') canvas.add(vertCenterLine) const horiCenterLineLeft = createCenterLine( lines[1].x1 - eaves, centerPointY, lines[1].x1 + horiCenterHalfLine, centerPointY, 'black', 2, 'center', [5, 5], ) canvas.add(horiCenterLineLeft) const horiCenterLineRight = createCenterLine( horiCenterLineLeft.x2, centerPointY, horiCenterLineLeft.x2 + horiCenterHalfLine + eaves, centerPointY, 'black', 2, 'center', [5, 5], ) canvas.add(horiCenterLineRight) const drawArray = lines .map((line) => { if (line.x1 === line.x2 && line.y1 < line.y2) { return [{ x1: line.x1 - eaves, y1: line.y1 - edge, x2: line.x1 - eaves, y2: line.y2 + edge }] } else if (line.x1 === line.x2 && line.y1 > line.y2) { return [{ x1: line.x1 + eaves, y1: line.y1 + edge, x2: line.x1 + eaves, y2: line.y2 - edge }] } else if (line.x1 < line.x2 && line.y1 === line.y2) { return [ { x1: line.x1 - eaves, y1: line.y1 + edge, x2: line.x1 + horiCenterHalfLine, y2: line.y2 + edge }, { x1: line.x1 + horiCenterHalfLine, y1: line.y1 + edge, x2: line.x1 + horiCenterHalfLine + horiCenterHalfLine + eaves, y2: line.y2 + edge, }, ] } else if (line.x1 > line.x2 && line.y1 === line.y2) { return [ { x1: line.x2 - eaves, y1: line.y1 - edge, x2: line.x2 + horiCenterHalfLine, y2: line.y2 - edge }, { x1: line.x2 + horiCenterHalfLine, y1: line.y1 - edge, x2: line.x2 + horiCenterHalfLine + horiCenterHalfLine + eaves, y2: line.y2 - edge, }, ] } return [] }) .flat() drawArray.forEach((line) => { const outLine = createLine({ x: line.x1, y: line.y1 }, { x: line.x2, y: line.y2 }, 'blue', 'normal') canvas.add(outLine) }) const roofPatternPolygonArray = [] const leftLine = drawArray[0] const rightLine = drawArray[3] //사각형 왼쪽 지붕 패턴 생성 배열 const leftPolygon = [ { x: leftLine.x1, y: leftLine.y1 }, { x: leftLine.x2, y: leftLine.y2 }, { x: vertCenterLine.x1, y: vertCenterLine.y1 }, { x: vertCenterLine.x2, y: vertCenterLine.y2 }, ] roofPatternPolygonArray.push(leftPolygon) //사각형 오른쪽 지붕 패턴 생성 배열 const rightPolygon = [ { x: vertCenterLine.x1, y: vertCenterLine.y1 }, { x: vertCenterLine.x2, y: vertCenterLine.y2 }, { x: rightLine.x1, y: rightLine.y1 }, { x: rightLine.x2, y: rightLine.y2 }, ] roofPatternPolygonArray.push(rightPolygon) setRoofPolygonPattern({ roofPatternPolygonArray, lines }) //모든 행을 저장 } //탬플릿A 적용 const handleOuterLineTemplateA6Points = (polygon) => { let lines = [] let outLines = [] let halfLength = 0 const dashedCenterLineOpt = { stroke: 'black', strokeWidth: 1, property: 'centerLine', strokeDashArray: [10, 5], fontSize: 14, } const centerLineOpt = { stroke: 'blue', strokeWidth: 1, property: 'bigHoriCenter', fontSize: 14, } // 폴리곤의 각 변을 선으로 생성 for (let i = 0; i < polygon.points.length; i++) { const start = polygon.points[i] const end = polygon.points[(i + 1) % polygon.points.length] // 다음 점, 마지막 점의 경우 첫 점으로 const line = new QLine([start.x, start.y, end.x, end.y], { stroke: '#A0D468', strokeWidth: 2, property: 'normal', fontSize: 14, }) // 선을 배열에 추가 lines.push(line) canvas.add(line) } canvas?.remove(polygon) //폴리곤 let highLineLength = 0 let lowLineLength = 0 let prevHighIndex = 0 let prevHighLength = 0 let prevLowIndex = 0 let prevLowLength = 0 let edgeIndexArray = [] let normalIndexArray = [] for (let i = 0; i < lines.length; i++) { let line = lines[i] if (!(i % 2) == 0) { //홀수일떄 line.line.set('stroke', 'skyblue').set('property', 'egde') let length = Math.abs(line.get('x1') - line.get('x2')) + Math.abs(line.get('y1') - line.get('y2')) if (length > prevHighLength) { //잴긴거 찾음 prevHighIndex = i prevHighLength = length highLineLength = length } if (prevLowLength === 0 || length <= prevLowLength) { //최초에는 없어서 한번 넣음 prevLowIndex = i prevLowLength = length lowLineLength = length } edgeIndexArray.push(i) } else { normalIndexArray.push(i) } // 캔버스에 선 추가 canvas.add(line) } let prevDirecArray //긴선 앞배열 let nextDirecArray //긴선 뒷배열 let horizontalDirection //뽈록이 좌우 방향 if (prevHighIndex === 1) { //카라바 기준 1과 5밖에 없음 prevDirecArray = lines[prevHighIndex - 1] nextDirecArray = lines[prevHighIndex + 1] //밑에쪽이 긴 방향 horizontalDirection = prevDirecArray.height > nextDirecArray.height ? 'left' : 'right' } else { prevDirecArray = lines[prevHighIndex - 1] nextDirecArray = lines[0] //위에가 긴 방향 horizontalDirection = prevDirecArray.height > nextDirecArray.height ? 'right' : 'left' } const edge = 20 //케라바 const eaves = 50 //처마 let firstLine = lines[1] let secondLine = lines[3] let lastLine = lines[5] let vertCenterLine let secondVertCenterLine let templatePolygonObj = {} let roofPatternPolygonArray = [] let bigRoofPolygon = [] let middleRoofPolygon = [] let smallRoofPolygon = [] let templateCenterLine = [] //추후 셀 입력시 상하좌우를 센터선 기준으로 판단하기 위함 if (prevHighIndex === 1) { if (horizontalDirection === 'left') { //배열 순서대로 뒤에꺼를 찾아서 계산한다 const firstSubLine = lines[2] const middleSubLine = lines[4] //ㄴ자 일경우 //긴면 세로선 그리기 let vertCenterPoint = (firstLine.x1 + firstLine.x2) / 2 //가장 긴선 중앙선 가운데 점 vertCenterLine = new QLine( [ vertCenterPoint, firstSubLine.y1 + edge, //다음 선의 높이만큼 가져와서 edge길이를 합함 vertCenterPoint, firstSubLine.y2 - edge, //다음 선의 높이만큼 가져옴 edge길이를 합함 ], centerLineOpt, ) canvas.add(vertCenterLine) outLines.push(vertCenterLine) //긴면 가로선 그리기 let horiCenterPoint = (firstSubLine.y1 + firstSubLine.y2) / 2 let horiCenterLine1 = new QLine( [firstLine.x1 - eaves, horiCenterPoint, firstLine.x1 - eaves + firstLine.length / 2 + eaves, horiCenterPoint], dashedCenterLineOpt, ) canvas.add(horiCenterLine1) //나중에 기울기 길이 적용할때 쓸라고 대충 냅둠 // canvas?.renderAll() // console.log('horiCenterLine1', horiCenterLine1) // horiCenterLine1.text.set('text', getRoofHeight(horiCenterLine1.length, getDegreeByChon(4)).toString()) let horiCenterLine2 = new QLine( [ firstLine.x1 - eaves + firstLine.length / 2 + eaves, horiCenterPoint, firstLine.x1 - eaves + firstLine.length / 2 + eaves + firstLine.length / 2 + eaves, horiCenterPoint, ], dashedCenterLineOpt, ) canvas.add(horiCenterLine2) //작은 지붕쪽 높이 길이를 구하는 로직 let secondVertCenterPoint = (lastLine.x1 + lastLine.x2) / 2 secondVertCenterLine = new QLine([secondVertCenterPoint, middleSubLine.y1, secondVertCenterPoint, middleSubLine.y2 - edge], centerLineOpt) canvas.add(secondVertCenterLine) outLines.push(secondVertCenterLine) templateCenterLine.push(vertCenterLine) templateCenterLine.push(secondVertCenterLine) //작은 지붕쪽 너비 길이를 구한는 로직 let secondHoriCenterLength = (Math.abs(lastLine.get('x1') - lastLine.get('x2')) + Math.abs(lastLine.get('y1') - lastLine.get('y2'))) / 2 let secondHoriCenterPoint = (secondVertCenterLine.y1 + secondVertCenterLine.y2) / 2 let secondHoriCenterLine = new QLine( [secondVertCenterLine.x1, secondHoriCenterPoint, secondVertCenterLine.x1 + secondHoriCenterLength + eaves, secondHoriCenterPoint], dashedCenterLineOpt, ) canvas.add(secondHoriCenterLine) //일반라인 외각선 그리기 normalIndexArray.forEach((index) => { const line = lines[index] let points = [] if (index === 0) { points.push(line.x1 - eaves, line.y1 - edge, line.x2 - eaves, line.y2 + edge) } else { let tmpEdge = index === 2 ? edge : 0 points.push(line.x1 + eaves, line.y1 + tmpEdge, line.x2 + eaves, line.y2 - edge) } let drawline = new QLine(points, centerLineOpt) canvas.add(drawline) outLines.push(drawline) }) //케라바 라인 외각선 그리기 const firstOuterLine = lines[1] const middleOuterLine = lines[3] const lastOuterLine = lines[5] //첫번째 외곽선 1번 halfLength = firstOuterLine.length / 2 let drawFirstLine1 = new QLine( [firstOuterLine.x1 - eaves, firstOuterLine.y1 + edge, firstOuterLine.x1 + halfLength, firstOuterLine.y2 + edge], centerLineOpt, ) canvas.add(drawFirstLine1) outLines.push(drawFirstLine1) //첫번째 외곽선 2번 let drawFirstLine2 = new QLine( [drawFirstLine1.x2, firstOuterLine.y1 + edge, firstOuterLine.x2 + eaves, firstOuterLine.y2 + edge], centerLineOpt, ) canvas.add(drawFirstLine2) outLines.push(drawFirstLine2) //중간라인 외각선 let drawMiddleLine = new QLine([drawFirstLine2.x2, middleOuterLine.y1 - edge, drawFirstLine2.x1, middleOuterLine.y2 - edge], centerLineOpt) canvas.add(drawMiddleLine) outLines.push(drawMiddleLine) //마지막 외각선 halfLength = lastLine.length / 2 let drawLastLine1 = new QLine( [lastOuterLine.x2 + halfLength, lastOuterLine.y1 - edge, lastOuterLine.x2 - eaves, lastOuterLine.y2 - edge], centerLineOpt, ) canvas.add(drawLastLine1) outLines.push(drawLastLine1) let drawLastLine2 = new QLine( [drawLastLine1.x1, lastOuterLine.y1 - edge, lastOuterLine.x1 + length + eaves, lastOuterLine.y2 - edge], centerLineOpt, ) canvas.add(drawLastLine2) outLines.push(drawLastLine2) let drawLastInLine1 = new QLine( [secondVertCenterLine.x1, secondVertCenterLine.y1, secondVertCenterLine.x1 + halfLength + eaves, secondVertCenterLine.y1], centerLineOpt, ) canvas.add(drawLastInLine1) let drawLastInLine2 = new QLine([secondVertCenterLine.x1, vertCenterLine.y2, vertCenterLine.x2, vertCenterLine.y2], centerLineOpt) canvas.add(drawLastInLine2) bigRoofPolygon = [ { x: outLines[2].x1, y: outLines[2].y1 }, { x: outLines[2].x2, y: outLines[2].y2 }, { x: outLines[0].x1, y: outLines[0].y1 }, { x: outLines[0].x2, y: outLines[0].y2 }, { x: outLines[1].x2, y: outLines[0].y2 }, { x: outLines[1].x2, y: outLines[1].y2 }, ] middleRoofPolygon = [ { x: outLines[0].x2, y: outLines[0].y2 }, { x: outLines[0].x1, y: outLines[0].y1 }, { x: outLines[3].x2, y: outLines[3].y2 }, { x: outLines[3].x1, y: outLines[3].y1 }, ] smallRoofPolygon = [ { x: outLines[1].x2, y: outLines[1].y2 }, { x: outLines[1].x1, y: outLines[1].y1 }, { x: outLines[4].x2, y: outLines[4].y2 }, { x: outLines[4].x1, y: outLines[4].y1 }, ] } else { //아래쪽 길게 오른쪽 방향 //배열 순서대로 뒤에꺼를 찾아서 계산한다 firstLine = lines[1] secondLine = lines[5] lastLine = lines[3] const firstSubLine = lines[0] const middleSubLine = lines[4] //ㄴ자 일경우 //긴면 세로선 그리기 let vertCenterPoint = (firstLine.x1 + firstLine.x2) / 2 //가장 긴선 중앙선 가운데 점 vertCenterLine = new QLine( [ vertCenterPoint, firstSubLine.y1 - edge, //다음 선의 높이만큼 가져와서 edge길이를 합함 vertCenterPoint, firstSubLine.y2 + edge, //다음 선의 높이만큼 가져옴 edge길이를 합함 ], centerLineOpt, ) canvas.add(vertCenterLine) outLines.push(vertCenterLine) //긴면 가로선 그리기 let horiCenterPoint = (firstSubLine.y1 + firstSubLine.y2) / 2 let horiCenterLine1 = new QLine( [firstLine.x1 - eaves, horiCenterPoint, firstLine.x1 - eaves + firstLine.length / 2 + eaves, horiCenterPoint], dashedCenterLineOpt, ) canvas.add(horiCenterLine1) let horiCenterLine2 = new QLine( [ firstLine.x1 - eaves + firstLine.length / 2 + eaves, horiCenterPoint, firstLine.x1 - eaves + firstLine.length / 2 + eaves + firstLine.length / 2 + eaves, horiCenterPoint, ], dashedCenterLineOpt, ) canvas.add(horiCenterLine2) //작은 지붕쪽 높이 길이를 구하는 로직 let secondVertCenterPoint = (lastLine.x1 + lastLine.x2) / 2 secondVertCenterLine = new QLine([secondVertCenterPoint, middleSubLine.y1 - edge, secondVertCenterPoint, middleSubLine.y2], centerLineOpt) canvas.add(secondVertCenterLine) outLines.push(secondVertCenterLine) //작은 지붕쪽 너비 길이를 구한는 로직 let secondHoriCenterLength = (Math.abs(lastLine.get('x1') - lastLine.get('x2')) + Math.abs(lastLine.get('y1') - lastLine.get('y2'))) / 2 let secondHoriCenterPoint = (secondVertCenterLine.y1 + secondVertCenterLine.y2) / 2 let secondHoriCenterLine = new QLine( [secondVertCenterLine.x1 - secondHoriCenterLength - eaves, secondHoriCenterPoint, secondVertCenterLine.x1, secondHoriCenterPoint], dashedCenterLineOpt, ) canvas.add(secondHoriCenterLine) templateCenterLine.push(vertCenterLine) templateCenterLine.push(secondVertCenterLine) //일반라인 외각선 그리기 normalIndexArray.forEach((index) => { const line = lines[index] let points = [] if (index === 0 || index === 4) { let tmpEdge = index === 4 ? 0 : edge points = [line.x1 - eaves, line.y1 - edge, line.x2 - eaves, line.y2 + tmpEdge] } else { points = [line.x1 + eaves, line.y1 + edge, line.x2 + eaves, line.y2 - edge] } let drawline = new QLine(points, centerLineOpt) canvas.add(drawline) outLines.push(drawline) }) //케라바 라인 외각선 그리기 const firstOuterLine = lines[1] const middleOuterLine = lines[5] const lastOuterLine = lines[3] //첫번째 외곽선 1번 halfLength = firstOuterLine.length / 2 let drawFirstLine1 = new QLine( [firstOuterLine.x1 - eaves, firstOuterLine.y1 + edge, firstOuterLine.x1 + halfLength, firstOuterLine.y2 + edge], centerLineOpt, ) canvas.add(drawFirstLine1) outLines.push(drawFirstLine1) //첫번째 외곽선 2번 let drawFirstLine2 = new QLine( [drawFirstLine1.x2, firstOuterLine.y1 + edge, firstOuterLine.x2 + eaves, firstOuterLine.y2 + edge], centerLineOpt, ) canvas.add(drawFirstLine2) outLines.push(drawFirstLine2) //중간라인 외각선 let drawMiddleLine = new QLine([drawFirstLine1.x1, middleOuterLine.y1 - edge, drawFirstLine1.x2, middleOuterLine.y2 - edge], centerLineOpt) canvas.add(drawMiddleLine) outLines.push(drawMiddleLine) //마지막 외각선 halfLength = lastLine.length / 2 let drawLastLine1 = new QLine( [lastOuterLine.x2 - eaves, lastOuterLine.y1 - edge, lastOuterLine.x1 - halfLength, lastOuterLine.y2 - edge], centerLineOpt, ) canvas.add(drawLastLine1) let drawLastLine2 = new QLine( [drawLastLine1.x1, lastOuterLine.y1 - edge, lastOuterLine.x1 + length + eaves, lastOuterLine.y2 - edge], centerLineOpt, ) canvas.add(drawLastLine2) let drawLastInLine1 = new QLine( [secondVertCenterLine.x1, secondVertCenterLine.y2, secondVertCenterLine.x1 - halfLength - eaves, secondVertCenterLine.y2], centerLineOpt, ) canvas.add(drawLastInLine1) let drawLastInLine2 = new QLine([secondVertCenterLine.x2, vertCenterLine.y1, vertCenterLine.x1, vertCenterLine.y1], centerLineOpt) canvas.add(drawLastInLine2) bigRoofPolygon = [ { x: outLines[0].x1, y: outLines[0].y1 }, { x: outLines[0].x2, y: outLines[0].y2 }, { x: outLines[3].x1, y: outLines[3].y1 }, { x: outLines[3].x2, y: outLines[3].y2 }, { x: outLines[1].x1, y: outLines[1].y1 }, { x: outLines[1].x2, y: outLines[0].y1 }, ] middleRoofPolygon = [ { x: outLines[2].x1, y: outLines[2].y1 }, { x: outLines[2].x2, y: outLines[2].y2 }, { x: outLines[0].x2, y: outLines[0].y2 }, { x: outLines[0].x1, y: outLines[0].y1 }, ] smallRoofPolygon = [ { x: outLines[4].x1, y: outLines[4].y1 }, { x: outLines[4].x2, y: outLines[4].y2 }, { x: outLines[1].x2, y: outLines[1].y2 }, { x: outLines[1].x1, y: outLines[1].y1 }, ] } } else { if (horizontalDirection === 'left') { //아래쪽 길게 오른쪽 방향 //배열 순서대로 뒤에꺼를 찾아서 계산한다 firstLine = lines[5] secondLine = lines[3] lastLine = lines[1] const firstSubLine = lines[4] const middleSubLine = lines[2] //ㄴ자 일경우 //긴면 세로선 그리기 let vertCenterPoint = (firstLine.x1 + firstLine.x2) / 2 //가장 긴선 중앙선 가운데 점 vertCenterLine = new QLine( [ vertCenterPoint, firstSubLine.y2 - edge, //다음 선의 높이만큼 가져와서 edge길이를 합함 vertCenterPoint, firstSubLine.y1 + edge, //다음 선의 높이만큼 가져옴 edge길이를 합함 ], centerLineOpt, ) canvas.add(vertCenterLine) outLines.push(vertCenterLine) //긴면 가로선 그리기 let horiCenterPoint = (firstSubLine.y1 + firstSubLine.y2) / 2 let horiCenterLine1 = new QLine( [firstLine.x2 - eaves, horiCenterPoint, firstLine.x2 - eaves + firstLine.length / 2 + eaves, horiCenterPoint], dashedCenterLineOpt, ) canvas.add(horiCenterLine1) let horiCenterLine2 = new QLine( [ firstLine.x2 - eaves + firstLine.length / 2 + eaves, horiCenterPoint, firstLine.x2 - eaves + firstLine.length / 2 + eaves + firstLine.length / 2 + eaves, horiCenterPoint, ], dashedCenterLineOpt, ) canvas.add(horiCenterLine2) //작은 지붕쪽 높이 길이를 구하는 로직 let secondVertCenterPoint = (lastLine.x1 + lastLine.x2) / 2 secondVertCenterLine = new QLine([secondVertCenterPoint, middleSubLine.y1 + edge, secondVertCenterPoint, middleSubLine.y2], centerLineOpt) canvas.add(secondVertCenterLine) outLines.push(secondVertCenterLine) templateCenterLine.push(vertCenterLine) templateCenterLine.push(secondVertCenterLine) //작은 지붕쪽 너비 길이를 구한는 로직 let secondHoriCenterLength = (Math.abs(lastLine.get('x1') - lastLine.get('x2')) + Math.abs(lastLine.get('y1') - lastLine.get('y2'))) / 2 let secondHoriCenterPoint = (secondVertCenterLine.y1 + secondVertCenterLine.y2) / 2 let secondHoriCenterLine = new QLine( [secondVertCenterLine.x1 + secondHoriCenterLength + eaves, secondHoriCenterPoint, secondVertCenterLine.x1, secondHoriCenterPoint], dashedCenterLineOpt, ) canvas.add(secondHoriCenterLine) //일반라인 외각선 그리기 normalIndexArray.forEach((index) => { const line = lines[index] let points = [] if (index === 0) { points = [line.x1 - eaves, line.y1 - edge, line.x2 - eaves, line.y2 + edge] } else { let tmpEdge = index === 2 ? 0 : edge points = [line.x1 + eaves, line.y1 + edge, line.x2 + eaves, line.y2 - tmpEdge] } let drawline = new QLine(points, centerLineOpt) canvas.add(drawline) outLines.push(drawline) }) //케라바 라인 외각선 그리기 const firstOuterLine = lines[5] const middleOuterLine = lines[3] const lastOuterLine = lines[1] //첫번째 외곽선 1번 halfLength = firstOuterLine.length / 2 let drawFirstLine1 = new QLine( [firstOuterLine.x2 - eaves, firstOuterLine.y1 - edge, firstOuterLine.x2 + halfLength, firstOuterLine.y2 - edge], centerLineOpt, ) canvas.add(drawFirstLine1) //첫번째 외곽선 2번 let drawFirstLine2 = new QLine( [drawFirstLine1.x2, drawFirstLine1.y1, drawFirstLine1.x2 + halfLength + eaves, drawFirstLine1.y2], centerLineOpt, ) canvas.add(drawFirstLine2) //중간라인 외각선 let drawMiddleLine = new QLine([drawFirstLine2.x1, middleOuterLine.y1 + edge, drawFirstLine2.x2, middleOuterLine.y2 + edge], centerLineOpt) canvas.add(drawMiddleLine) //마지막 외각선 halfLength = lastLine.length / 2 let drawLastLine1 = new QLine( [lastOuterLine.x1 - eaves, lastOuterLine.y1 + edge, lastOuterLine.x1 + halfLength, lastOuterLine.y2 + edge], centerLineOpt, ) canvas.add(drawLastLine1) let drawLastLine2 = new QLine([drawLastLine1.x2, drawLastLine1.y1, drawLastLine1.x2 + halfLength + eaves, drawLastLine1.y1], centerLineOpt) canvas.add(drawLastLine2) let drawLastInLine1 = new QLine( [secondVertCenterLine.x1, secondVertCenterLine.y2, secondVertCenterLine.x1 + halfLength + eaves, secondVertCenterLine.y2], centerLineOpt, ) canvas.add(drawLastInLine1) let drawLastInLine2 = new QLine([vertCenterLine.x1, vertCenterLine.y2, secondVertCenterLine.x2, vertCenterLine.y2], centerLineOpt) canvas.add(drawLastInLine2) bigRoofPolygon = [ { x: outLines[2].x1, y: outLines[2].y1 }, { x: outLines[2].x2, y: outLines[2].y2 }, { x: outLines[1].x1, y: outLines[1].y1 }, { x: outLines[1].x2, y: outLines[0].y2 }, { x: outLines[0].x1, y: outLines[0].y2 }, { x: outLines[0].x1, y: outLines[0].y1 }, ] middleRoofPolygon = [ { x: outLines[0].x1, y: outLines[0].y1 }, { x: outLines[0].x2, y: outLines[0].y2 }, { x: outLines[4].x2, y: outLines[4].y2 }, { x: outLines[4].x1, y: outLines[4].y1 }, ] smallRoofPolygon = [ { x: outLines[1].x2, y: outLines[1].y2 }, { x: outLines[1].x1, y: outLines[1].y1 }, { x: outLines[3].x2, y: outLines[3].y2 }, { x: outLines[3].x1, y: outLines[3].y1 }, ] } else { //윗쪽 길게 오른쪽 방향 //배열 순서대로 뒤에꺼를 찾아서 계산한다 firstLine = lines[5] secondLine = lines[1] lastLine = lines[3] const firstSubLine = lines[0] const middleSubLine = lines[2] //ㄴ자 일경우 //긴면 세로선 그리기 let vertCenterPoint = (firstLine.x1 + firstLine.x2) / 2 //가장 긴선 중앙선 가운데 점 vertCenterLine = new QLine( [ vertCenterPoint, firstSubLine.y1 - edge, //다음 선의 높이만큼 가져와서 edge길이를 합함 vertCenterPoint, firstSubLine.y2 + edge, //다음 선의 높이만큼 가져옴 edge길이를 합함 ], centerLineOpt, ) canvas.add(vertCenterLine) outLines.push(vertCenterLine) //긴면 가로선 그리기 let horiCenterPoint = (firstSubLine.y1 + firstSubLine.y2) / 2 let horiCenterLine1 = new QLine( [firstLine.x2 - eaves, horiCenterPoint, firstLine.x2 - eaves + firstLine.length / 2 + eaves, horiCenterPoint], dashedCenterLineOpt, ) canvas.add(horiCenterLine1) let horiCenterLine2 = new QLine( [ firstLine.x2 - eaves + firstLine.length / 2 + eaves, horiCenterPoint, firstLine.x2 - eaves + firstLine.length / 2 + eaves + firstLine.length / 2 + eaves, horiCenterPoint, ], dashedCenterLineOpt, ) canvas.add(horiCenterLine2) //작은 지붕쪽 높이 길이를 구하는 로직 let secondVertCenterPoint = (lastLine.x1 + lastLine.x2) / 2 secondVertCenterLine = new QLine([secondVertCenterPoint, middleSubLine.y1, secondVertCenterPoint, middleSubLine.y2 + edge], centerLineOpt) canvas.add(secondVertCenterLine) outLines.push(secondVertCenterLine) templateCenterLine.push(vertCenterLine) templateCenterLine.push(secondVertCenterLine) //작은 지붕쪽 너비 길이를 구한는 로직 let secondHoriCenterLength = (Math.abs(lastLine.get('x1') - lastLine.get('x2')) + Math.abs(lastLine.get('y1') - lastLine.get('y2'))) / 2 let secondHoriCenterPoint = (secondVertCenterLine.y1 + secondVertCenterLine.y2) / 2 let secondHoriCenterLine = new QLine( [secondVertCenterLine.x1, secondHoriCenterPoint, secondVertCenterLine.x1 - secondHoriCenterLength - eaves, secondHoriCenterPoint], dashedCenterLineOpt, ) canvas.add(secondHoriCenterLine) //일반라인 외각선 그리기 normalIndexArray.forEach((index) => { const line = lines[index] let drawline if (index === 0 || index === 2) { let tmpEdge = index === 2 ? 0 : edge drawline = new QLine([line.x1 - eaves, line.y1 - tmpEdge, line.x2 - eaves, line.y2 + edge], centerLineOpt) canvas.add(drawline) } else { drawline = new QLine([line.x1 + eaves, line.y1 + edge, line.x2 + eaves, line.y2 - edge], centerLineOpt) canvas.add(drawline) } outLines.push(drawline) }) //케라바 라인 외각선 그리기 const firstOuterLine = lines[5] const middleOuterLine = lines[1] const lastOuterLine = lines[3] //첫번째 외곽선 1번 halfLength = firstOuterLine.length / 2 let drawFirstLine1 = new QLine( [firstOuterLine.x2 - eaves, firstOuterLine.y1 - edge, firstOuterLine.x2 + halfLength, firstOuterLine.y2 - edge], centerLineOpt, ) canvas.add(drawFirstLine1) //첫번째 외곽선 2번 let drawFirstLine2 = new QLine( [drawFirstLine1.x2, drawFirstLine1.y1, drawFirstLine1.x2 + halfLength + eaves, drawFirstLine1.y2], centerLineOpt, ) canvas.add(drawFirstLine2) //중간라인 외각선 let drawMiddleLine = new QLine([drawFirstLine1.x1, middleOuterLine.y1 + edge, drawFirstLine1.x2, middleOuterLine.y2 + edge], centerLineOpt) canvas.add(drawMiddleLine) //마지막 외각선 halfLength = lastLine.length / 2 let drawLastLine1 = new QLine( [lastOuterLine.x1 - eaves, lastOuterLine.y1 + edge, lastOuterLine.x1 + halfLength, lastOuterLine.y2 + edge], centerLineOpt, ) canvas.add(drawLastLine1) let drawLastLine2 = new QLine([drawLastLine1.x2, drawLastLine1.y1, drawLastLine1.x2 + halfLength + eaves, drawLastLine1.y1], centerLineOpt) canvas.add(drawLastLine2) let drawLastInLine1 = new QLine([vertCenterLine.x2, vertCenterLine.y2, secondVertCenterLine.x1, vertCenterLine.y2], centerLineOpt) canvas.add(drawLastInLine1) let drawLastInLine2 = new QLine([secondLine.x2 - eaves, secondLine.y1, drawLastLine1.x2, secondLine.y1], centerLineOpt) canvas.add(drawLastInLine2) bigRoofPolygon = [ { x: outLines[0].x1, y: outLines[0].y1 }, { x: outLines[0].x2, y: outLines[0].y2 }, { x: outLines[1].x1, y: outLines[0].y2 }, { x: outLines[1].x2, y: outLines[1].y2 }, { x: outLines[4].x1, y: outLines[4].y1 }, { x: outLines[4].x2, y: outLines[4].y2 }, ] middleRoofPolygon = [ { x: outLines[2].x1, y: outLines[2].y1 }, { x: outLines[2].x2, y: outLines[2].y2 }, { x: outLines[0].x2, y: outLines[0].y2 }, { x: outLines[0].x1, y: outLines[0].y1 }, ] smallRoofPolygon = [ { x: outLines[3].x1, y: outLines[3].y1 }, { x: outLines[3].x2, y: outLines[3].y2 }, { x: outLines[1].x2, y: outLines[1].y2 }, { x: outLines[1].x1, y: outLines[1].y1 }, ] } } roofPatternPolygonArray.push(bigRoofPolygon) //지붕폴리곤 roofPatternPolygonArray.push(middleRoofPolygon) //중간라인 폴리곤 roofPatternPolygonArray.push(smallRoofPolygon) //작은지붕폴리곤 setRoofPolygonPattern({ roofPatternPolygonArray, lines }) //모든 행을 저장 setTemplateCenterLine(templateCenterLine) //A,B 템플릿의 센터 라인을 저장 canvas?.renderAll() } const handleOuterLineTemplateA8Points = (polygon, offsetInputX = 20, offsetInputY = 50) => { let offsetPoints = [] const originalMax = 71 const transformedMax = 100 let lines = [] //내각라인 let outLines = [] //아웃라인 let halfLength = 0 //선길이 let offsetX let offsetY const dashedCenterLineOpt = { stroke: 'black', strokeWidth: 1, property: 'centerLine', strokeDashArray: [8, 4], fontSize: 14, } const centerLineOpt = { stroke: 'blue', strokeWidth: 2, property: 'bigHoriCenter', fontSize: 14, } // 폴리곤의 각 변을 선으로 생성 for (let i = 0; i < polygon.points.length; i++) { const start = polygon.points[i] const end = polygon.points[(i + 1) % polygon.points.length] // 다음 점, 마지막 점의 경우 첫 점으로 const line = new QLine([start.x, start.y, end.x, end.y], { stroke: '#A0D468', strokeWidth: 2, property: 'normal', fontSize: 14, }) // 선을 배열에 추가 lines.push(line) canvas.add(line) } offsetInputY = offsetInputY !== 0 ? offsetInputY : offsetInputX const sortedIndex = getStartIndex(polygon.lines) let tmpArraySorted = rearrangeArray(polygon.lines, sortedIndex) setSortedArray(tmpArraySorted) //recoil에 넣음 const points = tmpArraySorted.map((line) => ({ x: line.x1, y: line.y1, })) //좌표 재정렬 function reSortQlineArray(array) { let tmpArray = [] let minX, minY, maxX, maxY let tmp array.forEach((arr, index) => { tmp = arr if (arr.x2 < arr.x1 || arr.y2 < arr.y1) { minX = arr.x2 minY = arr.y2 maxX = arr.x1 maxY = arr.y1 tmp['x1'] = minX tmp['y1'] = minY tmp['x2'] = maxX tmp['y2'] = maxY tmp.line['x1'] = minX tmp.line['y1'] = minY tmp.line['x2'] = maxX tmp.line['y2'] = maxY } tmpArray.push(tmp) }) return tmpArray } // 오목한 부분 인덱스 찾기 const concaveIndicesObj = findConcavePointIndices(points) //오목한 부분을 제외한 인덱스 let concavePointIndices = concaveIndicesObj.concavePointIndices const concaveLine = { index: concavePointIndices[0], line: lines[concavePointIndices[0]], } for (let i = 0; i < points.length; i++) { let prev = points[(i - 1 + points.length) % points.length] let current = points[i] let next = points[(i + 1) % points.length] // 두 벡터 계산 (prev -> current, current -> next) let vector1 = { x: current.x - prev.x, y: current.y - prev.y } let vector2 = { x: next.x - current.x, y: next.y - current.y } // 벡터의 길이 계산 let length1 = Math.sqrt(vector1.x * vector1.x + vector1.y * vector1.y) let length2 = Math.sqrt(vector2.x * vector2.x + vector2.y * vector2.y) // 벡터를 단위 벡터로 정규화 let unitVector1 = { x: vector1.x / length1, y: vector1.y / length1 } let unitVector2 = { x: vector2.x / length2, y: vector2.y / length2 } // 법선 벡터 계산 (왼쪽 방향) let normal1 = { x: -unitVector1.y, y: unitVector1.x } let normal2 = { x: -unitVector2.y, y: unitVector2.x } // 법선 벡터 평균 계산 let averageNormal = { x: (normal1.x + normal2.x) / 2, y: (normal1.y + normal2.y) / 2, } // 평균 법선 벡터를 단위 벡터로 정규화 let lengthNormal = Math.sqrt(averageNormal.x * averageNormal.x + averageNormal.y * averageNormal.y) let unitNormal = { x: averageNormal.x / lengthNormal, y: averageNormal.y / lengthNormal, } offsetX = (offsetInputX / transformedMax) * originalMax * 2 offsetY = (offsetInputY / transformedMax) * originalMax * 2 // 오프셋 적용 let offsetPoint = { x1: current.x + unitNormal.x * offsetX, y1: current.y + unitNormal.y * offsetY, } offsetPoints.push(offsetPoint) } const outlinePolygon = makePolygon(offsetPoints, false) outlinePolygon.setViewLengthText(false) // 아웃라인 폴리곤의 각 변을 선으로 생성 for (let i = 0; i < outlinePolygon.points.length; i++) { const start = outlinePolygon.points[i] const end = outlinePolygon.points[(i + 1) % outlinePolygon.points.length] // 다음 점, 마지막 점의 경우 첫 점으로 const line = new QLine([start.x, start.y, end.x, end.y], { stroke: 'blue', strokeWidth: 2, property: 'normal', fontSize: 14, idx: i, }) // 선을 배열에 추가 outLines.push(line) canvas.add(line) } canvas?.remove(outlinePolygon) //임시 폴리곤을 삭제 let parallelLinesIdx = concavePointIndices[0] + 4 //들어간선에 무조건 평행하는 선 찾기 if (parallelLinesIdx >= outLines.length) { parallelLinesIdx = parallelLinesIdx - outLines.length } let vertCenterLine = [] let halfHoriCenterLinePoint = [] //카라바선의 2분할의 1번 배열 let horiCenterLine = [] let shorVertCenterLine = [] let edgeIndexArray = [] if (concavePointIndices[0] % 2 === 0) { //concave가 짝수면 좌우로 그려진 ㄷ자 //케라바 색을 바꾼다 lines.forEach((line, index) => { if (index % 2 === 0) { line.line.set('stroke', 'skyblue') if (concavePointIndices[0] !== index) { edgeIndexArray.push(index) } } }) outLines = reSortQlineArray(outLines) edgeIndexArray.forEach((idx, index) => { //가로라인이 케라바 라인임 if (concavePointIndices[0] !== idx) { //오목이가 아니면 반으로 갈라서 계산 //카라바 선의 2분할 치수를 그림 let halfLength = outLines[idx].length / 2 let centerLine1 = new QLine([outLines[idx].x1, outLines[idx].y1, outLines[idx].x1, outLines[idx].y1 + halfLength], centerLineOpt) canvas.add(centerLine1) let centerLine2 = new QLine([outLines[idx].x1, centerLine1.y2, outLines[idx].x2, centerLine1.y2 + halfLength], centerLineOpt) canvas.add(centerLine2) canvas.remove(outLines[idx]) //기존 라인 삭제 halfHoriCenterLinePoint.push({ index: idx, x1: centerLine1.x1, y1: centerLine1.y1, x2: centerLine1.x2, y2: centerLine1.y2, }) //각 카라바 라인의 1번이 마지막점을 잡아서 센터선으로 설정 } }) // //각 센터 라인을 그림 halfHoriCenterLinePoint.forEach((centerPoint) => { let tmpX2 = parallelLinesIdx !== centerPoint.index ? concaveLine.line.x2 : outLines[concavePointIndices[0]].x1 //평행선에서 내려오는 선은 아웃라인에 닿아야한다 let line = new QLine([centerPoint.x2, centerPoint.y2, tmpX2, centerPoint.y2], centerLineOpt) canvas.add(line) line['arrayIndex'] = centerPoint.index //커스텀으로 기존 index를 넣어줌 vertCenterLine.push(line) }) vertCenterLine = reSortQlineArray(vertCenterLine) lines = reSortQlineArray(lines) setTemplateCenterLine(vertCenterLine) //해당라인에서 만나는점을 계산 vertCenterLine.forEach((vertLine) => { if (parallelLinesIdx !== vertLine.arrayIndex) { //평행선을 제외한 애들만 네모를 연결 let nearLine let nearOutline if (vertLine.arrayIndex > concaveLine.index) { //센터에 인덱스가 오목점 보다 크면 다음 작으면 앞에꺼 nearLine = lines[concaveLine.index + 1] nearOutline = outLines[concaveLine.index + 1] } else { nearLine = lines[concaveLine.index - 1] nearOutline = outLines[concaveLine.index - 1] } let nearLineY = nearLine.y1 if (parallelLinesIdx < concaveLine.index) { //오목점 위치가 평행선보다 크면 위쪽으로 오목 nearLineY = nearLine.y2 } //기존에 있는 라인에서 연장해서 새로 그림 let centerExtendHoriLine = new QLine([vertLine.x1, nearOutline.line.y1, vertLine.x2, nearOutline.line.y2], centerLineOpt) canvas.add(centerExtendHoriLine) canvas.remove(nearOutline) outLines.splice(nearOutline.idx, 1, centerExtendHoriLine) //아웃라인에 데이터를 다시 넣는다 //가로형에선 기본으로 ㄷ자 형태로 한다 let centerExtendLine = new QLine([vertLine.line.x1, vertLine.line.y1, centerExtendHoriLine.x1, centerExtendHoriLine.y1], centerLineOpt) //오목이가 배열에 반보다 작으면 역 ㄷ자 여서 변경 if (concavePointIndices[0] < outLines.length / 2) { centerExtendLine = new QLine([vertLine.line.x2, vertLine.line.y2, centerExtendHoriLine.x2, centerExtendHoriLine.y2], centerLineOpt) } canvas.add(centerExtendLine) //새로그리고 let betweenCenterLine = (vertLine.line.x1 + vertLine.line.x2) / 2 let centerDashLine = new QLine([betweenCenterLine, centerExtendLine.y1, betweenCenterLine, centerExtendLine.y2], dashedCenterLineOpt) canvas.add(centerDashLine) horiCenterLine.push(centerDashLine) shorVertCenterLine.push(vertLine) //마지막에 가운데 선을 긋기 위해 담음 } else { let longDashLine = halfHoriCenterLinePoint.find((obj) => obj.index === parallelLinesIdx) //평행선 let dashCenterExtendLineLength = longDashLine.y2 - longDashLine.y1 //y반개 길이 let betweenCenterLine = (vertLine.line.x1 + vertLine.line.x2) / 2 //y의 길이 let totalLength = ((longDashLine.y2 - longDashLine.y1) * 2) / dashCenterExtendLineLength //2개로 나눔 //반 쪼개서 그린다 for (let i = 0; i < totalLength; i++) { //2번에 나눠서 let startY = i === 0 ? longDashLine.y1 : longDashLine.y1 + dashCenterExtendLineLength //시작 하는 y의 좌표 //x값은 고정이임 //TODO: 지붕 각도 계산법에 의해 재계산해야함 let centerDashLine = new QLine([betweenCenterLine, startY, betweenCenterLine, startY + dashCenterExtendLineLength], dashedCenterLineOpt) canvas.add(centerDashLine) horiCenterLine.push(centerDashLine) } } }) //마지막에 오목한 외곽선을 연장한다 const tmpLastOutLine = outLines[concavePointIndices[0]] const lastOutLine = new QLine([tmpLastOutLine.x1, shorVertCenterLine[0].y1, tmpLastOutLine.x1, shorVertCenterLine[1].y1], centerLineOpt) canvas.add(lastOutLine) canvas.remove(tmpLastOutLine) //폴리곤 패턴을 그리기 위해 작성 let tmpVertCenterLine = outLines.filter((x, index) => index % 2 !== 0) //세로만 찾음 tmpVertCenterLine = tmpVertCenterLine.concat(vertCenterLine) tmpVertCenterLine.sort((a, b) => a.y1 - b.y1) tmpVertCenterLine.push(lastOutLine) let roofPatternPolygonArray = [] let tmpArray = [] let tmpBigArray = [] const lastCenterLine = tmpVertCenterLine[tmpVertCenterLine.length - 1] //마지막 센터라인을 정의 for (let i = 0; i < tmpVertCenterLine.length - 1; i++) { //-1인건 마지막은 오목한 선이라 돌 필요 없음 //라인 하나에 두점씩 나온다 let firstPointObj = {} let secondPointObj = {} let x1 = tmpVertCenterLine[i].x1 let y1 = tmpVertCenterLine[i].y1 let x2 = tmpVertCenterLine[i].x2 let y2 = tmpVertCenterLine[i].y2 if (i === 2 || i === 4) { //작은 네모들 tmpArray = [] const prevLine = tmpVertCenterLine[i - 1] //뒤에서 앞라인을 찾는다 const nextLine = tmpVertCenterLine[i + 1] //내 앞뒤 라인 const tmpX1 = i === 2 ? prevLine.x1 : nextLine.x1 const tmpY1 = i === 2 ? prevLine.y1 : nextLine.y1 const tmpX2 = i === 2 ? prevLine.x2 : nextLine.x2 const tmpY2 = i === 2 ? prevLine.y2 : nextLine.y2 firstPointObj = { x: tmpX1, y: tmpY1 } secondPointObj = { x: tmpX2, y: tmpY2 } tmpArray.push(firstPointObj) tmpArray.push(secondPointObj) //현재 내 선 firstPointObj = { x: x1, y: y1 } secondPointObj = { x: x2, y: y2 } tmpArray.push(firstPointObj) tmpArray.push(secondPointObj) roofPatternPolygonArray.push(tmpArray) } else { //큰 육각 if (i === 1 || i === 5) { // 큰 폴리곤은 가운데 선으로 되야됨 if (outLines.length / 2 > concavePointIndices[0]) { x2 = i === 1 ? lastCenterLine.x1 : lastCenterLine.x2 y2 = i === 1 ? lastCenterLine.y1 : lastCenterLine.y2 } else { //오목이가 배열 전체보다 크면 오른쪽 오목이 x1 = i === 1 ? lastCenterLine.x1 : lastCenterLine.x2 y1 = i === 1 ? lastCenterLine.y2 : lastCenterLine.y1 } } if (i === 5) { //5번일때는 앞에 3번에 선이 필요하다 let prevX1 = tmpVertCenterLine[i - 2].x1 let prevY1 = tmpVertCenterLine[i - 2].y1 let prevX2 = tmpVertCenterLine[i - 2].x2 let prevY2 = tmpVertCenterLine[i - 2].y2 firstPointObj = { x: prevX1, y: prevY1 } secondPointObj = { x: prevX2, y: prevY2 } tmpBigArray.push(firstPointObj) tmpBigArray.push(secondPointObj) } firstPointObj = { x: x1, y: y1 } secondPointObj = { x: x2, y: y2 } tmpBigArray.push(firstPointObj) tmpBigArray.push(secondPointObj) if (i === 3 || i === 6) { roofPatternPolygonArray.push(tmpBigArray) tmpBigArray = [] } } } setRoofPolygonPattern({ roofPatternPolygonArray, lines }) } else { // 오목한 부분이 세로선일때 아래ㄷ, 위ㄷ //라인들을 좌측에서 -> 우측으로 그리는거처럼 데이터 보정 lines.forEach((line, index) => { if (!(index % 2 === 0)) { line.line.set('stroke', 'skyblue') } }) outLines = reSortQlineArray(outLines) outLines.forEach((outline, index) => { if (!(index % 2 === 0)) { //세로라인이 케라바 라인임 if (concavePointIndices[0] !== index) { //오목이가 아니면 반으로 갈라서 계산 //카라바 선의 2분할 치수를 그림 let halfLength = outline.length / 2 let centerLine1 = new QLine([outline.x1, outline.y1, outline.x1 + halfLength, outline.y1], centerLineOpt) canvas.add(centerLine1) let centerLine2 = new QLine([centerLine1.x2, outline.y1, centerLine1.x2 + halfLength, outline.y1], centerLineOpt) canvas.add(centerLine2) canvas.remove(outline) //기존 라인 삭제 halfHoriCenterLinePoint.push({ index: index, x1: centerLine1.x1, y1: centerLine1.y1, x2: centerLine1.x2, y2: centerLine1.y2, }) //각 카라바 라인의 1번이 마지막점을 잡아서 센터선으로 설정 } } }) //각 센터 라인을 그림 halfHoriCenterLinePoint.forEach((centerPoint) => { let tmpY2 = parallelLinesIdx !== centerPoint.index ? concaveLine.line.y1 : outLines[concavePointIndices[0]].y2 //평행선에서 내려오는 선은 아웃라인에 닿아야한다 let line = new QLine([centerPoint.x2, centerPoint.y1, centerPoint.x2, tmpY2], centerLineOpt) canvas.add(line) line['arrayIndex'] = centerPoint.index //커스텀으로 기존 index를 넣어줌 vertCenterLine.push(line) }) vertCenterLine = reSortQlineArray(vertCenterLine) lines = reSortQlineArray(lines) setTemplateCenterLine(vertCenterLine) //해당라인에서 만나는점을 계산 vertCenterLine.forEach((vertLine) => { if (parallelLinesIdx !== vertLine.arrayIndex) { //평행선을 제외한 애들만 네모를 연결 let nearLine let nearOutline if (vertLine.arrayIndex > concaveLine.index) { //센터에 인덱스가 오목점 보다 크면 다음 작으면 앞에꺼 nearLine = lines[concaveLine.index + 1] nearOutline = outLines[concaveLine.index + 1] } else { nearLine = lines[concaveLine.index - 1] nearOutline = outLines[concaveLine.index - 1] } let nearLineY = nearLine.y1 if (parallelLinesIdx < concaveLine.index) { //오목점 위치가 평행선보다 크면 위쪽으로 오목 nearLineY = nearLine.y2 } let centerExtendLine = new QLine([vertLine.line.x1, nearLineY, nearOutline.x1, nearLineY], centerLineOpt) canvas.add(centerExtendLine) //새로그리고 //기존에 있는 라인에서 연장해서 새로 그림 let centerExtendHoriLine = new QLine([nearOutline.line.x1, vertLine.y1, nearOutline.line.x2, vertLine.line.y2], centerLineOpt) canvas.add(centerExtendHoriLine) canvas.remove(nearOutline) outLines.splice(nearOutline.idx, 1, centerExtendHoriLine) //아웃라인에 데이터를 다시 넣는다 let betweenCenterLine = (vertLine.line.y1 + vertLine.line.y2) / 2 let centerDashLine = new QLine([vertLine.line.x1, betweenCenterLine, nearOutline.x1, betweenCenterLine], dashedCenterLineOpt) canvas.add(centerDashLine) horiCenterLine.push(centerDashLine) shorVertCenterLine.push(vertLine) //마지막에 가운데 선을 긋기 위해 담음 } else { let longDashLine = halfHoriCenterLinePoint.find((obj) => obj.index === parallelLinesIdx) let dashCenterExtendLineLength = longDashLine.x2 - longDashLine.x1 let betweenCenterLine = (vertLine.line.y1 + vertLine.line.y2) / 2 let totalLength = ((longDashLine.x2 - longDashLine.x1) * 2) / dashCenterExtendLineLength //반 쪼개서 그린다 for (let i = 0; i < totalLength; i++) { let startX = i === 0 ? longDashLine.x1 : longDashLine.x1 + dashCenterExtendLineLength let centerDashLine = new QLine([startX, betweenCenterLine, startX + dashCenterExtendLineLength, betweenCenterLine], dashedCenterLineOpt) canvas.add(centerDashLine) horiCenterLine.push(centerDashLine) } } }) //마지막에 오목한 외곽선을 연장한다 const tmpLastOutLine = outLines[concavePointIndices[0]] const lastOutLine = new QLine([shorVertCenterLine[0].x1, tmpLastOutLine.y1, shorVertCenterLine[1].x1, tmpLastOutLine.y2], centerLineOpt) canvas.add(lastOutLine) canvas.remove(tmpLastOutLine) let tmpVertCenterLine = outLines.filter((x, index) => index % 2 === 0) //세로만 찾음 tmpVertCenterLine = tmpVertCenterLine.concat(vertCenterLine) tmpVertCenterLine.sort((a, b) => a.x1 - b.x1) tmpVertCenterLine.push(lastOutLine) let roofPatternPolygonArray = [] let tmpArray = [] let tmpBigArray = [] const lastCenterLine = tmpVertCenterLine[tmpVertCenterLine.length - 1] //마지막 센터라인을 정의 for (let i = 0; i < tmpVertCenterLine.length - 1; i++) { //-1인건 마지막은 오목한 선이라 돌 필요 없음 //라인 하나에 두점씩 나온다 let firstPointObj = {} let secondPointObj = {} let x1 = tmpVertCenterLine[i].x1 let y1 = tmpVertCenterLine[i].y1 let x2 = tmpVertCenterLine[i].x2 let y2 = tmpVertCenterLine[i].y2 if (i === 2 || i === 4) { tmpArray = [] const prevLine = tmpVertCenterLine[i - 1] //뒤에서 앞라인을 찾는다 const nextLine = tmpVertCenterLine[i + 1] //내 앞뒤 라인 const tmpX1 = i === 2 ? prevLine.x1 : nextLine.x1 const tmpY1 = i === 2 ? prevLine.y1 : nextLine.y1 const tmpX2 = i === 2 ? prevLine.x2 : nextLine.x2 const tmpY2 = i === 2 ? prevLine.y2 : nextLine.y2 firstPointObj = { x: tmpX1, y: tmpY1 } secondPointObj = { x: tmpX2, y: tmpY2 } tmpArray.push(firstPointObj) tmpArray.push(secondPointObj) //현재 내 선 firstPointObj = { x: x1, y: y1 } secondPointObj = { x: x2, y: y2 } tmpArray.push(firstPointObj) tmpArray.push(secondPointObj) roofPatternPolygonArray.push(tmpArray) } else { if (i === 1 || i === 5) { // 큰 폴리곤은 가운데 선으로 되야됨 if (outLines.length / 2 < concavePointIndices[0]) { //오목이가 배열 전체보다 크면 위쪽 방향 x2 = i === 1 ? lastCenterLine.x2 : lastCenterLine.x1 y2 = i === 1 ? lastCenterLine.y2 : lastCenterLine.y1 } else { x1 = i === 1 ? lastCenterLine.x1 : lastCenterLine.x2 y1 = i === 1 ? lastCenterLine.y1 : lastCenterLine.y2 } } if (i === 5) { //5번일때는 앞에 3번에 선이 필요하다 let prevX1 = tmpVertCenterLine[i - 2].x1 let prevY1 = tmpVertCenterLine[i - 2].y1 let prevX2 = tmpVertCenterLine[i - 2].x2 let prevY2 = tmpVertCenterLine[i - 2].y2 firstPointObj = { x: prevX1, y: prevY1 } secondPointObj = { x: prevX2, y: prevY2 } tmpBigArray.push(firstPointObj) tmpBigArray.push(secondPointObj) } firstPointObj = { x: x1, y: y1 } secondPointObj = { x: x2, y: y2 } tmpBigArray.push(firstPointObj) tmpBigArray.push(secondPointObj) if (i === 3 || i === 6) { roofPatternPolygonArray.push(tmpBigArray) tmpBigArray = [] } } } setRoofPolygonPattern({ roofPatternPolygonArray, lines }) } canvas?.renderAll() } /** * 템플릿 B 적용 */ const applyTemplateB = () => { if (historyPoints.current.length === 0) { changeMode(canvas, Mode.EDIT) return } const polygon = drawWallPolygon(false) const params = { eaves: 50, edge: 20, polygon, } handleInnerLineColor(polygon) // handleOuterLineTemplateB(params) console.log(polygon.lines.length) if (polygon.lines.length === 4) { console.log('4각형') handleTemplateBRect(params) } else if (polygon.lines.length === 6) { console.log('6각형') handleTemplateB(params) } else if (polygon.lines.length === 8) { handleOuterLineTemplateB8Points(polygon) } setTemplateType(3) } /** * 4각형일때 계산 로직 * @param {obj} params */ const handleTemplateBRect = (params) => { const { eaves, edge, polygon } = params const centerLinePoint = {} const centerDashLinePoint = {} const qlineOpt = { stroke: 'blue', strokeWidth: 2, selectable: false, fontSize: fontSize, } const qlineOptDash = { stroke: 'black', strokeWidth: 2, strokeDashArray: [5, 5], selectable: false, fontSize: fontSize, } const qlineOptDashWithoutLength = { ...qlineOptDash, isActiveLengthText: false, } const bigRoofPolygon = [] const middleRoofPolygon = [] polygon.lines.forEach((line, index) => { let outline if (index === 0) { outline = new QLine([line.x1 - edge, line.y1 - eaves, line.x2 - edge, line.y2 + eaves], qlineOpt) const centeredPoint = getCenterPoint(line.y1, line.y2) centerLinePoint.x1 = line.x1 - edge centerLinePoint.y1 = centeredPoint centerLinePoint.y2 = centeredPoint centerDashLinePoint.y1 = line.y1 - eaves centerDashLinePoint.y2 = line.y2 + eaves bigRoofPolygon[0] = { x: line.x1 - edge, y: line.y1 - eaves } middleRoofPolygon[0] = { x: line.x1 - edge, y: centeredPoint } middleRoofPolygon[1] = { x: line.x2 - edge, y: line.y2 + eaves } } else if (index === 1) { outline = new QLine([line.x1 - edge, line.y1 + eaves, line.x2 + edge, line.y2 + eaves], qlineOpt) const centeredPoint = getCenterPoint(line.x1, line.x2) centerLinePoint.x2 = line.x2 + edge centerDashLinePoint.x1 = centeredPoint centerDashLinePoint.x2 = centeredPoint } else if (index === 2) { outline = new QLine([line.x1 + edge, line.y1 + eaves, line.x2 + edge, line.y2 - eaves], qlineOpt) bigRoofPolygon[3] = { x: line.x2 + edge, y: line.y2 - eaves } middleRoofPolygon[2] = { x: line.x1 + edge, y: line.y1 + eaves } } else if (index === 3) { outline = new QLine([line.x1 + edge, line.y1 - eaves, line.x2 - edge, line.y2 - eaves], qlineOpt) } canvas.add(outline) }) const centerLine = new QLine([centerLinePoint.x1, centerLinePoint.y1, centerLinePoint.x2, centerLinePoint.y2], qlineOpt) canvas.add(centerLine) const centerDashLine1 = new QLine( [centerDashLinePoint.x1, centerDashLinePoint.y1, centerDashLinePoint.x2, getCenterPoint(centerDashLinePoint.y1, centerDashLinePoint.y2)], qlineOptDashWithoutLength, ) canvas.add(centerDashLine1) const centerDashLine2 = new QLine( [centerDashLinePoint.x1, getCenterPoint(centerDashLinePoint.y1, centerDashLinePoint.y2), centerDashLinePoint.x2, centerDashLinePoint.y2], qlineOptDashWithoutLength, ) canvas.add(centerDashLine2) bigRoofPolygon[1] = { x: centerLine.x1, y: centerLine.y1 } bigRoofPolygon[2] = { x: centerLine.x2, y: centerLine.y2 } middleRoofPolygon[3] = { x: centerLinePoint.x2, y: centerLinePoint.y2 } const roofPatternPolygonArray = [] roofPatternPolygonArray.push(bigRoofPolygon) roofPatternPolygonArray.push(middleRoofPolygon) if (roofPatternPolygonArray.length > 0) { setRoofPolygonPattern({ roofPatternPolygonArray, lines: polygon.lines }) } canvas?.renderAll() } /** * 6각형일때 계산 로직 * @param {obj} params */ const handleTemplateB = (params) => { const { eaves, edge, polygon } = params // 가장 긴 라인이 첫번째일때 let shapeType = 0 console.log(polygon) const odd = polygon.lines.filter((line, index) => index % 2 === 0) const even = polygon.lines.filter((line, index) => index % 2 !== 0) const rerangeOdd = chgLineDirectionVertical(odd) const rerangeEven = chgLineDirectionHorizontal(even) // 가장 긴 라인이 첫번째인지 판단 chkLengthIndex({ arr: odd, type: 'L' }) !== 0 ? (shapeType = 1) : null // 가장 짧은 라인의 인덱스 반환 const shortIndex = chkLengthIndex({ arr: odd, type: 'S' }) const centralLinePoint = { x1: 0, y1: 0, x2: 0, y2: 0, } const centralSubLinePoint = { x1: 0, y1: 0, x2: 0, y2: 0, } const centralDashLinePoint = { x1: 0, y1: 0, x2: 0, y2: 0, } const centralSubDashLinePoint = { x1: 0, y1: 0, x2: 0, y2: 0, } const qlineOpt = { stroke: 'blue', strokeWidth: 2, selectable: false, fontSize: fontSize, } const qlineOptDash = { stroke: 'black', strokeWidth: 2, strokeDashArray: [5, 5], selectable: false, fontSize: fontSize, } /** * 지붕 패턴을 위한 폴리곤 좌표 생성 */ const bigRoofPolygon = [] const middleRoofPolygon = [] const smallRoofPolygon = [] const templateCenterLine = [] rerangeOdd.forEach((line, index) => { const centeredPoint = getCenterPoint(line.y1, line.y2) let points1 = [] let points2 = [] if (polygon.shape === 2 || polygon.shape === 3) { if (index === 0) { points1 = [line.x1 - edge, line.y1 - eaves, line.x1 - edge, centeredPoint] points2 = [line.x2 - edge, centeredPoint, line.x2 - edge, line.y2 + eaves] centralLinePoint.x1 = line.x1 - edge centralLinePoint.y1 = centeredPoint centralLinePoint.y2 = centeredPoint centralDashLinePoint.y1 = line.y1 - eaves centralDashLinePoint.y2 = line.y2 + eaves if (polygon.shape === 2) { middleRoofPolygon[0] = { x: line.x1 - edge, y: line.y1 - eaves } middleRoofPolygon[1] = { x: line.x1 - edge, y: centeredPoint } } else { bigRoofPolygon[1] = { x: line.x1 - edge, y: centeredPoint } bigRoofPolygon[0] = { x: line.x1 - edge, y: line.y1 - eaves } middleRoofPolygon[1] = { x: line.x2 - edge, y: line.y2 + eaves } } } else if (index === 1) { if (polygon.shape === 2) { points1 = [line.x1 + edge, line.y1 - eaves, line.x1 + edge, centeredPoint] points2 = [line.x1 + edge, centeredPoint, line.x2 + edge, line.y2 + eaves] centralSubLinePoint.x2 = line.x1 + edge centralSubLinePoint.y2 = centeredPoint bigRoofPolygon[2] = { x: line.x2 + edge, y: line.y2 + eaves } bigRoofPolygon[3] = { x: line.x2 + edge, y: centeredPoint } smallRoofPolygon[0] = { x: line.x1 + edge, y: centeredPoint } smallRoofPolygon[1] = { x: line.x1 + edge, y: line.y1 - eaves } } else { points1 = [line.x1 + edge, getCenterPoint(rerangeOdd[2].y1, rerangeOdd[2].y2), line.x2 + edge, line.y2 + eaves] points2 = [line.x1, getCenterPoint(rerangeOdd[2].y1, rerangeOdd[2].y2), line.x1, line.y1 + eaves] centralLinePoint.x2 = line.x1 + edge centralSubLinePoint.y1 = getCenterPoint(rerangeOdd[2].y1, rerangeOdd[2].y2) centralSubLinePoint.y2 = getCenterPoint(rerangeOdd[2].y1, rerangeOdd[2].y2) bigRoofPolygon[3] = { x: line.x1 + edge, y: centralSubLinePoint.y1 } middleRoofPolygon[2] = { x: line.x2 + edge, y: line.y2 + eaves } smallRoofPolygon[0] = { x: line.x1, y: centralSubLinePoint.y1 } smallRoofPolygon[1] = { x: line.x1, y: line.y1 + eaves } } } else if (index === 2) { if (polygon.shape === 2) { points1 = [line.x1 + edge, line.y1 - eaves, line.x2 + edge, centralSubLinePoint.y2] points2 = [line.x2, line.y2 - eaves, line.x2, centralSubLinePoint.y2] bigRoofPolygon[4] = { x: line.x2 + edge, y: centralSubLinePoint.y2 } middleRoofPolygon[3] = { x: line.x1 + edge, y: line.y1 - eaves } smallRoofPolygon[2] = { x: line.x2, y: line.y2 - eaves } smallRoofPolygon[3] = { x: line.x2, y: centralSubLinePoint.y2 } } else { points1 = [line.x1 + edge, line.y1 - eaves, line.x2 + edge, centeredPoint] points2 = [line.x2 + edge, centeredPoint, line.x2 + edge, line.y2 + eaves] bigRoofPolygon[4] = { x: line.x1 + edge, y: centralSubLinePoint.y2 } bigRoofPolygon[5] = { x: line.x1 + edge, y: line.y1 - eaves } smallRoofPolygon[2] = { x: line.x2 + edge, y: line.y2 + eaves } smallRoofPolygon[3] = { x: line.x2 + edge, y: centeredPoint } } } } else { if (index === 0) { points1 = [line.x1 - edge, line.y1 - eaves, line.x2 - edge, line.y2 + eaves] centralSubLinePoint.x1 = line.x1 - edge centralSubLinePoint.y1 = getCenterPoint(line.y1, line.y2) centralSubLinePoint.y2 = getCenterPoint(line.y1, line.y2) if (polygon.shape === 1) { bigRoofPolygon[0] = { x: line.x1 - edge, y: line.y1 - eaves } bigRoofPolygon[1] = { x: line.x1 - edge, y: getCenterPoint(line.y1, line.y2) } smallRoofPolygon[0] = { x: line.x1 - edge, y: getCenterPoint(line.y1, line.y2) } smallRoofPolygon[1] = { x: line.x2 - edge, y: line.y2 + eaves } } else { bigRoofPolygon[0] = { x: line.x1 - edge, y: centeredPoint } bigRoofPolygon[1] = { x: line.x1 - edge, y: line.y2 + eaves } smallRoofPolygon[0] = { x: line.x1 - edge, y: line.y1 - eaves } smallRoofPolygon[1] = { x: centralSubLinePoint.x1, y: centralSubLinePoint.y1 } } } else if (index === 1) { if (polygon.shape === 1) { points1 = [line.x1 - edge, centralSubLinePoint.y1, line.x2 - edge, line.y2 + eaves] points2 = [line.x1, centralSubLinePoint.y1, line.x1, line.y1 + eaves] centralLinePoint.x1 = line.x1 - edge centralSubLinePoint.x2 = line.x2 bigRoofPolygon[2] = { x: line.x1 - edge, y: centralSubLinePoint.y2 } middleRoofPolygon[1] = { x: line.x2 - edge, y: line.y2 + eaves } smallRoofPolygon[2] = { x: line.x1, y: line.y1 + eaves } } else { points1 = [line.x1 + edge, line.y1 - eaves, line.x2 + edge, centeredPoint] points2 = [line.x2 + edge, centeredPoint, line.x2 + edge, line.y2 + eaves] centralLinePoint.x2 = line.x1 + edge centralLinePoint.y1 = centeredPoint centralLinePoint.y2 = centeredPoint centralDashLinePoint.y1 = line.y1 - eaves centralDashLinePoint.y2 = line.y2 + eaves bigRoofPolygon[2] = { x: line.x2 + edge, y: line.y2 + eaves } bigRoofPolygon[3] = { x: line.x2 + edge, y: centralLinePoint.y2 } middleRoofPolygon[3] = { x: line.x1 + edge, y: line.y1 - eaves } } } else { if (polygon.shape === 1) { points1 = [line.x1 + edge, line.y1 - eaves, line.x1 + edge, centeredPoint] points2 = [line.x2 + edge, centeredPoint, line.x2 + edge, line.y2 + eaves] centralLinePoint.x2 = line.x1 + edge centralLinePoint.y1 = centeredPoint centralLinePoint.y2 = centeredPoint centralDashLinePoint.y1 = line.y1 - eaves centralDashLinePoint.y2 = line.y2 + eaves bigRoofPolygon[5] = { x: line.x1 + edge, y: line.y1 - eaves } middleRoofPolygon[2] = { x: line.x2 + edge, y: line.y2 + eaves } } else { points1 = [line.x1 - edge, line.y1 - eaves, line.x2 - edge, centralSubLinePoint.y1] points2 = [line.x2, line.y2 - eaves, line.x2, centralSubLinePoint.y2] centralLinePoint.x1 = line.x1 - edge centralSubLinePoint.x2 = line.x2 bigRoofPolygon[4] = { x: line.x2 - edge, y: centralLinePoint.y1 } bigRoofPolygon[5] = { x: line.x2 - edge, y: centralSubLinePoint.y2 } middleRoofPolygon[0] = { x: line.x1 - edge, y: line.y1 - eaves } smallRoofPolygon[2] = { x: line.x2, y: centralSubLinePoint.y2 } smallRoofPolygon[3] = { x: line.x2, y: line.y2 - eaves } } } } if (points1.length > 0) { const subLine1 = new QLine(points1, qlineOpt) canvas.add(subLine1) } if (points2.length > 0) { const subLine2 = new QLine(points2, qlineOpt) canvas.add(subLine2) } }) rerangeEven.forEach((line, index) => { let points = [] if (polygon.shape === 2 || polygon.shape === 3) { if (index === 0) { points = [line.x1 - edge, line.y1 + eaves, line.x2 + edge, line.y2 + eaves] if (polygon.shape === 3) { centralDashLinePoint.x1 = getCenterPoint(line.x1, line.x2) centralDashLinePoint.x2 = getCenterPoint(line.x1, line.x2) } } else if (index === 2) { points = [line.x1 - edge, line.y1 - eaves, line.x2 + edge, line.y2 - eaves] if (polygon.shape === 2) { centralDashLinePoint.x1 = getCenterPoint(line.x1, line.x2) centralDashLinePoint.x2 = getCenterPoint(line.x1, line.x2) centralLinePoint.x2 = line.x2 + edge } } else { if (polygon.shape === 2) { const subLines = [ [line.x1, line.y1 - eaves, line.x2 + edge, line.y2 - eaves], [line.x1, centralSubLinePoint.y2, centralSubLinePoint.x2, centralSubLinePoint.y2], ] subLines.forEach((sLine, index) => { const subLine = new QLine(sLine, qlineOpt) if (index === 1) { templateCenterLine.push(subLine) } canvas.add(subLine) }) centralSubDashLinePoint.x1 = getCenterPoint(line.x1, line.x2 + edge) centralSubDashLinePoint.x2 = getCenterPoint(line.x1, line.x2 + edge) centralSubDashLinePoint.y1 = line.y1 - eaves centralSubDashLinePoint.y2 = centralSubLinePoint.y2 } else { const subLines = [ [line.x1, line.y1 + eaves, line.x2 + edge, line.y2 + eaves], [line.x1, centralSubLinePoint.y1, line.x2 + edge, centralSubLinePoint.y2], ] subLines.forEach((sLine, index) => { const subLine = new QLine(sLine, qlineOpt) if (index === 1) { templateCenterLine.push(subLine) } canvas.add(subLine) }) centralSubDashLinePoint.x1 = getCenterPoint(line.x1, line.x2 + edge) centralSubDashLinePoint.x2 = getCenterPoint(line.x1, line.x2 + edge) centralSubDashLinePoint.y1 = centralSubLinePoint.y1 centralSubDashLinePoint.y2 = line.y2 + eaves } } } else { if (index === 0) { if (polygon.shape === 1) { const subLines = [ [centralSubLinePoint.x1, centralSubLinePoint.y1, centralSubLinePoint.x2, centralSubLinePoint.y2], [line.x1 - edge, line.y1 + eaves, line.x2, line.y1 + eaves], ] subLines.forEach((sLine, index) => { const subLine = new QLine(sLine, qlineOpt) if (index === 0) { templateCenterLine.push(subLine) } canvas.add(subLine) }) centralSubDashLinePoint.x1 = getCenterPoint(line.x1 - edge, line.x2) centralSubDashLinePoint.x2 = getCenterPoint(line.x1 - edge, line.x2) centralSubDashLinePoint.y1 = centralSubLinePoint.y1 centralSubDashLinePoint.y2 = line.y2 + eaves } else { points = [line.x1 - edge, line.y1 + eaves, line.x2 + edge, line.y2 + eaves] } } else if (index === 1) { if (polygon.shape === 1) { points = [line.x1 - edge, line.y1 + eaves, line.x2 + edge, line.y2 + eaves] centralDashLinePoint.x1 = getCenterPoint(line.x1, line.x2) centralDashLinePoint.x2 = getCenterPoint(line.x1, line.x2) } else { points = [line.x1 - edge, line.y1 - eaves, line.x2 + edge, line.y2 - eaves] centralDashLinePoint.x1 = getCenterPoint(line.x1, line.x2) centralDashLinePoint.x2 = getCenterPoint(line.x1, line.x2) } } else { if (polygon.shape === 1) { points = [line.x1 - edge, line.y1 - eaves, line.x2 + edge, line.y2 - eaves] } else { const subLines = [ [centralSubLinePoint.x1, centralSubLinePoint.y1, centralSubLinePoint.x2, centralSubLinePoint.y2], [line.x1 - edge, line.y1 - eaves, line.x2, line.y1 - eaves], ] subLines.forEach((sLine, index) => { const subLine = new QLine(sLine, qlineOpt) if (index === 0) { templateCenterLine.push(subLine) } canvas.add(subLine) }) centralSubDashLinePoint.x1 = getCenterPoint(line.x1 - edge, line.x2) centralSubDashLinePoint.x2 = getCenterPoint(line.x1 - edge, line.x2) centralSubDashLinePoint.y1 = line.y1 - eaves centralSubDashLinePoint.y2 = centralSubLinePoint.y2 } } } if (points.length > 0) { const subLine = new QLine(points, qlineOpt) canvas.add(subLine) } }) const centralLine = new QLine([centralLinePoint.x1, centralLinePoint.y1, centralLinePoint.x2, centralLinePoint.y2], qlineOpt) canvas.add(centralLine) const centralDashLine1 = new QLine([centralDashLinePoint.x1, centralDashLinePoint.y1, centralDashLinePoint.x1, centralLinePoint.y2], qlineOptDash) canvas.add(centralDashLine1) const centralDashLine2 = new QLine([centralDashLine1.x2, centralDashLine1.y2, centralDashLinePoint.x2, centralDashLinePoint.y2], qlineOptDash) canvas.add(centralDashLine2) const centralSubDashLine = new QLine( [centralSubDashLinePoint.x1, centralSubDashLinePoint.y1, centralSubDashLinePoint.x2, centralSubDashLinePoint.y2], qlineOptDash, ) canvas.add(centralSubDashLine) templateCenterLine.push(centralLine) if (polygon.shape === 1) { bigRoofPolygon[3] = { x: centralLine.x1, y: centralLine.y1 } bigRoofPolygon[4] = { x: centralLine.x2, y: centralLine.y2 } middleRoofPolygon[0] = { x: centralLine.x1, y: centralLine.y1 } middleRoofPolygon[3] = { x: centralLine.x2, y: centralLine.y2 } smallRoofPolygon[3] = { x: centralSubLinePoint.x2, y: centralSubLinePoint.y2 } } else if (polygon.shape === 2) { bigRoofPolygon[0] = { x: centralLinePoint.x1, y: centralLinePoint.y1 } bigRoofPolygon[1] = { x: centralLinePoint.x1, y: centralDashLinePoint.y2 } bigRoofPolygon[5] = { x: centralLinePoint.x2, y: centralLinePoint.y2 } middleRoofPolygon[2] = { x: centralLine.x2, y: centralLine.y2 } } else if (polygon.shape === 3) { bigRoofPolygon[2] = { x: centralLine.x2, y: centralLine.y2 } middleRoofPolygon[0] = { x: centralLine.x1, y: centralLine.y1 } middleRoofPolygon[3] = { x: centralLine.x2, y: centralLine.y2 } } else if (polygon.shape === 4) { middleRoofPolygon[1] = { x: centralLine.x1, y: centralLine.y1 } middleRoofPolygon[2] = { x: centralLine.x2, y: centralLine.y2 } } const roofPatternPolygonArray = [] // if (bigRoofPolygon.length > 0 && middleRoofPolygon.length > 0 && smallRoofPolygon.length > 0) { roofPatternPolygonArray.push(bigRoofPolygon) roofPatternPolygonArray.push(middleRoofPolygon) roofPatternPolygonArray.push(smallRoofPolygon) setRoofPolygonPattern({ roofPatternPolygonArray, lines: polygon.lines }) setTemplateCenterLine(templateCenterLine) // } canvas?.renderAll() } const handleOuterLineTemplateB8Points = (polygon, offsetInputX = 20, offsetInputY = 50) => { let offsetPoints = [] const originalMax = 71 const transformedMax = 100 let lines = [] //내각라인 let outLines = [] //아웃라인 let halfLength = 0 //선길이 let offsetX let offsetY const dashedCenterLineOpt = { stroke: 'black', strokeWidth: 1, property: 'centerLine', strokeDashArray: [8, 4], fontSize: 14, } const centerLineOpt = { stroke: 'blue', strokeWidth: 2, property: 'bigHoriCenter', fontSize: 14, } // 폴리곤의 각 변을 선으로 생성 for (let i = 0; i < polygon.points.length; i++) { const start = polygon.points[i] const end = polygon.points[(i + 1) % polygon.points.length] // 다음 점, 마지막 점의 경우 첫 점으로 const line = new QLine([start.x, start.y, end.x, end.y], { stroke: '#A0D468', strokeWidth: 2, property: 'normal', fontSize: 14, }) // 선을 배열에 추가 lines.push(line) canvas.add(line) } offsetInputY = offsetInputY !== 0 ? offsetInputY : offsetInputX const sortedIndex = getStartIndex(polygon.lines) let tmpArraySorted = rearrangeArray(polygon.lines, sortedIndex) setSortedArray(tmpArraySorted) //recoil에 넣음 const points = tmpArraySorted.map((line) => ({ x: line.x1, y: line.y1, })) //좌표 재정렬 function reSortQlineArray(array) { let tmpArray = [] let minX, minY, maxX, maxY let tmp array.forEach((arr, index) => { tmp = arr if (arr.x2 < arr.x1 || arr.y2 < arr.y1) { minX = arr.x2 minY = arr.y2 maxX = arr.x1 maxY = arr.y1 tmp['x1'] = minX tmp['y1'] = minY tmp['x2'] = maxX tmp['y2'] = maxY tmp.line['x1'] = minX tmp.line['y1'] = minY tmp.line['x2'] = maxX tmp.line['y2'] = maxY } tmpArray.push(tmp) }) return tmpArray } // 오목한 부분 인덱스 찾기 const concaveIndicesObj = findConcavePointIndices(points) //오목한 부분을 제외한 인덱스 let concavePointIndices = concaveIndicesObj.concavePointIndices const concaveLine = { index: concavePointIndices[0], line: lines[concavePointIndices[0]], } for (let i = 0; i < points.length; i++) { let prev = points[(i - 1 + points.length) % points.length] let current = points[i] let next = points[(i + 1) % points.length] // 두 벡터 계산 (prev -> current, current -> next) let vector1 = { x: current.x - prev.x, y: current.y - prev.y } let vector2 = { x: next.x - current.x, y: next.y - current.y } // 벡터의 길이 계산 let length1 = Math.sqrt(vector1.x * vector1.x + vector1.y * vector1.y) let length2 = Math.sqrt(vector2.x * vector2.x + vector2.y * vector2.y) // 벡터를 단위 벡터로 정규화 let unitVector1 = { x: vector1.x / length1, y: vector1.y / length1 } let unitVector2 = { x: vector2.x / length2, y: vector2.y / length2 } // 법선 벡터 계산 (왼쪽 방향) let normal1 = { x: -unitVector1.y, y: unitVector1.x } let normal2 = { x: -unitVector2.y, y: unitVector2.x } // 법선 벡터 평균 계산 let averageNormal = { x: (normal1.x + normal2.x) / 2, y: (normal1.y + normal2.y) / 2, } // 평균 법선 벡터를 단위 벡터로 정규화 let lengthNormal = Math.sqrt(averageNormal.x * averageNormal.x + averageNormal.y * averageNormal.y) let unitNormal = { x: averageNormal.x / lengthNormal, y: averageNormal.y / lengthNormal, } offsetX = (offsetInputX / transformedMax) * originalMax * 2 offsetY = (offsetInputY / transformedMax) * originalMax * 2 // 오프셋 적용 let offsetPoint = { x1: current.x + unitNormal.x * offsetX, y1: current.y + unitNormal.y * offsetY, } offsetPoints.push(offsetPoint) } const outlinePolygon = makePolygon(offsetPoints, false) outlinePolygon.setViewLengthText(false) // 아웃라인 폴리곤의 각 변을 선으로 생성 for (let i = 0; i < outlinePolygon.points.length; i++) { const start = outlinePolygon.points[i] const end = outlinePolygon.points[(i + 1) % outlinePolygon.points.length] // 다음 점, 마지막 점의 경우 첫 점으로 const line = new QLine([start.x, start.y, end.x, end.y], { stroke: 'blue', strokeWidth: 2, property: 'normal', fontSize: 14, idx: i, }) // 선을 배열에 추가 outLines.push(line) canvas.add(line) } canvas?.remove(outlinePolygon) //임시 폴리곤을 삭제 let parallelLinesIdx = concavePointIndices[0] + 4 //들어간선에 무조건 평행하는 선 찾기 if (parallelLinesIdx >= outLines.length) { parallelLinesIdx = parallelLinesIdx - outLines.length } let vertCenterLine = [] let halfHoriCenterLinePoint = [] //카라바선의 2분할의 1번 배열 let horiCenterLine = [] let shorVertCenterLine = [] let edgeIndexArray = [] if (concavePointIndices[0] % 2 === 0) { //concave가 짝수면 좌우로 그려진 ㄷ자 //케라바 색을 바꾼다 lines.forEach((line, index) => { if (index % 2 === 0) { line.line.set('stroke', 'skyblue') if (concavePointIndices[0] !== index) { edgeIndexArray.push(index) } } }) outLines = reSortQlineArray(outLines) edgeIndexArray.forEach((idx, index) => { //가로라인이 케라바 라인임 if (concavePointIndices[0] !== idx) { //오목이가 아니면 반으로 갈라서 계산 //카라바 선의 2분할 치수를 그림 let halfLength = outLines[idx].length / 2 let centerLine1 = new QLine([outLines[idx].x1, outLines[idx].y1, outLines[idx].x1, outLines[idx].y1 + halfLength], centerLineOpt) canvas.add(centerLine1) let centerLine2 = new QLine([outLines[idx].x1, centerLine1.y2, outLines[idx].x2, centerLine1.y2 + halfLength], centerLineOpt) canvas.add(centerLine2) canvas.remove(outLines[idx]) //기존 라인 삭제 halfHoriCenterLinePoint.push({ index: idx, x1: centerLine1.x1, y1: centerLine1.y1, x2: centerLine1.x2, y2: centerLine1.y2, }) //각 카라바 라인의 1번이 마지막점을 잡아서 센터선으로 설정 } }) // //각 센터 라인을 그림 halfHoriCenterLinePoint.forEach((centerPoint) => { let tmpX2 = parallelLinesIdx !== centerPoint.index ? concaveLine.line.x2 : outLines[concavePointIndices[0]].x1 //평행선에서 내려오는 선은 아웃라인에 닿아야한다 let line = new QLine([centerPoint.x2, centerPoint.y2, tmpX2, centerPoint.y2], centerLineOpt) canvas.add(line) line['arrayIndex'] = centerPoint.index //커스텀으로 기존 index를 넣어줌 vertCenterLine.push(line) }) vertCenterLine = reSortQlineArray(vertCenterLine) lines = reSortQlineArray(lines) setTemplateCenterLine(vertCenterLine) //해당라인에서 만나는점을 계산 vertCenterLine.forEach((vertLine) => { if (parallelLinesIdx !== vertLine.arrayIndex) { //평행선을 제외한 애들만 네모를 연결 let nearLine let nearOutline if (vertLine.arrayIndex > concaveLine.index) { //센터에 인덱스가 오목점 보다 크면 다음 작으면 앞에꺼 nearLine = lines[concaveLine.index + 1] nearOutline = outLines[concaveLine.index + 1] } else { nearLine = lines[concaveLine.index - 1] nearOutline = outLines[concaveLine.index - 1] } let nearLineY = nearLine.y1 if (parallelLinesIdx < concaveLine.index) { //오목점 위치가 평행선보다 크면 위쪽으로 오목 nearLineY = nearLine.y2 } //기존에 있는 라인에서 연장해서 새로 그림 let centerExtendHoriLine = new QLine([vertLine.x1, nearOutline.line.y1, vertLine.x2, nearOutline.line.y2], centerLineOpt) canvas.add(centerExtendHoriLine) canvas.remove(nearOutline) outLines.splice(nearOutline.idx, 1, centerExtendHoriLine) //아웃라인에 데이터를 다시 넣는다 //가로형에선 기본으로 ㄷ자 형태로 한다 let centerExtendLine = new QLine([vertLine.line.x1, vertLine.line.y1, centerExtendHoriLine.x1, centerExtendHoriLine.y1], centerLineOpt) //오목이가 배열에 반보다 작으면 역 ㄷ자 여서 변경 if (concavePointIndices[0] < outLines.length / 2) { centerExtendLine = new QLine([vertLine.line.x2, vertLine.line.y2, centerExtendHoriLine.x2, centerExtendHoriLine.y2], centerLineOpt) } canvas.add(centerExtendLine) //새로그리고 let betweenCenterLine = (vertLine.line.x1 + vertLine.line.x2) / 2 let centerDashLine = new QLine([betweenCenterLine, centerExtendLine.y1, betweenCenterLine, centerExtendLine.y2], dashedCenterLineOpt) canvas.add(centerDashLine) horiCenterLine.push(centerDashLine) shorVertCenterLine.push(vertLine) //마지막에 가운데 선을 긋기 위해 담음 } else { let longDashLine = halfHoriCenterLinePoint.find((obj) => obj.index === parallelLinesIdx) //평행선 let dashCenterExtendLineLength = longDashLine.y2 - longDashLine.y1 //y반개 길이 let betweenCenterLine = (vertLine.line.x1 + vertLine.line.x2) / 2 //y의 길이 let totalLength = ((longDashLine.y2 - longDashLine.y1) * 2) / dashCenterExtendLineLength //2개로 나눔 //반 쪼개서 그린다 for (let i = 0; i < totalLength; i++) { //2번에 나눠서 let startY = i === 0 ? longDashLine.y1 : longDashLine.y1 + dashCenterExtendLineLength //시작 하는 y의 좌표 //x값은 고정이임 //TODO: 지붕 각도 계산법에 의해 재계산해야함 let centerDashLine = new QLine([betweenCenterLine, startY, betweenCenterLine, startY + dashCenterExtendLineLength], dashedCenterLineOpt) canvas.add(centerDashLine) horiCenterLine.push(centerDashLine) } } }) //마지막에 오목한 외곽선을 연장한다 const tmpLastOutLine = outLines[concavePointIndices[0]] const lastOutLine = new QLine([tmpLastOutLine.x1, shorVertCenterLine[0].y1, tmpLastOutLine.x1, shorVertCenterLine[1].y1], centerLineOpt) canvas.add(lastOutLine) canvas.remove(tmpLastOutLine) //폴리곤 패턴을 그리기 위해 작성 let tmpVertCenterLine = outLines.filter((x, index) => index % 2 !== 0) //세로만 찾음 tmpVertCenterLine = tmpVertCenterLine.concat(vertCenterLine) tmpVertCenterLine.sort((a, b) => a.y1 - b.y1) tmpVertCenterLine.push(lastOutLine) let roofPatternPolygonArray = [] let tmpArray = [] let tmpBigArray = [] const lastCenterLine = tmpVertCenterLine[tmpVertCenterLine.length - 1] //마지막 센터라인을 정의 for (let i = 0; i < tmpVertCenterLine.length - 1; i++) { //-1인건 마지막은 오목한 선이라 돌 필요 없음 //라인 하나에 두점씩 나온다 let firstPointObj = {} let secondPointObj = {} let x1 = tmpVertCenterLine[i].x1 let y1 = tmpVertCenterLine[i].y1 let x2 = tmpVertCenterLine[i].x2 let y2 = tmpVertCenterLine[i].y2 if (i === 2 || i === 4) { //작은 네모들 tmpArray = [] const prevLine = tmpVertCenterLine[i - 1] //뒤에서 앞라인을 찾는다 const nextLine = tmpVertCenterLine[i + 1] //내 앞뒤 라인 const tmpX1 = i === 2 ? prevLine.x1 : nextLine.x1 const tmpY1 = i === 2 ? prevLine.y1 : nextLine.y1 const tmpX2 = i === 2 ? prevLine.x2 : nextLine.x2 const tmpY2 = i === 2 ? prevLine.y2 : nextLine.y2 firstPointObj = { x: tmpX1, y: tmpY1 } secondPointObj = { x: tmpX2, y: tmpY2 } tmpArray.push(firstPointObj) tmpArray.push(secondPointObj) //현재 내 선 firstPointObj = { x: x1, y: y1 } secondPointObj = { x: x2, y: y2 } tmpArray.push(firstPointObj) tmpArray.push(secondPointObj) roofPatternPolygonArray.push(tmpArray) } else { //큰 육각 if (i === 1 || i === 5) { // 큰 폴리곤은 가운데 선으로 되야됨 if (outLines.length / 2 > concavePointIndices[0]) { x2 = i === 1 ? lastCenterLine.x1 : lastCenterLine.x2 y2 = i === 1 ? lastCenterLine.y1 : lastCenterLine.y2 } else { //오목이가 배열 전체보다 크면 오른쪽 오목이 x1 = i === 1 ? lastCenterLine.x1 : lastCenterLine.x2 y1 = i === 1 ? lastCenterLine.y2 : lastCenterLine.y1 } } if (i === 5) { //5번일때는 앞에 3번에 선이 필요하다 let prevX1 = tmpVertCenterLine[i - 2].x1 let prevY1 = tmpVertCenterLine[i - 2].y1 let prevX2 = tmpVertCenterLine[i - 2].x2 let prevY2 = tmpVertCenterLine[i - 2].y2 firstPointObj = { x: prevX1, y: prevY1 } secondPointObj = { x: prevX2, y: prevY2 } tmpBigArray.push(firstPointObj) tmpBigArray.push(secondPointObj) } firstPointObj = { x: x1, y: y1 } secondPointObj = { x: x2, y: y2 } tmpBigArray.push(firstPointObj) tmpBigArray.push(secondPointObj) if (i === 3 || i === 6) { roofPatternPolygonArray.push(tmpBigArray) tmpBigArray = [] } } } setRoofPolygonPattern({ roofPatternPolygonArray, lines }) } else { // 오목한 부분이 세로선일때 아래ㄷ, 위ㄷ //라인들을 좌측에서 -> 우측으로 그리는거처럼 데이터 보정 lines.forEach((line, index) => { if (!(index % 2 === 0)) { line.line.set('stroke', 'skyblue') } }) outLines = reSortQlineArray(outLines) outLines.forEach((outline, index) => { if (!(index % 2 === 0)) { //세로라인이 케라바 라인임 if (concavePointIndices[0] !== index) { //오목이가 아니면 반으로 갈라서 계산 //카라바 선의 2분할 치수를 그림 let halfLength = outline.length / 2 let centerLine1 = new QLine([outline.x1, outline.y1, outline.x1 + halfLength, outline.y1], centerLineOpt) canvas.add(centerLine1) let centerLine2 = new QLine([centerLine1.x2, outline.y1, centerLine1.x2 + halfLength, outline.y1], centerLineOpt) canvas.add(centerLine2) canvas.remove(outline) //기존 라인 삭제 halfHoriCenterLinePoint.push({ index: index, x1: centerLine1.x1, y1: centerLine1.y1, x2: centerLine1.x2, y2: centerLine1.y2, }) //각 카라바 라인의 1번이 마지막점을 잡아서 센터선으로 설정 } } }) //각 센터 라인을 그림 halfHoriCenterLinePoint.forEach((centerPoint) => { let tmpY2 = parallelLinesIdx !== centerPoint.index ? concaveLine.line.y1 : outLines[concavePointIndices[0]].y2 //평행선에서 내려오는 선은 아웃라인에 닿아야한다 let line = new QLine([centerPoint.x2, centerPoint.y1, centerPoint.x2, tmpY2], centerLineOpt) canvas.add(line) line['arrayIndex'] = centerPoint.index //커스텀으로 기존 index를 넣어줌 vertCenterLine.push(line) }) vertCenterLine = reSortQlineArray(vertCenterLine) lines = reSortQlineArray(lines) setTemplateCenterLine(vertCenterLine) //해당라인에서 만나는점을 계산 vertCenterLine.forEach((vertLine) => { if (parallelLinesIdx !== vertLine.arrayIndex) { //평행선을 제외한 애들만 네모를 연결 let nearLine let nearOutline if (vertLine.arrayIndex > concaveLine.index) { //센터에 인덱스가 오목점 보다 크면 다음 작으면 앞에꺼 nearLine = lines[concaveLine.index + 1] nearOutline = outLines[concaveLine.index + 1] } else { nearLine = lines[concaveLine.index - 1] nearOutline = outLines[concaveLine.index - 1] } let nearLineY = nearLine.y1 if (parallelLinesIdx < concaveLine.index) { //오목점 위치가 평행선보다 크면 위쪽으로 오목 nearLineY = nearLine.y2 } let centerExtendLine = new QLine([vertLine.line.x1, nearLineY, nearOutline.x1, nearLineY], centerLineOpt) canvas.add(centerExtendLine) //새로그리고 //기존에 있는 라인에서 연장해서 새로 그림 let centerExtendHoriLine = new QLine([nearOutline.line.x1, vertLine.y1, nearOutline.line.x2, vertLine.line.y2], centerLineOpt) canvas.add(centerExtendHoriLine) canvas.remove(nearOutline) outLines.splice(nearOutline.idx, 1, centerExtendHoriLine) //아웃라인에 데이터를 다시 넣는다 let betweenCenterLine = (vertLine.line.y1 + vertLine.line.y2) / 2 let centerDashLine = new QLine([vertLine.line.x1, betweenCenterLine, nearOutline.x1, betweenCenterLine], dashedCenterLineOpt) canvas.add(centerDashLine) horiCenterLine.push(centerDashLine) shorVertCenterLine.push(vertLine) //마지막에 가운데 선을 긋기 위해 담음 } else { let longDashLine = halfHoriCenterLinePoint.find((obj) => obj.index === parallelLinesIdx) let dashCenterExtendLineLength = longDashLine.x2 - longDashLine.x1 let betweenCenterLine = (vertLine.line.y1 + vertLine.line.y2) / 2 let totalLength = ((longDashLine.x2 - longDashLine.x1) * 2) / dashCenterExtendLineLength //반 쪼개서 그린다 for (let i = 0; i < totalLength; i++) { let startX = i === 0 ? longDashLine.x1 : longDashLine.x1 + dashCenterExtendLineLength let centerDashLine = new QLine([startX, betweenCenterLine, startX + dashCenterExtendLineLength, betweenCenterLine], dashedCenterLineOpt) canvas.add(centerDashLine) horiCenterLine.push(centerDashLine) } } }) //마지막에 오목한 외곽선을 연장한다 const tmpLastOutLine = outLines[concavePointIndices[0]] const lastOutLine = new QLine([shorVertCenterLine[0].x1, tmpLastOutLine.y1, shorVertCenterLine[1].x1, tmpLastOutLine.y2], centerLineOpt) canvas.add(lastOutLine) canvas.remove(tmpLastOutLine) let tmpVertCenterLine = outLines.filter((x, index) => index % 2 === 0) //세로만 찾음 tmpVertCenterLine = tmpVertCenterLine.concat(vertCenterLine) tmpVertCenterLine.sort((a, b) => a.x1 - b.x1) tmpVertCenterLine.push(lastOutLine) let roofPatternPolygonArray = [] let tmpArray = [] let tmpBigArray = [] const lastCenterLine = tmpVertCenterLine[tmpVertCenterLine.length - 1] //마지막 센터라인을 정의 for (let i = 0; i < tmpVertCenterLine.length - 1; i++) { //-1인건 마지막은 오목한 선이라 돌 필요 없음 //라인 하나에 두점씩 나온다 let firstPointObj = {} let secondPointObj = {} let x1 = tmpVertCenterLine[i].x1 let y1 = tmpVertCenterLine[i].y1 let x2 = tmpVertCenterLine[i].x2 let y2 = tmpVertCenterLine[i].y2 if (i === 2 || i === 4) { tmpArray = [] const prevLine = tmpVertCenterLine[i - 1] //뒤에서 앞라인을 찾는다 const nextLine = tmpVertCenterLine[i + 1] //내 앞뒤 라인 const tmpX1 = i === 2 ? prevLine.x1 : nextLine.x1 const tmpY1 = i === 2 ? prevLine.y1 : nextLine.y1 const tmpX2 = i === 2 ? prevLine.x2 : nextLine.x2 const tmpY2 = i === 2 ? prevLine.y2 : nextLine.y2 firstPointObj = { x: tmpX1, y: tmpY1 } secondPointObj = { x: tmpX2, y: tmpY2 } tmpArray.push(firstPointObj) tmpArray.push(secondPointObj) //현재 내 선 firstPointObj = { x: x1, y: y1 } secondPointObj = { x: x2, y: y2 } tmpArray.push(firstPointObj) tmpArray.push(secondPointObj) roofPatternPolygonArray.push(tmpArray) } else { if (i === 1 || i === 5) { // 큰 폴리곤은 가운데 선으로 되야됨 if (outLines.length / 2 < concavePointIndices[0]) { //오목이가 배열 전체보다 크면 위쪽 방향 x2 = i === 1 ? lastCenterLine.x2 : lastCenterLine.x1 y2 = i === 1 ? lastCenterLine.y2 : lastCenterLine.y1 } else { x1 = i === 1 ? lastCenterLine.x1 : lastCenterLine.x2 y1 = i === 1 ? lastCenterLine.y1 : lastCenterLine.y2 } } if (i === 5) { //5번일때는 앞에 3번에 선이 필요하다 let prevX1 = tmpVertCenterLine[i - 2].x1 let prevY1 = tmpVertCenterLine[i - 2].y1 let prevX2 = tmpVertCenterLine[i - 2].x2 let prevY2 = tmpVertCenterLine[i - 2].y2 firstPointObj = { x: prevX1, y: prevY1 } secondPointObj = { x: prevX2, y: prevY2 } tmpBigArray.push(firstPointObj) tmpBigArray.push(secondPointObj) } firstPointObj = { x: x1, y: y1 } secondPointObj = { x: x2, y: y2 } tmpBigArray.push(firstPointObj) tmpBigArray.push(secondPointObj) if (i === 3 || i === 6) { roofPatternPolygonArray.push(tmpBigArray) tmpBigArray = [] } } } setRoofPolygonPattern({ roofPatternPolygonArray, lines }) } canvas?.renderAll() } /** * 세로 방샹 라인의 좌표 순서를 위에서 아래로 변경 * @param {array} arr * @returns */ const chgLineDirectionVertical = (arr) => { const newArr = arr.map((line, index) => { if (line.direction !== 'bottom') { const newPoint = { x1: line.x2, y1: line.y2, x2: line.x1, y2: line.y1 } return newPoint } else { return line } }) return newArr } /** * 가로 방향 라인의 좌표 순서를 왼쪽에서 오른쪽으로 변경 * @param {array} arr * @returns */ const chgLineDirectionHorizontal = (arr) => { const newArr = arr.map((line, index) => { if (line.direction !== 'right') { const newPoint = { x1: line.x2, y1: line.y2, x2: line.x1, y2: line.y1 } return newPoint } else { return line } }) return newArr } /** * 라인 배열 중 가장 긴 라인의 인덱스를 반환합니다. */ const chkLengthIndex = (params) => { const { arr, type } = params let maxIndex = 0 for (let i = 1; i < arr.length; i++) { if (type === 'L') { if (arr[maxIndex].length < arr[i].length) { maxIndex = i } } else { if (arr[maxIndex].length > arr[i].length) { maxIndex = i } } } return maxIndex } /** * 외벽선 색깔을 변경 * @param {array} polygon */ const handleInnerLineColor = (polygon) => { polygon.lines.forEach((line, index) => { if (index % 2 === 0) { line.set('stroke', 'rgb(0, 220, 221)') } else { line.set('stroke', 'rgb(0, 224, 61)') } const overLine = new QLine([line.x1, line.y1, line.x2, line.y2], { stroke: line.stroke, strokeWidth: 2, selectable: false, fontSize: fontSize, isActiveLengthText: false, }) canvas.add(overLine) }) canvas?.renderAll() } const getRoofPattern = (roofStyle, mode = 'normal') => { const ratio = window.devicePixelRatio || 1 const inputPatternSize = { width: 30, height: 20 } //임시 사이즈 const patternSize = { ...inputPatternSize } // 입력된 값을 뒤집기 위해 if (templateType === 2) { //세로형이면 width height를 바꿈 ;[patternSize.width, patternSize.height] = [inputPatternSize.height, patternSize.width] } // 패턴 소스를 위한 임시 캔버스 생성 const patternSourceCanvas = document.createElement('canvas') patternSourceCanvas.width = roofStyle === 2 ? patternSize.width * 2 * ratio : patternSize.width * ratio patternSourceCanvas.height = roofStyle === 2 ? patternSize.height * 2 * ratio : patternSize.height * ratio const ctx = patternSourceCanvas.getContext('2d') // 벽돌 패턴 그리기 ctx.scale(ratio, ratio) if (mode === 'cell') { ctx.fillStyle = 'rgba(0, 0, 0, 0.1)' ctx.fillRect(0, 0, patternSize.width * 2, patternSize.height * 2) } ctx.strokeStyle = 'green' ctx.lineWidth = 0.4 // 첫 번째 행 벽돌 if (roofStyle === 2) { patternSize.width = patternSize.width * 2 patternSize.height = patternSize.height * 2 //지그재그 // // 두 번째 행 벽돌 if (templateType === 2) { ctx.strokeRect(0, 0, patternSize.width / 2, patternSize.height) ctx.strokeRect(patternSize.width / 2, patternSize.height / 2, patternSize.width / 2, patternSize.height) } else if (templateType === 3) { ctx.strokeRect(0, 0, patternSize.width, patternSize.height / 2) ctx.strokeRect(patternSize.width / 2, patternSize.height / 2, patternSize.width, patternSize.height / 2) } } else { ctx.strokeRect(0, 0, patternSize.width, patternSize.height) // 원패턴일때랑 지그재그일때랑은 다르게 들어가야함 } // 패턴 생성 const pattern = new fabric.Pattern({ source: patternSourceCanvas, repeat: 'repeat', }) return pattern } /** * 지붕 패턴 생성 로직 * @param roofStyle */ const makeRoofPatternPolygon = (roofStyle) => { if (Object.keys(roofPolygonPattern).length === 0 && roofPolygonPattern.constructor === Object) { alert('객체가 비어있습니다.') return } setRoofStyle(roofStyle) //클릭한 지붕패턴을 저장 //내부 선 점선으로 변경 추후에 다시 되돌리는 로직 필요 roofPolygonPattern.lines.forEach((line, index) => { line.line.set('strokeDashArray', [10, 5, 2, 5]) line.line.set('stroke', 'blue') line.line.set('strokeWidth', 1) }) const pattern = getRoofPattern(roofStyle) const commonOption = { fill: pattern, selectable: false, fontSize: 15, // fontSize는 필요에 따라 조정 sort: false, lockMovementX: true, lockMovementY: true, lockRotation: true, lockScalingY: true, lockScalingX: true, } let polygonArray = [] //패턴 폴리곤을 생성 후 배열에 담음 roofPolygonPattern.roofPatternPolygonArray.forEach((patternPolygon, index) => { const drawPolygon = new QPolygon(patternPolygon, commonOption) canvas.add(drawPolygon) drawPolygon.setViewLengthText(false) drawPolygon.set('customIndex', index) polygonArray.push(drawPolygon) }) canvas?.renderAll() //지붕 폴리곤 recoil에 담음 setRoofPolygonArray(polygonArray) } /** * 가대 생성 로직 */ const makeRoofTrestle = () => { if (compass === undefined) { alert('방위를 먼저 선택 해주세요.') return } if (Object.keys(roofPolygonPattern).length === 0 && roofPolygonPattern.constructor === Object) { alert('객체가 비어있습니다.') return } const polygons = roofPolygonArray //리코일에 있는 패턴그린 폴리곤가져옴 /** * 지붕가대 생성 후 가대 선택 이벤트를 추가하는 로직 * @param polygon */ const pattern = getRoofPattern(roofStyle, 'cell') //셀모드 배경색을 칠한다 polygons.sort((a, b) => b.points.length - a.points.length) //무조건 잴 긴거 정렬 // 외각선을 안쪽으로 그려 가대선을 그린다. polygons.forEach((polygon, index) => { const trestlePolygon = handleOuterlinesTest(polygon, -12) let referenceDirection = 'none' //상단 기준 값 let parallelPoint = -1 trestlePolygon.setViewLengthText(false) //얘는 set으로 안먹는다... trestlePolygon.bringForward() trestlePolygon.set({ stroke: 'red', strokeDashArray: [9, 5], strokeWidth: 0.3, lockMovementX: true, lockMovementY: true, lockRotation: true, lockScalingX: true, lockScalingY: true, idx: polygon.customIndex, //가대 폴리곤의 임시 인덱스를 넣어줌 name: 'trestlePolygon', }) if (polygon.points.length > 4) { //6, 8각 //4각 이상일때만 한다 const concave = findConcavePointIndices(polygon.points) //오목한 부분기준으로 시작점을 찾으려 계산 parallelPoint = parseInt(concave.concavePointIndices[0] + 3) % polygon.points.length //시작점을 찾기 위해 적용 if (templateType === 2) { //셀이 그려져야할 기준 방향 //A타입 referenceDirection = parallelPoint === 0 || parallelPoint === 5 ? 'top' : 'bottom' } else if (templateType === 3) { //B타입 referenceDirection = parallelPoint === 0 || parallelPoint === 1 ? 'left' : 'right' } } if (templateCenterLine.length > 0) { //셀이 생성될 지붕의 흐름방향을 정함 templateCenterLine.some((centerLine) => { if (templateType === 2) { trestlePolygon.set({ referenceDirection: referenceDirection, startIndex: parallelPoint, }) //기준면 방향 trestlePolygon.points.forEach((trestleLine, index) => { if (trestleLine.x === centerLine.x1 - 12) { trestlePolygon.set({ wallDirection: 'left' }) return true } else if (trestleLine.x === centerLine.x1 + 12) { trestlePolygon.set({ wallDirection: 'right' }) return true } }) } else if (templateType === 3) { trestlePolygon.set({ referenceDirection: referenceDirection, startIndex: parallelPoint, }) //기준면 방향 trestlePolygon.points.forEach((trestleLine, index) => { if (trestleLine.y === centerLine.y1 - 12) { trestlePolygon.set({ wallDirection: 'top' }) return true } else if (trestleLine.y === centerLine.y1 + 12) { trestlePolygon.set({ wallDirection: 'bottom' }) return true } }) } }) } else { if (templateType === 2) { index === 0 ? trestlePolygon.set({ wallDirection: 'left' }) : trestlePolygon.set({ wallDirection: 'right' }) } else if (templateType === 3) { index === 0 ? trestlePolygon.set({ wallDirection: 'top' }) : trestlePolygon.set({ wallDirection: 'bottom' }) } trestlePolygon.set({ referenceDirection: 'none' }) } /** * 가대 선택 이벤트 */ trestlePolygon.on('mousedown', function () { toggleSelection(trestlePolygon) }) polygon.set({ fill: pattern }) trestlePolygon.bringForward() }) canvas?.renderAll() setMode(Mode.DEFAULT) //default 모드로 변경 } /** * 가대 영역 선택 시 이벤트 * @param {} polygon */ const toggleSelection = (polygon) => { const selectedAreaArray = selectedCellRoofArray const defualtStrokeStyle = { stroke: 'red', strokeDashArray: [9, 5], strokeWidth: 0.3, } const wallDirection = polygon.wallDirection const invalidDirections = { //반대편으로 작성 2: { 90: 'right', 270: 'left', }, 3: { 0: 'top', 180: 'bottom', }, } if (invalidDirections[templateType] && invalidDirections[templateType][compass] === wallDirection) { alert('선택할 수 없는 방향입니다.') canvas.discardActiveObject() // 객체의 활성 상태 해제 return } const selectedStrokeStyle = { stroke: 'red', strokeWidth: 3, } if (polygon.strokeWidth === defualtStrokeStyle.strokeWidth) { //기본 선택이랑 스트로크 굵기가 같으면 선택 안됨으로 봄 polygon.set({ stroke: selectedStrokeStyle.stroke, strokeWidth: selectedStrokeStyle.strokeWidth, strokeDashArray: [0], }) canvas.discardActiveObject() // 객체의 활성 상태 해제 //중복으로 들어가는걸 방지하기 위한 코드 const isExist = selectedAreaArray.some((x) => x.idx === polygon.idx) if (!isExist) { selectedAreaArray.push(polygon) } } else { //선택후 재선택하면 선택안됨으로 변경 polygon.set({ stroke: defualtStrokeStyle.stroke, strokeWidth: defualtStrokeStyle.strokeWidth, strokeDashArray: defualtStrokeStyle.strokeDashArray, }) canvas.discardActiveObject() // 객체의 활성 상태 해제 //폴리곤에 커스텀 인덱스를 가지고 해당 배열 인덱스를 찾아 삭제함 const removeIndex = polygon.idx const removeArrayIndex = selectedAreaArray.findIndex((x) => x.idx === removeIndex) selectedAreaArray.splice(removeArrayIndex, 1) } setSelectedCellRoofArray(selectedAreaArray) canvas?.renderAll() } /** * 가대 선택 후 셀 채우기 */ const makeRoofFillCells = () => { const drawCellsArray = [] const selectedCellRoofs = [...selectedCellRoofArray] if (selectedCellRoofs.length === 0) { //배열에 선택된 가대 셀이 없으면 리턴 alert('선택된 영역이 없습니다.') setMode(Mode.DEFAULT) //default 모드로 변경 return } if (drewRoofCells.length > 0) { if (confirm('패널이 초기화 됩니다.')) { drewRoofCells.forEach((cells, index) => { cells.drawCells.forEach((cell) => { canvas?.remove(cell) }) }) setDrewRoofCells([]) canvas?.renderAll() } } const inputCellSize = { width: 172, height: 70 } //추후 입력받는 값으로 변경 const cellSize = { ...inputCellSize } //기본으로 가로형으로 넣고 if (templateType === 2) { ;[cellSize.width, cellSize.height] = [cellSize.height, cellSize.width] } selectedCellRoofs.forEach((polygon, index) => { const drawCells = polygon.fillCellABType({ width: cellSize.width, height: cellSize.height, padding: 10, wallDirection: polygon.wallDirection, referenceDirection: polygon.referenceDirection, startIndex: polygon.startIndex, isCellCenter: isCellCenter, }) drawCellsArray.push({ roofIndex: polygon.idx, drawCells: drawCells }) }) setDrewRoofCells(drawCellsArray) setMode(Mode.DEFAULT) //default 모드로 변경 } const createRoofRack = () => { removeMouseLines() canvas?.off('mouse:move') canvas?.off('mouse:out') const roofs = canvas?.getObjects().filter((obj) => obj.name === 'roof') roofs.forEach((roof, index) => { // const offsetPolygonPoint = offsetPolygon(roof.points(), -20) //이동되서 찍을라고 바꿈 const offsetPolygonPoint = offsetPolygon(roof.getCurrentPoints(), -20) const trestlePoly = new QPolygon(offsetPolygonPoint, { fill: 'transparent', stroke: 'red', strokeDashArray: [5, 5], strokeWidth: 1, selectable: false, fontSize: fontSize, name: 'trestle', defense: roof.defense, lockMovementX: true, // X 축 이동 잠금 lockMovementY: true, // Y 축 이동 잠금 lockRotation: true, // 회전 잠금 lockScalingX: true, // X 축 크기 조정 잠금 lockScalingY: true, // Y 축 크기 조정 잠금 idx: index, parentId: roof.id, }) canvas?.add(trestlePoly) trestlePoly.setViewLengthText(false) }) removeHelpPointAndHelpLine() } //배터리 셀 넣기 const drawCellInTrestle = () => { const trestlePolygons = canvas?.getObjects().filter((obj) => obj.name === 'trestle' && obj.selected) const notSelectedTrestlePolygons = canvas?.getObjects().filter((obj) => obj.name === 'trestle' && !obj.selected) if (trestlePolygons.length === 0) { alert('가대가 없습니다.') return } if (drewRoofCells.length > 0) { alert('기존 셀은 제거됩니다.') } notSelectedTrestlePolygons.forEach((trestle) => { trestle.cells.forEach((cell) => { canvas?.remove(cell) }) trestle.cells = [] }) const drawCellsArray = [] trestlePolygons.forEach((trestle, index) => { trestle.fire('mousedown') let maxLengthLine = trestle.lines.reduce((acc, cur) => { return acc.length > cur.length ? acc : cur }) let drawRoofCells if (maxLengthLine.direction === 'right' || maxLengthLine.direction === 'left') { drawRoofCells = trestle.fillCell({ width: 113.4, height: 172.2, padding: 0 }) trestle.direction = 'south' } else { drawRoofCells = trestle.fillCell({ width: 172.2, height: 113.4, padding: 0 }) trestle.direction = 'east' } drawRoofCells.forEach((cell) => { drawCellsArray.push(cell) }) }) setDrewRoofCells(drawCellsArray) } // 가대 방위 설정 const setDirectionTrestles = () => { console.log('roof', roof) } const makeCellPowercon = () => { setMode(Mode.DEFAULT) let cellsGroupObj = [] drewRoofCells.forEach((obj) => { cellsGroupObj = cellsGroupObj.concat(obj.drawCells) }) const chunkSize = 1000 / 200 // 파워콘와트 나누기 셀와트 const cellPowerconArray = [] //파워콘과 셀의 파워를 나눠서 나온 갯수만큼 배열을 재생성 for (let i = 0; i < cellsGroupObj.length; i += chunkSize) { cellPowerconArray.push(cellsGroupObj.slice(i, i + chunkSize)) } for (let i = 0; i < cellPowerconArray.length; i++) { const cellPowerconGroups = cellPowerconArray[i] cellPowerconGroups.forEach((cellPowerconGroup, index) => { const cellRectObj = cellPowerconGroup._objects[0] const cellTextObj = cellPowerconGroup._objects[1] cellTextObj.set({ text: `(${i + 1})`, }) cellPowerconGroup.addWithUpdate() //폰트 사이즈가 커진 후에 계산을 해야함 cellTextObj.set({ left: cellRectObj.left + cellRectObj.width / 2 - cellTextObj.width / 2, top: cellRectObj.top + cellRectObj.height / 2 - cellTextObj.height / 2, }) cellPowerconGroup.addWithUpdate() }) } canvas.renderAll() } // 외적을 계산하는 함수 const crossProduct = (p1, p2, p3) => { const dx1 = p2.x - p1.x const dy1 = p2.y - p1.y const dx2 = p3.x - p2.x const dy2 = p3.y - p2.y return dx1 * dy2 - dy1 * dx2 } // 오목한 부분 찾기 const findConcavePointIndices = (points) => { let concaveIndices = [] let concavePointIndices = [] for (let i = 0; i < points.length; i++) { const p1 = points[i] const p2 = points[(i + 1) % points.length] const p3 = points[(i + 2) % points.length] const cross = crossProduct(p1, p2, p3) if (cross < 0) { concaveIndices.push((i + 1) % points.length) } else { concavePointIndices.push((i + 1) % points.length) } } return { concaveIndices: concaveIndices, concavePointIndices: concavePointIndices } } const drawHelpLineMode = () => { if (!isObjectNotEmpty(roof)) { alert('지붕을 먼저 그려주세요.') setMode(Mode.DEFAULT) return } const roofPoints = roof.points const wallPoints = wall.points roofPoints.forEach((roofPoint, index) => { const circle = new fabric.Circle({ radius: 5, fill: 'red', left: roofPoint.x - 5, top: roofPoint.y - 5, selectable: true, // 선택 가능하게 설정 lockMovementX: true, // X 축 이동 잠금 lockMovementY: true, // Y 축 이동 잠금 lockRotation: true, // 회전 잠금 lockScalingX: true, // X 축 크기 조정 잠금 lockScalingY: true, // Y 축 크기 조정 잠금 name: 'helpPoint', }) canvas?.add(circle) canvas?.renderAll() }) } const cutHelpLines = () => { // 먼저 hip을 자른다. canvas ?.getObjects() .filter((obj) => obj.name === 'helpLine') .forEach((line, index1) => { canvas ?.getObjects() .filter((obj) => obj.name === 'helpLine') .forEach((line2, index2) => { if (line === line2) { return } const intersectionPoint = calculateIntersection(line, line2) if (!intersectionPoint) { return } canvas?.remove(line) canvas?.remove(line2) const hip1 = new QLine([line.x1, line.y1, intersectionPoint.x, intersectionPoint.y], { stroke: 'black', strokeWidth: 1, selectable: false, name: 'hip', }) const hip2 = new QLine([line2.x1, line2.y1, intersectionPoint.x, intersectionPoint.y], { stroke: 'black', strokeWidth: 1, selectable: false, name: 'hip', }) const interSectionCircle = new fabric.Circle({ radius: 5, fill: 'red', left: intersectionPoint.x - 5, top: intersectionPoint.y - 5, selectable: true, // 선택 가능하게 설정 lockMovementX: true, // X 축 이동 잠금 lockMovementY: true, // Y 축 이동 잠금 lockRotation: true, // 회전 잠금 lockScalingX: true, // X 축 크기 조정 잠금 lockScalingY: true, // Y 축 크기 조정 잠금 name: 'helpPoint', }) canvas?.add(hip1) canvas?.add(hip2) roof.innerLines.push(hip1) roof.innerLines.push(hip2) canvas?.add(interSectionCircle) canvas?.renderAll() }) }) canvas ?.getObjects() .filter((obj) => obj.name === 'helpLine') .forEach((line) => { const helpPoints = canvas?.getObjects().filter((obj) => obj.name === 'helpPoint') let cnt = 0 let intersectionPoints = [] helpPoints.forEach((point) => { if (cnt === 2) { return } if ( turf.booleanPointOnLine( turf.point([point.left + point.radius, point.top + point.radius]), turf.lineString([ [line.x1, line.y1], [line.x2, line.y2], ]), ) ) { intersectionPoints.push(point) cnt++ } }) if (intersectionPoints.length === 2) { const ridge = new QLine( [ intersectionPoints[0].left + intersectionPoints[0].radius, intersectionPoints[0].top + intersectionPoints[0].radius, intersectionPoints[1].left + intersectionPoints[1].radius, intersectionPoints[1].top + intersectionPoints[1].radius, ], { stroke: 'black', strokeWidth: 1, selectable: false, name: 'ridge', }, ) roof.innerLines.push(ridge) canvas?.add(ridge) canvas?.remove(line) canvas?.renderAll() } }) } const removeHelpPointAndHelpLine = () => { const helpPoints = canvas?.getObjects().filter((obj) => obj.name === 'helpPoint') helpPoints.forEach((point) => { canvas?.remove(point) }) const helpLines = canvas?.getObjects().filter((obj) => obj.name === 'helpLine') helpLines.forEach((line) => { canvas?.remove(line) }) canvas?.renderAll() } return { mode, setMode, changeMode, setCanvas, handleClear, zoomIn, zoomOut, zoom, togglePolygonLine, handleOuterlinesTest, handleOuterlinesTest2, makeRoofPatternPolygon, makeRoofTrestle, createRoofRack, drawRoofPolygon, drawCellInTrestle, setDirectionTrestles, cutHelpLines, } }