diff --git a/src/hooks/surface/useSurfaceShapeBatch.js b/src/hooks/surface/useSurfaceShapeBatch.js index 296632a7..0de86ecc 100644 --- a/src/hooks/surface/useSurfaceShapeBatch.js +++ b/src/hooks/surface/useSurfaceShapeBatch.js @@ -159,6 +159,7 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) { addCanvasMouseEventListener('mouse:down', (e) => { isDrawing = false + const { xInversion, yInversion } = surfaceRefs canvas?.remove(obj) //각도 추가 @@ -179,6 +180,7 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) { } //회전, flip등이 먹은 기준으로 새로생성 + // const batchSurface = addPolygon(reorderedPoints, { const batchSurface = addPolygon(obj.getCurrentPoints(), { fill: 'transparent', stroke: 'red', @@ -208,6 +210,38 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) { // const popupId = uuidv4() // addPopup(popupId, 2, ) + // console.log('xInversion', xInversion) //상하반전 + // console.log('yInversion', yInversion) //좌우반전 + + //좌우 반전일때 방향 변경 + if (yInversion) { + batchSurface.lines.forEach((line) => { + const { x1, y1, x2, y2 } = line + if (line.x1 > line.x2) { + if (line.direction === 'left') { + line.direction = 'right' + line.x1 = x2 + line.x2 = x1 + } else if (line.direction === 'right') { + line.direction = 'left' + line.x1 = x2 + line.x2 = x1 + } + } + if (line.y2 > line.y1) { + if (line.direction === 'bottom') { + line.direction = 'top' + line.y1 = y2 + line.y2 = y1 + } else if (line.direction === 'top') { + line.direction = 'bottom' + line.y1 = y2 + line.y2 = y1 + } + } + }) + } + changeSurfaceLineType(batchSurface) if (setIsHidden) setIsHidden(false) @@ -491,18 +525,18 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) { } case 10: { points = [ - { x: pointer.x + length1 / 2, y: pointer.y + length4 / 2 }, - { x: pointer.x + length1 / 2 - length1, y: pointer.y + length4 / 2 }, { x: pointer.x + length1 / 2 - length1, y: pointer.y + length4 / 2 - length5 }, - { x: pointer.x + length1 / 2 - length1 + length2, y: pointer.y + length4 / 2 - length5 }, - { - x: pointer.x + length1 / 2 - length1 + length2, - y: pointer.y + length4 / 2 - length5 - (length4 - length5), - }, + { x: pointer.x + length1 / 2 - length1, y: pointer.y + length4 / 2 }, + { x: pointer.x + length1 / 2, y: pointer.y + length4 / 2 }, { x: pointer.x + length1 / 2 - length1 + length2 + length3, y: pointer.y + length4 / 2 - length5 - (length4 - length5), }, + { + x: pointer.x + length1 / 2 - length1 + length2, + y: pointer.y + length4 / 2 - length5 - (length4 - length5), + }, + { x: pointer.x + length1 / 2 - length1 + length2, y: pointer.y + length4 / 2 - length5 }, ] break } @@ -1095,10 +1129,10 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) { if (line[coord1] === line[coord2]) { if (line.direction === evaesDirection) { line.attributes.type = LINE_TYPE.WALLLINE.EAVES - line.stroke = 'rgb(47, 0, 255)' + line.stroke = 'rgb(1, 1, 1)' } else if (line.direction === ridgeDirection) { line.attributes.type = LINE_TYPE.SUBLINE.RIDGE - line.stroke = 'rgb(44, 255, 2)' + line.stroke = 'rgb(9, 9, 9)' } } }) @@ -1118,20 +1152,21 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) { : a }) - if ( - (polygon.direction === 'south' && maxLineSorted.direction === 'left') || - (polygon.direction === 'north' && maxLineSorted.direction === 'right') || - (polygon.direction === 'east' && maxLineSorted.direction === 'bottom') || - (polygon.direction === 'west' && maxLineSorted.direction === 'top') - ) { - polygon.lines.forEach((line) => { - if (line.attributes.type === LINE_TYPE.WALLLINE.EAVES) { - line.attributes.type = LINE_TYPE.SUBLINE.RIDGE - } else if (line.attributes.type === LINE_TYPE.SUBLINE.RIDGE) { - line.attributes.type = LINE_TYPE.WALLLINE.EAVES - } - }) - } + // if ( + // (polygon.direction === 'south' && maxLineSorted.direction === 'left') || + // (polygon.direction === 'north' && maxLineSorted.direction === 'right') + // // || + // // (polygon.direction === 'east' && maxLineSorted.direction === 'bottom') || + // // (polygon.direction === 'west' && maxLineSorted.direction === 'top') + // ) { + // polygon.lines.forEach((line) => { + // if (line.attributes.type === LINE_TYPE.WALLLINE.EAVES) { + // line.attributes.type = LINE_TYPE.SUBLINE.RIDGE + // } else if (line.attributes.type === LINE_TYPE.SUBLINE.RIDGE) { + // line.attributes.type = LINE_TYPE.WALLLINE.EAVES + // } + // }) + // } if (maxLine.length === 1) { const maxLineCoord = polygon.lines.reduce((a, b) => { @@ -1141,31 +1176,226 @@ export function useSurfaceShapeBatch({ isHidden, setIsHidden }) { : a }) - const isRealEavesLine = polygon.lines.find((line) => line.attributes.type === LINE_TYPE.WALLLINE.EAVES) - if (isRealEavesLine) { - if (polygon.direction === 'south' || polygon.direction === 'north') { - const targetCoord = - polygon.direction === 'south' ? Math.max(maxLineCoord.y1, maxLineCoord.y2) : Math.min(maxLineCoord.y1, maxLineCoord.y2) - const realLineCoord = - polygon.direction === 'south' ? Math.max(isRealEavesLine.y1, isRealEavesLine.y2) : Math.min(isRealEavesLine.y1, isRealEavesLine.y2) + const isRealEavesLine = polygon.lines.filter((line) => line.attributes.type === LINE_TYPE.WALLLINE.EAVES) + if (isRealEavesLine.length > 0) { + isRealEavesLine.forEach((line) => { + if (polygon.direction === 'south' || polygon.direction === 'north') { + const targetCoord = + polygon.direction === 'south' ? Math.max(maxLineCoord.y1, maxLineCoord.y2) : Math.min(maxLineCoord.y1, maxLineCoord.y2) + const realLineCoord = polygon.direction === 'south' ? Math.max(line.y1, line.y2) : Math.min(line.y1, line.y2) - if (targetCoord !== realLineCoord) { - isRealEavesLine.attributes.type = LINE_TYPE.SUBLINE.RIDGE - } - } else if (polygon.direction === 'east' || polygon.direction === 'west') { - const targetCoord = polygon.direction === 'east' ? Math.max(maxLineCoord.x1, maxLineCoord.x2) : Math.min(maxLineCoord.x1, maxLineCoord.x2) - const realLineCoord = - polygon.direction === 'east' ? Math.max(isRealEavesLine.x1, isRealEavesLine.x2) : Math.min(isRealEavesLine.x1, isRealEavesLine.x2) + if (targetCoord !== realLineCoord) { + line.attributes.type = LINE_TYPE.SUBLINE.RIDGE + line.stroke = 'rgb(9, 9, 9)' + } + } else if (polygon.direction === 'east' || polygon.direction === 'west') { + const targetCoord = + polygon.direction === 'east' ? Math.max(maxLineCoord.x1, maxLineCoord.x2) : Math.min(maxLineCoord.x1, maxLineCoord.x2) + const realLineCoord = polygon.direction === 'east' ? Math.max(line.x1, line.x2) : Math.min(line.x1, line.x2) - if (targetCoord !== realLineCoord) { - isRealEavesLine.attributes.type = LINE_TYPE.SUBLINE.RIDGE + if (targetCoord !== realLineCoord) { + line.attributes.type = LINE_TYPE.SUBLINE.RIDGE + line.stroke = 'rgb(9, 9, 9)' + } } - } + }) } } } } + function findCentroid(points) { + let sumX = 0, + sumY = 0 + for (let i = 0; i < points.length; i++) { + sumX += points[i].x + sumY += points[i].y + } + return { x: sumX / points.length, y: sumY / points.length } + } + + // 도형의 포인트를 왼쪽부터 반시계 방향으로 정렬하는 함수 + /** + * 다각형의 점들을 시계 반대 방향으로 정렬하는 함수 + * @param {Array} points - {x, y} 좌표 객체 배열 + * @param {Object} startPoint - 시작점 (제공되지 않으면 가장 왼쪽 아래 점을 사용) + * @returns {Array} 시계 반대 방향으로 정렬된 점들의 배열 + */ + function orderPointsCounterClockwise(points, startPoint = null) { + if (points.length <= 3) { + return points // 점이 3개 이하면 이미 다각형의 모든 점이므로 그대로 반환 + } + + // 시작점이 제공되지 않았다면 가장 왼쪽 아래 점을 찾음 + let start = startPoint + if (!start) { + start = points[0] + for (let i = 1; i < points.length; i++) { + if (points[i].x < start.x || (points[i].x === start.x && points[i].y < start.y)) { + start = points[i] + } + } + } + + // 다각형의 중심점 계산 + let centerX = 0, + centerY = 0 + for (let i = 0; i < points.length; i++) { + centerX += points[i].x + centerY += points[i].y + } + centerX /= points.length + centerY /= points.length + + // 시작점에서 시계 반대 방향으로 각도 계산 + let angles = [] + for (let i = 0; i < points.length; i++) { + // 시작점은 제외 + if (points[i] === start) continue + + // 시작점을 기준으로 각 점의 각도 계산 + let angle = Math.atan2(points[i].y - start.y, points[i].x - start.x) + + // 각도가 음수면 2π를 더해 0~2π 범위로 변환 + if (angle < 0) angle += 2 * Math.PI + + angles.push({ + point: points[i], + angle: angle, + }) + } + + // 각도에 따라 정렬 (시계 반대 방향) + angles.sort((a, b) => a.angle - b.angle) + + // 정렬된 배열 생성 (시작점을 첫 번째로) + let orderedPoints = [start] + for (let i = 0; i < angles.length; i++) { + orderedPoints.push(angles[i].point) + } + + return orderedPoints + } + + /** + * 특정 점에서 시작하여 시계 반대 방향으로 다음 점을 찾는 함수 + * @param {Object} currentPoint - 현재 점 {x, y} + * @param {Array} points - 모든 점들의 배열 + * @param {Array} visited - 방문한 점들의 인덱스 배열 + * @param {Object} prevVector - 이전 벡터 방향 (첫 호출에서는 null) + * @returns {Object} 다음 점의 인덱스와 객체 + */ + function findNextCounterClockwisePoint(currentPoint, points, visited, prevVector = null) { + let minAngle = Infinity + let nextIndex = -1 + + // 이전 벡터가 없으면 (첫 점인 경우) 아래쪽을 향하는 벡터 사용 + if (!prevVector) { + prevVector = { x: 0, y: -1 } + } + + for (let i = 0; i < points.length; i++) { + // 이미 방문했거나 현재 점이면 건너뜀 + if (visited.includes(i) || (points[i].x === currentPoint.x && points[i].y === currentPoint.y)) { + continue + } + + // 현재 점에서 다음 후보 점으로의 벡터 + let vector = { + x: points[i].x - currentPoint.x, + y: points[i].y - currentPoint.y, + } + + // 벡터의 크기 + let magnitude = Math.sqrt(vector.x * vector.x + vector.y * vector.y) + + // 단위 벡터로 정규화 + vector.x /= magnitude + vector.y /= magnitude + + // 이전 벡터와 현재 벡터 사이의 각도 계산 (내적 사용) + let dotProduct = prevVector.x * vector.x + prevVector.y * vector.y + let crossProduct = prevVector.x * vector.y - prevVector.y * vector.x + + // 각도 계산 (atan2 사용) + let angle = Math.atan2(crossProduct, dotProduct) + + // 시계 반대 방향으로 가장 작은 각도를 가진 점 찾기 + // 각도가 음수면 2π를 더해 0~2π 범위로 변환 + if (angle < 0) angle += 2 * Math.PI + + if (angle < minAngle) { + minAngle = angle + nextIndex = i + } + } + + return nextIndex !== -1 ? { index: nextIndex, point: points[nextIndex] } : null + } + + /** + * 다각형의 점들을 시계 반대 방향으로 추적하는 함수 + * @param {Array} points - {x, y} 좌표 객체 배열 + * @param {Object} startPoint - 시작점 (제공되지 않으면 가장 왼쪽 아래 점을 사용) + * @returns {Array} 시계 반대 방향으로 정렬된 점들의 배열 + */ + function tracePolygonCounterClockwise(points, startPoint = null) { + if (points.length <= 3) { + return orderPointsCounterClockwise(points, startPoint) + } + + // 시작점이 제공되지 않았다면 가장 왼쪽 아래 점을 찾음 + let startIndex = 0 + if (!startPoint) { + for (let i = 1; i < points.length; i++) { + if (points[i].x < points[startIndex].x || (points[i].x === points[startIndex].x && points[i].y < points[startIndex].y)) { + startIndex = i + } + } + startPoint = points[startIndex] + } else { + // 시작점이 제공된 경우 해당 점의 인덱스 찾기 + for (let i = 0; i < points.length; i++) { + if (points[i].x === startPoint.x && points[i].y === startPoint.y) { + startIndex = i + break + } + } + } + + // 결과 배열 초기화 + let orderedPoints = [startPoint] + let visited = [startIndex] + + let currentPoint = startPoint + let prevVector = null + + // 모든 점을 방문할 때까지 반복 + while (visited.length < points.length) { + let next = findNextCounterClockwisePoint(currentPoint, points, visited, prevVector) + + if (!next) break // 더 이상 찾을 점이 없으면 종료 + + orderedPoints.push(next.point) + visited.push(next.index) + + // 이전 벡터 업데이트 (현재 점에서 다음 점으로의 벡터) + prevVector = { + x: next.point.x - currentPoint.x, + y: next.point.y - currentPoint.y, + } + + // 벡터 정규화 + let magnitude = Math.sqrt(prevVector.x * prevVector.x + prevVector.y * prevVector.y) + prevVector.x /= magnitude + prevVector.y /= magnitude + + currentPoint = next.point + } + + return orderedPoints + } + return { applySurfaceShape, deleteAllSurfacesAndObjects,