Merge pull request '[1751] 기와 형태에서 처마 돌출 치수를 변경했을 때의 동작' (#723) from dev into dev-deploy
Reviewed-on: #723
This commit is contained in:
commit
a22137210e
@ -149,9 +149,9 @@ export default function CircuitTrestleSetting({ id }) {
|
|||||||
return {
|
return {
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
width: Math.floor(width),
|
width: Math.round(width),
|
||||||
height: Math.floor(height),
|
height: Math.round(height),
|
||||||
area: Math.floor(width) * Math.floor(height),
|
area: Math.round(width) * Math.round(height),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -227,7 +227,7 @@ export default function CircuitTrestleSetting({ id }) {
|
|||||||
// 인접 모듈 여부 확인
|
// 인접 모듈 여부 확인
|
||||||
const isAdjacent = (current, target) => {
|
const isAdjacent = (current, target) => {
|
||||||
const gap = getGap(current, target)
|
const gap = getGap(current, target)
|
||||||
return gap >= 0 && gap <= primaryInterval + 1
|
return gap >= 0 && gap <= primaryInterval + 2
|
||||||
}
|
}
|
||||||
|
|
||||||
// 정렬된 모듈 순회
|
// 정렬된 모듈 순회
|
||||||
@ -238,7 +238,7 @@ export default function CircuitTrestleSetting({ id }) {
|
|||||||
const directTargets = findTargetModules(current, secondaryInterval)
|
const directTargets = findTargetModules(current, secondaryInterval)
|
||||||
const closestTarget = getClosestTarget(directTargets)
|
const closestTarget = getClosestTarget(directTargets)
|
||||||
|
|
||||||
if (closestTarget && isAdjacent(current, closestTarget) && closestTarget.area > current.area) {
|
if (closestTarget && isAdjacent(current, closestTarget) && closestTarget.area - current.area > 1) {
|
||||||
return false // 검증 실패
|
return false // 검증 실패
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -264,7 +264,7 @@ export default function CircuitTrestleSetting({ id }) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const closestHalf = getClosestTarget(findHalfTarget)
|
const closestHalf = getClosestTarget(findHalfTarget)
|
||||||
if (closestHalf && isAdjacent(current, closestHalf) && closestHalf.area > current.area) {
|
if (closestHalf && isAdjacent(current, closestHalf) && closestHalf.area - current.area > 1) {
|
||||||
return false // 검증 실패
|
return false // 검증 실패
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -324,7 +324,7 @@ const movingLineFromSkeleton = (roofId, canvas) => {
|
|||||||
|
|
||||||
|
|
||||||
const cleaned = removeNonOrthogonalPoints(newPoints);
|
const cleaned = removeNonOrthogonalPoints(newPoints);
|
||||||
console.log(cleaned); // 결과: 4개의 점만 남음 (P1, P2, P3, P4)
|
// console.log(cleaned)
|
||||||
|
|
||||||
return cleaned;
|
return cleaned;
|
||||||
}
|
}
|
||||||
@ -380,127 +380,98 @@ export const skeletonBuilder = (roofId, canvas, textMode) => {
|
|||||||
const hipLines = canvas.getObjects().filter((object) => object.name === 'hip' && 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 ridgeLines = canvas.getObjects().filter((object) => object.name === 'ridge' && object.parentId === roofId) || []
|
||||||
|
|
||||||
//const skeletonLines = [];
|
// 1. 지붕 폴리곤 유효성 검사
|
||||||
// 1. 지붕 폴리곤 좌표 전처리
|
|
||||||
const coordinates = preprocessPolygonCoordinates(roof.points)
|
const coordinates = preprocessPolygonCoordinates(roof.points)
|
||||||
if (coordinates.length < 3) {
|
if (coordinates.length < 3) {
|
||||||
console.warn('Polygon has less than 3 unique points. Cannot generate skeleton.')
|
console.warn('Polygon has less than 3 unique points. Cannot generate skeleton.')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const moveFlowLine = roof.moveFlowLine || 0 // Provide a default value
|
const moveFlowLine = roof.moveFlowLine || 0
|
||||||
const moveUpDown = roof.moveUpDown || 0 // Provide a default value
|
const moveUpDown = roof.moveUpDown || 0
|
||||||
|
|
||||||
|
// 닫힌 폴리곤(첫점 = 끝점)인 경우 마지막 중복 점 제거
|
||||||
const isClosedPolygon = (points) =>
|
const isClosedPolygon = (points) =>
|
||||||
points.length > 1 && isSamePoint(points[0], points[points.length - 1])
|
points.length > 1 && isSamePoint(points[0], points[points.length - 1])
|
||||||
|
|
||||||
|
// roofLinePoints : drawRoofPolygon에서 baseLine + 각 변의 offset으로 계산된 실제 처마 외곽선
|
||||||
|
// orderedBaseLinePoints: 외벽선(baseLine) 꼭짓점을 연결 순서대로 정렬
|
||||||
|
const roofLinePoints = isClosedPolygon(roof.points) ? roof.points.slice(0, -1) : roof.points
|
||||||
const orderedBaseLinePoints = isClosedPolygon(baseLinePoints) ? baseLinePoints.slice(0, -1) : baseLinePoints
|
const orderedBaseLinePoints = isClosedPolygon(baseLinePoints) ? baseLinePoints.slice(0, -1) : baseLinePoints
|
||||||
|
|
||||||
// baseLine 폴리곤의 중심 계산 (offset 방향 결정용)
|
// 2. 각 baseLine 꼭짓점 → 대응하는 roofLine 꼭짓점 방향벡터 계산
|
||||||
|
// 각 변의 offset이 달라도 방향은 항상 baseLine → roofLine (바깥쪽)
|
||||||
|
const contactData = orderedBaseLinePoints.map((point, index) => {
|
||||||
|
const roofPoint = roofLinePoints[index]
|
||||||
|
if (!roofPoint) return { dx: 0, dy: 0, signDx: 0, signDy: 0 }
|
||||||
|
|
||||||
|
const dx = roofPoint.x - point.x
|
||||||
|
const dy = roofPoint.y - point.y
|
||||||
|
return { dx, dy, signDx: Math.sign(dx), signDy: Math.sign(dy) }
|
||||||
|
})
|
||||||
|
|
||||||
|
// 3. maxStep: 모든 꼭짓점 중 가장 큰 45도 대각 step 값
|
||||||
|
// - 각 꼭짓점의 min(|dx|, |dy|) = 해당 꼭짓점에서 45도 방향으로 확장 가능한 거리
|
||||||
|
// - 전체에서 max를 취해 SkeletonBuilder 입력 다각형이 충분히 확장되도록 보장
|
||||||
|
// - 모든 꼭짓점에 동일한 maxStep을 적용하므로 각 변의 offset이 달라도
|
||||||
|
// SkeletonBuilder 입력은 항상 대칭 다각형이 되어 스켈레톤이 안정적으로 생성됨
|
||||||
|
const maxStep = contactData.reduce((max, data) => {
|
||||||
|
return Math.max(max, Math.min(Math.abs(data.dx), Math.abs(data.dy)))
|
||||||
|
}, 0)
|
||||||
|
|
||||||
|
// baseLine 폴리곤 중심점 (dx 또는 dy가 0인 꼭짓점의 확장 방향 보정에 사용)
|
||||||
const centroid = orderedBaseLinePoints.reduce((acc, p) => {
|
const centroid = orderedBaseLinePoints.reduce((acc, p) => {
|
||||||
acc.x += p.x / orderedBaseLinePoints.length
|
acc.x += p.x / orderedBaseLinePoints.length
|
||||||
acc.y += p.y / orderedBaseLinePoints.length
|
acc.y += p.y / orderedBaseLinePoints.length
|
||||||
return acc
|
return acc
|
||||||
}, { x: 0, y: 0 })
|
}, { x: 0, y: 0 })
|
||||||
|
|
||||||
// baseLine을 offset만큼 바깥으로 평행이동
|
// 4. 모든 꼭짓점을 동일한 maxStep으로 45도 대각 방향 확장
|
||||||
const offsetLine = (line, index) => {
|
// - dx 또는 dy가 0인 경우(한 축 이동만 있는 꼭짓점): centroid 기준 바깥 방향으로 보정
|
||||||
const sp = line.startPoint, ep = line.endPoint
|
let roofLineContactPoints = orderedBaseLinePoints.map((point, index) => {
|
||||||
let offset = line.attributes?.offset ?? 0
|
const data = contactData[index]
|
||||||
|
|
||||||
// 모든 offset이 동일할 때 수치적 안정성을 위해 약간의 변화 추가
|
let signDx = data.signDx
|
||||||
if (baseLines.every(l => (l.attributes?.offset ?? 0) === offset)) {
|
let signDy = data.signDy
|
||||||
offset += index * 0.01 // 각 라인에 약간의 차이 추가
|
if (signDx === 0) signDx = point.x >= centroid.x ? 1 : -1
|
||||||
}
|
if (signDy === 0) signDy = point.y >= centroid.y ? 1 : -1
|
||||||
|
|
||||||
const edx = ep.x - sp.x, edy = ep.y - sp.y
|
|
||||||
const len = Math.hypot(edx, edy)
|
|
||||||
if (len === 0) return { sp: { ...sp }, ep: { ...ep } }
|
|
||||||
|
|
||||||
// 수직 벡터 계산 (항상 바깥 방향)
|
|
||||||
let nx = -edy / len, ny = edx / len
|
|
||||||
|
|
||||||
// 방향 결정 로직 제거 - 항상 바깥으로 확장
|
|
||||||
// if (nx * (centroid.x - mid.x) + ny * (centroid.y - mid.y) > 0) { nx = -nx; ny = -ny }
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
sp: { x: sp.x + nx * offset, y: sp.y + ny * offset },
|
x: point.x + signDx * maxStep,
|
||||||
ep: { x: ep.x + nx * offset, y: ep.y + ny * offset }
|
y: point.y + signDy * maxStep
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 두 직선의 교차점
|
|
||||||
const lineIntersect = (a1, a2, b1, b2) => {
|
|
||||||
const denom = (a1.x - a2.x) * (b1.y - b2.y) - (a1.y - a2.y) * (b1.x - b2.x)
|
|
||||||
if (Math.abs(denom) < 0.001) return null
|
|
||||||
const t = ((a1.x - b1.x) * (b1.y - b2.y) - (a1.y - b1.y) * (b1.x - b2.x)) / denom
|
|
||||||
return { x: a1.x + t * (a2.x - a1.x), y: a1.y + t * (a2.y - a1.y) }
|
|
||||||
}
|
|
||||||
|
|
||||||
// 각 꼭짓점에서 인접 두 baseLine의 offset 교차점으로 확장 좌표 계산
|
|
||||||
let changRoofLinePoints = orderedBaseLinePoints.map((point) => {
|
|
||||||
const adjLines = baseLines.filter(line =>
|
|
||||||
isSamePoint(point, line.startPoint) || isSamePoint(point, line.endPoint)
|
|
||||||
)
|
|
||||||
if (adjLines.length < 2) return { ...point }
|
|
||||||
const idx1 = baseLines.indexOf(adjLines[0])
|
|
||||||
const idx2 = baseLines.indexOf(adjLines[1])
|
|
||||||
const oL1 = offsetLine(adjLines[0], idx1), oL2 = offsetLine(adjLines[1], idx2)
|
|
||||||
return lineIntersect(oL1.sp, oL1.ep, oL2.sp, oL2.ep) || { ...point }
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// 중복 좌표 및 일직선 위의 불필요한 점 제거 (L자 확장 시 사각형으로 단순화)
|
// 5. changRoofLinePoints: SkeletonBuilder에 입력할 최종 다각형
|
||||||
// const simplifyPolygon = (pts) => {
|
// - roofLineContactPoints 방향으로 maxContactDistance(= √2 × maxStep) 만큼 확장
|
||||||
// let result = [...pts]
|
// - 45도 대각 방향(signDx, signDy 각 ±1)이므로 실제 x·y 이동량은 각 축으로 maxStep
|
||||||
|
const maxContactDistance = Math.hypot(maxStep, maxStep)
|
||||||
|
|
||||||
// // 복잡한 형태 보호를 위해 최소 6개 점 유지
|
let changRoofLinePoints = orderedBaseLinePoints.map((point, index) => {
|
||||||
// if (result.length <= 6) return result
|
const contactPoint = roofLineContactPoints[index]
|
||||||
|
if (!contactPoint) return point
|
||||||
|
|
||||||
// let changed = true
|
const dx = contactPoint.x - point.x
|
||||||
// while (changed) {
|
const dy = contactPoint.y - point.y
|
||||||
// changed = false
|
const len = Math.hypot(dx, dy)
|
||||||
// for (let i = result.length - 1; i >= 0; i--) {
|
if (len === 0 || maxContactDistance === 0) return point
|
||||||
// const next = result[(i + 1) % result.length]
|
|
||||||
// if (Math.abs(result[i].x - next.x) < 1.0 && Math.abs(result[i].y - next.y) < 1.0) {
|
|
||||||
// result.splice(i, 1)
|
|
||||||
// changed = true
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// // 최소 6개 점 유지 확인
|
|
||||||
// if (result.length <= 6) break
|
|
||||||
|
|
||||||
// for (let i = result.length - 1; i >= 0 && result.length > 3; i--) {
|
return {
|
||||||
// const prev = result[(i - 1 + result.length) % result.length]
|
x: point.x + (dx / len) * maxContactDistance,
|
||||||
// const curr = result[i]
|
y: point.y + (dy / len) * maxContactDistance
|
||||||
// const next = result[(i + 1) % result.length]
|
}
|
||||||
// const cross = (curr.x - prev.x) * (next.y - prev.y) - (curr.y - prev.y) * (next.x - prev.x)
|
})
|
||||||
// // 복잡한 형태 보호를 위해 임계값 대폭 증가
|
|
||||||
// if (Math.abs(cross) < 50) {
|
|
||||||
// result.splice(i, 1)
|
|
||||||
// changed = true
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return result
|
|
||||||
// }
|
|
||||||
// changRoofLinePoints = simplifyPolygon(changRoofLinePoints)
|
|
||||||
|
|
||||||
let roofLineContactPoints = [...changRoofLinePoints]
|
// 6. 마루이동(용마루 위치 수동 조정)이 설정된 경우 movingLineFromSkeleton 결과로 대체
|
||||||
|
|
||||||
console.log('baseLinePoints:', orderedBaseLinePoints)
|
|
||||||
console.log('changRoofLinePoints:', changRoofLinePoints)
|
|
||||||
|
|
||||||
//마루이동
|
|
||||||
if (moveFlowLine !== 0 || moveUpDown !== 0) {
|
if (moveFlowLine !== 0 || moveUpDown !== 0) {
|
||||||
const movedPoints = movingLineFromSkeleton(roofId, canvas)
|
const movedPoints = movingLineFromSkeleton(roofId, canvas)
|
||||||
roofLineContactPoints = movedPoints
|
roofLineContactPoints = movedPoints
|
||||||
changRoofLinePoints = movedPoints
|
changRoofLinePoints = movedPoints
|
||||||
}
|
}
|
||||||
|
|
||||||
// changRoofLinePoints 좌표를 roof.skeletonPoints에 저장 (원본 roof.points는 유지)
|
// changRoofLinePoints를 roof.skeletonPoints에 저장 (roof.points 원본은 유지)
|
||||||
roof.skeletonPoints = changRoofLinePoints.map(p => ({ x: p.x, y: p.y }))
|
roof.skeletonPoints = changRoofLinePoints.map(p => ({ x: p.x, y: p.y }))
|
||||||
|
|
||||||
console.log('points:', changRoofLinePoints)
|
|
||||||
const geoJSONPolygon = toGeoJSON(changRoofLinePoints)
|
const geoJSONPolygon = toGeoJSON(changRoofLinePoints)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -511,7 +482,7 @@ export const skeletonBuilder = (roofId, canvas, textMode) => {
|
|||||||
// 스켈레톤 데이터를 기반으로 내부선 생성
|
// 스켈레톤 데이터를 기반으로 내부선 생성
|
||||||
roof.innerLines = roof.innerLines || []
|
roof.innerLines = roof.innerLines || []
|
||||||
roof.innerLines = createInnerLinesFromSkeleton(roofId, canvas, skeleton, textMode)
|
roof.innerLines = createInnerLinesFromSkeleton(roofId, canvas, skeleton, textMode)
|
||||||
console.log("roofInnerLines:::", roof.innerLines);
|
//console.log("roofInnerLines:::", roof.innerLines);
|
||||||
// 캔버스에 스켈레톤 상태 저장
|
// 캔버스에 스켈레톤 상태 저장
|
||||||
if (!canvas.skeletonStates) {
|
if (!canvas.skeletonStates) {
|
||||||
canvas.skeletonStates = {}
|
canvas.skeletonStates = {}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user