[1840]생성되지 않은 팔작지붕 도면 사례 #751
@ -253,13 +253,12 @@ export default class SkeletonBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static CorrectBisectorDirection(bisector: LineParametric2d, beginNextVertex: Vertex, endPreviousVertex: Vertex, beginEdge: Edge, endEdge: Edge) {
|
private static CorrectBisectorDirection(bisector: LineParametric2d, beginNextVertex: Vertex, endPreviousVertex: Vertex, beginEdge: Edge, endEdge: Edge) {
|
||||||
const beginEdge2 = beginNextVertex.PreviousEdge;
|
// Use the vertex's actual edges — chain edges can become stale
|
||||||
const endEdge2 = endPreviousVertex.NextEdge;
|
// when previous iterations of MultiSplitEvent modify the LAV
|
||||||
|
const actualBeginEdge = beginNextVertex.PreviousEdge;
|
||||||
|
const actualEndEdge = endPreviousVertex.NextEdge;
|
||||||
|
|
||||||
if (beginEdge !== beginEdge2 || endEdge !== endEdge2)
|
if (actualBeginEdge.Norm.Dot(actualEndEdge.Norm) < -0.97) {
|
||||||
throw new Error();
|
|
||||||
|
|
||||||
if (beginEdge.Norm.Dot(endEdge.Norm) < -0.97) {
|
|
||||||
const n1 = PrimitiveUtils.FromTo(endPreviousVertex.Point, bisector.A).Normalized();
|
const n1 = PrimitiveUtils.FromTo(endPreviousVertex.Point, bisector.A).Normalized();
|
||||||
const n2 = PrimitiveUtils.FromTo(bisector.A, beginNextVertex.Point).Normalized();
|
const n2 = PrimitiveUtils.FromTo(bisector.A, beginNextVertex.Point).Normalized();
|
||||||
const bisectorPrediction = this.CalcVectorBisector(n1, n2);
|
const bisectorPrediction = this.CalcVectorBisector(n1, n2);
|
||||||
@ -887,7 +886,7 @@ export default class SkeletonBuilder {
|
|||||||
private static GetEdgeInLav(lav: CircularList<Vertex>, oppositeEdge: Edge): Vertex {
|
private static GetEdgeInLav(lav: CircularList<Vertex>, oppositeEdge: Edge): Vertex {
|
||||||
for (const node of lav.Iterate())
|
for (const node of lav.Iterate())
|
||||||
if (oppositeEdge === node.PreviousEdge ||
|
if (oppositeEdge === node.PreviousEdge ||
|
||||||
oppositeEdge === node.Previous.Next)
|
oppositeEdge === (node.Previous as Vertex).NextEdge)
|
||||||
return node;
|
return node;
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@ -16,6 +16,39 @@ import wallLine from '@/components/floor-plan/modal/wallLineOffset/type/WallLine
|
|||||||
*/
|
*/
|
||||||
const EPSILON = 0.1
|
const EPSILON = 0.1
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 오목(concave) 폴리곤인지 판별합니다.
|
||||||
|
* cross product의 부호가 혼재하면 오목 폴리곤입니다.
|
||||||
|
*/
|
||||||
|
const isConcavePolygon = (points) => {
|
||||||
|
if (!points || points.length < 4) return false
|
||||||
|
let positive = 0, negative = 0
|
||||||
|
for (let i = 0; i < points.length; i++) {
|
||||||
|
const prev = points[(i - 1 + points.length) % points.length]
|
||||||
|
const curr = points[i]
|
||||||
|
const next = points[(i + 1) % points.length]
|
||||||
|
const cross = (curr.x - prev.x) * (next.y - curr.y) - (curr.y - prev.y) * (next.x - curr.x)
|
||||||
|
if (cross > 0) positive++
|
||||||
|
else if (cross < 0) negative++
|
||||||
|
}
|
||||||
|
return positive > 0 && negative > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 오목 폴리곤에만 미세한 perturbation을 적용하여 동시 이벤트(MultiSplitEvent)를 방지합니다.
|
||||||
|
* 대칭적 변 길이(예: 45.5, 45.5)가 동일 거리의 이벤트를 만들어 LAV 불일치 오류를 유발하므로
|
||||||
|
* 각 꼭짓점에 인덱스 기반 미세 오프셋을 적용하면 이벤트가 개별 SplitEvent로 처리됩니다.
|
||||||
|
* 볼록 폴리곤은 이 문제가 없으므로 원본을 그대로 반환합니다.
|
||||||
|
*/
|
||||||
|
const perturbPolygonPoints = (points, eps = 1e-4) => {
|
||||||
|
if (!points || points.length < 3) return points
|
||||||
|
if (!isConcavePolygon(points)) return points
|
||||||
|
return points.map((p, i) => ({
|
||||||
|
x: p.x + (i + 1) * eps,
|
||||||
|
y: p.y + (i + 1) * eps
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
export const drawSkeletonRidgeRoof = (roofId, canvas, textMode) => {
|
export const drawSkeletonRidgeRoof = (roofId, canvas, textMode) => {
|
||||||
|
|
||||||
// 2. 스켈레톤 생성 및 그리기
|
// 2. 스켈레톤 생성 및 그리기
|
||||||
@ -503,11 +536,13 @@ export const skeletonBuilder = (roofId, canvas, textMode) => {
|
|||||||
// 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 }))
|
||||||
|
|
||||||
const geoJSONPolygon = toGeoJSON(changRoofLinePoints)
|
const perturbedPoints = perturbPolygonPoints(changRoofLinePoints)
|
||||||
|
const geoJSONPolygon = toGeoJSON(perturbedPoints)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// SkeletonBuilder는 닫히지 않은 폴리곤을 기대하므로 마지막 점 제거
|
// SkeletonBuilder는 닫히지 않은 폴리곤을 기대하므로 마지막 점 제거
|
||||||
geoJSONPolygon.pop()
|
geoJSONPolygon.pop()
|
||||||
|
console.log('[SkeletonBuilder] geoJSONPolygon:', JSON.stringify(geoJSONPolygon, null, 2))
|
||||||
const skeleton = SkeletonBuilder.BuildFromGeoJSON([[geoJSONPolygon]])
|
const skeleton = SkeletonBuilder.BuildFromGeoJSON([[geoJSONPolygon]])
|
||||||
|
|
||||||
// 스켈레톤 데이터를 기반으로 내부선 생성
|
// 스켈레톤 데이터를 기반으로 내부선 생성
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user