diff --git a/.prettierrc b/.prettierrc index 198a1031..4b55b596 100644 --- a/.prettierrc +++ b/.prettierrc @@ -4,7 +4,7 @@ "useTabs": false, "tabWidth": 2, "trailingComma": "all", - "printWidth": 80, + "printWidth": 150, "arrowParens": "always", "endOfLine": "auto" } diff --git a/package.json b/package.json index 78ce1907..f7fa513c 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "@nextui-org/react": "^2.4.2", "fabric": "^5.3.0", "framer-motion": "^11.2.13", + "mathjs": "^13.0.2", "next": "14.2.3", "react": "^18", "react-dom": "^18", diff --git a/src/app/roof2/page.jsx b/src/app/roof2/page.jsx index 8c83730e..c33a5a99 100644 --- a/src/app/roof2/page.jsx +++ b/src/app/roof2/page.jsx @@ -1,6 +1,5 @@ 'use client' -import Hero from '@/components/Hero' import Roof2 from '@/components/Roof2' import { textState } from '@/store/canvasAtom' import { useEffect } from 'react' diff --git a/src/components/Roof2.jsx b/src/components/Roof2.jsx index 9587a7d2..bac1efbd 100644 --- a/src/components/Roof2.jsx +++ b/src/components/Roof2.jsx @@ -7,21 +7,11 @@ import QPolygon from '@/components/fabric/QPolygon' import RangeSlider from './ui/RangeSlider' import { useRecoilState, useRecoilValue } from 'recoil' -import { - canvasSizeState, - fontSizeState, - sortedPolygonArray, -} from '@/store/canvasAtom' +import { canvasSizeState, fontSizeState, sortedPolygonArray } from '@/store/canvasAtom' import { QLine } from '@/components/fabric/QLine' export default function Roof2() { - const { - canvas, - handleRedo, - handleUndo, - setCanvasBackgroundWithDots, - saveImage, - } = useCanvas('canvas') + const { canvas, handleRedo, handleUndo, setCanvasBackgroundWithDots, saveImage } = useCanvas('canvas') //canvas 기본 사이즈 const [canvasSize, setCanvasSize] = useRecoilState(canvasSizeState) @@ -39,18 +29,8 @@ export default function Roof2() { const [showControl, setShowControl] = useState(false) - const { - mode, - changeMode, - handleClear, - fillCellInPolygon, - zoomIn, - zoomOut, - zoom, - togglePolygonLine, - handleOuterlinesTest2, - applyTemplateB, - } = useMode() + const { mode, changeMode, handleClear, fillCellInPolygon, zoomIn, zoomOut, zoom, togglePolygonLine, handleOuterlinesTest2, applyTemplateB } = + useMode() useEffect(() => { if (!canvas) { @@ -170,19 +150,33 @@ export default function Roof2() { { x: 550, y: 300 }, { x: 550, y: 450 }, ] + + const type1A = [ + { x: 67, y: 81 }, + { x: 67, y: 660 }, + { x: 437, y: 660 }, + { x: 437, y: 1190 }, + { x: 858, y: 1190 }, + { x: 858, y: 81 }, + ] + + const type1B = [ + { x: 137, y: 42 }, + { x: 137, y: 621 }, + { x: 667, y: 621 }, + { x: 667, y: 991 }, + { x: 1088, y: 991 }, + { x: 1088, y: 42 }, + ] if (canvas) { - const polygon = new QPolygon( - type4, - { - fill: 'transparent', - stroke: 'black', - strokeWidth: 1, - selectable: true, - fontSize: fontSize, - name: 'QPolygon1', - }, - canvas, // 필수로 넣어줘야 함 - ) + const polygon = new QPolygon(type1, { + fill: 'transparent', + stroke: 'black', + strokeWidth: 1, + selectable: true, + fontSize: fontSize, + name: 'QPolygon1', + }) canvas?.add(polygon) @@ -251,11 +245,7 @@ export default function Roof2() { {canvas && ( <>
- - - - - - -
-
+
- +
- +
diff --git a/src/components/fabric/HelpLine.js b/src/components/fabric/HelpLine.js new file mode 100644 index 00000000..68112099 --- /dev/null +++ b/src/components/fabric/HelpLine.js @@ -0,0 +1,108 @@ +import { fabric } from 'fabric' + +export class QLine extends fabric.Group { + line + text + fontSize + length = 0 + x1 + y1 + x2 + y2 + direction + type = 'HelpLine' + parent + #lengthTxt = 0 + + constructor(points, option, lengthTxt) { + const [x1, y1, x2, y2] = points + + if (!option.fontSize) { + throw new Error('Font size is required.') + } + + const line = new fabric.Line(points, { ...option, strokeWidth: 1 }) + super([line], {}) + + this.x1 = x1 + this.y1 = y1 + this.x2 = x2 + this.y2 = y2 + this.line = line + this.fontSize = option.fontSize + this.direction = option.direction + this.parent = option.parent + + if (lengthTxt > 0) { + this.#lengthTxt = Number(lengthTxt) + } + + this.#init() + this.#addControl() + } + + #init() { + this.#addLengthText(true) + } + + #addControl() { + this.on('moving', () => { + this.#addLengthText(false) + }) + + this.on('modified', (e) => { + this.#addLengthText(false) + }) + + this.on('selected', () => { + Object.keys(this.controls).forEach((controlKey) => { + if (controlKey !== 'ml' && controlKey !== 'mr') { + this.setControlVisible(controlKey, false) + } + }) + }) + } + + #addLengthText(isFirst) { + if (this.text) { + this.removeWithUpdate(this.text) + this.text = null + } + + if (isFirst && this.#lengthTxt > 0) { + const text = new fabric.Textbox(this.#lengthTxt.toFixed(0).toString(), { + left: (this.x1 + this.x2) / 2, + top: (this.y1 + this.y2) / 2, + fontSize: this.fontSize, + }) + this.length = this.#lengthTxt + this.text = text + this.addWithUpdate(text) + return + } + + const scaleX = this.scaleX + const scaleY = this.scaleY + const x1 = this.left + const y1 = this.top + const x2 = this.left + this.width * scaleX + const y2 = this.top + this.height * scaleY + const dx = x2 - x1 + const dy = y2 - y1 + this.length = Number(Math.sqrt(dx * dx + dy * dy).toFixed(0)) + + const text = new fabric.Textbox(this.length.toFixed(0).toString(), { + left: (x1 + x2) / 2, + top: (y1 + y2) / 2, + fontSize: this.fontSize, + }) + this.text = text + this.addWithUpdate(text) + } + + setFontSize(fontSize) { + this.fontSize = fontSize + this.text.set({ fontSize }) + this.addWithUpdate() + } +} diff --git a/src/components/fabric/QLine.js b/src/components/fabric/QLine.js index 9ccb1a5c..23dbb0a4 100644 --- a/src/components/fabric/QLine.js +++ b/src/components/fabric/QLine.js @@ -10,11 +10,17 @@ export class QLine extends fabric.Group { x2 y2 direction + idx type = 'QLine' parent #lengthTxt = 0 - constructor(points, option, lengthTxt) { + constructor(points, option = { isActiveLengthText: true }, lengthTxt) { + // 소수점 전부 제거 + points.forEach((point) => { + point = Math.round(point) + }) + const [x1, y1, x2, y2] = points if (!option.fontSize) { @@ -32,12 +38,13 @@ export class QLine extends fabric.Group { this.fontSize = option.fontSize this.direction = option.direction this.parent = option.parent + this.idx = option.idx if (lengthTxt > 0) { this.#lengthTxt = Number(lengthTxt) } - this.#init() + option.isActiveLengthText ?? this.#init() this.#addControl() } diff --git a/src/components/fabric/QPolygon.js b/src/components/fabric/QPolygon.js index 93d39298..711b1a4a 100644 --- a/src/components/fabric/QPolygon.js +++ b/src/components/fabric/QPolygon.js @@ -1,6 +1,8 @@ import { fabric } from 'fabric' import { + calculateIntersection, distanceBetweenPoints, + findClosestLineToPoint, findTopTwoIndexesByDistance, getDegreeByChon, getDirectionByPoint, @@ -23,14 +25,22 @@ export default class QPolygon extends fabric.Group { shape = 0 // 점 6개일때의 shape 모양 helpPoints = [] helpLines = [] + + wall constructor(points, options, canvas) { - if (points.length !== 4 && points.length !== 6) { + /*if (points.length !== 4 && points.length !== 6) { throw new Error('Points must be 4 or 6.') - } + }*/ if (!options.fontSize) { throw new Error('Font size is required.') } + // 소수점 전부 제거 + points.forEach((point) => { + point.x = Math.round(point.x) + point.y = Math.round(point.y) + }) + const sortPoints = sortedPoints(points) const polygon = new fabric.Polygon(sortPoints, options) @@ -80,6 +90,10 @@ export default class QPolygon extends fabric.Group { }) } + setWall(wall) { + this.wall = wall + } + setFontSize(fontSize) { this.fontSize = fontSize this.texts.forEach((text) => { @@ -110,10 +124,7 @@ export default class QPolygon extends fabric.Group { const dy = end.y - start.y const length = Math.sqrt(dx * dx + dy * dy) - const midPoint = new fabric.Point( - (start.x + end.x) / 2, - (start.y + end.y) / 2, - ) + const midPoint = new fabric.Point((start.x + end.x) / 2, (start.y + end.y) / 2) // Create new text object if it doesn't exist const text = new fabric.Text(length.toFixed(0), { @@ -157,16 +168,8 @@ export default class QPolygon extends fabric.Group { return } - for ( - let x = bounds.left; - x < bounds.left + bounds.width; - x += cell.width + cell.padding - ) { - for ( - let y = bounds.top; - y < bounds.top + bounds.height; - y += cell.height + cell.padding - ) { + for (let x = bounds.left; x < bounds.left + bounds.width; x += cell.width + cell.padding) { + for (let y = bounds.top; y < bounds.top + bounds.height; y += cell.height + cell.padding) { const rect = new fabric.Rect({ left: x, top: y, @@ -184,11 +187,7 @@ export default class QPolygon extends fabric.Group { new fabric.Point(rect.left + rect.width, rect.top + rect.height), ] - const isInside = rectPoints.every( - (rectPoint) => - this.inPolygon(rectPoint) && - this.#distanceFromEdge(rectPoint) >= cell.padding, - ) + const isInside = rectPoints.every((rectPoint) => this.inPolygon(rectPoint) && this.#distanceFromEdge(rectPoint) >= cell.padding) if (isInside) { this.addWithUpdate(rect) @@ -205,24 +204,13 @@ export default class QPolygon extends fabric.Group { * return {boolean} */ isValid() { - const leftLinesLengthSum = this.lines - .filter((line) => line.direction === 'left') - .reduce((sum, line) => sum + line.length, 0) - const rightLinesLengthSum = this.lines - .filter((line) => line.direction === 'right') - .reduce((sum, line) => sum + line.length, 0) + const leftLinesLengthSum = this.lines.filter((line) => line.direction === 'left').reduce((sum, line) => sum + line.length, 0) + const rightLinesLengthSum = this.lines.filter((line) => line.direction === 'right').reduce((sum, line) => sum + line.length, 0) - const topLinesLengthSum = this.lines - .filter((line) => line.direction === 'top') - .reduce((sum, line) => sum + line.length, 0) - const bottomLinesLengthSum = this.lines - .filter((line) => line.direction === 'bottom') - .reduce((sum, line) => sum + line.length, 0) + const topLinesLengthSum = this.lines.filter((line) => line.direction === 'top').reduce((sum, line) => sum + line.length, 0) + const bottomLinesLengthSum = this.lines.filter((line) => line.direction === 'bottom').reduce((sum, line) => sum + line.length, 0) - return ( - leftLinesLengthSum === rightLinesLengthSum && - topLinesLengthSum === bottomLinesLengthSum - ) + return leftLinesLengthSum === rightLinesLengthSum && topLinesLengthSum === bottomLinesLengthSum } inPolygon(point) { @@ -247,10 +235,7 @@ export default class QPolygon extends fabric.Group { continue } - let xInt = - ((point.y - vertex1.y) * (vertex2.x - vertex1.x)) / - (vertex2.y - vertex1.y) + - vertex1.x + let xInt = ((point.y - vertex1.y) * (vertex2.x - vertex1.x)) / (vertex2.y - vertex1.y) + vertex1.x if (xInt < point.x) { intersects++ } @@ -270,9 +255,7 @@ export default class QPolygon extends fabric.Group { const dx = vertex2.x - vertex1.x const dy = vertex2.y - vertex1.y - const t = - ((point.x - vertex1.x) * dx + (point.y - vertex1.y) * dy) / - (dx * dx + dy * dy) + const t = ((point.x - vertex1.x) * dx + (point.y - vertex1.y) * dy) / (dx * dx + dy * dy) let closestPoint if (t < 0) { @@ -352,9 +335,7 @@ export default class QPolygon extends fabric.Group { return } //외각선 기준 - const topIndex = findTopTwoIndexesByDistance(this.lines).sort( - (a, b) => a - b, - ) //배열중에 큰 2값을 가져옴 TODO: 나중에는 인자로 받아서 다각으로 수정 해야됨 + const topIndex = findTopTwoIndexesByDistance(this.lines).sort((a, b) => a - b) //배열중에 큰 2값을 가져옴 TODO: 나중에는 인자로 받아서 다각으로 수정 해야됨 //일단 배열 6개 짜리 기준의 선 번호 if (topIndex[0] === 4) { @@ -425,23 +406,15 @@ export default class QPolygon extends fabric.Group { const direction = smallestLines[0].direction if (direction === 'top' || direction === 'bottom') { - needPlusLine = - smallestLines[0].x1 < smallestLines[1].x1 - ? smallestLines[0] - : smallestLines[1] - needMinusLine = - needPlusLine === smallestLines[0] ? smallestLines[1] : smallestLines[0] + needPlusLine = smallestLines[0].x1 < smallestLines[1].x1 ? smallestLines[0] : smallestLines[1] + needMinusLine = needPlusLine === smallestLines[0] ? smallestLines[1] : smallestLines[0] type = 1 // 가로가 긴 사각형 } if (direction === 'left' || direction === 'right') { - needPlusLine = - smallestLines[0].y1 < smallestLines[1].y1 - ? smallestLines[0] - : smallestLines[1] - needMinusLine = - needPlusLine === smallestLines[0] ? smallestLines[1] : smallestLines[0] + needPlusLine = smallestLines[0].y1 < smallestLines[1].y1 ? smallestLines[0] : smallestLines[1] + needMinusLine = needPlusLine === smallestLines[0] ? smallestLines[1] : smallestLines[0] type = 2 // 세로가 긴 사각형 } @@ -452,33 +425,21 @@ export default class QPolygon extends fabric.Group { if (type === 1) { point1 = { x: needPlusLine.x1 + smallestLength / 2, - y: - needPlusLine.y1 > needPlusLine.y2 - ? needPlusLine.y1 - smallestLength / 2 - : needPlusLine.y2 - smallestLength / 2, + y: needPlusLine.y1 > needPlusLine.y2 ? needPlusLine.y1 - smallestLength / 2 : needPlusLine.y2 - smallestLength / 2, } point2 = { x: needMinusLine.x1 - smallestLength / 2, - y: - needMinusLine.y1 > needMinusLine.y2 - ? needMinusLine.y1 - smallestLength / 2 - : needMinusLine.y2 - smallestLength / 2, + y: needMinusLine.y1 > needMinusLine.y2 ? needMinusLine.y1 - smallestLength / 2 : needMinusLine.y2 - smallestLength / 2, } } else if (type === 2) { point1 = { - x: - needPlusLine.x1 > needPlusLine.x2 - ? needPlusLine.x1 - smallestLength / 2 - : needPlusLine.x2 - smallestLength / 2, + x: needPlusLine.x1 > needPlusLine.x2 ? needPlusLine.x1 - smallestLength / 2 : needPlusLine.x2 - smallestLength / 2, y: needPlusLine.y1 + smallestLength / 2, } point2 = { - x: - needMinusLine.x1 > needMinusLine.x2 - ? needMinusLine.x1 - smallestLength / 2 - : needMinusLine.x2 - smallestLength / 2, + x: needMinusLine.x1 > needMinusLine.x2 ? needMinusLine.x1 - smallestLength / 2 : needMinusLine.x2 - smallestLength / 2, y: needMinusLine.y1 - smallestLength / 2, } } @@ -566,357 +527,144 @@ export default class QPolygon extends fabric.Group { } #drawHelpLineInHexagon(chon) { - let type = this.shape + const historyLines = [] + const helpPoints = [] + const notInterSectionLines = [] + const ridge = [] + const maxLength = this.lines.reduce((max, obj) => Math.min(max, obj.length), 999999) + this.points.forEach((point, index) => { + const wallPoint = this.wall.points[index] + // 두 점의 좌표 + const x1 = point.x + const y1 = point.y + const x2 = wallPoint.x + const y2 = wallPoint.y - // 1 = 0, 3 - // 2 = 2, 5 - // 3 = 1, 4 - // 4 = 0, 3 + let newX2, newY2 - // 라인 기준점 1,2 - let lines, lines2 - // 용마루 시작점 2개 - let vPoint1, vPoint2 - // 용마루 시작점과 만나는 지붕의 중앙 - let centerPoint1, centerPoint2 + // x1, y1을 기준으로 x2, y2와의 거리를 유지한 새로운 직선 생성 + const angle = Math.atan2(y2 - y1, x2 - x1) - // 가장 긴 라인 - let longestLines + newX2 = x1 + (maxLength + 50) * Math.cos(angle) + newY2 = y1 + (maxLength + 50) * Math.sin(angle) - // 용마루 길이 - let ridgeLength = 0 - let ridgeStartPoint1, ridgeStartPoint2 + const line = new QLine([x1, y1, newX2, newY2], { + fontSize: this.fontSize, + stroke: 'blue', + idx: index, + }) + historyLines.push(line) + this.addWithUpdate(line) - let ridgeEndPoint1, ridgeEndPoint2 + this.canvas.renderAll() + }) - let ridgeLength1, ridgeLength2 + /** + * 삼각 지붕 + */ + historyLines.forEach((line, index) => { + const prevLine = historyLines[(index - 1 + historyLines.length) % historyLines.length] - let ridgeHelpLinePoint1, ridgeHelpLinePoint2 + let intersectionPoint = calculateIntersection(line, prevLine) - if (type === 1) { - lines = [this.lines[0], this.lines[3]] - lines2 = [this.lines[1], this.lines[4]] - longestLines = [this.lines[4], this.lines[5]] - ridgeLength1 = lines2[0].length - ridgeLength2 = longestLines[0].length - lines[1].length - vPoint1 = { - x: lines[0].x1 + lines[0].length / 2, - y: lines[0].y1 + lines[0].length / 2, - } - vPoint2 = { - x: lines[1].x1 + lines[1].length / 2, - y: lines[1].y1 - lines[1].length / 2, - } - centerPoint1 = { - x: (lines[0].x1 + lines[0].x2) / 2, - y: (lines[0].y1 + lines[0].y2) / 2, - } - centerPoint2 = { - x: (lines[1].x1 + lines[1].x2) / 2, - y: (lines[1].y1 + lines[1].y2) / 2, + if (!intersectionPoint) { + notInterSectionLines.push(line) + return } - ridgeEndPoint1 = [ - vPoint1.x, - vPoint1.y, - vPoint1.x + ridgeLength1, - vPoint1.y, - ] - ridgeEndPoint2 = [ - vPoint2.x, - vPoint2.y, - vPoint2.x, - vPoint2.y - ridgeLength2, - ] + const helpLine1 = new QLine([prevLine.x1, prevLine.y1, intersectionPoint.x, intersectionPoint.y], { + fontSize: this.fontSize, + stroke: 'red', + }) - ridgeHelpLinePoint1 = [ - lines2[0].x2, - lines2[0].y2, - ridgeEndPoint1[2], - ridgeEndPoint1[3], - ] - ridgeHelpLinePoint2 = [ - lines2[1].x2, - lines2[1].y2, - ridgeEndPoint2[2], - ridgeEndPoint2[3], - ] - } else if (type === 2) { - lines = [this.lines[2], this.lines[5]] - lines2 = [this.lines[0], this.lines[3]] - longestLines = [this.lines[0], this.lines[1]] - ridgeLength1 = lines2[1].length - ridgeLength2 = longestLines[0].length - lines[1].length - vPoint1 = { - x: lines[0].x1 - lines[0].length / 2, - y: lines[0].y1 - lines[0].length / 2, - } - vPoint2 = { - x: lines[1].x1 - lines[1].length / 2, - y: lines[1].y1 + lines[1].length / 2, - } - centerPoint1 = { - x: (lines[0].x1 + lines[0].x2) / 2, - y: (lines[0].y1 + lines[0].y2) / 2, - } - centerPoint2 = { - x: (lines[1].x1 + lines[1].x2) / 2, - y: (lines[1].y1 + lines[1].y2) / 2, + const helpLine2 = new QLine([line.x1, line.y1, intersectionPoint.x, intersectionPoint.y], { + fontSize: this.fontSize, + stroke: 'red', + }) + notInterSectionLines.pop() + helpPoints.push(intersectionPoint) + + this.addWithUpdate(helpLine1) + this.addWithUpdate(helpLine2) + this.removeWithUpdate(prevLine) + this.removeWithUpdate(line) + this.canvas.renderAll() + }) + // 용마루 + + const ridgePoint = [] + + helpPoints.forEach((helpPoint, index) => { + const closestLine = findClosestLineToPoint(helpPoint, notInterSectionLines) + + // 가까운 선의 중심점 + const centerClosestLinePoint = { + x: (closestLine.x1 + closestLine.x2) / 2, + y: (closestLine.y1 + closestLine.y2) / 2, } - ridgeEndPoint1 = [ - vPoint1.x, - vPoint1.y, - vPoint1.x - ridgeLength1, - vPoint1.y, - ] + const direction = getDirectionByPoint(helpPoint, centerClosestLinePoint) - ridgeEndPoint2 = [ - vPoint2.x, - vPoint2.y, - vPoint2.x, - vPoint2.y + ridgeLength2, - ] + let newX, newY - ridgeHelpLinePoint1 = [ - lines2[1].x2, - lines2[1].y2, - ridgeEndPoint1[2], - ridgeEndPoint1[3], - ] - ridgeHelpLinePoint2 = [ - lines2[0].x2, - lines2[0].y2, - ridgeEndPoint2[2], - ridgeEndPoint2[3], - ] - } else if (type === 3) { - lines = [this.lines[1], this.lines[4]] - lines2 = [this.lines[2], this.lines[5]] - longestLines = [this.lines[0], this.lines[5]] - ridgeLength1 = this.lines[3].length - ridgeLength2 = longestLines[0].length - lines[0].length - vPoint1 = { - x: lines[0].x1 + lines[0].length / 2, - y: lines[0].y1 - lines[0].length / 2, - } - vPoint2 = { - x: lines[1].x1 - lines[1].length / 2, - y: lines[1].y1 - lines[1].length / 2, - } - centerPoint1 = { - x: (lines[0].x1 + lines[0].x2) / 2, - y: (lines[0].y1 + lines[0].y2) / 2, - } - centerPoint2 = { - x: (lines[1].x1 + lines[1].x2) / 2, - y: (lines[1].y1 + lines[1].y2) / 2, + switch (direction) { + case 'left': { + newX = ((closestLine.x2 - closestLine.x1) * (helpPoint.y - closestLine.y1)) / (closestLine.y2 - closestLine.y1) + closestLine.x1 + newY = helpPoint.y + break + } + case 'right': { + newX = ((closestLine.x2 - closestLine.x1) * (helpPoint.y - closestLine.y1)) / (closestLine.y2 - closestLine.y1) + closestLine.x1 + newY = helpPoint.y + break + } + case 'top': { + newX = helpPoint.x + newY = ((closestLine.y2 - closestLine.y1) * (helpPoint.x - closestLine.x1)) / (closestLine.x2 - closestLine.x1) + closestLine.y1 + break + } + case 'bottom': { + newX = helpPoint.x + newY = ((closestLine.y2 - closestLine.y1) * (helpPoint.x - closestLine.x1)) / (closestLine.x2 - closestLine.x1) + closestLine.y1 + break + } } - ridgeEndPoint1 = [ - vPoint1.x, - vPoint1.y, - vPoint1.x, - vPoint1.y - ridgeLength2, - ] + const ridgeHelpLine = new QLine([closestLine.x1, closestLine.y1, newX, newY], { + fontSize: this.fontSize, + stroke: 'purple', + strokeWidth: 5, + }) - ridgeEndPoint2 = [ - vPoint2.x, - vPoint2.y, - vPoint2.x - ridgeLength1, - vPoint2.y, - ] + ridgePoint.push({ x: newX, y: newY }) - ridgeHelpLinePoint1 = [ - lines2[1].x2, - lines2[1].y2, - ridgeEndPoint1[2], - ridgeEndPoint1[3], - ] - ridgeHelpLinePoint2 = [ - lines2[0].x2, - lines2[0].y2, - ridgeEndPoint2[2], - ridgeEndPoint2[3], - ] - } else if (type === 4) { - lines = [this.lines[0], this.lines[3]] - lines2 = [this.lines[1], this.lines[4]] - longestLines = [this.lines[1], this.lines[2]] - ridgeLength1 = longestLines[0].length - lines[0].length - ridgeLength2 = this.lines[4].length - vPoint1 = { - x: lines[0].x1 + lines[0].length / 2, - y: lines[0].y1 + lines[0].length / 2, - } - vPoint2 = { - x: lines[1].x1 - lines[1].length / 2, - y: lines[1].y1 + lines[1].length / 2, - } - centerPoint1 = { - x: (lines[0].x1 + lines[0].x2) / 2, - y: (lines[0].y1 + lines[0].y2) / 2, - } - centerPoint2 = { - x: (lines[1].x1 + lines[1].x2) / 2, - y: (lines[1].y1 + lines[1].y2) / 2, - } + const ridge = new QLine([helpPoint.x, helpPoint.y, newX, newY], { + fontSize: this.fontSize, + stroke: 'skyblue', + strokeWidth: 5, + }) - ridgeEndPoint1 = [ - vPoint1.x, - vPoint1.y, - vPoint1.x + ridgeLength1, - vPoint1.y, - ] + this.addWithUpdate(ridge) + this.canvas.renderAll() - ridgeEndPoint2 = [ - vPoint2.x, - vPoint2.y, - vPoint2.x, - vPoint2.y + ridgeLength2, - ] + this.addWithUpdate(ridgeHelpLine) + this.removeWithUpdate(closestLine) + this.canvas.renderAll() + }) - ridgeHelpLinePoint1 = [ - lines2[0].x2, - lines2[0].y2, - ridgeEndPoint1[2], - ridgeEndPoint1[3], - ] - ridgeHelpLinePoint2 = [ - lines2[1].x2, - lines2[1].y2, - ridgeEndPoint2[2], - ridgeEndPoint2[3], - ] + // 용마루 끼리 연결 + for (let i = 0; i < ridgePoint.length; i = i + 2) { + const currentRidgeEndPoint = ridgePoint[i] + const nextRidgeEndPoint = ridgePoint[(i + 1) % ridgePoint.length] + const ridgeConnectLine = new QLine([currentRidgeEndPoint.x, currentRidgeEndPoint.y, nextRidgeEndPoint.x, nextRidgeEndPoint.y], { + fontSize: this.fontSize, + stroke: 'green', + strokeWidth: 5, + }) + this.addWithUpdate(ridgeConnectLine) + this.canvas.renderAll() } - const realLine1 = new QLine( - [lines[0].x1, lines[0].y1, vPoint1.x, vPoint1.y], - { fontSize: this.fontSize, stroke: 'blue', strokeWidth: 1 }, - getRoofHypotenuse(lines[0].length / 2), - ) - - const realLine2 = new QLine( - [lines[0].x2, lines[0].y2, vPoint1.x, vPoint1.y], - { fontSize: this.fontSize, stroke: 'blue', strokeWidth: 1 }, - getRoofHypotenuse(lines[0].length / 2), - ) - - const realLine3 = new QLine( - [lines[1].x1, lines[1].y1, vPoint2.x, vPoint2.y], - { fontSize: this.fontSize, stroke: 'blue', strokeWidth: 1 }, - getRoofHypotenuse(lines[1].length / 2), - ) - - const realLine4 = new QLine( - [lines[1].x2, lines[1].y2, vPoint2.x, vPoint2.y], - { fontSize: this.fontSize, stroke: 'blue', strokeWidth: 1 }, - getRoofHypotenuse(lines[1].length / 2), - ) - - // 옆으로 누워있는 지붕의 높이 점선 - const realLine5 = new QLine( - [vPoint1.x, vPoint1.y, centerPoint1.x, centerPoint1.y], - { - fontSize: this.fontSize, - stroke: 'blue', - strokeWidth: 1, - strokeDashArray: [5, 5], - }, - getRoofHeight(lines[0].length / 2, getDegreeByChon(chon)), - ) - - // 옆으로 누워있는 지붕의 높이 점선 - const realLine6 = new QLine( - [vPoint2.x, vPoint2.y, centerPoint2.x, centerPoint2.y], - { - fontSize: this.fontSize, - stroke: 'blue', - strokeWidth: 1, - strokeDashArray: [5, 5], - }, - getRoofHeight(lines[1].length / 2, getDegreeByChon(chon)), - ) - - // 용마루 보조선 - const ridgeHelpLine1 = new QLine( - ridgeHelpLinePoint1, - { - fontSize: this.fontSize, - stroke: 'blue', - strokeWidth: 1, - }, - getRoofHypotenuse(lines[0].length / 2), - ) - - // 용마루 보조선 - const ridgeHelpLine2 = new QLine( - ridgeHelpLinePoint2, - { - fontSize: this.fontSize, - stroke: 'blue', - strokeWidth: 1, - }, - getRoofHypotenuse(lines[1].length / 2), - ) - - // 용마루 - const ridge1 = new QLine( - [vPoint1.x, vPoint1.y, ridgeEndPoint1[2], ridgeEndPoint1[3]], - { - fontSize: this.fontSize, - stroke: 'blue', - strokeWidth: 1, - }, - ) - - // 용마루 - const ridge2 = new QLine( - [vPoint2.x, vPoint2.y, ridgeEndPoint2[2], ridgeEndPoint2[3]], - { - fontSize: this.fontSize, - stroke: 'blue', - strokeWidth: 1, - }, - ) - - const ridgeEndLine = new QLine( - [ - ridgeEndPoint1[2], - ridgeEndPoint1[3], - ridgeEndPoint2[2], - ridgeEndPoint2[3], - ], - { - fontSize: this.fontSize, - stroke: 'blue', - strokeWidth: 1, - }, - Math.abs(realLine1.length - realLine3.length), - ) - this.helpLines = [ - realLine1, - realLine2, - realLine3, - realLine4, - realLine5, - realLine6, - ridge1, - ridge2, - ridgeEndLine, - ] - this.addWithUpdate(realLine1) - this.addWithUpdate(realLine2) - this.addWithUpdate(realLine3) - this.addWithUpdate(realLine4) - this.addWithUpdate(realLine5) - this.addWithUpdate(realLine6) - this.addWithUpdate(ridgeHelpLine1) - this.addWithUpdate(ridgeHelpLine2) - this.addWithUpdate(ridge1) - this.addWithUpdate(ridge2) - this.addWithUpdate(ridgeEndLine) - this.canvas.renderAll() } diff --git a/src/hooks/useMode.js b/src/hooks/useMode.js index 91380be4..6785e828 100644 --- a/src/hooks/useMode.js +++ b/src/hooks/useMode.js @@ -1,20 +1,9 @@ import { useEffect, useRef, useState } from 'react' import QRect from '@/components/fabric/QRect' import QPolygon from '@/components/fabric/QPolygon' -import { - getStartIndex, - rearrangeArray, - findTopTwoIndexesByDistance, - getDirection, - getCenterPoint, -} from '@/util/canvas-util' +import { getStartIndex, rearrangeArray, findTopTwoIndexesByDistance, getDirection, getCenterPoint } from '@/util/canvas-util' import { useRecoilState, useSetRecoilState } from 'recoil' -import { - fontSizeState, - roofState, - sortedPolygonArray, - wallState, -} from '@/store/canvasAtom' +import { fontSizeState, roofState, sortedPolygonArray, wallState } from '@/store/canvasAtom' import { QLine } from '@/components/fabric/QLine' export const Mode = { @@ -101,8 +90,7 @@ export function useMode() { if (isNaN(length) || length === 0) { //마지막 추가 된 points 제거합니다. - const lastPoint = - historyPoints.current[historyPoints.current.length - 1] + const lastPoint = historyPoints.current[historyPoints.current.length - 1] canvas?.remove(lastPoint) @@ -134,12 +122,7 @@ export function useMode() { } const line = new QLine( - [ - points.current[0].left, - points.current[0].top, - points.current[0].left + scaledVector.x, - points.current[0].top + scaledVector.y, - ], + [points.current[0].left, points.current[0].top, points.current[0].left + scaledVector.x, points.current[0].top + scaledVector.y], { stroke: 'black', strokeWidth: 2, @@ -180,27 +163,20 @@ export function useMode() { } const pushHistoryLine = (line) => { - if ( - historyLines.current.length > 0 && - historyLines.current[historyLines.current.length - 1].direction === - line.direction - ) { + 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, - }, - ) + 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 { @@ -246,9 +222,12 @@ export function useMode() { points.current = [] historyPoints.current = [] - handleOuterlinesTest() + const roof = handleOuterlinesTest() const wall = makePolygon() + + roof.setWall(wall) setWall(wall) + roof.drawHelpLine() } } @@ -460,9 +439,7 @@ export function useMode() { const newOuterlines = [] for (let i = 0; i < historyLines.current.length; i++) { const next = historyLines.current[i + 1] - const prev = - historyLines.current[i - 1] ?? - historyLines.current[historyLines.current.length - 1] + const prev = historyLines.current[i - 1] ?? historyLines.current[historyLines.current.length - 1] if (next) { if (next.direction === 'right') { // 다름 라인이 오른쪽으로 이동 @@ -797,9 +774,7 @@ export function useMode() { } // 평균 법선 벡터를 단위 벡터로 정규화 - var lengthNormal = Math.sqrt( - averageNormal.x * averageNormal.x + averageNormal.y * averageNormal.y, - ) + var lengthNormal = Math.sqrt(averageNormal.x * averageNormal.x + averageNormal.y * averageNormal.y) var unitNormal = { x: averageNormal.x / lengthNormal, y: averageNormal.y / lengthNormal, @@ -816,7 +791,8 @@ export function useMode() { const roof = makePolygon(offsetPoints) setRoof(roof) - roof.drawHelpLine() + + return roof } /** @@ -867,9 +843,7 @@ export function useMode() { } // 평균 법선 벡터를 단위 벡터로 정규화 - const lengthNormal = Math.sqrt( - averageNormal.x * averageNormal.x + averageNormal.y * averageNormal.y, - ) + const lengthNormal = Math.sqrt(averageNormal.x * averageNormal.x + averageNormal.y * averageNormal.y) const unitNormal = { x: averageNormal.x / lengthNormal, y: averageNormal.y / lengthNormal, @@ -885,6 +859,7 @@ export function useMode() { } const roof = makePolygon(offsetPoints) + roof.setWall(polygon) setRoof(roof) roof.drawHelpLine() } @@ -927,70 +902,532 @@ export function useMode() { /** * 템플릿 B 적용 - * 1. 모드 체인지 - * 2. 외벽선 그리기 마무리 */ const applyTemplateB = () => { changeMode(canvas, Mode.EDIT) const polygon = drawWallPolygon() - handleOuterLineTemplateB(polygon) + const params = { + eaves: 50, + edge: 20, + polygon, + } + handleInnerLineColor(polygon) + // handleOuterLineTemplateB(params) + console.log(polygon.lines.length) + if (polygon.lines.length === 4) { + handleTemplateBRect(params) + } else if (polygon.length === 6) { + handleTemplateB(params) + } } - const handleOuterLineTemplateB = (polygon) => { + /** + * 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, + } + + 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 + } 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) + } 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) + + 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, + } + + 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 + } 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 + } 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) + } + } 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] + } else { + points1 = [line.x1 + edge, line.y1 - eaves, line.x2 + edge, centeredPoint] + points2 = [line.x2 + edge, centeredPoint, line.x2 + edge, line.y2 + eaves] + } + } + } 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) + } 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 + } 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 + } + } 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 + } 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 + } + } + } + + 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) + 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) + 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) + 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) + 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) + + 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 handleOuterLineTemplateB = (params) => { + const { eaves, edge, polygon } = params polygon.points.forEach((point, index) => { - let x2 = - index === polygon.points.length - 1 - ? polygon.points[0].x - : polygon.points[index + 1].x - let y2 = - index === polygon.points.length - 1 - ? polygon.points[0].y - : polygon.points[index + 1].y + let x2 = index === polygon.points.length - 1 ? polygon.points[0].x : polygon.points[index + 1].x + let y2 = index === polygon.points.length - 1 ? polygon.points[0].y : polygon.points[index + 1].y let x1 = point.x let y1 = point.y if (index % 2 === 0) { if (polygon.lines[index].direction === 'bottom') { - y1 = y1 - 50 - y2 = y2 + 50 - x1 = x1 - 20 - x2 = x2 - 20 + y1 = y1 - eaves + y2 = y2 + eaves + x1 = x1 - edge + x2 = x2 - edge } else { - y1 = y1 + 50 - y2 = y2 - 50 - x1 = x1 + 20 - x2 = x2 + 20 + y1 = y1 + eaves + y2 = y2 - eaves + x1 = x1 + edge + x2 = x2 + edge } } else { if (polygon.lines[index].direction === 'right') { - x1 = x1 - 20 - x2 = x2 + 20 - y1 = y1 + 50 - y2 = y2 + 50 + x1 = x1 - edge + x2 = x2 + edge + y1 = y1 + eaves + y2 = y2 + eaves } else { - x1 = x1 + 20 - x2 = x2 - 20 - y1 = y1 - 50 - y2 = y2 - 50 + x1 = x1 + edge + x2 = x2 - edge + y1 = y1 - eaves + y2 = y2 - eaves } } switch (polygon.shape) { case 1: - break - case 2: - const centerPoint = - polygon.points[3].y + - (polygon.points[2].y - polygon.points[3].y) / 2 if (index === 0) { const subLine = new QLine( [ - point.x - 20, - polygon.points[0].y + - (polygon.points[1].y - polygon.points[0].y) / 2, - polygon.points[5].x + 20, - polygon.points[0].y + - (polygon.points[1].y - polygon.points[0].y) / 2, + point.x - edge, + polygon.points[0].y + (polygon.points[1].y - polygon.points[0].y) / 2, + polygon.points[2].x, + polygon.points[0].y + (polygon.points[1].y - polygon.points[0].y) / 2, + ], + { + stroke: 'blue', + strokeWidth: 2, + selectable: false, + fontSize: fontSize, + }, + ) + canvas.add(subLine) + } + if (index === 1) { + x2 = x2 - edge + const subLine = new QLine([x2, y2, x2, polygon.points[0].y + (polygon.points[1].y - polygon.points[0].y) / 2], { + stroke: 'blue', + strokeWidth: 2, + selectable: false, + fontSize: fontSize, + }) + canvas.add(subLine) + } + if (index === 2) { + y1 = point.y - (polygon.points[1].y - polygon.points[0].y) / 2 + const centeredPoint = getCenterPoint(polygon.points[1].x, point.x) + const subLine = new QLine( + [centeredPoint, point.y + eaves, centeredPoint, polygon.points[0].y + (polygon.points[1].y - polygon.points[0].y) / 2], + { + stroke: 'black', + strokeWidth: 2, + strokeDashArray: [5, 5], + selectable: false, + fontSize: fontSize, + }, + ) + canvas.add(subLine) + } + if (index === 3) { + const centeredPoint = getCenterPoint(point.x, polygon.points[4].x) + const subLine = new QLine([centeredPoint, point.y + eaves, centeredPoint, polygon.points[5].y - eaves], { + stroke: 'black', + strokeWidth: 2, + strokeDashArray: [5, 5], + selectable: false, + fontSize: fontSize, + }) + canvas.add(subLine) + } + if (index === 5) { + const centeredPoint = getCenterPoint(point.y, polygon.points[4].y) + const subLine = new QLine([point.x + edge, centeredPoint, polygon.points[2].x - edge, centeredPoint], { + stroke: 'blue', + strokeWidth: 2, + selectable: false, + fontSize: fontSize, + }) + canvas.add(subLine) + } + break + case 2: + const centerPoint = polygon.points[3].y + (polygon.points[2].y - polygon.points[3].y) / 2 + if (index === 0) { + const subLine = new QLine( + [ + point.x - egde, + polygon.points[0].y + (polygon.points[1].y - polygon.points[0].y) / 2, + polygon.points[5].x + egde, + polygon.points[0].y + (polygon.points[1].y - polygon.points[0].y) / 2, ], { stroke: 'blue', @@ -1002,7 +1439,7 @@ export function useMode() { canvas.add(subLine) } if (index === 3) { - x2 = x2 + 20 + x2 = x2 + egde const subLine = new QLine([x2, y2, x2, centerPoint], { stroke: 'blue', @@ -1013,26 +1450,20 @@ export function useMode() { canvas.add(subLine) } if (index === 4) { - y1 = - point.y + - (polygon.points[index - 2].y - polygon.points[index - 1].y) / 2 + y1 = point.y + (polygon.points[index - 2].y - polygon.points[index - 1].y) / 2 - const subLine = new QLine( - [point.x, centerPoint, polygon.points[2].x + 20, centerPoint], - { - stroke: 'blue', - strokeWidth: 2, - selectable: false, - fontSize: fontSize, - }, - ) - canvas.add(subLine) + const subLine = new QLine([point.x, centerPoint, polygon.points[2].x + 20, centerPoint], { + stroke: 'blue', + strokeWidth: 2, + selectable: false, + fontSize: fontSize, + }) const subVerticalLine = new QLine( [ - getCenterPoint(point.x, polygon.points[2].x + 20), - polygon.points[3].y - 50, - getCenterPoint(point.x, polygon.points[2].x + 20), + getCenterPoint(point.x, polygon.points[2].x + egde), + polygon.points[3].y - eaves, + getCenterPoint(point.x, polygon.points[2].x + egde), centerPoint, ], { @@ -1047,25 +1478,14 @@ export function useMode() { } if (index === 5) { - const centeredPoint = getCenterPoint( - polygon.points[0].x, - polygon.points[5].x, - ) - const verticalSubLine = new QLine( - [ - centeredPoint, - polygon.points[0].y - 50, - centeredPoint, - polygon.points[1].y + 50, - ], - { - stroke: 'black', - strokeWidth: 2, - strokeDashArray: [5, 5], - selectable: false, - fontSize: fontSize, - }, - ) + const centeredPoint = getCenterPoint(polygon.points[0].x, polygon.points[5].x) + const verticalSubLine = new QLine([centeredPoint, polygon.points[0].y - eaves, centeredPoint, polygon.points[1].y + eaves], { + stroke: 'black', + strokeWidth: 2, + strokeDashArray: [5, 5], + selectable: false, + fontSize: fontSize, + }) canvas.add(verticalSubLine) } diff --git a/src/util/canvas-util.js b/src/util/canvas-util.js index e1c4b0fa..63deb773 100644 --- a/src/util/canvas-util.js +++ b/src/util/canvas-util.js @@ -352,6 +352,18 @@ export function calculateIntersection(line1, line2) { return { x: intersectX, y: intersectY } } +function findOrthogonalPoint(x1, y1, x2, y2, x3, y3, x4, y4) { + // Calculate the intersection point of two lines + const intersectionX = + ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / + ((x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)) + const intersectionY = + ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / + ((x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)) + + return { x: intersectionX, y: intersectionY } +} + /** * points배열을 입력받아 반시계방향으로 정렬된 points를 반환합니다. * @param points @@ -417,3 +429,61 @@ export const sortedPoints = (points) => { return resultPoints } + +/** + * point가 선 위에 있는지 확인 + * @param line + * @param point + * @returns {boolean} + */ +// 직선의 방정식. +// 방정식은 ax + by + c = 0이며, 점의 좌표를 대입하여 계산된 값은 직선과 점 사이의 관계를 나타낸다. +export function isPointOnLine(line, point) { + const a = line.y2 - line.y1 + const b = line.x1 - line.x2 + const c = line.x2 * line.y1 - line.x1 * line.y2 + return a * point.x + b * point.y + c === 0 +} + +/** + * 점과 가까운 line 찾기 + * @param point + * @param lines {Array} + * @returns {null} + */ +export function findClosestLineToPoint(point, lines) { + let closestLine = null + let closestDistance = Infinity + + for (const line of lines) { + const distance = calculateDistance(point, line) + + if (distance < closestDistance) { + closestDistance = distance + closestLine = line + } + } + + return closestLine +} + +/** + * 점과 직선사이의 최단 거리 + * @param point + * @param line + * @returns {number} + */ +export function calculateDistance(point, line) { + const x1 = line.x1 + const y1 = line.y1 + const x2 = line.x2 + const y2 = line.y2 + const x0 = point.x + const y0 = point.y + + const numerator = Math.abs( + (y2 - y1) * x0 - (x2 - x1) * y0 + x2 * y1 - y2 * x1, + ) + const denominator = Math.sqrt(Math.pow(y2 - y1, 2) + Math.pow(x2 - x1, 2)) + return numerator / denominator +} diff --git a/startscript.js b/startscript.js new file mode 100644 index 00000000..37f52696 --- /dev/null +++ b/startscript.js @@ -0,0 +1,2 @@ +var exec = require('child_process').exec +exec('yarn start', { windowsHide: true }) diff --git a/yarn.lock b/yarn.lock index f1b9d24f..bd27ddfe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14,6 +14,13 @@ dependencies: regenerator-runtime "^0.14.0" +"@babel/runtime@^7.24.7": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.8.tgz#5d958c3827b13cc6d05e038c07fb2e5e3420d82e" + integrity sha512-5F7SDGs1T72ZczbRwbGO9lQi0NLjQxzl6i4lJxLxfW9U5UluCSyEJeniWvnhl3/euNiqQVbo8zruhsDfid0esA== + dependencies: + regenerator-runtime "^0.14.0" + "@formatjs/ecma402-abstract@2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-2.0.0.tgz#39197ab90b1c78b7342b129a56a7acdb8f512e17" @@ -2262,6 +2269,11 @@ commander@^4.0.0: resolved "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz" integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== +complex.js@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/complex.js/-/complex.js-2.1.1.tgz#0675dac8e464ec431fb2ab7d30f41d889fb25c31" + integrity sha512-8njCHOTtFFLtegk6zQo0kkVX1rngygb/KQI6z1qZxlFI3scluC+LVTCFbrkWjBv4vvLlbQ9t88IPMC6k95VTTg== + compute-scroll-into-view@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/compute-scroll-into-view/-/compute-scroll-into-view-3.1.0.tgz#753f11d972596558d8fe7c6bcbc8497690ab4c87" @@ -2324,7 +2336,7 @@ debug@4: dependencies: ms "2.1.2" -decimal.js@^10.3.1: +decimal.js@^10.3.1, decimal.js@^10.4.3: version "10.4.3" resolved "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz" integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== @@ -2393,6 +2405,11 @@ emoji-regex@^9.2.2: resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz" integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== +escape-latex@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/escape-latex/-/escape-latex-1.2.0.tgz#07c03818cf7dac250cce517f4fda1b001ef2bca1" + integrity sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw== + escodegen@^2.0.0: version "2.1.0" resolved "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz" @@ -2474,6 +2491,11 @@ form-data@^4.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" +fraction.js@^4.3.7: + version "4.3.7" + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7" + integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew== + framer-motion@^11.2.13: version "11.2.13" resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-11.2.13.tgz#ab23fbc386233b1a1548757d840190054e5e1f1d" @@ -2703,6 +2725,11 @@ jackspeak@^3.1.2: optionalDependencies: "@pkgjs/parseargs" "^0.11.0" +javascript-natural-sort@^0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz#f9e2303d4507f6d74355a73664d1440fb5a0ef59" + integrity sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw== + jiti@^1.21.0: version "1.21.6" resolved "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz" @@ -2810,6 +2837,21 @@ make-dir@^3.1.0: dependencies: semver "^6.0.0" +mathjs@^13.0.2: + version "13.0.2" + resolved "https://registry.yarnpkg.com/mathjs/-/mathjs-13.0.2.tgz#eb87e31a21d9ffc57e26ce98ddb14a4a07b13d91" + integrity sha512-8vK/+InU4FTphRTWsrnvRsgSjbyNupRQYDDIXLuEGDZtJsGdbA9dVV4HZ0amBQb+RXplRjVJNGZZfB0WoHWFWA== + dependencies: + "@babel/runtime" "^7.24.7" + complex.js "^2.1.1" + decimal.js "^10.4.3" + escape-latex "^1.2.0" + fraction.js "^4.3.7" + javascript-natural-sort "^0.7.1" + seedrandom "^3.0.5" + tiny-emitter "^2.1.0" + typed-function "^4.2.1" + merge2@^1.3.0: version "1.4.1" resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz" @@ -3263,6 +3305,11 @@ scroll-into-view-if-needed@3.0.10: dependencies: compute-scroll-into-view "^3.0.2" +seedrandom@^3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-3.0.5.tgz#54edc85c95222525b0c7a6f6b3543d8e0b3aa0a7" + integrity sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg== + semver@^6.0.0: version "6.3.1" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" @@ -3370,7 +3417,14 @@ string_decoder@^1.1.1: dependencies: safe-buffer "~5.2.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -3480,6 +3534,11 @@ thenify-all@^1.0.0: dependencies: any-promise "^1.0.0" +tiny-emitter@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423" + integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q== + to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz" @@ -3519,6 +3578,11 @@ tslib@^2.0.0, tslib@^2.1.0, tslib@^2.4.0: resolved "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz" integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ== +typed-function@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/typed-function/-/typed-function-4.2.1.tgz#19aa51847aa2dea9ef5e7fb7641c060179a74426" + integrity sha512-EGjWssW7Tsk4DGfE+5yluuljS1OGYWiI1J6e8puZz9nTMM51Oug8CD5Zo4gWMsOhq5BI+1bF+rWTm4Vbj3ivRA== + universalify@^0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz"