diff --git a/next.config.mjs b/next.config.mjs index 107c5460..8cc4c040 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,6 +1,6 @@ /** @type {import('next').NextConfig} */ const nextConfig = { - // reactStrictMode: false, + reactStrictMode: false, webpack: (config) => { config.externals.push({ "utf-8-validate": "commonjs utf-8-validate", diff --git a/package.json b/package.json index 9c745fdf..a5c429a7 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ }, "devDependencies": { "postcss": "^8", + "prettier": "^3.3.3", "prisma": "^5.17.0", "tailwindcss": "^3.4.1" } diff --git a/src/app/404.js b/src/app/404.js new file mode 100644 index 00000000..8aa5e9bc --- /dev/null +++ b/src/app/404.js @@ -0,0 +1,23 @@ +import Link from 'next/link' + +export default function NotFound() { + return ( +
+
+
+

404

+

Something's missing.

+

+ Sorry, we can't find that page. You'll find lots to explore on the home page.{' '} +

+ + Back to Homepage + +
+
+
+ ) +} diff --git a/src/app/500.js b/src/app/500.js new file mode 100644 index 00000000..654b605a --- /dev/null +++ b/src/app/500.js @@ -0,0 +1,13 @@ +export default function ServerError() { + return ( +
+
+
+

500

+

Internal Server Error.

+

We are already working to solve the problem.

+
+
+
+ ) +} diff --git a/src/components/Roof2.jsx b/src/components/Roof2.jsx index 004f03db..e384901e 100644 --- a/src/components/Roof2.jsx +++ b/src/components/Roof2.jsx @@ -3,14 +3,14 @@ import { useEffect, useState } from 'react' import { Mode, useMode } from '@/hooks/useMode' import { Button } from '@nextui-org/react' import QRect from '@/components/fabric/QRect' -import QPolygon from '@/components/fabric/QPolygon' import RangeSlider from './ui/RangeSlider' import { useRecoilState, useRecoilValue } from 'recoil' -import { canvasAtom, canvasListState, canvasSizeState, fontSizeState, sortedPolygonArray } from '@/store/canvasAtom' +import { canvasSizeState, fontSizeState, roofState, sortedPolygonArray } from '@/store/canvasAtom' import { QLine } from '@/components/fabric/QLine' -import { getTests, getCanvasState, insertCanvasState } from '@/lib/canvas' -import { calculateIntersection2 } from '@/util/canvas-util' +import { getCanvasState, insertCanvasState } from '@/lib/canvas' +import { calculateIntersection } from '@/util/canvas-util' +import { QPolygon } from '@/components/fabric/QPolygon' export default function Roof2() { const { canvas, handleRedo, handleUndo, setCanvasBackgroundWithDots, saveImage, addCanvas } = useCanvas('canvas') @@ -31,6 +31,8 @@ export default function Roof2() { const [showControl, setShowControl] = useState(false) + const roof = useRecoilValue(roofState) + const { mode, changeMode, @@ -99,12 +101,10 @@ export default function Roof2() { selectable: true, fontSize: fontSize, }, - canvas, ) - canvas?.add(polygon) - polygon.fillCell({ width: 50, height: 30, padding: 10 }) + // polygon.fillCell({ width: 50, height: 30, padding: 10 }) } } @@ -186,14 +186,14 @@ export default function Roof2() { ] const eightPoint = [ - { x: 240, y: 130 }, - { x: 240, y: 630 }, - { x: 640, y: 630 }, - { x: 640, y: 480 }, - { x: 440, y: 480 }, - { x: 440, y: 280 }, - { x: 740, y: 280 }, - { x: 740, y: 130 }, + { x: 240.1111, y: 130.1111 }, + { x: 240.1111, y: 630.1111 }, + { x: 640.1111, y: 630.1111 }, + { x: 640.1111, y: 480.1111 }, + { x: 440.1111, y: 480.1111 }, + { x: 440.1111, y: 280.1111 }, + { x: 740.1111, y: 280.1111 }, + { x: 740.1111, y: 130.1111 }, ] const eightPoint2 = [ @@ -207,25 +207,75 @@ export default function Roof2() { { x: 897, y: 215 }, ] - if (canvas) { - const polygon = new QPolygon(eightPoint, { - fill: 'transparent', - stroke: 'black', - strokeWidth: 1, - selectable: true, - fontSize: fontSize, - name: 'QPolygon1', - }) + const eightPoint3 = [ + { x: 190, y: 147 }, + { x: 190, y: 747 }, + { x: 490, y: 747 }, + { x: 490, y: 497 }, + { x: 640, y: 497 }, + { x: 640, y: 747 }, + { x: 1090, y: 747 }, + { x: 1090, y: 147 }, + ] - canvas?.add(polygon) + const eightPoint4 = [ + { x: 228, y: 92 }, + { x: 228, y: 592 }, + { x: 478, y: 592 }, + { x: 478, y: 342 }, + { x: 728, y: 342 }, + { x: 728, y: 592 }, + { x: 1078, y: 592 }, + { x: 1078, y: 92 }, + ] - console.log(polygon) + const twelvePoint = [ + { x: 195, y: 166 }, + { x: 195, y: 466 }, + { x: 395, y: 466 }, + { x: 395, y: 766 }, + { x: 545, y: 766 }, + { x: 545, y: 466 }, + { x: 695, y: 466 }, + { x: 695, y: 666 }, + { x: 845, y: 666 }, + { x: 845, y: 466 }, + { x: 995, y: 466 }, + { x: 995, y: 166 }, + ] - handleOuterlinesTest2(polygon) + const complicatedType = [ + { x: 100, y: 100 }, + { x: 100, y: 1100 }, + { x: 400, y: 1100 }, + { x: 400, y: 800 }, + { x: 700, y: 800 }, + { x: 700, y: 1100 }, + { x: 1000, y: 1100 }, + { x: 1000, y: 600 }, + { x: 700, y: 600 }, + { x: 700, y: 300 }, + { x: 1000, y: 300 }, + { x: 1000, y: 100 }, + ] - // const lines = togglePolygonLine(polygon) - // togglePolygonLine(lines[0]) - } + const types = [type1, type2, type3, type4, type1A, type1B, eightPoint, eightPoint2, eightPoint3, eightPoint4, twelvePoint] + + const polygon = new QPolygon(type1B, { + fill: 'transparent', + stroke: 'black', + strokeWidth: 1, + selectable: true, + fontSize: fontSize, + name: 'QPolygon1', + }) + + canvas?.add(polygon) + + handleOuterlinesTest2(polygon) + + // const lines = togglePolygonLine(polygon) + // togglePolygonLine(lines[0]) } const rotateShape = () => { @@ -241,36 +291,26 @@ export default function Roof2() { const makeQLine = () => { if (canvas) { - const line = new QLine( - [50, 250, 900, 250], - { - stroke: 'black', - strokeWidth: 5, - fontSize: fontSize, - selectable: true, - }, - 50, - ) + const line = new QLine([50, 250, 900, 250], { + stroke: 'black', + strokeWidth: 5, + fontSize: fontSize, + selectable: true, + }) - const line2 = new QLine( - [450, 450, 821, 78], - { - stroke: 'black', - strokeWidth: 5, - fontSize: fontSize, - selectable: true, - }, - 50, - ) + const line2 = new QLine([450, 450, 821, 78], { + stroke: 'black', + strokeWidth: 5, + fontSize: fontSize, + selectable: true, + }) canvas?.add(line) canvas?.add(line2) - const interSectionPoint = calculateIntersection2(line, line2) + const interSectionPoint = calculateIntersection(line, line2) if (interSectionPoint) { - console.log(interSectionPoint) - const circle = new fabric.Circle({ radius: 5, fill: 'red', @@ -376,6 +416,13 @@ export default function Roof2() { + diff --git a/src/components/fabric/QLine.js b/src/components/fabric/QLine.js index 98948ac4..2d1e87df 100644 --- a/src/components/fabric/QLine.js +++ b/src/components/fabric/QLine.js @@ -1,112 +1,87 @@ import { fabric } from 'fabric' -import { getDirection, getDirectionByPoint } from '@/util/canvas-util' +import { v4 as uuidv4 } from 'uuid' +import { getDirection } from '@/util/canvas-util' -export class QLine extends fabric.Group { - line - text - fontSize - length = 0 - x1 - y1 - x2 - y2 - direction - idx - type = 'QLine' - parent - isAlreadyInterSection = false - - interSectionPoints = [] - lengthTxt = 0 - - initPoints - initOption - initLengthTxt - - constructor(points, option = { isActiveLengthText: true }, lengthTxt) { +export const QLine = fabric.util.createClass(fabric.Line, { + type: 'QLine', + text: null, + id: null, + line: null, + length: 0, + direction: null, + idx: 0, + initialize: function (points, options, canvas) { + this.callSuper('initialize', points, options) + if (options.id) { + this.id = options.id + } else { + this.id = uuidv4() + } + this.line = this // 소수점 전부 제거 points.forEach((point) => { point = Math.round(point) }) - 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.initPoints = points - this.initOption = option - this.initLengthTxt = lengthTxt - - this.x1 = x1 - this.y1 = y1 - this.x2 = x2 - this.y2 = y2 - this.line = line - this.fontSize = option.fontSize - this.direction = option.direction ?? getDirectionByPoint({ x: x1, y: y1 }, { x: x2, y: y2 }) - this.parent = option.parent - this.idx = option.idx - - if (lengthTxt > 0) { - this.lengthTxt = Number(lengthTxt) - } - - option.isActiveLengthText ?? 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', () => { - console.log(this) - 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 + this.idx = options.idx ?? 0 + const dx = x2 - x1 + const dy = y2 - y1 + this.length = Number(Math.sqrt(dx * dx + dy * dy).toFixed(0)) + + this.direction = options.direction ?? getDirection({ x: this.x1, y: this.y1 }, { x: this.x2, y: this.y2 }) + + if (canvas) { + this.canvas = canvas + } + }, + + toObject: function (propertiesToInclude) { + return fabric.util.object.extend(this.callSuper('toObject', propertiesToInclude), { + type: this.type, + text: this.text, + }) + }, + init: function () { + this.addLengthText() + + this.on('moving', () => { + this.addLengthText() + }) + + this.on('modified', (e) => { + this.addLengthText() + }) + + this.on('removed', () => { + const thisText = this.canvas.getObjects().find((obj) => obj.name === 'lengthText' && obj.parentId === this.id) + if (thisText) { + this.canvas.remove(thisText) + } + this.text = null + }) + }, + + addLengthText() { + const thisText = this.canvas.getObjects().find((obj) => obj.name === 'lengthText' && obj.parentId === this.id) + + 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 + + if (thisText) { + thisText.set({ text: this.length.toFixed(0).toString(), left: (x1 + x2) / 2, top: (y1 + y2) / 2 }) + return + } + const dx = x2 - x1 const dy = y2 - y1 this.length = Number(Math.sqrt(dx * dx + dy * dy).toFixed(0)) @@ -115,51 +90,27 @@ export class QLine extends fabric.Group { left: (x1 + x2) / 2, top: (y1 + y2) / 2, fontSize: this.fontSize, + selectable: false, + parentId: this.id, + name: 'lengthText', }) - this.text = text - this.addWithUpdate(text) - } + this.text = text + this.canvas.add(text) + }, setFontSize(fontSize) { this.fontSize = fontSize this.text.set({ fontSize }) - this.addWithUpdate() - } - - fromObject(object, callback) { - console.log('fromObject', object, callback) - } - - async = true - - toObject(propertiesToInclude) { - return fabric.util.object.extend(this.callSuper('toObject'), { - length: this.length, - line: this.line, - text: this.text, - fontSize: this.fontSize, - x1: this.x1, - y1: this.y1, - x2: this.x2, - y2: this.y2, - direction: this.direction, - idx: this.idx, - type: this.type, - parent: this.parent, - isAlreadyInterSection: this.isAlreadyInterSection, - interSectionPoints: this.interSectionPoints, - lengthTxt: this.lengthTxt, - setFontSize: this.setFontSize, - addLengthText: this.addLengthText, - init: this.init, - addControl: this.addControl, - initPoints: this.initPoints, - initOption: this.initOption, - initLengthTxt: this.initLengthTxt, - }) - } - - _set(key, value) { + }, + _render: function (ctx) { + this.callSuper('_render', ctx) + this.init() + }, + _set: function (key, value) { this.callSuper('_set', key, value) - } -} + }, + + setCanvas(canvas) { + this.canvas = canvas + }, +}) diff --git a/src/components/fabric/QPolygon.js b/src/components/fabric/QPolygon.js index f903289e..c178714a 100644 --- a/src/components/fabric/QPolygon.js +++ b/src/components/fabric/QPolygon.js @@ -1,344 +1,46 @@ import { fabric } from 'fabric' -import { - calculateIntersection, - calculateIntersection2, - distanceBetweenPoints, - findClosestLineToPoint, - findTopTwoIndexesByDistance, - getDegreeByChon, - getDirectionByPoint, - getRoofHeight, - getRoofHypotenuse, - sortedPoints, -} from '@/util/canvas-util' +import { v4 as uuidv4 } from 'uuid' import { QLine } from '@/components/fabric/QLine' -import { drawHelpLineInHexagon2 } from '@/util/qpolygon-utils' - -export default class QPolygon extends fabric.Group { - type = 'QPolygon' - polygon - points - texts = [] - lines = [] - canvas - fontSize - qCells = [] - name - shape = 0 // 점 6개일때의 shape 모양 - helpPoints = [] - helpLines = [] - - wall - - initPoints - initOption - - constructor(points, options, canvas) { - /*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.') - } +import { distanceBetweenPoints, findTopTwoIndexesByDistance, getDirectionByPoint, sortedPointLessEightPoint, sortedPoints } from '@/util/canvas-util' +import { drawHelpLineInHexagon } from '@/util/qpolygon-utils' +export const QPolygon = fabric.util.createClass(fabric.Polygon, { + type: 'QPolygon', + lines: [], + texts: [], + id: null, + length: 0, + hips: [], + ridges: [], + connectRidges: [], + initialize: function (points, options, canvas) { // 소수점 전부 제거 points.forEach((point) => { point.x = Math.round(point.x) point.y = Math.round(point.y) }) + if (points.length <= 8) { + points = sortedPointLessEightPoint(points) + } else { + points = sortedPoints(points) + } - const sortPoints = sortedPoints(points) - const polygon = new fabric.Polygon(sortPoints, options) + this.callSuper('initialize', points, options) + if (options.id) { + this.id = options.id + } else { + this.id = uuidv4() + } - super([polygon], { selectable: false }) - - this.fontSize = options.fontSize - this.points = sortPoints - this.polygon = polygon - this.name = options.name - - this.initPoints = points - this.initOption = options + if (canvas) { + this.canvas = canvas + } this.init() - this.addEvent() this.initLines() this.setShape() - } + }, - initLines() { - this.points.forEach((point, i) => { - const nextPoint = this.points[(i + 1) % this.points.length] - const line = new QLine([point.x, point.y, nextPoint.x, nextPoint.y], { - stroke: this.stroke, - strokeWidth: this.strokeWidth, - fontSize: this.fontSize, - direction: getDirectionByPoint(point, nextPoint), - }) - - this.lines.push(line) - }) - } - - init() { - this.addLengthText() - } - - addEvent() { - this.on('scaling', (e) => { - this.updateLengthText() - }) - - this.on('selected', function () { - // 모든 컨트롤 떼기 - - Object.keys(this.controls).forEach((controlKey) => { - if (controlKey !== 'mtr') { - this.setControlVisible(controlKey, false) - } - }) - }) - } - - setWall(wall) { - this.wall = wall - } - - setFontSize(fontSize) { - this.fontSize = fontSize - this.texts.forEach((text) => { - text.set({ fontSize }) - }) - - this.getObjects().forEach((obj) => { - if (obj.type[0] === 'Q') { - obj.setFontSize(fontSize) - } - }) - - this.canvas.add() - } - - addLengthText() { - if (this.texts.length > 0) { - this.texts.forEach((text) => { - this.canvas.remove(text) - }) - this.texts = [] - } - const points = this.points - - points.forEach((start, i) => { - const end = points[(i + 1) % points.length] - const dx = end.x - start.x - const dy = end.y - start.y - const length = Math.sqrt(dx * dx + dy * dy) - - 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), { - left: midPoint.x, - top: midPoint.y, - fontSize: this.fontSize, - selectable: false, - }) - - this.texts.push(text) - this.canvas.add(text) - }) - - this.canvas.renderAll() - } - - updateLengthText() { - const points = this.getCurrentPoints() - - points.forEach((start, i) => { - const end = points[(i + 1) % points.length] - const dx = end.x - start.x - const dy = end.y - start.y - const length = Math.sqrt(dx * dx + dy * dy) - - // Update the text object with the new length - this.texts[i].set({ text: length.toFixed(0) }) - }) - - this.canvas.renderAll() - } - - fillCell(cell = { width: 50, height: 100, padding: 10 }) { - const points = this.getCurrentPoints() - let bounds - - try { - bounds = fabric.util.makeBoundingBoxFromPoints(points) - } catch (error) { - alert('다각형의 꼭지점이 4개 이상이어야 합니다.') - 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) { - const rect = new fabric.Rect({ - left: x, - top: y, - width: cell.width, - height: cell.height, - fill: 'transparent', - stroke: 'black', - selectable: false, - }) - - const rectPoints = [ - new fabric.Point(rect.left, rect.top), - new fabric.Point(rect.left + rect.width, rect.top), - new fabric.Point(rect.left, rect.top + rect.height), - new fabric.Point(rect.left + rect.width, rect.top + rect.height), - ] - - const isInside = rectPoints.every((rectPoint) => this.inPolygon(rectPoint) && this.distanceFromEdge(rectPoint) >= cell.padding) - - if (isInside) { - this.canvas.add(rect) - } - } - } - - this.canvas.renderAll() - } - - /** - * this.lines의 direction이 top 인 line의 모든 합이 bottom 인 line의 모든 합과 같은지 확인 - * this.lines의 direction이 left 인 line의 모든 합이 right 인 line의 모든 합과 같은지 확인 - * 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 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 - } - - inPolygon(point) { - const vertices = this.getCurrentPoints() - let intersects = 0 - - for (let i = 0; i < vertices.length; i++) { - let vertex1 = vertices[i] - let vertex2 = vertices[(i + 1) % vertices.length] - - if (vertex1.y > vertex2.y) { - let tmp = vertex1 - vertex1 = vertex2 - vertex2 = tmp - } - - if (point.y === vertex1.y || point.y === vertex2.y) { - point.y += 0.01 - } - - if (point.y <= vertex1.y || point.y > vertex2.y) { - continue - } - - let xInt = ((point.y - vertex1.y) * (vertex2.x - vertex1.x)) / (vertex2.y - vertex1.y) + vertex1.x - if (xInt < point.x) { - intersects++ - } - } - - return intersects % 2 === 1 - } - - distanceFromEdge(point) { - const vertices = this.getCurrentPoints() - let minDistance = Infinity - - for (let i = 0; i < vertices.length; i++) { - let vertex1 = vertices[i] - let vertex2 = vertices[(i + 1) % vertices.length] - - 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) - - let closestPoint - if (t < 0) { - closestPoint = vertex1 - } else if (t > 1) { - closestPoint = vertex2 - } else { - closestPoint = new fabric.Point(vertex1.x + t * dx, vertex1.y + t * dy) - } - - const distance = distanceBetweenPoints(point, closestPoint) - if (distance < minDistance) { - minDistance = distance - } - } - - return minDistance - } - - setViewLengthText(boolean) { - this.texts.forEach((text) => { - text.visible = boolean - }) - - this.canvas.renderAll() - } - - getCurrentPoints() { - const scaleX = this.scaleX - const scaleY = this.scaleY - - const left = this.left - const top = this.top - - // 시작점 - const point = this.points[0] - - const movingX = left - point.x * scaleX - const movingY = top - point.y * scaleY - - return this.points.map((point) => { - return { - x: point.x * scaleX + movingX, - y: point.y * scaleY + movingY, - } - }) - } - - fillBackground(pattern) { - this.polygon.set({ fill: pattern }) - this.canvas.requestRenderAll() - } - - // 보조선 그리기 - drawHelpLine(chon = 4) { - if (!this.isValid()) { - return - } - - if (this.lines.length === 4) { - this.drawHelpLineInRect(chon) - } else if (this.lines.length === 6 || this.lines.length === 8) { - // TODO : 6각형 - drawHelpLineInHexagon2(this, chon) - } /* else if (this.lines.length === 8) { - // TODO : 8각형 - this.drawHelpLineInOctagon(chon) - }*/ - } - - /** - * 현재 점 6개만 가능 - */ setShape() { let shape = 0 if (this.lines.length !== 6) { @@ -369,566 +71,250 @@ export default class QPolygon extends fabric.Group { } this.shape = shape - } + }, - /** - * 현재 점 6개만 가능 - * @returns {number} - */ - getShape() { - return this.shape - } + toObject: function (propertiesToInclude) { + return fabric.util.object.extend(this.callSuper('toObject', propertiesToInclude), { + type: this.type, + text: this.text, + }) + }, + init: function () { + this.addLengthText() - drawHelpLineInRect(chon) { - let type = 1 - let smallestLength = Infinity - let maxLength = 0 - - this.lines.forEach((line) => { - if (line.length < smallestLength) { - smallestLength = line.length - } - if (line.length > maxLength) { - maxLength = line.length - } + this.on('moving', () => { + this.addLengthText() }) - // QPolygon 객체의 모든 선들을 가져옵니다. - const lines = [...this.lines] - - // 이 선들을 길이에 따라 정렬합니다. - lines.sort((a, b) => a.length - b.length) - - // 정렬된 배열에서 가장 작은 두 선을 선택합니다. - let smallestLines - - if (smallestLength === maxLength) { - // 정사각형인 경우 0, 2번째 라인이 가장 짧은 라인 - - smallestLines = [lines[0], lines[2]] - } else { - smallestLines = lines.slice(0, 2) - } - - let needPlusLine - let needMinusLine - - 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] - - 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] - - type = 2 // 세로가 긴 사각형 - } - - let point1 - let point2 - - if (type === 1) { - point1 = { - x: needPlusLine.x1 + 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, - } - } else if (type === 2) { - point1 = { - 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, - y: needMinusLine.y1 - smallestLength / 2, - } - } - - // 빗변1 - const realLine1 = new QLine( - [needPlusLine.x1, needPlusLine.y1, point1.x, point1.y], - { fontSize: this.fontSize, stroke: 'black', strokeWidth: 1 }, - getRoofHypotenuse(smallestLength / 2), - ) - - // 빗변2 - const realLine2 = new QLine( - [needPlusLine.x2, needPlusLine.y2, point1.x, point1.y], - { fontSize: this.fontSize, stroke: 'black', strokeWidth: 1 }, - getRoofHypotenuse(smallestLength / 2), - ) - - // 빗변3 - const realLine3 = new QLine( - [needMinusLine.x1, needMinusLine.y1, point2.x, point2.y], - { fontSize: this.fontSize, stroke: 'black', strokeWidth: 1 }, - getRoofHypotenuse(smallestLength / 2), - ) - - // 빗변4 - const realLine4 = new QLine( - [needMinusLine.x2, needMinusLine.y2, point2.x, point2.y], - { fontSize: this.fontSize, stroke: 'black', strokeWidth: 1 }, - getRoofHypotenuse(smallestLength / 2), - ) - - let centerPoint1 - let centerPoint2 - - if (type === 1) { - centerPoint1 = { x: point1.x - smallestLength / 2, y: point1.y } - centerPoint2 = { x: point2.x + smallestLength / 2, y: point2.y } - } else if (type === 2) { - centerPoint1 = { x: point1.x, y: point1.y - smallestLength / 2 } - centerPoint2 = { x: point2.x, y: point2.y + smallestLength / 2 } - } - - // 옆으로 누워있는 지붕의 높이 - const realLine5 = new QLine( - [point1.x, point1.y, centerPoint1.x, centerPoint1.y], - { - fontSize: this.fontSize, - stroke: 'black', - strokeWidth: 1, - strokeDashArray: [5, 5], - }, - getRoofHeight(smallestLength / 2, getDegreeByChon(chon)), - ) - - // 옆으로 누워있는 지붕의 높이 - const realLine6 = new QLine( - [point2.x, point2.y, centerPoint2.x, centerPoint2.y], - { - fontSize: this.fontSize, - stroke: 'black', - strokeWidth: 1, - strokeDashArray: [5, 5], - }, - getRoofHeight(smallestLength / 2, getDegreeByChon(chon)), - ) - - // 용마루 - const ridge = new QLine([point1.x, point1.y, point2.x, point2.y], { - fontSize: this.fontSize, - stroke: 'black', - strokeWidth: 1, + this.on('modified', (e) => { + this.addLengthText() }) - this.canvas.add(realLine1) - this.canvas.add(realLine2) - this.canvas.add(realLine3) - this.canvas.add(realLine4) - this.canvas.add(realLine5) - this.canvas.add(realLine6) - if (smallestLength !== maxLength) { - // 정사각형이 아닌경우에만 용마루를 추가한다. - this.canvas.add(ridge) - } - } - drawHelpLineInHexagon2(chon) { - const oneSideLines = [...this.lines].map((line) => { - let newX1, newY1, newX2, newY2 - if (line.direction === 'top') { - newX1 = line.x2 - newY1 = line.y2 - newX2 = line.x1 - newY2 = line.y1 - - line.x1 = newX1 - line.y1 = newY1 - line.x2 = newX2 - line.y2 = newY2 - line.direction = 'bottom' - } else if (line.direction === 'left') { - newX1 = line.x2 - newY1 = line.y2 - newX2 = line.x1 - newY2 = line.y1 - - line.x1 = newX1 - line.y1 = newY1 - line.x2 = newX2 - line.y2 = newY2 - line.direction = 'right' - } - return line - }) - - const centerLines = [] - const helpLines = [] - const ridgeStartPoints = [] - - const horizontalLines = oneSideLines.filter((line) => line.direction === 'right') - const verticalLines = oneSideLines.filter((line) => line.direction === 'bottom') - - const horizontalMaxLength = horizontalLines.reduce((max, obj) => Math.max(max, obj.length), 0) - const verticalMaxLength = verticalLines.reduce((max, obj) => Math.max(max, obj.length), 0) - // 모든 가로선의 중심선을 긋는다. - horizontalLines.forEach((line, index) => { - const nextLine = horizontalLines[(index + 1) % horizontalLines.length] - - const startCenterX = Math.max(line.x1, nextLine.x1) - const startCenterY = (line.y1 + nextLine.y1) / 2 - - let endCenterX = line.length >= nextLine.length ? startCenterX + line.length : startCenterX + nextLine.length - const endCenterY = startCenterY - - if (endCenterX > Math.max(line.x2, nextLine.x2)) { - endCenterX = Math.max(line.x2, nextLine.x2) - } - - const centerLine = new QLine([startCenterX, startCenterY, endCenterX, endCenterY], { - fontSize: this.fontSize, - stroke: 'red', - strokeWidth: 1, - direction: 'horizontal', + this.on('selected', () => { + Object.keys(this.controls).forEach((controlKey) => { + if (controlKey !== 'ml' && controlKey !== 'mr') { + this.setControlVisible(controlKey, false) + } }) - - // this.addWithUpdate(centerLine) - - centerLines.push(centerLine) }) - // 모든 세로선의 중심선을 긋는다. - verticalLines.forEach((line, index) => { - const nextLine = verticalLines[(index + 1) % verticalLines.length] - - const startCenterX = (line.x1 + nextLine.x1) / 2 - const startCenterY = Math.min(line.y1, nextLine.y1) - - const endCenterX = startCenterX - let endCenterY = line.length >= nextLine.length ? startCenterY + line.length : startCenterY + nextLine.length - - if (endCenterY > Math.max(line.y2, nextLine.y2)) { - endCenterY = Math.max(line.y2, nextLine.y2) - } - - const centerLine = new QLine([startCenterX, startCenterY, endCenterX, endCenterY], { - fontSize: this.fontSize, - stroke: 'blue', - strokeWidth: 1, - direction: 'vertical', + this.on('removed', () => { + const thisText = this.canvas.getObjects().filter((obj) => obj.name === 'lengthText' && obj.parentId === this.id) + thisText.forEach((text) => { + this.canvas.remove(text) }) - // this.addWithUpdate(centerLine) - - centerLines.push(centerLine) + this.texts = null }) + }, - const maxLength = horizontalMaxLength < verticalMaxLength ? horizontalMaxLength : verticalMaxLength + initLines() { + this.lines = [] - 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 - - let newX2, newY2 - - // x1, y1을 기준으로 x2, y2와의 거리를 유지한 새로운 직선 생성 - const angle = Math.atan2(y2 - y1, x2 - x1) - - newX2 = Math.floor(x1 + (maxLength / 2 + 50) * Math.cos(angle)) - newY2 = Math.floor(y1 + (maxLength / 2 + 50) * Math.sin(angle)) - - const line = new QLine([x1, y1, newX2, newY2], { + this.points.forEach((point, i) => { + const nextPoint = this.points[(i + 1) % this.points.length] + const line = new QLine([point.x, point.y, nextPoint.x, nextPoint.y], { + stroke: this.stroke, + strokeWidth: this.strokeWidth, fontSize: this.fontSize, - stroke: 'green', - idx: index, + direction: getDirectionByPoint(point, nextPoint), + idx: i, }) - this.addWithUpdate(line) - helpLines.push(line) - this.canvas.renderAll() + this.lines.push(line) }) + }, - helpLines.forEach((line, index) => { - if (line.isAlreadyInterSection) { - return - } - const nextLine = helpLines[(index + 1 + helpLines.length) % helpLines.length] - this.canvas.renderAll() + // 보조선 그리기 + drawHelpLine(chon = 4) { + drawHelpLineInHexagon(this, chon) + }, - let intersectionPoint = calculateIntersection(line, nextLine) - if (!intersectionPoint) { + addLengthText() { + let points = this.points + + points.forEach((start, i) => { + const thisText = this.canvas.getObjects().find((obj) => obj.name === 'lengthText' && obj.parentId === this.id && obj.idx === i) + + const end = points[(i + 1) % points.length] + const dx = end.x - start.x + const dy = end.y - start.y + const length = Math.sqrt(dx * dx + dy * dy) + + if (thisText) { + thisText.set({ + left: (start.x + points[(i + 1) % points.length].x) / 2, + top: (start.y + points[(i + 1) % points.length].y) / 2, + text: length.toFixed(0), + }) return } - line.set({ isAlreadyInterSection: true }) - nextLine.set({ isAlreadyInterSection: true }) + const midPoint = new fabric.Point((start.x + end.x) / 2, (start.y + end.y) / 2) - const helpLine1 = new QLine([nextLine.x1, nextLine.y1, intersectionPoint.x, intersectionPoint.y], { + // Create new text object if it doesn't exist + const text = new fabric.Text(length.toFixed(0), { + left: midPoint.x, + top: midPoint.y, fontSize: this.fontSize, - stroke: 'skyblue', + selectable: false, + parentId: this.id, + idx: i, + name: 'lengthText', }) - const helpLine2 = new QLine([line.x1, line.y1, intersectionPoint.x, intersectionPoint.y], { - fontSize: this.fontSize, - stroke: 'skyblue', - }) - - ridgeStartPoints.push(intersectionPoint) - this.addWithUpdate(helpLine1) - this.addWithUpdate(helpLine2) - this.removeWithUpdate(nextLine) - this.removeWithUpdate(line) + this.texts.push(text) + this.canvas.add(text) this.canvas.renderAll() }) - - // 안만나는 선들 - const notInterSectionLines = helpLines.filter((line) => !line.isAlreadyInterSection) - const ridgeEndPoints = [] - const interSectionPoints = [] - - notInterSectionLines.forEach((line, index) => { - line.line.set({ strokeWidth: (index + 1) * 5 }) - - centerLines.forEach((centerLine) => { - const interSectionPoint = calculateIntersection2(line, centerLine) - - if (!this.inPolygon(interSectionPoint) || !interSectionPoint) { - return - } - line.interSectionPoints.push(interSectionPoint) - interSectionPoints.push(interSectionPoint) - }) - }) - - ridgeStartPoints.forEach((point, index) => { - let arrivalPoint - let distance = Infinity - let startPoint - interSectionPoints.forEach((interSectionPoint) => { - if (Math.abs(point.x - interSectionPoint.x) < 3 || Math.abs(point.y - interSectionPoint.y) < 3) { - if (distanceBetweenPoints(point, interSectionPoint) < distance) { - startPoint = point - distance = distanceBetweenPoints(point, interSectionPoint) - arrivalPoint = interSectionPoint - } - } - }) - - if (arrivalPoint) { - const line = notInterSectionLines.filter((line) => line.interSectionPoints.includes(arrivalPoint))[0] - - const ridge = new QLine([startPoint.x, startPoint.y, arrivalPoint.x, arrivalPoint.y], { - stroke: 'black', - fontSize: this.fontSize, - }) - - const helpLine = new QLine([line.x1, line.y1, arrivalPoint.x, arrivalPoint.y], { - stroke: 'red', - fontSize: this.fontSize, - }) - - ridgeEndPoints.push(arrivalPoint) - this.addWithUpdate(ridge) - this.addWithUpdate(helpLine) - this.removeWithUpdate(line) - this.canvas.renderAll() - } - }) - - ridgeEndPoints.forEach((point, index) => { - const currentRidgeEndPoint = ridgeEndPoints[index] - const nextRidgeEndPoint = ridgeEndPoints[(index + 1) % ridgeEndPoints.length] - const ridgeConnectLine = new QLine([currentRidgeEndPoint.x, currentRidgeEndPoint.y, nextRidgeEndPoint.x, nextRidgeEndPoint.y], { - fontSize: this.fontSize, - stroke: 'green', - }) - - this.addWithUpdate(ridgeConnectLine) - this.canvas.renderAll() - }) - } - drawHelpLineInHexagon(chon) { - const historyLines = [] - const helpPoints = [] - const notInterSectionLines = [] - const ridge = [] - const maxLength = this.lines.reduce((max, obj) => Math.max(max, obj.length), 0) - 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 - const historyLines = [] - const helpPoints = [] - const notInterSectionLines = [] - const ridge = [] - const maxLength = this.lines.reduce((max, obj) => Math.max(max, obj.length), 0) - 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 - - let newX2, newY2 - - // x1, y1을 기준으로 x2, y2와의 거리를 유지한 새로운 직선 생성 - const angle = Math.atan2(y2 - y1, x2 - x1) - - newX2 = x1 + (maxLength / 2) * Math.cos(angle) - newY2 = y1 + (maxLength / 2) * Math.sin(angle) - - const line = new QLine([x1, y1, newX2, newY2], { - fontSize: this.fontSize, - stroke: 'blue', - idx: index, - }) - historyLines.push(line) - this.canvas.add(line) - - this.canvas.renderAll() - }) - - /** - * 삼각 지붕 - */ - historyLines.forEach((line, index) => { - const prevLine = historyLines[(index - 1 + historyLines.length) % historyLines.length] - - let intersectionPoint = calculateIntersection(line, prevLine) - - if (!intersectionPoint) { - notInterSectionLines.push(line) - return - } - - const helpLine1 = new QLine([prevLine.x1, prevLine.y1, intersectionPoint.x, intersectionPoint.y], { - fontSize: this.fontSize, - stroke: 'red', - }) - - const helpLine2 = new QLine([line.x1, line.y1, intersectionPoint.x, intersectionPoint.y], { - fontSize: this.fontSize, - stroke: 'red', - }) - notInterSectionLines.pop() - helpPoints.push(intersectionPoint) - - this.canvas.add(helpLine1) - this.canvas.add(helpLine2) - this.canvas.remove(prevLine) - this.canvas.remove(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, - } - - const direction = getDirectionByPoint(helpPoint, centerClosestLinePoint) - - let newX, newY - - 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 - } - } - - const ridgeHelpLine = new QLine([closestLine.x1, closestLine.y1, newX, newY], { - fontSize: this.fontSize, - stroke: 'purple', - strokeWidth: 5, - }) - - ridgePoint.push({ x: newX, y: newY }) - - const ridge = new QLine([helpPoint.x, helpPoint.y, newX, newY], { - fontSize: this.fontSize, - stroke: 'skyblue', - strokeWidth: 5, - }) - - this.canvas.add(ridge) - this.canvas.renderAll() - - this.canvas.add(ridgeHelpLine) - this.canvas.remove(closestLine) - this.canvas.renderAll() - }) - - // 용마루 끼리 연결 - 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.canvas.add(ridgeConnectLine) - this.canvas.renderAll() - } - - this.canvas.renderAll() - }) - } - - drawHelpLineInOctagon(chon) {} - - getObject() { - return this - } - sou - - toObject(propertiesToInclude) { - return fabric.util.object.extend(this.callSuper('toObject'), { - points: this.points, - fontSize: this.fontSize, - name: this.name, - shape: this.shape, - texts: this.texts, - lines: this.lines, - wall: this.wall, - initPoints: this.initPoints, - initOption: this.initOption, - objects: this.getObjects().map((obj) => obj.toObject(propertiesToInclude)), - }) - } - - _set(key, value) { + }, + setFontSize(fontSize) { + this.fontSize = fontSize + this.text.set({ fontSize }) + }, + _render: function (ctx) { + this.callSuper('_render', ctx) + }, + _set: function (key, value) { this.callSuper('_set', key, value) - } -} + }, + setCanvas(canvas) { + this.canvas = canvas + }, + fillCell(cell = { width: 50, height: 100, padding: 10 }) { + const points = this.points + const minX = Math.min(...points.map((p) => p.x)) + const maxX = Math.max(...points.map((p) => p.x)) + const minY = Math.min(...points.map((p) => p.y)) + const maxY = Math.max(...points.map((p) => p.y)) + + const boundingBoxWidth = maxX - minX + const boundingBoxHeight = maxY - minY + + const rectWidth = cell.width + const rectHeight = cell.height + + const cols = Math.floor((boundingBoxWidth + cell.padding) / (rectWidth + cell.padding)) + const rows = Math.floor((boundingBoxHeight + cell.padding) / (rectHeight + cell.padding)) + + for (let i = 0; i < cols; i++) { + for (let j = 0; j < rows; j++) { + const rectLeft = minX + i * (rectWidth + cell.padding) + const rectTop = minY + j * (rectHeight + cell.padding) + + const rectPoints = [ + { x: rectLeft, y: rectTop }, + { x: rectLeft + rectWidth, y: rectTop }, + { x: rectLeft, y: rectTop + rectHeight }, + { x: rectLeft + rectWidth, y: rectTop + rectHeight }, + ] + + const allPointsInside = rectPoints.every((point) => this.inPolygon(point)) + + if (allPointsInside) { + const rect = new fabric.Rect({ + left: rectLeft, + top: rectTop, + width: rectWidth, + height: rectHeight, + fill: '#BFFD9F', + selectable: false, + opacity: 0.6, + }) + + this.canvas.add(rect) + } + } + } + + this.canvas.renderAll() + }, + inPolygon(point) { + const vertices = this.getCurrentPoints() + let intersects = 0 + + for (let i = 0; i < vertices.length; i++) { + let vertex1 = vertices[i] + let vertex2 = vertices[(i + 1) % vertices.length] + + if (vertex1.y > vertex2.y) { + let tmp = vertex1 + vertex1 = vertex2 + vertex2 = tmp + } + + if (point.y === vertex1.y || point.y === vertex2.y) { + point.y += 0.01 + } + + if (point.y <= vertex1.y || point.y > vertex2.y) { + continue + } + + let xInt = ((point.y - vertex1.y) * (vertex2.x - vertex1.x)) / (vertex2.y - vertex1.y) + vertex1.x + if (xInt < point.x) { + intersects++ + } + } + + return intersects % 2 === 1 + }, + + distanceFromEdge(point) { + const vertices = this.getCurrentPoints() + let minDistance = Infinity + + for (let i = 0; i < vertices.length; i++) { + let vertex1 = vertices[i] + let vertex2 = vertices[(i + 1) % vertices.length] + + 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) + + let closestPoint + if (t < 0) { + closestPoint = vertex1 + } else if (t > 1) { + closestPoint = vertex2 + } else { + closestPoint = new fabric.Point(vertex1.x + t * dx, vertex1.y + t * dy) + } + + const distance = distanceBetweenPoints(point, closestPoint) + if (distance < minDistance) { + minDistance = distance + } + } + + return minDistance + }, + getCurrentPoints() { + const scaleX = this.scaleX + const scaleY = this.scaleY + + const left = this.left + const top = this.top + + // 시작점 + const point = this.points[0] + + const movingX = left - point.x * scaleX + const movingY = top - point.y * scaleY + + return this.points.map((point) => { + return { + x: point.x * scaleX + movingX, + y: point.y * scaleY + movingY, + } + }) + }, + setWall: function (wall) { + this.wall = wall + }, + setViewLengthText(isView) { + this.texts.forEach((text) => { + text.set({ visible: isView }) + }) + }, + divideLine() {}, +}) diff --git a/src/hooks/useCanvas.js b/src/hooks/useCanvas.js index 44ed17ce..dee2983b 100644 --- a/src/hooks/useCanvas.js +++ b/src/hooks/useCanvas.js @@ -4,9 +4,9 @@ import { actionHandler, anchorWrapper, polygonPositionHandler } from '@/util/can import { useRecoilState } from 'recoil' import { canvasSizeState, fontSizeState } from '@/store/canvasAtom' -import QPolygon from '@/components/fabric/QPolygon' import { QLine } from '@/components/fabric/QLine' import QRect from '@/components/fabric/QRect' +import { QPolygon } from '@/components/fabric/QPolygon' export function useCanvas(id) { const [canvas, setCanvas] = useState() @@ -110,28 +110,25 @@ export function useCanvas(id) { fabric.Object.prototype.cornerStyle = 'rect' fabric.Object.prototype.cornerStrokeColor = '#2BEBC8' fabric.Object.prototype.cornerSize = 6 - + fabric.QLine = QLine + fabric.QPolygon = QPolygon QPolygon.prototype.canvas = canvas QLine.prototype.canvas = canvas QRect.prototype.canvas = canvas - fabric.QLine = fabric.util.createClass(fabric.Group, {}) - fabric.QPolygon = fabric.util.createClass(fabric.Group, {}) - - // fromObject 메서드를 QLine 클래스에 직접 추가 fabric.QLine.fromObject = function (object, callback) { - const { initOption, initPoints, initLengthTxt } = object - fabric.util.enlivenObjects(object.objects, function (enlivenedObjects) { - return callback(new QLine(initPoints, initOption, initLengthTxt)) - }) + function _callback(instance) { + delete instance.points + callback && callback(instance) + } + const options = fabric.util.object.clone(object, true) + options.points = [object.x1, object.y1, object.x2, object.y2] + + fabric.Object._fromObject('QLine', options, _callback, 'points') } - // fromObject 메서드를 QLine 클래스에 직접 추가 fabric.QPolygon.fromObject = function (object, callback) { - const { initOption, initPoints, initLengthTxt } = object - fabric.util.enlivenObjects(object.objects, function (enlivenedObjects) { - return callback(new QPolygon(initPoints, initOption, initLengthTxt)) - }) + fabric.Object._fromObject('QPolygon', object, callback, 'points') } } @@ -556,44 +553,19 @@ export function useCanvas(id) { const addCanvas = () => { // const canvasState = canvas - const objs = canvas + const objs = canvas?.toJSON(['selectable', 'name', 'parentId', 'id', 'length', 'idx', 'direction', 'lines', 'points']) - console.log(objs) const str = JSON.stringify(objs) canvas?.clear() - console.log(str) - console.log(JSON.parse(str)) - - // 역직렬화하여 캔버스에 객체를 다시 추가합니다. setTimeout(() => { - canvas?.loadFromJSON( - JSON.parse(str), - function () { - // 모든 객체가 로드되고 캔버스에 추가된 후 호출됩니다. - canvas?.renderAll() // 캔버스를 다시 그립니다. - console.log('Objects are reloaded and rendered on canvas.') - }, - function (o, object) { - // 각 객체가 로드될 때마다 호출됩니다. - console.log('Object loaded: ', o, object) - - canvas?.add(object) - canvas?.renderAll() - }, - ) + // 역직렬화하여 캔버스에 객체를 다시 추가합니다. + canvas?.loadFromJSON(JSON.parse(str), function () { + // 모든 객체가 로드되고 캔버스에 추가된 후 호출됩니다. + canvas?.renderAll() // 캔버스를 다시 그립니다. + }) }, 1000) - - /*canvas?.loadFromJSON(JSON.parse(str), () => { - console.log('load done') - })*/ - - /*const stateArr = canvasState.map((state) => state.getObject().getObjects()) - - const newCanvasList = [...canvasList, JSON.stringify(stateArr)] - - setCanvasList(newCanvasList)*/ } const changeCanvas = (idx) => { diff --git a/src/hooks/useMode.js b/src/hooks/useMode.js index dab596c2..932ec2cd 100644 --- a/src/hooks/useMode.js +++ b/src/hooks/useMode.js @@ -1,11 +1,21 @@ import { useEffect, useRef, useState } from 'react' import QRect from '@/components/fabric/QRect' -import QPolygon from '@/components/fabric/QPolygon' import { findTopTwoIndexesByDistance, getCenterPoint, getDirection, getStartIndex, rearrangeArray } from '@/util/canvas-util' import { useRecoilState } from 'recoil' -import { fontSizeState, roofPolygonPatternArrayState, roofState, sortedPolygonArray, wallState } from '@/store/canvasAtom' +import { + canvasSizeState, + fontSizeState, + roofPolygonArrayState, + roofPolygonPatternArrayState, + roofState, + sortedPolygonArray, + templateTypeState, + wallState, +} from '@/store/canvasAtom' import { QLine } from '@/components/fabric/QLine' +import { fabric } from 'fabric' +import { QPolygon } from '@/components/fabric/QPolygon' export const Mode = { DRAW_LINE: 'drawLine', // 기준선 긋기모드 @@ -16,6 +26,8 @@ export const Mode = { TEXTBOX: 'textbox', DRAW_RECT: 'drawRect', ROOF_PATTERN: 'roofPattern', + MODULE: 'module', + ROOT_TRESTLE: 'rootTrestle', DEFAULT: 'default', } @@ -24,6 +36,7 @@ export function useMode() { 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) @@ -32,7 +45,15 @@ export function useMode() { 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) useEffect(() => { // 이벤트 리스너 추가 @@ -47,6 +68,147 @@ export function useMode() { } }, [canvas]) // 빈 배열을 전달하여 컴포넌트가 마운트될 때만 실행되도록 함 + 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]) + + const drawMouseLines = (e) => { + // 현재 마우스 포인터의 위치를 가져옵니다. + const pointer = canvas?.getPointer(e.e) + + // 기존에 그려진 가이드라인을 제거합니다. + removeMouseLines() + + if (canvas?.getActiveObject()) { + return + } + + // 가로선을 그립니다. + const horizontalLine = new fabric.Line([0, pointer.y, canvasSize.horizontal, pointer.y], { + stroke: 'black', + strokeWidth: 1, + selectable: false, + name: 'mouseLine', + }) + + // 세로선을 그립니다. + const verticalLine = new fabric.Line([pointer.x, 0, pointer.x, canvasSize.vertical], { + stroke: 'black', + 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 === 'guideLine') + 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 = 'guideLine' + 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 = 'guideLine' + guideLine2.name = 'guideLine' + + 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) + + // 마우스 포인터 위치랑 endPoint를 연결하는 line 생성 + const line = new fabric.Line([endPoint.left, endPoint.top, pointer.x, pointer.y], { + stroke: 'black', + strokeWidth: 1, + selectable: false, + }) + + line.set({ name: 'connectLine' }) + + canvas?.add(line) + canvas?.renderAll() + } + const addEvent = (mode) => { switch (mode) { case 'default': @@ -76,6 +238,9 @@ export function useMode() { case 'roofPattern': makeRoofPatternPolygon() break + case 'rootTrestle': + makeRoofTrestle() + break } } @@ -98,6 +263,7 @@ export function useMode() { originY: 'center', selectable: false, }) + historyPoints.current.push(circle) points.current.push(circle) canvas?.add(circle) @@ -149,6 +315,10 @@ export function useMode() { points.current.forEach((point) => { canvas?.remove(point) }) + + setEndPoint(endPointCircle) + pointCount.current = pointCount.current + 1 + points.current = [endPointCircle] canvas.renderAll() @@ -221,10 +391,14 @@ export function useMode() { } const changeMode = (canvas, mode) => { + setEndPoint(null) + pointCount.current = 0 + setMode(mode) // mode변경 시 이전 이벤트 제거 setCanvas(canvas) canvas?.off('mouse:down') + addEvent(mode) } @@ -232,15 +406,21 @@ export function useMode() { canvas?.on('mouse:down', function (options) { const pointer = canvas?.getPointer(options.e) const circle = new fabric.Circle({ - radius: 1, + radius: 5, fill: 'transparent', // 원 안을 비웁니다. - stroke: 'black', // 원 테두리 색상을 검은색으로 설정합니다. - left: pointer.x, - top: pointer.y, + stroke: 'red', // 원 테두리 색상을 검은색으로 설정합니다. + left: pointer.x - 5, + top: pointer.y - 5, originX: 'center', originY: 'center', selectable: false, }) + if (!startPoint.current) { + startPoint.current = circle + pointCount.current = pointCount.current + 1 + } + + setEndPoint(circle) historyPoints.current.push(circle) points.current.push(circle) @@ -248,6 +428,7 @@ export function useMode() { if (points.current.length === 2) { const length = Number(prompt('길이를 입력하세요:')) + // length 값이 숫자가 아닌 경우 if (isNaN(length) || length === 0) { //마지막 추가 된 points 제거합니다. @@ -283,40 +464,10 @@ 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], - { - stroke: 'black', - strokeWidth: 2, - selectable: false, - viewLengthText: true, - direction: getDirection(points.current[0], points.current[1]), - fontSize: fontSize, - }, - ) + const verticalLength = scaledVector.y + const horizontalLength = scaledVector.x - pushHistoryLine(line) - - // 라인의 끝에 점을 추가합니다. - const endPointCircle = new fabric.Circle({ - radius: 1, - fill: 'transparent', // 원 안을 비웁니다. - stroke: 'black', // 원 테두리 색상을 검은색으로 설정합니다. - left: points.current[0].left + scaledVector.x, - top: points.current[0].top + scaledVector.y, - originX: 'center', - originY: 'center', - selectable: false, - }) - - canvas?.add(endPointCircle) - - historyPoints.current.push(endPointCircle) - - points.current.forEach((point) => { - canvas?.remove(point) - }) - points.current = [endPointCircle] + drawCircleAndLine(verticalLength, horizontalLength) } } @@ -556,7 +707,7 @@ export function useMode() { if (!otherLines) { // polygon.fillCell() canvas.renderAll() - polygon.setViewLengthText(false) + // polygon.setViewLengthText(false) setMode(Mode.DEFAULT) } @@ -568,6 +719,8 @@ export function useMode() { */ const handleClear = () => { canvas?.clear() + startPoint.current = null + setEndPoint(null) points.current = [] historyPoints.current = [] historyLines.current = [] @@ -876,9 +1029,7 @@ export function useMode() { /** *벽 지붕 외곽선 생성 */ - const handleOuterlinesTest = (offsetInputX, offsetInputY = 0) => { - const polygon = drawWallPolygon() - + const handleOuterlinesTest = (polygon, offsetInputX, offsetInputY = 0) => { let offsetPoints = [] const originalMax = 71 const transformedMax = 100 @@ -946,7 +1097,7 @@ export function useMode() { offsetPoints.push(offsetPoint) } - makePolygon(offsetPoints) + return makePolygon(offsetPoints) } /** @@ -1069,6 +1220,7 @@ export function useMode() { } else if (polygon.lines.length === 8) { handleOuterLineTemplateA8Points(polygon) } + setTemplateType(2) } const handleOuterLineTemplateA4Points = (polygon) => { @@ -1190,15 +1342,15 @@ export function useMode() { const dashedCenterLineOpt = { stroke: 'black', - strokeWidth: 4, + strokeWidth: 1, property: 'centerLine', - strokeDashArray: [5, 5], + strokeDashArray: [10, 5], fontSize: 14, } const centerLineOpt = { stroke: 'blue', - strokeWidth: 5, + strokeWidth: 1, property: 'bigHoriCenter', fontSize: 14, } @@ -1210,7 +1362,7 @@ export function useMode() { const line = new QLine([start.x, start.y, end.x, end.y], { stroke: '#A0D468', - strokeWidth: 5, + strokeWidth: 2, property: 'normal', fontSize: 14, }) @@ -1290,6 +1442,11 @@ export function useMode() { let vertCenterLine let secondVertCenterLine let templatePolygonObj = {} + let roofPatternPolygonArray = [] + + let bigRoofPolygon = [] + let middleRoofPolygon = [] + let smallRoofPolygon = [] if (prevHighIndex === 1) { if (horizontalDirection === 'left') { @@ -1415,7 +1572,7 @@ export function useMode() { let drawLastInLine2 = new QLine([secondVertCenterLine.x1, vertCenterLine.y2, vertCenterLine.x2, vertCenterLine.y2], centerLineOpt) canvas.add(drawLastInLine2) - const bigRoofPolygon = [ + 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 }, @@ -1424,20 +1581,19 @@ export function useMode() { { x: outLines[1].x2, y: outLines[1].y2 }, ] - const middleRoofPolygon = [ + 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 }, ] - const smallRoofPolygon = [ + 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 }, ] - setRoofPolygonPattern({ bigRoofPolygon, middleRoofPolygon, smallRoofPolygon, lines }) } else { //아래쪽 길게 오른쪽 방향 //배열 순서대로 뒤에꺼를 찾아서 계산한다 @@ -1565,7 +1721,7 @@ export function useMode() { let drawLastInLine2 = new QLine([secondVertCenterLine.x2, vertCenterLine.y1, vertCenterLine.x1, vertCenterLine.y1], centerLineOpt) canvas.add(drawLastInLine2) - const bigRoofPolygon = [ + 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 }, @@ -1574,20 +1730,19 @@ export function useMode() { { x: outLines[1].x2, y: outLines[0].y1 }, ] - const middleRoofPolygon = [ + 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 }, ] - const smallRoofPolygon = [ + 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 }, ] - setRoofPolygonPattern({ bigRoofPolygon, middleRoofPolygon, smallRoofPolygon, lines }) } } else { if (horizontalDirection === 'left') { @@ -1709,7 +1864,7 @@ export function useMode() { let drawLastInLine2 = new QLine([vertCenterLine.x1, vertCenterLine.y2, secondVertCenterLine.x2, vertCenterLine.y2], centerLineOpt) canvas.add(drawLastInLine2) - const bigRoofPolygon = [ + 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 }, @@ -1718,21 +1873,19 @@ export function useMode() { { x: outLines[0].x1, y: outLines[0].y1 }, ] - const middleRoofPolygon = [ + 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 }, ] - const smallRoofPolygon = [ + 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 }, ] - - setRoofPolygonPattern({ bigRoofPolygon, middleRoofPolygon, smallRoofPolygon, lines }) } else { //윗쪽 길게 오른쪽 방향 //배열 순서대로 뒤에꺼를 찾아서 계산한다 @@ -1854,7 +2007,7 @@ export function useMode() { let drawLastInLine2 = new QLine([secondLine.x2 - eaves, secondLine.y1, drawLastLine1.x2, secondLine.y1], centerLineOpt) canvas.add(drawLastInLine2) - const bigRoofPolygon = [ + 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 }, @@ -1863,28 +2016,31 @@ export function useMode() { { x: outLines[4].x2, y: outLines[4].y2 }, ] - const middleRoofPolygon = [ + 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 }, ] - const smallRoofPolygon = [ + 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 }, ] - - setRoofPolygonPattern({ bigRoofPolygon, middleRoofPolygon, smallRoofPolygon, lines }) } } + roofPatternPolygonArray.push(bigRoofPolygon) //지붕폴리곤 + roofPatternPolygonArray.push(middleRoofPolygon) //중간라인 폴리곤 + roofPatternPolygonArray.push(smallRoofPolygon) //작은지붕폴리곤 + setRoofPolygonPattern({ roofPatternPolygonArray, lines }) //모든 행을 저장 + canvas.renderAll() } - const handleOuterLineTemplateA8Points = (polygon, offsetInputX = 50, offsetInputY = 20) => { + const handleOuterLineTemplateA8Points = (polygon, offsetInputX = 20, offsetInputY = 50) => { let offsetPoints = [] const originalMax = 71 @@ -1893,18 +2049,20 @@ export function useMode() { let lines = [] //내각라인 let outLines = [] //아웃라인 let halfLength = 0 //선길이 + let offsetX + let offsetY const dashedCenterLineOpt = { stroke: 'black', - strokeWidth: 4, + strokeWidth: 1, property: 'centerLine', - strokeDashArray: [5, 5], + strokeDashArray: [8, 4], fontSize: 14, } const centerLineOpt = { stroke: 'blue', - strokeWidth: 5, + strokeWidth: 2, property: 'bigHoriCenter', fontSize: 14, } @@ -1914,10 +2072,8 @@ export function useMode() { const start = polygon.points[i] const end = polygon.points[(i + 1) % polygon.points.length] // 다음 점, 마지막 점의 경우 첫 점으로 - const color = i % 2 === 0 ? '#A0D468' : 'skyblue' - const line = new QLine([start.x, start.y, end.x, end.y], { - stroke: color, + stroke: '#A0D468', strokeWidth: 2, property: 'normal', fontSize: 14, @@ -1932,12 +2088,6 @@ export function useMode() { 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) => ({ @@ -1945,6 +2095,33 @@ export function useMode() { 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 + } + // 외적을 계산하는 함수 function crossProduct(p1, p2, p3) { const dx1 = p2.x - p1.x @@ -1977,60 +2154,50 @@ export function useMode() { // 오목한 부분 인덱스 찾기 concaveIndices = findConcavePointIndices(points) //오목한 부분을 제외한 인덱스 const concavePoints = concaveIndices.map((index) => points[index]) + const concaveLine = { + index: concavePointIndices[0], + line: lines[concavePointIndices[0]], + } - 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] + 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) - var vector1 = { x: current.x - prev.x, y: current.y - prev.y } - var vector2 = { x: next.x - current.x, y: next.y - current.y } + let vector1 = { x: current.x - prev.x, y: current.y - prev.y } + let 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) + let length1 = Math.sqrt(vector1.x * vector1.x + vector1.y * vector1.y) + let 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 } + let unitVector1 = { x: vector1.x / length1, y: vector1.y / length1 } + let 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 } + let normal1 = { x: -unitVector1.y, y: unitVector1.x } + let normal2 = { x: -unitVector2.y, y: unitVector2.x } // 법선 벡터 평균 계산 - var averageNormal = { + let 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 = { + let lengthNormal = Math.sqrt(averageNormal.x * averageNormal.x + averageNormal.y * averageNormal.y) + let unitNormal = { x: averageNormal.x / lengthNormal, y: averageNormal.y / lengthNormal, } - let offsetX - let offsetY - - if (concavePointIndices[0] === i || concavePointIndices[1] === i) { - // 인덱스가 배열이랑 같으면 - if ((concavePointIndices[0] === 4 && concavePointIndices[1] === 5) || (concavePointIndices[0] === 2 && concavePointIndices[1] === 3)) { - offsetX = 1 - offsetY = (offsetInputY / transformedMax) * originalMax * 2 - } else { - offsetX = (offsetInputX / transformedMax) * originalMax * 2 - offsetY = 1 - } - } else { - offsetX = (offsetInputX / transformedMax) * originalMax * 2 - offsetY = (offsetInputY / transformedMax) * originalMax * 2 - } + offsetX = (offsetInputX / transformedMax) * originalMax * 2 + offsetY = (offsetInputY / transformedMax) * originalMax * 2 // 오프셋 적용 - var offsetPoint = { + let offsetPoint = { x1: current.x + unitNormal.x * offsetX, y1: current.y + unitNormal.y * offsetY, } @@ -2039,6 +2206,7 @@ export function useMode() { } const outlinePolygon = makePolygon(offsetPoints) + outlinePolygon.setViewLengthText(false) // 아웃라인 폴리곤의 각 변을 선으로 생성 for (let i = 0; i < outlinePolygon.points.length; i++) { @@ -2050,6 +2218,7 @@ export function useMode() { strokeWidth: 2, property: 'normal', fontSize: 14, + idx: i, }) // 선을 배열에 추가 @@ -2058,37 +2227,419 @@ export function useMode() { } canvas?.remove(outlinePolygon) //임시 폴리곤을 삭제 - canvas?.remove(outLines[concavePointIndices[0]]) //가운데 제외되는 선을 지운다 - //라인들을 좌측에서 -> 우측으로 그리는거처럼 데이터 보정 - outLines.forEach((outline, index) => { - let minX, minY, maxX, maxY - if (outline.x2 < outline.x1 || outline.y2 < outline.y1) { - outLines[index].x1 = outline.x2 - outLines[index].y1 = outline.y2 - outLines[index].x2 = outline.x1 - outLines[index].y2 = outline.y1 - outLines[index].line.x1 = minX - outLines[index].line.y1 = minY - outLines[index].line.x2 = maxX - outLines[index].line.y2 = maxY - } - }) - - // for (let i = 0; i < outLines.length; i++) { - // if (!i % 2 === 0) { - // if (i === concavePointIndices[0] - 1 || i === concavePointIndices[1] + 1) { - // //배열 3번이나 5번일때 - // } else { - // } - // } - // } - - let parallelLines = concavePointIndices[0] + 4 //들어간선에 무조건 평행하는 선 찾기 - if (parallelLines > outLines.length) { - parallelLines = outLines[concavePointIndices[0] + 4 - outLines.length - 1] + 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) + + //해당라인에서 만나는점을 계산 + 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 = [] + + console.log('tmpVertCenterLine', tmpVertCenterLine) + + 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 = [] + } + } + } + + console.log('roofPatternPolygonArray', roofPatternPolygonArray) + + 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) + + //해당라인에서 만나는점을 계산 + 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() } @@ -2107,10 +2658,14 @@ export function useMode() { // handleOuterLineTemplateB(params) console.log(polygon.lines.length) if (polygon.lines.length === 4) { + console.log('4각형') handleTemplateBRect(params) - } else if (polygon.length === 6) { + } else if (polygon.lines.length === 6) { + console.log('6각형') handleTemplateB(params) } + + setTemplateType(3) } /** @@ -2140,6 +2695,9 @@ export function useMode() { isActiveLengthText: false, } + const bigRoofPolygon = [] + const middleRoofPolygon = [] + polygon.lines.forEach((line, index) => { let outline if (index === 0) { @@ -2150,6 +2708,9 @@ export function useMode() { 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) @@ -2158,6 +2719,8 @@ export function useMode() { 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) } @@ -2176,6 +2739,18 @@ export function useMode() { ) 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() } @@ -2235,6 +2810,13 @@ export function useMode() { fontSize: fontSize, } + /** + * 지붕 패턴을 위한 폴리곤 좌표 생성 + */ + const bigRoofPolygon = [] + const middleRoofPolygon = [] + const smallRoofPolygon = [] + rerangeOdd.forEach((line, index) => { const centeredPoint = getCenterPoint(line.y1, line.y2) let points1 = [] @@ -2248,26 +2830,50 @@ export function useMode() { 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 { @@ -2276,12 +2882,26 @@ export function useMode() { 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] @@ -2290,6 +2910,9 @@ export function useMode() { 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) { @@ -2300,11 +2923,18 @@ export function useMode() { 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 } } } } @@ -2430,6 +3060,34 @@ export function useMode() { ) canvas.add(centralSubDashLine) + 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 }) + // } + canvas.renderAll() } @@ -2557,26 +3215,38 @@ export function useMode() { const commonOption = { fill: pattern, - selectable: false, + selectable: true, fontSize: 15, // fontSize는 필요에 따라 조정 } - const bigRoof = new QPolygon(roofPolygonPattern.bigRoofPolygon, commonOption) - const middleRoof = new QPolygon(roofPolygonPattern.middleRoofPolygon, commonOption) - const smallRoof = new QPolygon(roofPolygonPattern.smallRoofPolygon, commonOption) + let polygonArray = [] - bigRoof.setViewLengthText(false) //치수 필요없음 - middleRoof.setViewLengthText(false) - smallRoof.setViewLengthText(false) - - bigRoof.sendToBack() //객체를 가장 뒤로 - middleRoof.sendToBack() - smallRoof.sendToBack() - - canvas.add(bigRoof) //캔버스 객체 추가 - canvas.add(middleRoof) - canvas.add(smallRoof) + roofPolygonPattern.roofPatternPolygonArray.forEach((patternPolygon, index) => { + const drawPolygon = new QPolygon(patternPolygon, commonOption) + canvas.add(drawPolygon) + drawPolygon.setViewLengthText(false) + polygonArray.push(drawPolygon) + }) canvas.renderAll() + + setRoofPolygonArray(polygonArray) + } + + const makeRoofTrestle = () => { + if (Object.keys(roofPolygonPattern).length === 0 && roofPolygonPattern.constructor === Object) { + alert('객체가 비어있습니다.') + return + } + + // 오목한 부분 인덱스 찾기 + const polygons = roofPolygonArray + let concavePolygonObj = [] + + polygons.forEach((polygon, index) => { + const trestlePolygon = handleOuterlinesTest(polygon, -12) + trestlePolygon.set('stroke', 'red').set('strokeDashArray', [9, 5]).set('strokeWidth', 0.3).setViewLengthText(false) + trestlePolygon.fillCell({ width: 100, height: 30, padding: 10 }) + }) } return { @@ -2591,5 +3261,6 @@ export function useMode() { handleOuterlinesTest, handleOuterlinesTest2, makeRoofPatternPolygon, + makeRoofTrestle, } } diff --git a/src/store/canvasAtom.js b/src/store/canvasAtom.js index 91df31ed..c51a7061 100644 --- a/src/store/canvasAtom.js +++ b/src/store/canvasAtom.js @@ -41,3 +41,15 @@ export const roofPolygonPatternArrayState = atom({ default: {}, //object ex) big, mid, sht = {point : [{x1, y1}, {x2, y1}], direction : left or right or top or bottom} dangerouslyAllowMutability: true, }) + +export const roofPolygonArrayState = atom({ + key: 'roofPolygonArray', + default: [], + dangerouslyAllowMutability: true, +}) + +export const templateTypeState = atom({ + key: 'templateType', + default: 1, //1:모임지붕, 2:A타입, 3:B타입 + dangerouslyAllowMutability: true, +}) diff --git a/src/util/canvas-util.js b/src/util/canvas-util.js index 072f7bab..5977eeb4 100644 --- a/src/util/canvas-util.js +++ b/src/util/canvas-util.js @@ -292,56 +292,6 @@ export const getDirectionByPoint = (a, b) => { } } -/** - * 두 선분의 교차점을 찾는 함수입니다. 이 함수는 두 선분이 실제로 교차하는 지점 내에서 교차하는지 확인합니다. - * @param {Object} line1 첫 번째 선분, {x1, y1, x2, y2} 형태의 객체 - * @param {Object} line2 두 번째 선분, {x1, y1, x2, y2} 형태의 객체 - * @returns {{x: number, y: number}|null} 교차점의 좌표를 반환하거나, 교차점이 없으면 null을 반환합니다. - */ -export function calculateIntersection(line1, line2) { - const { x1: x1_1, y1: y1_1, x2: x2_1, y2: y2_1 } = line1 - const { x1: x1_2, y1: y1_2, x2: x2_2, y2: y2_2 } = line2 - - const denominator = (x1_1 - x2_1) * (y1_2 - y2_2) - (y1_1 - y2_1) * (x1_2 - x2_2) - if (denominator === 0) return null // 선분이 평행하거나 일치하는 경우 - - const t = ((x1_1 - x1_2) * (y1_2 - y2_2) - (y1_1 - y1_2) * (x1_2 - x2_2)) / denominator - const u = -((x1_1 - x2_1) * (y1_1 - y1_2) - (y1_1 - y2_1) * (x1_1 - x1_2)) / denominator - - // t와 u가 모두 0과 1 사이에 있을 때, 선분 내에서 교차 - if (t >= 0 && t <= 1 && u >= 0 && u <= 1) { - const intersectionX = x1_1 + t * (x2_1 - x1_1) - const intersectionY = y1_1 + t * (y2_1 - y1_1) - - // Determine the min and max for line1 and line2 for both x and y - const line1MinX = Math.min(line1.x1, line1.x2) - const line1MaxX = Math.max(line1.x1, line1.x2) - const line2MinX = Math.min(line2.x1, line2.x2) - const line2MaxX = Math.max(line2.x1, line2.x2) - - const line1MinY = Math.min(line1.y1, line1.y2) - const line1MaxY = Math.max(line1.y1, line1.y2) - const line2MinY = Math.min(line2.y1, line2.y2) - const line2MaxY = Math.max(line2.y1, line2.y2) - - // 교차점이 선분의 범위 내에 있는지 확인 - if ( - intersectionX >= line1MinX && - intersectionX <= line1MaxX && - intersectionX >= line2MinX && - intersectionX <= line2MaxX && - intersectionY >= line1MinY && - intersectionY <= line1MaxY && - intersectionY >= line2MinY && - intersectionY <= line2MaxY - ) { - return { x: Math.round(intersectionX), y: Math.round(intersectionY) } - } - } - - return null // 교차점이 선분의 범위 내에 없음 -} - export const findIntersection1 = (line1, line2) => { const { x1, y1, x2, y2 } = line1 // 첫 번째 선의 두 점 @@ -372,7 +322,7 @@ export const findIntersection1 = (line1, line2) => { return { x, y } } -export const calculateIntersection2 = (line1, line2) => { +export const calculateIntersection = (line1, line2) => { const result = intersect([line1.x1, line1.y1], [line1.x2, line1.y2], [line2.x1, line2.y1], [line2.x2, line2.y2]) if (!result) { @@ -424,6 +374,55 @@ export function findOrthogonalPoint(line1, line2) { * @param points */ export const sortedPoints = (points) => { + const copyPoints = [...points] + copyPoints.forEach((point) => { + point.x1 = point.x + point.y1 = point.y + const nextPoint = copyPoints[(copyPoints.indexOf(point) + 1) % copyPoints.length] + point.x2 = nextPoint.x + point.y2 = nextPoint.y + }) + + // copyPoint에서 x1, y1 값을 기준으로 시작 인덱스 + const startIndex = getStartIndex(copyPoints) + const startDirection = getDirectionByPoint( + { x: copyPoints[startIndex].x1, y: copyPoints[startIndex].y1 }, + { x: copyPoints[startIndex].x2, y: copyPoints[startIndex].y2 }, + ) + + const resultPoints = [copyPoints[startIndex]] + + let currentPoint = copyPoints[startIndex] + + switch (startDirection) { + case 'right': { + copyPoints.forEach((point, index) => { + if (index === startIndex) return + + const nextPoint = copyPoints.find((p) => p.x2 === currentPoint.x && p.y2 === currentPoint.y) + resultPoints.push(nextPoint) + currentPoint = nextPoint + }) + break + } + case 'bottom': { + copyPoints.forEach((point, index) => { + if (index === startIndex) return + + const nextPoint = copyPoints.find((p) => p.x1 === currentPoint.x2 && p.y1 === currentPoint.y2) + resultPoints.push(nextPoint) + currentPoint = nextPoint + }) + break + } + } + + return resultPoints.map((point) => { + return { x: point.x, y: point.y } + }) +} + +export const sortedPointLessEightPoint = (points) => { const copyPoints = [...points] //points를 x,y좌표를 기준으로 정렬합니다. copyPoints.sort((a, b) => { @@ -433,10 +432,6 @@ export const sortedPoints = (points) => { return a.x - b.x }) - // 이때 copyPoints를 순회하며 최초엔 x값을 비교하여 같은 점을 찾는다. 이때 이 점이 2번째 점이 된다. - // 그 다음점은 2번째 점과 y값이 같은 점이 된다. - // 또 그다음 점은 3번째 점과 x값이 같은 점이 된다. - // 이를 반복하여 copyPoints를 재배열한다. const resultPoints = [copyPoints[0]] let index = 1 let currentPoint = { ...copyPoints[0] } @@ -649,3 +644,26 @@ export function removeDuplicatePoints(points) { return uniquePoints } + +/** + * x,y가 다르면서 가장 가까운 점 + * @param targetPoint + * @param points + * @returns {null} + */ +export function findClosestPointWithDifferentXY(targetPoint, points) { + let closestPoint = null + let smallestDistance = Infinity + + points.forEach((point) => { + if (point.x !== targetPoint.x && point.y !== targetPoint.y) { + const distance = Math.sqrt(Math.pow(point.x - targetPoint.x, 2) + Math.pow(point.y - targetPoint.y, 2)) + if (distance < smallestDistance) { + smallestDistance = distance + closestPoint = point + } + } + }) + + return closestPoint +} diff --git a/src/util/qline-utils.js b/src/util/qline-utils.js index 4cbcb118..77821886 100644 --- a/src/util/qline-utils.js +++ b/src/util/qline-utils.js @@ -2,11 +2,8 @@ import { fabric } from 'fabric' import { QLine } from '@/components/fabric/QLine' export const defineQLine = () => { - fabric.QLine = fabric.util.createClass(fabric.Group, {}) - fabric.QLine.fromObject = function (object, callback) { - const { initOption, initPoints, initLengthTxt } = object - fabric.util.enlivenObjects(object.objects, function (enlivenedObjects) { - return callback(new QLine(initPoints, initOption, initLengthTxt)) - }) - } + /*fabric.QLine = QLine + fabric.QLine.fromObject = (object, callback) => { + return new fabric.QLine([object.x1, object.y1, object.x2, object.y2], object) + }*/ } diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js index e683e4be..af5d676e 100644 --- a/src/util/qpolygon-utils.js +++ b/src/util/qpolygon-utils.js @@ -1,21 +1,312 @@ import { fabric } from 'fabric' -import QPolygon from '@/components/fabric/QPolygon' import { QLine } from '@/components/fabric/QLine' -import { calculateIntersection, calculateIntersection2, distanceBetweenPoints, findIntersection1, removeDuplicatePoints } from '@/util/canvas-util' +import { calculateIntersection, distanceBetweenPoints, findClosestPoint } from '@/util/canvas-util' export const defineQPloygon = () => { - fabric.QPolygon = fabric.util.createClass(fabric.Group, {}) - // fromObject 메서드를 QLine 클래스에 직접 추가 fabric.QPolygon.fromObject = function (object, callback) { - const { initOption, initPoints, initLengthTxt } = object - fabric.util.enlivenObjects(object.objects, function (enlivenedObjects) { - return callback(new QPolygon(initPoints, initOption, initLengthTxt)) - }) + fabric.Object._fromObject('QPolygon', object, callback, 'points') } } -export const drawHelpLineInHexagon2 = (polygon, chon) => { - const oneSideLines = [...polygon.lines].map((line) => { +export const drawHelpLineInHexagon = (polygon, chon) => { + const centerLines = drawCenterLines(polygon) + + let helpLines = [] + + const interSectionPoints = [] + const tempInterSectionPoints = [] + + const ridgeStartPoints = [] + const ridgeEndPoints = [] + + const centerInterSectionPoints = [] + + // polygon.lines = polygon.lines.sort((a, b) => a.length - b.length) + polygon.wall.lines = getOneSideLines(polygon.wall) + + const maxLength = Math.max(...polygon.lines.map((line) => line.length)) + + polygon.points.forEach((point, index) => { + const wallPoint = polygon.wall.points[index] + + const angle = Math.atan2(wallPoint.y - point.y, wallPoint.x - point.x) + + const degree = fabric.util.radiansToDegrees(angle) + + const newX2 = Math.floor(point.x + maxLength * Math.cos(angle)) + const newY2 = Math.floor(point.y + maxLength * Math.sin(angle)) + + const helpLine = new QLine([point.x, point.y, newX2, newY2], { + fontSize: polygon.fontSize, + stroke: 'green', + startPoint: point, + degree: degree, + idx: index, + }) + + // polygon.canvas?.add(helpLine) + + helpLines.push(helpLine) + }) + + helpLines.forEach((line, index) => { + for (let i = index + 1; i < helpLines.length; i++) { + const nextLine = helpLines[i] + if (!line.connectedPoint) { + line.connectedPoint = null + line.connectedPoints = [] + } + if (!nextLine.connectedPoint) { + nextLine.connectedPoint = null + nextLine.connectedPoints = [] + } + + const interSectionPoint = calculateIntersection(line, nextLine) + + if ( + interSectionPoint && + polygon.inPolygon(interSectionPoint) && + polygon.wall.inPolygon(interSectionPoint) && + Math.abs(distanceBetweenPoints(line.startPoint, interSectionPoint) - distanceBetweenPoints(nextLine.startPoint, interSectionPoint)) < 2 + ) { + const area = calculateTriangleArea(line.startPoint, nextLine.startPoint, interSectionPoint) + const currentLineConnectedPoint = line.connectedPoint + const nextLineConnectedPoint = nextLine.connectedPoint + + if (area <= 1) { + return + } + + if (currentLineConnectedPoint && currentLineConnectedPoint.area < area) { + return + } + + //startPoint는 line의 startPoint와 nextLine의 startPoint를 비교하여 x가 같은경우 y가 더 작은 값, y가 같은경우 x가 더 작은 값을 선택한다. + const startPoint = + line.startPoint.x === nextLine.startPoint.x + ? line.startPoint.y < nextLine.startPoint.y + ? line.startPoint + : nextLine.startPoint + : line.startPoint.x < nextLine.startPoint.x + ? line.startPoint + : nextLine.startPoint + + const endPoint = + line.startPoint.x === nextLine.startPoint.x + ? line.startPoint.y > nextLine.startPoint.y + ? line.startPoint + : nextLine.startPoint + : line.startPoint.x > nextLine.startPoint.x + ? line.startPoint + : nextLine.startPoint + + line.connectedPoint = { interSectionPoint, area, startPoint, endPoint } + line.connectedPoints.push(interSectionPoint) + nextLine.connectedPoint = { interSectionPoint, area, startPoint, endPoint } + nextLine.connectedPoints.push(interSectionPoint) + } + } + }) + + helpLines.forEach((line) => { + if (line.connectedPoint) { + tempInterSectionPoints.push(line.connectedPoint) + } + }) + + // interSectionPoints에서 interSectionPoint가 중복인 값이 있는 경우만 선택한다. + tempInterSectionPoints.forEach((point) => { + // intersectionPoint가 중복인 경우 + const isDuplicated = + tempInterSectionPoints.filter((p) => p.interSectionPoint.x === point.interSectionPoint.x && p.interSectionPoint.y === point.interSectionPoint.y) + .length > 1 + if (isDuplicated) { + interSectionPoints.push(point) + } + }) + + // interSectionPoints에서 interSectionPoint 기준으로 중복을 제거한다. + const uniqueInterSectionPoints = Array.from( + new Set(interSectionPoints.map((point) => `${point.interSectionPoint.x},${point.interSectionPoint.y}`)), + ).map((key) => { + const { interSectionPoint, area, startPoint, endPoint } = interSectionPoints.find( + (point) => `${point.interSectionPoint.x},${point.interSectionPoint.y}` === key, + ) + return { interSectionPoint, area, startPoint, endPoint } + }) + + uniqueInterSectionPoints.forEach((point) => { + ridgeStartPoints.push(point.interSectionPoint) + + const line = new QLine([point.startPoint.x, point.startPoint.y, point.interSectionPoint.x, point.interSectionPoint.y], { + stroke: 'purple', + fontSize: polygon.fontSize, + name: 'hip', + }) + + const line2 = new QLine([point.endPoint.x, point.endPoint.y, point.interSectionPoint.x, point.interSectionPoint.y], { + stroke: 'purple', + fontSize: polygon.fontSize, + name: 'hip', + }) + + polygon.hips.push(line) + polygon.hips.push(line2) + + polygon.canvas.add(line) + polygon.canvas.add(line2) + }) + + const removedIdx = [] + + helpLines.forEach((line) => { + const connectedPoints = line.connectedPoints + connectedPoints.forEach((connectedPoint) => { + uniqueInterSectionPoints.forEach((point) => { + const interSectionPoint = point.interSectionPoint + + if (connectedPoint.x === interSectionPoint.x && connectedPoint.y === interSectionPoint.y) { + removedIdx.push(line.idx) + } + }) + }) + }) + + const notIntersectedLines = helpLines.filter((line) => !removedIdx.includes(line.idx)) + + notIntersectedLines.forEach((line) => { + centerLines.forEach((centerLine) => { + const interSectionPoint = calculateIntersection(line, centerLine) + + if (interSectionPoint && polygon.inPolygon(interSectionPoint) && polygon.wall.inPolygon(interSectionPoint)) { + centerInterSectionPoints.push(interSectionPoint) + } + }) + }) + + // centerInterSectionPoints에서 ridgeStartPoints와 x가 같거나 y가 같은것중 가장 가까운 점들을 찾는다. + ridgeStartPoints.forEach((point) => { + const xPoints = centerInterSectionPoints.filter((centerPoint) => Math.abs(centerPoint.x - point.x) < 2) + const yPoints = centerInterSectionPoints.filter((centerPoint) => Math.abs(centerPoint.y - point.y) < 2) + let closestPoint + if (xPoints.length === 0) { + closestPoint = findClosestPoint(point, yPoints) + } else { + closestPoint = findClosestPoint(point, xPoints) + } + + if (closestPoint) { + const line = new QLine([point.x, point.y, closestPoint.x, closestPoint.y], { + stroke: 'purple', + fontSize: polygon.fontSize, + name: 'ridge', + }) + polygon.ridges.push(line) + polygon.canvas.add(line) + ridgeEndPoints.push(closestPoint) + } + }) + + // ridgeEndPoints끼리 이어준다. + const remainingPoints = ridgeEndPoints + + remainingPoints.forEach((ridgePoint) => { + polygon.points.forEach((point) => { + const degree = calculateAngle(ridgePoint, point) + + if (Math.abs(degree) % 45 < 1) { + const line = new QLine([ridgePoint.x, ridgePoint.y, point.x, point.y], { + stroke: 'purple', + fontSize: polygon.fontSize, + name: 'hip', + }) + + polygon.hips.push(line) + polygon.canvas.add(line) + } + }) + }) + + while (remainingPoints.length > 0) { + const point = remainingPoints.shift() + const closestPoint = findClosestPoint(point, remainingPoints) + if (!closestPoint) continue + // 마루끼리 연결 + const line = new QLine([point.x, point.y, closestPoint.x, closestPoint.y], { + stroke: 'purple', + fontSize: polygon.fontSize, + name: 'connectRidge', + }) + polygon.connectRidges.push(line) + + polygon.canvas.add(line) + } +} + +export const drawCenterLines = (polygon) => { + const centerLines = [] + + const oneSideLines = getOneSideLines(polygon) + + const horizontalLines = oneSideLines.filter((line) => line.direction === 'right') + const verticalLines = oneSideLines.filter((line) => line.direction === 'bottom') + // horizontalLines 를 y1 좌표 기준으로 정렬한다. + horizontalLines.sort((a, b) => a.y1 - b.y1) + // verticalLines 를 x1 좌표 기준으로 정렬한다. + verticalLines.sort((a, b) => a.x1 - b.x1) + + let maxHorizontalLineLength = 0 + let maxVerticalLineLength = 0 + // 모든 가로선의 중심선을 긋는다. + horizontalLines.forEach((line, index) => { + const nextLine = horizontalLines[(index + 1) % horizontalLines.length] + + line.set({ strokeWidth: 5 }) + nextLine.set({ strokeWidth: 5 }) + + polygon.canvas.renderAll() + + const startCenterX = Math.min(line.x1, nextLine.x1) + const startCenterY = (line.y1 + nextLine.y1) / 2 + + const endCenterX = line.x2 > nextLine.x2 ? line.x2 : nextLine.x2 + const endCenterY = startCenterY + + const centerLine = new QLine([startCenterX, startCenterY, endCenterX, endCenterY], { + fontSize: polygon.fontSize, + stroke: 'red', + strokeWidth: 1, + direction: 'horizontal', + }) + + centerLines.push(centerLine) + }) + + // 모든 세로선의 중심선을 긋는다. + verticalLines.forEach((line, index) => { + const nextLine = verticalLines[(index + 1) % verticalLines.length] + + const startCenterX = (line.x1 + nextLine.x1) / 2 + const startCenterY = Math.min(line.y1, nextLine.y1) + + const endCenterX = startCenterX + let endCenterY = line.y2 > nextLine.y2 ? line.y2 : nextLine.y2 + + const centerLine = new QLine([startCenterX, startCenterY, endCenterX, endCenterY], { + fontSize: polygon.fontSize, + stroke: 'blue', + strokeWidth: 1, + direction: 'vertical', + }) + + centerLines.push(centerLine) + }) + + return centerLines +} + +const getOneSideLines = (polygon) => { + return [...polygon.lines].map((line) => { let newX1, newY1, newX2, newY2 if (line.direction === 'top') { newX1 = line.x2 @@ -42,288 +333,45 @@ export const drawHelpLineInHexagon2 = (polygon, chon) => { } return line }) - - const centerLines = [] - const helpLines = [] - const ridgeStartPoints = [] - - const horizontalLines = oneSideLines.filter((line) => line.direction === 'right') - const verticalLines = oneSideLines.filter((line) => line.direction === 'bottom') - // horizontalLines 를 y1 좌표 기준으로 정렬한다. - horizontalLines.sort((a, b) => a.y1 - b.y1) - // verticalLines 를 x1 좌표 기준으로 정렬한다. - verticalLines.sort((a, b) => a.x1 - b.x1) - - const maxHorizontalLineLength = horizontalLines.reduce((prev, current) => (prev.length > current.length ? prev.length : current.length)) - const maxVerticalLineLength = verticalLines.reduce((prev, current) => (prev.length > current.length ? prev.length : current.length)) - - // 모든 가로선의 중심선을 긋는다. - horizontalLines.forEach((line, index) => { - const nextLine = horizontalLines[(index + 1) % horizontalLines.length] - - line.line.set({ strokeWidth: 5 }) - nextLine.line.set({ strokeWidth: 5 }) - - polygon.canvas.renderAll() - - const startCenterX = Math.min(line.x1, nextLine.x1) - const startCenterY = (line.y1 + nextLine.y1) / 2 - - const endCenterX = line.length >= nextLine.length ? startCenterX + line.length : startCenterX + nextLine.length - const endCenterY = startCenterY - - const centerLine = new QLine([startCenterX, startCenterY, endCenterX, endCenterY], { - fontSize: polygon.fontSize, - stroke: 'red', - strokeWidth: 1, - direction: 'horizontal', - }) - - /*polygon.canvas.add(centerLine) - polygon.canvas.renderAll()*/ - - centerLines.push(centerLine) - }) - - // 모든 세로선의 중심선을 긋는다. - verticalLines.forEach((line, index) => { - const nextLine = verticalLines[(index + 1) % verticalLines.length] - - const startCenterX = (line.x1 + nextLine.x1) / 2 - const startCenterY = Math.min(line.y1, nextLine.y1) - - const endCenterX = startCenterX - let endCenterY = line.length >= nextLine.length ? startCenterY + line.length : startCenterY + nextLine.length - - const centerLine = new QLine([startCenterX, startCenterY, endCenterX, endCenterY], { - fontSize: polygon.fontSize, - stroke: 'blue', - strokeWidth: 1, - direction: 'vertical', - }) - - /*polygon.canvas.add(centerLine) - polygon.canvas.renderAll()*/ - centerLines.push(centerLine) - }) - - polygon.points.forEach((point, index) => { - const wallPoint = polygon.wall.points[index] - // 두 점의 좌표 - const x1 = point.x - const y1 = point.y - const x2 = wallPoint.x - const y2 = wallPoint.y - - let newX2, newY2 - - // x1, y1을 기준으로 x2, y2와의 거리를 유지한 새로운 직선 생성 - const angle = Math.atan2(y2 - y1, x2 - x1) - - let previousIndex = index === 0 ? polygon.lines.length - 1 : index - 1 - const maxLength = Math.max(polygon.lines[index].length, polygon.lines[previousIndex].length) - - newX2 = Math.floor(x1 + (maxLength / 2 + polygon.points.length * 10) * Math.cos(angle)) - newY2 = Math.floor(y1 + (maxLength / 2 + polygon.points.length * 10) * Math.sin(angle)) - - const line = new QLine([x1, y1, newX2, newY2], { - fontSize: polygon.fontSize, - stroke: 'green', - idx: index, - }) - line.set({ degree: fabric.util.radiansToDegrees(angle) }) - polygon.canvas.add(line) - helpLines.push(line) - polygon.canvas.renderAll() - }) - - helpLines.forEach((line, index) => { - for (let i = index + 1; i < helpLines.length; i++) { - const nextLine = helpLines[i] - - if (line.isAlreadyInterSection || nextLine.isAlreadyInterSection) { - continue - } - - let intersectionPoint = calculateIntersection(line, nextLine) - - if (!intersectionPoint) { - continue - } - - const circle = new fabric.Circle({ - radius: 3, - fill: 'red', - left: intersectionPoint.x - 3, - top: intersectionPoint.y - 3, - }) - - polygon.canvas.add(circle) - - line.set({ isAlreadyInterSection: true }) - nextLine.set({ isAlreadyInterSection: true }) - - const helpLine1 = new QLine([nextLine.x1, nextLine.y1, intersectionPoint.x, intersectionPoint.y], { - fontSize: polygon.fontSize, - stroke: 'skyblue', - }) - - const helpLine2 = new QLine([line.x1, line.y1, intersectionPoint.x, intersectionPoint.y], { - fontSize: polygon.fontSize, - stroke: 'skyblue', - }) - - ridgeStartPoints.push(intersectionPoint) - polygon.canvas.add(helpLine1) - polygon.canvas.add(helpLine2) - polygon.canvas.remove(nextLine) - polygon.canvas.remove(line) - polygon.canvas.renderAll() - } - }) - - // 안만나는 선들 - const notInterSectionLines = helpLines.filter((line) => !line.isAlreadyInterSection) - const ridgeEndPoints = [] - let interSectionPoints = [] - - notInterSectionLines.forEach((line, index) => { - let subCenterLines - if (Math.abs(line.degree) < 90) { - subCenterLines = centerLines.filter((centerLine) => centerLine.direction === 'vertical') - } else { - subCenterLines = centerLines.filter((centerLine) => centerLine.direction === 'horizontal') - } - - centerLines.forEach((centerLine) => { - const interSectionPoint = calculateIntersection2(line, centerLine) - - if (!interSectionPoint) { - return - } - - ridgeStartPoints.forEach((point) => { - line.interSectionPoints.push(interSectionPoint) - interSectionPoints.push(interSectionPoint) - - const newLine = new QLine([line.x1, line.y1, interSectionPoint.x, interSectionPoint.y], { - stroke: 'black', - fontSize: polygon.fontSize, - }) - - const circle = new fabric.Circle({ - radius: 3, - fill: 'blue', - left: interSectionPoint.x - 3, - top: interSectionPoint.y - 3, - }) - polygon.canvas.add(circle) - polygon.canvas.add(newLine) - - polygon.canvas.remove(line) - - line.set({ isAlreadyInterSection: true }) - }) - }) - }) - - interSectionPoints = removeDuplicatePoints(interSectionPoints) - - const startRidgePoint = ridgeStartPoints[0] - - const endRidgePoint = ridgeStartPoints[ridgeStartPoints.length - 1] - - let step = 0 - - while (true) { - if (step % 2 === 0) { - const nextPoint = interSectionPoints.find((point) => point.x === startRidgePoint.x || point.y === startRidgePoint.y) - - if (nextPoint) { - const ridge = new QLine([startRidgePoint.x, startRidgePoint.y, nextPoint.x, nextPoint.y], { - stroke: 'green', - fontSize: polygon.fontSize, - }) - polygon.canvas.add(ridge) - polygon.canvas.renderAll() - } - - console.log('nextPoint', nextPoint) - console.log('startRidgePoint', startRidgePoint) - } else { - } - - step++ - - break - } - - /*ridgeStartPoints.forEach((point, index) => { - for (let i = index + 1; i < ridgeStartPoints.length; i++) { - const currentPoint = ridgeStartPoints[index] - const nextPoint = ridgeStartPoints[i] - - if (currentPoint.x === nextPoint.x || currentPoint.y === nextPoint.y) { - const ridge = new QLine([currentPoint.x, currentPoint.y, nextPoint.x, nextPoint.y], { - stroke: 'black', - fontSize: polygon.fontSize, - }) - polygon.canvas.add(ridge) - polygon.canvas.renderAll() - break - } - } - })*/ - - /*ridgeStartPoints.forEach((point, index) => { - let arrivalPoint - let distance = Infinity - let startPoint - interSectionPoints.forEach((interSectionPoint) => { - if (Math.abs(point.x - interSectionPoint.x) < 1 || Math.abs(point.y - interSectionPoint.y) < 1) { - if (distanceBetweenPoints(point, interSectionPoint) < distance) { - startPoint = point - distance = distanceBetweenPoints(point, interSectionPoint) - arrivalPoint = interSectionPoint - } - } - }) - - if (arrivalPoint) { - const line = notInterSectionLines.filter((line) => line.interSectionPoints.includes(arrivalPoint))[0] - - const ridge = new QLine([startPoint.x, startPoint.y, arrivalPoint.x, arrivalPoint.y], { - stroke: 'black', - fontSize: polygon.fontSize, - }) - - const helpLine = new QLine([line.x1, line.y1, arrivalPoint.x, arrivalPoint.y], { - stroke: 'red', - fontSize: polygon.fontSize, - }) - - ridgeEndPoints.push(arrivalPoint) - polygon.canvas.add(ridge) - polygon.canvas.add(helpLine) - // polygon.canvas.remove(line) - polygon.canvas.renderAll() - debugger - } - })*/ - - /*for (let i = 0; i < ridgeEndPoints.length; i = i + 2) { - const currentRidgeEndPoint = ridgeEndPoints[i] - const nextRidgeEndPoint = ridgeEndPoints[(i + 1) % ridgeEndPoints.length] - const ridgeConnectLine = new QLine([currentRidgeEndPoint.x, currentRidgeEndPoint.y, nextRidgeEndPoint.x, nextRidgeEndPoint.y], { - fontSize: polygon.fontSize, - stroke: 'green', - }) - - polygon.canvas.add(ridgeConnectLine) - polygon.canvas.renderAll() - }*/ +} +const calculateAngle = (point1, point2) => { + const deltaX = point2.x - point1.x + const deltaY = point2.y - point1.y + const angleInRadians = Math.atan2(deltaY, deltaX) + return angleInRadians * (180 / Math.PI) } -export const drawHelpLineInHexagon = (polygon, chon) => { - // 가장 긴라인을 기준으로 centerLine을 그린다. +/** + * 3개의 점을 이용해 직각 이등변 삼각형인지 확인 + * @param point1 + * @param point2 + * @param point3 + * @returns {boolean} + */ +const isRightIsoscelesTriangle = (point1, point2, point3) => { + const distance = (p1, p2) => Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2)) + + const d1 = distance(point1, point2) + const d2 = distance(point2, point3) + const d3 = distance(point3, point1) + + const distances = [d1, d2, d3].sort((a, b) => a - b) + + // Check if the two smaller distances are equal and the largest distance is the hypotenuse + return distances[0] === distances[1] && Math.abs(Math.pow(distances[0], 2) * 2 - Math.pow(distances[2], 2)) < 1 +} + +/** + * 세개의 점으로 삼각형의 넓이를 구한다. + * @param point1 + * @param point2 + * @param point3 + * @returns {number} + */ +const calculateTriangleArea = (point1, point2, point3) => { + const { x: x1, y: y1 } = point1 + const { x: x2, y: y2 } = point2 + const { x: x3, y: y3 } = point3 + + return Math.abs(x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2)) / 2 } diff --git a/yarn.lock b/yarn.lock index 94bdd351..f34efa44 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3221,6 +3221,11 @@ postcss@^8, postcss@^8.4.23: picocolors "^1.0.0" source-map-js "^1.2.0" +prettier@^3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.3.tgz#30c54fe0be0d8d12e6ae61dbb10109ea00d53105" + integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew== + prisma@^5.17.0: version "5.17.0" resolved "https://registry.yarnpkg.com/prisma/-/prisma-5.17.0.tgz#267b43921ab94805b010537cffa5ccaf530fa066"