From a75ea4c98abdcfa7d6970319813989184d3c6ace Mon Sep 17 00:00:00 2001 From: ysCha Date: Wed, 29 Oct 2025 16:02:28 +0900 Subject: [PATCH 1/7] =?UTF-8?q?=ED=98=84=EC=9D=98=20=EC=83=81(out),=20?= =?UTF-8?q?=ED=95=98(in)=20=EC=9D=98=EB=AF=B8=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/roofcover/useMovementSetting.js | 200 ++++++++++++++-------- 1 file changed, 124 insertions(+), 76 deletions(-) diff --git a/src/hooks/roofcover/useMovementSetting.js b/src/hooks/roofcover/useMovementSetting.js index d4c9ff9f..f3a30473 100644 --- a/src/hooks/roofcover/useMovementSetting.js +++ b/src/hooks/roofcover/useMovementSetting.js @@ -91,7 +91,7 @@ export function useMovementSetting(id) { } wall.baseLines.forEach((line) => { if (type === TYPE.UP_DOWN) { - line.set({ selectable: true, visible: true, stroke: '#1083E3', strokeWidth: 5 }) + line.set({ selectable: true, visible: true, stroke: '#1085E5', strokeWidth: 5 }) line.setCoords() line.bringToFront() } else { @@ -102,7 +102,7 @@ export function useMovementSetting(id) { /** outerLines 속성처리*/ const outerLines = canvas.getObjects().filter((obj) => obj.name === 'outerLine') - outerLines.forEach((line) => line.set({ visible: false })) + outerLines.forEach((line) => line.set({ visible: true })) canvas.renderAll() }, [type]) @@ -194,101 +194,144 @@ export function useMovementSetting(id) { canvas.renderAll() }, [currentObject]) + const clearRef = () => { if (type === TYPE.FLOW_LINE) { - FLOW_LINE_REF.POINTER_INPUT_REF.current.value = '' - FLOW_LINE_REF.FILLED_INPUT_REF.current.value = '' - - if (FLOW_LINE_REF.UP_RIGHT_RADIO_REF.current.checked || FLOW_LINE_REF.DOWN_LEFT_RADIO_REF.current.checked) { - // If one is checked, uncheck the other - FLOW_LINE_REF.UP_RIGHT_RADIO_REF.current.checked = !FLOW_LINE_REF.DOWN_LEFT_RADIO_REF.current.checked; - FLOW_LINE_REF.DOWN_LEFT_RADIO_REF.current.checked = !FLOW_LINE_REF.UP_RIGHT_RADIO_REF.current.checked; - }else{ - FLOW_LINE_REF.DOWN_LEFT_RADIO_REF.current.checked = true - FLOW_LINE_REF.UP_RIGHT_RADIO_REF.current.checked = false + // 안전한 ref 접근 + if (FLOW_LINE_REF.POINTER_INPUT_REF.current) { + FLOW_LINE_REF.POINTER_INPUT_REF.current.value = '' + } + if (FLOW_LINE_REF.FILLED_INPUT_REF.current) { + FLOW_LINE_REF.FILLED_INPUT_REF.current.value = '' } + const upRightChecked = FLOW_LINE_REF.UP_RIGHT_RADIO_REF.current?.checked || false + const downLeftChecked = FLOW_LINE_REF.DOWN_LEFT_RADIO_REF.current?.checked || false + + if (upRightChecked || downLeftChecked) { + if (FLOW_LINE_REF.UP_RIGHT_RADIO_REF.current) { + FLOW_LINE_REF.UP_RIGHT_RADIO_REF.current.checked = !downLeftChecked + } + if (FLOW_LINE_REF.DOWN_LEFT_RADIO_REF.current) { + FLOW_LINE_REF.DOWN_LEFT_RADIO_REF.current.checked = !upRightChecked + } + } else { + if (FLOW_LINE_REF.DOWN_LEFT_RADIO_REF.current) { + FLOW_LINE_REF.DOWN_LEFT_RADIO_REF.current.checked = true + } + if (FLOW_LINE_REF.UP_RIGHT_RADIO_REF.current) { + FLOW_LINE_REF.UP_RIGHT_RADIO_REF.current.checked = false + } + } } + if (type === TYPE.UP_DOWN) { - UP_DOWN_REF.POINTER_INPUT_REF.current.value = '' - UP_DOWN_REF.FILLED_INPUT_REF.current.value = '' - UP_DOWN_REF.UP_RADIO_REF.current.checked = true - UP_DOWN_REF.DOWN_RADIO_REF.current.checked = false + // 안전한 ref 접근 + if (UP_DOWN_REF.POINTER_INPUT_REF.current) { + UP_DOWN_REF.POINTER_INPUT_REF.current.value = '' + } + if (UP_DOWN_REF.FILLED_INPUT_REF.current) { + UP_DOWN_REF.FILLED_INPUT_REF.current.value = '' + } + if (UP_DOWN_REF.UP_RADIO_REF.current) { + UP_DOWN_REF.UP_RADIO_REF.current.checked = true + } + if (UP_DOWN_REF.DOWN_RADIO_REF.current) { + UP_DOWN_REF.DOWN_RADIO_REF.current.checked = false + } } } let currentCalculatedValue = 0 const mouseMoveEvent = (e) => { + const target = canvas.getActiveObject() if (!target) return + // 디버깅 로그 추가 + if (typeRef.current === TYPE.UP_DOWN) { + console.log('UP_DOWN_REF.POINTER_INPUT_REF.current:', UP_DOWN_REF.POINTER_INPUT_REF.current); + if (!UP_DOWN_REF.POINTER_INPUT_REF.current) { + console.warn('UP_DOWN_REF.POINTER_INPUT_REF.current is null/undefined'); + } + } + + + const { top: targetTop, left: targetLeft } = target - const currentX = Big(getIntersectMousePoint(e).x) //.round(0, Big.roundUp) - const currentY = Big(getIntersectMousePoint(e).y) //.round(0, Big.roundUp) + const currentX = Big(getIntersectMousePoint(e).x) + const currentY = Big(getIntersectMousePoint(e).y) let value = '' if (Math.abs(target.y1 - target.y2) < 0.5) { - // 가로라인의 경우 value = Big(targetTop).minus(currentY).times(10).round(0) - console.log('가로라인 계산:', `${targetTop} - ${currentY.toNumber()} = ${value.toNumber()}`) } else { - // 세로라인의 경우 value = Big(targetLeft).minus(currentX).times(10).round(0).neg() - console.log('세로라인 계산:', `-(${targetLeft} - ${currentX.toNumber()}) = ${value.toNumber()}`) } currentCalculatedValue = value.toNumber() - if (typeRef.current === TYPE.FLOW_LINE) { - FLOW_LINE_REF.POINTER_INPUT_REF.current.value = value.toNumber() - } else { - UP_DOWN_REF.POINTER_INPUT_REF.current.value = value.abs().toNumber() - const midX = Big(target.x1).plus(target.x2).div(2) - const midY = Big(target.y1).plus(target.y2).div(2) - const wall = canvas.getObjects().find((obj) => obj.id === target.attributes.wallId) - let checkPoint - if (target.y1 === target.y2) { - checkPoint = { x: midX.toNumber(), y: midY.plus(10).toNumber() } - if (wall.inPolygon(checkPoint)) { - if (value.s === -1) { - UP_DOWN_REF.UP_RADIO_REF.current.checked = false - UP_DOWN_REF.DOWN_RADIO_REF.current.checked = true - } else { - UP_DOWN_REF.UP_RADIO_REF.current.checked = true - UP_DOWN_REF.DOWN_RADIO_REF.current.checked = false - } - } else { - if (value.s === 1) { - UP_DOWN_REF.UP_RADIO_REF.current.checked = false - UP_DOWN_REF.DOWN_RADIO_REF.current.checked = true - } else { - UP_DOWN_REF.UP_RADIO_REF.current.checked = true - UP_DOWN_REF.DOWN_RADIO_REF.current.checked = false - } - } + if (typeRef.current === TYPE.FLOW_LINE) { + // ref가 존재하는지 확인 후 값 설정 + if (FLOW_LINE_REF.POINTER_INPUT_REF.current) { + FLOW_LINE_REF.POINTER_INPUT_REF.current.value = value.toNumber() + } + } else { + // UP_DOWN 타입일 때 안전한 접근 + if (UP_DOWN_REF.POINTER_INPUT_REF.current) { + UP_DOWN_REF.POINTER_INPUT_REF.current.value = value.abs().toNumber() + } + + const midX = Big(target.x1).plus(target.x2).div(2) + const midY = Big(target.y1).plus(target.y2).div(2) + const wall = canvas.getObjects().find((obj) => obj.id === target.attributes.wallId) + let checkPoint + + + if (target.y1 === target.y2) { //수평벽 + checkPoint = { x: midX.toNumber(), y: midY.plus(10).toNumber() } + if (wall.inPolygon(checkPoint)) { //선택라인이 내부 + if (value.s !== -1) { + console.log('-1value:::', value.s) + if (UP_DOWN_REF.UP_RADIO_REF.current) UP_DOWN_REF.UP_RADIO_REF.current.checked = false + if (UP_DOWN_REF.DOWN_RADIO_REF.current) UP_DOWN_REF.DOWN_RADIO_REF.current.checked = true } else { - checkPoint = { x: midX.plus(10).toNumber(), y: midY.toNumber() } - if (wall.inPolygon(checkPoint)) { - if (value.s === 1) { - UP_DOWN_REF.UP_RADIO_REF.current.checked = false - UP_DOWN_REF.DOWN_RADIO_REF.current.checked = true - } else { - UP_DOWN_REF.UP_RADIO_REF.current.checked = true - UP_DOWN_REF.DOWN_RADIO_REF.current.checked = false - } - } else { - if (value.s === -1) { - UP_DOWN_REF.UP_RADIO_REF.current.checked = false - UP_DOWN_REF.DOWN_RADIO_REF.current.checked = true - } else { - UP_DOWN_REF.UP_RADIO_REF.current.checked = true - UP_DOWN_REF.DOWN_RADIO_REF.current.checked = false - } - } + if (UP_DOWN_REF.UP_RADIO_REF.current) UP_DOWN_REF.UP_RADIO_REF.current.checked = true + if (UP_DOWN_REF.DOWN_RADIO_REF.current) UP_DOWN_REF.DOWN_RADIO_REF.current.checked = false + } + } else { // + if (value.s === 1) { //선택라인이 외부 + console.log('+1value:::', value.s) + if (UP_DOWN_REF.UP_RADIO_REF.current) UP_DOWN_REF.UP_RADIO_REF.current.checked = false + if (UP_DOWN_REF.DOWN_RADIO_REF.current) UP_DOWN_REF.DOWN_RADIO_REF.current.checked = true + } else { + if (UP_DOWN_REF.UP_RADIO_REF.current) UP_DOWN_REF.UP_RADIO_REF.current.checked = true + if (UP_DOWN_REF.DOWN_RADIO_REF.current) UP_DOWN_REF.DOWN_RADIO_REF.current.checked = false + } + } + } else { + checkPoint = { x: midX.plus(10).toNumber(), y: midY.toNumber() } + if (wall.inPolygon(checkPoint)) { + if (value.s !== 1) { + if (UP_DOWN_REF.UP_RADIO_REF.current) UP_DOWN_REF.UP_RADIO_REF.current.checked = false + if (UP_DOWN_REF.DOWN_RADIO_REF.current) UP_DOWN_REF.DOWN_RADIO_REF.current.checked = true + } else { + if (UP_DOWN_REF.UP_RADIO_REF.current) UP_DOWN_REF.UP_RADIO_REF.current.checked = true + if (UP_DOWN_REF.DOWN_RADIO_REF.current) UP_DOWN_REF.DOWN_RADIO_REF.current.checked = false + } + } else { + if (value.s === -1) { + if (UP_DOWN_REF.UP_RADIO_REF.current) UP_DOWN_REF.UP_RADIO_REF.current.checked = false + if (UP_DOWN_REF.DOWN_RADIO_REF.current) UP_DOWN_REF.DOWN_RADIO_REF.current.checked = true + } else { + if (UP_DOWN_REF.UP_RADIO_REF.current) UP_DOWN_REF.UP_RADIO_REF.current.checked = true + if (UP_DOWN_REF.DOWN_RADIO_REF.current) UP_DOWN_REF.DOWN_RADIO_REF.current.checked = false } } } + } + } @@ -348,14 +391,15 @@ export function useMovementSetting(id) { const roof = canvas.getObjects().find((obj) => obj.id === roofId) // 현이동, 동이동 추가 - let flPointValue = FLOW_LINE_REF.POINTER_INPUT_REF.current?.value??0; - let flFilledValue = FLOW_LINE_REF.FILLED_INPUT_REF.current?.value??0; - flPointValue = (flFilledValue > 0 || flFilledValue < 0)? flFilledValue : flPointValue; + let flPointValue = FLOW_LINE_REF.POINTER_INPUT_REF.current?.value ?? 0; + let flFilledValue = FLOW_LINE_REF.FILLED_INPUT_REF.current?.value ?? 0; + flPointValue = (flFilledValue > 0 || flFilledValue < 0) ? flFilledValue : flPointValue; const moveFlowLine = typeRef.current === TYPE.FLOW_LINE ? flPointValue : 0 - let udPointValue = UP_DOWN_REF.POINTER_INPUT_REF.current?.value??0; - let udFilledValue = UP_DOWN_REF.FILLED_INPUT_REF.current?.value??0; + + let udPointValue = UP_DOWN_REF.POINTER_INPUT_REF.current?.value ?? 0; + let udFilledValue = UP_DOWN_REF.FILLED_INPUT_REF.current?.value ?? 0; udPointValue = udFilledValue > 0 ? udFilledValue : udPointValue; - const moveUpDown = typeRef.current === TYPE.UP_DOWN ? udPointValue: 0 + const moveUpDown = typeRef.current === TYPE.UP_DOWN ? udPointValue : 0 roof.moveFlowLine = parseInt(moveFlowLine, 10) || 0; roof.moveUpDown = parseInt(moveUpDown, 10) || 0; roof.moveDirect = ""; @@ -448,6 +492,7 @@ export function useMovementSetting(id) { break } } else { + roof.moveDirect = UP_DOWN_REF.UP_RADIO_REF.current.checked ? 'out' : UP_DOWN_REF.DOWN_RADIO_REF.current.checked ? 'in' : 'out' targetBaseLines.push({ line: target, distance: 0 }) } @@ -516,9 +561,12 @@ export function useMovementSetting(id) { const inPolygon = wall.inPolygon(checkPoint) - if (UP_DOWN_REF.UP_RADIO_REF.current.checked && inPolygon) { - value = value.neg() - } else if (UP_DOWN_REF.DOWN_RADIO_REF.current.checked && !inPolygon) { + // if (UP_DOWN_REF.UP_RADIO_REF.current.checked && inPolygon) { + // value = value.neg() + // } else if (UP_DOWN_REF.DOWN_RADIO_REF.current.checked && !inPolygon) { + // value = value.neg() + // } + if (UP_DOWN_REF.DOWN_RADIO_REF.current.checked){ value = value.neg() } } From baeb2be0a18a9fb2a80096502618379236cde46c Mon Sep 17 00:00:00 2001 From: ysCha Date: Thu, 30 Oct 2025 16:35:42 +0900 Subject: [PATCH 2/7] =?UTF-8?q?=EB=8F=99,=ED=98=84=EC=9D=B4=EB=8F=99=20?= =?UTF-8?q?=EB=8F=99=EC=9D=BC=EB=9D=BC=EC=9D=B8=EC=B2=B4=ED=81=AC=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD,=20=EC=83=81=EC=9A=B0=EC=84=A0=ED=83=9D?= =?UTF-8?q?=EC=8B=9C=20=EC=B4=88=EA=B8=B0=ED=99=94=20=EB=AC=B8=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/roofcover/useMovementSetting.js | 148 +++++++++++++++++++--- 1 file changed, 131 insertions(+), 17 deletions(-) diff --git a/src/hooks/roofcover/useMovementSetting.js b/src/hooks/roofcover/useMovementSetting.js index f3a30473..3b285e77 100644 --- a/src/hooks/roofcover/useMovementSetting.js +++ b/src/hooks/roofcover/useMovementSetting.js @@ -245,19 +245,17 @@ export function useMovementSetting(id) { let currentCalculatedValue = 0 const mouseMoveEvent = (e) => { - + //console.log('mouseMoveEvent:::::',e) const target = canvas.getActiveObject() if (!target) return // 디버깅 로그 추가 - if (typeRef.current === TYPE.UP_DOWN) { - console.log('UP_DOWN_REF.POINTER_INPUT_REF.current:', UP_DOWN_REF.POINTER_INPUT_REF.current); - if (!UP_DOWN_REF.POINTER_INPUT_REF.current) { - console.warn('UP_DOWN_REF.POINTER_INPUT_REF.current is null/undefined'); - } - } - - + // if (typeRef.current === TYPE.UP_DOWN) { + // console.log('UP_DOWN_REF.POINTER_INPUT_REF.current:', UP_DOWN_REF.POINTER_INPUT_REF.current); + // if (!UP_DOWN_REF.POINTER_INPUT_REF.current) { + // console.warn('UP_DOWN_REF.POINTER_INPUT_REF.current is null/undefined'); + // } + // } const { top: targetTop, left: targetLeft } = target const currentX = Big(getIntersectMousePoint(e).x) @@ -286,14 +284,38 @@ export function useMovementSetting(id) { const midX = Big(target.x1).plus(target.x2).div(2) const midY = Big(target.y1).plus(target.y2).div(2) const wall = canvas.getObjects().find((obj) => obj.id === target.attributes.wallId) - let checkPoint + wall.lines.forEach(line => { + + console.log(classifyWallLine(wall, line)) + + }) + + let linePosition = classifyWallLine(wall, target).position; if (target.y1 === target.y2) { //수평벽 + + const setRadioStates = (isUp) => { + if (UP_DOWN_REF.UP_RADIO_REF.current) { + UP_DOWN_REF.UP_RADIO_REF.current.checked = isUp; + } + if (UP_DOWN_REF.DOWN_RADIO_REF.current) { + UP_DOWN_REF.DOWN_RADIO_REF.current.checked = !isUp; + } + }; + + if (linePosition === 'top') { + setRadioStates(value.s !== -1); + } else if (linePosition === 'bottom') { + setRadioStates(value.s !== 1); + } + + + /* checkPoint = { x: midX.toNumber(), y: midY.plus(10).toNumber() } if (wall.inPolygon(checkPoint)) { //선택라인이 내부 - if (value.s !== -1) { - console.log('-1value:::', value.s) + if (value.s === -1) { + console.log('1value:::', value.s) if (UP_DOWN_REF.UP_RADIO_REF.current) UP_DOWN_REF.UP_RADIO_REF.current.checked = false if (UP_DOWN_REF.DOWN_RADIO_REF.current) UP_DOWN_REF.DOWN_RADIO_REF.current.checked = true } else { @@ -302,7 +324,7 @@ export function useMovementSetting(id) { } } else { // if (value.s === 1) { //선택라인이 외부 - console.log('+1value:::', value.s) + console.log('2value:::', value.s) if (UP_DOWN_REF.UP_RADIO_REF.current) UP_DOWN_REF.UP_RADIO_REF.current.checked = false if (UP_DOWN_REF.DOWN_RADIO_REF.current) UP_DOWN_REF.DOWN_RADIO_REF.current.checked = true } else { @@ -310,18 +332,38 @@ export function useMovementSetting(id) { if (UP_DOWN_REF.DOWN_RADIO_REF.current) UP_DOWN_REF.DOWN_RADIO_REF.current.checked = false } } + */ } else { + + const setRadioStates = (isUp) => { + if (UP_DOWN_REF.UP_RADIO_REF.current) { + UP_DOWN_REF.UP_RADIO_REF.current.checked = isUp; + } + if (UP_DOWN_REF.DOWN_RADIO_REF.current) { + UP_DOWN_REF.DOWN_RADIO_REF.current.checked = !isUp; + } + }; + + if (linePosition === 'left') { + setRadioStates(value.s !== 1); + } else if (linePosition === 'right') { + setRadioStates(value.s !== -1); + } + /* checkPoint = { x: midX.plus(10).toNumber(), y: midY.toNumber() } if (wall.inPolygon(checkPoint)) { - if (value.s !== 1) { + if (value.s === 1) { + console.log('3value:::', value.s) if (UP_DOWN_REF.UP_RADIO_REF.current) UP_DOWN_REF.UP_RADIO_REF.current.checked = false if (UP_DOWN_REF.DOWN_RADIO_REF.current) UP_DOWN_REF.DOWN_RADIO_REF.current.checked = true } else { if (UP_DOWN_REF.UP_RADIO_REF.current) UP_DOWN_REF.UP_RADIO_REF.current.checked = true if (UP_DOWN_REF.DOWN_RADIO_REF.current) UP_DOWN_REF.DOWN_RADIO_REF.current.checked = false + } } else { if (value.s === -1) { + console.log('-1value:::', value.s) if (UP_DOWN_REF.UP_RADIO_REF.current) UP_DOWN_REF.UP_RADIO_REF.current.checked = false if (UP_DOWN_REF.DOWN_RADIO_REF.current) UP_DOWN_REF.DOWN_RADIO_REF.current.checked = true } else { @@ -329,6 +371,7 @@ export function useMovementSetting(id) { if (UP_DOWN_REF.DOWN_RADIO_REF.current) UP_DOWN_REF.DOWN_RADIO_REF.current.checked = false } } + */ } } } @@ -406,6 +449,7 @@ export function useMovementSetting(id) { roof.moveSelectLine = target; const wall = canvas.getObjects().find((obj) => obj.name === POLYGON_TYPE.WALL && obj.attributes.roofId === roofId) const baseLines = wall.baseLines + let centerPoint = wall.getCenterPoint(); let targetBaseLines = [] let isGableRoof if (typeRef.current === TYPE.FLOW_LINE) { @@ -544,6 +588,7 @@ export function useMovementSetting(id) { value = value.neg() } } else { + console.log("error::", UP_DOWN_REF) value = UP_DOWN_REF.FILLED_INPUT_REF.current.value !== '' ? Big(UP_DOWN_REF.FILLED_INPUT_REF.current.value) @@ -566,15 +611,33 @@ export function useMovementSetting(id) { // } else if (UP_DOWN_REF.DOWN_RADIO_REF.current.checked && !inPolygon) { // value = value.neg() // } - if (UP_DOWN_REF.DOWN_RADIO_REF.current.checked){ - value = value.neg() - } } + + let linePosition = classifyWallLine(wall, target).position; + + value = value.div(10) targetBaseLines .filter((line) => Math.sqrt(Math.pow(line.line.x2 - line.line.x1, 2) + Math.pow(line.line.y2 - line.line.y1, 2)) >= 1) .forEach((target) => { const currentLine = target.line + + console.log("linePosition::::::::::::::", linePosition) + if (UP_DOWN_REF.DOWN_RADIO_REF.current.checked ){ + //position확인 + if(linePosition === 'bottom' || linePosition === 'right') { + console.log("1value::::::::::::::", value.toString()) + value = value.neg() + + } + }else { + if(linePosition === 'top' || linePosition === 'left') { + console.log("1value::::::::::::::", value.toString()) + value = value.neg() + } + } + + console.log("2value::::::::::::::", value.toString()) const index = baseLines.findIndex((line) => line === currentLine) const nextLine = baseLines[(index + 1) % baseLines.length] const prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] @@ -627,6 +690,55 @@ export function useMovementSetting(id) { closePopup(id) } + // javascript + const classifyWallLine = (wall, line, epsilon = 0.5, offset = 10)=> { + if (!wall || !line) return { orientation: 'unknown', position: 'unknown' } + + const center = wall.getCenterPoint() + const dx = Math.abs(line.x2 - line.x1) + const dy = Math.abs(line.y2 - line.y1) + + let orientation = 'slanted' + if (dy < epsilon && dx >= epsilon) orientation = 'horizontal' + else if (dx < epsilon && dy >= epsilon) orientation = 'vertical' + else orientation = dx > dy ? 'horizontal' : 'vertical' + + const midX = (line.x1 + line.x2) / 2 + const midY = (line.y1 + line.y2) / 2 + + // checkPoint: mid에서 center 방향으로 약간 이동한 점을 계산 + let checkX = midX + let checkY = midY + if (orientation === 'horizontal') { + const dir = Math.sign(center.y - midY) || 1 + checkY = midY + dir * offset + } else if (orientation === 'vertical') { + const dir = Math.sign(center.x - midX) || 1 + checkX = midX + dir * offset + } else { + const dirX = Math.sign(center.x - midX) || 1 + const dirY = Math.sign(center.y - midY) || 1 + checkX = midX + dirX * offset + checkY = midY + dirY * offset + } + + const inside = typeof wall.inPolygon === 'function' ? wall.inPolygon({ x: checkX, y: checkY }) : true + + let position = 'unknown' + if (orientation === 'horizontal') { + const dir = center.y - midY + if (inside) position = dir > 0 ? 'top' : 'bottom' + else position = dir > 0 ? 'bottom' : 'top' + } else if (orientation === 'vertical') { + const dir = center.x - midX + if (inside) position = dir > 0 ? 'left' : 'right' + else position = dir > 0 ? 'right' : 'left' + } + + return { orientation, position, mid: { x: midX, y: midY }, center, checkPoint: { x: checkX, y: checkY }, inside } + } + + return { TYPE, closePopup, @@ -636,5 +748,7 @@ export function useMovementSetting(id) { FLOW_LINE_REF, UP_DOWN_REF, handleSave, + classifyWallLine } } + From a428cc31e89c57acd757bdab636eea41ccfd4836 Mon Sep 17 00:00:00 2001 From: ysCha Date: Thu, 30 Oct 2025 16:36:53 +0900 Subject: [PATCH 3/7] =?UTF-8?q?=EC=B4=88=EA=B8=B0=ED=99=94=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/floor-plan/modal/movement/type/Updown.jsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/floor-plan/modal/movement/type/Updown.jsx b/src/components/floor-plan/modal/movement/type/Updown.jsx index 2a3f2393..f03685e7 100644 --- a/src/components/floor-plan/modal/movement/type/Updown.jsx +++ b/src/components/floor-plan/modal/movement/type/Updown.jsx @@ -12,7 +12,7 @@ const UP_DOWN_TYPE = { export default function Updown({ UP_DOWN_REF }) { const { getMessage } = useMessage() const [type, setType] = useState(UP_DOWN_TYPE.UP) - const [filledInput, setFilledInput] = useState('') + const [filledInput, setFilledInput] = useState('100') const currentObject = useRecoilValue(currentObjectState) const handleFocus = () => { if (currentObject === null) { @@ -71,7 +71,6 @@ export default function Updown({ UP_DOWN_REF }) { Date: Thu, 30 Oct 2025 16:38:48 +0900 Subject: [PATCH 4/7] =?UTF-8?q?log=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/roofcover/useMovementSetting.js | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/hooks/roofcover/useMovementSetting.js b/src/hooks/roofcover/useMovementSetting.js index 3b285e77..f421a3bb 100644 --- a/src/hooks/roofcover/useMovementSetting.js +++ b/src/hooks/roofcover/useMovementSetting.js @@ -285,12 +285,6 @@ export function useMovementSetting(id) { const midY = Big(target.y1).plus(target.y2).div(2) const wall = canvas.getObjects().find((obj) => obj.id === target.attributes.wallId) - wall.lines.forEach(line => { - - console.log(classifyWallLine(wall, line)) - - }) - let linePosition = classifyWallLine(wall, target).position; if (target.y1 === target.y2) { //수평벽 @@ -622,22 +616,22 @@ export function useMovementSetting(id) { .forEach((target) => { const currentLine = target.line - console.log("linePosition::::::::::::::", linePosition) + //console.log("linePosition::::::::::::::", linePosition) if (UP_DOWN_REF.DOWN_RADIO_REF.current.checked ){ //position확인 if(linePosition === 'bottom' || linePosition === 'right') { - console.log("1value::::::::::::::", value.toString()) + //console.log("1value::::::::::::::", value.toString()) value = value.neg() } }else { if(linePosition === 'top' || linePosition === 'left') { - console.log("1value::::::::::::::", value.toString()) + //console.log("1value::::::::::::::", value.toString()) value = value.neg() } } - console.log("2value::::::::::::::", value.toString()) + //console.log("2value::::::::::::::", value.toString()) const index = baseLines.findIndex((line) => line === currentLine) const nextLine = baseLines[(index + 1) % baseLines.length] const prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length] From 03c31c82ff7e8ce7c71d0d29aa4bc773dd6ac5c4 Mon Sep 17 00:00:00 2001 From: ysCha Date: Fri, 31 Oct 2025 18:08:23 +0900 Subject: [PATCH 5/7] =?UTF-8?q?log=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/roofcover/useMovementSetting.js | 78 +- src/util/skeleton-utils.js | 1104 +++++++++++++++++---- 2 files changed, 941 insertions(+), 241 deletions(-) diff --git a/src/hooks/roofcover/useMovementSetting.js b/src/hooks/roofcover/useMovementSetting.js index f421a3bb..91ca0093 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 { getSelectLinePosition } from '@/util/skeleton-utils' import { useMouse } from '@/hooks/useMouse' //동선이동 형 올림 내림 @@ -285,7 +286,14 @@ export function useMovementSetting(id) { const midY = Big(target.y1).plus(target.y2).div(2) const wall = canvas.getObjects().find((obj) => obj.id === target.attributes.wallId) - let linePosition = classifyWallLine(wall, target).position; + const result = getSelectLinePosition(wall, target, { + testDistance: 5, // 테스트 거리 + debug: true // 디버깅 로그 출력 + }); + //console.log("1111litarget:::::", target); + //console.log("1111linePosition:::::", result.position); // 'top', 'bottom', 'left', 'right' + + let linePosition = result.position; if (target.y1 === target.y2) { //수평벽 @@ -440,7 +448,8 @@ export function useMovementSetting(id) { roof.moveFlowLine = parseInt(moveFlowLine, 10) || 0; roof.moveUpDown = parseInt(moveUpDown, 10) || 0; roof.moveDirect = ""; - roof.moveSelectLine = target; + roof.moveSelectLine = target + //console.log("target::::", target, roof.moveSelectLine) const wall = canvas.getObjects().find((obj) => obj.name === POLYGON_TYPE.WALL && obj.attributes.roofId === roofId) const baseLines = wall.baseLines let centerPoint = wall.getCenterPoint(); @@ -582,7 +591,7 @@ export function useMovementSetting(id) { value = value.neg() } } else { - console.log("error::", UP_DOWN_REF) + //console.log("error::", UP_DOWN_REF) value = UP_DOWN_REF.FILLED_INPUT_REF.current.value !== '' ? Big(UP_DOWN_REF.FILLED_INPUT_REF.current.value) @@ -606,10 +615,22 @@ export function useMovementSetting(id) { // value = value.neg() // } } + // console.log("2222titarget:::::", target); + // console.log("2222저장된 moveSelectLine:", roof.moveSelectLine); + // console.log("222wall::::", wall.points) + const result = getSelectLinePosition(wall, target, { + testDistance: 5, // 테스트 거리 + debug: true // 디버깅 로그 출력 + }); - let linePosition = classifyWallLine(wall, target).position; + //console.log("2222linePosition:::::", result.position); + +// 디버깅용 분류 결과 확인 + + let linePosition = result.position; + roof.movePosition = linePosition value = value.div(10) targetBaseLines .filter((line) => Math.sqrt(Math.pow(line.line.x2 - line.line.x1, 2) + Math.pow(line.line.y2 - line.line.y1, 2)) >= 1) @@ -617,7 +638,7 @@ export function useMovementSetting(id) { const currentLine = target.line //console.log("linePosition::::::::::::::", linePosition) - if (UP_DOWN_REF.DOWN_RADIO_REF.current.checked ){ + if (UP_DOWN_REF?.DOWN_RADIO_REF?.current?.checked ){ //position확인 if(linePosition === 'bottom' || linePosition === 'right') { //console.log("1value::::::::::::::", value.toString()) @@ -685,52 +706,6 @@ export function useMovementSetting(id) { } // javascript - const classifyWallLine = (wall, line, epsilon = 0.5, offset = 10)=> { - if (!wall || !line) return { orientation: 'unknown', position: 'unknown' } - - const center = wall.getCenterPoint() - const dx = Math.abs(line.x2 - line.x1) - const dy = Math.abs(line.y2 - line.y1) - - let orientation = 'slanted' - if (dy < epsilon && dx >= epsilon) orientation = 'horizontal' - else if (dx < epsilon && dy >= epsilon) orientation = 'vertical' - else orientation = dx > dy ? 'horizontal' : 'vertical' - - const midX = (line.x1 + line.x2) / 2 - const midY = (line.y1 + line.y2) / 2 - - // checkPoint: mid에서 center 방향으로 약간 이동한 점을 계산 - let checkX = midX - let checkY = midY - if (orientation === 'horizontal') { - const dir = Math.sign(center.y - midY) || 1 - checkY = midY + dir * offset - } else if (orientation === 'vertical') { - const dir = Math.sign(center.x - midX) || 1 - checkX = midX + dir * offset - } else { - const dirX = Math.sign(center.x - midX) || 1 - const dirY = Math.sign(center.y - midY) || 1 - checkX = midX + dirX * offset - checkY = midY + dirY * offset - } - - const inside = typeof wall.inPolygon === 'function' ? wall.inPolygon({ x: checkX, y: checkY }) : true - - let position = 'unknown' - if (orientation === 'horizontal') { - const dir = center.y - midY - if (inside) position = dir > 0 ? 'top' : 'bottom' - else position = dir > 0 ? 'bottom' : 'top' - } else if (orientation === 'vertical') { - const dir = center.x - midX - if (inside) position = dir > 0 ? 'left' : 'right' - else position = dir > 0 ? 'right' : 'left' - } - - return { orientation, position, mid: { x: midX, y: midY }, center, checkPoint: { x: checkX, y: checkY }, inside } - } return { @@ -742,7 +717,6 @@ export function useMovementSetting(id) { FLOW_LINE_REF, UP_DOWN_REF, handleSave, - classifyWallLine } } diff --git a/src/util/skeleton-utils.js b/src/util/skeleton-utils.js index d7168e74..0571569b 100644 --- a/src/util/skeleton-utils.js +++ b/src/util/skeleton-utils.js @@ -6,6 +6,7 @@ import { getDegreeByChon } from '@/util/canvas-util' import Big from 'big.js' import { line } from 'framer-motion/m' import { QPolygon } from '@/components/fabric/QPolygon' +import { point } from '@turf/turf' /** * 지붕 폴리곤의 스켈레톤(중심선)을 생성하고 캔버스에 그립니다. @@ -23,13 +24,17 @@ export const drawSkeletonRidgeRoof = (roofId, canvas, textMode) => { } -const movingRidgeFromSkeleton = (roofId, canvas) => { +const movingLineFromSkeleton = (roofId, canvas) => { let roof = canvas?.getObjects().find((object) => object.id === roofId) + let moveDirection = roof.moveDirect; let moveFlowLine = roof.moveFlowLine??0; - const selectLine = roof.moveSelectLine; + let moveUpDown = roof.moveUpDown??0; + const getSelectLine = () => roof.moveSelectLine; + const selectLine = getSelectLine(); + let movePosition = roof.movePosition; const startPoint = selectLine.startPoint const endPoint = selectLine.endPoint @@ -37,6 +42,10 @@ const movingRidgeFromSkeleton = (roofId, canvas) => { const oldPoints = canvas?.skeleton.lastPoints ?? orgRoofPoints // 여기도 변경 const oppositeLine = findOppositeLine(canvas.skeleton.Edges, startPoint, endPoint, oldPoints); + const wall = canvas.getObjects().find((obj) => obj.name === POLYGON_TYPE.WALL && obj.attributes.roofId === roofId) + const baseLines = wall.baseLines + roof.basePoints = createOrderedBasePoints(roof.points, baseLines) + const skeletonPolygon = canvas.getObjects().filter((object) => object.skeletonType === 'polygon' && object.parentId === roofId) const skeletonLines = canvas.getObjects().filter((object) => object.skeletonType === 'line' && object.parentId === roofId) @@ -46,193 +55,261 @@ const movingRidgeFromSkeleton = (roofId, canvas) => { console.log('No opposite line found'); } - let baseLines = canvas.getObjects().filter((object) => object.name === 'baseLine' && object.parentId === roofId) || []; - console.log('baseLines::::', baseLines); - let baseLinePoints = baseLines.map((line) => ({x:line.x1, y:line.y1})); + if(moveFlowLine !== 0) { + return oldPoints.map((point, index) => { + console.log('Point:', point); + const newPoint = { ...point }; + const absMove = Big(moveFlowLine).times(2).div(10); + + console.log('skeletonBuilder moveDirection:', moveDirection); + + switch (moveDirection) { + case 'left': + // Move left: decrease X + if (moveFlowLine !== 0) { + for (const line of oppositeLine) { + if (line.position === 'left') { + if (isSamePoint(newPoint, line.start)) { + newPoint.x = Big(line.start.x).plus(absMove).toNumber(); + } else if (isSamePoint(newPoint, line.end)) { + newPoint.x = Big(line.end.x).plus(absMove).toNumber(); + } + + break; + + } + + } + } else if (moveUpDown !== 0) { + + } + + break; + case 'right': + for (const line of oppositeLine) { + if (line.position === 'right') { + if (isSamePoint(newPoint, line.start)) { + newPoint.x = Big(line.start.x).minus(absMove).toNumber(); + } else if (isSamePoint(newPoint, line.end)) { + newPoint.x = Big(line.end.x).minus(absMove).toNumber(); + } + break + + } + + } + + break; + case 'up': + // Move up: decrease Y (toward top of screen) + + for (const line of oppositeLine) { + if (line.position === 'top') { + if (isSamePoint(newPoint, line.start)) { + newPoint.y = Big(line.start.y).minus(absMove).toNumber(); + } else if (isSamePoint(newPoint, line.end)) { + newPoint.y = Big(line.end.y).minus(absMove).toNumber(); + } + break; + + } + } + + break; + case 'down': + // Move down: increase Y (toward bottom of screen) + for (const line of oppositeLine) { + + if (line.position === 'bottom') { + + console.log('oldPoint:', point); + + if (isSamePoint(newPoint, line.start)) { + newPoint.y = Big(line.start.y).minus(absMove).toNumber(); + + } else if (isSamePoint(newPoint, line.end)) { + newPoint.y = Big(line.end.y).minus(absMove).toNumber(); + } + break; + } - /* - walls.forEach((wall) => { - if (wall.baseLines.length === 0) { - wall.baseLines = canvas.getObjects().filter((obj) => obj.name === 'baseLine' && obj.attributes.wallId === wall.id) + } + break; + default : +// 사용 예시 } - // Extract points from each baseLine - wall.baseLines.forEach(line => { - console.log("useSk:::", line.x1, line.y1, line.x2, line.y2); - // 시작점과 끝점을 배열에 추가 - const points = [ - { x: line.x1, y: line.y1 }, - { x: line.x2, y: line.y2 } - ]; - - points.forEach(point => { - const key = `${point.x},${point.y}`; - if (!pointSet.has(key)) { - pointSet.add(key); - baseLinePoints.push(point); - } - }); - }); - + console.log('newPoint:', newPoint); + //baseline 변경 + return newPoint; }) - return [...baseLinePoints]; - */ - - return oldPoints.map((point, index) => { - - const newPoint = { ...point }; - const absMove = Big(moveFlowLine).times(2).div(10); - //console.log('absMove:', absMove); - - const skeletonLines = canvas.skeletonLines; - - //console.log('skeleton line:', canvas.skeletonLines); - // const changeSkeletonLine = (canvas, oldPoint, newPoint, str) => { - // for (const line of canvas.skeletonLines) { - // if (str === 'start' && isSamePoint(line.startPoint, oldPoint)) { - // // Fabric.js 객체의 set 메서드로 속성 업데이트 - // line.set({ - // x1: newPoint.x, - // y1: newPoint.y, - // x2: line.x2 || line.endPoint?.x, - // y2: line.y2 || line.endPoint?.y - // }); - // line.startPoint = newPoint; // 참조 업데이트 - // } - // else if (str === 'end' && isSamePoint(line.endPoint, oldPoint)) { - // line.set({ - // x1: line.x1 || line.startPoint?.x, - // y1: line.y1 || line.startPoint?.y, - // x2: newPoint.x, - // y2: newPoint.y - // }); - // line.endPoint = newPoint; // 참조 업데이트 - // } - // } - // canvas.requestRenderAll(); - // console.log('skeleton line:', canvas.skeletonLines); - // } + } else if(moveUpDown !== 0) { + // const selectLine = getSelectLine(); + // + // console.log("wall::::", wall.points) + // console.log("저장된 3333moveSelectLine:", roof.moveSelectLine); + // console.log("저장된 3moveSelectLine:", selectLine); + // const result = getSelectLinePosition(wall, selectLine, { + // testDistance: 5, // 테스트 거리 + // debug: true // 디버깅 로그 출력 + // }); + // console.log("3333linePosition:::::", result.position); - console.log('skeletonBuilder moveDirection:', moveDirection); + const position = movePosition //result.position; + const absMove = Big(moveUpDown).times(1).div(10); + const modifiedStartPoints = []; + // oldPoints를 복사해서 새로운 points 배열 생성 + let newPoints = oldPoints.map(point => ({...point})); - switch (moveDirection) { - case 'left': - // Move left: decrease X - for (const line of oppositeLine) { - if (line.position === 'left') { - if (isSamePoint(newPoint, line.start)) { - newPoint.x = Big(line.start.x).plus(absMove).toNumber(); - } else if (isSamePoint(newPoint, line.end)) { - newPoint.x = Big(line.end.x).plus(absMove).toNumber(); - } + // selectLine과 일치하는 baseLines 찾기 + const matchingLines = baseLines + .map((line, index) => ({ ...line, findIndex: index })) + .filter(line => + (isSamePoint(line.startPoint, selectLine.startPoint) && + isSamePoint(line.endPoint, selectLine.endPoint)) || + (isSamePoint(line.startPoint, selectLine.endPoint) && + isSamePoint(line.endPoint, selectLine.startPoint)) + ); - break; - // } else if (line.position === 'right') { - // if (isSamePoint(newPoint, line.start)) { - // newPoint.x = Big(line.start.x).minus(absMove).toNumber(); - // (newPoint.x < originalPoint.x)? newPoint.x = originalPoint.x : newPoint.x; // 변경된 이름 사용 - // }else if(isSamePoint(newPoint, line.end)) { - // newPoint.x = Big(line.end.x).minus(absMove).toNumber(); - // (newPoint.x < originalPoint.x)? newPoint.x = originalPoint.x : newPoint.x; // 변경된 이름 사용 - // } - } - } + matchingLines.forEach(line => { + const originalStartPoint = line.startPoint; + const originalEndPoint = line.endPoint; + const offset = line.attributes.offset + // 새로운 좌표 계산 + let newStartPoint = {...originalStartPoint}; + let newEndPoint = {...originalEndPoint}; - break; - case 'right': - for (const line of oppositeLine) { - if (line.position === 'right') { - if (isSamePoint(newPoint, line.start)) { - newPoint.x = Big(line.start.x).minus(absMove).toNumber(); - } else if (isSamePoint(newPoint, line.end)) { - newPoint.x = Big(line.end.x).minus(absMove).toNumber(); - } - break - // }else if(line.position === 'left') { - // if (isSamePoint(newPoint, line.start)) { - // newPoint.x = Big(line.start.x).plus(absMove).toNumber(); - // (newPoint.x > originalPoint.x)? newPoint.x = originalPoint.x : newPoint.x; // 변경된 이름 사용 - // } else if (isSamePoint(newPoint, line.end)) { - // newPoint.x= Big(line.end.x).plus(absMove).toNumber(); - // (newPoint.x > originalPoint.x)? newPoint.x = originalPoint.x : newPoint.x; // 변경된 이름 사용 - // } - } - - } - - break; - case 'up': - // Move up: decrease Y (toward top of screen) - - for (const line of oppositeLine) { - if (line.position === 'top') { - if (isSamePoint(newPoint, line.start)) { - newPoint.y = Big(line.start.y).minus(absMove).toNumber(); - } else if (isSamePoint(newPoint, line.end)) { - newPoint.y = Big(line.end.y).minus(absMove).toNumber(); + // 위치와 방향에 따라 좌표 조정 +/* + switch (position) { + case 'left': + if (moveDirection === 'up') { + newStartPoint.x = Big(line.startPoint.x).minus(absMove).toNumber(); + newEndPoint.x = Big(line.endPoint.x).minus(absMove).toNumber(); + } else if (moveDirection === 'down') { + newStartPoint.x = Big(line.startPoint.x).plus(absMove).toNumber(); + newEndPoint.x = Big(line.endPoint.x).plus(absMove).toNumber(); + } + break; + case 'right': + if (moveDirection === 'up') { + newStartPoint.x = Big(line.startPoint.x).plus(absMove).toNumber(); + newEndPoint.x = Big(line.endPoint.x).plus(absMove).toNumber(); + } else if (moveDirection === 'down') { + newStartPoint.x = Big(line.startPoint.x).minus(absMove).toNumber(); + newEndPoint.x = Big(line.endPoint.x).minus(absMove).toNumber(); + } + break; + case 'top': + if (moveDirection === 'up') { + newStartPoint.y = Big(line.startPoint.y).minus(absMove).toNumber(); + newEndPoint.y = Big(line.endPoint.y).minus(absMove).toNumber(); + } else if (moveDirection === 'down') { + newStartPoint.y = Big(line.startPoint.y).plus(absMove).toNumber(); + newEndPoint.y = Big(line.endPoint.y).plus(absMove).toNumber(); + } + break; + case 'bottom': + if (moveDirection === 'up') { + newStartPoint.y = Big(line.startPoint.y).plus(absMove).toNumber(); + newEndPoint.y = Big(line.endPoint.y).plus(absMove).toNumber(); + } else if (moveDirection === 'down') { + newStartPoint.y = Big(line.startPoint.y).minus(absMove).toNumber(); + newEndPoint.y = Big(line.endPoint.y).minus(absMove).toNumber(); } break; - - // }else if(line.position === 'bottom') { - // if(newPoint.y !== originalPoint.y) { - // if (isSamePoint(newPoint, line.start)) { - // newPoint.y = Big(line.start.y).minus(absMove).toNumber(); - // (newPoint.y < originalPoint.y)? newPoint.y = originalPoint.y : newPoint.y; // 변경된 이름 사용 - // }else if(isSamePoint(newPoint, line.end)) { - // newPoint.y = Big(line.end.y).minus(absMove).toNumber(); - // (newPoint.y < originalPoint.y)? newPoint.y = originalPoint.y : newPoint.y; // 변경된 이름 사용 - // } - // } - } } - - break; - case 'down': - // Move down: increase Y (toward bottom of screen) - for (const line of oppositeLine) { - - if (line.position === 'bottom') { - - console.log('oldPoint:', point); - - if (isSamePoint(newPoint, line.start)) { - newPoint.y = Big(line.start.y).minus(absMove).toNumber(); - } else if (isSamePoint(newPoint, line.end)) { - newPoint.y = Big(line.end.y).minus(absMove).toNumber(); +*/ +// 원본 라인 업데이트 + // newPoints 배열에서 일치하는 포인트들을 찾아서 업데이트 + console.log('absMove::', absMove); + newPoints.forEach((point, index) => { + if(position === 'bottom'){ + if (moveDirection === 'in') { + if(isSamePoint(roof.basePoints[index], originalStartPoint)) { + point.y = Big(point.y).minus(absMove).toNumber(); + } + if (isSamePoint(roof.basePoints[index], originalEndPoint)) { + point.y = Big(point.y).minus(absMove).toNumber(); + } + }else if (moveDirection === 'out'){ + if(isSamePoint(roof.basePoints[index], originalStartPoint)) { + point.y = Big(point.y).plus(absMove).toNumber(); + } + if (isSamePoint(roof.basePoints[index], originalEndPoint)) { + point.y = Big(point.y).plus(absMove).toNumber(); + } + } + + }else if (position === 'top'){ + if(moveDirection === 'in'){ + if(isSamePoint(roof.basePoints[index], originalStartPoint)) { + point.y = Big(point.y).plus(absMove).toNumber(); + } + if (isSamePoint(roof.basePoints[index], originalEndPoint)) { + point.y = Big(point.y).plus(absMove).toNumber(); + } + }else if(moveDirection === 'out'){ + if(isSamePoint(roof.basePoints[index], originalStartPoint)) { + point.y = Big(point.y).minus(absMove).toNumber(); + } + if (isSamePoint(roof.basePoints[index], originalEndPoint)) { + point.y = Big(point.y).minus(absMove).toNumber(); + } + } + + }else if(position === 'left'){ + if(moveDirection === 'in'){ + if(isSamePoint(roof.basePoints[index], originalStartPoint)) { + point.x = Big(point.x).plus(absMove).toNumber(); + } + if (isSamePoint(roof.basePoints[index], originalEndPoint)) { + point.x = Big(point.x).plus(absMove).toNumber(); + } + }else if(moveDirection === 'out'){ + if(isSamePoint(roof.basePoints[index], originalStartPoint)) { + point.x = Big(point.x).minus(absMove).toNumber(); + } + if (isSamePoint(roof.basePoints[index], originalEndPoint)) { + point.x = Big(point.x).minus(absMove).toNumber(); + } + } + + }else if(position === 'right'){ + if(moveDirection === 'in'){ + if(isSamePoint(roof.basePoints[index], originalStartPoint)) { + point.x = Big(point.x).minus(absMove).toNumber(); + } + if (isSamePoint(roof.basePoints[index], originalEndPoint)) { + point.x = Big(point.x).minus(absMove).toNumber(); + } + }else if(moveDirection === 'out'){ + if(isSamePoint(roof.basePoints[index], originalStartPoint)) { + point.x = Big(point.x).plus(absMove).toNumber(); + } + if (isSamePoint(roof.basePoints[index], originalEndPoint)) { + point.x = Big(point.x).plus(absMove).toNumber(); + } } - // }else if(line.position === 'top') { - // - // if(newPoint.y !== originalPoint.y) { - // - // if (isSamePoint(newPoint, line.start)) { - // newPoint.y = Big(line.start.y).plus(absMove).toNumber(); - // (newPoint.y > originalPoint.y)? newPoint.y = originalPoint.y : newPoint.y; // 변경된 이름 사용 - // } else if (isSamePoint(newPoint, line.end)) { - // newPoint.y = Big(line.end.y).plus(absMove).toNumber(); - // (newPoint.y > originalPoint.y)? newPoint.y = originalPoint.y : newPoint.y; // 변경된 이름 사용 - // } - // } - break; } - } - break; - } - - - console.log('newPoint:', newPoint); - //baseline 변경 - return newPoint; - }) + }); + // 원본 baseLine도 업데이트 + line.startPoint = newStartPoint; + line.endPoint = newEndPoint; + }); + return newPoints; + } } + /** * SkeletonBuilder를 사용하여 스켈레톤을 생성하고 내부선을 그립니다. * @param {string} roofId - 지붕 ID @@ -281,18 +358,12 @@ export const skeletonBuilder = (roofId, canvas, textMode) => { //마루이동 - if (moveFlowLine !== 0) { - - - points = movingRidgeFromSkeleton(roofId, canvas) - - + if (moveFlowLine !== 0 || moveUpDown !== 0) { + points = movingLineFromSkeleton(roofId, canvas) } -//처마 - if(moveUpDown !== 0) { - } + console.log('points:', points); const geoJSONPolygon = toGeoJSON(points) @@ -313,6 +384,7 @@ export const skeletonBuilder = (roofId, canvas, textMode) => { canvas.skeletonStates[roofId] = true canvas.skeletonLines = []; canvas.skeletonLines.push(...roof.innerLines) + roof.skeletonLines = canvas.skeletonLines; const cleanSkeleton = { Edges: skeleton.Edges.map(edge => ({ @@ -355,7 +427,7 @@ export const skeletonBuilder = (roofId, canvas, textMode) => { const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { if (!skeleton?.Edges) return [] let roof = canvas?.getObjects().find((object) => object.id === roofId) - const skeletonLines = [] + let skeletonLines = [] const processedInnerEdges = new Set() // 1. 모든 Edge를 순회하며 기본 스켈레톤 선(용마루)을 수집합니다. @@ -366,10 +438,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { processEavesEdge(roofId, canvas, skeleton, edgeResult, skeletonLines); }); - -/* // 2. 케라바(Gable) 속성을 가진 외벽선에 해당하는 스켈레톤을 후처리합니다. - skeleton.Edges.forEach(edgeResult => { const { Begin, End } = edgeResult.Edge; @@ -401,9 +470,9 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { } }); -* + //2. 연결이 끊어진 스켈레톤 선을 찾아 연장합니다. - const { disconnectedLines } = findDisconnectedSkeletonLines(skeletonLines, baseLines); + const { disconnectedLines } = findDisconnectedSkeletonLines(skeletonLines, roof.lines); if(disconnectedLines.length > 0) { @@ -424,7 +493,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => { } -*/ + // 3. 최종적으로 정리된 스켈레톤 선들을 QLine 객체로 변환하여 캔버스에 추가합니다. const innerLines = []; @@ -514,7 +583,7 @@ function processEavesEdge(roofId, canvas, skeleton, edgeResult, skeletonLines) { ); if(!outerLine) { outerLine = findMatchingLine(edgeResult.Polygon, roof, roof.points); - console.log('Has matching line:', outerLine); + //console.log('Has matching line:', outerLine); } let pitch = outerLine?.attributes?.pitch??0 @@ -543,11 +612,9 @@ function processEavesEdge(roofId, canvas, skeleton, edgeResult, skeletonLines) { const p1 = polygonPoints[i]; const p2 = polygonPoints[(i + 1) % polygonPoints.length]; - // 외벽선에 해당하는 스켈레톤 선은 제외하고 내부선만 추가 - // if (!isOuterEdge(p1, p2, [edgeResult.Edge])) { - //외벽선 밖으로 나간 선을 정리한다(roof.line의 교점까지 정리한다) + // 지붕 경계선과 교차 확인 및 클리핑 - const clippedLine = clipLineToRoofBoundary(p1, p2, roof.lines); + const clippedLine = clipLineToRoofBoundary(p1, p2, roof.lines, roof.moveSelectLine); //console.log('clipped line', clippedLine.p1, clippedLine.p2); const isOuterLine = isOuterEdge(clippedLine.p1, clippedLine.p2, [edgeResult.Edge]) addRawLine(roof.id, skeletonLines, clippedLine.p1, clippedLine.p2, 'ridge', 'red', 5, pitch, isOuterLine); @@ -1471,13 +1538,18 @@ function findPolygonsContainingLine(edges, p1, p2) { * @param {Object} p1 - 선분의 시작점 {x, y} * @param {Object} p2 - 선분의 끝점 {x, y} * @param {Array} roofLines - 지붕 경계선 배열 (QLine 객체의 배열) + * @param skeletonLines * @returns {Object} {p1: {x, y}, p2: {x, y}} - 다각형 내부로 클리핑된 선분 */ -function clipLineToRoofBoundary(p1, p2, roofLines) { +function clipLineToRoofBoundary(p1, p2, roofLines, selectLine) { if (!roofLines || !roofLines.length) { return { p1: { ...p1 }, p2: { ...p2 } }; } + const dx = Math.abs(p2.x - p1.x); + const dy = Math.abs(p2.y - p1.y); + const isDiagonal = dx > 0.5 && dy > 0.5; + // 기본값으로 원본 좌표 설정 let clippedP1 = { x: p1.x, y: p1.y }; let clippedP2 = { x: p2.x, y: p2.y }; @@ -1492,6 +1564,10 @@ function clipLineToRoofBoundary(p1, p2, roofLines) { // 두 점 모두 내부에 있으면 그대로 반환 if (p1Inside && p2Inside) { + if(!selectLine || isDiagonal){ + return { p1: clippedP1, p2: clippedP2 }; + } + console.log('평행선::', clippedP1, clippedP2) return { p1: clippedP1, p2: clippedP2 }; } @@ -1516,7 +1592,7 @@ function clipLineToRoofBoundary(p1, p2, roofLines) { } } - console.log('Found intersections:', intersections.length); + //console.log('Found intersections:', intersections.length); // 교차점들을 t 값으로 정렬 intersections.sort((a, b) => a.t - b.t); @@ -1634,4 +1710,654 @@ function getLineDirection(p1, p2) { if ((angle >= 45 && angle < 135)) return 'bottom'; if ((angle >= 135 || angle < -135)) return 'left'; return 'top'; // (-135 ~ -45) +} + +// selectLine과 baseLines 비교하여 방향 찾기 +function findLineDirection(selectLine, baseLines) { + for (const baseLine of baseLines) { + // baseLine의 시작점과 끝점 + const baseStart = baseLine.startPoint; + const baseEnd = baseLine.endPoint; + + // selectLine의 시작점과 끝점 + const selectStart = selectLine.startPoint; + const selectEnd = selectLine.endPoint; + + // 정방향 또는 역방향으로 일치하는지 확인 + if ((isSamePoint(baseStart, selectStart) && isSamePoint(baseEnd, selectEnd)) || + (isSamePoint(baseStart, selectEnd) && isSamePoint(baseEnd, selectStart))) { + + // baseLine의 방향 계산 + const dx = baseEnd.x - baseStart.x; + const dy = baseEnd.y - baseStart.y; + + // 기울기를 바탕으로 방향 판단 + if (Math.abs(dx) > Math.abs(dy)) { + return dx > 0 ? 'right' : 'left'; + } else { + return dy > 0 ? 'down' : 'up'; + } + } + } + + return null; // 일치하는 라인이 없는 경우 +} + +function getLinePositionRelativeToWall(selectLine, wall) { + // wall의 경계를 가져옵니다. + const bounds = wall.getBoundingRect(); + const { left, top, width, height } = bounds; + const right = left + width; + const bottom = top + height; + + // selectLine의 중간점을 계산합니다. + const midX = (selectLine.startPoint.x + selectLine.endPoint.x) / 2; + const midY = (selectLine.startPoint.y + selectLine.endPoint.y) / 2; + + // 경계로부터의 거리를 계산합니다. + const distanceToLeft = Math.abs(midX - left); + const distanceToRight = Math.abs(midX - right); + const distanceToTop = Math.abs(midY - top); + const distanceToBottom = Math.abs(midY - bottom); + + // 가장 가까운 경계를 찾습니다. + const minDistance = Math.min( + distanceToLeft, + distanceToRight, + distanceToTop, + distanceToBottom + ); + + // 가장 가까운 경계를 반환합니다. + if (minDistance === distanceToLeft) return 'left'; + if (minDistance === distanceToRight) return 'right'; + if (minDistance === distanceToTop) return 'top'; + return 'bottom'; +} + +/** + * Convert a line into an array of coordinate points + * @param {Object} line - Line object with startPoint and endPoint + * @param {Object} line.startPoint - Start point with x, y coordinates + * @param {Object} line.endPoint - End point with x, y coordinates + * @param {number} [step=1] - Distance between points (default: 1) + * @returns {Array} Array of points [{x, y}, ...] + */ +function lineToPoints(line, step = 1) { + const { startPoint, endPoint } = line; + const points = []; + + // Add start point + points.push({ x: startPoint.x, y: startPoint.y }); + + // Calculate distance between points + const dx = endPoint.x - startPoint.x; + const dy = endPoint.y - startPoint.y; + const distance = Math.sqrt(dx * dx + dy * dy); + const steps = Math.ceil(distance / step); + + // Add intermediate points + for (let i = 1; i < steps; i++) { + const t = i / steps; + points.push({ + x: startPoint.x + dx * t, + y: startPoint.y + dy * t + }); + } + + // Add end point + points.push({ x: endPoint.x, y: endPoint.y }); + + return points; +} + +/** + * 다각형의 모든 좌표를 offset만큼 안쪽/바깥쪽으로 이동 + * @param {Array} points - 다각형 좌표 배열 [{x, y}, ...] + * @param {number} offset - offset 값 (양수: 안쪽, 음수: 바깥쪽) + * @returns {Array} offset이 적용된 새로운 좌표 배열 + */ +function offsetPolygon(points, offset) { + if (points.length < 3) return points; + + const offsetPoints = []; + const numPoints = points.length; + + for (let i = 0; i < numPoints; i++) { + const prevIndex = (i - 1 + numPoints) % numPoints; + const currentIndex = i; + const nextIndex = (i + 1) % numPoints; + + const prevPoint = points[prevIndex]; + const currentPoint = points[currentIndex]; + const nextPoint = points[nextIndex]; + + // 이전 변의 방향 벡터 + const prevVector = { + x: currentPoint.x - prevPoint.x, + y: currentPoint.y - prevPoint.y + }; + + // 다음 변의 방향 벡터 + const nextVector = { + x: nextPoint.x - currentPoint.x, + y: nextPoint.y - currentPoint.y + }; + + // 정규화 + const prevLength = Math.sqrt(prevVector.x * prevVector.x + prevVector.y * prevVector.y); + const nextLength = Math.sqrt(nextVector.x * nextVector.x + nextVector.y * nextVector.y); + + if (prevLength === 0 || nextLength === 0) continue; + + const prevNormal = { + x: -prevVector.y / prevLength, + y: prevVector.x / prevLength + }; + + const nextNormal = { + x: -nextVector.y / nextLength, + y: nextVector.x / nextLength + }; + + // 평균 법선 벡터 계산 + const avgNormal = { + x: (prevNormal.x + nextNormal.x) / 2, + y: (prevNormal.y + nextNormal.y) / 2 + }; + + // 평균 법선 벡터 정규화 + const avgLength = Math.sqrt(avgNormal.x * avgNormal.x + avgNormal.y * avgNormal.y); + if (avgLength === 0) continue; + + const normalizedAvg = { + x: avgNormal.x / avgLength, + y: avgNormal.y / avgLength + }; + + // 각도 보정 (예각일 때 offset 조정) + const cosAngle = prevNormal.x * nextNormal.x + prevNormal.y * nextNormal.y; + const adjustedOffset = Math.abs(cosAngle) > 0.1 ? offset / Math.abs(cosAngle) : offset; + + // 새로운 점 계산 + const offsetPoint = { + x: currentPoint.x + normalizedAvg.x * adjustedOffset, + y: currentPoint.y + normalizedAvg.y * adjustedOffset + }; + + offsetPoints.push(offsetPoint); + } + + return offsetPoints; +} + +/** + * baseLines를 연결하여 다각형 순서로 정렬된 점들 반환 + * @param {Array} baseLines - 라인 배열 + * @returns {Array} 순서대로 정렬된 점들의 배열 + */ +function getOrderedBasePoints(baseLines) { + if (baseLines.length === 0) return []; + + const points = []; + const usedLines = new Set(); + + // 첫 번째 라인으로 시작 + let currentLine = baseLines[0]; + points.push({ ...currentLine.startPoint }); + points.push({ ...currentLine.endPoint }); + usedLines.add(0); + + let lastPoint = currentLine.endPoint; + + // 연결된 라인들을 찾아가며 점들 수집 + while (usedLines.size < baseLines.length) { + let foundNext = false; + + for (let i = 0; i < baseLines.length; i++) { + if (usedLines.has(i)) continue; + + const line = baseLines[i]; + + // 현재 끝점과 연결되는 라인 찾기 + if (isSamePoint(lastPoint, line.startPoint)) { + points.push({ ...line.endPoint }); + lastPoint = line.endPoint; + usedLines.add(i); + foundNext = true; + break; + } else if (isSamePoint(lastPoint, line.endPoint)) { + points.push({ ...line.startPoint }); + lastPoint = line.startPoint; + usedLines.add(i); + foundNext = true; + break; + } + } + + if (!foundNext) break; // 연결되지 않는 경우 중단 + } + + // 마지막 점이 첫 번째 점과 같으면 제거 (닫힌 다각형) + if (points.length > 2 && isSamePoint(points[0], points[points.length - 1])) { + points.pop(); + } + + return points; +} + +/** + * roof.points와 baseLines가 정확히 대응되는 경우의 간단한 버전 + */ +function createOrderedBasePoints(roofPoints, baseLines) { + const basePoints = []; + + // baseLines에서 연결된 순서대로 점들을 추출 + const orderedBasePoints = getOrderedBasePoints(baseLines); + + // roofPoints의 개수와 맞추기 + if (orderedBasePoints.length >= roofPoints.length) { + return orderedBasePoints.slice(0, roofPoints.length); + } + + // 부족한 경우 roofPoints 기반으로 보완 + roofPoints.forEach((roofPoint, index) => { + if (index < orderedBasePoints.length) { + basePoints.push(orderedBasePoints[index]); + } else { + basePoints.push({ ...roofPoint }); // fallback + } + }); + + return basePoints; +} + +export const getSelectLinePosition = (wall, selectLine, options = {}) => { + const { testDistance = 10, epsilon = 0.5, debug = false } = options; + + if (!wall || !selectLine) { + if (debug) console.log('ERROR: wall 또는 selectLine이 없음'); + return { position: 'unknown', orientation: 'unknown', error: 'invalid_input' }; + } + + // selectLine의 좌표 추출 + const lineCoords = extractLineCoords(selectLine); + if (!lineCoords.valid) { + if (debug) console.log('ERROR: selectLine 좌표가 유효하지 않음'); + return { position: 'unknown', orientation: 'unknown', error: 'invalid_coords' }; + } + + const { x1, y1, x2, y2 } = lineCoords; + + // 라인 방향 분석 + const lineInfo = analyzeLineOrientation(x1, y1, x2, y2, epsilon); + + if (debug) { + console.log('=== getSelectLinePosition ==='); + console.log('selectLine 좌표:', lineCoords); + console.log('라인 방향:', lineInfo.orientation); + } + + // 라인의 중점 + const midX = (x1 + x2) / 2; + const midY = (y1 + y2) / 2; + + let position = 'unknown'; + + if (lineInfo.orientation === 'horizontal') { + // 수평선: top 또는 bottom 판단 + + // 바로 위쪽 테스트 포인트 + const topTestPoint = { x: midX, y: midY - testDistance }; + // 바로 아래쪽 테스트 포인트 + const bottomTestPoint = { x: midX, y: midY + testDistance }; + + const topIsInside = checkPointInPolygon(topTestPoint, wall); + const bottomIsInside = checkPointInPolygon(bottomTestPoint, wall); + + if (debug) { + console.log('수평선 테스트:'); + console.log(' 위쪽 포인트:', topTestPoint, '-> 내부:', topIsInside); + console.log(' 아래쪽 포인트:', bottomTestPoint, '-> 내부:', bottomIsInside); + } + + // top 조건: 위쪽이 외부, 아래쪽이 내부 + if (!topIsInside && bottomIsInside) { + position = 'top'; + } + // bottom 조건: 위쪽이 내부, 아래쪽이 외부 + else if (topIsInside && !bottomIsInside) { + position = 'bottom'; + } + + } else if (lineInfo.orientation === 'vertical') { + // 수직선: left 또는 right 판단 + + // 바로 왼쪽 테스트 포인트 + const leftTestPoint = { x: midX - testDistance, y: midY }; + // 바로 오른쪽 테스트 포인트 + const rightTestPoint = { x: midX + testDistance, y: midY }; + + const leftIsInside = checkPointInPolygon(leftTestPoint, wall); + const rightIsInside = checkPointInPolygon(rightTestPoint, wall); + + if (debug) { + console.log('수직선 테스트:'); + console.log(' 왼쪽 포인트:', leftTestPoint, '-> 내부:', leftIsInside); + console.log(' 오른쪽 포인트:', rightTestPoint, '-> 내부:', rightIsInside); + } + + // left 조건: 왼쪽이 외부, 오른쪽이 내부 + if (!leftIsInside && rightIsInside) { + position = 'left'; + } + // right 조건: 오른쪽이 외부, 왼쪽이 내부 + else if (leftIsInside && !rightIsInside) { + position = 'right'; + } + + } else { + // 대각선 + if (debug) console.log('대각선은 지원하지 않음'); + return { position: 'unknown', orientation: 'diagonal', error: 'not_supported' }; + } + + const result = { + position, + orientation: lineInfo.orientation, + method: 'inside_outside_test', + confidence: position !== 'unknown' ? 1.0 : 0.0, + testPoints: lineInfo.orientation === 'horizontal' ? { + top: { x: midX, y: midY - testDistance }, + bottom: { x: midX, y: midY + testDistance } + } : { + left: { x: midX - testDistance, y: midY }, + right: { x: midX + testDistance, y: midY } + }, + midPoint: { x: midX, y: midY } + }; + + if (debug) { + console.log('최종 결과:', result); + } + + return result; +}; + +// 점이 다각형 내부에 있는지 확인하는 함수 +const checkPointInPolygon = (point, wall) => { + // 1. wall의 inPolygon 메서드가 있으면 사용 + if (typeof wall.inPolygon === 'function') { + return wall.inPolygon(point); + } + + // 2. wall.baseLines를 이용한 Ray Casting Algorithm + if (!wall.baseLines || !Array.isArray(wall.baseLines)) { + console.warn('wall.baseLines가 없습니다'); + return false; + } + + return raycastingAlgorithm(point, wall.baseLines); +}; + +// Ray Casting Algorithm 구현 +const raycastingAlgorithm = (point, lines) => { + const { x, y } = point; + let intersectionCount = 0; + + for (const line of lines) { + const coords = extractLineCoords(line); + if (!coords.valid) continue; + + const { x1, y1, x2, y2 } = coords; + + // Ray casting: 점에서 오른쪽으로 수평선을 그어서 다각형 경계와의 교점 개수를 셈 + // 교점 개수가 홀수면 내부, 짝수면 외부 + + // 선분의 y 범위 확인 + if ((y1 > y) !== (y2 > y)) { + // x 좌표에서의 교점 계산 + const intersectX = (x2 - x1) * (y - y1) / (y2 - y1) + x1; + + // 점의 오른쪽에 교점이 있으면 카운트 + if (x < intersectX) { + intersectionCount++; + } + } + } + + // 홀수면 내부, 짝수면 외부 + return intersectionCount % 2 === 1; +}; + +// 라인 객체에서 좌표를 추출하는 헬퍼 함수 (중복 방지용 - 이미 있다면 제거) +const extractLineCoords = (line) => { + if (!line) { + return { x1: 0, y1: 0, x2: 0, y2: 0, valid: false }; + } + + let x1, y1, x2, y2; + + // 다양한 라인 객체 형태에 대응 + if (line.x1 !== undefined && line.y1 !== undefined && + line.x2 !== undefined && line.y2 !== undefined) { + x1 = line.x1; + y1 = line.y1; + x2 = line.x2; + y2 = line.y2; + } + else if (line.startPoint && line.endPoint) { + x1 = line.startPoint.x; + y1 = line.startPoint.y; + x2 = line.endPoint.x; + y2 = line.endPoint.y; + } + else if (line.p1 && line.p2) { + x1 = line.p1.x; + y1 = line.p1.y; + x2 = line.p2.x; + y2 = line.p2.y; + } + else { + return { x1: 0, y1: 0, x2: 0, y2: 0, valid: false }; + } + + const coords = [x1, y1, x2, y2]; + const valid = coords.every(coord => + typeof coord === 'number' && + !Number.isNaN(coord) && + Number.isFinite(coord) + ); + + return { x1, y1, x2, y2, valid }; +}; + +// 라인 방향 분석 함수 (중복 방지용 - 이미 있다면 제거) +const analyzeLineOrientation = (x1, y1, x2, y2, epsilon = 0.5) => { + const dx = x2 - x1; + const dy = y2 - y1; + const absDx = Math.abs(dx); + const absDy = Math.abs(dy); + const length = Math.sqrt(dx * dx + dy * dy); + + let orientation; + if (absDy < epsilon && absDx >= epsilon) { + orientation = 'horizontal'; + } else if (absDx < epsilon && absDy >= epsilon) { + orientation = 'vertical'; + } else { + orientation = 'diagonal'; + } + + return { + orientation, + dx, dy, absDx, absDy, length, + midX: (x1 + x2) / 2, + midY: (y1 + y2) / 2, + isHorizontal: orientation === 'horizontal', + isVertical: orientation === 'vertical' + }; +}; + +function extendLineToBoundary(p1, p2, roofLines) { + // 1. Calculate line direction and length + const dx = p2.x - p1.x; + const dy = p2.y - p1.y; + const length = Math.sqrt(dx * dx + dy * dy); + if (length === 0) return { p1: { ...p1 }, p2: { ...p2 } }; + + // 2. Get all polygon points + const points = []; + const seen = new Set(); + + for (const line of roofLines) { + const p1 = { x: line.x1, y: line.y1 }; + const p2 = { x: line.x2, y: line.y2 }; + + const key1 = `${p1.x},${p1.y}`; + const key2 = `${p2.x},${p2.y}`; + + if (!seen.has(key1)) { + points.push(p1); + seen.add(key1); + } + if (!seen.has(key2)) { + points.push(p2); + seen.add(key2); + } + } + + // 3. Find the bounding box + let minX = Infinity, minY = Infinity; + let maxX = -Infinity, maxY = -Infinity; + + for (const p of points) { + minX = Math.min(minX, p.x); + minY = Math.min(minY, p.y); + maxX = Math.max(maxX, p.x); + maxY = Math.max(maxY, p.y); + } + + // 4. Extend line to bounding box + const bboxLines = [ + { x1: minX, y1: minY, x2: maxX, y2: minY }, // top + { x1: maxX, y1: minY, x2: maxX, y2: maxY }, // right + { x1: maxX, y1: maxY, x2: minX, y2: maxY }, // bottom + { x1: minX, y1: maxY, x2: minX, y2: minY } // left + ]; + + const intersections = []; + + // 5. Find intersections with bounding box + for (const line of bboxLines) { + const intersect = getLineIntersection( + p1, p2, + { x: line.x1, y: line.y1 }, + { x: line.x2, y: line.y2 } + ); + + if (intersect) { + const t = ((intersect.x - p1.x) * dx + (intersect.y - p1.y) * dy) / (length * length); + if (t >= 0 && t <= 1) { + intersections.push({ x: intersect.x, y: intersect.y, t }); + } + } + } + + // 6. If we have two intersections, use them + if (intersections.length >= 2) { + // Sort by t value + intersections.sort((a, b) => a.t - b.t); + return { + p1: { x: intersections[0].x, y: intersections[0].y }, + p2: { + x: intersections[intersections.length - 1].x, + y: intersections[intersections.length - 1].y + } + }; + } + + // 7. Fallback to original points + return { p1: { ...p1 }, p2: { ...p2 } }; +} + +/** + * 점에서 특정 방향으로 경계선과의 교차점을 찾습니다. + * @param {Object} point - 시작점 {x, y} + * @param {Object} direction - 방향 벡터 {x, y} (정규화된 값) + * @param {Array} roofLines - 지붕 경계선 배열 + * @returns {Object|null} 교차점 {x, y} 또는 null + */ +function findBoundaryIntersection(point, direction, roofLines) { + let closestIntersection = null; + let minDistance = Infinity; + + // 충분히 긴 거리로 광선 생성 (임의로 큰 값 사용) + const rayLength = 10000; + const rayEnd = { + x: point.x + direction.x * rayLength, + y: point.y + direction.y * rayLength + }; + + // 모든 경계선과의 교차점 확인 + for (const line of roofLines) { + const lineP1 = { x: line.x1, y: line.y1 }; + const lineP2 = { x: line.x2, y: line.y2 }; + + const intersection = getLineIntersection(point, rayEnd, lineP1, lineP2); + + if (intersection) { + // 교차점까지의 거리 계산 + const distance = Math.sqrt( + Math.pow(intersection.x - point.x, 2) + + Math.pow(intersection.y - point.y, 2) + ); + + // 가장 가까운 교차점 저장 (거리가 0보다 큰 경우만) + if (distance > 0.01 && distance < minDistance) { + minDistance = distance; + closestIntersection = intersection; + } + } + } + + return closestIntersection; +} + +/** + * 점이 다른 스켈레톤 라인과의 교점인지 확인합니다. + * @param {Object} point - 확인할 점 {x, y} + * @param {Array} skeletonLines - 모든 스켈레톤 라인 배열 + * @param {Object} currentLine - 현재 라인 {p1, p2} (자기 자신 제외용) + * @param {number} tolerance - 허용 오차 + * @returns {boolean} 교점이면 true + */ +function hasIntersectionWithOtherLines(point, skeletonLines, currentLine, tolerance = 0.5) { + if (!skeletonLines || skeletonLines.length === 0) { + return false; + } + + let connectionCount = 0; + + for (const line of skeletonLines) { + // 자기 자신과의 비교는 제외 + if (line.p1 && line.p2 && currentLine.p1 && currentLine.p2) { + const isSameLineCheck = + (isSamePoint(line.p1, currentLine.p1, tolerance) && isSamePoint(line.p2, currentLine.p2, tolerance)) || + (isSamePoint(line.p1, currentLine.p2, tolerance) && isSamePoint(line.p2, currentLine.p1, tolerance)); + + if (isSameLineCheck) continue; + } + + // 다른 라인의 끝점이 현재 점과 일치하는지 확인 + if (line.p1 && isSamePoint(point, line.p1, tolerance)) { + connectionCount++; + } + if (line.p2 && isSamePoint(point, line.p2, tolerance)) { + connectionCount++; + } + } + + // 1개 이상의 다른 라인과 연결되어 있으면 교점으로 간주 + return connectionCount >= 1; } \ No newline at end of file From fecc4e999a39fb4b263a5a9f405964cea917b3ff Mon Sep 17 00:00:00 2001 From: Cha Date: Sun, 2 Nov 2025 00:48:53 +0900 Subject: [PATCH 6/7] =?UTF-8?q?baseLine=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/roofcover/useMovementSetting.js | 35 ++++++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/src/hooks/roofcover/useMovementSetting.js b/src/hooks/roofcover/useMovementSetting.js index 91ca0093..e52003b3 100644 --- a/src/hooks/roofcover/useMovementSetting.js +++ b/src/hooks/roofcover/useMovementSetting.js @@ -263,12 +263,33 @@ export function useMovementSetting(id) { const currentY = Big(getIntersectMousePoint(e).y) let value = '' - if (Math.abs(target.y1 - target.y2) < 0.5) { + let direction = '' + + if (Math.abs(target.y1 - target.y2) < 0.5) { // 수평 라인 value = Big(targetTop).minus(currentY).times(10).round(0) - } else { + + // 방향 감지 + if (value.toNumber() > 0) { + direction = 'up' // 마우스가 라인 위쪽에 있음 (위로 움직임) + } else if (value.toNumber() < 0) { + direction = 'down' // 마우스가 라인 아래쪽에 있음 (아래로 움직임) + } + } else { // 수직 라인 value = Big(targetLeft).minus(currentX).times(10).round(0).neg() + + // 방향 감지 + if (value.toNumber() > 0) { + direction = 'right' // 마우스가 라인 오른쪽에 있음 (오른쪽으로 움직임) + } else if (value.toNumber() < 0) { + direction = 'left' // 마우스가 라인 왼쪽에 있음 (왼쪽으로 움직임) + } } + // 방향 정보를 사용하여 라디오 버튼 상태 업데이트 + + console.log(`방향: ${direction}, 값: ${value.toNumber()}`) + + currentCalculatedValue = value.toNumber() if (typeRef.current === TYPE.FLOW_LINE) { @@ -286,6 +307,8 @@ export function useMovementSetting(id) { const midY = Big(target.y1).plus(target.y2).div(2) const wall = canvas.getObjects().find((obj) => obj.id === target.attributes.wallId) + + const result = getSelectLinePosition(wall, target, { testDistance: 5, // 테스트 거리 debug: true // 디버깅 로그 출력 @@ -294,6 +317,7 @@ export function useMovementSetting(id) { //console.log("1111linePosition:::::", result.position); // 'top', 'bottom', 'left', 'right' let linePosition = result.position; +console.log("1111linePosition:::::", direction, linePosition); if (target.y1 === target.y2) { //수평벽 @@ -312,7 +336,9 @@ export function useMovementSetting(id) { setRadioStates(value.s !== 1); } + if(direction === 'up') { + } /* checkPoint = { x: midX.toNumber(), y: midY.plus(10).toNumber() } if (wall.inPolygon(checkPoint)) { //선택라인이 내부 @@ -376,6 +402,7 @@ export function useMovementSetting(id) { */ } } + } @@ -623,8 +650,8 @@ export function useMovementSetting(id) { debug: true // 디버깅 로그 출력 }); - //console.log("2222linePosition:::::", result.position); - + console.log("2222linePosition:::::", result.position); + console.log("222moveDirect:::::", roof.moveDirect); // 디버깅용 분류 결과 확인 From a3d1704390613ee9bfd133dc558d20576c236689 Mon Sep 17 00:00:00 2001 From: Cha Date: Sun, 2 Nov 2025 00:51:49 +0900 Subject: [PATCH 7/7] =?UTF-8?q?baseLine=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/roofcover/useMovementSetting.js | 8 ++++---- src/util/skeleton-utils.js | 21 +++++++++++++++++---- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/hooks/roofcover/useMovementSetting.js b/src/hooks/roofcover/useMovementSetting.js index e52003b3..cb5c0f02 100644 --- a/src/hooks/roofcover/useMovementSetting.js +++ b/src/hooks/roofcover/useMovementSetting.js @@ -287,7 +287,7 @@ export function useMovementSetting(id) { // 방향 정보를 사용하여 라디오 버튼 상태 업데이트 - console.log(`방향: ${direction}, 값: ${value.toNumber()}`) + //console.log(`방향: ${direction}, 값: ${value.toNumber()}`) currentCalculatedValue = value.toNumber() @@ -317,7 +317,7 @@ export function useMovementSetting(id) { //console.log("1111linePosition:::::", result.position); // 'top', 'bottom', 'left', 'right' let linePosition = result.position; -console.log("1111linePosition:::::", direction, linePosition); +//console.log("1111linePosition:::::", direction, linePosition); if (target.y1 === target.y2) { //수평벽 @@ -650,8 +650,8 @@ console.log("1111linePosition:::::", direction, linePosition); debug: true // 디버깅 로그 출력 }); - console.log("2222linePosition:::::", result.position); - console.log("222moveDirect:::::", roof.moveDirect); + //console.log("2222linePosition:::::", result.position); + //console.log("222moveDirect:::::", roof.moveDirect); // 디버깅용 분류 결과 확인 diff --git a/src/util/skeleton-utils.js b/src/util/skeleton-utils.js index 0571569b..6294faaf 100644 --- a/src/util/skeleton-utils.js +++ b/src/util/skeleton-utils.js @@ -1989,6 +1989,23 @@ export const getSelectLinePosition = (wall, selectLine, options = {}) => { const { x1, y1, x2, y2 } = lineCoords; + console.log('wall.points', wall.baseLines); + for(const line of wall.baseLines) { + console.log('line', line); + const basePoint = extractLineCoords(line); + const { x1: bx1, y1: by1, x2: bx2, y2: by2 } = basePoint; + console.log('x1, y1, x2, y2', bx1, by1, bx2, by2); + + // 객체 비교 대신 좌표값 비교 + if (Math.abs(bx1 - x1) < 0.1 && + Math.abs(by1 - y1) < 0.1 && + Math.abs(bx2 - x2) < 0.1 && + Math.abs(by2 - y2) < 0.1) { + console.log('basePoint 일치!!!', basePoint); + } + } + + // 라인 방향 분석 const lineInfo = analyzeLineOrientation(x1, y1, x2, y2, epsilon); @@ -2086,10 +2103,6 @@ export const getSelectLinePosition = (wall, selectLine, options = {}) => { // 점이 다각형 내부에 있는지 확인하는 함수 const checkPointInPolygon = (point, wall) => { - // 1. wall의 inPolygon 메서드가 있으면 사용 - if (typeof wall.inPolygon === 'function') { - return wall.inPolygon(point); - } // 2. wall.baseLines를 이용한 Ray Casting Algorithm if (!wall.baseLines || !Array.isArray(wall.baseLines)) {