diff --git a/.gitignore b/.gitignore
index f3b61bd7..a235b0ff 100644
--- a/.gitignore
+++ b/.gitignore
@@ -42,4 +42,5 @@ next-env.d.ts
yarn.lock
package-lock.json
pnpm-lock.yaml
-certificates
\ No newline at end of file
+certificates
+.ai
\ No newline at end of file
diff --git a/package.json b/package.json
index 7be9b3f3..676d8f4f 100644
--- a/package.json
+++ b/package.json
@@ -22,7 +22,7 @@
"chart.js": "^4.4.6",
"dayjs": "^1.11.13",
"env-cmd": "^10.1.0",
- "fabric": "^5.3.0",
+ "fabric": "^5.5.2",
"framer-motion": "^11.2.13",
"fs": "^0.0.1-security",
"iron-session": "^8.0.2",
diff --git a/src/components/fabric/QPolygon.js b/src/components/fabric/QPolygon.js
index 8186b449..3ca095a8 100644
--- a/src/components/fabric/QPolygon.js
+++ b/src/components/fabric/QPolygon.js
@@ -336,8 +336,8 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
if (types.every((type) => type === LINE_TYPE.WALLLINE.EAVES)) {
// 용마루 -- straight-skeleton
console.log('용마루 지붕')
- drawRidgeRoof(this.id, this.canvas, textMode)
- //drawSkeletonRidgeRoof(this.id, this.canvas, textMode);
+ //drawRidgeRoof(this.id, this.canvas, textMode)
+ drawSkeletonRidgeRoof(this.id, this.canvas, textMode);
} else if (isGableRoof(types)) {
// A형, B형 박공 지붕
console.log('패턴 지붕')
diff --git a/src/components/simulator/Simulator.jsx b/src/components/simulator/Simulator.jsx
index 9831d1b3..d873049f 100644
--- a/src/components/simulator/Simulator.jsx
+++ b/src/components/simulator/Simulator.jsx
@@ -264,7 +264,6 @@ export default function Simulator() {
style={{ width: '30%' }}
className="select-light"
value={pwrGnrSimType}
- defaultValue={`D`}
onChange={(e) => {
handleChartChangeData(e.target.value)
setPwrGnrSimType(e.target.value)
@@ -334,33 +333,31 @@ export default function Simulator() {
- {moduleInfoList.length > 0 ? (
- moduleInfoList.map((moduleInfo) => {
- return (
- <>
-
- {/* 지붕면 */}
- | {moduleInfo.roofSurface} |
- {/* 경사각 */}
-
- {convertNumberToPriceDecimal(moduleInfo.slopeAngle)}
- {moduleInfo.classType == 0 ? '寸' : 'º'}
- |
- {/* 방위각(도) */}
- {convertNumberToPriceDecimal(moduleInfo.azimuth)} |
- {/* 태양전지모듈 */}
-
- {moduleInfo.itemNo}
- |
- {/* 매수 */}
- {convertNumberToPriceDecimal(moduleInfo.amount)} |
-
- >
- )
- })
- ) : (
-
- | {getMessage('common.message.no.data')} |
+ {moduleInfoList.length > 0 ? (
+ moduleInfoList.map((moduleInfo) => {
+ return (
+
+ {/* 지붕면 */}
+ | {moduleInfo.roofSurface} |
+ {/* 경사각 */}
+
+ {convertNumberToPriceDecimal(moduleInfo.slopeAngle)}
+ {moduleInfo.classType == 0 ? '寸' : 'º'}
+ |
+ {/* 방위각(도) */}
+ {convertNumberToPriceDecimal(moduleInfo.azimuth)} |
+ {/* 태양전지모듈 */}
+
+ {moduleInfo.itemNo}
+ |
+ {/* 매수 */}
+ {convertNumberToPriceDecimal(moduleInfo.amount)} |
+
+ )
+ })
+ ) : (
+
+ | {getMessage('common.message.no.data')} |
)}
@@ -385,25 +382,23 @@ export default function Simulator() {
- {pcsInfoList.length > 0 ? (
- pcsInfoList.map((pcsInfo) => {
- return (
- <>
-
- {/* 파워컨디셔너 */}
- |
- {pcsInfo.itemNo}
- |
- {/* 대 */}
- {convertNumberToPriceDecimal(pcsInfo.amount)} |
-
- >
- )
- })
- ) : (
-
- | {getMessage('common.message.no.data')} |
-
+ {pcsInfoList.length > 0 ? (
+ pcsInfoList.map((pcsInfo) => {
+ return (
+
+ {/* 파워컨디셔너 */}
+ |
+ {pcsInfo.itemNo}
+ |
+ {/* 대 */}
+ {convertNumberToPriceDecimal(pcsInfo.amount)} |
+
+ )
+ })
+ ) : (
+
+ | {getMessage('common.message.no.data')} |
+
)}
diff --git a/src/hooks/roofcover/useRoofAllocationSetting.js b/src/hooks/roofcover/useRoofAllocationSetting.js
index 6165a0de..4bc26098 100644
--- a/src/hooks/roofcover/useRoofAllocationSetting.js
+++ b/src/hooks/roofcover/useRoofAllocationSetting.js
@@ -30,6 +30,8 @@ import { QcastContext } from '@/app/QcastProvider'
import { usePlan } from '@/hooks/usePlan'
import { roofsState } from '@/store/roofAtom'
import { useText } from '@/hooks/useText'
+import { processEaveHelpLines } from '@/util/skeleton-utils'
+import { QLine } from '@/components/fabric/QLine'
export function useRoofAllocationSetting(id) {
const canvas = useRecoilValue(canvasState)
@@ -456,6 +458,22 @@ export function useRoofAllocationSetting(id) {
const wallLines = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.WALL)
roofBases.forEach((roofBase) => {
try {
+
+ const roofEaveHelpLines = canvas.getObjects().filter((obj) => obj.lineName === 'eaveHelpLine' && obj.roofId === roofBase.id)
+ if (roofEaveHelpLines.length > 0) {
+ if (roofBase.lines) {
+ // Filter out any eaveHelpLines that are already in lines to avoid duplicates
+ const existingEaveLineIds = new Set(roofBase.lines.map((line) => line.id))
+ const newEaveLines = roofEaveHelpLines.filter((line) => !existingEaveLineIds.has(line.id))
+ roofBase.lines = [...newEaveLines]
+ } else {
+ roofBase.lines = [...roofEaveHelpLines]
+ }
+ if (!roofBase.innerLines) {
+ roofBase.innerLines = []
+ }
+ }
+
if (roofBase.separatePolygon.length > 0) {
splitPolygonWithSeparate(roofBase.separatePolygon)
} else {
diff --git a/src/hooks/usePolygon.js b/src/hooks/usePolygon.js
index 2ae37440..5e21e902 100644
--- a/src/hooks/usePolygon.js
+++ b/src/hooks/usePolygon.js
@@ -845,6 +845,8 @@ export const usePolygon = () => {
polygonLines.forEach((line) => {
line.need = true
})
+ // 순서에 의존하지 않도록 모든 조합을 먼저 확인한 후 처리
+ const innerLineMapping = new Map() // innerLine -> polygonLine 매핑 저장
// innerLines와 polygonLines의 겹침을 확인하고 type 변경
innerLines.forEach((innerLine) => {
@@ -854,14 +856,28 @@ export const usePolygon = () => {
if (innerLine.attributes && polygonLine.attributes.type) {
// innerLine이 polygonLine보다 긴 경우 polygonLine.need를 false로 변경
if (polygonLine.length < innerLine.length) {
- polygonLine.need = false
+ if(polygonLine.lineName !== 'eaveHelpLine'){
+ polygonLine.need = false
+ }
}
- innerLine.attributes.planeSize = innerLine.attributes.planeSize ?? polygonLine.attributes.planeSize
- innerLine.attributes.actualSize = innerLine.attributes.actualSize ?? polygonLine.attributes.actualSize
- innerLine.attributes.type = polygonLine.attributes.type
- innerLine.direction = polygonLine.direction
- innerLine.attributes.isStart = true
- innerLine.parentLine = polygonLine
+ // innerLine.attributes.planeSize = innerLine.attributes.planeSize ?? polygonLine.attributes.planeSize
+ // innerLine.attributes.actualSize = innerLine.attributes.actualSize ?? polygonLine.attributes.actualSize
+ // innerLine.attributes.type = polygonLine.attributes.type
+ // innerLine.direction = polygonLine.direction
+ // innerLine.attributes.isStart = true
+ // innerLine.parentLine = polygonLine
+
+
+ // 매핑된 innerLine의 attributes를 변경 (교차점 계산 전에 적용)
+ innerLineMapping.forEach((polygonLine, innerLine) => {
+ innerLine.attributes.planeSize = innerLine.attributes.planeSize ?? polygonLine.attributes.planeSize
+ innerLine.attributes.actualSize = innerLine.attributes.actualSize ?? polygonLine.attributes.actualSize
+ innerLine.attributes.type = polygonLine.attributes.type
+ innerLine.direction = polygonLine.direction
+ innerLine.attributes.isStart = true
+ innerLine.parentLine = polygonLine
+ })
+
}
}
})
@@ -1371,7 +1387,7 @@ export const usePolygon = () => {
let representLine
// 지붕을 그리면서 기존 polygon의 line중 연결된 line을 찾는다.
- ;[...polygonLines, ...innerLines].forEach((line) => {
+ [...polygonLines, ...innerLines].forEach((line) => {
let startFlag = false
let endFlag = false
const startPoint = line.startPoint
@@ -1567,52 +1583,126 @@ export const usePolygon = () => {
// ==== Dijkstra pathfinding ====
+ // function findShortestPath(start, end, graph, epsilon = 1) {
+ // const startKey = pointToKey(start, epsilon)
+ // const endKey = pointToKey(end, epsilon)
+ //
+ // const distances = {}
+ // const previous = {}
+ // const visited = new Set()
+ // const queue = [{ key: startKey, dist: 0 }]
+ //
+ // for (const key in graph) distances[key] = Infinity
+ // distances[startKey] = 0
+ //
+ // while (queue.length > 0) {
+ // queue.sort((a, b) => a.dist - b.dist)
+ // const { key } = queue.shift()
+ // if (visited.has(key)) continue
+ // visited.add(key)
+ //
+ // for (const neighbor of graph[key] || []) {
+ // const neighborKey = pointToKey(neighbor.point, epsilon)
+ // const alt = distances[key] + neighbor.distance
+ // if (alt < distances[neighborKey]) {
+ // distances[neighborKey] = alt
+ // previous[neighborKey] = key
+ // queue.push({ key: neighborKey, dist: alt })
+ // }
+ // }
+ // }
+ //
+ // const path = []
+ // let currentKey = endKey
+ //
+ // if (!previous[currentKey]) return null
+ //
+ // while (currentKey !== startKey) {
+ // const [x, y] = currentKey.split(',').map(Number)
+ // path.unshift({ x, y })
+ // currentKey = previous[currentKey]
+ // }
+ //
+ // const [sx, sy] = startKey.split(',').map(Number)
+ // path.unshift({ x: sx, y: sy })
+ //
+ // return path
+ // }
+
function findShortestPath(start, end, graph, epsilon = 1) {
- const startKey = pointToKey(start, epsilon)
- const endKey = pointToKey(end, epsilon)
+ const startKey = pointToKey(start, epsilon);
+ const endKey = pointToKey(end, epsilon);
- const distances = {}
- const previous = {}
- const visited = new Set()
- const queue = [{ key: startKey, dist: 0 }]
+ // 거리와 이전 노드 추적
+ const distances = { [startKey]: 0 };
+ const previous = {};
+ const visited = new Set();
- for (const key in graph) distances[key] = Infinity
- distances[startKey] = 0
+ // 우선순위 큐 (거리가 짧은 순으로 정렬)
+ const queue = [{ key: startKey, dist: 0 }];
- while (queue.length > 0) {
- queue.sort((a, b) => a.dist - b.dist)
- const { key } = queue.shift()
- if (visited.has(key)) continue
- visited.add(key)
+ // 모든 노드 초기화
+ for (const key in graph) {
+ if (key !== startKey) {
+ distances[key] = Infinity;
+ }
+ }
- for (const neighbor of graph[key] || []) {
- const neighborKey = pointToKey(neighbor.point, epsilon)
- const alt = distances[key] + neighbor.distance
- if (alt < distances[neighborKey]) {
- distances[neighborKey] = alt
- previous[neighborKey] = key
- queue.push({ key: neighborKey, dist: alt })
+ // 우선순위 큐에서 다음 노드 선택
+ const getNextNode = () => {
+ if (queue.length === 0) return null;
+ queue.sort((a, b) => a.dist - b.dist);
+ return queue.shift();
+ };
+
+ let current;
+ while ((current = getNextNode())) {
+ const currentKey = current.key;
+
+ // 목적지에 도달하면 종료
+ if (currentKey === endKey) break;
+
+ // 이미 방문한 노드는 건너뜀
+ if (visited.has(currentKey)) continue;
+ visited.add(currentKey);
+
+ // 인접 노드 탐색
+ for (const neighbor of graph[currentKey] || []) {
+ const neighborKey = pointToKey(neighbor.point, epsilon);
+ if (visited.has(neighborKey)) continue;
+
+ const alt = distances[currentKey] + neighbor.distance;
+
+ // 더 짧은 경로를 찾은 경우 업데이트
+ if (alt < (distances[neighborKey] || Infinity)) {
+ distances[neighborKey] = alt;
+ previous[neighborKey] = currentKey;
+
+ // 우선순위 큐에 추가
+ queue.push({ key: neighborKey, dist: alt });
}
}
}
- const path = []
- let currentKey = endKey
+ // 경로 재구성
+ const path = [];
+ let currentKey = endKey;
- if (!previous[currentKey]) return null
-
- while (currentKey !== startKey) {
- const [x, y] = currentKey.split(',').map(Number)
- path.unshift({ x, y })
- currentKey = previous[currentKey]
+ // 시작점에 도달할 때까지 역추적
+ while (previous[currentKey] !== undefined) {
+ const [x, y] = currentKey.split(',').map(Number);
+ path.unshift({ x, y });
+ currentKey = previous[currentKey];
}
- const [sx, sy] = startKey.split(',').map(Number)
- path.unshift({ x: sx, y: sy })
+ // 시작점 추가
+ if (path.length > 0) {
+ const [sx, sy] = startKey.split(',').map(Number);
+ path.unshift({ x: sx, y: sy });
+ }
- return path
+ return path.length > 0 ? path : null;
}
-
// 최종 함수
function getPath(start, end, graph, epsilon = 1) {
// startPoint와 arrivalPoint가 될 수 있는 점은 line.attributes.type이 'default' 혹은 null이 아닌 line인 경우에만 가능
diff --git a/src/util/skeleton-utils.js b/src/util/skeleton-utils.js
index 95c5cb7b..cbbc4127 100644
--- a/src/util/skeleton-utils.js
+++ b/src/util/skeleton-utils.js
@@ -4,9 +4,8 @@ import { calcLineActualSize, calcLinePlaneSize, toGeoJSON } from '@/util/qpolygo
import { QLine } from '@/components/fabric/QLine'
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'
+import wallLine from '@/components/floor-plan/modal/wallLineOffset/type/WallLine'
/**
* 지붕 폴리곤의 스켈레톤(중심선)을 생성하고 캔버스에 그립니다.
@@ -15,7 +14,7 @@ import { point } from '@turf/turf'
* @param {string} textMode - 텍스트 표시 모드
* @param pitch
*/
-
+const EPSILON = 0.1
export const drawSkeletonRidgeRoof = (roofId, canvas, textMode) => {
@@ -185,127 +184,127 @@ const movingLineFromSkeleton = (roofId, canvas) => {
let newEndPoint = {...originalEndPoint};
// 위치와 방향에 따라 좌표 조정
-/*
- 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;
- }
-*/
+ /*
+ 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;
+ }
+ */
// 원본 라인 업데이트
- // newPoints 배열에서 일치하는 포인트들을 찾아서 업데이트
+ // 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();
- }
+ newPoints.forEach((point, index) => {
+ if(position === 'bottom'){
+ if (moveDirection === 'in') {
+ if(isSamePoint(roof.basePoints[index], originalStartPoint) || isSamePoint(roof.basePoints[index], originalEndPoint)) {
+ point.y = Big(point.y).minus(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();
- }
+ // if (isSamePoint(roof.basePoints[index], originalEndPoint)) {
+ // point.y = Big(point.y).minus(absMove).toNumber();
+ // }
+ }else if (moveDirection === 'out'){
+ if(isSamePoint(roof.basePoints[index], originalStartPoint) || isSamePoint(roof.basePoints[index], originalEndPoint)) {
+ point.y = Big(point.y).plus(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();
- }
- }
-
+ // 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) || isSamePoint(roof.basePoints[index], originalEndPoint)) {
+ 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) || isSamePoint(roof.basePoints[index], originalEndPoint)) {
+ 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();
+ }
+ }
+
+ }
- // 원본 baseLine도 업데이트
- line.startPoint = newStartPoint;
- line.endPoint = newEndPoint;
});
- return newPoints;
+
+ // 원본 baseLine도 업데이트
+ line.startPoint = newStartPoint;
+ line.endPoint = newEndPoint;
+ });
+ return newPoints;
}
}
@@ -319,10 +318,29 @@ const movingLineFromSkeleton = (roofId, canvas) => {
* @param baseLines
*/
export const skeletonBuilder = (roofId, canvas, textMode) => {
-
//처마
let roof = canvas?.getObjects().find((object) => object.id === roofId)
+ // [추가] wall 객체를 찾아 roof.lines에 wallId를 직접 주입 (초기화)
+ // 지붕은 벽을 기반으로 생성되므로 라인의 순서(Index)가 동일합니다.
+ const wallObj = canvas.getObjects().find((object) => object.name === POLYGON_TYPE.WALL && object.attributes.roofId === roofId)
+
+ if (roof && wallObj && roof.lines && wallObj.lines) {
+ // 개선된 코드 (기하학적 매칭)
+ // or use some other unique properties
+
+
+ roof.lines.forEach((rLine, index) => {
+ // 벽 라인 중에서 시작점과 끝점이 일치하는 라인 찾기
+ const wLine = wallObj.lines[index]
+ if (wLine) {
+ // 안정적인 ID 생성
+ rLine.attributes.wallLine = wLine.id; // Use the stable ID
+
+ // ...
+ }
+ })
+ }
const eavesType = [LINE_TYPE.WALLLINE.EAVES, LINE_TYPE.WALLLINE.HIPANDGABLE]
const gableType = [LINE_TYPE.WALLLINE.GABLE, LINE_TYPE.WALLLINE.JERKINHEAD]
@@ -331,40 +349,35 @@ export const skeletonBuilder = (roofId, canvas, textMode) => {
const wall = canvas.getObjects().find((object) => object.name === POLYGON_TYPE.WALL && object.attributes.roofId === roofId)
//const baseLines = wall.baseLines.filter((line) => line.attributes.planeSize > 0)
- const baseLines = canvas.getObjects().filter((object) => object.name === 'baseLine' && object.parentId === roofId) || [];
- const baseLinePoints = baseLines.map((line) => ({x:line.left, y:line.top}));
+ const baseLines = canvas.getObjects().filter((object) => object.name === 'baseLine' && object.parentId === roofId) || []
+ const baseLinePoints = baseLines.map((line) => ({ x: line.left, y: line.top }))
- const outerLines = canvas.getObjects().filter((object) => object.name === 'outerLinePoint') || [];
- const outerLinePoints = outerLines.map((line) => ({x:line.left, y:line.top}))
+ const outerLines = canvas.getObjects().filter((object) => object.name === 'outerLinePoint') || []
+ const outerLinePoints = outerLines.map((line) => ({ x: line.left, y: line.top }))
- const hipLines = canvas.getObjects().filter((object) => object.name === 'hip' && object.parentId === roofId) || [];
- const ridgeLines = canvas.getObjects().filter((object) => object.name === 'ridge' && object.parentId === roofId) || [];
+ const hipLines = canvas.getObjects().filter((object) => object.name === 'hip' && object.parentId === roofId) || []
+ const ridgeLines = canvas.getObjects().filter((object) => object.name === 'ridge' && object.parentId === roofId) || []
//const skeletonLines = [];
// 1. 지붕 폴리곤 좌표 전처리
- const coordinates = preprocessPolygonCoordinates(roof.points);
+ const coordinates = preprocessPolygonCoordinates(roof.points)
if (coordinates.length < 3) {
- console.warn("Polygon has less than 3 unique points. Cannot generate skeleton.");
- return;
+ console.warn('Polygon has less than 3 unique points. Cannot generate skeleton.')
+ return
}
- const moveFlowLine = roof.moveFlowLine || 0; // Provide a default value
- const moveUpDown = roof.moveUpDown || 0; // Provide a default value
-
-
-
- let points = roof.points;
+ const moveFlowLine = roof.moveFlowLine || 0 // Provide a default value
+ const moveUpDown = roof.moveUpDown || 0 // Provide a default value
+ let points = roof.points
//마루이동
if (moveFlowLine !== 0 || moveUpDown !== 0) {
-
points = movingLineFromSkeleton(roofId, canvas)
}
-
- console.log('points:', points);
+ console.log('points:', points)
const geoJSONPolygon = toGeoJSON(points)
try {
@@ -373,7 +386,7 @@ export const skeletonBuilder = (roofId, canvas, textMode) => {
const skeleton = SkeletonBuilder.BuildFromGeoJSON([[geoJSONPolygon]])
// 스켈레톤 데이터를 기반으로 내부선 생성
- roof.innerLines = roof.innerLines || [];
+ roof.innerLines = roof.innerLines || []
roof.innerLines = createInnerLinesFromSkeleton(roofId, canvas, skeleton, textMode)
// 캔버스에 스켈레톤 상태 저장
@@ -382,12 +395,12 @@ export const skeletonBuilder = (roofId, canvas, textMode) => {
canvas.skeletonLines = []
}
canvas.skeletonStates[roofId] = true
- canvas.skeletonLines = [];
+ canvas.skeletonLines = []
canvas.skeletonLines.push(...roof.innerLines)
- roof.skeletonLines = canvas.skeletonLines;
+ roof.skeletonLines = canvas.skeletonLines
const cleanSkeleton = {
- Edges: skeleton.Edges.map(edge => ({
+ Edges: skeleton.Edges.map((edge) => ({
X1: edge.Edge.Begin.X,
Y1: edge.Edge.Begin.Y,
X2: edge.Edge.End.X,
@@ -398,13 +411,14 @@ export const skeletonBuilder = (roofId, canvas, textMode) => {
})),
roofId: roofId,
// Add other necessary top-level properties
- };
- canvas.skeleton = [];
+ }
+ canvas.skeleton = []
canvas.skeleton = cleanSkeleton
canvas.skeleton.lastPoints = points
- canvas.set("skeleton", cleanSkeleton);
+ canvas.set('skeleton', cleanSkeleton)
canvas.renderAll()
- console.log('skeleton rendered.', canvas);
+
+ console.log('skeleton rendered.', canvas)
} catch (e) {
console.error('스켈레톤 생성 중 오류 발생:', e)
if (canvas.skeletonStates) {
@@ -427,9 +441,61 @@ 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)
+ let wall = canvas.getObjects().find((obj) => obj.name === POLYGON_TYPE.WALL && obj.attributes.roofId === roofId)
let skeletonLines = []
+ let findPoints = [];
+
const processedInnerEdges = new Set()
+ const textElements = {};
+
+ const coordinateText = (line) => {
+ // Generate a stable ID for this line
+ const lineKey = `${line.x1},${line.y1},${line.x2},${line.y2}`;
+
+ // Remove existing text elements for this line
+ if (textElements[lineKey]) {
+ textElements[lineKey].forEach(text => {
+ if (canvas.getObjects().includes(text)) {
+ canvas.remove(text);
+ }
+ });
+ }
+
+ // Create start point text
+ const startText = new fabric.Text(`(${Math.round(line.x1)}, ${Math.round(line.y1)})`, {
+ left: line.x1 + 5,
+ top: line.y1 - 20,
+ fontSize: 10,
+ fill: 'magenta',
+ fontFamily: 'Arial',
+ selectable: false,
+ hasControls: false,
+ hasBorders: false
+ });
+
+ // Create end point text
+ const endText = new fabric.Text(`(${Math.round(line.x2)}, ${Math.round(line.y2)})`, {
+ left: line.x2 + 5,
+ top: line.y2 - 20,
+ fontSize: 10,
+ fill: 'orange',
+ fontFamily: 'Arial',
+ selectable: false,
+ hasControls: false,
+ hasBorders: false
+ });
+
+ // Add to canvas
+ canvas.add(startText, endText);
+
+ // Store references
+ textElements[lineKey] = [startText, endText];
+
+ // Bring lines to front
+ canvas.bringToFront(startText);
+ canvas.bringToFront(endText);
+ };
// 1. 모든 Edge를 순회하며 기본 스켈레톤 선(용마루)을 수집합니다.
skeleton.Edges.forEach((edgeResult, index) => {
@@ -470,41 +536,46 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
}
});
-/*
- //2. 연결이 끊어진 스켈레톤 선을 찾아 연장합니다.
- const { disconnectedLines } = findDisconnectedSkeletonLines(skeletonLines, roof.lines);
+ /*
+ //2. 연결이 끊어진 스켈레톤 선을 찾아 연장합니다.
+ const { disconnectedLines } = findDisconnectedSkeletonLines(skeletonLines, roof.lines);
- if(disconnectedLines.length > 0) {
+ if(disconnectedLines.length > 0) {
- disconnectedLines.forEach(dLine => {
- const { index, extendedLine, p1Connected, p2Connected } = dLine;
- const newPoint = extendedLine?.point;
- if (!newPoint) return;
- // p1이 끊어졌으면 p1을, p2가 끊어졌으면 p2를 연장된 지점으로 업데이트
- if (p1Connected) { //p2 연장
- skeletonLines[index].p2 = { ...skeletonLines[index].p2, x: newPoint.x, y: newPoint.y };
- } else if (p2Connected) {//p1 연장
- skeletonLines[index].p1 = { ...skeletonLines[index].p1, x: newPoint.x, y: newPoint.y };
- }
- });
+ disconnectedLines.forEach(dLine => {
+ const { index, extendedLine, p1Connected, p2Connected } = dLine;
+ const newPoint = extendedLine?.point;
+ if (!newPoint) return;
+ // p1이 끊어졌으면 p1을, p2가 끊어졌으면 p2를 연장된 지점으로 업데이트
+ if (p1Connected) { //p2 연장
+ skeletonLines[index].p2 = { ...skeletonLines[index].p2, x: newPoint.x, y: newPoint.y };
+ } else if (p2Connected) {//p1 연장
+ skeletonLines[index].p1 = { ...skeletonLines[index].p1, x: newPoint.x, y: newPoint.y };
+ }
+ });
- //2-1 확장된 스켈레톤 선이 연장되다가 서로 만나면 만난점(접점)에서 멈추어야 된다.
- trimIntersectingExtendedLines(skeletonLines, disconnectedLines);
+ //2-1 확장된 스켈레톤 선이 연장되다가 서로 만나면 만난점(접점)에서 멈추어야 된다.
+ trimIntersectingExtendedLines(skeletonLines, disconnectedLines);
- }
-*/
+ }
+ */
//2. 연결이 끊어진 라인이 있을경우 찾아서 추가한다(동 이동일때)
// 3. 최종적으로 정리된 스켈레톤 선들을 QLine 객체로 변환하여 캔버스에 추가합니다.
const innerLines = [];
+ const addLines = []
const existingLines = new Set(); // 이미 추가된 라인을 추적하기 위한 Set
+ //처마라인
+ const roofLines = roof.lines
+ //벽라인
+ const wallLines = wall.lines
- skeletonLines.forEach(line => {
- const { p1, p2, attributes, lineStyle } = line;
+ skeletonLines.forEach((sktLine, skIndex) => {
+ let { p1, p2, attributes, lineStyle } = sktLine;
- // 라인을 고유하게 식별할 수 있는 키 생성 (정규화된 좌표로 정렬하여 비교)
+ // 중복방지 - 라인을 고유하게 식별할 수 있는 키 생성 (정규화된 좌표로 정렬하여 비교)
const lineKey = [
[p1.x, p1.y].sort().join(','),
[p2.x, p2.y].sort().join(',')
@@ -516,181 +587,919 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
}
const direction = getLineDirection(
- { x: line.p1.x, y: line.p1.y },
- { x: line.p2.x, y: line.p2.y }
+ { x: sktLine.p1.x, y: sktLine.p1.y },
+ { x: sktLine.p2.x, y: sktLine.p2.y }
);
- const innerLine = new QLine([p1.x, p1.y, p2.x, p2.y], {
+ //그림을 그릴때 idx 가 필요함 roof는 왼쪽부터 시작됨 - 그림그리는 순서가 필요함
+
+
+ // roofLines.forEach((roofLine) => {
+ //
+ // if (isSameLine(p1.x, p1.y, p2.x, p2.y, roofLine) || isSameLine(p2.x, p2.y, p1.x, p1.y, roofLine)) {
+ // roofIdx = roofLine.idx;
+ // console.log("roofIdx::::::", roofIdx)
+ // return false; // forEach 중단
+ // }
+ // });
+
+
+ const skeletonLine = new QLine([p1.x, p1.y, p2.x, p2.y], {
parentId: roof.id,
fontSize: roof.fontSize,
- stroke: lineStyle.color,
+ stroke: (sktLine.attributes.isOuterEdge)?'orange':lineStyle.color,
strokeWidth: lineStyle.width,
- name: (line.attributes.isOuterEdge)?'eaves': attributes.type,
- attributes: attributes,
+ name: (sktLine.attributes.isOuterEdge)?'eaves': attributes.type,
+ attributes: {
+ ...attributes,
+
+ },
direction: direction,
- isBaseLine: line.attributes.isOuterEdge,
- lineName: (line.attributes.isOuterEdge)?'outerLine': attributes.type,
- selectable:(!line.attributes.isOuterEdge),
- roofId: roofId,
+ isBaseLine: sktLine.attributes.isOuterEdge,
+ lineName: (sktLine.attributes.isOuterEdge)?'roofLine': attributes.type,
+ selectable:(!sktLine.attributes.isOuterEdge),
+ //visible: (!sktLine.attributes.isOuterEdge),
});
+ //coordinateText(skeletonLine)
+ canvas.add(skeletonLine);
+ skeletonLine.bringToFront();
+ existingLines.add(lineKey); // 추가된 라인을 추적
+
+
+
//skeleton 라인에서 처마선은 삭제
- if(innerLine.lineName !== 'outerLine'){
- canvas.add(innerLine);
- innerLine.bringToFront();
- existingLines.add(lineKey); // 추가된 라인을 추적
+ if(skeletonLine.lineName === 'roofLine'){
+
+ skeletonLine.set('visible', false); //임시
+ roof.set({
+ //stroke: 'black',
+ strokeWidth: 4
+ });
+
+
+
+
+
}else{
- const wall = canvas.getObjects().find((object) => object.name === POLYGON_TYPE.WALL && object.attributes.roofId === roofId)
- const wallLines = wall.baseLines
- // 현재 지점과 다음 지점을 비교하기 위한 변수
- let changedLine = roof.moveSelectLine;
- const roofLines = [];
-
-
- if (!wall.lines || !wall.baseLines) {
- return wall.baseLines || wall.lines || [];
- }
-
- // 길이가 다른 경우 baseLines 반환
- if (wall.lines.length !== wall.baseLines.length) {
- return wall.baseLines;
- }
-
- for (let i = 0; i < wall.baseLines.length; i++) {
- const baseLine = wall.baseLines[i];
- const line = wall.lines[i];
-
- if (!line ||
- ((!isSamePoint(baseLine.startPoint, line.startPoint)) && // 시작점이 다르고
- (!isSamePoint(baseLine.endPoint, line.endPoint)))) { // 끝점도 다른 경우
-
- }
- }
-
-
- const startClosest = findClosestRoofLine(p1, roof.lines);
- const endClosest = findClosestRoofLine(p2, roof.lines);
-
-
- const { point, closest, selectPoint, otherPoint } =
- startClosest.distance > endClosest.distance
- ? {
- point : p2,
- closest : endClosest,
- otherPoint: p1
- }
- : {
- point : p1,
- closest : startClosest,
- otherPoint: p2
- };
-
-// Log the relevant information
- console.log("Point:", point);
- console.log("Closest intersection:", closest);
- console.log("moveSelectLinePoint:", selectPoint);
- let isTarget = false;
- for(const roofLine of roof.lines){
- if( isSamePoint(p1, roofLine.startPoint) ||
- isSamePoint(p2, roofLine.endPoint) ||
- isSamePoint(p1, roofLine.endPoint) ||
- isSamePoint(p2, roofLine.startPoint) ) {
- isTarget = true ;
- break
- }
- }
- if (isTarget) {
- console.warn("matching line found in roof.lines");
- return; // 또는 적절한 오류 처리
- }
-
- const innerLine2 = new QLine([p1.x, p1.y, p2.x, p2.y], {
- parentId: roof.id,
- fontSize: roof.fontSize,
- stroke: 'red',
- strokeWidth: lineStyle.width,
- name: (line.attributes.isOuterEdge)?'eaves': attributes.type,
- attributes: attributes,
- direction: direction,
- isBaseLine: line.attributes.isOuterEdge,
- lineName: (line.attributes.isOuterEdge)?'addLine': attributes.type,
- selectable:(!line.attributes.isOuterEdge),
- roofId: roofId,
- });
-
- canvas.add(innerLine2);
- //existingLines.add(lineKey); // 추가된 라인을 추적
- /*
- //라인추가(까지 못할때때) 외벽선에서 추가
- const wall = canvas.getObjects().find((object) => object.name === POLYGON_TYPE.WALL && object.attributes.roofId === roofId)
- const wallLines = wall.baseLines
- // 현재 지점과 다음 지점을 비교하기 위한 변수
- let changedLine = roof.moveSelectLine;
- const roofLines = [];
-
-
- if (!wall.lines || !wall.baseLines) {
- return wall.baseLines || wall.lines || [];
- }
-
- // 길이가 다른 경우 baseLines 반환
- if (wall.lines.length !== wall.baseLines.length) {
- return wall.baseLines;
- }
-
-
- //그려지는 처마라인이 처마 && 포인터모두가 wall.baseLine에 들어가 있는 경우
- const checkPoint = {x1:line.x1, y1:line.y1, x2:line.x2, y2:line.y2}
- if(line.attributes.type === 'hip' && !checkPointInPolygon(checkPoint, wall)) {
- const startClosest = findClosestRoofLine(p1, roof.lines);
- const endClosest = findClosestRoofLine(p2, roof.lines);
- console.log("Lindd::::",line)
- const { point, closest, selectPoint, otherPoint } =
- startClosest.distance > endClosest.distance
- ? {
- point : p2,
- closest : endClosest,
- //selectPoint : changedLine.endPoint,
- otherPoint: p1
- }
- : {
- point : p1,
- closest : startClosest,
- //selectPoint : changedLine.startPoint,
- otherPoint: p2
- };
-
-// Log the relevant information
- console.log("Point:", point);
- console.log("Closest intersection:", closest);
- console.log("moveSelectLinePoint:", selectPoint);
- }
-*/
-
- const coordinateText = new fabric.Text(`(${Math.round(p1.x)}, ${Math.round(p1.y)})`, {
- left: p1.x + 5, // 좌표점에서 약간 오른쪽으로 이동
- top: p1.y - 20, // 좌표점에서 약간 위로 이동
- fontSize: 13,
- fill: 'red',
- fontFamily: 'Arial',
- selectable: true,
- lockMovementX: false,
- lockMovementY: false,
- lockRotation: true,
- lockScalingX: true,
- lockScalingY: true,
- name: 'lengthText'
- })
-
- canvas?.add(coordinateText)
}
- innerLines.push(innerLine)
+
+ innerLines.push(skeletonLine)
canvas.renderAll();
});
+ if((roof.moveUpDown??0 > 0) ) {
+
+ // 같은 라인이 없으므로 새 다각형 라인 생성
+ //라인 편집
+ // let i = 0
+ const currentRoofLines = canvas.getObjects().filter((obj) => obj.lineName === 'roofLine' && obj.attributes.roofId === roofId)
+ let roofLineRects = canvas.getObjects().filter((obj) => obj.name === 'roofLineRect' && obj.roofId === roofId)
+
+
+ roofLineRects.forEach((roofLineRect) => {
+ canvas.remove(roofLineRect)
+ canvas.renderAll()
+ })
+
+ let helpLines = canvas.getObjects().filter((obj) => obj.lineName === 'helpLine' && obj.roofId === roofId)
+ helpLines.forEach((helpLine) => {
+ canvas.remove(helpLine)
+ canvas.renderAll()
+ })
+
+ function sortCurrentRoofLines(lines) {
+ return [...lines].sort((a, b) => {
+ // Get all coordinates in a consistent order
+ const getCoords = (line) => {
+ const x1 = line.x1 ?? line.get('x1');
+ const y1 = line.y1 ?? line.get('y1');
+ const x2 = line.x2 ?? line.get('x2');
+ const y2 = line.y2 ?? line.get('y2');
+
+ // Sort points left-to-right, then top-to-bottom
+ return x1 < x2 || (x1 === x2 && y1 < y2)
+ ? [x1, y1, x2, y2]
+ : [x2, y2, x1, y1];
+ };
+
+ const aCoords = getCoords(a);
+ const bCoords = getCoords(b);
+
+ // Compare each coordinate in order
+ for (let i = 0; i < 4; i++) {
+ if (Math.abs(aCoords[i] - bCoords[i]) > 0.1) {
+ return aCoords[i] - bCoords[i];
+ }
+ }
+ return 0;
+ });
+ }
+
+
+ // function sortCurrentRoofLines(lines) {
+ // return [...lines].sort((a, b) => {
+ // const aX = a.x1 ?? a.get('x1')
+ // const aY = a.y1 ?? a.get('y1')
+ // const bX = b.x1 ?? b.get('x1')
+ // const bY = b.y1 ?? b.get('y1')
+
+ // if (aX !== bX) return aX - bX
+ // return aY - bY
+ // })
+ // }
+
+
+ // 각 라인 집합 정렬
+
+ // roofLines의 방향에 맞춰 currentRoofLines의 방향을 조정
+ const alignLineDirection = (sourceLines, targetLines) => {
+ return sourceLines.map(sourceLine => {
+ // 가장 가까운 targetLine 찾기
+ const nearestTarget = targetLines.reduce((nearest, targetLine) => {
+ const sourceCenter = {
+ x: (sourceLine.x1 + sourceLine.x2) / 2,
+ y: (sourceLine.y1 + sourceLine.y2) / 2
+ };
+ const targetCenter = {
+ x: (targetLine.x1 + targetLine.x2) / 2,
+ y: (targetLine.y1 + targetLine.y2) / 2
+ };
+ const distance = Math.hypot(
+ sourceCenter.x - targetCenter.x,
+ sourceCenter.y - targetCenter.y
+ );
+
+ return !nearest || distance < nearest.distance
+ ? { line: targetLine, distance }
+ : nearest;
+ }, null)?.line;
+
+ if (!nearestTarget) return sourceLine;
+
+ // 방향이 반대인지 확인 (벡터 내적을 사용)
+ const sourceVec = {
+ x: sourceLine.x2 - sourceLine.x1,
+ y: sourceLine.y2 - sourceLine.y1
+ };
+ const targetVec = {
+ x: nearestTarget.x2 - nearestTarget.x1,
+ y: nearestTarget.y2 - nearestTarget.y1
+ };
+
+ const dotProduct = sourceVec.x * targetVec.x + sourceVec.y * targetVec.y;
+
+ // 내적이 음수이면 방향이 반대이므로 뒤집기
+ if (dotProduct < 0) {
+ return {
+ ...sourceLine,
+ x1: sourceLine.x2,
+ y1: sourceLine.y2,
+ x2: sourceLine.x1,
+ y2: sourceLine.y1
+ };
+ }
+
+ return sourceLine;
+ });
+ };
+
+ // const sortedWallLines = sortCurrentRoofLines(wall.lines);
+ // roofLines의 방향에 맞춰 currentRoofLines 조정 후 정렬
+ const alignedCurrentRoofLines = alignLineDirection(currentRoofLines, roofLines);
+ const sortedCurrentRoofLines = sortCurrentRoofLines(alignedCurrentRoofLines);
+ // const sortedRoofLines = sortCurrentRoofLines(roofLines);
+ const sortedWallBaseLines = sortCurrentRoofLines(wall.baseLines);
+ // const sortedBaseLines = sortBaseLinesByWallLines(wall.baseLines, wallLines);
+ const sortRoofLines = sortBaseLinesByWallLines(roofLines, wallLines);
+
+ // 원본 wallLines를 복사하여 사용
+ const sortedWallLines = [...wallLines];
+ const sortedBaseLines = sortBaseLinesByWallLines(wall.baseLines, sortedWallLines);
+ const sortedRoofLines = sortBaseLinesByWallLines(roofLines, sortedWallLines);
+
+ //wall.lines 는 기본 벽 라인
+ //wall.baseLine은 움직인라인
+ const movedLines = []
+
+
+ wallLines.forEach((wallLine, index) => {
+
+
+ // const roofLine = sortedRoofLines[index];
+ // const currentRoofLine = sortedCurrentRoofLines[index];
+ // const moveLine = sortedWallBaseLines[index]
+ // const wallBaseLine = sortedWallBaseLines[index]
+
+ const roofLine = sortRoofLines[index];
+ if(roofLine.attributes.wallLine !== wallLine.id || (roofLine.idx - 1) !== index ){
+ console.log("wallLine2::::", wallLine.id)
+ console.log('roofLine:::',roofLine.attributes.wallLine)
+ console.log("w:::",wallLine.startPoint, wallLine.endPoint)
+ console.log("R:::",roofLine.startPoint, roofLine.endPoint)
+ console.log("not matching roofLine", roofLine);
+ return false
+ }//roofLines.find(line => line.attributes.wallLineId === wallLine.attributes.wallId);
+
+ const currentRoofLine = currentRoofLines[index];
+ const moveLine = sortedBaseLines[index]
+ const wallBaseLine = sortedBaseLines[index]
+ //console.log("wallBaseLine", wallBaseLine);
+
+ //roofline 외곽선 설정
+
+
+ // Check if wallBaseLine is inside the polygon formed by sortedWallLines
+
+ /*
+ console.log('=== Line Coordinates ===');
+ console.table({
+ 'Point' : ['X', 'Y'],
+ 'roofLine' : [roofLine.x1, roofLine.y1],
+ 'currentRoofLine': [currentRoofLine.x1, currentRoofLine.y1],
+ 'moveLine' : [moveLine.x1, moveLine.y1],
+ 'wallBaseLine' : [wallBaseLine.x1, wallBaseLine.y1]
+ });
+ console.log('End Points:');
+ console.table({
+ 'Point' : ['X', 'Y'],
+ 'roofLine' : [roofLine.x2, roofLine.y2],
+ 'currentRoofLine': [currentRoofLine.x2, currentRoofLine.y2],
+ 'moveLine' : [moveLine.x2, moveLine.y2],
+ 'wallBaseLine' : [wallBaseLine.x2, wallBaseLine.y2]
+ });
+ */
+ const origin = moveLine.attributes?.originPoint
+ if (!origin) return
+
+ if (isSamePoint(moveLine, wallLine)) {
+
+ return false
+ }
+
+ const movedStart = Math.abs(moveLine.x1 - wallLine.x1) > EPSILON || Math.abs(moveLine.y1 - origin.y1) > EPSILON
+ const movedEnd = Math.abs(moveLine.x2 - wallLine.x2) > EPSILON || Math.abs(moveLine.y2 - origin.y2) > EPSILON
+
+
+ const fullyMoved = movedStart && movedEnd
+
+
+//반시계 방향
+ let newPStart //= {x:roofLine.x1, y:roofLine.y1}
+ let newPEnd //= {x:movedLines.x2, y:movedLines.y2}
+
+//현재 roof는 무조건 시계방향
+
+ const getAddLine = (p1, p2, stroke = '') => {
+ movedLines.push({ index, p1, p2 })
+
+// Usage:
+ // let mergeLines = mergeMovedLines(movedLines);
+ //console.log("mergeLines:::::::", mergeLines);
+ const line = new QLine([p1.x, p1.y, p2.x, p2.y], {
+ parentId : roof.id,
+ fontSize : roof.fontSize,
+ stroke : '#3FBAE6',
+ strokeWidth: 2,
+ name : 'eaveHelpLine',
+ lineName : 'eaveHelpLine',
+ selectable : true,
+ visible : true,
+ roofId : roofId,
+ attributes : {
+ type: 'eaveHelpLine',
+ isStart : true,
+ pitch: wallLine.attributes.pitch,
+ }
+ });
+ //coordinateText(line)
+ canvas.add(line)
+ canvas.renderAll();
+ return line
+ }
+
+ //getAddLine(roofLine.startPoint, roofLine.endPoint, ) //외곽선을 그린다
+
+ newPStart = { x: roofLine.x1, y: roofLine.y1 }
+ newPEnd = { x: roofLine.x2, y: roofLine.y2 }
+
+ const getInnerLines = (lines, point) => {
+
+ }
+ let isIn = false
+ let isOut = false
+
+//두 포인트가 변경된 라인인
+ if (fullyMoved ) {
+ //반시계방향향
+
+ const mLine = getSelectLinePosition(wall, wallBaseLine)
+
+ if (getOrientation(roofLine) === 'vertical') {
+
+ if (['left', 'right'].includes(mLine.position)) {
+ if(Math.abs(wallLine.x1 - wallBaseLine.x1) < 0.1 || Math.abs(wallLine.x2 - wallBaseLine.x2) < 0.1) {
+ return false
+ }
+ const positionType =
+ (mLine.position === 'left' && wallLine.x1 < wallBaseLine.x1) ||
+ (mLine.position === 'right' && wallLine.x1 > wallBaseLine.x1)
+ ? 'in' : 'out';
+ const condition = `${mLine.position}_${positionType}`;
+ let isStartEnd = findInteriorPoint(wallBaseLine, sortedBaseLines)
+ let sPoint, ePoint;
+ if(condition === 'left_in') {
+ isIn = true
+
+ if (isStartEnd.start ) {
+ newPEnd.y = roofLine.y2;
+ newPEnd.x = roofLine.x2;
+
+ const moveDist = Big(wallBaseLine.x1).minus(wallLine.x1).abs().toNumber()
+ ePoint = {x: wallBaseLine.x1, y: wallBaseLine.y1};
+ newPStart.y = wallBaseLine.y1
+
+ findPoints.push({ x: ePoint.x, y: ePoint.y, position: 'left_in_start' });
+ const newPointX = Big(roofLine.x1).plus(moveDist).abs().toNumber()
+ const pDist = Big(wallLine.x1).minus(roofLine.x1).abs().toNumber()
+ const pLineY = Big(roofLine.y1).minus(0).abs().toNumber()
+ let idx = (0 > index - 1)?roofLines.length:index
+ const pLineX = roofLines[idx-1].x1
+
+ getAddLine({ x: newPStart.x, y: newPStart.y }, { x: ePoint.x, y: ePoint.y }, 'blue')
+ getAddLine({ x: roofLine.x2, y: roofLine.y2 }, { x: newPointX, y: roofLine.y2 }, 'orange')
+
+ if(Math.abs(wallBaseLine.y1 - wallLine.y1) < 0.1) {
+ getAddLine({ x: pLineX, y: pLineY }, { x: newPointX, y: pLineY }, 'green')
+ getAddLine({ x: newPointX, y: pLineY }, { x: ePoint.x, y: ePoint.y }, 'pink')
+ }
+ }
+
+ if(isStartEnd.end) {
+ newPStart.y = roofLine.y1;
+ newPStart.x = roofLine.x1;
+
+ const moveDist = Big(wallBaseLine.x2).minus(wallLine.x2).abs().toNumber()
+ ePoint = {x: wallBaseLine.x2, y: wallBaseLine.y2};
+ newPEnd.y = wallBaseLine.y2
+
+ findPoints.push({ x: ePoint.x, y: ePoint.y, position: 'left_in_end' });
+ const newPointX = Big(roofLine.x1).plus(moveDist).toNumber()
+ const pDist = Big(wallLine.x1).minus(roofLine.x1).abs().toNumber()
+ const pLineY = Big(roofLine.y2).minus(0).abs().toNumber()
+ let idx = (roofLines.length < index + 1)?0:index
+ const pLineX = roofLines[idx+1].x2
+
+ getAddLine({ x: newPEnd.x, y: newPEnd.y }, { x: ePoint.x, y: ePoint.y }, 'blue')
+ getAddLine({ x: roofLine.x1, y: roofLine.y1 }, { x: newPointX, y: roofLine.y1 }, 'orange')
+
+ if(Math.abs(wallBaseLine.y2 - wallLine.y2) < 0.1) {
+ getAddLine({ x: pLineX, y: pLineY }, { x: newPointX, y: pLineY }, 'green')
+ getAddLine({ x: newPointX, y: pLineY }, { x: ePoint.x, y: ePoint.y }, 'pink')
+ }
+ //getAddLine({ x: roofLine.x2, y: roofLine.y2 }, { x: newPointX, y: roofLine.y2 }, 'orange')
+ }
+
+ }else if(condition === 'left_out') {
+ console.log("left_out::::isStartEnd:::::", isStartEnd);
+ if(isStartEnd.start){
+
+ const moveDist = Big(wallLine.x1).minus(wallBaseLine.x1).abs().toNumber()
+ const aStartY = Big(roofLine.y1).minus(moveDist).abs().toNumber()
+ const bStartY = Big(wallLine.y1).minus(moveDist).abs().toNumber()
+ const inLine = findLineContainingPoint(innerLines, { y: aStartY, x: roofLine.x2 })
+
+ const eLineY = Big(bStartY).minus(wallLine.y1).abs().toNumber()
+ newPStart.y = aStartY
+ newPEnd.y = Big(roofLine.y2).minus(eLineY).toNumber()
+ let idx = (0 >= index - 1)?roofLines.length:index
+ const newLine = roofLines[idx-1];
+
+ if(Math.abs(wallBaseLine.y1 - wallLine.y1) < 0.1) {
+ if(inLine){
+ if(inLine.x1 < inLine.x2) {
+ getAddLine({ y: bStartY, x: wallLine.x2 }, { y: inLine.y2, x: inLine.x2 }, 'pink')
+ }else{
+ getAddLine({ y: inLine.y2, x: inLine.x2 },{ y: bStartY, x: wallLine.x2 }, 'pink')
+ }
+
+ }
+ getAddLine({ y: bStartY, x: wallLine.x2 }, { y: roofLine.y1, x: wallLine.x1 }, 'magenta')
+ getAddLine({ y: newLine.y1, x: newLine.x1 }, { y: newLine.y2, x: wallLine.x2 }, 'Gray')
+ findPoints.push({ y: aStartY, x: newPStart.x, position: 'left_out_start' });
+ }else{
+ const cLineY = Big(wallBaseLine.x1).minus(wallLine.x1).abs().toNumber()
+ newPStart.y = Big(newPStart.y).minus(cLineY).toNumber();
+ const inLine = findLineContainingPoint(innerLines, { y: newPStart.y, x: newPStart.x })
+ if(inLine){
+ if(inLine.x1 < inLine.x2) {
+ getAddLine({ y: newPStart.y, x: newPStart.x }, { y: inLine.y2, x: inLine.x2 }, 'purple')
+ }else{
+ getAddLine({ y: inLine.y1, x: inLine.x1},{ y: newPStart.y, x: newPStart.x }, 'purple')
+ }
+ }else {
+ newPStart.y = wallLine.y1;
+ }
+
+ }
+ }
+
+
+ if(isStartEnd.end){
+ const moveDist = Big(wallLine.x1).minus(wallBaseLine.x1).abs().toNumber()
+ const aStartY = Big(roofLine.y2).plus(moveDist).toNumber()
+ const bStartY = Big(wallLine.y2).plus(moveDist).toNumber()
+ const inLine = findLineContainingPoint(innerLines, { y: aStartY, x: roofLine.x1 })
+ console.log("startLines:::::::", inLine);
+ const eLineY = Big(bStartY).minus(wallLine.y2).abs().toNumber()
+ newPEnd.y = aStartY
+ newPStart.y = Big(roofLine.y1).plus(eLineY).toNumber()
+ let idx = (roofLines.length < index + 1)?0:index
+ const newLine = roofLines[idx+1];
+
+ if(Math.abs(wallBaseLine.y2 - wallLine.y2) < 0.1) {
+ if(inLine){
+ if(inLine.x1 < inLine.x2) {
+ getAddLine({ y: bStartY, x: wallLine.x1 }, { y: inLine.y2, x: inLine.x2 }, 'pink')
+ }else{
+ getAddLine({ y: inLine.y1, x: inLine.x1 }, { y: bStartY, x: wallLine.x1 }, 'pink')
+ }
+ }
+ getAddLine({ y: bStartY, x: wallLine.x1 }, { y: roofLine.y2, x: wallLine.x2 }, 'magenta')
+ getAddLine({ y: newLine.y2, x: newLine.x2 }, { y: newLine.y1, x: wallLine.x1 }, 'Gray')
+ findPoints.push({ y: aStartY, x: newPEnd.x, position: 'left_out_end' });
+ }else{
+ const cLineY = Big(wallBaseLine.x2).minus(wallLine.x2).abs().toNumber()
+ newPEnd.y = Big(newPEnd.y).plus(cLineY).toNumber();
+ const inLine = findLineContainingPoint(innerLines, { y: newPEnd.y, x: newPEnd.x })
+ if(inLine){
+ if(inLine.x1 < inLine.x2) {
+ getAddLine({ y: newPEnd.y, x: newPEnd.x }, { y: inLine.y2, x: inLine.x2 }, 'purple')
+ }else{
+ getAddLine({ y: inLine.y1, x: inLine.x1 },{ y: newPEnd.y, x: newPEnd.x }, 'purple')
+ }
+ }else {
+
+ newPEnd.y = wallLine.y2
+ }
+
+ }
+ findPoints.push({ y: newPStart.y, x: newPEnd.x, position: 'left_out_end' });
+ }
+ }else if(condition === 'right_in') {
+ if (isStartEnd.start ) {
+
+ newPEnd.y = roofLine.y2;
+ newPEnd.x = roofLine.x2;
+
+ const moveDist = Big(wallBaseLine.x1).minus(wallLine.x1).abs().toNumber()
+ ePoint = {x: wallBaseLine.x1, y: wallBaseLine.y1};
+ newPStart.y = wallBaseLine.y1
+
+ findPoints.push({ x: ePoint.x, y: ePoint.y, position: 'right_in_start'});
+ const newPointX = Big(roofLine.x1).minus(moveDist).abs().toNumber()
+ const pDist = Big(wallLine.x1).minus(roofLine.x1).abs().toNumber()
+ const pLineY = Big(roofLine.y1).minus(0).abs().toNumber()
+ let idx = (0 >= index - 1)?roofLines.length:index
+ const pLineX = roofLines[idx-1].x1
+
+ getAddLine({ x: newPStart.x, y: newPStart.y }, { x: ePoint.x, y: ePoint.y }, 'blue')
+ //getAddLine({ x: roofLine.x2, y: roofLine.y2 }, { x: newPointX, y: roofLine.y2 }, 'orange')
+
+ if(Math.abs(wallBaseLine.y1 - wallLine.y1) < 0.1) {
+ getAddLine({ x: pLineX, y: pLineY }, { x: newPointX, y: pLineY }, 'green')
+ getAddLine({ x: newPointX, y: pLineY }, { x: ePoint.x, y: ePoint.y }, 'pink')
+ }
+ }
+
+ if(isStartEnd.end) {
+ newPStart.y = roofLine.y1;
+ newPStart.x = roofLine.x1;
+
+ const moveDist = Big(wallBaseLine.x2).minus(wallLine.x2).abs().toNumber()
+ ePoint = {x: wallBaseLine.x2, y: wallBaseLine.y2};
+ newPEnd.y = wallBaseLine.y2
+
+ findPoints.push({ x: ePoint.x, y: ePoint.y, position: 'right_in_end' });
+ const newPointX = Big(roofLine.x1).minus(moveDist).toNumber()
+ const pDist = Big(wallLine.x1).minus(roofLine.x1).abs().toNumber()
+ const pLineY = Big(roofLine.y2).minus(0).abs().toNumber()
+ let idx = (roofLines.length < index + 1)?0:index
+ const pLineX = roofLines[idx+1].x2
+
+ getAddLine({ x: newPEnd.x, y: newPEnd.y }, { x: ePoint.x, y: ePoint.y }, 'blue')
+ getAddLine({ x: roofLine.x1, y: roofLine.y1 }, { x: newPointX, y: roofLine.y1 }, 'orange')
+
+ if(Math.abs(wallBaseLine.y2 - wallLine.y2) < 0.1) {
+ getAddLine({ x: pLineX, y: pLineY }, { x: newPointX, y: pLineY }, 'green')
+ getAddLine({ x: newPointX, y: pLineY }, { x: ePoint.x, y: ePoint.y }, 'pink')
+ }
+ getAddLine({ x: roofLine.x2, y: roofLine.y2 }, { x: newPointX, y: roofLine.y2 }, 'orange')
+ }
+
+ }else if(condition === 'right_out') {
+ console.log("right_out::::isStartEnd:::::", isStartEnd);
+ if (isStartEnd.start ) { //x1 inside
+ const moveDist = Big(wallLine.x1).minus(wallBaseLine.x1).abs().toNumber()
+ const aStartY = Big(roofLine.y1).plus(moveDist).abs().toNumber()
+ const bStartY = Big(wallLine.y1).plus(moveDist).abs().toNumber()
+ const inLine = findLineContainingPoint(innerLines, { y: aStartY, x: roofLine.x1 })
+ console.log("startLines:::::::", inLine);
+ const eLineY = Big(bStartY).minus(wallLine.y1).abs().toNumber()
+ newPStart.y = aStartY
+ newPEnd.y = Big(roofLine.y2).plus(eLineY).toNumber()
+ let idx = (0 >= index - 1)?roofLines.length:index
+ const newLine = roofLines[idx-1];
+
+ if(Math.abs(wallBaseLine.y1 - wallLine.y1) < 0.1) {
+ if(inLine){
+ if(inLine.x2 < inLine.x1) {
+ getAddLine({ y: bStartY, x: wallLine.x2 }, { y: inLine.y2, x: inLine.x2 }, 'pink')
+ }else{
+ getAddLine({ y: inLine.y1, x: inLine.x1 },{ y: bStartY, x: wallLine.x2 }, 'pink')
+ }
+ }
+ getAddLine({ y: bStartY, x: wallLine.x2 }, { y: roofLine.y1, x: wallLine.x1 }, 'magenta')
+ getAddLine({ y: newLine.y1, x: newLine.x1 }, { y: newLine.y2, x: wallLine.x2 }, 'Gray')
+ findPoints.push({ y: aStartY, x: newPEnd.x, position: 'right_out_start' });
+ }else{
+ const cLineY = Big(wallBaseLine.x1).minus(wallLine.x1).abs().toNumber()
+ newPStart.y = Big(newPStart.y).plus(cLineY).toNumber();
+ const inLine = findLineContainingPoint(innerLines, { y: newPStart.y, x: newPStart.x })
+ if(inLine){
+ if(inLine.x2 < inLine.x1 ) {
+ getAddLine({ y: newPStart.y, x: newPStart.x }, { y: inLine.y2, x: inLine.x2 }, 'purple')
+ }else{
+ getAddLine({ y: inLine.y1, x: inLine.x1 },{ y: newPStart.y, x: newPStart.x }, 'purple')
+ }
+ }else {
+ newPStart.y = wallLine.y1;
+ }
+
+ }
+
+ }
+
+ if(isStartEnd.end){
+ const moveDist = Big(wallLine.x1).minus(wallBaseLine.x1).abs().toNumber()
+ const aStartY = Big(roofLine.y2).minus(moveDist).abs().toNumber()
+ const bStartY = Big(wallLine.y2).minus(moveDist).abs().toNumber()
+ const inLine = findLineContainingPoint(innerLines, { y: aStartY, x: roofLine.x1 })
+ console.log("startLines:::::::", inLine);
+ const eLineY = Big(bStartY).minus(wallLine.y2).abs().toNumber()
+ newPEnd.y = aStartY
+ newPStart.y = Big(roofLine.y1).minus(eLineY).toNumber()
+ let idx = (roofLines.length < index + 1)?0:index
+ const newLine = roofLines[idx+1];
+ if(inLine){
+ if(inLine.x2 < inLine.x1) {
+ getAddLine({ y: bStartY, x: wallLine.x1 }, { y: inLine.y2, x: inLine.x2 }, 'pink')
+ }else{
+ getAddLine({ y: inLine.y1, x: inLine.x1 },{ y: bStartY, x: wallLine.x1 }, 'pink')
+ }
+ }
+ if(Math.abs(wallBaseLine.y2 - wallLine.y2) < 0.1) {
+ getAddLine({ y: bStartY, x: wallLine.x1 }, { y: roofLine.y2, x: wallLine.x2 }, 'magenta')
+ getAddLine({ y: newLine.y2, x: newLine.x2 }, { y: newLine.y1, x: wallLine.x1 }, 'Gray')
+ findPoints.push({ y: aStartY, x: newPEnd.x, position: 'right_out_end' });
+ }else{
+ const cLineY = Big(wallBaseLine.x2).minus(wallLine.x2).abs().toNumber()
+ newPEnd.y = Big(newPEnd.y).minus(cLineY).toNumber();
+ const inLine = findLineContainingPoint(innerLines, { y: newPEnd.y, x: newPEnd.x })
+ if(inLine){
+ if(inLine.x2 < inLine.x1) {
+ getAddLine({ y: newPEnd.y, x: newPEnd.x }, { y: inLine.y2, x: inLine.x2 }, 'purple')
+ }else{
+ getAddLine({ y: inLine.y1, x: inLine.x1 }, { y: newPEnd.y, x: newPEnd.x }, 'purple')
+ }
+ }else {
+ newPEnd.y = wallLine.y2;
+ }
+
+ }
+ }
+ }
+
+ // switch (condition) {
+ // case 'left_in':
+ // break;
+ // case 'left_out':
+ // break;
+ // case 'right_in':
+ // break;
+ // case 'right_out':
+ // break;
+ // }
+ }
+
+
+ } else if (getOrientation(roofLine) === 'horizontal') { //red
+
+ if (['top', 'bottom'].includes(mLine.position)) {
+ if(Math.abs(wallLine.y1 - wallBaseLine.y1) < 0.1 || Math.abs(wallLine.y2 - wallBaseLine.y2) < 0.1) {
+ return false
+ }
+ const positionType =
+ (mLine.position === 'top' && wallLine.y1 < wallBaseLine.y1) ||
+ (mLine.position === 'bottom' && wallLine.y1 > wallBaseLine.y1)
+ ? 'in' : 'out';
+
+ const condition = `${mLine.position}_${positionType}`;
+ let isStartEnd = findInteriorPoint(wallBaseLine, sortedBaseLines)
+
+ let sPoint, ePoint;
+
+ if(condition === 'top_in') {
+ if (isStartEnd.start ) {
+ const moveDist = Big(wallLine.y1).minus(wallBaseLine.y1).abs().toNumber()
+ sPoint = {x: wallBaseLine.x1, y: wallBaseLine.y1};
+ newPStart.x = wallBaseLine.x1;
+
+
+ const newPointY = Big(roofLine.y2).plus(moveDist).toNumber()
+
+ const pDist = Big(wallLine.y2).minus(roofLine.y2).abs().toNumber()
+ const pLineX = Big(roofLine.x1).minus(0).abs().toNumber()
+ let idx = (0 >= index - 1)?roofLines.length:index
+ const pLineY = roofLines[idx-1].y1
+ getAddLine({ x: newPStart.x, y: newPStart.y }, { x: sPoint.x, y: sPoint.y }, 'blue')
+ findPoints.push({ x: sPoint.x, y: sPoint.y, position: 'top_in_start' });
+
+ if(Math.abs(wallBaseLine.x1 - wallLine.x1) < 0.1) {
+ getAddLine({ x: pLineX, y: pLineY }, { x: pLineX, y: newPointY }, 'green')
+ getAddLine({ x: pLineX, y: newPointY }, { x: sPoint.x, y: sPoint.y }, 'pink')
+ }
+ //getAddLine({ x: roofLine.x2, y: roofLine.y2 }, { x: roofLine.x2, y: newPointY }, 'orange')
+
+ }
+
+ if(isStartEnd.end){
+ const moveDist = Big(wallLine.y2).minus(wallBaseLine.y2).abs().toNumber()
+ sPoint = { x: wallBaseLine.x2, y: wallBaseLine.y2 }
+ newPEnd.x = wallBaseLine.x2
+
+ const newPointY = Big(roofLine.y1).plus(moveDist).toNumber()
+
+ const pDist = Big(wallLine.y1).minus(roofLine.y1).abs().toNumber()
+ const pLineX = Big(roofLine.x2).minus(0).abs().toNumber()
+ let idx = roofLines.length < index + 1 ? 0 : index
+ const pLineY = roofLines[idx + 1].y2
+ getAddLine({ x: newPEnd.x, y: newPEnd.y }, { x: sPoint.x, y: sPoint.y }, 'blue')
+ findPoints.push({ x: sPoint.x, y: sPoint.y, position: 'top_in_end' });
+
+ if (Math.abs(wallBaseLine.x2 - wallLine.x2) < 0.1) {
+ getAddLine({ x: pLineX, y: pLineY }, { x: pLineX, y: newPointY }, 'green')
+ getAddLine({ x: pLineX, y: newPointY }, { x: sPoint.x, y: sPoint.y }, 'pink')
+ }
+ //getAddLine({ x: roofLine.x1, y: roofLine.y1 }, { x: roofLine.x1, y: newPointY }, 'orange')
+ }
+
+ }else if(condition === 'top_out') {
+ console.log("top_out isStartEnd:::::::", isStartEnd);
+ if (isStartEnd.start ) {
+ const moveDist = Big(wallLine.y1).minus(wallBaseLine.y1).abs().toNumber()
+ const aStartX = Big(roofLine.x1).plus(moveDist).toNumber()
+ const bStartX = Big(wallLine.x1).plus(moveDist).toNumber()
+ const inLine = findLineContainingPoint(innerLines, { x: aStartX, y: newPEnd.y })
+
+ const eLineX = Big(bStartX).minus(wallLine.x1).abs().toNumber()
+ newPEnd.x = Big(newPEnd.x).plus(eLineX).toNumber()
+ newPStart.x = aStartX
+ let idx = (0 > index - 1)?roofLines.length:index
+ const newLine = roofLines[idx-1];
+
+ if(Math.abs(wallBaseLine.x1 - wallLine.x1) < 0.1) {
+ if(inLine){
+ if(inLine.y2 > inLine.y1 ) {
+ getAddLine({ x: bStartX, y: wallLine.y1 }, { x: inLine.x2, y: inLine.y2 }, 'pink')
+ }else{
+ getAddLine({ x: inLine.x1, y: inLine.y1 }, { x: bStartX, y: wallLine.y1 }, 'pink')
+ }
+ }
+ getAddLine({ x: bStartX, y: wallLine.y1 }, { x: roofLine.x1, y: wallLine.y1 }, 'magenta')
+ getAddLine({ x: newLine.x1, y: newLine.y1 }, { x: newLine.x1, y: wallLine.y1 }, 'Gray')
+ findPoints.push({ x: aStartX, y: newPEnd.y, position: 'top_out_start' });
+ }else{
+ const cLineX = Big(wallBaseLine.y1).minus(wallLine.y1).abs().toNumber()
+ newPStart.x = Big(newPStart.x).plus(cLineX).toNumber();
+ const inLine = findLineContainingPoint(innerLines, { y: newPStart.y, x: newPStart.x })
+ if(inLine){
+ if(inLine.y2 > inLine.y1 ) {
+ getAddLine({ y: newPStart.y, x: newPStart.x }, { y: inLine.y2, x: inLine.x2 }, 'purple')
+ }else{
+ getAddLine({ y: inLine.y1, x: inLine.x1 }, { y: newPStart.y, x: newPStart.x } , 'purple')
+ }
+
+ }else {
+ newPStart.x = wallLine.x1;
+ }
+
+ }
+ }
+ if(isStartEnd.end){
+ const moveDist = Big(wallLine.y1).minus(wallBaseLine.y1).abs().toNumber()
+ const aStartX = Big(roofLine.x2).minus(moveDist).abs().toNumber()
+ const bStartX = Big(wallLine.x2).minus(moveDist).abs().toNumber()
+ const inLine = findLineContainingPoint(innerLines, { x: aStartX, y: newPEnd.y })
+ console.log("startLines:::::::", inLine);
+ const eLineX = Big(bStartX).minus(wallLine.x2).abs().toNumber()
+ newPStart.x = Big(newPStart.x).minus(eLineX).abs().toNumber()
+ newPEnd.x = aStartX
+ let idx = (roofLines.length < index + 1)?0:index
+ const newLine = roofLines[idx+1];
+
+ if(Math.abs(wallBaseLine.x2 - wallLine.x2) < 0.1) {
+ if(inLine){
+ if(inLine.y2 > inLine.y1 ){
+ getAddLine({ x: bStartX, y: wallLine.y1 }, { x: inLine.x2, y: inLine.y2 }, 'pink')
+ }else{
+ getAddLine({ x: inLine.x1, y: inLine.y1 },{ x: bStartX, y: wallLine.y1 }, 'pink')
+ }
+
+ }
+ getAddLine({ x: bStartX, y: wallLine.y1 }, { x: roofLine.x2, y: wallLine.y2 }, 'magenta')
+ getAddLine({ x: newLine.x2, y: newLine.y2 }, { x: newLine.x1, y: wallLine.y1 }, 'Gray')
+ findPoints.push({ x: aStartX, y: newPEnd.y, position: 'top_out_end' });
+ }else{
+ const cLineX = Big(wallLine.y2).minus(wallBaseLine.y2).abs().toNumber()
+ newPEnd.x = Big(newPEnd.x).minus(cLineX).toNumber();
+ const inLine = findLineContainingPoint(innerLines, { y: newPEnd.y, x: newPEnd.x })
+ if(inLine){
+ if(inLine.y2 > inLine.y1 ) {
+ getAddLine({ y: newPEnd.y, x: newPEnd.x }, { y: inLine.y2, x: inLine.x2 }, 'purple')
+ }else{
+ getAddLine({ y: inLine.y1, x: inLine.x1 },{ y: newPEnd.y, x: newPEnd.x }, 'purple')
+ }
+ }else {
+ newPEnd.x = wallLine.x2;
+ }
+
+ }
+ }
+ }else if(condition === 'bottom_in') {
+ if (isStartEnd.start ) {
+ const moveDist = Big(wallLine.y1).minus(wallBaseLine.y1).abs().toNumber()
+ sPoint = {x: wallBaseLine.x1, y: wallBaseLine.y1};
+ newPStart.x = wallBaseLine.x1;
+
+
+ const newPointY = Big(roofLine.y2).minus(moveDist).toNumber()
+
+ const pDist = Big(wallLine.y2).minus(roofLine.y2).abs().toNumber()
+ const pLineX = Big(roofLine.x1).minus(0).abs().toNumber()
+ let idx = (0 > index - 1)?roofLines.length:index
+ const pLineY = roofLines[idx-1].y1
+ getAddLine({ x: newPStart.x, y: newPStart.y }, { x: sPoint.x, y: sPoint.y }, 'blue')
+ findPoints.push({ x: sPoint.x, y: sPoint.y, position: 'bottom_in_start' });
+
+ if(Math.abs(wallBaseLine.x1 - wallLine.x1) < 0.1) {
+ getAddLine({ x: pLineX, y: pLineY }, { x: pLineX, y: newPointY }, 'green')
+ getAddLine({ x: pLineX, y: newPointY }, { x: sPoint.x, y: sPoint.y }, 'pink')
+ }
+ getAddLine({ x: roofLine.x2, y: roofLine.y2 }, { x: roofLine.x2, y: newPointY }, 'orange')
+ }
+
+ if(isStartEnd.end){
+ const moveDist = Big(wallLine.y2).minus(wallBaseLine.y2).abs().toNumber()
+ sPoint = {x: wallBaseLine.x2, y: wallBaseLine.y2};
+ newPEnd.x = wallBaseLine.x2;
+
+
+ const newPointY = Big(roofLine.y1).minus(moveDist).toNumber()
+
+ const pDist = Big(wallLine.y1).minus(roofLine.y1).abs().toNumber()
+ const pLineX = Big(roofLine.x2).minus(0).abs().toNumber()
+ let idx = (roofLines.length < index + 1)?0:index
+ const pLineY = roofLines[idx+1].y2
+ getAddLine({ x: newPEnd.x, y: newPEnd.y }, { x: sPoint.x, y: sPoint.y }, 'blue')
+ findPoints.push({ x: sPoint.x, y: sPoint.y, position: 'bottom_in_end' });
+
+ if(Math.abs(wallBaseLine.x2 - wallLine.x2) < 0.1) {
+ getAddLine({ x: pLineX, y: pLineY }, { x: pLineX, y: newPointY }, 'green')
+ getAddLine({ x: pLineX, y: newPointY }, { x: sPoint.x, y: sPoint.y }, 'pink')
+ }
+ getAddLine({ x: roofLine.x1, y: roofLine.y1 }, { x: roofLine.x1, y: newPointY }, 'orange')
+
+ }
+ }else if(condition === 'bottom_out') {
+ console.log("bottom_out isStartEnd:::::::", isStartEnd);
+ if (isStartEnd.start ) {
+ const moveDist = Big(wallLine.y1).minus(wallBaseLine.y1).abs().toNumber()
+ const aStartX = Big(roofLine.x1).minus(moveDist).abs().toNumber()
+ const bStartX = Big(wallLine.x1).minus(moveDist).abs().toNumber()
+ const inLine = findLineContainingPoint(innerLines, { x: aStartX, y: roofLine.y1 })
+ console.log("startLines:::::::", inLine);
+ const eLineX = Big(bStartX).minus(wallLine.x1).abs().toNumber()
+ newPEnd.x = Big(roofLine.x2).minus(eLineX).toNumber()
+ newPStart.x = aStartX
+ let idx = (0 > index - 1)?roofLines.length:index
+ const newLine = roofLines[idx-1];
+
+
+ if(Math.abs(wallBaseLine.x1 - wallLine.x1) < 0.1) {
+ if(inLine){
+ if(inLine.y2 < inLine.y1 ) {
+ getAddLine({ x: bStartX, y: wallLine.y1 }, { x: inLine.x2, y: inLine.y2 }, 'pink')
+ }else{
+ getAddLine({ x: inLine.x1, y: inLine.y1 },{ x: bStartX, y: wallLine.y1 }, 'pink')
+ }
+ }
+ getAddLine({ x: bStartX, y: wallLine.y1 }, { x: roofLine.x1, y: wallLine.y1 }, 'magenta')
+ getAddLine({ x: newLine.x1, y: newLine.y1 }, { x: newLine.x1, y: wallLine.y1 }, 'Gray')
+ findPoints.push({ x: aStartX, y: newPEnd.y, position: 'bottom_out_start' });
+ }else{
+ const cLineX = Big(wallBaseLine.y1).minus(wallLine.y1).abs().toNumber()
+ newPStart.x = Big(newPStart.x).minus(cLineX).toNumber();
+ const inLine = findLineContainingPoint(innerLines, { y: newPStart.y, x: newPStart.x })
+ if(inLine){
+ if(inLine.y2 < inLine.y1 ) {
+ getAddLine({ y: newPStart.y, x: newPStart.x }, { y: inLine.y2, x: inLine.x2 }, 'purple')
+ }else{
+ getAddLine({ y: inLine.y1, x: inLine.x1 }, { y: newPStart.y, x: newPStart.x }, 'purple')
+ }
+ }else{
+ newPStart.x = wallLine.x1;
+ }
+
+ }
+ }
+
+ if(isStartEnd.end){
+ const moveDist = Big(wallLine.y1).minus(wallBaseLine.y1).abs().toNumber()
+ const aStartX = Big(roofLine.x2).plus(moveDist).toNumber()
+ const bStartX = Big(wallLine.x2).plus(moveDist).toNumber()
+ const inLine = findLineContainingPoint(innerLines, { x: aStartX, y: roofLine.y1 })
+ console.log("startLines:::::::", inLine);
+ const eLineX = Big(bStartX).minus(wallLine.x2).abs().toNumber()
+ newPEnd.x = aStartX
+ newPStart.x = Big(roofLine.x1).plus(eLineX).toNumber()
+ let idx = (roofLines.length < index + 1)?0:index
+ const newLine = roofLines[idx + 1];
+
+ if(Math.abs(wallBaseLine.x2 - wallLine.x2) < 0.1) {
+ if(inLine){
+ if(inLine.y2 < inLine.y1 ) {
+ getAddLine({ x: bStartX, y: wallLine.y1 }, { x: inLine.x2, y: inLine.y2 }, 'pink')
+ }else{
+ getAddLine({ x: inLine.x1, y: inLine.y1 }, { x: bStartX, y: wallLine.y1 }, 'pink')
+ }
+ }
+ getAddLine({ x: bStartX, y: wallLine.y1 }, { x: roofLine.x2, y: wallLine.y2 }, 'magenta')
+ getAddLine({ x: newLine.x2, y: newLine.y2 }, { x: newLine.x1, y: wallLine.y1 }, 'Gray')
+ findPoints.push({ x: aStartX, y: newPEnd.y, position: 'bottom_out_end' });
+ }else{
+ const cLineX = Big(wallBaseLine.y2).minus(wallLine.y2).abs().toNumber()
+ newPEnd.x = Big(newPEnd.x).plus(cLineX).toNumber();
+ const inLine = findLineContainingPoint(innerLines, { y: newPEnd.y, x: newPEnd.x })
+ if(inLine){
+ if(inLine.y2 < inLine.y1 ) {
+ getAddLine({ y: newPEnd.y, x: newPEnd.x }, { y: inLine.y2, x: inLine.x2 }, 'purple')
+ }else{
+ getAddLine({ y: inLine.y1, x: inLine.x1 },{ y: newPEnd.y, x: newPEnd.x }, 'purple')
+ }
+ }else{
+ newPEnd.x = wallLine.x2;
+ }
+
+ }
+ }
+ }
+
+ // switch (condition) {
+ // case 'top_in':
+ // //console.log("findInteriorPoint result:::::::", isStartEnd);
+ // break;
+ // case 'top_out':
+ // //console.log("findInteriorPoint result:::::::", isStartEnd);
+ // break;
+ // case 'bottom_in':
+ // break;
+ // case 'bottom_out':
+ // break;
+ // }
+ }
+ }
+
+ getAddLine(newPStart, newPEnd, 'red')
+ //canvas.remove(roofLine)
+ }else{
+ getAddLine(roofLine.startPoint, roofLine.endPoint, )
+ }
+ canvas.renderAll()
+ });
+ }
+
+ if (findPoints.length > 0) {
+ // 모든 점에 대해 라인 업데이트를 누적
+ return findPoints.reduce((innerLines, point) => {
+ return updateAndAddLine(innerLines, point);
+ }, [...innerLines]);
+
+ }
return innerLines;
+
}
/**
@@ -703,17 +1512,33 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
*/
function processEavesEdge(roofId, canvas, skeleton, edgeResult, skeletonLines) {
let roof = canvas?.getObjects().find((object) => object.id === roofId)
+ // [1] 벽 객체를 가져옵니다.
+ let wall = canvas.getObjects().find((obj) => obj.name === POLYGON_TYPE.WALL && obj.attributes.roofId === roofId);
+
const polygonPoints = edgeResult.Polygon.map(p => ({ x: p.X, y: p.Y }));
//처마선인지 확인하고 pitch 대입 각 처마선마다 pitch가 다를수 있음
const { Begin, End } = edgeResult.Edge;
- let outerLine = roof.lines.find(line =>
+ // [2] 현재 처리 중인 엣지가 roof.lines의 몇 번째 인덱스인지 찾습니다.
+ const roofLineIndex = roof.lines.findIndex(line =>
line.attributes.type === 'eaves' && isSameLine(Begin.X, Begin.Y, End.X, End.Y, line)
-
);
+
+ let outerLine = null;
+ let targetWallId = null;
+
+ // [3] 인덱스를 통해 매칭되는 벽 라인의 불변 ID(wallId)를 가져옵니다.
+ if (roofLineIndex !== -1) {
+ outerLine = roof.lines[roofLineIndex];
+ if (wall && wall.lines && wall.lines[roofLineIndex]) {
+ targetWallId = wall.lines[roofLineIndex].attributes.wallId;
+ }
+ targetWallId = outerLine.attributes.wallId;
+ }
+
if(!outerLine) {
- outerLine = findMatchingLine(edgeResult.Polygon, roof, roof.points);
- console.log('Has matching line:', outerLine);
+ outerLine = findMatchingLine(edgeResult.Polygon, roof, roof.points);
+ console.log('Has matching line:', outerLine);
}
let pitch = outerLine?.attributes?.pitch??0
@@ -728,7 +1553,7 @@ function processEavesEdge(roofId, canvas, skeleton, edgeResult, skeletonLines) {
type: POLYGON_TYPE.ROOF,
fill: false,
stroke: 'blue',
- strokeWidth: 8,
+ strokeWidth: 4,
skeletonType: 'polygon',
polygonName: '',
parentId: roof.id,
@@ -745,10 +1570,10 @@ function processEavesEdge(roofId, canvas, skeleton, edgeResult, skeletonLines) {
// 지붕 경계선과 교차 확인 및 클리핑
const clippedLine = clipLineToRoofBoundary(p1, p2, roof.lines, roof.moveSelectLine);
- console.log('clipped line', clippedLine.p1, clippedLine.p2);
+ //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);
- // }
+ addRawLine(roof.id, skeletonLines, clippedLine.p1, clippedLine.p2, 'ridge', '#1083E3', 4, pitch, isOuterLine, targetWallId);
+ // }
}
}
@@ -872,7 +1697,7 @@ function isOuterEdge(p1, p2, edges) {
* @param pitch
* @param isOuterLine
*/
-function addRawLine(id, skeletonLines, p1, p2, lineType, color, width, pitch, isOuterLine) {
+function addRawLine(id, skeletonLines, p1, p2, lineType, color, width, pitch, isOuterLine, wallLineId) {
// const edgeKey = [`${p1.x.toFixed(1)},${p1.y.toFixed(1)}`, `${p2.x.toFixed(1)},${p2.y.toFixed(1)}`].sort().join('|');
// if (processedInnerEdges.has(edgeKey)) return;
// processedInnerEdges.add(edgeKey);
@@ -909,6 +1734,7 @@ function addRawLine(id, skeletonLines, p1, p2, lineType, color, width, pitch, is
isRidge: normalizedType === LINE_TYPE.SUBLINE.RIDGE,
isOuterEdge: isOuterLine,
pitch: pitch,
+ wallLineId: wallLineId, // [5] attributes에 wallId 저장 (이 정보가 최종 roofLines에 들어갑니다)
...(eavesIndex !== undefined && { eavesIndex })
},
lineStyle: { color, width },
@@ -1399,58 +2225,15 @@ const isPointOnSegment = (point, segStart, segEnd) => {
export {
findAllIntersections,
collectAllPoints,
- createPolygonsFromSkeletonLines
+ createPolygonsFromSkeletonLines,
+ preprocessPolygonCoordinates,
+ findOppositeLine,
+ createOrderedBasePoints,
+ createInnerLinesFromSkeleton
};
-/**
- * Finds lines in the roof that match certain criteria based on the given points
- * @param {Array} lines - The roof lines to search through
- * @param {Object} startPoint - The start point of the reference line
- * @param {Object} endPoint - The end point of the reference line
- * @param {Array} oldPoints - The old points to compare against
- * @returns {Array} Array of matching line objects with their properties
- */
-function findMatchingRoofLines(lines, startPoint, endPoint, oldPoints) {
- const result = [];
- // If no lines provided, return empty array
- if (!lines || !lines.length) return result;
-
- // Process each line in the roof
- for (const line of lines) {
- // Get the start and end points of the current line
- const p1 = { x: line.x1, y: line.y1 };
- const p2 = { x: line.x2, y: line.y2 };
-
- // Check if both points exist in the oldPoints array
- const p1Exists = oldPoints.some(p =>
- Math.abs(p.x - p1.x) < 0.0001 && Math.abs(p.y - p1.y) < 0.0001
- );
-
- const p2Exists = oldPoints.some(p =>
- Math.abs(p.x - p2.x) < 0.0001 && Math.abs(p.y - p2.y) < 0.0001
- );
-
- // If both points exist in oldPoints, add to results
- if (p1Exists && p2Exists) {
- // Calculate line position relative to the reference line
- const position = getLinePosition(
- { start: p1, end: p2 },
- { start: startPoint, end: endPoint }
- );
-
- result.push({
- start: p1,
- end: p2,
- position: position,
- line: line
- });
- }
- }
-
- return result;
-}
/**
* Finds the opposite line in a polygon based on the given line
@@ -1525,55 +2308,7 @@ function findOppositeLine(edges, startPoint, endPoint, points) {
});
}
- // // 현재 선분의 기울기 계산
- // const currentSlope = calculateSlope(p1, p2);
- //
- // // 기울기가 같은지 확인 (평행한 선분)
- // if (areLinesParallel(referenceSlope, currentSlope)) {
- // // 동일한 선분이 아닌지 확인
- // if (!areSameLine(p1, p2, startPoint, endPoint)) {
- // const position = getLinePosition(
- // { start: p1, end: p2 },
- // { start: startPoint, end: endPoint }
- // );
- //
- // const lineMid = {
- // x: (p1.x + p2.x) / 2,
- // y: (p1.y + p2.y) / 2
- // };
- //
- // const baseMid = {
- // x: (startPoint.x + endPoint.x) / 2,
- // y: (startPoint.y + endPoint.y) / 2
- // };
- // const distance = Math.sqrt(
- // Math.pow(lineMid.x - baseMid.x, 2) +
- // Math.pow(lineMid.y - baseMid.y, 2)
- // );
- //
- // const existingIndex = result.findIndex(line => line.position === position);
- //
- // if (existingIndex === -1) {
- // // If no line with this position exists, add it
- // result.push({
- // start: p1,
- // end: p2,
- // position: position,
- // polygon: polygon,
- // distance: distance
- // });
- // } else if (distance > result[existingIndex].distance) {
- // // If a line with this position exists but is closer, replace it
- // result[existingIndex] = {
- // start: p1,
- // end: p2,
- // position: position,
- // polygon: polygon,
- // distance: distance
- // };
- // }
- // }
- // }
+
}
}
@@ -1585,19 +2320,19 @@ function getLinePosition(line, referenceLine) {
// 대상선의 중점
const lineMidX = (line.start.x + line.end.x) / 2;
const lineMidY = (line.start.y + line.end.y) / 2;
-
+
// 참조선의 중점
const refMidX = (referenceLine.start.x + referenceLine.end.x) / 2;
const refMidY = (referenceLine.start.y + referenceLine.end.y) / 2;
-
+
// 단순히 좌표 차이로 판단
const deltaX = lineMidX - refMidX;
const deltaY = lineMidY - refMidY;
-
+
// 참조선의 기울기
const refDeltaX = referenceLine.end.x - referenceLine.start.x;
const refDeltaY = referenceLine.end.y - referenceLine.start.y;
-
+
// 참조선이 더 수평인지 수직인지 판단
if (Math.abs(refDeltaX) > Math.abs(refDeltaY)) {
// 수평선에 가까운 경우 - Y 좌표로 판단
@@ -1622,23 +2357,7 @@ function calculateSlope(p1, p2) {
return (p2.y - p1.y) / (p2.x - p1.x);
}
-// 두 직선이 평행한지 확인
-// function areLinesParallel(slope1, slope2) {
-// // 두 직선 모두 수직선인 경우
-// if (slope1 === Infinity && slope2 === Infinity) return true;
-//
-// // 기울기의 차이가 매우 작으면 평행한 것으로 간주
-// const epsilon = 0.0001;
-// return Math.abs(slope1 - slope2) < epsilon;
-// }
-// 두 선분이 동일한지 확인
-// function areSameLine(p1, p2, p3, p4) {
-// return (
-// (isSamePoint(p1, p3) && isSamePoint(p2, p4)) ||
-// (isSamePoint(p1, p4) && isSamePoint(p2, p3))
-// );
-// }
/**
* Helper function to find the polygon containing the given line
*/
@@ -1686,7 +2405,7 @@ function clipLineToRoofBoundary(p1, p2, roofLines, selectLine) {
// p1이 다각형 내부에 있는지 확인
const p1Inside = isPointInsidePolygon(p1, roofLines);
-
+
// p2가 다각형 내부에 있는지 확인
const p2Inside = isPointInsidePolygon(p2, roofLines);
@@ -1703,7 +2422,7 @@ function clipLineToRoofBoundary(p1, p2, roofLines, selectLine) {
// 선분과 다각형 경계선의 교차점들을 찾음
const intersections = [];
-
+
for (const line of roofLines) {
const lineP1 = { x: line.x1, y: line.y1 };
const lineP2 = { x: line.x2, y: line.y2 };
@@ -1765,31 +2484,6 @@ function clipLineToRoofBoundary(p1, p2, roofLines, selectLine) {
return { p1: clippedP1, p2: clippedP2 };
}
-/**
- * 점이 다각형 내부에 있는지 확인합니다 (Ray Casting 알고리즘 사용).
- * @param {Object} point - 확인할 점 {x, y}
- * @param {Array} roofLines - 다각형을 구성하는 선분들
- * @returns {boolean} 점이 다각형 내부에 있으면 true
- */
-function isPointInsidePolygon2(point, roofLines) {
- let inside = false;
- const x = point.x;
- const y = point.y;
-
- for (const line of roofLines) {
- const x1 = line.x1;
- const y1 = line.y1;
- const x2 = line.x2;
- const y2 = line.y2;
-
- // Ray casting: 점에서 오른쪽으로 수평선을 그었을 때 다각형 경계와 교차하는 횟수 확인
- if (((y1 > y) !== (y2 > y)) && (x < (x2 - x1) * (y - y1) / (y2 - y1) + x1)) {
- inside = !inside;
- }
- }
-
- return inside;
-}
function isPointInsidePolygon(point, roofLines) {
// 1. 먼저 경계선 위에 있는지 확인 (방향 무관)
@@ -1861,14 +2555,14 @@ function isPointOnLineSegmentDirectionIndependent(point, line, tolerance) {
* 선분 위의 점에 대한 매개변수 t를 계산합니다.
* p = p1 + t * (p2 - p1)에서 t 값을 구합니다.
* @param {Object} p1 - 선분의 시작점
- * @param {Object} p2 - 선분의 끝점
+ * @param {Object} p2 - 선분의 끝점
* @param {Object} point - 선분 위의 점
* @returns {number} 매개변수 t (0이면 p1, 1이면 p2)
*/
function getParameterT(p1, p2, point) {
const dx = p2.x - p1.x;
const dy = p2.y - p1.y;
-
+
// x 좌표가 더 큰 변화를 보이면 x로 계산, 아니면 y로 계산
if (Math.abs(dx) > Math.abs(dy)) {
return dx === 0 ? 0 : (point.x - p1.x) / dx;
@@ -1908,6 +2602,10 @@ function getLineDirection(p1, p2) {
return 'top'; // (-135 ~ -45)
}
+
+
+
+
// selectLine과 baseLines 비교하여 방향 찾기
function findLineDirection(selectLine, baseLines) {
for (const baseLine of baseLines) {
@@ -1939,153 +2637,6 @@ function findLineDirection(selectLine, baseLines) {
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를 연결하여 다각형 순서로 정렬된 점들 반환
@@ -2254,11 +2805,11 @@ export const getSelectLinePosition = (wall, selectLine, options = {}) => {
const leftIsInside = checkPointInPolygon(leftTestPoint, wall);
const rightIsInside = checkPointInPolygon(rightTestPoint, wall);
- if (debug) {
- console.log('수직선 테스트:');
- console.log(' 왼쪽 포인트:', leftTestPoint, '-> 내부:', leftIsInside);
- console.log(' 오른쪽 포인트:', rightTestPoint, '-> 내부:', rightIsInside);
- }
+ // if (debug) {
+ // console.log('수직선 테스트:');
+ // console.log(' 왼쪽 포인트:', leftTestPoint, '-> 내부:', leftIsInside);
+ // console.log(' 오른쪽 포인트:', rightTestPoint, '-> 내부:', rightIsInside);
+ // }
// left 조건: 왼쪽이 외부, 오른쪽이 내부
if (!leftIsInside && rightIsInside) {
@@ -2408,165 +2959,517 @@ const analyzeLineOrientation = (x1, y1, x2, y2, epsilon = 0.5) => {
};
};
-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();
+// 점에서 선분까지의 최단 거리를 계산하는 도우미 함수
+function pointToLineDistance(point, lineP1, lineP2) {
+ const A = point.x - lineP1.x;
+ const B = point.y - lineP1.y;
+ const C = lineP2.x - lineP1.x;
+ const D = lineP2.y - lineP1.y;
- for (const line of roofLines) {
- const p1 = { x: line.x1, y: line.y1 };
- const p2 = { x: line.x2, y: line.y2 };
+ const dot = A * C + B * D;
+ const lenSq = C * C + D * D;
+ let param = -1;
- const key1 = `${p1.x},${p1.y}`;
- const key2 = `${p2.x},${p2.y}`;
+ if (lenSq !== 0) {
+ param = dot / lenSq;
+ }
- if (!seen.has(key1)) {
- points.push(p1);
- seen.add(key1);
- }
- if (!seen.has(key2)) {
- points.push(p2);
- seen.add(key2);
+ let xx, yy;
+
+ if (param < 0) {
+ xx = lineP1.x;
+ yy = lineP1.y;
+ } else if (param > 1) {
+ xx = lineP2.x;
+ yy = lineP2.y;
+ } else {
+ xx = lineP1.x + param * C;
+ yy = lineP1.y + param * D;
+ }
+
+ const dx = point.x - xx;
+ const dy = point.y - yy;
+ return Math.sqrt(dx * dx + dy * dy);
+}
+
+
+const getOrientation = (line, eps = 0.1) => {
+ const x1 = line.get('x1')
+ const y1 = line.get('y1')
+ const x2 = line.get('x2')
+ const y2 = line.get('y2')
+ const dx = Math.abs(x2 - x1)
+ const dy = Math.abs(y2 - y1)
+
+ if (dx < eps && dy >= eps) return 'vertical'
+ if (dy < eps && dx >= eps) return 'horizontal'
+ if (dx < eps && dy < eps) return 'point'
+ return 'diagonal'
+}
+
+
+
+export const processEaveHelpLines = (lines) => {
+ if (!lines || lines.length === 0) return [];
+
+ // 수직/수평 라인 분류 (부동소수점 오차 고려)
+ const verticalLines = lines.filter(line => Math.abs(line.x1 - line.x2) < 0.1);
+ const horizontalLines = lines.filter(line => Math.abs(line.y1 - line.y2) < 0.1);
+
+ // 라인 병합 (더 엄격한 조건으로)
+ const mergedVertical = mergeLines(verticalLines, 'vertical');
+ const mergedHorizontal = mergeLines(horizontalLines, 'horizontal');
+
+ // 결과 확인용 로그
+ console.log('Original lines:', lines.length);
+ console.log('Merged vertical:', mergedVertical.length);
+ console.log('Merged horizontal:', mergedHorizontal.length);
+
+ return [...mergedVertical, ...mergedHorizontal];
+};
+
+const mergeLines = (lines, direction) => {
+ if (!lines || lines.length < 2) return lines || [];
+
+ // 방향에 따라 정렬 (수직: y1 기준, 수평: x1 기준)
+ lines.sort((a, b) => {
+ const aPos = direction === 'vertical' ? a.y1 : a.x1;
+ const bPos = direction === 'vertical' ? b.y1 : b.x1;
+ return aPos - bPos;
+ });
+
+ const merged = [];
+ let current = { ...lines[0] };
+
+ for (let i = 1; i < lines.length; i++) {
+ const line = lines[i];
+
+ // 같은 선상에 있는지 확인 (부동소수점 오차 고려)
+ const isSameLine = direction === 'vertical'
+ ? Math.abs(current.x1 - line.x1) < 0.1
+ : Math.abs(current.y1 - line.y1) < 0.1;
+
+ // 연결 가능한지 확인 (약간의 겹침 허용)
+ const isConnected = direction === 'vertical'
+ ? current.y2 + 0.1 >= line.y1 // 약간의 오차 허용
+ : current.x2 + 0.1 >= line.x1;
+
+ if (isSameLine && isConnected) {
+ // 라인 병합
+ current.y2 = Math.max(current.y2, line.y2);
+ current.x2 = direction === 'vertical' ? current.x1 : current.x2;
+ } else {
+ merged.push(current);
+ current = { ...line };
}
}
+ merged.push(current);
- // 3. Find the bounding box
- let minX = Infinity, minY = Infinity;
- let maxX = -Infinity, maxY = -Infinity;
+ // 병합 결과 로그
+ console.log(`Merged ${direction} lines:`, merged);
- 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);
- }
+ return merged;
+};
- // 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 }
- );
+/**
+ * 주어진 점을 포함하는 라인을 찾는 함수
+ * @param {Array} lines - 검색할 라인 배열 (각 라인은 x1, y1, x2, y2 속성을 가져야 함)
+ * @param {Object} point - 찾고자 하는 점 {x, y}
+ * @param {number} [tolerance=0.1] - 점이 선분 위에 있는지 판단할 때의 허용 오차
+ * @returns {Object|null} 점을 포함하는 첫 번째 라인 또는 null
+ */
+function findLineContainingPoint(lines, point, tolerance = 0.1) {
+ if (!point || !lines || !lines.length) return null;
- 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 } };
+ return lines.find(line => {
+ const { x1, y1, x2, y2 } = line;
+ return isPointOnLineSegment(point, {x: x1, y: y1}, {x: x2, y: y2}, tolerance);
+ }) || null;
}
/**
- * 점에서 특정 방향으로 경계선과의 교차점을 찾습니다.
- * @param {Object} point - 시작점 {x, y}
- * @param {Object} direction - 방향 벡터 {x, y} (정규화된 값)
- * @param {Array} roofLines - 지붕 경계선 배열
- * @returns {Object|null} 교차점 {x, y} 또는 null
+ * 점이 선분 위에 있는지 확인하는 함수
+ * @param {Object} point - 확인할 점 {x, y}
+ * @param {Object} lineStart - 선분의 시작점 {x, y}
+ * @param {Object} lineEnd - 선분의 끝점 {x, y}
+ * @param {number} tolerance - 허용 오차
+ * @returns {boolean}
*/
-function findBoundaryIntersection(point, direction, roofLines) {
- let closestIntersection = null;
- let minDistance = Infinity;
+function isPointOnLineSegment(point, lineStart, lineEnd, tolerance = 0.1) {
+ const { x: px, y: py } = point;
+ const { x: x1, y: y1 } = lineStart;
+ const { x: x2, y: y2 } = lineEnd;
- // 충분히 긴 거리로 광선 생성 (임의로 큰 값 사용)
- const rayLength = 10000;
- const rayEnd = {
- x: point.x + direction.x * rayLength,
- y: point.y + direction.y * rayLength
+ // 선분의 길이
+ const lineLength = Math.hypot(x2 - x1, y2 - y1);
+
+ // 점에서 선분의 양 끝점까지의 거리 합
+ const dist1 = Math.hypot(px - x1, py - y1);
+ const dist2 = Math.hypot(px - x2, py - y2);
+
+ // 점이 선분 위에 있는지 확인 (허용 오차 범위 내에서)
+ return Math.abs(dist1 + dist2 - lineLength) <= tolerance;
+}
+
+/**
+ * Updates a line in the innerLines array and returns the updated array
+ * @param {Array} innerLines - Array of line objects to update
+ * @param {Object} targetPoint - The point to find the line {x, y}
+ * @param {Object} wallBaseLine - The base line containing new coordinates
+ * @param {Function} getAddLine - Function to add a new line
+ * @returns {Array} Updated array of lines
+ */
+function updateAndAddLine(innerLines, targetPoint) {
+
+ // 1. Find the line containing the target point
+ const foundLine = findLineContainingPoint(innerLines, targetPoint);
+ if (!foundLine) {
+ console.warn('No line found containing the target point');
+ return [...innerLines];
+ }
+
+ // 2. Create a new array without the found line
+ const updatedLines = innerLines.filter(line =>
+ line !== foundLine &&
+ !(line.x1 === foundLine.x1 &&
+ line.y1 === foundLine.y1 &&
+ line.x2 === foundLine.x2 &&
+ line.y2 === foundLine.y2)
+ );
+
+ // Calculate distances to both endpoints
+ const distanceToStart = Math.hypot(
+ targetPoint.x - foundLine.x1,
+ targetPoint.y - foundLine.y1
+ );
+ const distanceToEnd = Math.hypot(
+ targetPoint.x - foundLine.x2,
+ targetPoint.y - foundLine.y2
+ );
+
+ // 단순 거리 비교: 타겟 포인트가 시작점에 더 가까우면 시작점을 수정(isUpdatingStart = true)
+ //무조건 start
+ let isUpdatingStart = false //distanceToStart < distanceToEnd;
+ if(targetPoint.position === "top_in_start"){
+ if(foundLine.y2 >= foundLine.y1){
+ isUpdatingStart = true;
+ }
+ }else if(targetPoint.position === "top_in_end"){
+ if(foundLine.y2 >= foundLine.y1){
+ isUpdatingStart = true;
+ }
+
+ }else if(targetPoint.position === "bottom_in_start"){
+ if(foundLine.y2 <= foundLine.y1){
+ isUpdatingStart = true;
+ }
+ }else if(targetPoint.position === "bottom_in_end"){
+ if(foundLine.y2 <= foundLine.y1){
+ isUpdatingStart = true;
+ }
+ }else if(targetPoint.position === "left_in_start"){
+ if(foundLine.x2 >= foundLine.x1){
+ isUpdatingStart = true;
+ }
+ }else if(targetPoint.position === "left_in_end"){
+ if(foundLine.x2 >= foundLine.x1){
+ isUpdatingStart = true;
+ }
+ }else if(targetPoint.position === "right_in_start"){
+ if(foundLine.x2 <= foundLine.x1){
+ isUpdatingStart = true;
+ }
+ }else if(targetPoint.position === "right_in_end"){
+ if(foundLine.x2 <= foundLine.x1){
+ isUpdatingStart = true;
+ }
+ }else if(targetPoint.position === "top_out_start"){
+ if(foundLine.y2 >= foundLine.y1){
+ isUpdatingStart = true;
+ }
+ }else if(targetPoint.position === "top_out_end"){
+ if(foundLine.y2 >= foundLine.y1){
+ isUpdatingStart = true;
+ }
+ }else if(targetPoint.position === "bottom_out_start"){
+ if(foundLine.y2 <= foundLine.y1){
+ isUpdatingStart = true;
+ }
+ }else if(targetPoint.position === "bottom_out_end"){
+ if(foundLine.y2 <= foundLine.y1){
+ isUpdatingStart = true;
+ }
+ }else if(targetPoint.position === "left_out_start"){
+ if(foundLine.x2 >= foundLine.x1){
+ isUpdatingStart = true;
+ }
+ }else if(targetPoint.position === "left_out_end"){
+ if(foundLine.x2 >= foundLine.x1){
+ isUpdatingStart = true;
+ }
+ }else if(targetPoint.position === "right_out_start"){
+ if(foundLine.x2 <= foundLine.x1){
+ isUpdatingStart = true;
+ }
+ }else if(targetPoint.position === "right_out_end"){
+ if(foundLine.x2 <= foundLine.x1){
+ isUpdatingStart = true;
+ }
+ }
+
+ const updatedLine = {
+ ...foundLine,
+ left: isUpdatingStart ? targetPoint.x : foundLine.x1,
+ top: isUpdatingStart ? targetPoint.y : foundLine.y1,
+ x1: isUpdatingStart ? targetPoint.x : foundLine.x1,
+ y1: isUpdatingStart ? targetPoint.y : foundLine.y1,
+ x2: isUpdatingStart ? foundLine.x2 : targetPoint.x,
+ y2: isUpdatingStart ? foundLine.y2 : targetPoint.y,
+ startPoint: {
+ x: isUpdatingStart ? targetPoint.x : foundLine.x1,
+ y: isUpdatingStart ? targetPoint.y : foundLine.y1
+ },
+ endPoint: {
+ x: isUpdatingStart ? foundLine.x2 : targetPoint.x,
+ y: isUpdatingStart ? foundLine.y2 : targetPoint.y
+ }
};
- // 모든 경계선과의 교차점 확인
- for (const line of roofLines) {
- const lineP1 = { x: line.x1, y: line.y1 };
- const lineP2 = { x: line.x2, y: line.y2 };
+ // 4. If it's a Fabric.js object, use set method if available
+ if (typeof foundLine.set === 'function') {
+ foundLine.set({
+ x1: isUpdatingStart ? targetPoint.x : foundLine.x1,
+ y1: isUpdatingStart ? targetPoint.y : foundLine.y1,
+ x2: isUpdatingStart ? foundLine.x2 : targetPoint.x,
+ y2: isUpdatingStart ? foundLine.y2 : targetPoint.y
+ });
+ updatedLines.push(foundLine);
+ } else {
+ updatedLines.push(updatedLine);
+ }
- const intersection = getLineIntersection(point, rayEnd, lineP1, lineP2);
+ return updatedLines;
+}
- 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;
+
+/**
+ * 점이 선분 위에 있는지 확인
+ * @param {Object} point - 확인할 점 {x, y}
+ * @param {Object} lineStart - 선분의 시작점 {x, y}
+ * @param {Object} lineEnd - 선분의 끝점 {x, y}
+ * @param {number} tolerance - 오차 허용 범위
+ * @returns {boolean} - 점이 선분 위에 있으면 true, 아니면 false
+ */
+function isPointOnLineSegment2(point, lineStart, lineEnd, tolerance = 0.1) {
+ const { x: px, y: py } = point;
+ const { x: x1, y: y1 } = lineStart;
+ const { x: x2, y: y2 } = lineEnd;
+
+ // 선분의 길이
+ const lineLength = Math.hypot(x2 - x1, y2 - y1);
+
+ // 점에서 선분의 양 끝점까지의 거리
+ const dist1 = Math.hypot(px - x1, py - y1);
+ const dist2 = Math.hypot(px - x2, py - y2);
+
+ // 점이 선분 위에 있는지 확인 (오차 허용 범위 내에서)
+ const isOnSegment = Math.abs((dist1 + dist2) - lineLength) <= tolerance;
+
+ if (isOnSegment) {
+ console.log(`점 (${px}, ${py})은 선분 [(${x1}, ${y1}), (${x2}, ${y2})] 위에 있습니다.`);
+ }
+
+ return isOnSegment;
+}
+
+/**
+ * 세 점(p1 -> p2 -> p3)의 방향성을 계산합니다. (2D 외적)
+ * 반시계 방향(CCW)으로 그려진 폴리곤(Y축 Down) 기준:
+ * - 결과 > 0 : 오른쪽 턴 (Right Turn) -> 골짜기 (Valley/Reflex Vertex)
+ * - 결과 < 0 : 왼쪽 턴 (Left Turn) -> 외곽 모서리 (Convex Vertex)
+ * - 결과 = 0 : 직선
+ */
+function getTurnDirection(p1, p2, p3) {
+ // 벡터 a: p1 -> p2
+ // 벡터 b: p2 -> p3
+ const val = (p2.x - p1.x) * (p3.y - p2.y) - (p2.y - p1.y) * (p3.x - p2.x);
+ return val;
+}
+
+/**
+ * 현재 점(point)을 기준으로 연결된 이전 라인과 다음 라인을 찾아 골짜기 여부 판단
+ */
+function isValleyVertex(targetPoint, connectedLine, allLines, isStartVertex) {
+ const tolerance = 0.1;
+
+ // 1. 연결된 '다른' 라인을 찾습니다.
+ // isStartVertex가 true면 : 이 점으로 '들어오는' 라인(Previous Line)을 찾아야 함
+ // isStartVertex가 false면 : 이 점에서 '나가는' 라인(Next Line)을 찾아야 함
+
+ let neighborLine = null;
+
+ if (isStartVertex) {
+ // targetPoint가 Start이므로, 어떤 라인의 End가 targetPoint와 같아야 함 (Previous Line)
+ neighborLine = allLines.find(l =>
+ l !== connectedLine &&
+ isSamePoint(l.endPoint || {x:l.x2, y:l.y2}, targetPoint, tolerance)
+ );
+ } else {
+ // targetPoint가 End이므로, 어떤 라인의 Start가 targetPoint와 같아야 함 (Next Line)
+ neighborLine = allLines.find(l =>
+ l !== connectedLine &&
+ isSamePoint(l.startPoint || {x:l.x1, y:l.y1}, targetPoint, tolerance)
+ );
+ }
+
+ // 연결된 라인을 못 찾았거나 끊겨있으면 판단 불가 (일단 false)
+ if (!neighborLine) return false;
+
+ // 2. 세 점을 구성하여 회전 방향(Turn) 계산
+ // 순서: PrevLine.Start -> [TargetVertex] -> NextLine.End
+ let p1, p2, p3;
+
+ if (isStartVertex) {
+ // neighbor(Prev) -> connected(Current)
+ p1 = neighborLine.startPoint || {x: neighborLine.x1, y: neighborLine.y1};
+ p2 = targetPoint; // 접점
+ p3 = connectedLine.endPoint || {x: connectedLine.x2, y: connectedLine.y2};
+ } else {
+ // connected(Current) -> neighbor(Next)
+ p1 = connectedLine.startPoint || {x: connectedLine.x1, y: connectedLine.y1};
+ p2 = targetPoint; // 접점
+ p3 = neighborLine.endPoint || {x: neighborLine.x2, y: neighborLine.y2};
+ }
+
+ // 3. 외적 계산 (Y축이 아래로 증가하는 캔버스 좌표계 + CCW 진행 기준)
+ // 값이 양수(+)면 오른쪽 턴 = 골짜기
+ const crossProduct = getTurnDirection(p1, p2, p3);
+
+ return crossProduct > 0;
+}
+
+function findInteriorPoint(line, polygonLines) {
+ const { x1, y1, x2, y2 } = line;
+
+ // line 객체 포맷 통일
+ const currentLine = {
+ ...line,
+ startPoint: { x: x1, y: y1 },
+ endPoint: { x: x2, y: y2 }
+ };
+
+ // 1. 시작점이 골짜기인지 확인 (들어오는 라인과 나가는 라인의 각도)
+ const startIsValley = isValleyVertex(currentLine.startPoint, currentLine, polygonLines, true);
+
+ // 2. 끝점이 골짜기인지 확인
+ const endIsValley = isValleyVertex(currentLine.endPoint, currentLine, polygonLines, false);
+
+ return {
+ start: startIsValley,
+ end: endIsValley
+ };
+}
+
+/**
+ * baseLines의 순서를 wallLines의 순서와 일치시킵니다.
+ * 1순위: 공통 ID(id, matchingId, parentId 등)를 이용한 직접 매칭
+ * 2순위: 기하학적 유사성(기울기, 길이, 위치)을 점수화하여 매칭
+ *
+ * @param {Array} baseLines - 정렬할 원본 baseLine 배열
+ * @param {Array} wallLines - 기준이 되는 wallLine 배열
+ * @returns {Array} wallLines 순서에 맞춰 정렬된 baseLines
+ */
+export const sortBaseLinesByWallLines = (baseLines, wallLines) => {
+ if (!baseLines || !wallLines || baseLines.length === 0 || wallLines.length === 0) {
+ return baseLines;
+ }
+
+ const sortedBaseLines = new Array(wallLines.length).fill(null);
+ const usedBaseIndices = new Set();
+
+ // [1단계] ID 매칭 (기존 로직 유지 - 혹시 ID가 있는 경우를 대비)
+ // ... (ID 매칭 코드는 생략하거나 유지) ...
+
+ // [2단계] 'originPoint' 또는 좌표 일치성을 이용한 강력한 기하학적 매칭
+ wallLines.forEach((wLine, wIndex) => {
+ if (sortedBaseLines[wIndex]) return;
+
+ // 비교할 기준 좌표 설정 (originPoint가 있으면 그것을, 없으면 현재 좌표 사용)
+ const wStart = wLine.attributes?.originPoint
+ ? { x: wLine.attributes.originPoint.x1, y: wLine.attributes.originPoint.y1 }
+ : { x: wLine.x1, y: wLine.y1 };
+
+ const wEnd = wLine.attributes?.originPoint
+ ? { x: wLine.attributes.originPoint.x2, y: wLine.attributes.originPoint.y2 }
+ : { x: wLine.x2, y: wLine.y2 };
+
+ // 수직/수평 여부 판단
+ const isVertical = Math.abs(wStart.x - wEnd.x) < 0.1;
+ const isHorizontal = Math.abs(wStart.y - wEnd.y) < 0.1;
+
+ let bestMatchIndex = -1;
+ let minDiff = Infinity;
+
+ baseLines.forEach((bLine, bIndex) => {
+ if (usedBaseIndices.has(bIndex)) return;
+
+ let diff = Infinity;
+
+ // 1. 수직선인 경우: X좌표가 일치해야 함 (예: 230.8 == 230.8)
+ if (isVertical) {
+ // bLine도 수직선인지 확인 (x1, x2 차이가 거의 없어야 함)
+ if (Math.abs(bLine.x1 - bLine.x2) < 1.0) {
+ // X좌표 차이를 오차(diff)로 계산
+ diff = Math.abs(wStart.x - bLine.x1);
+ }
+ }
+ // 2. 수평선인 경우: Y좌표가 일치해야 함
+ else if (isHorizontal) {
+ // bLine도 수평선인지 확인
+ if (Math.abs(bLine.y1 - bLine.y2) < 1.0) {
+ diff = Math.abs(wStart.y - bLine.y1);
+ }
+ }
+ // 3. 대각선인 경우: 기울기와 절편 비교 (복잡하므로 거리로 대체)
+ else {
+ // 중점 간 거리 + 기울기 차이
+ // (이전 답변의 로직 사용 가능)
+ }
+
+ // 오차가 매우 작으면(예: 1px 미만) 같은 라인으로 간주
+ if (diff < 1.0 && diff < minDiff) {
+ minDiff = diff;
+ bestMatchIndex = bIndex;
+ }
+ });
+
+ if (bestMatchIndex !== -1) {
+ sortedBaseLines[wIndex] = baseLines[bestMatchIndex];
+ usedBaseIndices.add(bestMatchIndex);
+ }
+ });
+
+ // [3단계] 남은 라인 처리 (Fallback)
+ // 매칭되지 않은 wallLine들에 대해 남은 baseLines를 순서대로 배정하거나
+ // 거리 기반 근사 매칭을 수행
+ // ... (기존 fallback 로직) ...
+
+ // 빈 구멍 채우기 (null 방지)
+ for(let i=0; i !usedBaseIndices.has(idx));
+ if(unused !== -1) {
+ sortedBaseLines[i] = baseLines[unused];
+ usedBaseIndices.add(unused);
+ } else {
+ sortedBaseLines[i] = baseLines[0]; // 최후의 수단
}
}
}
- 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
+ return sortedBaseLines;
+};
\ No newline at end of file