From f76c4551bf52f4e42cb9644dd147a12b0c2b3041 Mon Sep 17 00:00:00 2001 From: Jaeyoung Lee Date: Mon, 14 Oct 2024 16:13:43 +0900 Subject: [PATCH 01/13] =?UTF-8?q?=EC=A7=80=EB=B6=95=20=EB=AA=A8=EC=96=91?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD=20=EC=9E=91=EC=97=85=20=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/dictionary.txt | 3 +- src/components/Roof2.jsx | 18 +- src/components/fabric/QPolygon.js | 9 +- src/hooks/useMode.js | 257 +++-- src/util/qpolygon-utils.js | 1504 ++++++++++++++--------------- 5 files changed, 843 insertions(+), 948 deletions(-) diff --git a/docs/dictionary.txt b/docs/dictionary.txt index cef0921e..7e07124d 100644 --- a/docs/dictionary.txt +++ b/docs/dictionary.txt @@ -23,4 +23,5 @@ 개구: openSpace 도머: dormer 그림자: shadow - +복도치수: planeSize +실제치수: actualSize diff --git a/src/components/Roof2.jsx b/src/components/Roof2.jsx index fbe61038..910fd365 100644 --- a/src/components/Roof2.jsx +++ b/src/components/Roof2.jsx @@ -39,7 +39,7 @@ import QEmptyContextMenu from '@/components/common/context-menu/QEmptyContextMen import InitSettingsModal from './InitSettingsModal' import GridSettingsModal from './GridSettingsModal' import { SurfaceShapeModal } from '@/components/ui/SurfaceShape' -import { changeAllHipAndGableRoof, drawDirectionStringToArrow } from '@/util/qpolygon-utils' +import { drawDirectionStringToArrow } from '@/util/qpolygon-utils' import ThumbnailList from '@/components/ui/ThumbnailLIst' import ObjectPlacement from '@/components/ui/ObjectPlacement' import { globalLocaleStore } from '@/store/localeAtom' @@ -409,6 +409,20 @@ export default function Roof2(props) { { x: 600, y: 100 }, ] + const rectangleType1 = [ + { x: 100, y: 100 }, + { x: 100, y: 600 }, + { x: 300, y: 600 }, + { x: 300, y: 100 }, + ] + + const rectangleType2 = [ + { x: 100, y: 100 }, + { x: 100, y: 300 }, + { x: 600, y: 300 }, + { x: 600, y: 100 }, + ] + const types = [type1, type2, type3, type4, type1A, type1B, eightPoint, eightPoint2, eightPoint3, eightPoint4, twelvePoint] const newP = [ { x: 450, y: 450 }, @@ -417,7 +431,7 @@ export default function Roof2(props) { { x: 450, y: 850 }, ] - const polygon = new QPolygon(twelvePoint, { + const polygon = new QPolygon(rectangleType2, { fill: 'transparent', stroke: 'green', strokeWidth: 1, diff --git a/src/components/fabric/QPolygon.js b/src/components/fabric/QPolygon.js index 84e57974..9e034ac9 100644 --- a/src/components/fabric/QPolygon.js +++ b/src/components/fabric/QPolygon.js @@ -2,7 +2,7 @@ import { fabric } from 'fabric' import { v4 as uuidv4 } from 'uuid' import { QLine } from '@/components/fabric/QLine' import { distanceBetweenPoints, findTopTwoIndexesByDistance, getDirectionByPoint, sortedPointLessEightPoint, sortedPoints } from '@/util/canvas-util' -import { calculateAngle, drawDirectionArrow, drawHippedRoof, inPolygon, toGeoJSON } from '@/util/qpolygon-utils' +import { calculateAngle, drawDirectionArrow, drawRidgeRoof, inPolygon, toGeoJSON } from '@/util/qpolygon-utils' import * as turf from '@turf/turf' export const QPolygon = fabric.util.createClass(fabric.Polygon, { @@ -174,9 +174,10 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, { }, // 보조선 그리기 - drawHelpLine(chon = 4) { - // drawHelpLineInHexagon(this, chon) - drawHippedRoof(this, chon) + drawHelpLine(pitch = 4) { + // drawHelpLineInHexagon(this, pitch) + console.log('drawHelpLine roofId : ', this.id) + drawRidgeRoof(this.id, pitch, this.canvas) }, addLengthText() { diff --git a/src/hooks/useMode.js b/src/hooks/useMode.js index 6778bdba..e82c51f3 100644 --- a/src/hooks/useMode.js +++ b/src/hooks/useMode.js @@ -36,10 +36,7 @@ import { QPolygon } from '@/components/fabric/QPolygon' import offsetPolygon from '@/util/qpolygon-utils' import { isObjectNotEmpty } from '@/util/common-utils' import * as turf from '@turf/turf' -import { INPUT_TYPE, Mode } from '@/common/common' -import { m } from 'framer-motion' -import { set } from 'react-hook-form' -import { FaWineGlassEmpty } from 'react-icons/fa6' +import { INPUT_TYPE, LINE_TYPE, Mode } from '@/common/common' export function useMode() { const [mode, setMode] = useRecoilState(modeState) @@ -1509,6 +1506,25 @@ export function useMode() { *벽 지붕 외곽선 생성 polygon을 입력받아 만들기 */ const handleOuterlinesTest2 = (polygon, offset = 50) => { + console.log('polygon : ', polygon) + // TODO [ljyoung] : offset 입력 처리 후 제거 해야함. + polygon.lines.forEach((line, index) => { + if (index === 1 || index === 3) { + line.attributes = { + type: LINE_TYPE.WALLLINE.WALL, + offset: 50, + width: 100, + pitch: 4, + } + } else { + line.attributes = { + type: LINE_TYPE.WALLLINE.WALL, + offset: 50, + width: 100, + pitch: 4, + } + } + }) const roof = drawRoofPolygon(polygon) //지붕 그리기 roof.drawHelpLine() // roof.divideLine() @@ -1677,21 +1693,6 @@ export function useMode() { } const drawRoofPolygon = (wall) => { - // TODO [ljyoung] : offset 입력 처리 후 제거 해야함. - wall.lines.forEach((line, index) => { - if (index === wall.lines.length - 1 || index === 3) { - line.attributes = { - type: 'gable', - offset: 30, - } - } else { - line.attributes = { - type: 'hip', - offset: 50, - } - } - }) - const polygon = createRoofPolygon(wall.points) const originPolygon = new QPolygon(wall.points, { fontSize: 0 }) let offsetPolygon @@ -1710,13 +1711,33 @@ export function useMode() { return { x1: point.x, y1: point.y } }), ) - roof.name = 'roofBase' + roof.name = 'roof' roof.setWall(wall) roof.lines.forEach((line, index) => { - line.attributes = { type: wall.lines[index].attributes.type } + line.attributes = { + roofId: roof.id, + wallLine: wall.lines[index].id, + type: wall.lines[index].attributes.type, + offset: wall.lines[index].attributes.offset, + width: wall.lines[index].attributes.width, + pitch: wall.lines[index].attributes.pitch, + } }) + wall.attributes = { + roofId: roof.id, + } + + wall.lines.forEach((line, index) => { + line.attributes = { + roofId: roof.id, + } + }) + + console.log('roof : ', roof) + console.log('wall : ', wall) + setRoof(roof) setWall(wall) @@ -4852,12 +4873,6 @@ export function useMode() { ) } - const coordToTurfPolygon = (points) => { - const coordinates = points.map((point) => [point.x, point.y]) - coordinates.push(coordinates[0]) - return turf.polygon([coordinates]) - } - /** * trestle에서 영역을 가져와 mouse:move 이벤트로 해당 영역에 진입했을때 booleanPointInPolygon 로 진입여부를 확인 * 확인 후 셀을 이동시킴 @@ -4866,125 +4881,58 @@ export function useMode() { const trestlePolygons = canvas?.getObjects().filter((obj) => obj.name === 'trestle') //가대를 가져옴 if (trestlePolygons.length !== 0) { + let lastPointPosition = { x: 0, y: 0 } let fabricPolygon = null let inside = false let turfPolygon - let manualDrawCells = drewRoofCells // 앞에서 자동으로 했을때 추가됨 - let direction + let manualDrawCells = drewRoofCells // + canvas.on('mouse:move', (e) => { //마우스 이벤트 삭제 후 재추가 const mousePoint = canvas.getPointer(e.e) + const turfPoint = turf.point([mousePoint.x, mousePoint.y]) + for (let i = 0; i < trestlePolygons.length; i++) { turfPolygon = polygonToTurfPolygon(trestlePolygons[i]) - direction = trestlePolygons[i].direction //도형의 방향 - let width = direction === 'south' || direction === 'north' ? 172 : 113 - let height = direction === 'south' || direction === 'north' ? 113 : 172 - - const points = [ - { x: mousePoint.x - width / 2, y: mousePoint.y - height / 2 }, - { x: mousePoint.x + width / 2, y: mousePoint.y - height / 2 }, - { x: mousePoint.x + width / 2, y: mousePoint.y + height / 2 }, - { x: mousePoint.x - width / 2, y: mousePoint.y + height / 2 }, - ] - - const turfPoints = coordToTurfPolygon(points) - - if (turf.booleanWithin(turfPoints, turfPolygon)) { + if (turf.booleanPointInPolygon(turfPoint, turfPolygon)) { //turf에 보면 폴리곤안에 포인트가 있는지 함수가 있다 + const direction = trestlePolygons[i].direction //도형의 방향 + let width = direction === 'south' || direction === 'north' ? 172.2 : 113.4 + let height = direction === 'south' || direction === 'north' ? 113.4 : 172.2 + if (Math.abs(mousePoint.x - lastPointPosition.x) >= 5 || Math.abs(mousePoint.y - lastPointPosition.y) >= 5) { + let isDrawing = false - // if (Math.abs(mousePoint.x - lastPointPosition.x) >= 5 || Math.abs(mousePoint.y - lastPointPosition.y) >= 5) { - let isDrawing = false + if (isDrawing) return + canvas?.remove(...canvas?.getObjects().filter((obj) => obj.name === 'tmpCell')) //움직일때 일단 지워가면서 움직임 - if (isDrawing) return - canvas?.remove(...canvas?.getObjects().filter((obj) => obj.name === 'tmpCell')) //움직일때 일단 지워가면서 움직임 + const points = [ + { x: mousePoint.x - width / 2, y: mousePoint.y - height / 2 }, + { x: mousePoint.x + width / 2, y: mousePoint.y - height / 2 }, + { x: mousePoint.x + width / 2, y: mousePoint.y + height / 2 }, + { x: mousePoint.x - width / 2, y: mousePoint.y + height / 2 }, + ] - fabricPolygon = new QPolygon(points, { - fill: '#BFFD9F', - // stroke: 'black', - // strokeWidth: 1, - selectable: false, // 선택 가능하게 설정 - lockMovementX: true, // X 축 이동 잠금 - lockMovementY: true, // Y 축 이동 잠금 - lockRotation: true, // 회전 잠금 - lockScalingX: true, // X 축 크기 조정 잠금 - lockScalingY: true, // Y 축 크기 조정 잠금 - opacity: 0.8, - parentId: trestlePolygons[i].parentId, - name: 'tmpCell', - }) + fabricPolygon = new QPolygon(points, { + fill: '#BFFD9F', + stroke: 'black', + selectable: false, // 선택 가능하게 설정 + lockMovementX: true, // X 축 이동 잠금 + lockMovementY: true, // Y 축 이동 잠금 + lockRotation: true, // 회전 잠금 + lockScalingX: true, // X 축 크기 조정 잠금 + lockScalingY: true, // Y 축 크기 조정 잠금 + opacity: 0.8, + parentId: trestlePolygons[i].parentId, + name: 'tmpCell', + }) - canvas?.add(fabricPolygon) //움직여가면서 추가됨 - - /** - * 스냅기능 - */ - let snapDistance = 20 - - const bigLeft = trestlePolygons[i].left - const bigTop = trestlePolygons[i].top - const bigRight = bigLeft + trestlePolygons[i].width * trestlePolygons[i].scaleX - const bigBottom = bigTop + trestlePolygons[i].height * trestlePolygons[i].scaleY - const bigCenter = (bigTop + bigTop + trestlePolygons[i].height) / 2 - - // 작은 폴리곤의 경계 좌표 계산 - const smallLeft = fabricPolygon.left - const smallTop = fabricPolygon.top - const smallRight = smallLeft + fabricPolygon.width * fabricPolygon.scaleX - const smallBottom = smallTop + fabricPolygon.height * fabricPolygon.scaleY - const smallCenter = smallLeft + (fabricPolygon.width * fabricPolygon.scaleX) / 2 - - // 위쪽 변에 스냅 - if (Math.abs(smallTop - bigTop) < snapDistance) { - fabricPolygon.top = bigTop + canvas?.add(fabricPolygon) //움직여가면서 추가됨 + lastPointPosition = { x: mousePoint.x, y: mousePoint.y } } - // 아래쪽 변에 스냅 - if (Math.abs(smallTop + fabricPolygon.height * fabricPolygon.scaleY - (bigTop + trestlePolygons[i].height)) < snapDistance) { - fabricPolygon.top = bigTop + trestlePolygons[i].height - fabricPolygon.height * fabricPolygon.scaleY - } - - // 왼쪽변에 스냅 - if (Math.abs(smallLeft - bigLeft) < snapDistance) { - fabricPolygon.left = bigLeft - } - //오른쪽 변에 스냅 - if (Math.abs(smallRight - bigRight) < snapDistance) { - fabricPolygon.left = bigRight - fabricPolygon.width * fabricPolygon.scaleX - } - - if (direction === 'south' || direction === 'north') { - // 모듈왼쪽이 세로중앙선에 붙게 스냅 - if (Math.abs(smallLeft - (bigLeft + trestlePolygons[i].width / 2)) < snapDistance) { - fabricPolygon.left = bigLeft + trestlePolygons[i].width / 2 - } - - // 모듈이 가운데가 세로중앙선에 붙게 스냅 - if (Math.abs(smallCenter - (bigLeft + trestlePolygons[i].width / 2)) < snapDistance) { - fabricPolygon.left = bigLeft + trestlePolygons[i].width / 2 - (fabricPolygon.width * fabricPolygon.scaleX) / 2 - } - - // 모듈오른쪽이 세로중앙선에 붙게 스냅 - if (Math.abs(smallRight - (bigLeft + trestlePolygons[i].width / 2)) < snapDistance) { - fabricPolygon.left = bigLeft + trestlePolygons[i].width / 2 - fabricPolygon.width * fabricPolygon.scaleX - } - } else { - // 모듈이 가로중앙선에 스냅 - if (Math.abs(smallTop + fabricPolygon.height / 2 - bigCenter) < snapDistance) { - fabricPolygon.top = bigCenter - fabricPolygon.height / 2 - } - - if (Math.abs(smallTop - (bigTop + trestlePolygons[i].height / 2)) < snapDistance) { - fabricPolygon.top = bigTop + trestlePolygons[i].height / 2 - } - // 모듈 밑면이 가로중앙선에 스냅 - if (Math.abs(smallBottom - (bigTop + trestlePolygons[i].height / 2)) < snapDistance) { - fabricPolygon.top = bigTop + trestlePolygons[i].height / 2 - fabricPolygon.height * fabricPolygon.scaleY - } - } - - fabricPolygon.setCoords() canvas?.renderAll() inside = true + break } else { inside = false } @@ -5000,26 +4948,20 @@ export function useMode() { if (!inside) return if (fabricPolygon) { const turfCellPolygon = polygonToTurfPolygon(fabricPolygon) - fabricPolygon.setCoords() //좌표 재정렬 + if (turf.booleanWithin(turfCellPolygon, turfPolygon)) { //마우스 클릭시 set으로 해당 위치에 셀을 넣음 - - manualDrawCells.forEach((cell) => { - console.log('cells', cell.points) - }) - console.log('turfCellPolygon', turfCellPolygon.geometry.coordinates) - const isOverlap = manualDrawCells.some((cell) => turf.booleanOverlap(turfCellPolygon, polygonToTurfPolygon(cell))) if (!isOverlap) { //안겹치면 넣는다 - fabricPolygon.setCoords() fabricPolygon.set({ name: 'cell' }) + fabricPolygon.setCoords() manualDrawCells.push(fabricPolygon) } else { alert('셀끼리 겹치면 안되죠?') } - // } else { - // alert('나갔으요!!') + } else { + alert('나갔으요!!') } setDrewRoofCells(manualDrawCells) } @@ -5096,6 +5038,27 @@ export function useMode() { // console.log('bbox', bbox) + const boxes = [] + const installedCellsArray = [] + + for (let x = bbox[0]; x < bbox[2]; x += width) { + for (let y = bbox[1]; y < bbox[3]; y += height) { + const box = turf.polygon([ + [ + [x, y], + [x + width, y], + [x + width, y + height], + [x, y + height], + [x, y], + ], + ]) + + if (turf.booleanWithin(box, turfTrestlePolygon)) { + boxes.push(box) + } + } + } + for (let col = 0; col <= cols; col++) { for (let row = 0; row <= rows; row++) { let x = 0, @@ -5145,6 +5108,20 @@ export function useMode() { const squarePolygon = turf.polygon([square]) + // console.log('turfTrestlePolygon', turfTrestlePolygon) + // console.log('squarePolygon', squarePolygon) + + const areaSize = turf.area(turfTrestlePolygon) + + // console.log('areaSize', areaSize) + const objSize = turf.area(squarePolygon) + + // console.log('objSize', objSize) + + const maxObject = Math.floor(areaSize / objSize) + + // console.log('maxObjectSize', maxObject) + const disjointFromTrestle = turf.booleanContains(turfTrestlePolygon, squarePolygon) || turf.booleanWithin(squarePolygon, turfTrestlePolygon) if (disjointFromTrestle) { @@ -5185,8 +5162,6 @@ export function useMode() { lockScalingY: true, // Y 축 크기 조정 잠금 opacity: 0.8, parentId: trestle.parentId, - lineCol: col, - lineRow: row, }) canvas?.add(fabricPolygon) drawCellsArray.push(fabricPolygon) diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js index a7bcb0e2..c7ccfece 100644 --- a/src/util/qpolygon-utils.js +++ b/src/util/qpolygon-utils.js @@ -3,6 +3,7 @@ import { QLine } from '@/components/fabric/QLine' import { calculateIntersection, distanceBetweenPoints, findClosestPoint, getDirectionByPoint } from '@/util/canvas-util' import { QPolygon } from '@/components/fabric/QPolygon' import * as turf from '@turf/turf' +import { LINE_TYPE } from '@/common/common' const TWO_PI = Math.PI * 2 @@ -12,7 +13,7 @@ export const defineQPloygon = () => { } } -export const drawHelpLineInHexagon = (polygon, chon) => { +export const drawHelpLineInHexagon = (polygon, pitch) => { const centerLines = drawCenterLines(polygon) let helpLines = [] @@ -1191,26 +1192,33 @@ function calculateAngleBetweenLines(line1, line2) { return (angleInRadians * 180) / Math.PI } -export const drawHippedRoof = (polygon, chon) => { - const hasNonParallelLines = polygon.lines.filter((line) => line.x1 !== line.x2 && line.y1 !== line.y2) +export const drawRidgeRoof = (roofId, pitch, canvas) => { + console.log('drawRidgeRoof : ', roofId) + const roof = canvas?.getObjects().find((object) => object.id === roofId) + const hasNonParallelLines = roof.lines.filter((line) => line.x1 !== line.x2 && line.y1 !== line.y2) if (hasNonParallelLines.length > 0) { alert('대각선이 존재합니다.') return } - drawRidgeRoof(polygon, chon) - drawHips(polygon) - connectLinePoint(polygon) + drawRidge(roof, canvas) + drawHips(roof, canvas) + connectLinePoint(roof, canvas) + modifyRidge(roof, canvas) } /** + * 마루가 존재하면 그린다. 마루는 지붕의 중간에 위치한다. * - * @param polygon - * @param chon + * @param roof + * @param canvas */ -const drawRidgeRoof = (polygon, chon) => { - const walls = polygon.wall.lines // 외벽의 라인 - const roofs = polygon.lines // 지붕의 라인 +const drawRidge = (roof, canvas) => { + const walls = canvas?.getObjects().find((object) => object.name === 'wall' && object.attributes.roofId === roof.id).lines // 외벽의 라인 + const roofs = roof.lines // 지붕의 라인 let ridgeRoof = [] + console.log('drawRidge : ', roof.ridges) + console.log('walls : ', walls) + console.log('roofs : ', roofs) roofs.forEach((currentRoof, index) => { let prevRoof, @@ -1228,8 +1236,11 @@ const drawRidgeRoof = (polygon, chon) => { // 지붕의 길이가 짧은 순으로 정렬 ridgeRoof.sort((a, b) => a.length - b.length) + console.log('ridgeRoof : ', ridgeRoof) + ridgeRoof.forEach((item) => { - if (getMaxRidge(roofs.length) > polygon.ridges.length) { + console.log(getMaxRidge(roofs.length), roof.ridges.length) + if (getMaxRidge(roofs.length) > roof.ridges.length) { let index = item.index, beforePrevRoof, prevRoof, @@ -1463,37 +1474,38 @@ const drawRidgeRoof = (polygon, chon) => { } } } - const currentWall = walls[index] - if (currentWall.attributes.type === 'gable') { - if (currentRoof.x1 === currentRoof.x2) { - startXPoint = currentRoof.x1 - } - if (currentRoof.y1 === currentRoof.y2) { - startYPoint = currentRoof.y1 - } - } + console.log('startXPoint', startXPoint, 'startYPoint', startYPoint, 'endXPoint', endXPoint, 'endYPoint', endYPoint) // 마루 그리기 if (startXPoint !== undefined && startYPoint !== undefined && endXPoint !== undefined && endYPoint !== undefined) { const ridge = new QLine( [Math.min(startXPoint, endXPoint), Math.min(startYPoint, endYPoint), Math.max(startXPoint, endXPoint), Math.max(startYPoint, endYPoint)], { - fontSize: polygon.fontSize, + fontSize: roof.fontSize, stroke: 'blue', strokeWidth: 1, name: 'ridgeLine', + attributes: { roofId: roof.id }, }, ) - polygon.canvas.add(ridge) - polygon.ridges.push(ridge) - polygon.innerLines.push(ridge) + canvas.add(ridge) + roof.ridges.push(ridge) + roof.innerLines.push(ridge) + console.log('currentRoof', currentRoof.x1, currentRoof.y1) + console.log('startXPoint', startXPoint, 'startYPoint', startYPoint, 'endXPoint', endXPoint, 'endYPoint', endYPoint) + + const distance = (x1, y1, x2, y2) => Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)) + const dist1 = distance(startXPoint, startYPoint, currentRoof.x1, currentRoof.y1) + const dist2 = distance(endXPoint, endYPoint, currentRoof.x1, currentRoof.y1) + + currentRoof.attributes.ridgeCoordinate = { x1: dist1 < dist2 ? startXPoint : endXPoint, y1: dist1 < dist2 ? startYPoint : endYPoint } } } }) //겹쳐지는 마루는 하나로 합침 - polygon.ridges.forEach((ridge, index) => { - polygon.ridges + roof.ridges.forEach((ridge, index) => { + roof.ridges .filter((ridge2) => !(ridge.x1 === ridge2.x1 && ridge.y1 === ridge2.y1 && ridge.x2 === ridge2.x2 && ridge.y2 === ridge2.y2)) .forEach((ridge2) => { let overlap = segmentsOverlap(ridge, ridge2) @@ -1503,22 +1515,21 @@ const drawRidgeRoof = (polygon, chon) => { let y1 = Math.min(ridge.y1, ridge2.y1, ridge.y2, ridge2.y2) let y2 = Math.max(ridge.y1, ridge2.y1, ridge.y2, ridge2.y2) const newRidge = new QLine([x1, y1, x2, y2], { - fontSize: polygon.fontSize, + fontSize: roof.fontSize, stroke: 'blue', strokeWidth: 1, + name: 'ridgeLine', + attributes: { roofId: roof.id }, }) - polygon.canvas.remove(ridge) - polygon.canvas.remove(ridge2) - polygon.ridges = polygon.ridges.filter((r) => !(ridge.x1 === r.x1 && ridge.y1 === r.y1 && ridge.x2 === r.x2 && ridge.y2 === r.y2)) - polygon.ridges = polygon.ridges.filter((r) => !(ridge2.x1 === r.x1 && ridge2.y1 === r.y1 && ridge2.x2 === r.x2 && ridge2.y2 === r.y2)) - polygon.innerLines = polygon.innerLines.filter((r) => !(ridge.x1 === r.x1 && ridge.y1 === r.y1 && ridge.x2 === r.x2 && ridge.y2 === r.y2)) - polygon.innerLines = polygon.innerLines.filter( - (r) => !(ridge2.x1 === r.x1 && ridge2.y1 === r.y1 && ridge2.x2 === r.x2 && ridge2.y2 === r.y2), - ) - - polygon.canvas.add(newRidge) - polygon.ridges.push(newRidge) - polygon.innerLines.push(newRidge) + roof.canvas.remove(ridge) + roof.canvas.remove(ridge2) + roof.ridges = roof.ridges.filter((r) => !(ridge.x1 === r.x1 && ridge.y1 === r.y1 && ridge.x2 === r.x2 && ridge.y2 === r.y2)) + roof.ridges = roof.ridges.filter((r) => !(ridge2.x1 === r.x1 && ridge2.y1 === r.y1 && ridge2.x2 === r.x2 && ridge2.y2 === r.y2)) + roof.innerLines = roof.innerLines.filter((r) => !(ridge.x1 === r.x1 && ridge.y1 === r.y1 && ridge.x2 === r.x2 && ridge.y2 === r.y2)) + roof.innerLines = roof.innerLines.filter((r) => !(ridge2.x1 === r.x1 && ridge2.y1 === r.y1 && ridge2.x2 === r.x2 && ridge2.y2 === r.y2)) + canvas.add(newRidge) + roof.ridges.push(newRidge) + roof.innerLines.push(newRidge) } }) }) @@ -1565,720 +1576,151 @@ const segmentsOverlap = (line1, line2) => { return false } -const drawHips = (polygon) => { - /* - 마루에서 시작되는 hip을 먼저 그립니다. - */ - polygon.ridges.forEach((ridge) => { - let leftTop, rightTop, leftBottom, rightBottom - if (ridge.y1 === ridge.y2) { - //왼쪽 좌표 기준 225, 315도 방향 라인확인 - leftTop = polygon.lines - .filter((line) => line.x1 < ridge.x1 && line.y1 < ridge.y1 && Math.abs(line.x1 - ridge.x1) === Math.abs(line.y1 - ridge.y1)) - .reduce((prev, current) => { - if (prev === undefined) { - return current - } else { - return Math.min(ridge.x1 - current.x1) < Math.min(ridge.x1 - prev.x1) ? current : prev - } - }, undefined) +/** + * 추녀마루를 그린다. + * @param roof + * @param canvas + */ +const drawHips = (roof, canvas) => { + console.log('drawHips roofs : ', roof) - leftBottom = polygon.lines - .filter((line) => line.x1 < ridge.x1 && line.y1 > ridge.y1 && Math.abs(line.x1 - ridge.x1) === Math.abs(line.y1 - ridge.y1)) - .reduce((prev, current) => { - if (prev === undefined) { - return current - } else { - return Math.min(ridge.x1 - current.x1) < Math.min(ridge.x1 - prev.x1) ? current : prev - } - }, undefined) + const roofLines = roof.lines + const ridgeLines = canvas?.getObjects().filter((object) => object.name === 'ridgeLine' && object.attributes.roofId === roof.id) - //오른쪽 좌표 기준 45, 135도 방향 라인확인 - rightTop = polygon.lines - .filter((line) => line.x1 > ridge.x2 && line.y1 < ridge.y2 && Math.abs(line.x1 - ridge.x2) === Math.abs(line.y1 - ridge.y2)) - .reduce((prev, current) => { - if (prev === undefined) { - return current - } else { - return Math.min(current.x1 - ridge.x2) < Math.min(prev.x1 - ridge.x2) ? current : prev - } - }, undefined) - - rightBottom = polygon.lines - .filter((line) => line.x1 > ridge.x2 && line.y1 > ridge.y2 && Math.abs(line.x1 - ridge.x2) === Math.abs(line.y1 - ridge.y2)) - .reduce((prev, current) => { - if (prev === undefined) { - return current - } else { - return Math.min(current.x1 - ridge.x2) < Math.min(prev.x1 - ridge.x2) ? current : prev - } - }, undefined) - if (leftTop !== undefined) { - let isRidgePointOnLine = false - polygon.ridges.forEach((r) => { - if ( - r.x1 < ridge.x1 && - r.y1 < ridge.y1 && - (r.y1 - rightTop.y1) * (ridge.x1 - rightTop.x1) === (r.x1 - rightTop.x1) * (ridge.y1 - rightTop.y1) - ) { - isRidgePointOnLine = true - } - if ( - r.x2 < ridge.x1 && - r.y2 < ridge.y1 && - (r.y2 - rightTop.y1) * (ridge.x1 - rightTop.x1) === (r.x2 - rightTop.x1) * (ridge.y1 - rightTop.y1) - ) { - isRidgePointOnLine = true - } - }) - if (!isRidgePointOnLine) { - const hip = new QLine([leftTop.x1, leftTop.y1, ridge.x1, ridge.y1], { - fontSize: polygon.fontSize, - stroke: 'red', - strokeWidth: 1, - name: 'hipLine', - }) - polygon.canvas.add(hip) - polygon.hips.push(hip) - polygon.innerLines.push(hip) - } - } - if (leftBottom !== undefined) { - let isRidgePointOnLine = false - polygon.ridges.forEach((r) => { - if ( - r.x1 < ridge.x1 && - r.y1 > ridge.y1 && - (r.y1 - rightTop.y1) * (ridge.x1 - rightTop.x1) === (r.x1 - rightTop.x1) * (ridge.y1 - rightTop.y1) - ) { - isRidgePointOnLine = true - } - if ( - r.x2 < ridge.x1 && - r.y2 > ridge.y1 && - (r.y2 - rightTop.y1) * (ridge.x1 - rightTop.x1) === (r.x2 - rightTop.x1) * (ridge.y1 - rightTop.y1) - ) { - isRidgePointOnLine = true - } - }) - if (!isRidgePointOnLine) { - const hip = new QLine([leftBottom.x1, leftBottom.y1, ridge.x1, ridge.y1], { - fontSize: polygon.fontSize, - stroke: 'red', - strokeWidth: 1, - name: 'hipLine', - }) - polygon.canvas.add(hip) - polygon.hips.push(hip) - polygon.innerLines.push(hip) - } - } - if (rightTop !== undefined) { - let isRidgePointOnLine = false - polygon.ridges.forEach((r) => { - if ( - r.x1 > ridge.x2 && - r.y1 < ridge.y2 && - (r.y1 - rightTop.y1) * (ridge.x2 - rightTop.x1) === (r.x1 - rightTop.x1) * (ridge.y2 - rightTop.y1) - ) { - isRidgePointOnLine = true - } - if ( - r.x2 > ridge.x2 && - r.y2 < ridge.y2 && - (r.y2 - rightTop.y1) * (ridge.x2 - rightTop.x1) === (r.x2 - rightTop.x1) * (ridge.y2 - rightTop.y1) - ) { - isRidgePointOnLine = true - } - }) - if (!isRidgePointOnLine) { - const hip = new QLine([rightTop.x1, rightTop.y1, ridge.x2, ridge.y2], { - fontSize: polygon.fontSize, - stroke: 'red', - strokeWidth: 1, - name: 'hipLine', - }) - polygon.canvas.add(hip) - polygon.hips.push(hip) - polygon.innerLines.push(hip) - } - } - if (rightBottom !== undefined) { - let isRidgePointOnLine = false - polygon.ridges.forEach((r) => { - if ( - r.x1 > ridge.x2 && - r.y1 > ridge.y2 && - (r.y1 - rightBottom.y1) * (ridge.x2 - rightBottom.x1) === (r.x1 - rightBottom.x1) * (ridge.y2 - rightBottom.y1) - ) { - isRidgePointOnLine = true - } - if ( - r.x2 > ridge.x2 && - r.y2 > ridge.y2 && - (r.y2 - rightBottom.y1) * (ridge.x2 - rightBottom.x1) === (r.x2 - rightBottom.x1) * (ridge.y2 - rightBottom.y1) - ) { - isRidgePointOnLine = true - } - }) - if (!isRidgePointOnLine) { - const hip = new QLine([rightBottom.x1, rightBottom.y1, ridge.x2, ridge.y2], { - fontSize: polygon.fontSize, - stroke: 'red', - strokeWidth: 1, - name: 'hipLine', - }) - polygon.canvas.add(hip) - polygon.hips.push(hip) - polygon.innerLines.push(hip) - } - } - } - if (ridge.x1 === ridge.x2) { - //위쪽 좌표 기준 45, 315도 방향 라인확인 - leftTop = polygon.lines - .filter((line) => line.x1 < ridge.x1 && line.y1 < ridge.y1 && Math.abs(line.x1 - ridge.x1) === Math.abs(line.y1 - ridge.y1)) - .reduce((prev, current) => { - if (prev === undefined) { - return current - } else { - return Math.min(ridge.y1 - current.y1) < Math.min(ridge.y1 - prev.y1) ? current : prev - } - }, undefined) - - rightTop = polygon.lines - .filter((line) => line.x1 > ridge.x1 && line.y1 < ridge.y1 && Math.abs(line.x1 - ridge.x1) === Math.abs(line.y1 - ridge.y1)) - .reduce((prev, current) => { - if (prev === undefined) { - return current - } else { - return Math.min(ridge.y1 - current.y1) < Math.min(ridge.y1 - prev.y1) ? current : prev - } - }, undefined) - - //아래쪽 좌표 기준 135, 225도 방향 라인확인 - leftBottom = polygon.lines - .filter((line) => line.x1 < ridge.x2 && line.y1 > ridge.y2 && Math.abs(line.x1 - ridge.x2) === Math.abs(line.y1 - ridge.y2)) - .reduce((prev, current) => { - if (prev === undefined) { - return current - } else { - return Math.min(current.y1 - ridge.y2) < Math.min(prev.y1 - ridge.y2) ? current : prev - } - }, undefined) - - rightBottom = polygon.lines - .filter((line) => line.x1 > ridge.x2 && line.y1 > ridge.y2 && Math.abs(line.x1 - ridge.x2) === Math.abs(line.y1 - ridge.y2)) - .reduce((prev, current) => { - if (prev === undefined) { - return current - } else { - return Math.min(current.y1 - ridge.y2) < Math.min(prev.y1 - ridge.y2) ? current : prev - } - }, undefined) - - if (leftTop !== undefined) { - let isRidgePointOnLine = false - polygon.ridges.forEach((r) => { - if (r.x1 < ridge.x1 && r.y1 < ridge.y1) { - if ((r.y1 - leftTop.y1) * (ridge.x1 - leftTop.x1) === (r.x1 - leftTop.x1) * (ridge.y1 - leftTop.y1)) { - isRidgePointOnLine = true - } - } - if (r.x2 < ridge.x1 && r.y2 < ridge.y1) { - if ((r.y2 - leftTop.y1) * (ridge.x1 - leftTop.x1) === (r.x2 - leftTop.x1) * (ridge.y1 - leftTop.y1)) { - isRidgePointOnLine = true - } - } - }) - if (!isRidgePointOnLine) { - const hip = new QLine([leftTop.x1, leftTop.y1, ridge.x1, ridge.y1], { - fontSize: polygon.fontSize, - stroke: 'red', - strokeWidth: 1, - name: 'hipLine', - }) - polygon.canvas.add(hip) - polygon.hips.push(hip) - polygon.innerLines.push(hip) - } - } - if (rightTop !== undefined) { - let isRidgePointOnLine = false - polygon.ridges.forEach((r) => { - if (r.x1 > ridge.x1 && r.y1 < ridge.y1) { - if ((r.y1 - rightTop.y1) * (ridge.x1 - rightTop.x1) === (r.x1 - rightTop.x1) * (ridge.y1 - rightTop.y1)) { - isRidgePointOnLine = true - } - } - if (r.x2 > ridge.x1 && r.y2 < ridge.y1) { - if ((r.y2 - rightTop.y1) * (ridge.x1 - rightTop.x1) === (r.x2 - rightTop.x1) * (ridge.y1 - rightTop.y1)) { - isRidgePointOnLine = true - } - } - }) - if (!isRidgePointOnLine) { - const hip = new QLine([rightTop.x1, rightTop.y1, ridge.x1, ridge.y1], { - fontSize: polygon.fontSize, - stroke: 'red', - strokeWidth: 1, - name: 'hipLine', - }) - polygon.canvas.add(hip) - polygon.hips.push(hip) - polygon.innerLines.push(hip) - } - } - if (leftBottom !== undefined) { - let isRidgePointOnLine = false - polygon.ridges.forEach((r) => { - if ( - r.x1 < ridge.x2 && - r.y1 > ridge.y2 && - (r.y1 - leftBottom.y1) * (ridge.x2 - leftBottom.x1) === (r.x1 - leftBottom.x1) * (ridge.y2 - leftBottom.y1) - ) { - isRidgePointOnLine = true - } - if ( - r.x2 < ridge.x2 && - r.y2 > ridge.y2 && - (r.y2 - leftBottom.y1) * (ridge.x2 - leftBottom.x1) === (r.x2 - leftBottom.x1) * (ridge.y2 - leftBottom.y1) - ) { - isRidgePointOnLine = true - } - }) - if (!isRidgePointOnLine) { - const hip = new QLine([leftBottom.x1, leftBottom.y1, ridge.x2, ridge.y2], { - fontSize: polygon.fontSize, - stroke: 'red', - strokeWidth: 1, - name: 'hipLine', - }) - polygon.canvas.add(hip) - polygon.hips.push(hip) - polygon.innerLines.push(hip) - } - } - if (rightBottom !== undefined) { - let isRidgePointOnLine = false - polygon.ridges.forEach((r) => { - if ( - r.x1 < ridge.x2 && - r.y1 > ridge.y2 && - (r.y1 - rightBottom.y1) * (ridge.x2 - rightBottom.x1) === (r.x1 - rightBottom.x1) * (ridge.y2 - rightBottom.y1) - ) { - isRidgePointOnLine = true - } - if ( - r.x2 < ridge.x2 && - r.y2 > ridge.y2 && - (r.y2 - rightBottom.y1) * (ridge.x2 - rightBottom.x1) === (r.x2 - rightBottom.x1) * (ridge.y2 - rightBottom.y1) - ) { - isRidgePointOnLine = true - } - }) - if (!isRidgePointOnLine) { - const hip = new QLine([rightBottom.x1, rightBottom.y1, ridge.x2, ridge.y2], { - fontSize: polygon.fontSize, - stroke: 'red', - strokeWidth: 1, - name: 'hipLine', - }) - polygon.canvas.add(hip) - polygon.hips.push(hip) - polygon.innerLines.push(hip) - } - } - } - }) - - // 가장 가까운 마루를 확인하여 그릴 수 있는 라인이 존재하면 먼저 그린다. - let prevLine, currentLine, nextLine - polygon.lines.forEach((value, index) => { - if (index === 0) { - prevLine = polygon.lines[polygon.lines.length - 1] - } else { - prevLine = polygon.lines[index - 1] - } - currentLine = polygon.lines[index] - - if (index === polygon.lines.length - 1) { - nextLine = polygon.lines[0] - } else if (index === polygon.lines.length) { - nextLine = polygon.lines[1] - } else { - nextLine = polygon.lines[index + 1] - } - - if (!isAlreadyHip(polygon, currentLine)) { - let dVector = getDirectionForDegree(prevLine, currentLine) - let nearRidge - - switch (dVector) { - case 45: - nearRidge = polygon.ridges - .filter( - (ridge) => - ((currentLine.x1 < ridge.x1 && currentLine.y1 > ridge.y1) || (currentLine.x1 < ridge.x2 && currentLine.y1 > ridge.y2)) && - (Math.abs(currentLine.x1 - ridge.x1) === Math.abs(currentLine.y1 - ridge.y1) || - Math.abs(currentLine.x1 - ridge.x2) === Math.abs(currentLine.y1 - ridge.y2)), - ) - .reduce((prev, current) => { - if (prev !== undefined) { - if ( - currentLine.x1 < current.x1 && - currentLine.y1 > current.y1 && - Math.abs(currentLine.x1 - current.x1) === Math.abs(currentLine.y1 - current.y1) - ) { - return Math.min(Math.abs(current.x1 - currentLine.x1)) < Math.min(Math.abs(prev.x - currentLine.x1)) - ? { - x: current.x1, - y: current.y1, - } - : prev - } else if ( - currentLine.x1 < current.x2 && - currentLine.y1 > current.y2 && - Math.abs(currentLine.x1 - current.x2) === Math.abs(currentLine.y1 - current.y2) - ) { - return Math.min(Math.abs(current.x2 - currentLine.x1)) < Math.min(Math.abs(prev.x - currentLine.x1)) - ? { - x: current.x2, - y: current.y2, - } - : prev - } else { - return prev - } - } else { - if ( - currentLine.x1 < current.x1 && - currentLine.y1 > current.y1 && - Math.abs(currentLine.x1 - current.x1) === Math.abs(currentLine.y1 - current.y1) - ) { - return { x: current.x1, y: current.y1 } - } else if ( - currentLine.x1 < current.x2 && - currentLine.y1 > current.y2 && - Math.abs(currentLine.x1 - current.x2) === Math.abs(currentLine.y1 - current.y2) - ) { - return { x: current.x2, y: current.y2 } - } else { - return undefined - } - } - }, undefined) - break - case 135: - nearRidge = polygon.ridges - .filter( - (ridge) => - ((currentLine.x1 < ridge.x1 && currentLine.y1 < ridge.y1) || (currentLine.x1 < ridge.x2 && currentLine.y1 < ridge.y2)) && - (Math.abs(currentLine.x1 - ridge.x1) === Math.abs(currentLine.y1 - ridge.y1) || - Math.abs(currentLine.x1 - ridge.x2) === Math.abs(currentLine.y1 - ridge.y2)), - ) - .reduce((prev, current) => { - if (prev !== undefined) { - if ( - currentLine.x1 < current.x1 && - currentLine.y1 < current.y1 && - Math.abs(currentLine.x1 - current.x1) === Math.abs(currentLine.y1 - current.y1) - ) { - return Math.min(Math.abs(current.x1 - currentLine.x1)) < Math.min(Math.abs(prev.x - currentLine.x1)) - ? { - x: current.x1, - y: current.y1, - } - : prev - } else if ( - currentLine.x1 < current.x2 && - currentLine.y1 < current.y2 && - Math.abs(currentLine.x1 - current.x2) === Math.abs(currentLine.y1 - current.y2) - ) { - return Math.min(Math.abs(current.x1 - currentLine.x1)) < Math.min(Math.abs(prev.x - currentLine.x1)) - ? { - x: current.x2, - y: current.y2, - } - : prev - } else { - return prev - } - } else { - if ( - currentLine.x1 < current.x1 && - currentLine.y1 < current.y1 && - Math.abs(currentLine.x1 - current.x1) === Math.abs(currentLine.y1 - current.y1) - ) { - return { x: current.x1, y: current.y1 } - } else if ( - currentLine.x1 < current.x2 && - currentLine.y1 < current.y2 && - Math.abs(currentLine.x1 - current.x2) === Math.abs(currentLine.y1 - current.y2) - ) { - return { x: current.x2, y: current.y2 } - } else { - return undefined - } - } - }, undefined) - break - case 225: - nearRidge = polygon.ridges - .filter( - (ridge) => - ((currentLine.x1 > ridge.x1 && currentLine.y1 < ridge.y1) || (currentLine.x1 > ridge.x2 && currentLine.y1 < ridge.y2)) && - (Math.abs(currentLine.x1 - ridge.x1) === Math.abs(currentLine.y1 - ridge.y1) || - Math.abs(currentLine.x1 - ridge.x2) === Math.abs(currentLine.y1 - ridge.y2)), - ) - .reduce((prev, current) => { - if (prev !== undefined) { - if ( - currentLine.x1 > current.x1 && - currentLine.y1 < current.y1 && - Math.abs(currentLine.x1 - current.x1) === Math.abs(currentLine.y1 - current.y1) - ) { - return Math.min(Math.abs(current.x1 - currentLine.x1)) < Math.min(Math.abs(prev.x - currentLine.x1)) - ? { - x: current.x1, - y: current.y1, - } - : prev - } else if ( - currentLine.x1 > current.x2 && - currentLine.y1 < current.y2 && - Math.abs(currentLine.x1 - current.x2) === Math.abs(currentLine.y1 - current.y2) - ) { - return Math.min(Math.abs(current.x1 - currentLine.x1)) < Math.min(Math.abs(prev.x - currentLine.x1)) - ? { - x: current.x2, - y: current.y2, - } - : prev - } else { - return prev - } - } else { - if ( - currentLine.x1 > current.x1 && - currentLine.y1 < current.y1 && - Math.abs(currentLine.x1 - current.x1) === Math.abs(currentLine.y1 - current.y1) - ) { - return { x: current.x1, y: current.y1 } - } else if ( - currentLine.x1 > current.x2 && - currentLine.y1 < current.y2 && - Math.abs(currentLine.x1 - current.x2) === Math.abs(currentLine.y1 - current.y2) - ) { - return { x: current.x2, y: current.y2 } - } else { - return undefined - } - } - }, undefined) - break - case 315: - nearRidge = polygon.ridges - .filter( - (ridge) => - ((currentLine.x1 > ridge.x1 && currentLine.y1 > ridge.y1) || (currentLine.x1 > ridge.x2 && currentLine.y1 > ridge.y2)) && - (Math.abs(currentLine.x1 - ridge.x1) === Math.abs(currentLine.y1 - ridge.y1) || - Math.abs(currentLine.x1 - ridge.x2) === Math.abs(currentLine.y1 - ridge.y2)), - ) - .reduce((prev, current) => { - if (prev !== undefined) { - if ( - currentLine.x1 > current.x1 && - currentLine.y1 > current.y1 && - Math.abs(currentLine.x1 - current.x1) === Math.abs(currentLine.y1 - current.y1) - ) { - return Math.min(Math.abs(current.x1 - currentLine.x1)) < Math.min(Math.abs(prev.x - currentLine.x1)) - ? { - x: current.x1, - y: current.y1, - } - : prev - } else if ( - currentLine.x1 > current.x2 && - currentLine.y1 > current.y2 && - Math.abs(currentLine.x1 - current.x2) === Math.abs(currentLine.y1 - current.y2) - ) { - return Math.min(Math.abs(current.x1 - currentLine.x1)) < Math.min(Math.abs(prev.x - currentLine.x1)) - ? { - x: current.x2, - y: current.y2, - } - : prev - } else { - return prev - } - } else { - if ( - currentLine.x1 > current.x1 && - currentLine.y1 > current.y1 && - Math.abs(currentLine.x1 - current.x1) === Math.abs(currentLine.y1 - current.y1) - ) { - return { x: current.x1, y: current.y1 } - } else if ( - currentLine.x1 > current.x2 && - currentLine.y1 > current.y2 && - Math.abs(currentLine.x1 - current.x2) === Math.abs(currentLine.y1 - current.y2) - ) { - return { x: current.x2, y: current.y2 } - } else { - return undefined - } - } - }, undefined) - break - } - - if (nearRidge !== undefined) { - let endXPoint, endYPoint - let minX, maxX, minY, maxY - - switch (dVector) { - case 45: - endXPoint = nearRidge.x - endYPoint = nearRidge.y - minX = Math.min(currentLine.x1, nearRidge.x) - minY = Math.min(currentLine.y1, nearRidge.y) - maxX = Math.max(currentLine.x1, nearRidge.x) - maxY = Math.max(currentLine.y1, nearRidge.y) - break - case 135: - endXPoint = nearRidge.x - endYPoint = nearRidge.y - minX = Math.min(currentLine.x1, nearRidge.x) - minY = Math.min(currentLine.y1, nearRidge.y) - maxX = Math.max(currentLine.x1, nearRidge.x) - maxY = Math.max(currentLine.y1, nearRidge.y) - break - case 225: - endXPoint = nearRidge.x - endYPoint = nearRidge.y - minX = Math.min(currentLine.x1, nearRidge.x) - minY = Math.min(currentLine.y1, nearRidge.y) - maxX = Math.max(currentLine.x1, nearRidge.x) - maxY = Math.max(currentLine.y1, nearRidge.y) - break - case 315: - endXPoint = nearRidge.x - endYPoint = nearRidge.y - minX = Math.min(currentLine.x1, nearRidge.x) - minY = Math.min(currentLine.y1, nearRidge.y) - maxX = Math.max(currentLine.x1, nearRidge.x) - maxY = Math.max(currentLine.y1, nearRidge.y) - break - } - - let lineCoordinate = [ - { x: minX, y: minY }, - { x: minX, y: maxY }, - { x: maxX, y: maxY }, - { x: maxX, y: minY }, - ] - - let innerPoint = polygon.lines.filter((line) => { - if (getPointInPolygon(lineCoordinate, { x: line.x1, y: line.y1 })) { - return line - } - }) - - if (innerPoint <= 0) { - const hip = new QLine([currentLine.x1, currentLine.y1, endXPoint, endYPoint], { - fontSize: polygon.fontSize, - stroke: 'red', - strokeWidth: 1, - name: 'hipLine', - }) - polygon.canvas.add(hip) - polygon.hips.push(hip) - polygon.innerLines.push(hip) - } - } - } - }) - - // 마루와 연결되지 않은 hip을 그린다. - /*polygon.lines.forEach((line, index) => { - if (!isAlreadyHip(polygon, line)) { - console.log(' 확인 : ', line) - let prevLine, currentLine, nextLine - if (index === 0) { - prevLine = polygon.lines[polygon.lines.length - 1] - } else { - prevLine = polygon.lines[index - 1] - } - currentLine = polygon.lines[index] - - if (index === polygon.lines.length - 1) { - nextLine = polygon.lines[0] - } else if (index === polygon.lines.length) { - nextLine = polygon.lines[1] - } else { - nextLine = polygon.lines[index + 1] - } - - let endXPoint, endYPoint - let dVector = getDirectionForDegree(prevLine, currentLine) - - let minX = Math.min(currentLine.x1, currentLine.x2, prevLine.x1, nextLine.x2) - let maxX = Math.max(currentLine.x1, currentLine.x2, prevLine.x1, nextLine.x2) - let minY = Math.min(currentLine.y1, currentLine.y2, prevLine.y1, nextLine.y2) - let maxY = Math.max(currentLine.y1, currentLine.y2, prevLine.y1, nextLine.y2) - - let lineCoordinate = [ - { x: minX, y: minY }, - { x: minX, y: maxY }, - { x: maxX, y: maxY }, - { x: maxX, y: minY }, - ] - - let acrossLine = getAcrossLine(polygon, currentLine, dVector) - let hypotenuse, adjacent - console.log(acrossLine) - - if (getLineDirection(prevLine) === getLineDirection(nextLine)) { - hypotenuse = Math.round(getRoofHypotenuse(Math.abs(currentLine.x1 - acrossLine.x1) / 2)) - } else { - hypotenuse = Math.min( - Math.round(getRoofHypotenuse(currentLine.length / 2)), - Math.round(getRoofHypotenuse(Math.abs(currentLine.x1 - acrossLine.x1) / 2)), - ) - } - adjacent = getAdjacent(hypotenuse) - - switch (dVector) { - case 45: - endXPoint = currentLine.x1 + adjacent - endYPoint = currentLine.y1 - adjacent - break - case 135: - endXPoint = currentLine.x1 + adjacent - endYPoint = currentLine.y1 + adjacent - break - case 225: - endXPoint = currentLine.x1 - adjacent - endYPoint = currentLine.y1 + adjacent - break - case 315: - endXPoint = currentLine.x1 - adjacent - endYPoint = currentLine.y1 - adjacent - break - } - - const hip = new QLine([currentLine.x1, currentLine.y1, endXPoint, endYPoint], { - fontSize: polygon.fontSize, + //마루에서 시작되는 hip을 먼저 그립니다. + roofLines + .filter((roof) => roof.attributes.type === LINE_TYPE.WALLLINE.EAVES && roof.attributes.ridgeCoordinate !== undefined) + .forEach((currentRoof, index) => { + const ridgeCoordinate = currentRoof.attributes.ridgeCoordinate + const hip1 = new QLine([currentRoof.x1, currentRoof.y1, ridgeCoordinate.x1, ridgeCoordinate.y1], { + fontSize: roof.fontSize, stroke: 'red', strokeWidth: 1, name: 'hipLine', + attributes: { roofId: roof.id, currentRoofId: currentRoof.id, planeSize: currentRoof.length, actualSize: currentRoof.length }, }) - polygon.canvas.add(hip) - polygon.hips.push(hip) - polygon.innerLines.push(hip) + canvas.add(hip1) + roof.hips.push(hip1) + roof.innerLines.push(hip1) + + const hip2 = new QLine([currentRoof.x2, currentRoof.y2, ridgeCoordinate.x1, ridgeCoordinate.y1], { + fontSize: roof.fontSize, + stroke: 'red', + strokeWidth: 1, + name: 'hipLine', + attributes: { roofId: roof.id, currentRoofId: currentRoof.id, planeSize: currentRoof.length, actualSize: currentRoof.length }, + }) + canvas.add(hip2) + roof.hips.push(hip2) + roof.innerLines.push(hip2) + }) + + const hipLines = canvas?.getObjects().filter((object) => object.name === 'hipLine' && object.attributes.roofId === roof.id) + + //마루에서 시작되지 않는 hip을 그립니다. + roofLines + .filter((roof) => { + let isHip = false + if (hipLines.some((hip) => hip.x1 === roof.x1 && hip.y1 === roof.y1)) { + isHip = true + } + return !isHip + }) + .forEach((currentRoof) => { + let prevRoof + roofLines.forEach((roof, index) => { + if (roof === currentRoof) { + prevRoof = index === 0 ? roofLines[roofLines.length - 1] : roofLines[index - 1] + } + }) + + // if (currentRoof.attributes.type === LINE_TYPE.WALLLINE.EAVES && prevRoof.attributes.type === LINE_TYPE.WALLLINE.EAVES) { + let ridgePoints = [] + ridgeLines.forEach((ridge) => { + const deltaX1 = ridge.x1 - currentRoof.x1 + const deltaY1 = ridge.y1 - currentRoof.y1 + const deltaX2 = ridge.x2 - currentRoof.x1 + const deltaY2 = ridge.y2 - currentRoof.y1 + + if (Math.abs(deltaY1 / deltaX1) === 1) { + ridgePoints.push({ x: ridge.x1, y: ridge.y1 }) + } + if (Math.abs(deltaY2 / deltaX2) === 1) { + ridgePoints.push({ x: ridge.x2, y: ridge.y2 }) + } + }) + + ridgePoints = ridgePoints.reduce((prev, current) => { + if (prev !== undefined) { + const deltaPrevX = Math.abs(prev.x - currentRoof.x1) + const deltaPrevY = Math.abs(prev.y - currentRoof.y1) + const deltaCurrentX = Math.abs(current.x - currentRoof.x1) + const deltaCurrentY = Math.abs(current.y - currentRoof.y1) + if (deltaPrevX < deltaCurrentX && deltaPrevY < deltaCurrentY) { + return prev + } else { + return current + } + } else { + return current + } + }, undefined) + + if (ridgePoints !== undefined) { + const hip = new QLine([currentRoof.x1, currentRoof.y1, ridgePoints.x, ridgePoints.y], { + fontSize: roof.fontSize, + stroke: 'red', + strokeWidth: 1, + name: 'hipLine', + attributes: { roofId: roof.id, currentRoofId: currentRoof.id, planeSize: currentRoof.length, actualSize: currentRoof.length }, + }) + canvas.add(hip) + roof.hips.push(hip) + roof.innerLines.push(hip) + } + }) +} + +/** + * 라인 사이가 지붕골 인지 확인. + * @param polygon + * @param line1 + * @param line2 + * @returns {boolean} + */ +const checkValley = (polygon, line1, line2) => { + let points = [ + { x: line1.x1, y: line1.y1 }, + { x: line1.x2, y: line1.y2 }, + { x: line2.x1, y: line2.y1 }, + { x: line2.x2, y: line2.y2 }, + ] + + const uniquePointsMap = new Map() + points.forEach((point) => { + const key = `${point.x},${point.y}` + if (!uniquePointsMap.has(key)) { + uniquePointsMap.set(key, point) } - })*/ + }) + points = Array.from(uniquePointsMap.values()) + + const centroidX = points.reduce((acc, point) => acc + point.x, 0) / points.length + const centroidY = points.reduce((acc, point) => acc + point.y, 0) / points.length + + let isValley = false + const pPoints = polygon.points + pPoints.forEach((point, index) => { + let j = (index + 1) % pPoints.length + let xi = pPoints[index].x + polygon.left, + yi = pPoints[index].y + polygon.top + let xj = pPoints[j].x + polygon.left, + yj = pPoints[j].y + polygon.top + + let intersect = yi > centroidY !== yj > centroidY && centroidX < ((xj - xi) * (centroidY - yi)) / (yj - yi) + xi + if (intersect) isValley = !isValley + }) + return isValley } const getPointInPolygon = (polygon, point, isInclude = false) => { @@ -2564,6 +2006,7 @@ const connectLinePoint = (polygon) => { stroke: 'blue', strokeWidth: 1, name: 'ridgeLine', + attributes: { roofId: polygon.id }, }) if (polygon.ridges.filter((r) => newRidge.x1 === r.x1 && newRidge.y1 === r.y1 && newRidge.x2 === r.x2 && newRidge.y2 === r.y2).length === 0) { polygon.canvas.remove(ridge) @@ -2586,6 +2029,99 @@ const connectLinePoint = (polygon) => { console.log('polygon : ', polygon) } +const modifyRidge = (roof, canvas) => { + const roofLines = roof.lines + const ridgeLines = canvas?.getObjects().filter((object) => object.name === 'ridgeLine' && object.attributes.roofId === roof.id) + const hipLines = canvas?.getObjects().filter((object) => object.name === 'hipLine' && object.attributes.roofId === roof.id) + + ridgeLines.forEach((ridge) => { + let ridgeHip1 = hipLines.filter((hip) => hip.x2 === ridge.x1 && hip.y2 === ridge.y1) + let ridgeHip2 = hipLines.filter((hip) => hip.x2 === ridge.x2 && hip.y2 === ridge.y2) + if (ridgeHip1.length >= 2) { + let currentRoof = roof.lines + .filter((roofLine) => roofLine.attributes !== undefined && roofLine.attributes.ridgeCoordinate !== undefined) + .find((roofLine) => roofLine.attributes.ridgeCoordinate.x1 === ridge.x1 && roofLine.attributes.ridgeCoordinate.y1 === ridge.y1) + console.log('1 currentRoof : ', currentRoof) + if (currentRoof === undefined) { + currentRoof = roof.lines.find( + (roofLine) => + (roofLine.x1 === ridgeHip1[0].x1 && + roofLine.y1 === ridgeHip1[0].y1 && + roofLine.x2 === ridgeHip1[1].x1 && + roofLine.y2 === ridgeHip1[1].y1) || + (roofLine.x1 === ridgeHip1[1].x1 && + roofLine.y1 === ridgeHip1[1].y1 && + roofLine.x2 === ridgeHip1[0].x1 && + roofLine.y2 === ridgeHip1[0].y1), + ) + if (currentRoof !== undefined) { + currentRoof.attributes.ridgeCoordinate = { x1: ridge.x1, y1: ridge.y1 } + } + } + console.log('2 currentRoof : ', currentRoof) + if (currentRoof !== undefined) { + switch (currentRoof.attributes.type) { + case LINE_TYPE.WALLLINE.EAVES: + break + case LINE_TYPE.WALLLINE.GABLE: + changeGableRoof(roof.id, currentRoof, canvas) + break + case LINE_TYPE.WALLLINE.HIPANDGABLE: + changeHipAndGableRoof(roof.id, currentRoof, canvas) + break + case LINE_TYPE.WALLLINE.JERKINHEAD: + changeJerkInHeadRoof(roof.id, currentRoof, canvas) + break + case LINE_TYPE.WALLLINE.WALL: + changeWallRoof(roof.id, currentRoof, canvas) + break + } + } + } + if (ridgeHip2.length >= 2) { + let currentRoof = roof.lines + .filter((roofLine) => roofLine.attributes !== undefined && roofLine.attributes.ridgeCoordinate !== undefined) + .find((roofLine) => roofLine.attributes.ridgeCoordinate.x1 === ridge.x2 && roofLine.attributes.ridgeCoordinate.y1 === ridge.y2) + console.log('3 currentRoof : ', currentRoof) + if (currentRoof === undefined) { + currentRoof = roof.lines.find( + (roofLine) => + (roofLine.x1 === ridgeHip2[0].x1 && + roofLine.y1 === ridgeHip2[0].y1 && + roofLine.x2 === ridgeHip2[1].x1 && + roofLine.y2 === ridgeHip2[1].y1) || + (roofLine.x1 === ridgeHip2[1].x1 && + roofLine.y1 === ridgeHip2[1].y1 && + roofLine.x2 === ridgeHip2[0].x1 && + roofLine.y2 === ridgeHip2[0].y1), + ) + if (currentRoof !== undefined) { + currentRoof.attributes.ridgeCoordinate = { x1: ridge.x2, y1: ridge.y2 } + } + } + console.log('4 currentRoof : ', currentRoof) + if (currentRoof !== undefined) { + switch (currentRoof.attributes.type) { + case LINE_TYPE.WALLLINE.EAVES: + break + case LINE_TYPE.WALLLINE.GABLE: + changeGableRoof(roof.id, currentRoof, canvas) + break + case LINE_TYPE.WALLLINE.HIPANDGABLE: + changeHipAndGableRoof(roof.id, currentRoof, canvas) + break + case LINE_TYPE.WALLLINE.JERKINHEAD: + changeJerkInHeadRoof(roof.id, currentRoof, canvas) + break + case LINE_TYPE.WALLLINE.WALL: + changeWallRoof(roof.id, currentRoof, canvas) + break + } + } + } + }) +} + /* 최대 생성 마루 갯수 */ @@ -2593,74 +2129,405 @@ const getMaxRidge = (length) => { return (length - 4) / 2 + 1 } -/* - 두 라인의 사잇각 계산 +/** + * 처마지붕으로 변경 + * @param roofId + * @param currentRoof + * @param canvas */ -const getDirectionForDegree = (line1, line2) => { - let degree = getLineDirection(line1) + getLineDirection(line2) - let vector +export const changeEavesRoof = (roofId, currentRoof, canvas) => {} - switch (degree) { - case 'rb': - vector = 45 - break - case 'br': - vector = 45 - break - case 'lb': - vector = 135 - break - case 'bl': - vector = 135 - break - case 'lt': - vector = 225 - break - case 'tl': - vector = 225 - break - case 'rt': - vector = 315 - break - case 'tr': - vector = 315 - break +/** + * 박공지붕으로 변경 + * @param roofId + * @param currentRoof + * @param canvas + */ +export const changeGableRoof = (roofId, currentRoof, canvas) => { + if (currentRoof.attributes.type === LINE_TYPE.WALLLINE.GABLE) { + const roof = canvas?.getObjects().find((object) => object.name === 'roof' && object.id === roofId) + let hipLines = canvas?.getObjects().filter((object) => object.name === 'hipLine' && object.attributes.roofId === roofId) + let ridgeLines = canvas?.getObjects().filter((object) => object.name === 'ridgeLine' && object.attributes.roofId === roofId) + + ridgeLines = ridgeLines.filter( + (ridge) => + (ridge.x1 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y1 === currentRoof.attributes.ridgeCoordinate.y1) || + (ridge.x2 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y2 === currentRoof.attributes.ridgeCoordinate.y1), + ) + hipLines = hipLines.filter( + (hip) => (hip.x1 === currentRoof.x1 && hip.y1 === currentRoof.y1) || (hip.x1 === currentRoof.x2 && hip.y1 === currentRoof.y2), + ) + let midX = (currentRoof.x1 + currentRoof.x2) / 2 + let midY = (currentRoof.y1 + currentRoof.y2) / 2 + if (ridgeLines !== undefined && ridgeLines.length > 0) { + const ridge = ridgeLines[0] + if (ridge.x1 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y1 === currentRoof.attributes.ridgeCoordinate.y1) { + midX = ridge.x1 + (ridge.x1 - midX) + midY = ridge.y1 - (ridge.y1 - midY) + ridge.set({ + x1: midX, + y1: midY, + x2: ridge.x2, + y2: ridge.y2, + }) + } + if (ridge.x2 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y2 === currentRoof.attributes.ridgeCoordinate.y1) { + midX = ridge.x2 + (ridge.x2 - midX) + midY = ridge.y2 - (ridge.y2 - midY) + ridge.set({ + x1: ridge.x1, + y1: ridge.y1, + x2: midX, + y2: midY, + }) + } + } + + if (hipLines !== undefined && hipLines.length > 0) { + hipLines.forEach((hip) => + hip.set({ + x1: hip.x1, + y1: hip.y1, + x2: midX, + y2: midY, + }), + ) + } else { + let hip1 = new QLine([currentRoof.x1, currentRoof.y1, midX, midY], { + fontSize: roof.fontSize, + stroke: 'red', + strokeWidth: 1, + name: 'hipLine', + attributes: { roofId: roofId, currentRoofId: currentRoof.id, planeSize: currentRoof.length, actualSize: currentRoof.length }, + }) + canvas?.add(hip1) + roof.hips.push(hip1) + roof.innerLines.push(hip1) + + let hip2 = new QLine([currentRoof.x2, currentRoof.y2, midX, midY], { + fontSize: roof.fontSize, + stroke: 'red', + strokeWidth: 1, + name: 'hipLine', + attributes: { roofId: roofId, currentRoofId: currentRoof.id, planeSize: currentRoof.length, actualSize: currentRoof.length }, + }) + canvas?.add(hip2) + roof.hips.push(hip2) + roof.innerLines.push(hip2) + } } - - return vector } -/* - 현재 라인의 방향을 계산 +/** + * 팔작지붕으로 변경 + * @param roofId + * @param currentRoof + * @param canvas */ -const getLineDirection = (line) => { - let x1, x2, y1, y2, xp, yp - x1 = Math.round(line.x1) - x2 = Math.round(line.x2) - y1 = Math.round(line.y1) - y2 = Math.round(line.y2) +export const changeHipAndGableRoof = (roofId, currentRoof, canvas) => { + if (currentRoof.attributes.type === LINE_TYPE.WALLLINE.HIPANDGABLE) { + const roof = canvas?.getObjects().find((object) => object.name === 'roof' && object.id === roofId) + let hipLines = canvas?.getObjects().filter((object) => object.name === 'hipLine' && object.attributes.roofId === roofId) + let ridgeLines = canvas?.getObjects().filter((object) => object.name === 'ridgeLine' && object.attributes.roofId === roofId) - xp = x1 - x2 - yp = y1 - y2 + ridgeLines = ridgeLines.filter( + (ridge) => + (ridge.x1 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y1 === currentRoof.attributes.ridgeCoordinate.y1) || + (ridge.x2 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y2 === currentRoof.attributes.ridgeCoordinate.y1), + ) + hipLines = hipLines.filter( + (hip) => (hip.x1 === currentRoof.x1 && hip.y1 === currentRoof.y1) || (hip.x1 === currentRoof.x2 && hip.y1 === currentRoof.y2), + ) - if (xp === 0) { - if (yp < 0) { - return 'b' - } else { - return 't' + if (ridgeLines.length > 0) { + const ridge = ridgeLines[0] + let midX = (currentRoof.x1 + currentRoof.x2) / 2 + let midY = (currentRoof.y1 + currentRoof.y2) / 2 + let xWidth = currentRoof.attributes.width + let yWidth = currentRoof.attributes.width + let diffX = 0 + let diffY = 0 + + if (ridge.x1 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y1 === currentRoof.attributes.ridgeCoordinate.y1) { + diffX = ridge.x1 - midX + diffY = ridge.y1 - midY + midX = ridge.x1 - diffX + midY = ridge.y1 - diffY + xWidth = diffX === 0 ? 0 : Math.sign(diffX) * xWidth + yWidth = diffY === 0 ? 0 : Math.sign(diffY) * yWidth + ridge.set({ + x1: midX + xWidth, + y1: midY + yWidth, + x2: ridge.x2, + y2: ridge.y2, + }) + + currentRoof.attributes.ridgeCoordinate = { x1: ridge.x1, y1: ridge.y1 } + } + if (ridge.x2 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y2 === currentRoof.attributes.ridgeCoordinate.y1) { + diffX = ridge.x2 - midX + diffY = ridge.y2 - midY + midX = ridge.x2 - diffX + midY = ridge.y2 - diffY + xWidth = diffX === 0 ? 0 : Math.sign(diffX) * xWidth + yWidth = diffY === 0 ? 0 : Math.sign(diffY) * yWidth + ridge.set({ + x1: ridge.x1, + y1: ridge.y1, + x2: midX + xWidth, + y2: midY + yWidth, + }) + + currentRoof.attributes.ridgeCoordinate = { x1: ridge.x2, y1: ridge.y2 } + } + + if (hipLines.length > 0) { + hipLines.forEach((hip) => { + const singX = Math.sign(hip.x1 - currentRoof.attributes.ridgeCoordinate.x1) + const singY = Math.sign(hip.y1 - currentRoof.attributes.ridgeCoordinate.y1) + const alpha = diffX === 0 ? 0 : Math.abs(diffX) - currentRoof.attributes.width + const beta = diffY === 0 ? 0 : Math.abs(diffY) - currentRoof.attributes.width + const hypotenuse = Math.sqrt(Math.pow(alpha, 2) + Math.pow(beta, 2)) + + hip.set({ + x1: hip.x1, + y1: hip.y1, + x2: hip.x2 + singX * hypotenuse, + y2: hip.y2 + singY * hypotenuse, + }) + }) + } + + if (ridgeLines.length > 0 && hipLines.length > 0) { + hipLines.forEach((hip) => { + const hipLine = new QLine([hip.x2, hip.y2, currentRoof.attributes.ridgeCoordinate.x1, currentRoof.attributes.ridgeCoordinate.y1], { + fontSize: roof.fontSize, + stroke: 'red', + strokeWidth: 1, + name: 'gableLine', + }) + canvas?.add(hipLine) + roof.innerLines.push(hipLine) + }) + } } } - if (yp === 0) { - if (xp < 0) { - return 'r' - } else { - return 'l' +} + +/** + * 반절처 지붕으로 변경 + * @param roofId + * @param currentRoof + * @param canvas + */ +export const changeJerkInHeadRoof = (roofId, currentRoof, canvas) => { + if (currentRoof.attributes.type === LINE_TYPE.WALLLINE.JERKINHEAD) { + console.log('roofId : ', roofId) + console.log('currentRoof : ', currentRoof) + const roof = canvas?.getObjects().find((object) => object.name === 'roof' && object.id === roofId) + let hipLines = canvas?.getObjects().filter((object) => object.name === 'hipLine' && object.attributes.roofId === roofId) + let ridgeLines = canvas?.getObjects().filter((object) => object.name === 'ridgeLine' && object.attributes.roofId === roofId) + + ridgeLines = ridgeLines.filter( + (ridge) => + (ridge.x1 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y1 === currentRoof.attributes.ridgeCoordinate.y1) || + (ridge.x2 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y2 === currentRoof.attributes.ridgeCoordinate.y1), + ) + hipLines = hipLines.filter( + (hip) => (hip.x1 === currentRoof.x1 && hip.y1 === currentRoof.y1) || (hip.x1 === currentRoof.x2 && hip.y1 === currentRoof.y2), + ) + + console.log('ridgeLines : ', ridgeLines) + console.log('hipLines : ', hipLines) + + if (ridgeLines.length > 0) { + const ridge = ridgeLines[0] + let midX = (currentRoof.x1 + currentRoof.x2) / 2 + let midY = (currentRoof.y1 + currentRoof.y2) / 2 + let xWidth = currentRoof.attributes.width + let yWidth = currentRoof.attributes.width + if (ridge.x1 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y1 === currentRoof.attributes.ridgeCoordinate.y1) { + const diffX = ridge.x1 - midX + const diffY = ridge.y1 - midY + midX = ridge.x1 - diffX + midY = ridge.y1 - diffY + xWidth = diffX === 0 ? 0 : (Math.sign(diffX) * xWidth) / 2 + yWidth = diffY === 0 ? 0 : (Math.sign(diffY) * yWidth) / 2 + ridge.set({ + x1: midX + xWidth, + y1: midY + yWidth, + x2: ridge.x2, + y2: ridge.y2, + }) + + currentRoof.attributes.ridgeCoordinate = { x1: ridge.x1, y1: ridge.y1 } + } + if (ridge.x2 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y2 === currentRoof.attributes.ridgeCoordinate.y1) { + const diffX = ridge.x2 - midX + const diffY = ridge.y2 - midY + midX = ridge.x2 - diffX + midY = ridge.y2 - diffY + xWidth = diffX === 0 ? 0 : (Math.sign(diffX) * xWidth) / 2 + yWidth = diffY === 0 ? 0 : (Math.sign(diffY) * yWidth) / 2 + ridge.set({ + x1: ridge.x1, + y1: ridge.y1, + x2: midX + xWidth, + y2: midY + yWidth, + }) + + currentRoof.attributes.ridgeCoordinate = { x1: ridge.x2, y1: ridge.y2 } + } } + + if (hipLines.length > 0) { + const midX = (currentRoof.x1 + currentRoof.x2) / 2 + const midY = (currentRoof.y1 + currentRoof.y2) / 2 + + hipLines.forEach((hip) => { + const singX = Math.sign(hip.x1 - midX) + const singY = Math.sign(hip.y1 - midY) + const xWidth = singX === 0 ? 0 : (singX * currentRoof.attributes.width) / 2 + const yWidth = singY === 0 ? 0 : (singY * currentRoof.attributes.width) / 2 + + hip.set({ + x1: hip.x1, + y1: hip.y1, + x2: midX + xWidth, + y2: midY + yWidth, + }) + }) + } + + if (ridgeLines.length > 0 && hipLines.length > 0) { + console.log('currentRoof : ', currentRoof.attributes.ridgeCoordinate) + hipLines.forEach((hip) => { + const jerkinHeadLine = new QLine([hip.x2, hip.y2, currentRoof.attributes.ridgeCoordinate.x1, currentRoof.attributes.ridgeCoordinate.y1], { + fontSize: roof.fontSize, + stroke: 'red', + strokeWidth: 1, + name: 'jerkinHeadLine', + }) + canvas?.add(jerkinHeadLine) + roof.innerLines.push(jerkinHeadLine) + }) + + if (hipLines.length > 1) { + const jerkinHeadLine = new QLine([hipLines[0].x2, hipLines[0].y2, hipLines[1].x2, hipLines[1].y2], { + fontSize: roof.fontSize, + stroke: 'red', + strokeWidth: 1, + name: 'jerkinHeadLine', + }) + canvas?.add(jerkinHeadLine) + roof.innerLines.push(jerkinHeadLine) + } + } + } +} + +const changeWallRoof = (roofId, currentRoof, canvas) => { + console.log('roofId : ', roofId) + let roof = canvas?.getObjects().find((object) => object.name === 'roof' && object.id === roofId) + const roofLines = roof.lines + let prevRoof, nextRoof + roofLines.forEach((r, index) => { + if (r.id === currentRoof.id) { + currentRoof = r + prevRoof = roofLines[index === 0 ? roofLines.length - 1 : index - 1] + nextRoof = roofLines[index === roofLines.length - 1 ? 0 : index + 1] + } + }) + + const wall = canvas?.getObjects().find((object) => object.name === 'wall' && object.attributes.roofId === roofId) + let wallLine = wall.lines.filter((w) => w.id === currentRoof.attributes.wallLine) + let hipLines = canvas?.getObjects().filter((object) => object.name === 'hipLine' && object.attributes.roofId === roofId) + let ridgeLines = canvas?.getObjects().filter((object) => object.name === 'ridgeLine' && object.attributes.roofId === roofId) + + ridgeLines = ridgeLines.filter( + (ridge) => + (ridge.x1 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y1 === currentRoof.attributes.ridgeCoordinate.y1) || + (ridge.x2 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y2 === currentRoof.attributes.ridgeCoordinate.y1), + ) + hipLines = hipLines.filter( + (hip) => (hip.x1 === currentRoof.x1 && hip.y1 === currentRoof.y1) || (hip.x1 === currentRoof.x2 && hip.y1 === currentRoof.y2), + ) + + const wallMidX = (wallLine[0].x1 + wallLine[0].x2) / 2 + const wallMidY = (wallLine[0].y1 + wallLine[0].y2) / 2 + const roofMidX = (currentRoof.x1 + currentRoof.x2) / 2 + const roofMidY = (currentRoof.y1 + currentRoof.y2) / 2 + + const alpha = wallMidX - roofMidX === 0 ? 0 : wallMidX - roofMidX + const beta = wallMidY - roofMidY === 0 ? 0 : wallMidY - roofMidY + + currentRoof.set({ + x1: currentRoof.x1 + alpha, + y1: currentRoof.y1 + beta, + x2: currentRoof.x2 + alpha, + y2: currentRoof.y2 + beta, + }) + + prevRoof.set({ + x1: prevRoof.x1, + y1: prevRoof.y1, + x2: prevRoof.x2 + alpha, + y2: prevRoof.y2 + beta, + }) + + nextRoof.set({ + x1: nextRoof.x1 + alpha, + y1: nextRoof.y1 + beta, + x2: nextRoof.x2, + y2: nextRoof.y2, + }) + + reDrawPolygon(roof, canvas) + + if (ridgeLines.length > 0) { + const ridge = ridgeLines[0] + if (ridge.x1 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y1 === currentRoof.attributes.ridgeCoordinate.y1) { + const diffX = ridge.x1 - wallMidX === 0 ? 0 : ridge.x1 - wallMidX + const diffY = ridge.y1 - wallMidY === 0 ? 0 : ridge.y1 - wallMidY + + ridge.set({ + x1: ridge.x1 - diffX, + y1: ridge.y1 - diffY, + x2: ridge.x2, + y2: ridge.y2, + }) + } + if (ridge.x2 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y2 === currentRoof.attributes.ridgeCoordinate.y1) { + const diffX = ridge.x2 - wallMidX === 0 ? 0 : ridge.x2 - wallMidX + const diffY = ridge.y2 - wallMidY === 0 ? 0 : ridge.y2 - wallMidY + + ridge.set({ + x1: ridge.x1, + y1: ridge.y1, + x2: ridge.x2 - diffX, + y2: ridge.y2 - diffY, + }) + } + } + + console.log('hipLines : ', hipLines) + if (hipLines.length > 0) { + hipLines.forEach((hip) => { + const diffX = hip.x1 - wallMidX + const diffY = hip.y1 - wallMidY + console.log('diffX : ', diffX, ' diffY : ', diffY) + hip.set({ + x1: hip.x1, + y1: hip.y1, + x2: wallMidX, + y2: wallMidY, + }) + }) } } export const changeAllHipAndGableRoof = (polygon, offset, canvas) => { - const roof = polygon.filter((p) => p.name === 'roofBase')[0] // 지붕 + const roof = polygon.filter((p) => p.name === 'roof')[0] // 지붕 const roofLines = roof.lines // 지붕의 라인 const ridges = roof.ridges // 마루의 라인 const hips = roof.hips // 추녀마루의 라인 @@ -2983,6 +2850,43 @@ const setHipAndGableRoof = (roof, ridge, hip1, hip2, offset, canvas) => { return gableLine } +/** + * 지붕을 변경한다. + * @param polygon + * @param canvas + */ +const reDrawPolygon = (polygon, canvas) => { + const lines = polygon.lines + let point = [] + lines.forEach((line) => point.push({ x: line.x1, y: line.y1 })) + + console.log('point : ', point) + + const newPolygon = new QPolygon(point, { + id: polygon.id, + name: polygon.name, + fill: polygon.fill, + stroke: polygon.stroke, + strokeWidth: polygon.strokeWidth, + selectable: polygon.selectable, + fontSize: polygon.fontSize, + wall: polygon.wall !== undefined ? polygon.wall : null, + }) + + const newLines = newPolygon.lines + + newLines.forEach((line, index) => { + lines.forEach((l, i) => { + if (index === i) { + line.id = l.id + line.attributes = l.attributes + } + }) + }) + canvas?.add(newPolygon) + canvas?.remove(polygon) +} + function arePointsEqual(point1, point2) { return point1.x === point2.x && point1.y === point2.y } From 5064ff627c3a80b4e63a68e7bf65b78a4bec0a44 Mon Sep 17 00:00:00 2001 From: Jaeyoung Lee Date: Mon, 14 Oct 2024 17:23:24 +0900 Subject: [PATCH 02/13] =?UTF-8?q?dev=20merge=20=EC=B2=98=EB=A6=AC=20?= =?UTF-8?q?=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useMode.js | 316 ++++++++++++++++++++++++++++++------------- 1 file changed, 219 insertions(+), 97 deletions(-) diff --git a/src/hooks/useMode.js b/src/hooks/useMode.js index e82c51f3..e7b457e5 100644 --- a/src/hooks/useMode.js +++ b/src/hooks/useMode.js @@ -36,7 +36,10 @@ import { QPolygon } from '@/components/fabric/QPolygon' import offsetPolygon from '@/util/qpolygon-utils' import { isObjectNotEmpty } from '@/util/common-utils' import * as turf from '@turf/turf' -import { INPUT_TYPE, LINE_TYPE, Mode } from '@/common/common' +import { INPUT_TYPE, Mode } from '@/common/common' +import { m } from 'framer-motion' +import { set } from 'react-hook-form' +import { FaWineGlassEmpty } from 'react-icons/fa6' export function useMode() { const [mode, setMode] = useRecoilState(modeState) @@ -1506,25 +1509,6 @@ export function useMode() { *벽 지붕 외곽선 생성 polygon을 입력받아 만들기 */ const handleOuterlinesTest2 = (polygon, offset = 50) => { - console.log('polygon : ', polygon) - // TODO [ljyoung] : offset 입력 처리 후 제거 해야함. - polygon.lines.forEach((line, index) => { - if (index === 1 || index === 3) { - line.attributes = { - type: LINE_TYPE.WALLLINE.WALL, - offset: 50, - width: 100, - pitch: 4, - } - } else { - line.attributes = { - type: LINE_TYPE.WALLLINE.WALL, - offset: 50, - width: 100, - pitch: 4, - } - } - }) const roof = drawRoofPolygon(polygon) //지붕 그리기 roof.drawHelpLine() // roof.divideLine() @@ -4873,6 +4857,12 @@ export function useMode() { ) } + const coordToTurfPolygon = (points) => { + const coordinates = points.map((point) => [point.x, point.y]) + coordinates.push(coordinates[0]) + return turf.polygon([coordinates]) + } + /** * trestle에서 영역을 가져와 mouse:move 이벤트로 해당 영역에 진입했을때 booleanPointInPolygon 로 진입여부를 확인 * 확인 후 셀을 이동시킴 @@ -4880,56 +4870,190 @@ export function useMode() { const drawCellManualInTrestle = () => { const trestlePolygons = canvas?.getObjects().filter((obj) => obj.name === 'trestle') //가대를 가져옴 + const dormerTrestlePolygons = canvas?.getObjects().filter((obj) => obj.name === 'dormerTrestle') //도머 객체 + if (trestlePolygons.length !== 0) { - let lastPointPosition = { x: 0, y: 0 } let fabricPolygon = null let inside = false let turfPolygon - let manualDrawCells = drewRoofCells // - + let manualDrawCells = drewRoofCells // 앞에서 자동으로 했을때 추가됨 + let direction + let trestlePolygon canvas.on('mouse:move', (e) => { //마우스 이벤트 삭제 후 재추가 const mousePoint = canvas.getPointer(e.e) - const turfPoint = turf.point([mousePoint.x, mousePoint.y]) for (let i = 0; i < trestlePolygons.length; i++) { turfPolygon = polygonToTurfPolygon(trestlePolygons[i]) - if (turf.booleanPointInPolygon(turfPoint, turfPolygon)) { - //turf에 보면 폴리곤안에 포인트가 있는지 함수가 있다 - const direction = trestlePolygons[i].direction //도형의 방향 - let width = direction === 'south' || direction === 'north' ? 172.2 : 113.4 - let height = direction === 'south' || direction === 'north' ? 113.4 : 172.2 - if (Math.abs(mousePoint.x - lastPointPosition.x) >= 5 || Math.abs(mousePoint.y - lastPointPosition.y) >= 5) { - let isDrawing = false + trestlePolygon = trestlePolygons[i] + direction = trestlePolygons[i].direction //도형의 방향 + let width = direction === 'south' || direction === 'north' ? 172 : 113 + let height = direction === 'south' || direction === 'north' ? 113 : 172 - if (isDrawing) return - canvas?.remove(...canvas?.getObjects().filter((obj) => obj.name === 'tmpCell')) //움직일때 일단 지워가면서 움직임 + const points = [ + { x: mousePoint.x - width / 2, y: mousePoint.y - height / 2 }, + { x: mousePoint.x + width / 2, y: mousePoint.y - height / 2 }, + { x: mousePoint.x + width / 2, y: mousePoint.y + height / 2 }, + { x: mousePoint.x - width / 2, y: mousePoint.y + height / 2 }, + ] - const points = [ - { x: mousePoint.x - width / 2, y: mousePoint.y - height / 2 }, - { x: mousePoint.x + width / 2, y: mousePoint.y - height / 2 }, - { x: mousePoint.x + width / 2, y: mousePoint.y + height / 2 }, - { x: mousePoint.x - width / 2, y: mousePoint.y + height / 2 }, - ] + const turfPoints = coordToTurfPolygon(points) - fabricPolygon = new QPolygon(points, { - fill: '#BFFD9F', - stroke: 'black', - selectable: false, // 선택 가능하게 설정 - lockMovementX: true, // X 축 이동 잠금 - lockMovementY: true, // Y 축 이동 잠금 - lockRotation: true, // 회전 잠금 - lockScalingX: true, // X 축 크기 조정 잠금 - lockScalingY: true, // Y 축 크기 조정 잠금 - opacity: 0.8, - parentId: trestlePolygons[i].parentId, - name: 'tmpCell', + if (turf.booleanWithin(turfPoints, turfPolygon)) { + let isDrawing = false + + if (isDrawing) return + canvas?.remove(...canvas?.getObjects().filter((obj) => obj.name === 'tmpCell')) //움직일때 일단 지워가면서 움직임 + + fabricPolygon = new fabric.Rect({ + fill: 'white', + stroke: 'black', + strokeWidth: 1, + width: width, + height: height, + left: mousePoint.x - width / 2, + top: mousePoint.y - height / 2, + selectable: false, + lockMovementX: true, + lockMovementY: true, + lockRotation: true, + lockScalingX: true, + lockScalingY: true, + opacity: 0.8, + name: 'tmpCell', + parentId: trestlePolygons[i].parentId, + }) + + canvas?.add(fabricPolygon) //움직여가면서 추가됨 + + /** + * 스냅기능 + */ + let snapDistance = 10 + let cellSnapDistance = 20 + + const trestleLeft = trestlePolygons[i].left + const trestleTop = trestlePolygons[i].top + const trestleRight = trestleLeft + trestlePolygons[i].width * trestlePolygons[i].scaleX + const trestleBottom = trestleTop + trestlePolygons[i].height * trestlePolygons[i].scaleY + const bigCenterY = (trestleTop + trestleTop + trestlePolygons[i].height) / 2 + + // 작은 폴리곤의 경계 좌표 계산 + const smallLeft = fabricPolygon.left + const smallTop = fabricPolygon.top + const smallRight = smallLeft + fabricPolygon.width * fabricPolygon.scaleX + const smallBottom = smallTop + fabricPolygon.height * fabricPolygon.scaleY + const smallCenterX = smallLeft + (fabricPolygon.width * fabricPolygon.scaleX) / 2 + const smallCenterY = smallTop + (fabricPolygon.height * fabricPolygon.scaleX) / 2 + + /** + * 미리 깔아놓은 셀이 있을때 셀에 흡착됨 + */ + if (manualDrawCells) { + manualDrawCells.forEach((cell) => { + const holdCellLeft = cell.left + const holdCellTop = cell.top + const holdCellRight = holdCellLeft + cell.width * cell.scaleX + const holdCellBottom = holdCellTop + cell.height * cell.scaleY + const holdCellCenterX = holdCellLeft + (cell.width * cell.scaleX) / 2 + const holdCellCenterY = holdCellTop + (cell.height * cell.scaleY) / 2 + + //설치된 셀에 좌측에 스냅 + if (Math.abs(smallRight - holdCellLeft) < snapDistance) { + fabricPolygon.left = holdCellLeft - width - 0.5 + } + + //설치된 셀에 우측에 스냅 + if (Math.abs(smallLeft - holdCellRight) < snapDistance) { + fabricPolygon.left = holdCellRight + 0.5 + } + + //설치된 셀에 위쪽에 스냅 + if (Math.abs(smallBottom - holdCellTop) < snapDistance) { + fabricPolygon.top = holdCellTop - height - 0.5 + } + + //설치된 셀에 밑쪽에 스냅 + if (Math.abs(smallTop - holdCellBottom) < snapDistance) { + fabricPolygon.top = holdCellBottom + 0.5 + } + //가운데 -> 가운데 + if (Math.abs(smallCenterX - holdCellCenterX) < cellSnapDistance) { + fabricPolygon.left = holdCellCenterX - width / 2 + } + //왼쪽 -> 가운데 + if (Math.abs(smallLeft - holdCellCenterX) < cellSnapDistance) { + fabricPolygon.left = holdCellCenterX + } + // 오른쪽 -> 가운데 + if (Math.abs(smallRight - holdCellCenterX) < cellSnapDistance) { + fabricPolygon.left = holdCellCenterX - width + } + //세로 가운데 -> 가운데 + if (Math.abs(smallCenterY - holdCellCenterY) < cellSnapDistance) { + fabricPolygon.top = holdCellCenterY - height / 2 + } + //위쪽 -> 가운데 + if (Math.abs(smallTop - holdCellCenterY) < cellSnapDistance) { + fabricPolygon.top = holdCellCenterY + } + //아랫쪽 -> 가운데 + if (Math.abs(smallBottom - holdCellCenterY) < cellSnapDistance) { + fabricPolygon.top = holdCellCenterY - height + } }) - - canvas?.add(fabricPolygon) //움직여가면서 추가됨 - lastPointPosition = { x: mousePoint.x, y: mousePoint.y } } + // 위쪽 변에 스냅 + if (Math.abs(smallTop - trestleTop) < snapDistance) { + fabricPolygon.top = trestleTop + } + + // 아래쪽 변에 스냅 + if (Math.abs(smallTop + fabricPolygon.height * fabricPolygon.scaleY - (trestleTop + trestlePolygons[i].height)) < snapDistance) { + fabricPolygon.top = trestleTop + trestlePolygons[i].height - fabricPolygon.height * fabricPolygon.scaleY + } + + // 왼쪽변에 스냅 + if (Math.abs(smallLeft - trestleLeft) < snapDistance) { + fabricPolygon.left = trestleLeft + } + //오른쪽 변에 스냅 + if (Math.abs(smallRight - trestleRight) < snapDistance) { + fabricPolygon.left = trestleRight - fabricPolygon.width * fabricPolygon.scaleX + } + + if (direction === 'south' || direction === 'north') { + // 모듈왼쪽이 세로중앙선에 붙게 스냅 + if (Math.abs(smallLeft - (trestleLeft + trestlePolygons[i].width / 2)) < snapDistance) { + fabricPolygon.left = trestleLeft + trestlePolygons[i].width / 2 + } + + // 모듈이 가운데가 세로중앙선에 붙게 스냅 + if (Math.abs(smallCenterX - (trestleLeft + trestlePolygons[i].width / 2)) < snapDistance) { + fabricPolygon.left = trestleLeft + trestlePolygons[i].width / 2 - (fabricPolygon.width * fabricPolygon.scaleX) / 2 + } + + // 모듈오른쪽이 세로중앙선에 붙게 스냅 + if (Math.abs(smallRight - (trestleLeft + trestlePolygons[i].width / 2)) < snapDistance) { + fabricPolygon.left = trestleLeft + trestlePolygons[i].width / 2 - fabricPolygon.width * fabricPolygon.scaleX + } + } else { + // 모듈이 가로중앙선에 스냅 + if (Math.abs(smallTop + fabricPolygon.height / 2 - bigCenterY) < snapDistance) { + fabricPolygon.top = bigCenterY - fabricPolygon.height / 2 + } + + if (Math.abs(smallTop - (trestleTop + trestlePolygons[i].height / 2)) < snapDistance) { + fabricPolygon.top = trestleTop + trestlePolygons[i].height / 2 + } + // 모듈 밑면이 가로중앙선에 스냅 + if (Math.abs(smallBottom - (trestleTop + trestlePolygons[i].height / 2)) < snapDistance) { + fabricPolygon.top = trestleTop + trestlePolygons[i].height / 2 - fabricPolygon.height * fabricPolygon.scaleY + } + } + + fabricPolygon.setCoords() canvas?.renderAll() inside = true break @@ -4945,23 +5069,56 @@ export function useMode() { }) canvas?.on('mouse:up', (e) => { + let isIntersection = true if (!inside) return if (fabricPolygon) { - const turfCellPolygon = polygonToTurfPolygon(fabricPolygon) + const rectPoints = [ + { x: fabricPolygon.left + 0.5, y: fabricPolygon.top + 0.5 }, + { x: fabricPolygon.left + 0.5 + fabricPolygon.width * fabricPolygon.scaleX, y: fabricPolygon.top + 0.5 }, + { + x: fabricPolygon.left + fabricPolygon.width * fabricPolygon.scaleX + 0.5, + y: fabricPolygon.top + fabricPolygon.height * fabricPolygon.scaleY + 0.5, + }, + { x: fabricPolygon.left + 0.5, y: fabricPolygon.top + fabricPolygon.height * fabricPolygon.scaleY + 0.5 }, + ] - if (turf.booleanWithin(turfCellPolygon, turfPolygon)) { + fabricPolygon.set({ points: rectPoints }) + const tempTurfModule = polygonToTurfPolygon(fabricPolygon) + + //도머 객체를 가져옴 + if (dormerTrestlePolygons) { + dormerTrestlePolygons.forEach((dormerTrestle) => { + const dormerTurfPolygon = polygonToTurfPolygon(dormerTrestle) //turf객체로 변환 + const intersection = turf.intersect(turf.featureCollection([dormerTurfPolygon, tempTurfModule])) //겹치는지 확인 + //겹치면 안됨 + if (intersection) { + alert('도머위에 모듈을 올릴 수 없습니다.') + isIntersection = false + } + }) + } + + if (!isIntersection) return + + fabricPolygon.setCoords() //좌표 재정렬 + + if (turf.booleanWithin(tempTurfModule, turfPolygon)) { //마우스 클릭시 set으로 해당 위치에 셀을 넣음 - const isOverlap = manualDrawCells.some((cell) => turf.booleanOverlap(turfCellPolygon, polygonToTurfPolygon(cell))) + const isOverlap = manualDrawCells.some((cell) => turf.booleanOverlap(tempTurfModule, polygonToTurfPolygon(cell))) //겹치는지 확인 if (!isOverlap) { //안겹치면 넣는다 - fabricPolygon.set({ name: 'cell' }) fabricPolygon.setCoords() - manualDrawCells.push(fabricPolygon) + fabricPolygon.set({ name: 'cell', fill: '#BFFD9F' }) + manualDrawCells.push(fabricPolygon) //모듈배열에 추가 + //해당 모듈에 프로퍼티로 넣는다 + trestlePolygon.set({ + modules: manualDrawCells, + }) } else { alert('셀끼리 겹치면 안되죠?') } } else { - alert('나갔으요!!') + alert('나갔죠?!!') } setDrewRoofCells(manualDrawCells) } @@ -5036,29 +5193,6 @@ export function useMode() { const cols = Math.floor((bbox[2] - bbox[0]) / width) const rows = Math.floor((bbox[3] - bbox[1]) / height) - // console.log('bbox', bbox) - - const boxes = [] - const installedCellsArray = [] - - for (let x = bbox[0]; x < bbox[2]; x += width) { - for (let y = bbox[1]; y < bbox[3]; y += height) { - const box = turf.polygon([ - [ - [x, y], - [x + width, y], - [x + width, y + height], - [x, y + height], - [x, y], - ], - ]) - - if (turf.booleanWithin(box, turfTrestlePolygon)) { - boxes.push(box) - } - } - } - for (let col = 0; col <= cols; col++) { for (let row = 0; row <= rows; row++) { let x = 0, @@ -5108,20 +5242,6 @@ export function useMode() { const squarePolygon = turf.polygon([square]) - // console.log('turfTrestlePolygon', turfTrestlePolygon) - // console.log('squarePolygon', squarePolygon) - - const areaSize = turf.area(turfTrestlePolygon) - - // console.log('areaSize', areaSize) - const objSize = turf.area(squarePolygon) - - // console.log('objSize', objSize) - - const maxObject = Math.floor(areaSize / objSize) - - // console.log('maxObjectSize', maxObject) - const disjointFromTrestle = turf.booleanContains(turfTrestlePolygon, squarePolygon) || turf.booleanWithin(squarePolygon, turfTrestlePolygon) if (disjointFromTrestle) { @@ -5162,6 +5282,8 @@ export function useMode() { lockScalingY: true, // Y 축 크기 조정 잠금 opacity: 0.8, parentId: trestle.parentId, + lineCol: col, + lineRow: row, }) canvas?.add(fabricPolygon) drawCellsArray.push(fabricPolygon) From 0bc0e73a103bd46113320c202b87eac4265eb0e8 Mon Sep 17 00:00:00 2001 From: Jaeyoung Lee Date: Fri, 18 Oct 2024 10:29:31 +0900 Subject: [PATCH 03/13] =?UTF-8?q?=EC=A7=80=EB=B6=95=20=EB=AA=A8=EC=96=91?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD=20=EC=9E=91=EC=97=85=20=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/common.js | 1 + src/components/Roof2.jsx | 45 +++++--- src/hooks/useMode.js | 43 +++++++- src/util/qpolygon-utils.js | 205 +++++++++++++++++++++++-------------- 4 files changed, 197 insertions(+), 97 deletions(-) diff --git a/src/common/common.js b/src/common/common.js index ae96db06..bc067077 100644 --- a/src/common/common.js +++ b/src/common/common.js @@ -63,6 +63,7 @@ export const LINE_TYPE = { HIPANDGABLE: 'hipAndGable', JERKINHEAD: 'jerkinhead', SHED: 'shed', + ETC: 'etc', }, SUBLINE: { /** diff --git a/src/components/Roof2.jsx b/src/components/Roof2.jsx index 910fd365..2fe86ecc 100644 --- a/src/components/Roof2.jsx +++ b/src/components/Roof2.jsx @@ -4,7 +4,7 @@ import { useCanvas } from '@/hooks/useCanvas' import { useEffect, useRef, useState } from 'react' import { v4 as uuidv4 } from 'uuid' import { useMode } from '@/hooks/useMode' -import { Mode } from '@/common/common' +import { LINE_TYPE, Mode } from '@/common/common' import { Button, Input } from '@nextui-org/react' import RangeSlider from './ui/RangeSlider' import { useRecoilState, useRecoilValue } from 'recoil' @@ -39,7 +39,7 @@ import QEmptyContextMenu from '@/components/common/context-menu/QEmptyContextMen import InitSettingsModal from './InitSettingsModal' import GridSettingsModal from './GridSettingsModal' import { SurfaceShapeModal } from '@/components/ui/SurfaceShape' -import { drawDirectionStringToArrow } from '@/util/qpolygon-utils' +import { changeHipAndGableRoof, drawDirectionStringToArrow } from '@/util/qpolygon-utils' import ThumbnailList from '@/components/ui/ThumbnailLIst' import ObjectPlacement from '@/components/ui/ObjectPlacement' import { globalLocaleStore } from '@/store/localeAtom' @@ -431,7 +431,7 @@ export default function Roof2(props) { { x: 450, y: 850 }, ] - const polygon = new QPolygon(rectangleType2, { + const polygon = new QPolygon(rectangleType1, { fill: 'transparent', stroke: 'green', strokeWidth: 1, @@ -672,13 +672,14 @@ export default function Roof2(props) { canvas?.renderAll() } - const setAllGableRoof = () => { + const setGableRoof = () => { let offset = Number(prompt('gable roof offset', '50')) if (!isNaN(offset) && offset > 0) { - const polygon = canvas?.getObjects() - console.log('gable roof offset : ', offset) - console.log('polygon : ', polygon) - changeAllGableRoof(polygon, offset, canvas) + const polygon = canvas?.getObjects().find((obj) => obj.name === 'roof') + const currentRoof = polygon.lines[3] + currentRoof.attributes.type = LINE_TYPE.WALLLINE.HIPANDGABLE + currentRoof.attributes.width = offset + changeHipAndGableRoof(currentRoof, canvas) } else { alert('offset 은 0 보다 커야 함') } @@ -802,16 +803,28 @@ export default function Roof2(props) { - {templateType === 0 && ( - <> - - - )} - + {/* */} + {/*)}*/} + + + + + diff --git a/src/hooks/useMode.js b/src/hooks/useMode.js index e7b457e5..6a332d35 100644 --- a/src/hooks/useMode.js +++ b/src/hooks/useMode.js @@ -36,10 +36,7 @@ import { QPolygon } from '@/components/fabric/QPolygon' import offsetPolygon from '@/util/qpolygon-utils' import { isObjectNotEmpty } from '@/util/common-utils' import * as turf from '@turf/turf' -import { INPUT_TYPE, Mode } from '@/common/common' -import { m } from 'framer-motion' -import { set } from 'react-hook-form' -import { FaWineGlassEmpty } from 'react-icons/fa6' +import { INPUT_TYPE, LINE_TYPE, Mode } from '@/common/common' export function useMode() { const [mode, setMode] = useRecoilState(modeState) @@ -1509,6 +1506,43 @@ export function useMode() { *벽 지붕 외곽선 생성 polygon을 입력받아 만들기 */ const handleOuterlinesTest2 = (polygon, offset = 50) => { + console.log('polygon : ', polygon) + // TODO [ljyoung] : offset 입력 처리 후 제거 해야함. + polygon.lines.forEach((line, index) => { + line.attributes = { + type: LINE_TYPE.WALLLINE.EAVES, + offset: 40, + width: 50, + pitch: 4, + sleeve: true, + } + /*if (index === 1 || index === 3) { + line.attributes = { + type: LINE_TYPE.WALLLINE.WALL, + offset: 0, //출폭 + width: 30, //폭 + pitch: 4, //구배 + sleeve: true, //소매 + } + } else if (index === 0) { + line.attributes = { + type: LINE_TYPE.WALLLINE.EAVES, + offset: 0, + width: 50, + pitch: 4, + sleeve: true, + } + } else { + line.attributes = { + type: LINE_TYPE.WALLLINE.EAVES, + offset: 40, + width: 50, + pitch: 4, + sleeve: true, + } + }*/ + }) + const roof = drawRoofPolygon(polygon) //지붕 그리기 roof.drawHelpLine() // roof.divideLine() @@ -1706,6 +1740,7 @@ export function useMode() { offset: wall.lines[index].attributes.offset, width: wall.lines[index].attributes.width, pitch: wall.lines[index].attributes.pitch, + sleeve: wall.lines[index].attributes.sleeve || false, } }) diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js index c7ccfece..62597365 100644 --- a/src/util/qpolygon-utils.js +++ b/src/util/qpolygon-utils.js @@ -2030,7 +2030,6 @@ const connectLinePoint = (polygon) => { } const modifyRidge = (roof, canvas) => { - const roofLines = roof.lines const ridgeLines = canvas?.getObjects().filter((object) => object.name === 'ridgeLine' && object.attributes.roofId === roof.id) const hipLines = canvas?.getObjects().filter((object) => object.name === 'hipLine' && object.attributes.roofId === roof.id) @@ -2041,7 +2040,6 @@ const modifyRidge = (roof, canvas) => { let currentRoof = roof.lines .filter((roofLine) => roofLine.attributes !== undefined && roofLine.attributes.ridgeCoordinate !== undefined) .find((roofLine) => roofLine.attributes.ridgeCoordinate.x1 === ridge.x1 && roofLine.attributes.ridgeCoordinate.y1 === ridge.y1) - console.log('1 currentRoof : ', currentRoof) if (currentRoof === undefined) { currentRoof = roof.lines.find( (roofLine) => @@ -2058,7 +2056,6 @@ const modifyRidge = (roof, canvas) => { currentRoof.attributes.ridgeCoordinate = { x1: ridge.x1, y1: ridge.y1 } } } - console.log('2 currentRoof : ', currentRoof) if (currentRoof !== undefined) { switch (currentRoof.attributes.type) { case LINE_TYPE.WALLLINE.EAVES: @@ -2067,7 +2064,7 @@ const modifyRidge = (roof, canvas) => { changeGableRoof(roof.id, currentRoof, canvas) break case LINE_TYPE.WALLLINE.HIPANDGABLE: - changeHipAndGableRoof(roof.id, currentRoof, canvas) + changeHipAndGableRoof(currentRoof, canvas) break case LINE_TYPE.WALLLINE.JERKINHEAD: changeJerkInHeadRoof(roof.id, currentRoof, canvas) @@ -2082,7 +2079,6 @@ const modifyRidge = (roof, canvas) => { let currentRoof = roof.lines .filter((roofLine) => roofLine.attributes !== undefined && roofLine.attributes.ridgeCoordinate !== undefined) .find((roofLine) => roofLine.attributes.ridgeCoordinate.x1 === ridge.x2 && roofLine.attributes.ridgeCoordinate.y1 === ridge.y2) - console.log('3 currentRoof : ', currentRoof) if (currentRoof === undefined) { currentRoof = roof.lines.find( (roofLine) => @@ -2099,7 +2095,6 @@ const modifyRidge = (roof, canvas) => { currentRoof.attributes.ridgeCoordinate = { x1: ridge.x2, y1: ridge.y2 } } } - console.log('4 currentRoof : ', currentRoof) if (currentRoof !== undefined) { switch (currentRoof.attributes.type) { case LINE_TYPE.WALLLINE.EAVES: @@ -2108,7 +2103,7 @@ const modifyRidge = (roof, canvas) => { changeGableRoof(roof.id, currentRoof, canvas) break case LINE_TYPE.WALLLINE.HIPANDGABLE: - changeHipAndGableRoof(roof.id, currentRoof, canvas) + changeHipAndGableRoof(currentRoof, canvas) break case LINE_TYPE.WALLLINE.JERKINHEAD: changeJerkInHeadRoof(roof.id, currentRoof, canvas) @@ -2220,12 +2215,12 @@ export const changeGableRoof = (roofId, currentRoof, canvas) => { /** * 팔작지붕으로 변경 - * @param roofId * @param currentRoof * @param canvas */ -export const changeHipAndGableRoof = (roofId, currentRoof, canvas) => { +export const changeHipAndGableRoof = (currentRoof, canvas) => { if (currentRoof.attributes.type === LINE_TYPE.WALLLINE.HIPANDGABLE) { + const roofId = currentRoof.attributes.roofId const roof = canvas?.getObjects().find((object) => object.name === 'roof' && object.id === roofId) let hipLines = canvas?.getObjects().filter((object) => object.name === 'hipLine' && object.attributes.roofId === roofId) let ridgeLines = canvas?.getObjects().filter((object) => object.name === 'ridgeLine' && object.attributes.roofId === roofId) @@ -2239,7 +2234,11 @@ export const changeHipAndGableRoof = (roofId, currentRoof, canvas) => { (hip) => (hip.x1 === currentRoof.x1 && hip.y1 === currentRoof.y1) || (hip.x1 === currentRoof.x2 && hip.y1 === currentRoof.y2), ) - if (ridgeLines.length > 0) { + hipLines.forEach((hip) => canvas?.remove(hip)) + + console.log(canvas?.getObjects().find((object) => object.attributes !== undefined && object.attributes.roofId === roofId)) + + /*if (ridgeLines.length > 0) { const ridge = ridgeLines[0] let midX = (currentRoof.x1 + currentRoof.x2) / 2 let midY = (currentRoof.y1 + currentRoof.y2) / 2 @@ -2310,7 +2309,7 @@ export const changeHipAndGableRoof = (roofId, currentRoof, canvas) => { roof.innerLines.push(hipLine) }) } - } + }*/ } } @@ -2426,8 +2425,13 @@ export const changeJerkInHeadRoof = (roofId, currentRoof, canvas) => { } } +/** + * 벽지붕으로 변경 + * @param roofId + * @param currentRoof + * @param canvas + */ const changeWallRoof = (roofId, currentRoof, canvas) => { - console.log('roofId : ', roofId) let roof = canvas?.getObjects().find((object) => object.name === 'roof' && object.id === roofId) const roofLines = roof.lines let prevRoof, nextRoof @@ -2444,6 +2448,10 @@ const changeWallRoof = (roofId, currentRoof, canvas) => { let hipLines = canvas?.getObjects().filter((object) => object.name === 'hipLine' && object.attributes.roofId === roofId) let ridgeLines = canvas?.getObjects().filter((object) => object.name === 'ridgeLine' && object.attributes.roofId === roofId) + if (wallLine.length > 0) { + wallLine = wallLine[0] + } + ridgeLines = ridgeLines.filter( (ridge) => (ridge.x1 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y1 === currentRoof.attributes.ridgeCoordinate.y1) || @@ -2453,8 +2461,8 @@ const changeWallRoof = (roofId, currentRoof, canvas) => { (hip) => (hip.x1 === currentRoof.x1 && hip.y1 === currentRoof.y1) || (hip.x1 === currentRoof.x2 && hip.y1 === currentRoof.y2), ) - const wallMidX = (wallLine[0].x1 + wallLine[0].x2) / 2 - const wallMidY = (wallLine[0].y1 + wallLine[0].y2) / 2 + const wallMidX = (wallLine.x1 + wallLine.x2) / 2 + const wallMidY = (wallLine.y1 + wallLine.y2) / 2 const roofMidX = (currentRoof.x1 + currentRoof.x2) / 2 const roofMidY = (currentRoof.y1 + currentRoof.y2) / 2 @@ -2482,7 +2490,88 @@ const changeWallRoof = (roofId, currentRoof, canvas) => { y2: nextRoof.y2, }) - reDrawPolygon(roof, canvas) + if (currentRoof.attributes.sleeve && prevRoof.attributes.offset > 0 && nextRoof.attributes.offset > 0) { + const prevSignX = Math.sign(prevRoof.x1 - prevRoof.x2) + const prevSignY = Math.sign(prevRoof.y1 - prevRoof.y2) + const nextSignX = Math.sign(nextRoof.x1 - nextRoof.x2) + const nextSignY = Math.sign(nextRoof.y1 - nextRoof.y2) + + const prevWidthX = prevSignX === 0 ? 0 : prevSignX * currentRoof.attributes.width + const prevWidthY = prevSignY === 0 ? 0 : prevSignY * currentRoof.attributes.width + const nextWidthX = nextSignX === 0 ? 0 : nextSignX * currentRoof.attributes.width + const nextWidthY = nextSignY === 0 ? 0 : nextSignY * currentRoof.attributes.width + const prevX2 = prevRoof.x2 - prevWidthX + const prevY2 = prevRoof.y2 - prevWidthY + const nextX1 = nextRoof.x1 + nextWidthX + const nextY1 = nextRoof.y1 + nextWidthY + + currentRoof.set({ + x1: wallLine.x1, + y1: wallLine.y1, + x2: wallLine.x2, + y2: wallLine.y2, + }) + + prevRoof.set({ + x1: prevRoof.x1, + y1: prevRoof.y1, + x2: prevX2, + y2: prevY2, + }) + + nextRoof.set({ + x1: nextX1, + y1: nextY1, + x2: nextRoof.x2, + y2: nextRoof.y2, + }) + + const addPrevWallLine1 = new QLine([prevX2, prevY2, wallLine.x1 - prevWidthX, wallLine.y1 - prevWidthY], { + fontSize: roof.fontSize, + stroke: 'red', + strokeWidth: 1, + name: 'roofLine', + attributes: { roofId: roofId, type: LINE_TYPE.WALLLINE.ETC }, + }) + + const addPrevWallLine2 = new QLine( + [addPrevWallLine1.x2, addPrevWallLine1.y2, addPrevWallLine1.x2 + prevWidthX, addPrevWallLine1.y2 + prevWidthY], + { + fontSize: roof.fontSize, + stroke: 'cyan', + strokeWidth: 1, + name: 'roofLine', + attributes: { roofId: roofId, type: LINE_TYPE.WALLLINE.ETC }, + }, + ) + + const addNextWallLine1 = new QLine([wallLine.x2, wallLine.y2, wallLine.x2 + nextWidthX, wallLine.y2 + nextWidthY], { + fontSize: roof.fontSize, + stroke: 'green', + strokeWidth: 1, + name: 'roofLine', + attributes: { roofId: roofId, type: LINE_TYPE.WALLLINE.ETC }, + }) + + const addNextWallLine2 = new QLine([addNextWallLine1.x2, addNextWallLine1.y2, nextX1, nextY1], { + fontSize: roof.fontSize, + stroke: 'red', + strokeWidth: 1, + name: 'roofLine', + attributes: { roofId: roofId, type: LINE_TYPE.WALLLINE.ETC }, + }) + + canvas?.renderAll() + const prevIndex = roof.lines.indexOf(prevRoof) + 1 + roof.lines.splice(prevIndex, 0, addPrevWallLine1, addPrevWallLine2) + + const nextIndex = roof.lines.indexOf(currentRoof) + 1 + roof.lines.splice(nextIndex, 0, addNextWallLine1, addNextWallLine2) + } + + roof = reDrawPolygon(roof, canvas) + + console.log('roof : ', roof.lines) if (ridgeLines.length > 0) { const ridge = ridgeLines[0] @@ -2508,72 +2597,31 @@ const changeWallRoof = (roofId, currentRoof, canvas) => { y2: ridge.y2 - diffY, }) } - } - console.log('hipLines : ', hipLines) + let hip1 = new QLine([currentRoof.x1, currentRoof.y1, wallMidX, wallMidY], { + fontSize: roof.fontSize, + stroke: 'red', + strokeWidth: 1, + name: 'hipLine', + attributes: { roofId: roof.id, currentRoofId: currentRoof.id, planeSize: currentRoof.length, actualSize: currentRoof.length }, + }) + let hip2 = new QLine([currentRoof.x2, currentRoof.y2, wallMidX, wallMidY], { + fontSize: roof.fontSize, + stroke: 'red', + strokeWidth: 1, + name: 'hipLine', + attributes: { roofId: roof.id, currentRoofId: currentRoof.id, planeSize: currentRoof.length, actualSize: currentRoof.length }, + }) + canvas?.add(hip1) + canvas?.add(hip2) + } if (hipLines.length > 0) { hipLines.forEach((hip) => { - const diffX = hip.x1 - wallMidX - const diffY = hip.y1 - wallMidY - console.log('diffX : ', diffX, ' diffY : ', diffY) - hip.set({ - x1: hip.x1, - y1: hip.y1, - x2: wallMidX, - y2: wallMidY, - }) + canvas?.remove(hip) }) } } -export const changeAllHipAndGableRoof = (polygon, offset, canvas) => { - const roof = polygon.filter((p) => p.name === 'roof')[0] // 지붕 - const roofLines = roof.lines // 지붕의 라인 - const ridges = roof.ridges // 마루의 라인 - const hips = roof.hips // 추녀마루의 라인 - - console.log('roofLines : ', roofLines) - - ridges.forEach((ridge) => { - let ridgeHip1 = hips.filter((hip) => hip.x2 === ridge.x1 && hip.y2 === ridge.y1) - let ridgeHip2 = hips.filter((hip) => hip.x2 === ridge.x2 && hip.y2 === ridge.y2) - let gableLines = [] - if (ridgeHip1.length > 1) { - let x1 = ridgeHip1[0].x1, - y1 = ridgeHip1[0].y1, - x2 = ridgeHip1[1].x1, - y2 = ridgeHip1[1].y1 - roofLines.filter((roofLine) => { - if ( - (roofLine.x1 === x1 && roofLine.y1 === y1 && roofLine.x2 === x2 && roofLine.y2 === y2) || - (roofLine.x1 === x2 && roofLine.y1 === y2 && roofLine.x2 === x1 && roofLine.y2 === y1) - ) { - gableLines.push(setHipAndGableRoof(roof, ridge, ridgeHip1[0], ridgeHip1[1], offset, canvas)) - } - }) - } - if (ridgeHip2.length > 1) { - let x1 = ridgeHip2[0].x1, - y1 = ridgeHip2[0].y1, - x2 = ridgeHip2[1].x1, - y2 = ridgeHip2[1].y1 - roofLines.filter((roofLine) => { - if ( - (roofLine.x1 === x1 && roofLine.y1 === y1 && roofLine.x2 === x2 && roofLine.y2 === y2) || - (roofLine.x1 === x2 && roofLine.y1 === y2 && roofLine.x2 === x1 && roofLine.y2 === y1) - ) { - gableLines.push(setHipAndGableRoof(roof, ridge, ridgeHip2[0], ridgeHip2[1], offset, canvas)) - } - }) - } - gableLines.forEach((gableLine) => { - roof.innerLines.push(gableLine) - }) - }) - - // splitPolygonWithLines(roof) -} - /** * 모임지붕 -> 팔작지붕 변경 * @param roof @@ -2860,7 +2908,7 @@ const reDrawPolygon = (polygon, canvas) => { let point = [] lines.forEach((line) => point.push({ x: line.x1, y: line.y1 })) - console.log('point : ', point) + canvas?.remove(polygon) const newPolygon = new QPolygon(point, { id: polygon.id, @@ -2877,14 +2925,17 @@ const reDrawPolygon = (polygon, canvas) => { newLines.forEach((line, index) => { lines.forEach((l, i) => { - if (index === i) { + if (line.x1 === l.x1 && line.y1 === l.y1) { line.id = l.id line.attributes = l.attributes } }) }) + canvas?.add(newPolygon) - canvas?.remove(polygon) + canvas?.renderAll() + + return newPolygon } function arePointsEqual(point1, point2) { From fa0cad7707ef37c47ca9e7587a6f48c1dbfcdadf Mon Sep 17 00:00:00 2001 From: Jaeyoung Lee Date: Fri, 18 Oct 2024 10:46:32 +0900 Subject: [PATCH 04/13] =?UTF-8?q?=EB=AA=85=EC=B9=AD=20=EC=B6=94=EA=B0=80.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/common/common.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/common/common.js b/src/common/common.js index 1ca3e8ca..2319279b 100644 --- a/src/common/common.js +++ b/src/common/common.js @@ -67,8 +67,13 @@ export const LINE_TYPE = { }, SUBLINE: { /** - * + * 추녀 / 마루 / 박공 / 지붕골 / 박공단 */ + HIP: 'hip', + RIDGE: 'ridge', + GABLE: 'gable', + VALLEY: 'valley', + VERGE: 'verge', }, } From 637c7a75d5c1a0627e248e116df391ffaf5268c4 Mon Sep 17 00:00:00 2001 From: Jaeyoung Lee Date: Fri, 25 Oct 2024 10:52:54 +0900 Subject: [PATCH 05/13] =?UTF-8?q?=EB=B3=80=EB=B3=84=EB=A1=9C=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EC=9E=91=EC=97=85=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/dictionary.txt | 1 + src/common/common.js | 1 + src/components/Roof2.jsx | 56 +- src/components/fabric/QPolygon.js | 66 +- src/hooks/useMode.js | 53 +- src/util/canvas-util.js | 10 + src/util/qpolygon-utils.js | 1348 +++++++++++++++++------------ 7 files changed, 935 insertions(+), 600 deletions(-) diff --git a/docs/dictionary.txt b/docs/dictionary.txt index 7e07124d..f5f461b7 100644 --- a/docs/dictionary.txt +++ b/docs/dictionary.txt @@ -18,6 +18,7 @@ 출폭: offset 폭: width 경사(구배): pitch +각도: degree 이구배: doublePitch 소매: sleeve 개구: openSpace diff --git a/src/common/common.js b/src/common/common.js index 2319279b..5a14fa38 100644 --- a/src/common/common.js +++ b/src/common/common.js @@ -115,5 +115,6 @@ export const INPUT_TYPE = { export const POLYGON_TYPE = { ROOF: 'roof', + WALL: 'wall', TRESTLE: 'trestle', } diff --git a/src/components/Roof2.jsx b/src/components/Roof2.jsx index 2fe86ecc..fad075bd 100644 --- a/src/components/Roof2.jsx +++ b/src/components/Roof2.jsx @@ -39,7 +39,7 @@ import QEmptyContextMenu from '@/components/common/context-menu/QEmptyContextMen import InitSettingsModal from './InitSettingsModal' import GridSettingsModal from './GridSettingsModal' import { SurfaceShapeModal } from '@/components/ui/SurfaceShape' -import { changeHipAndGableRoof, drawDirectionStringToArrow } from '@/util/qpolygon-utils' +import { changeCurrentRoof, drawDirectionStringToArrow } from '@/util/qpolygon-utils' import ThumbnailList from '@/components/ui/ThumbnailLIst' import ObjectPlacement from '@/components/ui/ObjectPlacement' import { globalLocaleStore } from '@/store/localeAtom' @@ -431,7 +431,7 @@ export default function Roof2(props) { { x: 450, y: 850 }, ] - const polygon = new QPolygon(rectangleType1, { + const polygon = new QPolygon(rectangleType2, { fill: 'transparent', stroke: 'green', strokeWidth: 1, @@ -672,18 +672,52 @@ export default function Roof2(props) { canvas?.renderAll() } + const setHipRoof = () => { + const polygon = canvas?.getObjects().find((obj) => obj.name === 'roof') + const currentRoof = polygon.lines[2] + currentRoof.attributes.type = LINE_TYPE.WALLLINE.EAVES + currentRoof.attributes.offset = 50 + changeCurrentRoof(currentRoof, canvas) + } const setGableRoof = () => { - let offset = Number(prompt('gable roof offset', '50')) + const polygon = canvas?.getObjects().find((obj) => obj.name === 'roof') + const currentRoof = polygon.lines[2] + currentRoof.attributes.type = LINE_TYPE.WALLLINE.GABLE + currentRoof.attributes.offset = 30 + changeCurrentRoof(currentRoof, canvas) + } + const setHipAndGableRoof = () => { + let offset = Number(prompt('팔작지붕 폭', '50')) if (!isNaN(offset) && offset > 0) { const polygon = canvas?.getObjects().find((obj) => obj.name === 'roof') - const currentRoof = polygon.lines[3] + const currentRoof = polygon.lines[2] currentRoof.attributes.type = LINE_TYPE.WALLLINE.HIPANDGABLE currentRoof.attributes.width = offset - changeHipAndGableRoof(currentRoof, canvas) + changeCurrentRoof(currentRoof, canvas) } else { - alert('offset 은 0 보다 커야 함') + alert('폭은 0 보다 커야 함') } } + const setJerkInHeadRoof = () => { + let offset = Number(prompt('팔작지붕 폭', '50')) + if (!isNaN(offset) && offset > 0) { + const polygon = canvas?.getObjects().find((obj) => obj.name === 'roof') + const currentRoof = polygon.lines[2] + currentRoof.attributes.type = LINE_TYPE.WALLLINE.JERKINHEAD + currentRoof.attributes.width = offset + changeCurrentRoof(currentRoof, canvas) + } else { + alert('폭은 0 보다 커야 함') + } + } + const setWallRoof = () => { + let offset = Number(prompt('소매 폭', '0')) + const polygon = canvas?.getObjects().find((obj) => obj.name === 'roof') + const currentRoof = polygon.lines[2] + currentRoof.attributes.type = LINE_TYPE.WALLLINE.WALL + currentRoof.attributes.width = offset + changeCurrentRoof(currentRoof, canvas) + } return ( <> {canvas && ( @@ -810,19 +844,19 @@ export default function Roof2(props) { {/* */} {/*)}*/} - - - - - - diff --git a/src/locales/ja.json b/src/locales/ja.json index c57582b2..e4605537 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -814,7 +814,10 @@ "estimate.detail.sepcialEstimateProductInfo.calcFormula2": "PKG単価 (W)×PKG容量(W)", "estimate.detail.header.showPrice": "価格表示", "estimate.detail.showPrice.btn1": "Pricing", - "estimate.detail.showPrice.description": "クリックして製品の特異性を確認する", + "estimate.detail.showPrice.description1": "製品価格 OPEN", + "estimate.detail.showPrice.description2": "追加, 変更資材", + "estimate.detail.showPrice.description3": "添付必須", + "estimate.detail.showPrice.description4": "クリックして製品の特異性を確認する", "estimate.detail.showPrice.btn2": "製品を追加", "estimate.detail.showPrice.btn3": "製品削除" } diff --git a/src/locales/ko.json b/src/locales/ko.json index 1cb2c279..03443a0b 100644 --- a/src/locales/ko.json +++ b/src/locales/ko.json @@ -820,7 +820,10 @@ "estimate.detail.sepcialEstimateProductInfo.calcFormula2": "PKG단가(W) * PKG용량(W)", "estimate.detail.header.showPrice": "가격표시", "estimate.detail.showPrice.btn1": "Pricing", - "estimate.detail.showPrice.description": "클릭하여 제품 특이사항 확인", + "estimate.detail.showPrice.description1": "제품 가격 OPEN", + "estimate.detail.showPrice.description2": "추가, 변경 자재", + "estimate.detail.showPrice.description3": "첨부필수", + "estimate.detail.showPrice.description4": "클릭하여 제품 특이사항 확인", "estimate.detail.showPrice.btn2": "제품추가", "estimate.detail.showPrice.btn3": "제품삭제" } From 0adfe0916c496e3503818cf4ed7eabbb848ccf26 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Mon, 4 Nov 2024 14:46:38 +0900 Subject: [PATCH 10/13] =?UTF-8?q?polygon=20name=20=EC=88=98=EC=A0=95,=20?= =?UTF-8?q?=EC=A7=80=EB=B6=95=20=EB=82=98=EB=88=84=EA=B8=B0=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/option/useCanvasSetting.js | 8 +- src/hooks/option/useFirstOption.js | 8 +- src/hooks/roofcover/useAuxiliaryDrawing.js | 19 +- src/hooks/roofcover/useEavesGableEdit.js | 8 +- src/hooks/roofcover/useMovementSetting.js | 5 +- src/hooks/roofcover/usePropertiesSetting.js | 4 +- .../roofcover/useRoofAllocationSetting.js | 11 +- .../roofcover/useRoofShapePassivitySetting.js | 10 +- src/hooks/roofcover/useRoofShapeSetting.js | 10 +- src/store/settingAtom.js | 2 +- src/util/canvas-util.js | 6 +- src/util/qpolygon-utils.js | 257 +++++++++++++++++- 12 files changed, 299 insertions(+), 49 deletions(-) diff --git a/src/hooks/option/useCanvasSetting.js b/src/hooks/option/useCanvasSetting.js index 0f87e156..2b2ea3c5 100644 --- a/src/hooks/option/useCanvasSetting.js +++ b/src/hooks/option/useCanvasSetting.js @@ -225,9 +225,9 @@ export function useCanvasSetting() { const option1 = settingModalFirstOptions.option1 // 'allocDisplay' 할당 표시 - // 'outlineDisplay' 외벽선 표시 'outerLine', 'wallLine' + // 'outlineDisplay' 외벽선 표시 'outerLine', POLYGON_TYPE.WALL // 'gridDisplay' 그리드 표시 'lindGrid', 'dotGrid' - // 'lineDisplay' 지붕선 표시 'roof', 'roofBase' + // 'lineDisplay' 지붕선 표시 'roof', POLYGON_TYPE.ROOF // 'wordDisplay' 문자 표시 // 'circuitNumDisplay' 회로번호 표시 // 'flowDisplay' 흐름방향 표시 'arrow' @@ -244,13 +244,13 @@ export function useCanvasSetting() { optionName = ['1'] break case 'outlineDisplay': //외벽선 표시 - optionName = ['outerLine', 'wallLine'] + optionName = ['outerLine', POLYGON_TYPE.WALL] break case 'gridDisplay': //그리드 표시 optionName = ['lindGrid', 'dotGrid'] break case 'lineDisplay': //지붕선 표시 - optionName = ['roof', 'roofBase'] + optionName = ['roof', POLYGON_TYPE.ROOF] break case 'wordDisplay': //문자 표시 optionName = ['6'] diff --git a/src/hooks/option/useFirstOption.js b/src/hooks/option/useFirstOption.js index c3778587..4b85b4c4 100644 --- a/src/hooks/option/useFirstOption.js +++ b/src/hooks/option/useFirstOption.js @@ -12,9 +12,9 @@ export function useFirstOption() { const option1 = settingModalFirstOptions.option1 // 'allocDisplay' 할당 표시 - // 'outlineDisplay' 외벽선 표시 'outerLine', 'wallLine' + // 'outlineDisplay' 외벽선 표시 'outerLine', POLYGON_TYPE.WALL // 'gridDisplay' 그리드 표시 'lindGrid', 'dotGrid' - // 'lineDisplay' 지붕선 표시 'roof', 'roofBase' + // 'lineDisplay' 지붕선 표시 'roof', POLYGON_TYPE.ROOF // 'wordDisplay' 문자 표시 // 'circuitNumDisplay' 회로번호 표시 // 'flowDisplay' 흐름방향 표시 'arrow' @@ -30,13 +30,13 @@ export function useFirstOption() { optionName = ['1'] break case 'outlineDisplay': //외벽선 표시 - optionName = ['outerLine', 'wallLine'] + optionName = ['outerLine', POLYGON_TYPE.WALL] break case 'gridDisplay': //그리드 표시 optionName = ['lineGrid', 'dotGrid', 'adsorptionPoint', 'tempGrid'] break case 'lineDisplay': //지붕선 표시 - optionName = ['roof', 'roofBase'] + optionName = ['roof', POLYGON_TYPE.ROOF] break case 'wordDisplay': //문자 표시 optionName = ['6'] diff --git a/src/hooks/roofcover/useAuxiliaryDrawing.js b/src/hooks/roofcover/useAuxiliaryDrawing.js index 956bcf8f..72b094c9 100644 --- a/src/hooks/roofcover/useAuxiliaryDrawing.js +++ b/src/hooks/roofcover/useAuxiliaryDrawing.js @@ -15,7 +15,7 @@ import { outerLineLength2State, outerLineTypeState, } from '@/store/outerLineAtom' -import { calculateIntersection, distanceBetweenPoints, findClosestPoint, polygonToTurfPolygon } from '@/util/canvas-util' +import { calculateIntersection, distanceBetweenPoints, findClosestPoint, isPointOnLine, polygonToTurfPolygon } from '@/util/canvas-util' import { fabric } from 'fabric' import { useAdsorptionPoint } from '@/hooks/useAdsorptionPoint' import { useSwal } from '@/hooks/useSwal' @@ -23,6 +23,7 @@ import { booleanPointInPolygon } from '@turf/turf' import { usePopup } from '@/hooks/usePopup' import { calculateAngle } from '@/util/qpolygon-utils' import { QPolygon } from '@/components/fabric/QPolygon' +import { POLYGON_TYPE } from '@/common/common' // 보조선 작성 export function useAuxiliaryDrawing(id) { @@ -80,7 +81,7 @@ export function useAuxiliaryDrawing(id) { useEffect(() => { // innerLines가 있을경우 삭제 - const roofs = canvas?.getObjects().filter((obj) => obj.name === 'roofBase') + const roofs = canvas?.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF) if (roofs.length === 0) { swalFire({ text: '지붕형상이 없습니다.' }) closePopup(id) @@ -561,7 +562,7 @@ export function useAuxiliaryDrawing(id) { return } - const roofBases = canvas.getObjects().filter((obj) => obj.name === 'roofBase') + const roofBases = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF) /*const allLines = [...auxiliaryLines] roofBases.forEach((roofBase) => { @@ -611,6 +612,7 @@ export function useAuxiliaryDrawing(id) { }, ) lineHistory.current.push(newLine) + lineHistory.current = lineHistory.current.filter((history) => history !== line1) removeLine(line1) intersectionPoints.current.push(...interSectionPointsWithRoofLines) return @@ -659,6 +661,7 @@ export function useAuxiliaryDrawing(id) { }) } lineHistory.current.push(newLine) + lineHistory.current = lineHistory.current.filter((history) => history !== line1) removeLine(line1) }) @@ -742,7 +745,7 @@ export function useAuxiliaryDrawing(id) { return } - const roofBases = canvas.getObjects().filter((obj) => obj.name === 'roofBase') + const roofBases = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF) //lineHistory.current에 있는 선들 중 startPoint와 endPoint가 겹치는 line은 제거 // 겹치는 선 하나는 canvas에서 제거한다. @@ -772,9 +775,13 @@ export function useAuxiliaryDrawing(id) { }) const roofInnerLines = innerLines.filter((line) => { const inPolygon1 = - tempPolygonPoints.some((point) => point.x === line.x1 && point.y === line.y1) || roofBase.inPolygon({ x: line.x1, y: line.y1 }) + tempPolygonPoints.some((point) => point.x === line.x1 && point.y === line.y1) || + roofBase.inPolygon({ x: line.x1, y: line.y1 }) || + roofBase.lines.some((line) => isPointOnLine(line, { x: line.x1, y: line.y1 })) const inPolygon2 = - tempPolygonPoints.some((point) => point.x === line.x2 && point.y === line.y2) || roofBase.inPolygon({ x: line.x2, y: line.y2 }) + tempPolygonPoints.some((point) => point.x === line.x2 && point.y === line.y2) || + roofBase.inPolygon({ x: line.x2, y: line.y2 }) || + roofBase.lines.some((line) => isPointOnLine(line, { x: line.x2, y: line.y2 })) if (inPolygon1 && inPolygon2) { line.attributes = { ...line.attributes, roofId: roofBase.id } diff --git a/src/hooks/roofcover/useEavesGableEdit.js b/src/hooks/roofcover/useEavesGableEdit.js index 7696dc7a..5619abfb 100644 --- a/src/hooks/roofcover/useEavesGableEdit.js +++ b/src/hooks/roofcover/useEavesGableEdit.js @@ -3,7 +3,7 @@ import { useRecoilValue } from 'recoil' import { ANGLE_TYPE, canvasState, currentAngleTypeSelector, pitchTextSelector } from '@/store/canvasAtom' import { useMessage } from '@/hooks/useMessage' import { useEvent } from '@/hooks/useEvent' -import { LINE_TYPE } from '@/common/common' +import { LINE_TYPE, POLYGON_TYPE } from '@/common/common' import { useLine } from '@/hooks/useLine' import { useMode } from '@/hooks/useMode' import { outerLineFixState } from '@/store/outerLineAtom' @@ -54,7 +54,7 @@ export function useEavesGableEdit(id) { }, []) useEffect(() => { - const wallLines = canvas.getObjects().filter((obj) => obj.name === 'wallLine') + const wallLines = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.WALL) wallLines.forEach((wallLine) => { convertPolygonToLines(wallLine) }) @@ -160,7 +160,7 @@ export function useEavesGableEdit(id) { attributes, }) - const roofBases = canvas?.getObjects().filter((obj) => obj.name === 'roofBase') + const roofBases = canvas?.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF) roofBases.forEach((roof) => { roof.innerLines.forEach((line) => { @@ -169,7 +169,7 @@ export function useEavesGableEdit(id) { canvas.remove(roof) }) - const wallLines = canvas.getObjects().filter((obj) => obj.name === 'wallLine') + const wallLines = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.WALL) const removeTargets = canvas.getObjects().filter((obj) => obj.name === 'pitchText') removeTargets.forEach((obj) => { canvas.remove(obj) diff --git a/src/hooks/roofcover/useMovementSetting.js b/src/hooks/roofcover/useMovementSetting.js index f0814a44..f09aee6b 100644 --- a/src/hooks/roofcover/useMovementSetting.js +++ b/src/hooks/roofcover/useMovementSetting.js @@ -4,6 +4,7 @@ import { usePopup } from '@/hooks/usePopup' import { useMessage } from '@/hooks/useMessage' import { useEffect, useRef, useState } from 'react' import { useEvent } from '@/hooks/useEvent' +import { POLYGON_TYPE } from '@/common/common' //동선이동 형 올림 내림 export function useMovementSetting(id) { @@ -41,7 +42,7 @@ export function useMovementSetting(id) { }, [type]) useEffect(() => { - const wallLines = canvas.getObjects().filter((obj) => obj.name === 'wallLine') // 기존 wallLine의 visible false + const wallLines = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.WALL) // 기존 wallLine의 visible false wallLines.forEach((line) => { line.set({ visible: false }) }) @@ -55,7 +56,7 @@ export function useMovementSetting(id) { addCanvasMouseEventListener('mouse:move', mouseMoveEvent) return () => { initEvent() - const wallLines = canvas.getObjects().filter((obj) => obj.name === 'wallLine') + const wallLines = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.WALL) wallLines.forEach((line) => { line.set({ visible: true }) }) diff --git a/src/hooks/roofcover/usePropertiesSetting.js b/src/hooks/roofcover/usePropertiesSetting.js index b999c001..3a1535a3 100644 --- a/src/hooks/roofcover/usePropertiesSetting.js +++ b/src/hooks/roofcover/usePropertiesSetting.js @@ -1,5 +1,5 @@ import { useEffect, useRef } from 'react' -import { LINE_TYPE } from '@/common/common' +import { LINE_TYPE, POLYGON_TYPE } from '@/common/common' import { useRecoilState, useRecoilValue, useResetRecoilState } from 'recoil' import { canvasState, currentObjectState } from '@/store/canvasAtom' import { useMode } from '@/hooks/useMode' @@ -135,7 +135,7 @@ export function usePropertiesSetting(id) { hideLine(line) }) - const wall = addPolygonByLines(lines, { name: 'wallLine', fill: 'transparent', stroke: 'black' }) + const wall = addPolygonByLines(lines, { name: POLYGON_TYPE.WALL, fill: 'transparent', stroke: 'black' }) wall.lines = [...lines] diff --git a/src/hooks/roofcover/useRoofAllocationSetting.js b/src/hooks/roofcover/useRoofAllocationSetting.js index 3be745d6..c229549e 100644 --- a/src/hooks/roofcover/useRoofAllocationSetting.js +++ b/src/hooks/roofcover/useRoofAllocationSetting.js @@ -7,6 +7,7 @@ import { useSwal } from '@/hooks/useSwal' import { usePolygon } from '@/hooks/usePolygon' import { roofDisplaySelector } from '@/store/settingAtom' import { usePopup } from '@/hooks/usePopup' +import { POLYGON_TYPE } from '@/common/common' // 지붕면 할당 export function useRoofAllocationSetting(id) { @@ -81,12 +82,12 @@ export function useRoofAllocationSetting(id) { const [selectedRoofMaterial, setSelectedRoofMaterial] = useState(roofMaterials[0]) useEffect(() => { - const roofBases = canvas.getObjects().filter((obj) => obj.name === 'roofBase') + const roofBases = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF) if (roofBases.length === 0) { swalFire({ text: '할당할 지붕이 없습니다.' }) closePopup(id) } - // if (type === 'roofBase') { + // if (type === POLYGON_TYPE.ROOF) { // // 지붕면 할당 // // } else if ('roof') { @@ -104,8 +105,8 @@ export function useRoofAllocationSetting(id) { // 선택한 지붕재로 할당 const handleSave = () => { - const roofBases = canvas.getObjects().filter((obj) => obj.name === 'roofBase') - const wallLines = canvas.getObjects().filter((obj) => obj.name === 'wallLine') + const roofBases = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF) + const wallLines = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.WALL) roofBases.forEach((roofBase) => { try { splitPolygonWithLines(roofBase) @@ -117,7 +118,7 @@ export function useRoofAllocationSetting(id) { canvas.remove(line) }) - canvas.remove(roofBase) + // canvas.remove(roofBase) }) wallLines.forEach((wallLine) => { diff --git a/src/hooks/roofcover/useRoofShapePassivitySetting.js b/src/hooks/roofcover/useRoofShapePassivitySetting.js index ef7a4477..e0af3205 100644 --- a/src/hooks/roofcover/useRoofShapePassivitySetting.js +++ b/src/hooks/roofcover/useRoofShapePassivitySetting.js @@ -4,7 +4,7 @@ import { useEffect, useRef, useState } from 'react' import { useLine } from '@/hooks/useLine' import { useMessage } from '@/hooks/useMessage' import { useEvent } from '@/hooks/useEvent' -import { LINE_TYPE } from '@/common/common' +import { LINE_TYPE, POLYGON_TYPE } from '@/common/common' import { useMode } from '@/hooks/useMode' import { usePolygon } from '@/hooks/usePolygon' import { outerLineFixState } from '@/store/outerLineAtom' @@ -60,7 +60,7 @@ export function useRoofShapePassivitySetting(id) { useEffect(() => { if (!isLoading) return addCanvasMouseEventListener('mouse:down', mouseDown) - const wallLines = canvas.getObjects().filter((obj) => obj.name === 'wallLine') + const wallLines = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.WALL) canvas?.remove(...wallLines) @@ -185,7 +185,7 @@ export function useRoofShapePassivitySetting(id) { } const handleLineToPolygon = () => { - const roofBases = canvas.getObjects().filter((obj) => obj.name === 'roofBase') + const roofBases = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF) const exceptObjs = canvas.getObjects().filter((obj) => obj.name !== 'outerLine' && obj.parent?.name !== 'outerLine') const lines = canvas.getObjects().filter((obj) => obj.name === 'outerLine') exceptObjs.forEach((obj) => { @@ -199,10 +199,10 @@ export function useRoofShapePassivitySetting(id) { let wall if (isFix.current) { - wall = addPolygonByLines(lines, { name: 'wallLine', fill: 'transparent', stroke: 'black' }) + wall = addPolygonByLines(lines, { name: POLYGON_TYPE.WALL, fill: 'transparent', stroke: 'black' }) } else { // 그냥 닫을 경우 처리 - wall = addPolygonByLines([...initLines.current], { name: 'wallLine', fill: 'transparent', stroke: 'black' }) + wall = addPolygonByLines([...initLines.current], { name: POLYGON_TYPE.WALL, fill: 'transparent', stroke: 'black' }) lines.forEach((line, idx) => { line.attributes = initLines.current[idx].attributes }) diff --git a/src/hooks/roofcover/useRoofShapeSetting.js b/src/hooks/roofcover/useRoofShapeSetting.js index ae886ce9..2823415d 100644 --- a/src/hooks/roofcover/useRoofShapeSetting.js +++ b/src/hooks/roofcover/useRoofShapeSetting.js @@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from 'react' import { useMessage } from '@/hooks/useMessage' import { useRecoilValue, useSetRecoilState } from 'recoil' import { ANGLE_TYPE, canvasState, currentAngleTypeSelector, currentMenuState, currentObjectState, pitchTextSelector } from '@/store/canvasAtom' -import { LINE_TYPE } from '@/common/common' +import { LINE_TYPE, POLYGON_TYPE } from '@/common/common' import { usePolygon } from '@/hooks/usePolygon' import { useMode } from '@/hooks/useMode' import { useLine } from '@/hooks/useLine' @@ -129,7 +129,7 @@ export function useRoofShapeSetting(id) { useEffect(() => { if (shapeNum === 4) { - canvas?.remove(canvas.getObjects().find((obj) => obj.name === 'wallLine')) + canvas?.remove(canvas.getObjects().find((obj) => obj.name === POLYGON_TYPE.WALL)) const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine') outerLines.forEach((line) => { showLine(line) @@ -376,20 +376,20 @@ export function useRoofShapeSetting(id) { // 기존 wallLine, roofBase 제거 canvas .getObjects() - .filter((obj) => obj.name === 'wallLine') + .filter((obj) => obj.name === POLYGON_TYPE.WALL) .forEach((line) => { canvas.remove(line) }) canvas .getObjects() - .filter((obj) => obj.name === 'roofBase') + .filter((obj) => obj.name === POLYGON_TYPE.ROOF) .forEach((obj) => { canvas.remove(...obj.innerLines) canvas.remove(obj) }) - const polygon = addPolygonByLines(outerLines, { name: 'wallLine' }) + const polygon = addPolygonByLines(outerLines, { name: POLYGON_TYPE.WALL }) polygon.lines = [...outerLines] addPitchTextsByOuterLines() diff --git a/src/store/settingAtom.js b/src/store/settingAtom.js index 9679d2d7..fede515e 100644 --- a/src/store/settingAtom.js +++ b/src/store/settingAtom.js @@ -5,7 +5,7 @@ export const settingModalFirstOptionsState = atom({ default: { option1: [ { id: 1, column: 'allocDisplay', name: 'modal.canvas.setting.first.option.alloc', selected: false }, - { id: 2, column: 'outlineDisplay', name: 'modal.canvas.setting.first.option.outline', selected: false }, + { id: 2, column: 'outlineDisplay', name: 'modal.canvas.setting.first.option.outline', selected: true }, { id: 3, column: 'gridDisplay', name: 'modal.canvas.setting.first.option.grid', selected: false }, { id: 4, column: 'lineDisplay', name: 'modal.canvas.setting.first.option.roof.line', selected: false }, { id: 5, column: 'wordDisplay', name: 'modal.canvas.setting.first.option.word', selected: false }, diff --git a/src/util/canvas-util.js b/src/util/canvas-util.js index be2249a6..1a78f991 100644 --- a/src/util/canvas-util.js +++ b/src/util/canvas-util.js @@ -521,9 +521,11 @@ export function isPointOnLine(line, point) { const a = line.y2 - line.y1 const b = line.x1 - line.x2 const c = line.x2 * line.y1 - line.x1 * line.y2 - return a * point.x + b * point.y + c === 0 -} + const result = Math.abs(a * point.x + b * point.y + c) / 100 + // 점이 선 위에 있는지 확인 + return result <= 10 +} /** * 점과 가까운 line 찾기 * @param point diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js index 8e9c3553..65cbc96d 100644 --- a/src/util/qpolygon-utils.js +++ b/src/util/qpolygon-utils.js @@ -1,6 +1,14 @@ import { fabric } from 'fabric' import { QLine } from '@/components/fabric/QLine' -import { calculateIntersection, distanceBetweenPoints, findClosestPoint, getDegreeByChon, getDirectionByPoint } from '@/util/canvas-util' +import { + calculateIntersection, + distanceBetweenPoints, + findClosestPoint, + getDegreeByChon, + getDirectionByPoint, + isPointOnLine, +} from '@/util/canvas-util' + import { QPolygon } from '@/components/fabric/QPolygon' import * as turf from '@turf/turf' import { LINE_TYPE, POLYGON_TYPE } from '@/common/common' @@ -957,10 +965,13 @@ export default function offsetPolygon(vertices, offset) { } } -export const splitPolygonWithLines = (polygon) => { +/*export const splitPolygonWithLines = (polygon) => { const roofs = [] const allLines = [...polygon.innerLines] + const polygonLines = polygon.lines + const innerLines = polygon.innerLines + allLines.forEach((line) => { line.startPoint = { x: line.x1, y: line.y1 } line.endPoint = { x: line.x2, y: line.y2 } @@ -983,10 +994,10 @@ export const splitPolygonWithLines = (polygon) => { }) }) - /** + /!** * 좌표 테스트용 - */ - /*allLines.forEach((line) => { + *!/ + /!*allLines.forEach((line) => { const text = new fabric.Text(`(${line.startPoint.x},${line.startPoint.y})`, { left: line.startPoint.x, top: line.startPoint.y, @@ -1015,10 +1026,10 @@ export const splitPolygonWithLines = (polygon) => { polygon.canvas.add(text) polygon.canvas.renderAll() - })*/ - /** + })*!/ + /!** * 좌표 테스트용 끝 - */ + *!/ polygon.points.forEach((point, index) => { allLines.forEach((line) => { @@ -1164,6 +1175,233 @@ export const splitPolygonWithLines = (polygon) => { polygon.canvas.add(roof) polygon.canvas.renderAll() }) +}*/ +export const splitPolygonWithLines = (polygon) => { + const canvas = polygon.canvas + polygon.set({ visible: false }) + let innerLines = [...polygon.innerLines] + let polygonLines = [...polygon.lines] + const roofs = [] + + let delIndexs = [] + let newLines = [] + + polygonLines.forEach((line, index) => { + line.tempIndex = index + innerLines.forEach((innerLine) => { + let newLine1, newLine2 + if (isPointOnLine(line, innerLine.startPoint)) { + // 해당 line을 startPoint로 나눈 line2개를 canvas에 추가 하고 기존 line을 제거한다. + newLine1 = new QLine([line.x1, line.y1, innerLine.startPoint.x, innerLine.startPoint.y], { + fontSize: polygon.fontSize, + stroke: 'black', + strokeWidth: 3, + }) + + newLine2 = new QLine([innerLine.startPoint.x, innerLine.startPoint.y, line.x2, line.y2], { + fontSize: polygon.fontSize, + stroke: 'black', + strokeWidth: 3, + }) + delIndexs.push(polygonLines.indexOf(line)) + canvas.remove(polygonLines[polygonLines.indexOf(line)]) + if (newLine1.length / 10 > 10) { + newLines.push(newLine1) + } + if (newLine2.length / 10 > 10) { + newLines.push(newLine2) + } + } + if (isPointOnLine(line, innerLine.endPoint)) { + newLine1 = new QLine([line.x1, line.y1, innerLine.endPoint.x, innerLine.endPoint.y], { + fontSize: polygon.fontSize, + stroke: 'black', + strokeWidth: 3, + }) + + newLine2 = new QLine([innerLine.endPoint.x, innerLine.endPoint.y, line.x2, line.y2], { + fontSize: polygon.fontSize, + stroke: 'black', + strokeWidth: 3, + }) + delIndexs.push(polygonLines.indexOf(line)) + canvas.remove(polygonLines[polygonLines.indexOf(line)]) + if (newLine1.length / 10 > 10) { + newLines.push(newLine1) + } + if (newLine2.length / 10 > 10) { + newLines.push(newLine2) + } + } + }) + }) + polygonLines = polygonLines.filter((line) => !delIndexs.includes(line.tempIndex)) + polygonLines = [...polygonLines, ...newLines] + + const allLines = [...polygonLines, ...innerLines] + + /** + * 왼쪽 상단을 startPoint로 전부 변경 + */ + allLines.forEach((line) => { + let startPoint // 시작점 + let endPoint // 끝점 + if (line.x1 < line.x2) { + startPoint = { x: line.x1, y: line.y1 } + endPoint = { x: line.x2, y: line.y2 } + } else if (line.x1 > line.x2) { + startPoint = { x: line.x2, y: line.y2 } + endPoint = { x: line.x1, y: line.y1 } + } else { + if (line.y1 < line.y2) { + startPoint = { x: line.x1, y: line.y1 } + endPoint = { x: line.x2, y: line.y2 } + } else { + startPoint = { x: line.x2, y: line.y2 } + endPoint = { x: line.x1, y: line.y1 } + } + } + + line.startPoint = startPoint + line.endPoint = endPoint + }) + + polygonLines.forEach((line) => { + const startPoint = line.startPoint // 시작점 + let arrivalPoint = line.endPoint // 도착점 + + let currentPoint = startPoint + const roofPoints = [startPoint] + + const startLine = line + const visitPoints = [startPoint] + const visitLines = [startLine] + let cnt = 0 + + while (!isSamePoint(currentPoint, arrivalPoint)) { + line.set({ stroke: 'red' }) + canvas.renderAll() + let nextLines = allLines.filter( + (line2) => + (isSamePoint(line2.startPoint, currentPoint) || isSamePoint(line2.endPoint, currentPoint)) && + line !== line2 && + innerLines.includes(line2) && + !visitLines.includes(line2), + ) + + if (nextLines.length === 0) { + nextLines = allLines.filter( + (line2) => + (isSamePoint(line2.startPoint, currentPoint) || isSamePoint(line2.endPoint, currentPoint)) && + line !== line2 && + !visitLines.includes(line2), + ) + } + + if (!nextLines) { + break + } + + let comparisonPoints = [] + + nextLines.forEach((nextLine) => { + if (isSamePoint(nextLine.startPoint, currentPoint)) { + comparisonPoints.push(nextLine.endPoint) + } else { + comparisonPoints.push(nextLine.startPoint) + } + }) + + comparisonPoints = comparisonPoints.filter((point) => !visitPoints.some((visitPoint) => isSamePoint(visitPoint, point))) + comparisonPoints = comparisonPoints.filter((point) => !isSamePoint(point, currentPoint)) + + const minDistancePoint = comparisonPoints.reduce((prev, current) => { + const prevDistance = Math.sqrt(Math.pow(prev.x - arrivalPoint.x, 2) + Math.pow(prev.y - arrivalPoint.y, 2)) + const currentDistance = Math.sqrt(Math.pow(current.x - arrivalPoint.x, 2) + Math.pow(current.y - arrivalPoint.y, 2)) + + return prevDistance < currentDistance ? prev : current + }, comparisonPoints[0]) + + nextLines.forEach((nextLine) => { + if (isSamePoint(nextLine.startPoint, minDistancePoint) || isSamePoint(nextLine.endPoint, minDistancePoint)) { + visitLines.push(nextLine) + } + }) + + currentPoint = { ...minDistancePoint } + roofPoints.push(currentPoint) + cnt++ + if (cnt > 100) { + throw new Error('무한루프') + } + } + roofs.push(roofPoints) + }) + + const newRoofs = removeDuplicatePolygons(roofs) + console.log(newRoofs) + newRoofs.forEach((roofPoint, index) => { + let defense, pitch + const direction = getDirectionByPoint(roofPoint[0], roofPoint[roofPoint.length - 1]) + + switch (direction) { + case 'top': + defense = 'east' + break + case 'right': + defense = 'south' + break + case 'bottom': + defense = 'west' + break + case 'left': + defense = 'north' + break + } + pitch = polygon.lines[index].attributes?.pitch ?? 0 + + const roof = new QPolygon(roofPoint, { + fontSize: polygon.fontSize, + stroke: 'black', + fill: 'transparent', + strokeWidth: 3, + name: POLYGON_TYPE.ROOF, + originX: 'center', + originY: 'center', + selectable: true, + defense: defense, + direction: defense, + pitch: pitch, + }) + + polygon.canvas.add(roof) + canvas.remove(polygon) + polygon.canvas.renderAll() + }) +} + +const removeDuplicatePolygons = (polygons) => { + const uniquePolygons = [] + + polygons.forEach((polygon) => { + const sortedPolygon = polygon + .map((point) => `${Math.floor(point.x)},${Math.floor(point.y)}`) + .sort() + .join('|') + const isDuplicate = uniquePolygons.some((uniquePolygon) => { + const sortedUniquePolygon = uniquePolygon + .map((point) => `${Math.floor(point.x)},${Math.floor(point.y)}`) + .sort() + .join('|') + return sortedPolygon === sortedUniquePolygon + }) + + if (!isDuplicate) { + uniquePolygons.push(polygon) + } + }) + + return uniquePolygons } const isSamePoint = (a, b) => { @@ -1218,6 +1456,7 @@ export const drawRidgeRoof = (roofId, canvas) => { * @param canvas */ const drawRidge = (roof, canvas) => { + debugger const wallLines = canvas?.getObjects().find((object) => object.name === POLYGON_TYPE.WALL && object.attributes.roofId === roof.id).lines // 외벽의 라인 const roofLines = roof.lines // 지붕의 라인 let ridgeRoof = [] @@ -3312,7 +3551,7 @@ function createRoofPaddingPolygon(polygon, lines, arcSegments = 0) { } function arePointsEqual(point1, point2) { - return point1.x === point2.x && point1.y === point2.y + return Math.abs(point1.x - point2.x) < 1 && Math.abs(point1.y - point2.y) - 1 } function arraysHaveSamePoints(array1, array2) { From c60db3cda56005746c34a3fc55f97cf1bde819c0 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Mon, 4 Nov 2024 15:07:27 +0900 Subject: [PATCH 11/13] =?UTF-8?q?=EA=B0=99=EC=9D=80=EC=A2=8C=ED=91=9C?= =?UTF-8?q?=ED=8C=90=EB=8B=A8=201=EC=98=A4=EC=B0=A8=20=ED=97=88=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util/qpolygon-utils.js | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js index 65cbc96d..a574c393 100644 --- a/src/util/qpolygon-utils.js +++ b/src/util/qpolygon-utils.js @@ -1380,22 +1380,27 @@ export const splitPolygonWithLines = (polygon) => { }) } -const removeDuplicatePolygons = (polygons) => { +function normalizePoint(point) { + return { + x: Math.round(point.x), + y: Math.round(point.y), + } +} + +function arePolygonsEqual(polygon1, polygon2) { + if (polygon1.length !== polygon2.length) return false + + const normalizedPolygon1 = polygon1.map(normalizePoint).sort((a, b) => a.x - b.x || a.y - b.y) + const normalizedPolygon2 = polygon2.map(normalizePoint).sort((a, b) => a.x - b.x || a.y - b.y) + + return normalizedPolygon1.every((point, index) => arePointsEqual(point, normalizedPolygon2[index])) +} + +function removeDuplicatePolygons(polygons) { const uniquePolygons = [] polygons.forEach((polygon) => { - const sortedPolygon = polygon - .map((point) => `${Math.floor(point.x)},${Math.floor(point.y)}`) - .sort() - .join('|') - const isDuplicate = uniquePolygons.some((uniquePolygon) => { - const sortedUniquePolygon = uniquePolygon - .map((point) => `${Math.floor(point.x)},${Math.floor(point.y)}`) - .sort() - .join('|') - return sortedPolygon === sortedUniquePolygon - }) - + const isDuplicate = uniquePolygons.some((uniquePolygon) => arePolygonsEqual(polygon, uniquePolygon)) if (!isDuplicate) { uniquePolygons.push(polygon) } @@ -3551,7 +3556,7 @@ function createRoofPaddingPolygon(polygon, lines, arcSegments = 0) { } function arePointsEqual(point1, point2) { - return Math.abs(point1.x - point2.x) < 1 && Math.abs(point1.y - point2.y) - 1 + return Math.abs(point1.x - point2.x) <= 1 && Math.abs(point1.y - point2.y) <= 1 } function arraysHaveSamePoints(array1, array2) { From 19e22ba9eaaa105e10ff728999b2ae394003ae82 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Mon, 4 Nov 2024 15:15:36 +0900 Subject: [PATCH 12/13] =?UTF-8?q?=EB=B3=B4=EC=A1=B0=EC=84=A0=20=EC=9E=90?= =?UTF-8?q?=EB=A5=B4=EA=B8=B0=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/roofcover/useAuxiliaryDrawing.js | 31 ++++++++++++++++++++++ src/util/qpolygon-utils.js | 1 - 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/hooks/roofcover/useAuxiliaryDrawing.js b/src/hooks/roofcover/useAuxiliaryDrawing.js index 72b094c9..46311ad9 100644 --- a/src/hooks/roofcover/useAuxiliaryDrawing.js +++ b/src/hooks/roofcover/useAuxiliaryDrawing.js @@ -616,6 +616,37 @@ export function useAuxiliaryDrawing(id) { removeLine(line1) intersectionPoints.current.push(...interSectionPointsWithRoofLines) return + } else if (interSectionPointsWithRoofLines.length === 1) { + //지붕선과 만나는 점이 하나일 경우 + const distance1 = distanceBetweenPoints({ x: line1.x1, y: line1.y1 }, interSectionPointsWithRoofLines[0]) + const distance2 = distanceBetweenPoints({ x: line1.x2, y: line1.y2 }, interSectionPointsWithRoofLines[0]) + + if (!(distance1 === 0 || distance2 === 0)) { + if (distance1 >= distance2) { + const newLine = addLine([line1.x1, line1.y1, interSectionPointsWithRoofLines[0].x, interSectionPointsWithRoofLines[0].y], { + stroke: 'black', + strokeWidth: 1, + selectable: false, + name: 'auxiliaryLine', + isFixed: true, + }) + lineHistory.current.push(newLine) + lineHistory.current = lineHistory.current.filter((history) => history !== line1) + removeLine(line1) + } else { + const newLine = addLine([line1.x2, line1.y2, interSectionPointsWithRoofLines[0].x, interSectionPointsWithRoofLines[0].y], { + stroke: 'black', + strokeWidth: 1, + selectable: false, + name: 'auxiliaryLine', + isFixed: true, + }) + lineHistory.current.push(newLine) + lineHistory.current = lineHistory.current.filter((history) => history !== line1) + removeLine(line1) + } + intersectionPoints.current.push(interSectionPointsWithRoofLines[0]) + } } //보조선과 만나는 점을 찾는다. diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js index a574c393..1e82aefb 100644 --- a/src/util/qpolygon-utils.js +++ b/src/util/qpolygon-utils.js @@ -1461,7 +1461,6 @@ export const drawRidgeRoof = (roofId, canvas) => { * @param canvas */ const drawRidge = (roof, canvas) => { - debugger const wallLines = canvas?.getObjects().find((object) => object.name === POLYGON_TYPE.WALL && object.attributes.roofId === roof.id).lines // 외벽의 라인 const roofLines = roof.lines // 지붕의 라인 let ridgeRoof = [] From 4891eb50aa08c70d3fde895a7e4e7722241fb384 Mon Sep 17 00:00:00 2001 From: "hyojun.choi" Date: Mon, 4 Nov 2024 16:02:02 +0900 Subject: [PATCH 13/13] =?UTF-8?q?=EC=A7=80=EB=B6=95=EB=A9=B4=20=ED=95=A0?= =?UTF-8?q?=EB=8B=B9=20=EC=8B=9C=20=EB=B0=A9=ED=96=A5=20=EC=84=A4=EC=A0=95?= =?UTF-8?q?=20=EC=9E=91=EC=97=85=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/util/qpolygon-utils.js | 39 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js index 1e82aefb..0e0ac586 100644 --- a/src/util/qpolygon-utils.js +++ b/src/util/qpolygon-utils.js @@ -1339,10 +1339,45 @@ export const splitPolygonWithLines = (polygon) => { }) const newRoofs = removeDuplicatePolygons(roofs) - console.log(newRoofs) newRoofs.forEach((roofPoint, index) => { let defense, pitch - const direction = getDirectionByPoint(roofPoint[0], roofPoint[roofPoint.length - 1]) + const polygonLines = [...polygon.lines] + + let representLines = [] + let representLine + + // 지붕을 그리면서 기존 polygon의 line중 연결된 line을 찾는다. + polygonLines.forEach((line) => { + let startFlag = false + let endFlag = false + const startPoint = line.startPoint + const endPoint = line.endPoint + roofPoint.forEach((point, index) => { + if (isSamePoint(point, startPoint)) { + startFlag = true + } + if (isSamePoint(point, endPoint)) { + endFlag = true + } + }) + + if (startFlag && endFlag) { + representLines.push(line) + } + }) + + // representLines중 가장 긴 line을 찾는다. + representLines.forEach((line) => { + if (!representLine) { + representLine = line + } else { + if (representLine.length < line.length) { + representLine = line + } + } + }) + + const direction = representLine.direction switch (direction) { case 'top':