diff --git a/src/components/fabric/QPolygon.js b/src/components/fabric/QPolygon.js index ebf4e625..20be0c35 100644 --- a/src/components/fabric/QPolygon.js +++ b/src/components/fabric/QPolygon.js @@ -701,14 +701,14 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, { inPolygonImproved(point) { const vertices = this.points let inside = false - const testX = point.x - const testY = point.y + const testX = Number(point.x.toFixed(this.toFixed)) + const testY = Number(point.y.toFixed(this.toFixed)) for (let i = 0, j = vertices.length - 1; i < vertices.length; j = i++) { - const xi = vertices[i].x - const yi = vertices[i].y - const xj = vertices[j].x - const yj = vertices[j].y + const xi = Number(vertices[i].x.toFixed(this.toFixed)) + const yi = Number(vertices[i].y.toFixed(this.toFixed)) + const xj = Number(vertices[j].x.toFixed(this.toFixed)) + const yj = Number(vertices[j].y.toFixed(this.toFixed)) // 점이 정점 위에 있는지 확인 if (Math.abs(xi - testX) < 0.01 && Math.abs(yi - testY) < 0.01) { @@ -720,9 +720,16 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, { return true } - // Ray casting 알고리즘 - if (yi > testY !== yj > testY && testX < ((xj - xi) * (testY - yi)) / (yj - yi) + xi) { - inside = !inside + // Ray casting 알고리즘 - 부동소수점 정밀도 개선 + if (yi > testY !== yj > testY) { + const denominator = yj - yi + if (Math.abs(denominator) > 1e-10) { + // 0으로 나누기 방지 + const intersection = ((xj - xi) * (testY - yi)) / denominator + xi + if (testX < intersection) { + inside = !inside + } + } } } @@ -791,13 +798,15 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, { // pathOffset을 고려한 최종 좌표 계산 const pathOffset = this.get('pathOffset') const finalPoint = { - x: transformedPoint.x + pathOffset.x, - y: transformedPoint.y + pathOffset.y, + x: Number((transformedPoint.x + pathOffset.x).toFixed(this.toFixed)), + y: Number((transformedPoint.y + pathOffset.y).toFixed(this.toFixed)), } if (this.name === POLYGON_TYPE.ROOF && this.isFixed) { const isInside = this.inPolygonImproved(finalPoint) - this.set('selectable', isInside) + if (!this.selectable) { + this.set('selectable', isInside) + } return isInside } else { return this.inPolygonImproved(finalPoint) diff --git a/src/hooks/module/useTrestle.js b/src/hooks/module/useTrestle.js index 9fa75e67..8e234cf0 100644 --- a/src/hooks/module/useTrestle.js +++ b/src/hooks/module/useTrestle.js @@ -32,7 +32,10 @@ export const useTrestle = () => { const apply = () => { const notAllocationModules = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE && !obj.circuit) if (notAllocationModules.length > 0) { - swalFire({ text: '回路番号が設定されていないモジュールがあります。 番号を設定しなおすか、 パネルを削除してください。', icon: 'error' }) + swalFire({ + text: '回路番号が設定されていないモジュールがあります。 番号を設定しなおすか、 パネルを削除してください。', + icon: 'error', + }) setIsGlobalLoading(false) return } @@ -807,15 +810,12 @@ export const useTrestle = () => { const getAzimuth = (parent) => { const { moduleCompass, surfaceCompass, direction } = parent - if(surfaceCompass) { + if (surfaceCompass) { return -surfaceCompass } let resultAzimuth = moduleCompass - - - switch (direction) { case 'south': { return resultAzimuth @@ -1882,7 +1882,15 @@ export const useTrestle = () => { const { width, height } = { ...module } widthArr.push(width) heightArr.push(height) - centerPoints.push({ x, y, width: Math.floor(width), height: Math.floor(height), index, moduleInfo: module.moduleInfo, module }) + centerPoints.push({ + x, + y, + width: Math.floor(width), + height: Math.floor(height), + index, + moduleInfo: module.moduleInfo, + module, + }) }) //widthArr 중복 제거 1이상 차이가 나지 않으면 같은 너비로 간주 @@ -2055,8 +2063,8 @@ export const useTrestle = () => { /** * 디버깅용 - module.set('fill', originFill) - canvas.renderAll() + module.set('fill', originFill) + canvas.renderAll() */ if (bottomCell) { return @@ -2182,8 +2190,8 @@ export const useTrestle = () => { /** * 디버깅 용 - module.set('fill', originFill) - canvas.renderAll() + module.set('fill', originFill) + canvas.renderAll() */ if (leftTopCnt + rightTopCnt === 2) { @@ -3265,18 +3273,49 @@ export const useTrestle = () => { return result } + function countMatchingCircuitNumbers(arrays) { + let cnt = 0 + + // 모든 고유한 circuitNumber 찾기 + const allCircuitNumbers = new Set() + arrays.forEach((arr) => { + arr.forEach((item) => { + allCircuitNumbers.add(item.circuitNumber) + }) + }) + + // 각 circuitNumber가 몇 개의 배열에 나타나는지 세기 + allCircuitNumbers.forEach((circuitNum) => { + let arrayCount = 0 + + arrays.forEach((arr) => { + const hasCircuitNum = arr.some((item) => item.circuitNumber === circuitNum) + if (hasCircuitNum) { + arrayCount++ + } + }) + + // 2개 이상의 배열에 나타나는 경우에만 카운트 (배열 개수 - 1) + if (arrayCount >= 2) { + cnt += arrayCount - 1 + } + }) + + return cnt + } + // 양단 케이블 구하는 공식 const getTotalConnectCableCnt = () => { let cnt = 0 const surfaces = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE) const modules = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE) + surfaces.forEach((surface) => { const modules = surface.modules - const groups = groupByType(modules) - groups.forEach((group) => { - const result = groupPoints(group, surface) - cnt += result.length - 1 - }) + // 1. 현재 surface의 모듈들을 그룹화 + const groupInSurface = groupPoints(modules, surface) + + cnt += countMatchingCircuitNumbers(groupInSurface) }) const groupByCircuitAndSurfaceCnt = groupByCircuitAndSurface(modules) diff --git a/src/hooks/object/useObjectBatch.js b/src/hooks/object/useObjectBatch.js index 322f2531..accb6866 100644 --- a/src/hooks/object/useObjectBatch.js +++ b/src/hooks/object/useObjectBatch.js @@ -1,18 +1,17 @@ 'use client' -import { useEffect, useRef } from 'react' +import { useEffect } from 'react' import { useMessage } from '@/hooks/useMessage' import { useRecoilValue } from 'recoil' import { canvasState } from '@/store/canvasAtom' import { BATCH_TYPE, INPUT_TYPE, POLYGON_TYPE } from '@/common/common' import { useEvent } from '@/hooks/useEvent' import { + getDegreeByChon, + getTrianglePoints, pointsToTurfPolygon, polygonToTurfPolygon, rectToPolygon, - triangleToPolygon, - getDegreeByChon, toFixedWithoutRounding, - getTrianglePoints, } from '@/util/canvas-util' import { useSwal } from '@/hooks/useSwal' import * as turf from '@turf/turf' @@ -23,6 +22,7 @@ import { fontSelector } from '@/store/fontAtom' import { useRoofFn } from '@/hooks/common/useRoofFn' import { roofDisplaySelector } from '@/store/settingAtom' import { usePopup } from '@/hooks/usePopup' +import { useMouse } from '@/hooks/useMouse' export function useObjectBatch({ isHidden, setIsHidden }) { const { getMessage } = useMessage() @@ -35,6 +35,7 @@ export function useObjectBatch({ isHidden, setIsHidden }) { const lengthTextFont = useRecoilValue(fontSelector('lengthText')) const roofDisplay = useRecoilValue(roofDisplaySelector) const { closePopup } = usePopup() + const { getIntersectMousePoint } = useMouse() useEffect(() => { if (canvas) { @@ -59,7 +60,7 @@ export function useObjectBatch({ isHidden, setIsHidden }) { console.log('event', e) if (e.target && e.target instanceof fabric.Group) { - const pointer = canvas.getPointer(e.e) + const pointer = getIntersectMousePoint(e) const objects = e.target._objects // 클릭한 위치에 있는 객체 찾기 @@ -98,7 +99,7 @@ export function useObjectBatch({ isHidden, setIsHidden }) { if (selectedType === INPUT_TYPE.FREE) { addCanvasMouseEventListener('mouse:down', (e) => { isDown = true - const pointer = canvas.getPointer(e.e) + const pointer = getIntersectMousePoint(e) surfaceShapePolygons.forEach((surface) => { if (surface.inPolygon({ x: pointer.x, y: pointer.y })) { @@ -140,7 +141,7 @@ export function useObjectBatch({ isHidden, setIsHidden }) { if (!isDown) return if (selectedSurface) { - const pointer = canvas.getPointer(e.e) + const pointer = getIntersectMousePoint(e) const width = pointer.x - origX const height = pointer.y - origY @@ -208,7 +209,7 @@ export function useObjectBatch({ isHidden, setIsHidden }) { if (!isDown) return canvas?.remove(...canvas?.getObjects().filter((obj) => obj.name === objTempName)) //움직일때 일단 지워가면서 움직임 - const pointer = canvas.getPointer(e.e) + const pointer = getIntersectMousePoint(e) surfaceShapePolygons.forEach((surface) => { if (surface.inPolygon({ x: pointer.x, y: pointer.y })) { @@ -220,8 +221,8 @@ export function useObjectBatch({ isHidden, setIsHidden }) { strokeWidth: 1, width: width, height: height, - left: pointer.x - width / 2, - top: pointer.y - height / 2, + left: pointer.x, + top: pointer.y, selectable: true, lockMovementX: true, lockMovementY: true, @@ -329,7 +330,7 @@ export function useObjectBatch({ isHidden, setIsHidden }) { if (!isDown) return canvas?.remove(...canvas?.getObjects().filter((obj) => obj.name === dormerTempName)) //움직일때 일단 지워가면서 움직임 - const pointer = canvas.getPointer(e.e) + const pointer = getIntersectMousePoint(e) surfaceShapePolygons.forEach((surface) => { if (surface.inPolygon({ x: pointer.x, y: pointer.y })) { @@ -679,7 +680,7 @@ export function useObjectBatch({ isHidden, setIsHidden }) { if (!isDown) return canvas?.remove(...canvas?.getObjects().filter((obj) => obj.name === dormerTempName)) //움직일때 일단 지워가면서 움직임 - const pointer = canvas.getPointer(e.e) + const pointer = getIntersectMousePoint(e) surfaceShapePolygons.forEach((surface) => { if (surface.inPolygon({ x: pointer.x, y: pointer.y })) { diff --git a/src/hooks/roofcover/useMovementSetting.js b/src/hooks/roofcover/useMovementSetting.js index 19e44d2e..671ad439 100644 --- a/src/hooks/roofcover/useMovementSetting.js +++ b/src/hooks/roofcover/useMovementSetting.js @@ -8,6 +8,7 @@ import { useSwal } from '@/hooks/useSwal' import { LINE_TYPE, POLYGON_TYPE } from '@/common/common' import Big from 'big.js' import { calcLinePlaneSize } from '@/util/qpolygon-utils' +import { useMouse } from '@/hooks/useMouse' //동선이동 형 올림 내림 export function useMovementSetting(id) { @@ -19,6 +20,7 @@ export function useMovementSetting(id) { const { initEvent, addCanvasMouseEventListener } = useEvent() const { closePopup } = usePopup() const { getMessage } = useMessage() + const { getIntersectMousePoint } = useMouse() const currentObject = useRecoilValue(currentObjectState) const selectedObject = useRef(null) const buttonType = [ @@ -179,7 +181,7 @@ export function useMovementSetting(id) { FOLLOW_LINE_REF.current = followLine canvas.on('mouse:move', (event) => { - const mousePos = canvas.getPointer(event.e) + const mousePos = getIntersectMousePoint(event) if (followLine.x1 === followLine.x2) { followLine.left = mousePos.x - 2 } else { @@ -211,8 +213,8 @@ export function useMovementSetting(id) { if (!target) return const { top: targetTop, left: targetLeft } = target - const currentX = Big(canvas.getPointer(e.e).x).round(0, Big.roundUp) - const currentY = Big(canvas.getPointer(e.e).y).round(0, Big.roundUp) + const currentX = Big(getIntersectMousePoint(e).x).round(0, Big.roundUp) + const currentY = Big(getIntersectMousePoint(e).y).round(0, Big.roundUp) let value = '' if (target.y1 === target.y2) { value = Big(targetTop).minus(currentY).times(10) @@ -414,7 +416,12 @@ export function useMovementSetting(id) { startPoint: { x: currentLine.x1 + deltaX, y: currentLine.y1 + deltaY }, endPoint: { x: currentLine.x2 + deltaX, y: currentLine.y2 + deltaY }, }) - const currentSize = calcLinePlaneSize({ x1: currentLine.x1, y1: currentLine.y1, x2: currentLine.x2, y2: currentLine.y2 }) + const currentSize = calcLinePlaneSize({ + x1: currentLine.x1, + y1: currentLine.y1, + x2: currentLine.x2, + y2: currentLine.y2, + }) currentLine.attributes.planeSize = currentSize currentLine.attributes.actualSize = currentSize diff --git a/src/hooks/useEvent.js b/src/hooks/useEvent.js index ff134358..3b8924cf 100644 --- a/src/hooks/useEvent.js +++ b/src/hooks/useEvent.js @@ -92,6 +92,7 @@ export function useEvent() { const wheelEvent = (opt) => { const delta = opt.e.deltaY // 휠 이동 값 (양수면 축소, 음수면 확대) let zoom = canvas.getZoom() // 현재 줌 값 + // console.log('zoom', zoom, 'delta', delta) zoom += delta > 0 ? -0.1 : 0.1 @@ -100,9 +101,9 @@ export function useEvent() { if (zoom < 0.5) zoom = 0.5 setCanvasZoom(Number((zoom * 100).toFixed(0))) - const { x, y } = getIntersectMousePoint(opt) + // 마우스 위치 기준으로 확대/축소 - canvas.zoomToPoint(new fabric.Point(x, y), zoom) + canvas.zoomToPoint({ x: opt.e.offsetX, y: opt.e.offsetY }, zoom) canvas.getObjects().forEach((obj) => { obj.setCoords() @@ -176,6 +177,8 @@ export function useEvent() { outerLinePoints.push({ x: line.x1, y: line.y1 }) }) + const allAuxiliaryLines = canvas.getObjects().filter((obj) => obj.name === 'auxiliaryLine') + const adsorptionPoints = [ ...getAdsorptionPoints(), ...roofAdsorptionPoints.current, @@ -183,6 +186,18 @@ export function useEvent() { ...intersectionPoints.current, ...innerLinePoints, ...outerLinePoints, + ...allAuxiliaryLines.map((line) => { + return { + x: line.x1, + y: line.y1, + } + }), + ...allAuxiliaryLines.map((line) => { + return { + x: line.x2, + y: line.y2, + } + }), ] if (dotLineGridSetting.LINE || canvas.getObjects().filter((obj) => ['lineGrid', 'tempGrid'].includes(obj.name)).length > 1) {