diff --git a/src/components/fabric/QPolygon.js b/src/components/fabric/QPolygon.js index c94bb316..264d62d1 100644 --- a/src/components/fabric/QPolygon.js +++ b/src/components/fabric/QPolygon.js @@ -387,7 +387,15 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, { this.canvas = canvas }, fillCellABType( - cell = { width: 50, height: 100, padding: 5, wallDirection: 'left', referenceDirection: 'none', startIndex: -1, isCellCenter: false }, + cell = { + width: 50, + height: 100, + padding: 5, + wallDirection: 'left', + referenceDirection: 'none', + startIndex: -1, + isCellCenter: false, + }, ) { const points = this.points @@ -689,6 +697,59 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, { return intersects % 2 === 1 }, + + inPolygonImproved(point) { + const vertices = this.points + let inside = false + const testX = point.x + const testY = point.y + + 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 + + // 점이 정점 위에 있는지 확인 + if (Math.abs(xi - testX) < 0.01 && Math.abs(yi - testY) < 0.01) { + return true + } + + // 점이 선분 위에 있는지 확인 + if (this.isPointOnSegment(point, { x: xi, y: yi }, { x: xj, y: yj })) { + return true + } + + // Ray casting 알고리즘 + if (((yi > testY) !== (yj > testY)) && + (testX < (xj - xi) * (testY - yi) / (yj - yi) + xi)) { + inside = !inside + } + } + + return inside + }, + + isPointOnSegment(point, segStart, segEnd) { + const tolerance = 0.1 + const dxSegment = segEnd.x - segStart.x + const dySegment = segEnd.y - segStart.y + const dxPoint = point.x - segStart.x + const dyPoint = point.y - segStart.y + + // 벡터의 외적을 계산하여 점이 선분 위에 있는지 확인 + const crossProduct = Math.abs(dxPoint * dySegment - dyPoint * dxSegment) + + if (crossProduct > tolerance) { + return false + } + + // 점이 선분의 범위 내에 있는지 확인 + const dotProduct = dxPoint * dxSegment + dyPoint * dySegment + const squaredLength = dxSegment * dxSegment + dySegment * dySegment + + return dotProduct >= 0 && dotProduct <= squaredLength + }, setCoords: function () { // 부모 클래스의 setCoords 호출 this.callSuper('setCoords') @@ -699,10 +760,10 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, { delete this.oCoords delete this.aCoords delete this.__corner - + // 다시 부모 setCoords 호출 this.callSuper('setCoords') - + // 한 번 더 강제로 bounding rect 재계산 this._clearCache && this._clearCache() } @@ -711,7 +772,7 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, { containsPoint: function (point) { // 먼저 좌표 업데이트 this.setCoords() - + // 캔버스 줌과 viewport transform 고려한 좌표 변환 let localPoint = point if (this.canvas) { @@ -722,13 +783,25 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, { localPoint = fabric.util.transformPoint(point, inverted) } } - + + // 오브젝트의 transform matrix를 고려한 좌표 변환 + const matrix = this.calcTransformMatrix() + const invertedMatrix = fabric.util.invertTransform(matrix) + const transformedPoint = fabric.util.transformPoint(localPoint, invertedMatrix) + + // pathOffset을 고려한 최종 좌표 계산 + const pathOffset = this.get('pathOffset') + const finalPoint = { + x: transformedPoint.x + pathOffset.x, + y: transformedPoint.y + pathOffset.y, + } + if (this.name === POLYGON_TYPE.ROOF && this.isFixed) { - const isInside = this.inPolygon(localPoint) + const isInside = this.inPolygonImproved(finalPoint) this.set('selectable', isInside) return isInside } else { - return this.callSuper('containsPoint', localPoint) + return this.inPolygonImproved(finalPoint) } }, diff --git a/src/components/floor-plan/modal/auxiliary/AuxiliaryDrawing.jsx b/src/components/floor-plan/modal/auxiliary/AuxiliaryDrawing.jsx index 1417b501..ee0f304b 100644 --- a/src/components/floor-plan/modal/auxiliary/AuxiliaryDrawing.jsx +++ b/src/components/floor-plan/modal/auxiliary/AuxiliaryDrawing.jsx @@ -8,8 +8,12 @@ import { OUTER_LINE_TYPE } from '@/store/outerLineAtom' import OuterLineWall from '@/components/floor-plan/modal/lineTypes/OuterLineWall' import { useAuxiliaryDrawing } from '@/hooks/roofcover/useAuxiliaryDrawing' import { usePopup } from '@/hooks/usePopup' +import { useEffect } from 'react' +import { useRecoilValue } from 'recoil' +import { canvasState } from '@/store/canvasAtom' export default function AuxiliaryDrawing({ id, pos = { x: 50, y: 230 } }) { + const canvas = useRecoilValue(canvasState) const { getMessage } = useMessage() const { closePopup } = usePopup() @@ -52,6 +56,15 @@ export default function AuxiliaryDrawing({ id, pos = { x: 50, y: 230 } }) { cutAuxiliary, } = useAuxiliaryDrawing(id) + useEffect(() => { + return () => { + const auxiliaryLines = canvas.getObjects().filter((line) => line.name === 'auxiliaryLine' && !line.isAuxiliaryFixed) // 보조선이 그려져 있는 경우 물어본다. + if (auxiliaryLines.length > 0) { + handleFix() + } + } + }, []) + const outerLineProps = { length1, setLength1, diff --git a/src/hooks/module/useModuleBasicSetting.js b/src/hooks/module/useModuleBasicSetting.js index f1801ea2..fd8f3a12 100644 --- a/src/hooks/module/useModuleBasicSetting.js +++ b/src/hooks/module/useModuleBasicSetting.js @@ -2121,6 +2121,7 @@ export function useModuleBasicSetting(tabNum) { let calcModuleWidthCount = calcAreaWidth / (width + intvHor) //뺀 공간에서 모듈을 몇개를 넣을수 있는지 확인하는 로직 let calcAreaHeight = flowLines.bottom.y1 - flowLines.top.y1 + calcAreaHeight = isNaN(calcAreaHeight) ? moduleSetupSurface.height : calcAreaHeight let calcModuleHeightCount = calcAreaHeight / (height + intvVer) if (type === MODULE_SETUP_TYPE.LAYOUT) { diff --git a/src/hooks/roofcover/useAuxiliaryDrawing.js b/src/hooks/roofcover/useAuxiliaryDrawing.js index aad5a715..37095215 100644 --- a/src/hooks/roofcover/useAuxiliaryDrawing.js +++ b/src/hooks/roofcover/useAuxiliaryDrawing.js @@ -680,7 +680,7 @@ export function useAuxiliaryDrawing(id, isUseEffect = true) { const newLine = addLine([line1.x1, line1.y1, interSectionPointsWithRoofLines[0].x, interSectionPointsWithRoofLines[0].y], { stroke: 'black', strokeWidth: 1, - selectable: false, + selectable: true, name: 'auxiliaryLine', isFixed: true, attributes: { ...line1.attributes }, @@ -693,7 +693,7 @@ export function useAuxiliaryDrawing(id, isUseEffect = true) { const newLine = addLine([line1.x2, line1.y2, interSectionPointsWithRoofLines[0].x, interSectionPointsWithRoofLines[0].y], { stroke: 'black', strokeWidth: 1, - selectable: false, + selectable: true, name: 'auxiliaryLine', isFixed: true, attributes: { ...line1.attributes }, @@ -737,7 +737,6 @@ export function useAuxiliaryDrawing(id, isUseEffect = true) { // line2.set(originLine2) // canvas.renderAll() - const intersectionPoint = calculateIntersection(line1, line2) if (!intersectionPoint) { return @@ -865,6 +864,8 @@ export function useAuxiliaryDrawing(id, isUseEffect = true) { return } + cutAuxiliary() + const roofBases = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF) //lineHistory.current에 있는 선들 중 startPoint와 endPoint가 겹치는 line은 제거 diff --git a/src/util/qpolygon-utils.js b/src/util/qpolygon-utils.js index 72ea0db0..bf685ad8 100644 --- a/src/util/qpolygon-utils.js +++ b/src/util/qpolygon-utils.js @@ -744,21 +744,21 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { /** baseLine을 기준으로 확인용 polygon 작성 */ const checkWallPolygon = new QPolygon(baseLinePoints, {}) - /** - * 외벽선이 시계방향인지 시계반대 방향인지 확인 - * @type {boolean} - */ - let counterClockwise = true - let signedArea = 0 - - baseLinePoints.forEach((point, index) => { - const nextPoint = baseLinePoints[(index + 1) % baseLinePoints.length] - signedArea += point.x * nextPoint.y - point.y * nextPoint.x - }) - - if (signedArea > 0) { - counterClockwise = false - } + // /** + // * 외벽선이 시계방향인지 시계반대 방향인지 확인 + // * @type {boolean} + // */ + // let counterClockwise = true + // let signedArea = 0 + // + // baseLinePoints.forEach((point, index) => { + // const nextPoint = baseLinePoints[(index + 1) % baseLinePoints.length] + // signedArea += point.x * nextPoint.y - point.y * nextPoint.x + // }) + // + // if (signedArea > 0) { + // counterClockwise = false + // } const drawEavesFirstLines = [] const drawEavesSecondLines = [] @@ -780,6 +780,15 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { const prevAngle = calculateAngle(prevLine.startPoint, prevLine.endPoint) const nextAngle = calculateAngle(nextLine.startPoint, nextLine.endPoint) + // const checkLine = new fabric.Line([currentLine.x1, currentLine.y1, currentLine.x2, currentLine.y2], { + // stroke: 'red', + // strokeWidth: 4, + // parentId: roofId, + // name: 'checkLine', + // }) + // canvas.add(checkLine) + // canvas.renderAll() + const checkScale = Big(10) const xVector = Big(nextLine.x2).minus(Big(nextLine.x1)) const yVector = Big(nextLine.y2).minus(Big(nextLine.y1)) @@ -3039,6 +3048,26 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { }) polygonPoints = getSortedPoint(uniquePoints, baseHipLines) + /*polygonPoints.forEach((point) => { + const checkCircle = new fabric.Circle({ + left: point.x, + top: point.y, + radius: 4, + fill: 'red', + parentId: roofId, + name: 'checkCircle', + }) + canvas.add(checkCircle) + canvas.renderAll() + + /!** 확인용 라인 제거 *!/ + canvas + .getObjects() + .filter((obj) => obj.name === 'checkCircle' || obj.name === 'checkLine') + .forEach((obj) => canvas.remove(obj)) + canvas.renderAll() + })*/ + polygonPoints.forEach((currentPoint, index) => { const nextPoint = polygonPoints[(index + 1) % polygonPoints.length] const points = [currentPoint.x, currentPoint.y, nextPoint.x, nextPoint.y] @@ -3077,6 +3106,15 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { const nextLine = nextBaseLine.line let { x1, x2, y1, y2 } = currentBaseLine + /*const checkLine = new fabric.Line([x1, y1, x2, y2], { + stroke: 'red', + strokeWidth: 4, + parentId: roofId, + name: 'checkLine', + }) + canvas.add(checkLine) + canvas.renderAll()*/ + const currentVectorX = Math.sign(currentLine.x2 - currentLine.x1) const currentVectorY = Math.sign(currentLine.y2 - currentLine.y1) const currentDegree = getDegreeByChon(currentLine.attributes.pitch) @@ -3097,11 +3135,11 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { .forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection = edgesIntersection(checkEdge, lineEdge) - if (intersection) { + if (intersection && isPointOnLine(line, intersection)) { intersectionRoofs.push({ line, intersection, - size: Big(intersection.x).minus(currentMidX).abs().pow(2).plus(Big(intersection.y).minus(currentMidY).abs().pow(2)).sqrt(), + size: Math.sqrt(Math.pow(intersection.x - currentMidX.toNumber(), 2) + Math.pow(intersection.y - currentMidY.toNumber(), 2)), }) } }) @@ -3115,11 +3153,11 @@ export const drawRidgeRoof = (roofId, canvas, textMode) => { .forEach((line) => { const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } } const intersection = edgesIntersection(checkEdge, lineEdge) - if (intersection) { + if (intersection && isPointOnLine(line, intersection)) { intersectionRoofs.push({ line, intersection, - size: Big(intersection.x).minus(currentMidX).abs().pow(2).plus(Big(intersection.y).minus(currentMidY).abs().pow(2)).sqrt(), + size: Math.sqrt(Math.pow(intersection.x - currentMidX.toNumber(), 2) + Math.pow(intersection.y - currentMidY.toNumber(), 2)), }) } })