Merge pull request '[1308] 배치면 초기 설정의 지붕 경사(경사도)에서 소수점 입력이 불가능합니다' (#412) from dev into dev_ysCha
Reviewed-on: #412
This commit is contained in:
commit
575386c526
@ -370,7 +370,7 @@ export default function PlacementShapeSetting({ id, pos = { x: 50, y: 180 }, pla
|
||||
}}
|
||||
options={{
|
||||
allowNegative: false,
|
||||
allowDecimal: false //(index !== 0),
|
||||
allowDecimal: true //(index !== 0),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -376,8 +376,6 @@ export const skeletonBuilder = (roofId, canvas, textMode) => {
|
||||
roof.innerLines = roof.innerLines || [];
|
||||
roof.innerLines = createInnerLinesFromSkeleton(roofId, canvas, skeleton, textMode)
|
||||
|
||||
|
||||
|
||||
// 캔버스에 스켈레톤 상태 저장
|
||||
if (!canvas.skeletonStates) {
|
||||
canvas.skeletonStates = {}
|
||||
@ -387,7 +385,7 @@ export const skeletonBuilder = (roofId, canvas, textMode) => {
|
||||
canvas.skeletonLines = [];
|
||||
canvas.skeletonLines.push(...roof.innerLines)
|
||||
roof.skeletonLines = canvas.skeletonLines;
|
||||
canvas.renderAll()
|
||||
|
||||
const cleanSkeleton = {
|
||||
Edges: skeleton.Edges.map(edge => ({
|
||||
X1: edge.Edge.Begin.X,
|
||||
@ -436,10 +434,8 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
|
||||
|
||||
skeleton.Edges.forEach((edgeResult, index) => {
|
||||
|
||||
canvas.skeletonLines = [];
|
||||
|
||||
processEavesEdge(roofId, canvas, skeleton, edgeResult, skeletonLines);
|
||||
|
||||
|
||||
});
|
||||
|
||||
// 2. 케라바(Gable) 속성을 가진 외벽선에 해당하는 스켈레톤을 후처리합니다.
|
||||
@ -504,8 +500,8 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
|
||||
const innerLines = [];
|
||||
const existingLines = new Set(); // 이미 추가된 라인을 추적하기 위한 Set
|
||||
|
||||
console.log("length::::::::", skeletonLines.length);
|
||||
skeletonLines.forEach((line, index) => {
|
||||
|
||||
skeletonLines.forEach(line => {
|
||||
const { p1, p2, attributes, lineStyle } = line;
|
||||
|
||||
// 라인을 고유하게 식별할 수 있는 키 생성 (정규화된 좌표로 정렬하여 비교)
|
||||
@ -515,11 +511,9 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
|
||||
].sort().join('|');
|
||||
|
||||
// 이미 추가된 라인인지 확인
|
||||
// if (existingLines.has(lineKey)) {
|
||||
// return; // 이미 있는 라인이면 스킵
|
||||
// }
|
||||
|
||||
|
||||
if (existingLines.has(lineKey)) {
|
||||
return; // 이미 있는 라인이면 스킵
|
||||
}
|
||||
|
||||
const direction = getLineDirection(
|
||||
{ x: line.p1.x, y: line.p1.y },
|
||||
@ -540,22 +534,11 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
|
||||
roofId: roofId,
|
||||
});
|
||||
|
||||
console.log('Processing line:', {
|
||||
p1: {x: p1.x, y: p1.y},
|
||||
p2: {x: p2.x, y: p2.y},
|
||||
attributes,
|
||||
lineName: (line.attributes.isOuterEdge) ? 'outerLine' : attributes.type
|
||||
});
|
||||
|
||||
console.log('innerLines', innerLine.lineName);
|
||||
console.log("index::::",index)
|
||||
//skeleton 라인에서 처마선은 삭제
|
||||
|
||||
if(innerLine.lineName !== 'outerLine'){
|
||||
canvas.add(innerLine);
|
||||
innerLine.bringToFront();
|
||||
//existingLines.add(lineKey); // 추가된 라인을 추적
|
||||
|
||||
existingLines.add(lineKey); // 추가된 라인을 추적
|
||||
}else{
|
||||
|
||||
|
||||
@ -692,7 +675,6 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
|
||||
fontSize: 13,
|
||||
fill: 'red',
|
||||
fontFamily: 'Arial',
|
||||
textBaseline: 'alphabetic', // 올바른 값으로 설정
|
||||
selectable: true,
|
||||
lockMovementX: false,
|
||||
lockMovementY: false,
|
||||
@ -701,7 +683,7 @@ const createInnerLinesFromSkeleton = (roofId, canvas, skeleton, textMode) => {
|
||||
lockScalingY: true,
|
||||
name: 'lengthText'
|
||||
})
|
||||
//좌표점(임시)
|
||||
|
||||
canvas?.add(coordinateText)
|
||||
}
|
||||
innerLines.push(innerLine)
|
||||
@ -729,10 +711,10 @@ function processEavesEdge(roofId, canvas, skeleton, edgeResult, skeletonLines) {
|
||||
line.attributes.type === 'eaves' && isSameLine(Begin.X, Begin.Y, End.X, End.Y, line)
|
||||
|
||||
);
|
||||
// if(!outerLine) {
|
||||
// outerLine = findMatchingLine(edgeResult.Polygon, roof, roof.points);
|
||||
// console.log('Has matching line:', outerLine);
|
||||
// }
|
||||
if(!outerLine) {
|
||||
outerLine = findMatchingLine(edgeResult.Polygon, roof, roof.points);
|
||||
console.log('Has matching line:', outerLine);
|
||||
}
|
||||
let pitch = outerLine?.attributes?.pitch??0
|
||||
|
||||
|
||||
@ -756,10 +738,6 @@ function processEavesEdge(roofId, canvas, skeleton, edgeResult, skeletonLines) {
|
||||
}
|
||||
|
||||
let eavesLines = []
|
||||
// 이 다각형이 roof.lines와 일치하는 변을 가지고 있는지 확인
|
||||
|
||||
const isolatedPolygons = [];
|
||||
|
||||
for (let i = 0; i < polygonPoints.length; i++) {
|
||||
const p1 = polygonPoints[i];
|
||||
const p2 = polygonPoints[(i + 1) % polygonPoints.length];
|
||||
@ -767,28 +745,11 @@ function processEavesEdge(roofId, canvas, skeleton, edgeResult, skeletonLines) {
|
||||
|
||||
// 지붕 경계선과 교차 확인 및 클리핑
|
||||
const clippedLine = clipLineToRoofBoundary(p1, p2, roof.lines, roof.moveSelectLine);
|
||||
|
||||
console.log('clipped line', clippedLine.p1, clippedLine.p2);
|
||||
const isOuterLine = isOuterEdge(clippedLine.p1, clippedLine.p2, [edgeResult.Edge])
|
||||
|
||||
//현이동에 의해 스켈레톤 라인이 내부에서 끝난경우 roof.lines까지 수평은 수직, 수직은 수평되게 설정
|
||||
|
||||
// 다각형이 roof.lines와 일치하는 변이 하나도 없는 경우에만 확장
|
||||
// 이 다각형이 roof.lines와 일치하는 변을 가지고 있는지 확인
|
||||
// 다각형의 모든 변이 roof.lines와 일치하지 않는 경우에만 확장
|
||||
//const extendLine = extendLineToRoofBoundary(clippedLine, roof, convertedPolygon)
|
||||
|
||||
|
||||
const isIsolated = !convertedPolygon.some(pp =>
|
||||
roof.points.some(rp =>
|
||||
Math.abs(pp.x - rp.x) < 0.5 && Math.abs(pp.y - rp.y) < 0.5
|
||||
));
|
||||
//console.log("skeletonLines::::",skeletonLines);
|
||||
addRawLine(roof.id, skeletonLines, clippedLine.p1, clippedLine.p2, 'ridge', 'red', 5, pitch, isOuterLine);
|
||||
|
||||
// }
|
||||
}
|
||||
|
||||
//그려진
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -899,53 +860,6 @@ function isOuterEdge(p1, p2, edges) {
|
||||
});
|
||||
}
|
||||
|
||||
function isOuterRoofLine(p1, p2, lines) {
|
||||
const tolerance = 0.5;
|
||||
let foundLine = null;
|
||||
let isForward = false;
|
||||
let isBackward = false;
|
||||
|
||||
for (const line of lines) {
|
||||
console.log("lines of line::::", line.startPoint, line.endPoint);
|
||||
const lineStart = { x: line.startPoint.x, y: line.startPoint.y };
|
||||
const lineEnd = { x: line.endPoint.x, y: line.endPoint.y };
|
||||
|
||||
|
||||
let p1X = Math.abs(lineStart.x - p1.x) < tolerance && Math.abs(lineEnd.x - p2.x) < tolerance
|
||||
let p2X = Math.abs(lineEnd.x - p1.x) < tolerance && Math.abs(lineStart.x - p2.x) < tolerance
|
||||
let p1Y = Math.abs(lineStart.y - p1.y) < tolerance && Math.abs(lineEnd.y - p2.y) < tolerance
|
||||
let p2Y = Math.abs(lineEnd.y - p2.y) < tolerance && Math.abs(lineStart.y - p2.y) < tolerance
|
||||
|
||||
if (p1X || p2X) {
|
||||
foundLine = line;
|
||||
p1.y = p1X? lineStart.y : lineEnd.y
|
||||
p2.y = p2X? lineEnd.y : lineStart.y
|
||||
break; // 매칭되는 라인을 찾으면 루프 종료
|
||||
}else if(p1Y || p2Y) {
|
||||
foundLine = line;
|
||||
p1.x = p1Y? lineStart.x : lineEnd.x
|
||||
p2.x = p2Y? lineEnd.x : lineStart.x
|
||||
break; // 매칭되는 라인을 찾으면 루프 종료
|
||||
}
|
||||
}
|
||||
|
||||
if (foundLine) {
|
||||
return {
|
||||
result: true,
|
||||
line: foundLine,
|
||||
p1: p1,
|
||||
p2: p2
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
result: false,
|
||||
line: null,
|
||||
pX: false,
|
||||
p2: false,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 스켈레톤 라인 배열에 새로운 라인을 추가합니다. (중복 방지)
|
||||
* @param id
|
||||
@ -1708,27 +1622,23 @@ function calculateSlope(p1, p2) {
|
||||
return (p2.y - p1.y) / (p2.x - p1.x);
|
||||
}
|
||||
|
||||
// Helper function to calculate slope of a line
|
||||
function getSlope(line) {
|
||||
const dx = line.endPoint.x - line.startPoint.x;
|
||||
// Avoid division by zero for vertical lines
|
||||
return dx === 0 ? Infinity : (line.endPoint.y - line.startPoint.y) / dx;
|
||||
}
|
||||
|
||||
// Check if two lines are parallel
|
||||
function areLinesParallel(line1, line2) {
|
||||
const slope1 = getSlope(line1);
|
||||
const slope2 = getSlope(line2);
|
||||
|
||||
// Both lines are vertical
|
||||
if (slope1 === Infinity && slope2 === Infinity) return true;
|
||||
|
||||
// Check if slopes are approximately equal
|
||||
const epsilon = 0.0001;
|
||||
return Math.abs(slope1 - slope2) < epsilon;
|
||||
}
|
||||
|
||||
// 두 직선이 평행한지 확인
|
||||
// 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
|
||||
*/
|
||||
@ -1881,59 +1791,70 @@ function isPointInsidePolygon2(point, roofLines) {
|
||||
return inside;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 점이 다각형 내부에 있는지 확인합니다 (Ray Casting 알고리즘 사용).
|
||||
* @param {Object} point - 확인할 점 {x, y}
|
||||
* @param {Array} polygonLines - 다각형을 구성하는 선분들의 배열
|
||||
* @returns {boolean} 점이 다각형 내부에 있으면 true
|
||||
*/
|
||||
function isPointInsidePolygon(point, polygonLines) {
|
||||
if (!polygonLines || polygonLines.length < 3) {
|
||||
return false;
|
||||
function isPointInsidePolygon(point, roofLines) {
|
||||
// 1. 먼저 경계선 위에 있는지 확인 (방향 무관)
|
||||
if (isOnBoundaryDirectionIndependent(point, roofLines)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let inside = false;
|
||||
// 2. 내부/외부 판단 (기존 알고리즘)
|
||||
let winding = 0;
|
||||
const x = point.x;
|
||||
const y = point.y;
|
||||
const n = polygonLines.length;
|
||||
|
||||
// 경계 박스(bounding box) 체크로 빠르게 필터링
|
||||
let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
|
||||
for (const line of polygonLines) {
|
||||
minX = Math.min(minX, line.x1, line.x2);
|
||||
maxX = Math.max(maxX, line.x1, line.x2);
|
||||
minY = Math.min(minY, line.y1, line.y2);
|
||||
maxY = Math.max(maxY, line.y1, line.y2);
|
||||
for (let i = 0; i < roofLines.length; i++) {
|
||||
const line = roofLines[i];
|
||||
const x1 = line.x1, y1 = line.y1;
|
||||
const x2 = line.x2, y2 = line.y2;
|
||||
|
||||
if (y1 <= y) {
|
||||
if (y2 > y) {
|
||||
const orientation = (x2 - x1) * (y - y1) - (x - x1) * (y2 - y1);
|
||||
if (orientation > 0) winding++;
|
||||
}
|
||||
} else {
|
||||
if (y2 <= y) {
|
||||
const orientation = (x2 - x1) * (y - y1) - (x - x1) * (y2 - y1);
|
||||
if (orientation < 0) winding--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 점이 경계 박스 밖에 있으면 바로 false 반환
|
||||
if (x < minX || x > maxX || y < minY || y > maxY) {
|
||||
return winding !== 0;
|
||||
}
|
||||
|
||||
// 방향에 무관한 경계선 검사
|
||||
function isOnBoundaryDirectionIndependent(point, roofLines) {
|
||||
const tolerance = 1e-10;
|
||||
|
||||
for (const line of roofLines) {
|
||||
if (isPointOnLineSegmentDirectionIndependent(point, line, tolerance)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 핵심: 방향에 무관한 선분 위 점 검사
|
||||
function isPointOnLineSegmentDirectionIndependent(point, line, tolerance) {
|
||||
const x = point.x, y = point.y;
|
||||
const x1 = line.x1, y1 = line.y1;
|
||||
const x2 = line.x2, y2 = line.y2;
|
||||
|
||||
// 방향에 무관하게 경계 상자 체크
|
||||
const minX = Math.min(x1, x2);
|
||||
const maxX = Math.max(x1, x2);
|
||||
const minY = Math.min(y1, y2);
|
||||
const maxY = Math.max(y1, y2);
|
||||
|
||||
if (x < minX - tolerance || x > maxX + tolerance ||
|
||||
y < minY - tolerance || y > maxY + tolerance) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ray Casting 알고리즘
|
||||
for (let i = 0, j = n - 1; i < n; j = i++) {
|
||||
const xi = polygonLines[i].x1;
|
||||
const yi = polygonLines[i].y1;
|
||||
const xj = polygonLines[j].x1;
|
||||
const yj = polygonLines[j].y1;
|
||||
|
||||
// 점이 정점 위에 있는 경우
|
||||
if ((xi === x && yi === y) || (xj === x && yj === y)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 수평선과 교차하는지 확인
|
||||
const intersect = ((yi > y) !== (yj > y)) &&
|
||||
(x < (xj - xi) * (y - yi) / (yj - yi) + xi);
|
||||
|
||||
if (intersect) {
|
||||
inside = !inside;
|
||||
}
|
||||
}
|
||||
|
||||
return inside;
|
||||
// 외적을 이용한 직선 위 판단 (방향 무관)
|
||||
const cross = (y - y1) * (x2 - x1) - (x - x1) * (y2 - y1);
|
||||
return Math.abs(cross) < tolerance;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2649,453 +2570,3 @@ function hasIntersectionWithOtherLines(point, skeletonLines, currentLine, tolera
|
||||
// 1개 이상의 다른 라인과 연결되어 있으면 교점으로 간주
|
||||
return connectionCount >= 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 대각선의 양 끝점을 roof.lines의 가장 가까운 접점까지 확장합니다.
|
||||
* @param {Object} p1 - 대각선의 시작점 {x, y}
|
||||
* @param {Object} p2 - 대각선의 끝점 {x, y}
|
||||
* @param {Array} roofLines - 지붕 경계선 배열
|
||||
* @returns {Object|null} 확장된 라인 {p1: {x, y}, p2: {x, y}} 또는 null
|
||||
*/
|
||||
function extendDiagonalToRoofBoundary(p1, p2, roofLines) {
|
||||
if (!roofLines || roofLines.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const tolerance = 0.5;
|
||||
const extendedLine = {
|
||||
p1: { ...p1 },
|
||||
p2: { ...p2 }
|
||||
};
|
||||
|
||||
// p1이 roof.lines 위에 있는지 확인
|
||||
const p1OnBoundary = roofLines.some(line => {
|
||||
return isPointOnLineSegment(p1,
|
||||
{ x: line.x1, y: line.y1 },
|
||||
{ x: line.x2, y: line.y2 },
|
||||
tolerance
|
||||
);
|
||||
});
|
||||
|
||||
// p2가 roof.lines 위에 있는지 확인
|
||||
const p2OnBoundary = roofLines.some(line => {
|
||||
return isPointOnLineSegment(p2,
|
||||
{ x: line.x1, y: line.y1 },
|
||||
{ x: line.x2, y: line.y2 },
|
||||
tolerance
|
||||
);
|
||||
});
|
||||
|
||||
// p1을 확장해야 하는 경우
|
||||
if (!p1OnBoundary) {
|
||||
const extendedP1 = findClosestBoundaryPoint(p1, p2, roofLines, 'backward');
|
||||
if (extendedP1) {
|
||||
extendedLine.p1 = extendedP1;
|
||||
}
|
||||
}
|
||||
|
||||
// p2를 확장해야 하는 경우
|
||||
if (!p2OnBoundary) {
|
||||
const extendedP2 = findClosestBoundaryPoint(p2, p1, roofLines, 'forward');
|
||||
if (extendedP2) {
|
||||
extendedLine.p2 = extendedP2;
|
||||
}
|
||||
}
|
||||
|
||||
return extendedLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* 점이 선분 위에 있는지 확인합니다 (허용 오차 포함).
|
||||
* @param {Object} point - 확인할 점 {x, y}
|
||||
* @param {Object} lineStart - 선분 시작점 {x, y}
|
||||
* @param {Object} lineEnd - 선분 끝점 {x, y}
|
||||
* @param {number} tolerance - 허용 오차
|
||||
* @returns {boolean} 선분 위에 있으면 true
|
||||
*/
|
||||
function isPointOnLineSegment(point, lineStart, lineEnd, tolerance = 0.5) {
|
||||
const dist = Math.sqrt(
|
||||
Math.pow(lineEnd.x - lineStart.x, 2) +
|
||||
Math.pow(lineEnd.y - lineStart.y, 2)
|
||||
);
|
||||
|
||||
const dist1 = Math.sqrt(
|
||||
Math.pow(point.x - lineStart.x, 2) +
|
||||
Math.pow(point.y - lineStart.y, 2)
|
||||
);
|
||||
|
||||
const dist2 = Math.sqrt(
|
||||
Math.pow(point.x - lineEnd.x, 2) +
|
||||
Math.pow(point.y - lineEnd.y, 2)
|
||||
);
|
||||
|
||||
return Math.abs(dist - (dist1 + dist2)) < tolerance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 한 점에서 다른 점 방향으로 연장하여 가장 가까운 roof.lines 교차점을 찾습니다.
|
||||
* @param {Object} fromPoint - 연장할 시작점
|
||||
* @param {Object} directionPoint - 방향을 결정하는 점
|
||||
* @param {Array} roofLines - 지붕 경계선 배열
|
||||
* @param {string} direction - 'forward' 또는 'backward'
|
||||
* @returns {Object|null} 교차점 {x, y} 또는 null
|
||||
*/
|
||||
function findClosestBoundaryPoint(fromPoint, directionPoint, roofLines, direction = 'forward') {
|
||||
// 방향 벡터 계산
|
||||
const dx = directionPoint.x - fromPoint.x;
|
||||
const dy = directionPoint.y - fromPoint.y;
|
||||
const length = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (length === 0) return null;
|
||||
|
||||
// 정규화된 방향 벡터
|
||||
const dirVec = {
|
||||
x: dx / length,
|
||||
y: dy / length
|
||||
};
|
||||
|
||||
// backward 방향인 경우 벡터를 반대로
|
||||
if (direction === 'backward') {
|
||||
dirVec.x = -dirVec.x;
|
||||
dirVec.y = -dirVec.y;
|
||||
}
|
||||
|
||||
let closestIntersection = null;
|
||||
let minDistance = Infinity;
|
||||
|
||||
// 모든 roof.lines와의 교차점 찾기
|
||||
for (const line of roofLines) {
|
||||
const lineP1 = { x: line.x1, y: line.y1 };
|
||||
const lineP2 = { x: line.x2, y: line.y2 };
|
||||
|
||||
// 무한 직선과의 교차점 계산
|
||||
const intersection = getRayIntersectionWithSegment(
|
||||
fromPoint,
|
||||
dirVec,
|
||||
lineP1,
|
||||
lineP2
|
||||
);
|
||||
|
||||
if (intersection && intersection.t > 0.1) { // 약간의 여유를 둠
|
||||
const distance = Math.sqrt(
|
||||
Math.pow(intersection.point.x - fromPoint.x, 2) +
|
||||
Math.pow(intersection.point.y - fromPoint.y, 2)
|
||||
);
|
||||
|
||||
if (distance < minDistance) {
|
||||
minDistance = distance;
|
||||
closestIntersection = intersection.point;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return closestIntersection;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 다각형이 roof.lines와 일치하는 변(edge)을 하나 이상 가지고 있는지 확인합니다.
|
||||
* @param {Array} polygonPoints - 다각형의 점들 배열 [{x, y}, ...]
|
||||
* @param {Array} roofLines - 지붕 경계선 배열
|
||||
* @returns {boolean} 일치하는 변이 하나라도 있으면 true, 하나도 없으면 false
|
||||
*/
|
||||
function polygonHasMatchingRoofLine(polygonPoints, roofLines) {
|
||||
if (!polygonPoints || polygonPoints.length < 2) return false;
|
||||
if (!roofLines || roofLines.length === 0) return false;
|
||||
|
||||
const tolerance = 0.5;
|
||||
|
||||
// 다각형의 각 변을 순회
|
||||
for (let i = 0; i < polygonPoints.length; i++) {
|
||||
const p1 = polygonPoints[i];
|
||||
const p2 = polygonPoints[(i + 1) % polygonPoints.length];
|
||||
|
||||
// 이 변이 roof.lines 중 하나와 일치하는지 확인
|
||||
for (const roofLine of roofLines) {
|
||||
const rp1 = { x: roofLine.x1, y: roofLine.y1 };
|
||||
const rp2 = { x: roofLine.x2, y: roofLine.y2 };
|
||||
|
||||
// 정방향 또는 역방향으로 일치하는지 확인
|
||||
const forwardMatch =
|
||||
isSamePoint(p1, rp1, tolerance) && isSamePoint(p2, rp2, tolerance);
|
||||
const backwardMatch =
|
||||
isSamePoint(p1, rp2, tolerance) && isSamePoint(p2, rp1, tolerance);
|
||||
|
||||
if (forwardMatch || backwardMatch) {
|
||||
console.log('Found matching edge - this polygon touches roof.lines:', {
|
||||
polygonEdge: { p1, p2 },
|
||||
roofLine: { p1: rp1, p2: rp2 }
|
||||
});
|
||||
return true; // 하나라도 일치하면 즉시 true 반환
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('No matching edges - this polygon is isolated from roof.lines');
|
||||
return false; // 모든 변을 확인했는데 일치하는 게 없음
|
||||
}
|
||||
|
||||
|
||||
function extendLineToRoofBoundary(clippedLine, roof, convertedPolygon) {
|
||||
|
||||
const isIsolated = !convertedPolygon.some(pp =>
|
||||
roof.points.some(rp =>
|
||||
Math.abs(pp.x - rp.x) < 0.5 && Math.abs(pp.y - rp.y) < 0.5
|
||||
));
|
||||
|
||||
// 대각선 라인 선택해서 가장 가까운 경계선 까지 확장
|
||||
const dx = Math.abs(clippedLine.p2.x - clippedLine.p1.x);
|
||||
const dy = Math.abs(clippedLine.p2.y - clippedLine.p1.y);
|
||||
const isDiagonal = dx > 0.5 && dy > 0.5;
|
||||
|
||||
if (isIsolated && isDiagonal) {
|
||||
// moveSelectLine의 방향 벡터 계산
|
||||
const selectDx = roof.moveSelectLine.endPoint.x - roof.moveSelectLine.startPoint.x;
|
||||
const selectDy = roof.moveSelectLine.endPoint.y - roof.moveSelectLine.startPoint.y;
|
||||
const selectLength = Math.sqrt(selectDx * selectDx + selectDy * selectDy);
|
||||
const selectDir = { x: selectDx / selectLength, y: selectDy / selectLength };
|
||||
|
||||
// moveSelectLine의 중점
|
||||
const selectMidX = (roof.moveSelectLine.startPoint.x + roof.moveSelectLine.endPoint.x) / 2;
|
||||
const selectMidY = (roof.moveSelectLine.startPoint.y + roof.moveSelectLine.endPoint.y) / 2;
|
||||
|
||||
// 이동 방향 확인
|
||||
const moveDirection = roof.moveDirect; // 'up', 'down', 'left', 'right', 'in', 'out'
|
||||
const movePosiotn = roof.movePosition
|
||||
// moveSelectLine과 평행한 경계선 찾기
|
||||
let closestParallelLine = null;
|
||||
let minDistToLine = Infinity;
|
||||
|
||||
// 대각선의 중점
|
||||
const midX = (clippedLine.p1.x + clippedLine.p2.x) / 2;
|
||||
const midY = (clippedLine.p1.y + clippedLine.p2.y) / 2;
|
||||
|
||||
for (const roofLine of roof.lines) {
|
||||
const lineP1 = { x: roofLine.x1, y: roofLine.y1 };
|
||||
const lineP2 = { x: roofLine.x2, y: roofLine.y2 };
|
||||
|
||||
// 경계선의 방향 벡터 계산
|
||||
const lineDx = lineP2.x - lineP1.x;
|
||||
const lineDy = lineP2.y - lineP1.y;
|
||||
const lineLength = Math.sqrt(lineDx * lineDx + lineDy * lineDy);
|
||||
const lineDir = { x: lineDx / lineLength, y: lineDy / lineLength };
|
||||
|
||||
// moveSelectLine과 평행한지 확인
|
||||
const dotProduct = Math.abs(selectDir.x * lineDir.x + selectDir.y * lineDir.y);
|
||||
const isParallel = dotProduct > 0.95;
|
||||
|
||||
if (!isParallel) continue;
|
||||
|
||||
// 대각선 중점에서 경계선까지의 거리
|
||||
const lineMidX = (lineP1.x + lineP2.x) / 2;
|
||||
const lineMidY = (lineP1.y + lineP2.y) / 2;
|
||||
const dist = Math.sqrt(
|
||||
Math.pow(midX - lineMidX, 2) +
|
||||
Math.pow(midY - lineMidY, 2)
|
||||
);
|
||||
|
||||
if (dist < minDistToLine) {
|
||||
minDistToLine = dist;
|
||||
closestParallelLine = roofLine;
|
||||
}
|
||||
}
|
||||
|
||||
if (closestParallelLine) {
|
||||
const lineP1 = { x: closestParallelLine.x1, y: closestParallelLine.y1 };
|
||||
const lineP2 = { x: closestParallelLine.x2, y: closestParallelLine.y2 };
|
||||
const lineMidX = (lineP1.x + lineP2.x) / 2;
|
||||
const lineMidY = (lineP1.y + lineP2.y) / 2;
|
||||
|
||||
// 이동 방향에 따라 확장할 끝점 결정
|
||||
let shouldExtendP1 = false;
|
||||
|
||||
switch (movePosiotn) {
|
||||
case 'down':
|
||||
// moveSelectLine이 아래/밖으로 이동 -> 아래/밖에 있는 점 확장
|
||||
//shouldExtendP1 = (lineMidY > selectMidY) ? (clippedLine.p1.y > clippedLine.p2.y) : (clippedLine.p1.y < clippedLine.p2.y);
|
||||
break;
|
||||
case 'up':
|
||||
// moveSelectLine이 위/안으로 이동 -> 위/안에 있는 점 확장
|
||||
//shouldExtendP1 = (lineMidY < selectMidY) ? (clippedLine.p1.y < clippedLine.p2.y) : (clippedLine.p1.y > clippedLine.p2.y);
|
||||
break;
|
||||
case 'left':
|
||||
// moveSelectLine이 왼쪽으로 이동 -> 왼쪽에 있는 점 확장
|
||||
//shouldExtendP1 = (lineMidX < selectMidX) ? (clippedLine.p1.x < clippedLine.p2.x) : (clippedLine.p1.x > clippedLine.p2.x);
|
||||
break;
|
||||
case 'right':
|
||||
// moveSelectLine이 오른쪽으로 이동 -> 오른쪽에 있는 점 확장
|
||||
//shouldExtendP1 = (lineMidX > selectMidX) ? (clippedLine.p1.x > clippedLine.p2.x) : (clippedLine.p1.x < clippedLine.p2.x);
|
||||
break;
|
||||
}
|
||||
|
||||
const endPoint = shouldExtendP1 ? clippedLine.p1 : clippedLine.p2;
|
||||
const startPoint = shouldExtendP1 ? clippedLine.p2 : clippedLine.p1;
|
||||
|
||||
// 대각선 방향 벡터
|
||||
const dirX = endPoint.x - startPoint.x;
|
||||
const dirY = endPoint.y - startPoint.y;
|
||||
|
||||
// endPoint에서 방향으로 연장
|
||||
const farEnd = {
|
||||
x: endPoint.x + dirX * 10000,
|
||||
y: endPoint.y + dirY * 10000
|
||||
};
|
||||
|
||||
const intersection = getLineIntersection(startPoint, farEnd, lineP1, lineP2);
|
||||
|
||||
if (intersection) {
|
||||
// endPoint를 교차점으로 확장
|
||||
if (shouldExtendP1) {
|
||||
clippedLine.p1 = intersection;
|
||||
} else {
|
||||
clippedLine.p2 = intersection;
|
||||
}
|
||||
console.log('고립된 다각형 대각선 확장됨 (방향 고려):', clippedLine);
|
||||
return clippedLine
|
||||
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
function getIntersectionDetails(p1, p2, line) {
|
||||
const { startPoint: lineStart, endPoint: lineEnd } = line;
|
||||
const lineIsVertical = Math.abs(lineEnd.x - lineStart.x) < 0.0001;
|
||||
const lineIsHorizontal = Math.abs(lineEnd.y - lineStart.y) < 0.0001;
|
||||
|
||||
// Calculate intersection point
|
||||
const d1 = (p2.x - p1.x) * (lineStart.y - p1.y) - (p2.y - p1.y) * (lineStart.x - p1.x);
|
||||
const d2 = (p2.x - p1.x) * (lineEnd.y - p1.y) - (p2.y - p1.y) * (lineEnd.x - p1.x);
|
||||
const d3 = (lineEnd.x - lineStart.x) * (p1.y - lineStart.y) - (lineEnd.y - lineStart.y) * (p1.x - lineStart.x);
|
||||
const d4 = (lineEnd.x - lineStart.x) * (p2.y - lineStart.y) - (lineEnd.y - lineStart.y) * (p2.x - lineStart.x);
|
||||
|
||||
if ((d1 * d2 < 0) && (d3 * d4 < 0)) {
|
||||
// Calculate intersection point
|
||||
const t = d3 / (d3 - d4);
|
||||
const ix = p1.x + t * (p2.x - p1.x);
|
||||
const iy = p1.y + t * (p2.y - p1.y);
|
||||
|
||||
// Calculate distances to determine which point is closer
|
||||
const distToP1 = Math.hypot(ix - p1.x, iy - p1.y);
|
||||
const distToP2 = Math.hypot(ix - p2.x, iy - p2.y);
|
||||
|
||||
// Determine if intersection is closer to start or end of the line segment
|
||||
const distToLineStart = Math.hypot(ix - lineStart.x, iy - lineStart.y);
|
||||
const distToLineEnd = Math.hypot(ix - lineEnd.x, iy - lineEnd.y);
|
||||
|
||||
return {
|
||||
intersects: true,
|
||||
point: { x: ix, y: iy },
|
||||
// Which point of the segment (p1 or p2) is closer to intersection
|
||||
segmentPoint: distToP1 < distToP2 ? 'p1' : 'p2',
|
||||
// Which point of the moveSelectLine is closer to intersection
|
||||
linePoint: distToLineStart < distToLineEnd ? 'start' : 'end',
|
||||
// Line orientation
|
||||
lineOrientation: lineIsVertical ? 'vertical' : lineIsHorizontal ? 'horizontal' : 'diagonal'
|
||||
};
|
||||
}
|
||||
|
||||
return { intersects: false };
|
||||
}
|
||||
|
||||
function findClosestParallelLine(intersection, roofLines, orientation) {
|
||||
if (!intersection || !roofLines?.length) return null;
|
||||
|
||||
let closest = null;
|
||||
let minDist = Infinity;
|
||||
|
||||
for (const line of roofLines) {
|
||||
const p1 = line.x1 !== undefined ? {x: line.x1, y: line.y1} : line.startPoint;
|
||||
const p2 = line.x2 !== undefined ? {x: line.x2, y: line.y2} : line.endPoint;
|
||||
|
||||
// Check line orientation
|
||||
const dx = Math.abs(p2.x - p1.x);
|
||||
const dy = Math.abs(p2.y - p1.y);
|
||||
const lineOrientation = dx < 0.0001 ? 'vertical' :
|
||||
dy < 0.0001 ? 'horizontal' : 'diagonal';
|
||||
|
||||
if (lineOrientation !== orientation) continue;
|
||||
|
||||
// Calculate distance to line
|
||||
const dist = Math.min(
|
||||
Math.hypot(p1.x - intersection.x, p1.y - intersection.y),
|
||||
Math.hypot(p2.x - intersection.x, p2.y - intersection.y)
|
||||
);
|
||||
|
||||
if (dist < minDist) {
|
||||
minDist = dist;
|
||||
closest = { startPoint: p1, endPoint: p2 };
|
||||
}
|
||||
}
|
||||
|
||||
return closest;
|
||||
}
|
||||
|
||||
function findClosestRoofLine(point, roofLines) {
|
||||
let closestLine = null;
|
||||
let minDistance = Infinity;
|
||||
let roofLineIndex = 0;
|
||||
let interPoint = null;
|
||||
|
||||
roofLines.forEach((roofLine, index) => {
|
||||
const lineP1 = roofLine.startPoint;
|
||||
const lineP2 = roofLine.endPoint;
|
||||
|
||||
// 점에서 선분까지의 최단 거리 계산
|
||||
const distance = pointToLineDistance(point, lineP1, lineP2);
|
||||
|
||||
// 점에서 수직으로 내린 교점 계산
|
||||
const intersection = getProjectionPoint(point, {
|
||||
x1: lineP1.x,
|
||||
y1: lineP1.y,
|
||||
x2: lineP2.x,
|
||||
y2: lineP2.y
|
||||
});
|
||||
|
||||
if (distance < minDistance) {
|
||||
minDistance = distance;
|
||||
closestLine = roofLine;
|
||||
roofLineIndex = index
|
||||
interPoint = intersection;
|
||||
}
|
||||
});
|
||||
|
||||
return { line: closestLine, distance: minDistance, index: roofLineIndex, intersectionPoint: interPoint };
|
||||
}
|
||||
|
||||
// 점에서 선분까지의 최단 거리를 계산하는 도우미 함수
|
||||
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;
|
||||
|
||||
const dot = A * C + B * D;
|
||||
const lenSq = C * C + D * D;
|
||||
let param = -1;
|
||||
|
||||
if (lenSq !== 0) {
|
||||
param = dot / lenSq;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user