6254 lines
260 KiB
JavaScript
6254 lines
260 KiB
JavaScript
import { fabric } from 'fabric'
|
|
import { QLine } from '@/components/fabric/QLine'
|
|
import { getAdjacent, getDegreeByChon, isPointOnLine, isPointOnLineNew } from '@/util/canvas-util'
|
|
|
|
import { QPolygon } from '@/components/fabric/QPolygon'
|
|
import { LINE_TYPE, POLYGON_TYPE } from '@/common/common'
|
|
import Big from 'big.js'
|
|
|
|
const TWO_PI = Math.PI * 2
|
|
const EPSILON = 1e-10 //좌표계산 시 최소 차이값
|
|
|
|
export const defineQPolygon = () => {
|
|
fabric.QPolygon.fromObject = function (object, callback) {
|
|
fabric.Object._fromObject('QPolygon', object, callback, 'points')
|
|
}
|
|
}
|
|
|
|
/**
|
|
* point1에서 point2를 잇는 방향의 각도를 구한다.
|
|
* @param point1
|
|
* @param point2
|
|
* @returns {number}
|
|
*/
|
|
export const calculateAngle = (point1, point2) => {
|
|
const deltaX = Big(point2.x ?? 0)
|
|
.minus(point1.x ?? 0)
|
|
.toNumber()
|
|
const deltaY = Big(point2.y ?? 0)
|
|
.minus(point1.y ?? 0)
|
|
.toNumber()
|
|
const angleInRadians = Math.atan2(deltaY, deltaX)
|
|
return Big(angleInRadians * (180 / Math.PI))
|
|
.round()
|
|
.toNumber()
|
|
}
|
|
|
|
function inwardEdgeNormal(vertex1, vertex2) {
|
|
// Assuming that polygon vertices are in clockwise order
|
|
const dx = vertex2.x - vertex1.x
|
|
const dy = vertex2.y - vertex1.y
|
|
const edgeLength = Math.sqrt(dx * dx + dy * dy)
|
|
|
|
return {
|
|
x: -dy / edgeLength,
|
|
y: dx / edgeLength,
|
|
}
|
|
}
|
|
|
|
function outwardEdgeNormal(vertex1, vertex2) {
|
|
const n = inwardEdgeNormal(vertex1, vertex2)
|
|
|
|
return {
|
|
x: -n.x,
|
|
y: -n.y,
|
|
}
|
|
}
|
|
|
|
function createPolygon(vertices) {
|
|
const edges = []
|
|
let minX = vertices.length > 0 ? vertices[0].x : undefined
|
|
let minY = vertices.length > 0 ? vertices[0].y : undefined
|
|
let maxX = minX
|
|
let maxY = minY
|
|
|
|
for (let i = 0; i < vertices.length; i++) {
|
|
const vertex1 = vertices[i]
|
|
const vertex2 = vertices[(i + 1) % vertices.length]
|
|
|
|
const outwardNormal = outwardEdgeNormal(vertex1, vertex2)
|
|
|
|
const inwardNormal = inwardEdgeNormal(vertex1, vertex2)
|
|
|
|
const edge = {
|
|
vertex1,
|
|
vertex2,
|
|
index: i,
|
|
outwardNormal,
|
|
inwardNormal,
|
|
}
|
|
|
|
edges.push(edge)
|
|
|
|
const x = vertices[i].x
|
|
const y = vertices[i].y
|
|
minX = Math.min(x, minX)
|
|
minY = Math.min(y, minY)
|
|
maxX = Math.max(x, maxX)
|
|
maxY = Math.max(y, maxY)
|
|
}
|
|
|
|
return {
|
|
vertices,
|
|
edges,
|
|
minX,
|
|
minY,
|
|
maxX,
|
|
maxY,
|
|
}
|
|
}
|
|
|
|
/**
|
|
* edgeA와 edgeB가 마주치는 포인트의 좌표를 반환한다.
|
|
* @param edgeA
|
|
* @param edgeB
|
|
* @returns {{x: *, y: *, isIntersectionOutside: boolean}|null}
|
|
*/
|
|
function edgesIntersection(edgeA, edgeB) {
|
|
const den =
|
|
(edgeB.vertex2.y - edgeB.vertex1.y) * (edgeA.vertex2.x - edgeA.vertex1.x) -
|
|
(edgeB.vertex2.x - edgeB.vertex1.x) * (edgeA.vertex2.y - edgeA.vertex1.y)
|
|
|
|
if (den === 0) {
|
|
return null // 선들이 평행하거나 일치합니다
|
|
}
|
|
|
|
const ua =
|
|
((edgeB.vertex2.x - edgeB.vertex1.x) * (edgeA.vertex1.y - edgeB.vertex1.y) -
|
|
(edgeB.vertex2.y - edgeB.vertex1.y) * (edgeA.vertex1.x - edgeB.vertex1.x)) /
|
|
den
|
|
|
|
const ub =
|
|
((edgeA.vertex2.x - edgeA.vertex1.x) * (edgeA.vertex1.y - edgeB.vertex1.y) -
|
|
(edgeA.vertex2.y - edgeA.vertex1.y) * (edgeA.vertex1.x - edgeB.vertex1.x)) /
|
|
den
|
|
|
|
// 교차점이 두 선분의 범위 내에 있는지 확인
|
|
const isIntersectionOutside = ua < 0 || ub < 0 || ua > 1 || ub > 1
|
|
|
|
return {
|
|
x: edgeA.vertex1.x + ua * (edgeA.vertex2.x - edgeA.vertex1.x),
|
|
y: edgeA.vertex1.y + ua * (edgeA.vertex2.y - edgeA.vertex1.y),
|
|
isIntersectionOutside,
|
|
}
|
|
}
|
|
|
|
function appendArc(arcSegments, vertices, center, radius, startVertex, endVertex, isPaddingBoundary) {
|
|
let startAngle = Math.atan2(startVertex.y - center.y, startVertex.x - center.x)
|
|
let endAngle = Math.atan2(endVertex.y - center.y, endVertex.x - center.x)
|
|
|
|
if (startAngle < 0) {
|
|
startAngle += TWO_PI
|
|
}
|
|
|
|
if (endAngle < 0) {
|
|
endAngle += TWO_PI
|
|
}
|
|
|
|
const angle = startAngle > endAngle ? startAngle - endAngle : startAngle + TWO_PI - endAngle
|
|
const angleStep = (isPaddingBoundary ? -angle : TWO_PI - angle) / arcSegments
|
|
|
|
vertices.push(startVertex)
|
|
|
|
for (let i = 1; i < arcSegments; ++i) {
|
|
const angle = startAngle + angleStep * i
|
|
|
|
const vertex = {
|
|
x: center.x + Math.cos(angle) * radius,
|
|
y: center.y + Math.sin(angle) * radius,
|
|
}
|
|
|
|
vertices.push(vertex)
|
|
}
|
|
|
|
vertices.push(endVertex)
|
|
}
|
|
|
|
function createOffsetEdge(edge, dx, dy) {
|
|
return {
|
|
vertex1: {
|
|
x: edge.vertex1.x + dx,
|
|
y: edge.vertex1.y + dy,
|
|
},
|
|
vertex2: {
|
|
x: edge.vertex2.x + dx,
|
|
y: edge.vertex2.y + dy,
|
|
},
|
|
}
|
|
}
|
|
|
|
function createMarginPolygon(polygon, offset, arcSegments = 0) {
|
|
const offsetEdges = []
|
|
|
|
for (let i = 0; i < polygon.edges.length; i++) {
|
|
const edge = polygon.edges[i]
|
|
const dx = edge.outwardNormal.x * offset
|
|
const dy = edge.outwardNormal.y * offset
|
|
offsetEdges.push(createOffsetEdge(edge, dx, dy))
|
|
}
|
|
|
|
const vertices = []
|
|
|
|
for (let i = 0; i < offsetEdges.length; i++) {
|
|
const thisEdge = offsetEdges[i]
|
|
const prevEdge = offsetEdges[(i + offsetEdges.length - 1) % offsetEdges.length]
|
|
const vertex = edgesIntersection(prevEdge, thisEdge)
|
|
|
|
if (vertex && (!vertex.isIntersectionOutside || arcSegments < 1)) {
|
|
vertices.push({
|
|
x: vertex.x,
|
|
y: vertex.y,
|
|
})
|
|
} else {
|
|
const arcCenter = polygon.edges[i].vertex1
|
|
|
|
appendArc(arcSegments, vertices, arcCenter, offset, prevEdge.vertex2, thisEdge.vertex1, false)
|
|
}
|
|
}
|
|
|
|
const marginPolygon = createPolygon(vertices)
|
|
|
|
marginPolygon.offsetEdges = offsetEdges
|
|
|
|
return marginPolygon
|
|
}
|
|
|
|
function createPaddingPolygon(polygon, offset, arcSegments = 0) {
|
|
const offsetEdges = []
|
|
|
|
for (let i = 0; i < polygon.edges.length; i++) {
|
|
const edge = polygon.edges[i]
|
|
const dx = edge.inwardNormal.x * offset
|
|
const dy = edge.inwardNormal.y * offset
|
|
offsetEdges.push(createOffsetEdge(edge, dx, dy))
|
|
}
|
|
|
|
const vertices = []
|
|
|
|
for (let i = 0; i < offsetEdges.length; i++) {
|
|
const thisEdge = offsetEdges[i]
|
|
const prevEdge = offsetEdges[(i + offsetEdges.length - 1) % offsetEdges.length]
|
|
const vertex = edgesIntersection(prevEdge, thisEdge)
|
|
if (vertex && (!vertex.isIntersectionOutside || arcSegments < 1)) {
|
|
vertices.push({
|
|
x: vertex.x,
|
|
y: vertex.y,
|
|
})
|
|
} else {
|
|
const arcCenter = polygon.edges[i].vertex1
|
|
|
|
appendArc(arcSegments, vertices, arcCenter, offset, prevEdge.vertex2, thisEdge.vertex1, true)
|
|
}
|
|
}
|
|
|
|
const paddingPolygon = createPolygon(vertices)
|
|
|
|
paddingPolygon.offsetEdges = offsetEdges
|
|
|
|
return paddingPolygon
|
|
}
|
|
|
|
export default function offsetPolygon(vertices, offset) {
|
|
const polygon = createPolygon(vertices)
|
|
const arcSegments = 0
|
|
|
|
const originPolygon = new QPolygon(vertices, { fontSize: 0 })
|
|
originPolygon.setViewLengthText(false)
|
|
|
|
if (offset > 0) {
|
|
let result = createMarginPolygon(polygon, offset, arcSegments).vertices
|
|
const allPointsOutside = result.every((point) => !originPolygon.inPolygon(point))
|
|
|
|
if (allPointsOutside) {
|
|
return createMarginPolygon(polygon, offset, arcSegments).vertices
|
|
} else {
|
|
return createPaddingPolygon(polygon, offset, arcSegments).vertices
|
|
}
|
|
} else {
|
|
let result = createPaddingPolygon(polygon, offset, arcSegments).vertices
|
|
const allPointsInside = result.every((point) => originPolygon.inPolygon(point))
|
|
|
|
if (allPointsInside) {
|
|
return createPaddingPolygon(polygon, offset, arcSegments).vertices
|
|
} else {
|
|
return createMarginPolygon(polygon, offset, arcSegments).vertices
|
|
}
|
|
}
|
|
}
|
|
|
|
function normalizePoint(point) {
|
|
return {
|
|
x: Math.round(point.x),
|
|
y: Math.round(point.y),
|
|
}
|
|
}
|
|
|
|
function arePolygonsEqual(polygon1, polygon2) {
|
|
if (polygon1.length !== polygon2.length) return false
|
|
|
|
const normalizedPolygon1 = polygon1.map(normalizePoint).sort((a, b) => a.x - b.x || a.y - b.y)
|
|
const normalizedPolygon2 = polygon2.map(normalizePoint).sort((a, b) => a.x - b.x || a.y - b.y)
|
|
|
|
return normalizedPolygon1.every((point, index) => arePointsEqual(point, normalizedPolygon2[index]))
|
|
}
|
|
|
|
export function removeDuplicatePolygons(polygons, hasAuxiliaryLine = false) {
|
|
let uniquePolygons = []
|
|
|
|
// x가 전부 같거나, y가 전부 같은 경우 제거
|
|
polygons.forEach((polygon) => {
|
|
const isDuplicate = uniquePolygons.some((uniquePolygon) => arePolygonsEqual(polygon, uniquePolygon))
|
|
if (!isDuplicate) {
|
|
uniquePolygons.push(polygon)
|
|
}
|
|
})
|
|
|
|
uniquePolygons = uniquePolygons.filter((polygon) => {
|
|
return !checkPolygonSelfIntersection(polygon)
|
|
})
|
|
|
|
// uniquePolygons = uniquePolygons.filter((polygon) => {
|
|
// return isValidPoints(polygon)
|
|
// })
|
|
|
|
return uniquePolygons
|
|
}
|
|
|
|
/**
|
|
* 두 선분이 교차하는지 확인하는 함수
|
|
* @param {Object} p1 첫 번째 선분의 시작점 {x, y}
|
|
* @param {Object} q1 첫 번째 선분의 끝점 {x, y}
|
|
* @param {Object} p2 두 번째 선분의 시작점 {x, y}
|
|
* @param {Object} q2 두 번째 선분의 끝점 {x, y}
|
|
* @returns {boolean} 교차하면 true, 아니면 false
|
|
*/
|
|
function doSegmentsIntersect(p1, q1, p2, q2) {
|
|
// CCW (Counter-Clockwise) 방향 확인 함수
|
|
function orientation(p, q, r) {
|
|
const val = (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y)
|
|
if (val === 0) return 0 // 일직선
|
|
return val > 0 ? 1 : 2 // 시계방향 또는 반시계방향
|
|
}
|
|
|
|
// 점 q가 선분 pr 위에 있는지 확인
|
|
function onSegment(p, q, r) {
|
|
return q.x <= Math.max(p.x, r.x) && q.x >= Math.min(p.x, r.x) && q.y <= Math.max(p.y, r.y) && q.y >= Math.min(p.y, r.y)
|
|
}
|
|
|
|
// 같은 끝점을 공유하는 경우는 교차로 보지 않음
|
|
if ((p1.x === p2.x && p1.y === p2.y) || (p1.x === q2.x && p1.y === q2.y) || (q1.x === p2.x && q1.y === p2.y) || (q1.x === q2.x && q1.y === q2.y)) {
|
|
return false
|
|
}
|
|
|
|
const o1 = orientation(p1, q1, p2)
|
|
const o2 = orientation(p1, q1, q2)
|
|
const o3 = orientation(p2, q2, p1)
|
|
const o4 = orientation(p2, q2, q1)
|
|
|
|
// 일반적인 교차 경우
|
|
if (o1 !== o2 && o3 !== o4) return true
|
|
|
|
// 특별한 경우들 (한 점이 다른 선분 위에 있는 경우)
|
|
if (o1 === 0 && onSegment(p1, p2, q1)) return true
|
|
if (o2 === 0 && onSegment(p1, q2, q1)) return true
|
|
if (o3 === 0 && onSegment(p2, p1, q2)) return true
|
|
if (o4 === 0 && onSegment(p2, q1, q2)) return true
|
|
|
|
return false
|
|
}
|
|
|
|
/**
|
|
* 다각형의 자기 교차를 검사하는 메인 함수
|
|
* @param {Array} coordinates 다각형의 좌표 배열 [{x, y}, ...]
|
|
* @returns {Object} 검사 결과 {hasSelfIntersection: boolean, intersections: Array}
|
|
*/
|
|
function checkPolygonSelfIntersection(coordinates) {
|
|
if (coordinates.length < 3) {
|
|
return {
|
|
hasSelfIntersection: false,
|
|
intersections: [],
|
|
error: '다각형은 최소 3개의 점이 필요합니다.',
|
|
}
|
|
}
|
|
|
|
// 모든 점이 같은 직선상에 있는지 검사 (degenerate polygon)
|
|
const allSameX = coordinates.every((point) => point.x === coordinates[0].x)
|
|
const allSameY = coordinates.every((point) => point.y === coordinates[0].y)
|
|
|
|
if (allSameX || allSameY) {
|
|
return true // 직선상의 점들은 유효하지 않은 다각형이므로 true 반환
|
|
}
|
|
|
|
const intersections = []
|
|
const edges = []
|
|
|
|
// 모든 변(edge) 생성
|
|
for (let i = 0; i < coordinates.length; i++) {
|
|
const start = coordinates[i]
|
|
const end = coordinates[(i + 1) % coordinates.length]
|
|
edges.push({
|
|
start: start,
|
|
end: end,
|
|
index: i,
|
|
})
|
|
}
|
|
|
|
// 모든 변 쌍에 대해 교차 검사
|
|
for (let i = 0; i < edges.length; i++) {
|
|
for (let j = i + 1; j < edges.length; j++) {
|
|
// 인접한 변들은 제외 (끝점을 공유하므로)
|
|
if (Math.abs(i - j) === 1 || (i === 0 && j === edges.length - 1)) {
|
|
continue
|
|
}
|
|
|
|
const edge1 = edges[i]
|
|
const edge2 = edges[j]
|
|
|
|
if (doSegmentsIntersect(edge1.start, edge1.end, edge2.start, edge2.end)) {
|
|
intersections.push({
|
|
edge1Index: i,
|
|
edge2Index: j,
|
|
edge1: {
|
|
from: edge1.start,
|
|
to: edge1.end,
|
|
},
|
|
edge2: {
|
|
from: edge2.start,
|
|
to: edge2.end,
|
|
},
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
return intersections.length > 0
|
|
}
|
|
|
|
// 같은 직선상에 있는지 확인 같은 직선이라면 polygon을 생성할 수 없으므로 false
|
|
const isValidPoints = (points) => {
|
|
// 연속된 3개 이상의 점이 같은 x 또는 y 값을 가지는지 확인 (원형 배열로 처리)
|
|
for (let i = 0; i < points.length; i++) {
|
|
const point1 = points[i]
|
|
const point2 = points[(i + 1) % points.length]
|
|
const point3 = points[(i + 2) % points.length]
|
|
|
|
// x값이 같은 연속된 3개 점 확인
|
|
if (point1.x === point2.x && point2.x === point3.x) {
|
|
return false
|
|
}
|
|
// y값이 같은 연속된 3개 점 확인
|
|
if (point1.y === point2.y && point2.y === point3.y) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
function isColinear(p1, p2, p3) {
|
|
return (p2.x - p1.x) * (p3.y - p1.y) === (p3.x - p1.x) * (p2.y - p1.y)
|
|
}
|
|
|
|
function segmentsOverlap(a1, a2, b1, b2) {
|
|
// 같은 직선 상에 있는가?
|
|
if (!isColinear(a1, a2, b1) || !isColinear(a1, a2, b2)) {
|
|
return false
|
|
}
|
|
|
|
const isHorizontal = a1.y === a2.y
|
|
if (isHorizontal) {
|
|
const aMin = Math.min(a1.x, a2.x),
|
|
aMax = Math.max(a1.x, a2.x)
|
|
const bMin = Math.min(b1.x, b2.x),
|
|
bMax = Math.max(b1.x, b2.x)
|
|
return Math.max(aMin, bMin) < Math.min(aMax, bMax)
|
|
} else {
|
|
const aMin = Math.min(a1.y, a2.y),
|
|
aMax = Math.max(a1.y, a2.y)
|
|
const bMin = Math.min(b1.y, b2.y),
|
|
bMax = Math.max(b1.y, b2.y)
|
|
return Math.max(aMin, bMin) < Math.min(aMax, bMax)
|
|
}
|
|
}
|
|
|
|
for (let i = 0; i < points.length - 2; i++) {
|
|
const a1 = points[i]
|
|
const a2 = points[i + 1]
|
|
const b1 = points[i + 1] // 연속되는 점
|
|
const b2 = points[i + 2]
|
|
|
|
if (segmentsOverlap(a1, a2, b1, b2)) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
export const isSamePoint = (a, b) => {
|
|
if (!a || !b) {
|
|
return false
|
|
}
|
|
return Math.abs(Math.round(a.x) - Math.round(b.x)) <= 2 && Math.abs(Math.round(a.y) - Math.round(b.y)) <= 2
|
|
}
|
|
|
|
/**
|
|
* 박공지붕(A,B 패턴)
|
|
* @param roofId
|
|
* @param canvas
|
|
* @param textMode
|
|
*/
|
|
export const drawGableRoof = (roofId, canvas, textMode) => {
|
|
const roof = canvas?.getObjects().find((object) => object.id === roofId)
|
|
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 baseLinePoints = baseLines.map((line) => ({ x: line.x1, y: line.y1 }))
|
|
|
|
const eavesLines = baseLines.filter((line) => line.attributes.type === LINE_TYPE.WALLLINE.EAVES)
|
|
|
|
const ridgeLines = []
|
|
const innerLines = []
|
|
|
|
/**
|
|
* 처마 라인 속성 판단
|
|
* @param line
|
|
* @returns {{startPoint: {x, y}, endPoint: {x, y}, length: number, angleDegree: number, normalizedAngle: number, isHorizontal: boolean, isVertical: boolean, isDiagonal: boolean, directionVector: {x: number, y: number}, roofLine}}
|
|
*/
|
|
const analyzeEavesLine = (line) => {
|
|
const tolerance = 1
|
|
const dx = Big(line.x2).minus(Big(line.x1)).toNumber()
|
|
const dy = Big(line.y2).minus(Big(line.y1)).toNumber()
|
|
const length = Math.sqrt(dx * dx + dy * dy)
|
|
const angleDegree = (Math.atan2(dy, dx) * 180) / Math.PI
|
|
const normalizedAngle = Math.abs((Math.atan2(dy, dx) * 180) / Math.PI) % 180
|
|
const directionVector = { x: dx / length, y: dy / length }
|
|
let isHorizontal = false,
|
|
isVertical = false,
|
|
isDiagonal = false
|
|
if (normalizedAngle < tolerance || normalizedAngle >= 180 - tolerance) {
|
|
isHorizontal = true
|
|
} else if (Math.abs(normalizedAngle - 90) <= tolerance) {
|
|
isVertical = true
|
|
} else {
|
|
isDiagonal = true
|
|
}
|
|
|
|
const originPoint = line.attributes.originPoint
|
|
const midX = (originPoint.x1 + originPoint.x2) / 2
|
|
const midY = (originPoint.y1 + originPoint.y2) / 2
|
|
const offset = line.attributes.offset
|
|
|
|
const checkRoofLines = roof.lines.filter((roof) => {
|
|
const roofDx = Big(roof.x2).minus(Big(roof.x1)).toNumber()
|
|
const roofDy = Big(roof.y2).minus(Big(roof.y1)).toNumber()
|
|
const roofLength = Math.sqrt(roofDx * roofDx + roofDy * roofDy)
|
|
const roofVector = { x: roofDx / roofLength, y: roofDy / roofLength }
|
|
return directionVector.x === roofVector.x && directionVector.y === roofVector.y
|
|
})
|
|
|
|
let roofVector = { x: 0, y: 0 }
|
|
if (isHorizontal) {
|
|
const checkPoint = { x: midX, y: midY + offset }
|
|
if (wall.inPolygon(checkPoint)) {
|
|
roofVector = { x: 0, y: -1 }
|
|
} else {
|
|
roofVector = { x: 0, y: 1 }
|
|
}
|
|
}
|
|
if (isVertical) {
|
|
const checkPoint = { x: midX + offset, y: midY }
|
|
if (wall.inPolygon(checkPoint)) {
|
|
roofVector = { x: -1, y: 0 }
|
|
} else {
|
|
roofVector = { x: 1, y: 0 }
|
|
}
|
|
}
|
|
|
|
const findEdge = { vertex1: { x: midX, y: midY }, vertex2: { x: midX + roofVector.x * offset, y: midY + roofVector.y * offset } }
|
|
const edgeDx =
|
|
Big(findEdge.vertex2.x).minus(Big(findEdge.vertex1.x)).abs().toNumber() < 0.1
|
|
? 0
|
|
: Big(findEdge.vertex2.x).minus(Big(findEdge.vertex1.x)).toNumber()
|
|
const edgeDy =
|
|
Big(findEdge.vertex2.y).minus(Big(findEdge.vertex1.y)).abs().toNumber() < 0.1
|
|
? 0
|
|
: Big(findEdge.vertex2.y).minus(Big(findEdge.vertex1.y)).toNumber()
|
|
const edgeLength = Math.sqrt(edgeDx * edgeDx + edgeDy * edgeDy)
|
|
const edgeVector = { x: edgeDx / edgeLength, y: edgeDy / edgeLength }
|
|
|
|
const intersectRoofLines = []
|
|
checkRoofLines.forEach((roofLine) => {
|
|
const lineEdge = { vertex1: { x: roofLine.x1, y: roofLine.y1 }, vertex2: { x: roofLine.x2, y: roofLine.y2 } }
|
|
const intersect = edgesIntersection(lineEdge, findEdge)
|
|
if (intersect) {
|
|
const intersectDx =
|
|
Big(intersect.x).minus(Big(findEdge.vertex1.x)).abs().toNumber() < 0.1 ? 0 : Big(intersect.x).minus(Big(findEdge.vertex1.x)).toNumber()
|
|
const intersectDy =
|
|
Big(intersect.y).minus(Big(findEdge.vertex1.y)).abs().toNumber() < 0.1 ? 0 : Big(intersect.y).minus(Big(findEdge.vertex1.y)).toNumber()
|
|
const intersectLength = Math.sqrt(intersectDx * intersectDx + intersectDy * intersectDy)
|
|
const intersectVector = { x: intersectDx / intersectLength, y: intersectDy / intersectLength }
|
|
if (edgeVector.x === intersectVector.x && edgeVector.y === intersectVector.y) {
|
|
intersectRoofLines.push({ roofLine, intersect, length: intersectLength })
|
|
}
|
|
}
|
|
})
|
|
|
|
intersectRoofLines.sort((a, b) => a.length - b.length)
|
|
let currentRoof = intersectRoofLines.find((roof) => isPointOnLineNew(roof.roofLine, roof.intersect) && roof.length - offset < 0.1)
|
|
if (!currentRoof) {
|
|
currentRoof = intersectRoofLines[0]
|
|
}
|
|
|
|
let startPoint, endPoint
|
|
if (isHorizontal) {
|
|
startPoint = { x: Math.min(line.x1, line.x2, currentRoof.roofLine.x1, currentRoof.roofLine.x2), y: line.y1 }
|
|
endPoint = { x: Math.max(line.x1, line.x2, currentRoof.roofLine.x1, currentRoof.roofLine.x2), y: line.y2 }
|
|
}
|
|
if (isVertical) {
|
|
startPoint = { x: line.x1, y: Math.min(line.y1, line.y2, currentRoof.roofLine.y1, currentRoof.roofLine.y2) }
|
|
endPoint = { x: line.x2, y: Math.max(line.y1, line.y2, currentRoof.roofLine.y1, currentRoof.roofLine.y2) }
|
|
}
|
|
if (isDiagonal) {
|
|
startPoint = { x: line.x1, y: line.y1 }
|
|
endPoint = { x: line.x2, y: line.y2 }
|
|
}
|
|
|
|
return {
|
|
startPoint,
|
|
endPoint,
|
|
length,
|
|
angleDegree,
|
|
normalizedAngle,
|
|
isHorizontal,
|
|
isVertical,
|
|
isDiagonal,
|
|
directionVector: { x: dx / length, y: dy / length },
|
|
roofLine: currentRoof.roofLine,
|
|
roofVector,
|
|
}
|
|
}
|
|
|
|
const isOverlapLine = (currAnalyze, checkAnalyze) => {
|
|
// 허용 오차
|
|
const tolerance = 1
|
|
|
|
// 같은 방향인지 확인
|
|
if (
|
|
currAnalyze.isHorizontal !== checkAnalyze.isHorizontal ||
|
|
currAnalyze.isVertical !== checkAnalyze.isVertical ||
|
|
currAnalyze.isDiagonal !== checkAnalyze.isDiagonal
|
|
) {
|
|
return false
|
|
}
|
|
|
|
if (currAnalyze.isHorizontal && !(Math.abs(currAnalyze.startPoint.y - checkAnalyze.startPoint.y) < tolerance)) {
|
|
// 수평선: y좌표가 다른경우 false
|
|
return false
|
|
} else if (currAnalyze.isVertical && !(Math.abs(currAnalyze.startPoint.x - checkAnalyze.startPoint.x) < tolerance)) {
|
|
// 수직선: x좌표가 다른경우 false
|
|
return false
|
|
}
|
|
|
|
// 3. 선분 구간 겹침 확인
|
|
let range1, range2
|
|
|
|
if (currAnalyze.isHorizontal) {
|
|
// 수평선: x 범위로 비교
|
|
range1 = {
|
|
min: Math.min(currAnalyze.startPoint.x, currAnalyze.endPoint.x),
|
|
max: Math.max(currAnalyze.startPoint.x, currAnalyze.endPoint.x),
|
|
}
|
|
range2 = {
|
|
min: Math.min(checkAnalyze.startPoint.x, checkAnalyze.endPoint.x),
|
|
max: Math.max(checkAnalyze.startPoint.x, checkAnalyze.endPoint.x),
|
|
}
|
|
} else {
|
|
// 수직선: y 범위로 비교
|
|
range1 = {
|
|
min: Math.min(currAnalyze.startPoint.y, currAnalyze.endPoint.y),
|
|
max: Math.max(currAnalyze.startPoint.y, currAnalyze.endPoint.y),
|
|
}
|
|
range2 = {
|
|
min: Math.min(checkAnalyze.startPoint.y, checkAnalyze.endPoint.y),
|
|
max: Math.max(checkAnalyze.startPoint.y, checkAnalyze.endPoint.y),
|
|
}
|
|
}
|
|
// 구간 겹침 확인
|
|
const overlapStart = Math.max(range1.min, range2.min)
|
|
const overlapEnd = Math.min(range1.max, range2.max)
|
|
|
|
return Math.max(0, overlapEnd - overlapStart) > 0
|
|
}
|
|
|
|
/**
|
|
* 전체 처마 라인의 속성 확인 및 정리
|
|
* @param lines
|
|
* @returns {{forwardLines: Array, backwardLines: Array}}
|
|
*/
|
|
const analyzeAllEavesLines = (lines) => {
|
|
let forwardLines = []
|
|
let backwardLines = []
|
|
lines.forEach((line) => {
|
|
const analyze = analyzeEavesLine(line)
|
|
if (analyze.isHorizontal) {
|
|
if (analyze.directionVector.x > 0) {
|
|
const overlapLines = forwardLines.filter((forwardLine) => forwardLine !== line && isOverlapLine(analyze, forwardLine.analyze))
|
|
if (overlapLines.length > 0) {
|
|
overlapLines.forEach((overlap) => {
|
|
const overlapAnalyze = overlap.analyze
|
|
const startPoint = {
|
|
x: Math.min(analyze.startPoint.x, analyze.endPoint.x, overlapAnalyze.startPoint.x, overlapAnalyze.endPoint.x),
|
|
y: analyze.startPoint.y,
|
|
}
|
|
const endPoint = {
|
|
x: Math.max(analyze.startPoint.x, analyze.endPoint.x, overlapAnalyze.startPoint.x, overlapAnalyze.endPoint.x),
|
|
y: analyze.endPoint.y,
|
|
}
|
|
analyze.startPoint = startPoint
|
|
analyze.endPoint = endPoint
|
|
forwardLines = forwardLines.filter((forwardLine) => forwardLine !== overlap)
|
|
})
|
|
}
|
|
forwardLines.push({ eaves: line, analyze })
|
|
}
|
|
if (analyze.directionVector.x < 0) {
|
|
const overlapLines = backwardLines.filter((backwardLine) => backwardLine !== line && isOverlapLine(analyze, backwardLine.analyze))
|
|
if (overlapLines.length > 0) {
|
|
overlapLines.forEach((overlap) => {
|
|
const overlapAnalyze = overlap.analyze
|
|
const startPoint = {
|
|
x: Math.min(analyze.startPoint.x, analyze.endPoint.x, overlapAnalyze.startPoint.x, overlapAnalyze.endPoint.x),
|
|
y: analyze.startPoint.y,
|
|
}
|
|
const endPoint = {
|
|
x: Math.max(analyze.startPoint.x, analyze.endPoint.x, overlapAnalyze.startPoint.x, overlapAnalyze.endPoint.x),
|
|
y: analyze.endPoint.y,
|
|
}
|
|
analyze.startPoint = startPoint
|
|
analyze.endPoint = endPoint
|
|
backwardLines = backwardLines.filter((backwardLine) => backwardLine !== overlap)
|
|
})
|
|
}
|
|
backwardLines.push({ eaves: line, analyze })
|
|
}
|
|
}
|
|
if (analyze.isVertical) {
|
|
if (analyze.directionVector.y > 0) {
|
|
const overlapLines = forwardLines.filter((forwardLine) => forwardLine !== line && isOverlapLine(analyze, forwardLine.analyze))
|
|
if (overlapLines.length > 0) {
|
|
overlapLines.forEach((overlap) => {
|
|
const overlapAnalyze = overlap.analyze
|
|
const startPoint = {
|
|
x: analyze.startPoint.x,
|
|
y: Math.min(analyze.startPoint.y, analyze.endPoint.y, overlapAnalyze.startPoint.y, overlapAnalyze.endPoint.y),
|
|
}
|
|
const endPoint = {
|
|
x: analyze.endPoint.x,
|
|
y: Math.max(analyze.startPoint.y, analyze.endPoint.y, overlapAnalyze.startPoint.y, overlapAnalyze.endPoint.y),
|
|
}
|
|
analyze.startPoint = startPoint
|
|
analyze.endPoint = endPoint
|
|
forwardLines = forwardLines.filter((forwardLine) => forwardLine !== overlap)
|
|
})
|
|
}
|
|
forwardLines.push({ eaves: line, analyze })
|
|
}
|
|
if (analyze.directionVector.y < 0) {
|
|
const overlapLines = backwardLines.filter((backwardLine) => backwardLine !== line && isOverlapLine(analyze, backwardLine.analyze))
|
|
if (overlapLines.length > 0) {
|
|
overlapLines.forEach((overlap) => {
|
|
const overlapAnalyze = overlap.analyze
|
|
const startPoint = {
|
|
x: analyze.startPoint.x,
|
|
y: Math.min(analyze.startPoint.y, analyze.endPoint.y, overlapAnalyze.startPoint.y, overlapAnalyze.endPoint.y),
|
|
}
|
|
const endPoint = {
|
|
x: analyze.endPoint.x,
|
|
y: Math.max(analyze.startPoint.y, analyze.endPoint.y, overlapAnalyze.startPoint.y, overlapAnalyze.endPoint.y),
|
|
}
|
|
analyze.startPoint = startPoint
|
|
analyze.endPoint = endPoint
|
|
backwardLines = backwardLines.filter((backwardLine) => backwardLine !== overlap)
|
|
})
|
|
}
|
|
backwardLines.push({ eaves: line, analyze })
|
|
}
|
|
}
|
|
})
|
|
|
|
forwardLines.sort((a, b) => {
|
|
if (a.analyze.isHorizontal && b.analyze.isHorizontal) {
|
|
return a.analyze.startPoint.x - b.analyze.startPoint.x
|
|
} else if (a.analyze.isVertical && b.analyze.isVertical) {
|
|
return a.analyze.startPoint.y - b.analyze.startPoint.y
|
|
}
|
|
})
|
|
backwardLines.sort((a, b) => {
|
|
if (a.analyze.isHorizontal && b.analyze.isHorizontal) {
|
|
return a.analyze.startPoint.x - b.analyze.startPoint.x
|
|
} else if (a.analyze.isVertical && b.analyze.isVertical) {
|
|
return a.analyze.startPoint.y - b.analyze.startPoint.y
|
|
}
|
|
})
|
|
|
|
return { forwardLines, backwardLines }
|
|
}
|
|
|
|
/**
|
|
* 지점 사이의 길이와 지점별 각도에 따라서 교점까지의 길이를 구한다.
|
|
* @param distance
|
|
* @param angleA
|
|
* @param angleB
|
|
* @returns {{a: number, b: number}}
|
|
*/
|
|
const calculateIntersection = (distance, angleA, angleB) => {
|
|
// A에서 B방향으로의 각도 (0도가 B방향)
|
|
const tanA = Math.tan((angleA * Math.PI) / 180)
|
|
// B에서 A방향으로의 각도 (180도가 A방향, 실제 계산에서는 180-angleB)
|
|
const tanB = Math.tan(((180 - angleB) * Math.PI) / 180)
|
|
|
|
const a = Math.round(((distance * tanB) / (tanB - tanA)) * 10) / 10
|
|
const b = Math.round(Math.abs(distance - a) * 10) / 10
|
|
return { a, b }
|
|
}
|
|
|
|
/**
|
|
* polygon에 line이 겹쳐지는 구간을 확인.
|
|
* @param polygon
|
|
* @param linePoints
|
|
* @returns
|
|
*/
|
|
const findPloygonLineOverlap = (polygon, linePoints) => {
|
|
const polygonPoints = polygon.points
|
|
const checkLine = {
|
|
x1: linePoints[0],
|
|
y1: linePoints[1],
|
|
x2: linePoints[2],
|
|
y2: linePoints[3],
|
|
}
|
|
let startPoint = { x: checkLine.x1, y: checkLine.y1 }
|
|
let endPoint = { x: checkLine.x2, y: checkLine.y2 }
|
|
const lineEdge = { vertex1: startPoint, vertex2: endPoint }
|
|
|
|
const checkPolygon = new QPolygon(polygonPoints, {})
|
|
|
|
const isStartInside = checkPolygon.inPolygon(startPoint)
|
|
const isEndInside = checkPolygon.inPolygon(endPoint)
|
|
|
|
const intersections = []
|
|
|
|
checkPolygon.lines.forEach((line) => {
|
|
const edge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
|
|
const intersect = edgesIntersection(edge, lineEdge)
|
|
if (
|
|
intersect &&
|
|
isPointOnLineNew(line, intersect) &&
|
|
isPointOnLineNew(checkLine, intersect) &&
|
|
!(Math.abs(startPoint.x - intersect.x) < 1 && Math.abs(startPoint.y - intersect.y) < 1) &&
|
|
!(Math.abs(endPoint.x - intersect.x) < 1 && Math.abs(endPoint.y - intersect.y) < 1)
|
|
) {
|
|
intersections.push({ intersect, dist: Math.sqrt(Math.pow(startPoint.x - intersect.x, 2) + Math.pow(startPoint.y - intersect.y, 2)) })
|
|
}
|
|
})
|
|
|
|
if (intersections.length > 0) {
|
|
intersections.sort((a, b) => a.dist - b.dist)
|
|
let i = 0
|
|
if (!isStartInside) {
|
|
startPoint = { x: intersections[0].intersect.x, y: intersections[0].intersect.y }
|
|
i++
|
|
}
|
|
endPoint = { x: intersections[i].intersect.x, y: intersections[i].intersect.y }
|
|
}
|
|
return [startPoint.x, startPoint.y, endPoint.x, endPoint.y]
|
|
}
|
|
|
|
const { forwardLines, backwardLines } = analyzeAllEavesLines(eavesLines)
|
|
|
|
forwardLines.forEach((current) => {
|
|
const currentLine = current.eaves
|
|
const analyze = current.analyze
|
|
const currentDegree = getDegreeByChon(currentLine.attributes.pitch)
|
|
const currentX1 = Math.min(currentLine.x1, currentLine.x2)
|
|
const currentX2 = Math.max(currentLine.x1, currentLine.x2)
|
|
const currentY1 = Math.min(currentLine.y1, currentLine.y2)
|
|
const currentY2 = Math.max(currentLine.y1, currentLine.y2)
|
|
|
|
const x1 = analyze.startPoint.x
|
|
const x2 = analyze.endPoint.x
|
|
const y1 = analyze.startPoint.y
|
|
const y2 = analyze.endPoint.y
|
|
|
|
const lineVector = { x: 0, y: 0 }
|
|
if (analyze.isHorizontal) {
|
|
lineVector.x = Math.sign(analyze.roofLine.y1 - currentLine.attributes.originPoint.y1)
|
|
}
|
|
if (analyze.isVertical) {
|
|
lineVector.y = Math.sign(analyze.roofLine.x1 - currentLine.attributes.originPoint.x1)
|
|
}
|
|
|
|
const overlapLines = []
|
|
const findBackWardLines = backwardLines.filter((backward) => {
|
|
const backwardVector = { x: 0, y: 0 }
|
|
if (analyze.isHorizontal) {
|
|
backwardVector.x = Math.sign(analyze.roofLine.y1 - backward.analyze.startPoint.y)
|
|
}
|
|
if (analyze.isVertical) {
|
|
backwardVector.y = Math.sign(analyze.roofLine.x1 - backward.analyze.startPoint.x)
|
|
}
|
|
return backwardVector.x === lineVector.x && backwardVector.y === lineVector.y
|
|
})
|
|
const backWardLine = findBackWardLines.find((backward) => {
|
|
const backX1 = Math.min(backward.eaves.x1, backward.eaves.x2)
|
|
const backX2 = Math.max(backward.eaves.x1, backward.eaves.x2)
|
|
const backY1 = Math.min(backward.eaves.y1, backward.eaves.y2)
|
|
const backY2 = Math.max(backward.eaves.y1, backward.eaves.y2)
|
|
return (
|
|
(analyze.isHorizontal && Math.abs(currentX1 - backX1) < 0.1 && Math.abs(currentX2 - backX2) < 0.1) ||
|
|
(analyze.isVertical && Math.abs(currentY1 - backY1) < 0.1 && Math.abs(currentY2 - backY2) < 0.1)
|
|
)
|
|
})
|
|
if (backWardLine) {
|
|
overlapLines.push(backWardLine)
|
|
} else {
|
|
findBackWardLines.forEach((backward) => {
|
|
const backX1 = Math.min(backward.eaves.x1, backward.eaves.x2)
|
|
const backX2 = Math.max(backward.eaves.x1, backward.eaves.x2)
|
|
const backY1 = Math.min(backward.eaves.y1, backward.eaves.y2)
|
|
const backY2 = Math.max(backward.eaves.y1, backward.eaves.y2)
|
|
|
|
if (
|
|
analyze.isHorizontal &&
|
|
((currentX1 <= backX1 && currentX2 >= backX1) ||
|
|
(currentX1 <= backX2 && currentX2 >= backX2) ||
|
|
(backX1 < currentX1 && backX1 < currentX2 && backX2 > currentX1 && backX2 > currentX2))
|
|
) {
|
|
overlapLines.push(backward)
|
|
}
|
|
if (
|
|
analyze.isVertical &&
|
|
((currentY1 <= backY1 && currentY2 >= backY1) ||
|
|
(currentY1 <= backY2 && currentY2 >= backY2) ||
|
|
(backY1 < currentY1 && backY1 < currentY2 && backY2 > currentY1 && backY2 > currentY2))
|
|
) {
|
|
overlapLines.push(backward)
|
|
}
|
|
})
|
|
}
|
|
|
|
overlapLines.forEach((overlap) => {
|
|
const overlapAnalyze = overlap.analyze
|
|
const overlapDegree = getDegreeByChon(overlap.eaves.attributes.pitch)
|
|
const ridgePoint = []
|
|
if (analyze.isHorizontal) {
|
|
const overlapX1 = Math.min(overlapAnalyze.startPoint.x, overlapAnalyze.endPoint.x)
|
|
const overlapX2 = Math.max(overlapAnalyze.startPoint.x, overlapAnalyze.endPoint.x)
|
|
|
|
// 각 라인 사이의 길이를 구해서 각도에 대한 중간 지점으로 마루를 생성.
|
|
const currentMidY = (currentLine.y1 + currentLine.y2) / 2
|
|
const overlapMidY = (overlap.eaves.y1 + overlap.eaves.y2) / 2
|
|
const distance = calculateIntersection(Math.abs(currentMidY - overlapMidY), currentDegree, overlapDegree)
|
|
const vectorToOverlap = Math.sign(overlap.eaves.y1 - currentLine.y1)
|
|
const pointY = currentLine.y1 + vectorToOverlap * distance.a
|
|
|
|
if (x1 <= overlapX1 && overlapX1 <= x2) {
|
|
ridgePoint.push({ x: overlapX1, y: pointY })
|
|
} else {
|
|
ridgePoint.push({ x: x1, y: pointY })
|
|
}
|
|
if (x1 <= overlapX2 && overlapX2 <= x2) {
|
|
ridgePoint.push({ x: overlapX2, y: pointY })
|
|
} else {
|
|
ridgePoint.push({ x: x2, y: pointY })
|
|
}
|
|
}
|
|
if (analyze.isVertical) {
|
|
const overlapY1 = Math.min(overlapAnalyze.startPoint.y, overlapAnalyze.endPoint.y)
|
|
const overlapY2 = Math.max(overlapAnalyze.startPoint.y, overlapAnalyze.endPoint.y)
|
|
|
|
// 각 라인 사이의 길이를 구해서 각도에 대한 중간 지점으로 마루를 생성.
|
|
const currentMidX = (currentLine.x1 + currentLine.x2) / 2
|
|
const overlapMidX = (overlap.eaves.x1 + overlap.eaves.x2) / 2
|
|
const distance = calculateIntersection(Math.abs(currentMidX - overlapMidX), currentDegree, overlapDegree)
|
|
const vectorToOverlap = Math.sign(overlap.eaves.x1 - currentLine.x1)
|
|
const pointX = currentLine.x1 + vectorToOverlap * distance.a
|
|
|
|
if (y1 <= overlapY1 && overlapY1 <= y2) {
|
|
ridgePoint.push({ x: pointX, y: overlapY1 })
|
|
} else {
|
|
ridgePoint.push({ x: pointX, y: y1 })
|
|
}
|
|
if (y1 <= overlapY2 && overlapY2 <= y2) {
|
|
ridgePoint.push({ x: pointX, y: overlapY2 })
|
|
} else {
|
|
ridgePoint.push({ x: pointX, y: y2 })
|
|
}
|
|
}
|
|
const points = findPloygonLineOverlap(roof, [ridgePoint[0].x, ridgePoint[0].y, ridgePoint[1].x, ridgePoint[1].y], canvas, roofId)
|
|
ridgeLines.push(drawRidgeLine(points, canvas, roof, textMode))
|
|
})
|
|
})
|
|
|
|
/**
|
|
* currentLine {x1,y1}, {x2,y2} 좌표 안쪽에 있는 마루를 찾는다.
|
|
* @param currentLine
|
|
* @param roofVector
|
|
* @param lines
|
|
* @param tolerance
|
|
* @returns {*[]}
|
|
*/
|
|
const findInnerRidge = (currentLine, roofVector, lines, tolerance = 1) => {
|
|
const x1 = Math.min(currentLine.x1, currentLine.x2)
|
|
const y1 = Math.min(currentLine.y1, currentLine.y2)
|
|
const x2 = Math.max(currentLine.x1, currentLine.x2)
|
|
const y2 = Math.max(currentLine.y1, currentLine.y2)
|
|
const dx = Big(currentLine.x2).minus(Big(currentLine.x1)).toNumber()
|
|
const dy = Big(currentLine.y2).minus(Big(currentLine.y1)).toNumber()
|
|
const normalizedAngle = Math.abs((Math.atan2(dy, dx) * 180) / Math.PI) % 180
|
|
let isHorizontal = false,
|
|
isVertical = false
|
|
if (normalizedAngle < tolerance || normalizedAngle >= 180 - tolerance) {
|
|
isHorizontal = true
|
|
} else if (Math.abs(normalizedAngle - 90) <= tolerance) {
|
|
isVertical = true
|
|
}
|
|
let innerRidgeLines = []
|
|
if (isHorizontal) {
|
|
lines
|
|
.filter((line) => {
|
|
const dx = Big(line.x2).minus(Big(line.x1)).toNumber()
|
|
const dy = Big(line.y2).minus(Big(line.y1)).toNumber()
|
|
const normalizedAngle = Math.abs((Math.atan2(dy, dx) * 180) / Math.PI) % 180
|
|
return normalizedAngle < tolerance || normalizedAngle >= 180 - tolerance
|
|
})
|
|
.filter((line) => {
|
|
const minX = Math.min(line.x1, line.x2)
|
|
const maxX = Math.max(line.x1, line.x2)
|
|
return x1 <= minX && maxX <= x2 && roofVector.y * -1 === Math.sign(line.y1 - y1)
|
|
})
|
|
.forEach((line) => innerRidgeLines.push({ line, dist: Math.abs(currentLine.y1 - line.y1) }))
|
|
}
|
|
if (isVertical) {
|
|
lines
|
|
.filter((line) => {
|
|
const dx = Big(line.x2).minus(Big(line.x1)).toNumber()
|
|
const dy = Big(line.y2).minus(Big(line.y1)).toNumber()
|
|
const normalizedAngle = Math.abs((Math.atan2(dy, dx) * 180) / Math.PI) % 180
|
|
return Math.abs(normalizedAngle - 90) <= tolerance
|
|
})
|
|
.filter((line) => {
|
|
const minY = Math.min(line.y1, line.y2)
|
|
const maxY = Math.max(line.y1, line.y2)
|
|
return y1 <= minY && maxY <= y2 && roofVector.x * -1 === Math.sign(line.x1 - x1)
|
|
})
|
|
.forEach((line) => innerRidgeLines.push({ line, dist: Math.abs(currentLine.x1 - line.x1) }))
|
|
}
|
|
innerRidgeLines.sort((a, b) => a.dist - b.dist)
|
|
|
|
const ridge1 = innerRidgeLines.find((ridge) => {
|
|
if (isHorizontal) {
|
|
const minX = Math.min(ridge.line.x1, ridge.line.x2)
|
|
return Math.abs(x1 - minX) <= 1
|
|
}
|
|
if (isVertical) {
|
|
const minY = Math.min(ridge.line.y1, ridge.line.y2)
|
|
return Math.abs(y1 - minY) <= 1
|
|
}
|
|
})
|
|
|
|
const ridge2 = innerRidgeLines.find((ridge) => {
|
|
if (isHorizontal) {
|
|
const maxX = Math.max(ridge.line.x1, ridge.line.x2)
|
|
return Math.abs(x2 - maxX) <= 1
|
|
}
|
|
if (isVertical) {
|
|
const maxY = Math.max(ridge.line.y1, ridge.line.y2)
|
|
return Math.abs(y2 - maxY) <= 1
|
|
}
|
|
})
|
|
if (ridge1 === ridge2) {
|
|
innerRidgeLines = [ridge1]
|
|
} else {
|
|
if (ridge1 && ridge2) {
|
|
let range1, range2
|
|
if (isHorizontal) {
|
|
// 수평선: x 범위로 비교
|
|
range1 = {
|
|
min: Math.min(ridge1.line.x1, ridge1.line.x2),
|
|
max: Math.max(ridge1.line.x1, ridge1.line.x2),
|
|
}
|
|
range2 = {
|
|
min: Math.min(ridge2.line.x1, ridge2.line.x2),
|
|
max: Math.max(ridge2.line.x1, ridge2.line.x2),
|
|
}
|
|
} else {
|
|
// 수직선: y 범위로 비교
|
|
range1 = {
|
|
min: Math.min(ridge1.line.y1, ridge1.line.y2),
|
|
max: Math.max(ridge1.line.y1, ridge1.line.y2),
|
|
}
|
|
range2 = {
|
|
min: Math.min(ridge2.line.y1, ridge2.line.y2),
|
|
max: Math.max(ridge2.line.y1, ridge2.line.y2),
|
|
}
|
|
}
|
|
// 구간 겹침 확인
|
|
const overlapStart = Math.max(range1.min, range2.min)
|
|
const overlapEnd = Math.min(range1.max, range2.max)
|
|
if (Math.max(0, overlapEnd - overlapStart) > 0) {
|
|
innerRidgeLines = [ridge1, ridge2]
|
|
}
|
|
}
|
|
}
|
|
return innerRidgeLines
|
|
}
|
|
|
|
/**
|
|
* 지붕면을 그린다.
|
|
* @param currentLine
|
|
*/
|
|
const drawRoofPlane = (currentLine) => {
|
|
const currentDegree = getDegreeByChon(currentLine.eaves.attributes.pitch)
|
|
const analyze = currentLine.analyze
|
|
|
|
// 현재라인 안쪽의 마루를 filter하여 계산에 사용.
|
|
const innerRidgeLines = findInnerRidge(
|
|
{ x1: analyze.startPoint.x, y1: analyze.startPoint.y, x2: analyze.endPoint.x, y2: analyze.endPoint.y },
|
|
analyze.roofVector,
|
|
ridgeLines,
|
|
1,
|
|
)
|
|
|
|
// 안쪽의 마루가 있는 경우 마루의 연결 포인트를 설정
|
|
if (innerRidgeLines.length > 0) {
|
|
const innerRidgePoints = innerRidgeLines
|
|
.map((innerRidgeLine) => [
|
|
{ x: innerRidgeLine.line.x1, y: innerRidgeLine.line.y1 },
|
|
{ x: innerRidgeLine.line.x2, y: innerRidgeLine.line.y2 },
|
|
])
|
|
.flat()
|
|
const ridgeMinPoint = innerRidgePoints.reduce(
|
|
(min, curr) => (analyze.isHorizontal ? (curr.x < min.x ? curr : min) : curr.y < min.y ? curr : min),
|
|
innerRidgePoints[0],
|
|
)
|
|
const ridgeMaxPoint = innerRidgePoints.reduce(
|
|
(max, curr) => (analyze.isHorizontal ? (curr.x > max.x ? curr : max) : curr.y > max.y ? curr : max),
|
|
innerRidgePoints[0],
|
|
)
|
|
|
|
const roofPlanePoint = []
|
|
innerRidgeLines
|
|
.sort((a, b) => {
|
|
const line1 = a.line
|
|
const line2 = b.line
|
|
if (analyze.isHorizontal) {
|
|
return line1.x1 - line2.x1
|
|
}
|
|
if (analyze.isVertical) {
|
|
return line1.y1 - line2.y1
|
|
}
|
|
})
|
|
.forEach((ridge, index) => {
|
|
const isFirst = index === 0
|
|
const isLast = index === innerRidgeLines.length - 1
|
|
const line = ridge.line
|
|
if (isFirst) {
|
|
roofPlanePoint.push({ x: line.x1, y: line.y1 })
|
|
}
|
|
const nextRidge = innerRidgeLines[index + 1]
|
|
if (nextRidge) {
|
|
const nextLine = nextRidge.line
|
|
let range1, range2
|
|
if (analyze.isHorizontal) {
|
|
// 수평선: x 범위로 비교
|
|
range1 = {
|
|
min: Math.min(line.x1, line.x2),
|
|
max: Math.max(line.x1, line.x2),
|
|
}
|
|
range2 = {
|
|
min: Math.min(nextLine.x1, nextLine.x2),
|
|
max: Math.max(nextLine.x1, nextLine.x2),
|
|
}
|
|
// 겹쳐지는 구간
|
|
const overlapX = [Math.max(range1.min, range2.min), Math.min(range1.max, range2.max)]
|
|
let linePoints = [
|
|
{ x: line.x1, y: line.y1 },
|
|
{ x: line.x2, y: line.y2 },
|
|
{ x: nextLine.x1, y: nextLine.y1 },
|
|
{ x: nextLine.x2, y: nextLine.y2 },
|
|
].filter((point) => overlapX.includes(point.x))
|
|
const firstPoint =
|
|
ridge.dist > nextRidge.dist
|
|
? linePoints.find((point) => point.x === line.x1 || point.x === line.x2)
|
|
: linePoints.find((point) => point.x === nextLine.x1 || point.x === nextLine.x2)
|
|
const lastPoint = linePoints.find((point) => point !== firstPoint)
|
|
roofPlanePoint.push({ x: firstPoint.x, y: firstPoint.y }, { x: firstPoint.x, y: lastPoint.y })
|
|
} else {
|
|
// 수직선: y 범위로 비교
|
|
range1 = {
|
|
min: Math.min(line.y1, line.y2),
|
|
max: Math.max(line.y1, line.y2),
|
|
}
|
|
range2 = {
|
|
min: Math.min(nextLine.y1, nextLine.y2),
|
|
max: Math.max(nextLine.y1, nextLine.y2),
|
|
}
|
|
//겹쳐지는 구간
|
|
const overlapY = [Math.max(range1.min, range2.min), Math.min(range1.max, range2.max)]
|
|
let linePoints = [
|
|
{ x: line.x1, y: line.y1 },
|
|
{ x: line.x2, y: line.y2 },
|
|
{ x: nextLine.x1, y: nextLine.y1 },
|
|
{ x: nextLine.x2, y: nextLine.y2 },
|
|
].filter((point) => overlapY.includes(point.y))
|
|
const firstPoint =
|
|
ridge.dist > nextRidge.dist
|
|
? linePoints.find((point) => point.y === line.y1 || point.y === line.y2)
|
|
: linePoints.find((point) => point.y === nextLine.y1 || point.y === nextLine.y2)
|
|
const lastPoint = linePoints.find((point) => point !== firstPoint)
|
|
roofPlanePoint.push({ x: firstPoint.x, y: firstPoint.y }, { x: lastPoint.x, y: firstPoint.y })
|
|
}
|
|
}
|
|
if (isLast) {
|
|
roofPlanePoint.push({ x: line.x2, y: line.y2 })
|
|
}
|
|
})
|
|
const maxDistRidge = innerRidgeLines.reduce((max, curr) => (curr.dist > max.dist ? curr : max), innerRidgeLines[0])
|
|
// 지붕선에 맞닫는 포인트를 찾아서 지붕선의 모양에 따라 추가 한다.
|
|
let innerRoofLines = roof.lines
|
|
.filter((line) => {
|
|
//1.지붕선이 현재 라인의 안쪽에 있는지 판단
|
|
const tolerance = 1
|
|
const dx = Big(line.x2).minus(Big(line.x1)).toNumber()
|
|
const dy = Big(line.y2).minus(Big(line.y1)).toNumber()
|
|
const normalizedAngle = Math.abs((Math.atan2(dy, dx) * 180) / Math.PI) % 180
|
|
let isHorizontal = false,
|
|
isVertical = false
|
|
if (normalizedAngle < tolerance || normalizedAngle >= 180 - tolerance) {
|
|
isHorizontal = true
|
|
} else if (Math.abs(normalizedAngle - 90) <= tolerance) {
|
|
isVertical = true
|
|
}
|
|
const minX = Math.min(line.x1, line.x2)
|
|
const maxX = Math.max(line.x1, line.x2)
|
|
const minY = Math.min(line.y1, line.y2)
|
|
const maxY = Math.max(line.y1, line.y2)
|
|
return (
|
|
(analyze.isHorizontal && isHorizontal && analyze.startPoint.x <= minX && maxX <= analyze.endPoint.x) ||
|
|
(analyze.isVertical && isVertical && analyze.startPoint.y <= minY && maxY <= analyze.endPoint.y)
|
|
)
|
|
})
|
|
.filter((line) => {
|
|
//2.지붕선이 현재 라인의 바깥에 있는지 확인.
|
|
const dx = Big(line.x2).minus(Big(line.x1)).toNumber()
|
|
const dy = Big(line.y2).minus(Big(line.y1)).toNumber()
|
|
const length = Math.sqrt(dx * dx + dy * dy)
|
|
const lineVector = { x: 0, y: 0 }
|
|
if (analyze.isHorizontal) {
|
|
// lineVector.y = Math.sign(line.y1 - currentLine.eaves.attributes.originPoint.y1)
|
|
lineVector.y = Math.sign(line.y1 - maxDistRidge.line.y1)
|
|
} else if (analyze.isVertical) {
|
|
// lineVector.x = Math.sign(line.x1 - currentLine.eaves.attributes.originPoint.x1)
|
|
lineVector.x = Math.sign(line.x1 - maxDistRidge.line.x1)
|
|
}
|
|
return (
|
|
analyze.roofVector.x === lineVector.x &&
|
|
analyze.roofVector.y === lineVector.y &&
|
|
analyze.directionVector.x === dx / length &&
|
|
analyze.directionVector.y === dy / length
|
|
)
|
|
})
|
|
|
|
// 패턴 방향에 따라 최소지점, 최대지점의 포인트에서 지붕선 방향에 만나는 포인트를 찾기위한 vector
|
|
const checkMinVector = {
|
|
vertex1: { x: ridgeMinPoint.x, y: ridgeMinPoint.y },
|
|
vertex2: { x: ridgeMinPoint.x + analyze.roofVector.x, y: ridgeMinPoint.y + analyze.roofVector.y },
|
|
}
|
|
const checkMaxVector = {
|
|
vertex1: { x: ridgeMaxPoint.x, y: ridgeMaxPoint.y },
|
|
vertex2: { x: ridgeMaxPoint.x + analyze.roofVector.x, y: ridgeMaxPoint.y + analyze.roofVector.y },
|
|
}
|
|
|
|
// 최소, 최대 지점에 만나는 포인트들에 대한 정보
|
|
const roofMinPoint = [],
|
|
roofMaxPoint = []
|
|
|
|
innerRoofLines.forEach((line) => {
|
|
const lineVector = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
|
|
const minIntersect = edgesIntersection(checkMinVector, lineVector)
|
|
const maxIntersect = edgesIntersection(checkMaxVector, lineVector)
|
|
if (minIntersect) {
|
|
const distance = Math.abs(
|
|
analyze.isHorizontal ? ridgeMinPoint.x - Math.min(line.x1, line.x2) : ridgeMinPoint.y - Math.min(line.y1, line.y2),
|
|
)
|
|
roofMinPoint.push({ x: minIntersect.x, y: minIntersect.y, isPointOnLine: isPointOnLineNew(line, minIntersect), distance, line })
|
|
}
|
|
if (maxIntersect) {
|
|
const distance = Math.abs(
|
|
analyze.isHorizontal ? ridgeMaxPoint.x - Math.max(line.x1, line.x2) : ridgeMaxPoint.y - Math.max(line.y1, line.y2),
|
|
)
|
|
roofMaxPoint.push({ x: maxIntersect.x, y: maxIntersect.y, isPointOnLine: isPointOnLineNew(line, maxIntersect), distance, line })
|
|
}
|
|
})
|
|
|
|
// 최소지점, 최대지점에 연결되는 지붕선
|
|
let minRoof = roofMinPoint.find((point) => point.isPointOnLine)
|
|
let maxRoof = roofMaxPoint.find((point) => point.isPointOnLine)
|
|
if (!minRoof) {
|
|
minRoof = roofMinPoint.sort((a, b) => a.distance - b.distance)[0]
|
|
}
|
|
if (!maxRoof) {
|
|
maxRoof = roofMaxPoint.sort((a, b) => a.distance - b.distance)[0]
|
|
}
|
|
|
|
if (minRoof && maxRoof) {
|
|
// 1. 연결되는 지점의 포인트를 사용한다.
|
|
roofPlanePoint.push({ x: minRoof.x, y: minRoof.y }, { x: maxRoof.x, y: maxRoof.y })
|
|
// 2. 최소지점, 최대지점에 연결되는 지붕선이 하나가 아닐경우 연결되는 지점외에 지붕선의 다른 포인트를 추가 해야 한다.
|
|
if (minRoof.line !== maxRoof.line) {
|
|
if (analyze.isHorizontal) {
|
|
Math.abs(minRoof.x - minRoof.line.x1) < Math.abs(minRoof.x - minRoof.line.x2)
|
|
? roofPlanePoint.push({ x: minRoof.line.x2, y: minRoof.line.y2 })
|
|
: roofPlanePoint.push({ x: minRoof.line.x1, y: minRoof.line.y1 })
|
|
Math.abs(maxRoof.x - maxRoof.line.x1) < Math.abs(maxRoof.x - maxRoof.line.x2)
|
|
? roofPlanePoint.push({ x: maxRoof.line.x2, y: maxRoof.line.y2 })
|
|
: roofPlanePoint.push({ x: maxRoof.line.x1, y: maxRoof.line.y1 })
|
|
}
|
|
if (analyze.isVertical) {
|
|
Math.abs(minRoof.y - minRoof.line.y1) < Math.abs(minRoof.y - minRoof.line.y2)
|
|
? roofPlanePoint.push({ x: minRoof.line.x2, y: minRoof.line.y2 })
|
|
: roofPlanePoint.push({ x: minRoof.line.x1, y: minRoof.line.y1 })
|
|
Math.abs(maxRoof.y - maxRoof.line.y1) < Math.abs(maxRoof.y - maxRoof.line.y2)
|
|
? roofPlanePoint.push({ x: maxRoof.line.x2, y: maxRoof.line.y2 })
|
|
: roofPlanePoint.push({ x: maxRoof.line.x1, y: maxRoof.line.y1 })
|
|
}
|
|
// 3.지붕선이 세개 이상일 경우 최소, 최대 연결지점의 지붕선을 제외한 나머지 지붕선은 모든 포인트를 사용한다.
|
|
const otherRoof = innerRoofLines.filter((line) => line !== minRoof.line && line !== maxRoof.line)
|
|
otherRoof.forEach((line) => {
|
|
roofPlanePoint.push({ x: line.x1, y: line.y1 }, { x: line.x2, y: line.y2 })
|
|
})
|
|
}
|
|
}
|
|
|
|
//각 포인트들을 직교하도록 정렬
|
|
const sortedPoints = getSortedOrthogonalPoints(roofPlanePoint)
|
|
sortedPoints.forEach((currPoint, index) => {
|
|
const nextPoint = sortedPoints[(index + 1) % sortedPoints.length]
|
|
const points = [currPoint.x, currPoint.y, nextPoint.x, nextPoint.y]
|
|
const isAlready = ridgeLines.find(
|
|
(ridge) =>
|
|
(Math.abs(ridge.x1 - points[0]) < 1 &&
|
|
Math.abs(ridge.y1 - points[1]) < 1 &&
|
|
Math.abs(ridge.x2 - points[2]) < 1 &&
|
|
Math.abs(ridge.y2 - points[3]) < 1) ||
|
|
(Math.abs(ridge.x1 - points[2]) < 1 &&
|
|
Math.abs(ridge.y1 - points[3]) < 1 &&
|
|
Math.abs(ridge.x2 - points[0]) < 1 &&
|
|
Math.abs(ridge.y2 - points[1]) < 1),
|
|
)
|
|
if (isAlready) {
|
|
return true
|
|
}
|
|
|
|
const tolerance = 1
|
|
const dx = Big(points[2]).minus(Big(points[0])).toNumber()
|
|
const dy = Big(points[3]).minus(Big(points[1])).toNumber()
|
|
const normalizedAngle = Math.abs((Math.atan2(dy, dx) * 180) / Math.PI) % 180
|
|
let isHorizontal = false,
|
|
isVertical = false
|
|
if (normalizedAngle < tolerance || normalizedAngle >= 180 - tolerance) {
|
|
isHorizontal = true
|
|
} else if (Math.abs(normalizedAngle - 90) <= tolerance) {
|
|
isVertical = true
|
|
}
|
|
if (analyze.isHorizontal) {
|
|
//현재라인이 수평선일때
|
|
if (isHorizontal) {
|
|
//같은방향 처리
|
|
innerLines.push(drawRoofLine(points, canvas, roof, textMode))
|
|
} else {
|
|
//다른방향 처리
|
|
innerLines.push(drawHipLine(points, canvas, roof, textMode, currentDegree, currentDegree))
|
|
}
|
|
} else if (analyze.isVertical) {
|
|
//현재라인이 수직선일때
|
|
if (isVertical) {
|
|
//같은방향 처리
|
|
innerLines.push(drawRoofLine(points, canvas, roof, textMode))
|
|
} else {
|
|
//다른방향 처리
|
|
innerLines.push(drawHipLine(points, canvas, roof, textMode, currentDegree, currentDegree))
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
forwardLines.forEach((forward) => {
|
|
drawRoofPlane(forward)
|
|
})
|
|
backwardLines.forEach((backward) => {
|
|
drawRoofPlane(backward)
|
|
})
|
|
roof.innerLines.push(...ridgeLines, ...innerLines)
|
|
canvas
|
|
.getObjects()
|
|
.filter((obj) => obj.name === 'check')
|
|
.forEach((obj) => canvas.remove(obj))
|
|
canvas.renderAll()
|
|
}
|
|
|
|
/**
|
|
* 한쪽흐름 지붕
|
|
* @param roofId
|
|
* @param canvas
|
|
* @param textMode
|
|
*/
|
|
export const drawShedRoof = (roofId, canvas, textMode) => {
|
|
const roof = canvas?.getObjects().find((object) => object.id === roofId)
|
|
const hasNonParallelLines = roof.lines.filter((line) => Math.abs(line.x1 - line.x2) > 1 && Math.abs(line.y1 - line.y2) > 1)
|
|
if (hasNonParallelLines.length > 0) {
|
|
// alert('대각선이 존재합니다.')
|
|
return
|
|
}
|
|
|
|
const sheds = roof.lines.filter((line) => line.attributes?.type === LINE_TYPE.WALLLINE.SHED)
|
|
const gables = roof.lines.filter((line) => line.attributes?.type === LINE_TYPE.WALLLINE.GABLE)
|
|
const eaves = roof.lines.filter((line) => line.attributes?.type === LINE_TYPE.WALLLINE.EAVES)
|
|
|
|
const shedDegree = getDegreeByChon(sheds[0].attributes.pitch)
|
|
|
|
gables.forEach(
|
|
(gable) =>
|
|
(gable.attributes.actualSize = calcLineActualSize(
|
|
{
|
|
x1: gable.x1,
|
|
y1: gable.y1,
|
|
x2: gable.x2,
|
|
y2: gable.y2,
|
|
},
|
|
shedDegree,
|
|
)),
|
|
)
|
|
|
|
const pitchSizeLines = []
|
|
|
|
sheds.forEach((shed) => {
|
|
let points = []
|
|
let x1, y1, x2, y2
|
|
const signX = Math.sign(shed.x1 - shed.x2)
|
|
if (signX !== 0) {
|
|
points.push(shed.x1, shed.x2)
|
|
y1 = shed.y1
|
|
} else {
|
|
points.push(shed.y1, shed.y2)
|
|
x1 = shed.x1
|
|
}
|
|
eaves.forEach((eave) => {
|
|
if (signX !== 0) {
|
|
points.push(eave.x1, eave.x2)
|
|
points.sort((a, b) => a - b)
|
|
x1 = (points[1] + points[2]) / 2
|
|
x2 = (points[1] + points[2]) / 2
|
|
y2 = eave.y1
|
|
} else {
|
|
points.push(eave.y1, eave.y2)
|
|
points.sort((a, b) => a - b)
|
|
y1 = (points[1] + points[2]) / 2
|
|
y2 = (points[1] + points[2]) / 2
|
|
x2 = eave.x1
|
|
}
|
|
points.sort((a, b) => a - b)
|
|
const actualSize = calcLineActualSize({ x1, y1, x2, y2 }, shedDegree)
|
|
|
|
const line = new QLine([x1, y1, x2, y2], {
|
|
parentId: roof.id,
|
|
stroke: '#000000',
|
|
strokeWidth: 2,
|
|
strokeDashArray: [5, 5],
|
|
selectable: false,
|
|
fontSize: roof.fontSize,
|
|
textMode: textMode,
|
|
attributes: {
|
|
roofId: roof.id,
|
|
type: 'pitchSizeLine',
|
|
planeSize: actualSize,
|
|
actualSize: actualSize,
|
|
},
|
|
})
|
|
pitchSizeLines.push(line)
|
|
})
|
|
})
|
|
|
|
const maxLine = pitchSizeLines.reduce((prev, current) => (prev.length > current.length ? prev : current), pitchSizeLines[0])
|
|
canvas.add(maxLine)
|
|
canvas.renderAll()
|
|
}
|
|
|
|
/**
|
|
* 변별로 설정된 지붕을 그린다.
|
|
* @param roofId
|
|
* @param canvas
|
|
* @param textMode
|
|
*/
|
|
export const drawRoofByAttribute = (roofId, canvas, textMode) => {
|
|
const TYPES = { HIP: 'hip', RIDGE: 'ridge', GABLE_LINE: 'gableLine', NEW: 'new' }
|
|
let roof = canvas?.getObjects().find((object) => object.id === roofId)
|
|
const wall = canvas.getObjects().find((object) => object.name === POLYGON_TYPE.WALL && object.attributes.roofId === roofId)
|
|
|
|
const zeroLines = []
|
|
wall.baseLines.forEach((line, index) => (line.attributes.planeSize < EPSILON ? zeroLines.push(index) : null))
|
|
let baseLines = []
|
|
if (zeroLines.length > 0) {
|
|
//형이동으로 인해 뭉쳐지는 라인을 찾는다.
|
|
const mergedLines = []
|
|
zeroLines.forEach((zeroIndex) => {
|
|
const prevIndex = (zeroIndex - 1 + wall.baseLines.length) % wall.baseLines.length
|
|
const nextIndex = (zeroIndex + 1) % wall.baseLines.length
|
|
mergedLines.push({ prevIndex, nextIndex })
|
|
})
|
|
|
|
const proceedPair = []
|
|
wall.baseLines.forEach((line, index) => {
|
|
//뭉쳐지는 라인이면 하나로 합치고 baseLines에 추가. 아니면 baseLines에 바로 추가.
|
|
if (!zeroLines.includes(index) && !mergedLines.find((mergedLine) => mergedLine.prevIndex === index || mergedLine.nextIndex === index)) {
|
|
baseLines.push(line)
|
|
} else {
|
|
if (zeroLines.includes(index)) {
|
|
mergedLines.forEach((indexPair) => {
|
|
//이미 처리된 라인이편 패스
|
|
if (proceedPair.includes(indexPair.prevIndex) || proceedPair.includes(indexPair.nextIndex)) return
|
|
let prevLine = wall.baseLines[indexPair.prevIndex]
|
|
const lineVector = { x: Math.sign(prevLine.x2 - prevLine.x1), y: Math.sign(prevLine.y2 - prevLine.y1) }
|
|
|
|
//중복되는 지붕선을 병합한다. 라인 vector가 같아야한다.
|
|
const indexList = [indexPair.prevIndex, indexPair.nextIndex]
|
|
mergedLines
|
|
.filter((pair) => pair !== indexPair)
|
|
.forEach((pair) => {
|
|
const pLine = wall.baseLines[pair.prevIndex]
|
|
const nLine = wall.baseLines[pair.nextIndex]
|
|
const pVector = { x: Math.sign(pLine.x2 - pLine.x1), y: Math.sign(pLine.y2 - pLine.y1) }
|
|
const nVector = { x: Math.sign(nLine.x2 - nLine.x1), y: Math.sign(nLine.y2 - nLine.y1) }
|
|
if (
|
|
pVector.x === lineVector.x &&
|
|
pVector.y === lineVector.y &&
|
|
nVector.x === lineVector.x &&
|
|
nVector.y === lineVector.y &&
|
|
(indexList.includes(pair.prevIndex) || indexList.includes(pair.nextIndex))
|
|
) {
|
|
indexList.push(pair.prevIndex, pair.nextIndex)
|
|
}
|
|
})
|
|
|
|
const startLine = wall.baseLines[Math.min(...indexList)]
|
|
const endLine = wall.baseLines[Math.max(...indexList)]
|
|
const points = [startLine.x1, startLine.y1, endLine.x2, endLine.y2]
|
|
const size = calcLinePlaneSize({ x1: points[0], y1: points[1], x2: points[2], y2: points[3] })
|
|
|
|
//라인 좌표 조정.
|
|
startLine.set({
|
|
x1: points[0],
|
|
y1: points[1],
|
|
x2: points[2],
|
|
y2: points[3],
|
|
startPoint: { x: points[0], y: points[1] },
|
|
endPoint: { x: points[2], y: points[3] },
|
|
})
|
|
startLine.setCoords()
|
|
startLine.attributes.planeSize = size
|
|
startLine.attributes.actualSize = size
|
|
startLine.fire('setLength')
|
|
|
|
//처리된 index 추가.
|
|
proceedPair.push(...indexList)
|
|
//조정된라인 baseLine에 추가.
|
|
baseLines.push(startLine)
|
|
})
|
|
}
|
|
}
|
|
})
|
|
} else {
|
|
baseLines = wall.baseLines
|
|
}
|
|
const baseLinePoints = baseLines.map((line) => ({ x: line.x1, y: line.y1 }))
|
|
const checkWallPolygon = new QPolygon(baseLinePoints, {})
|
|
|
|
let innerLines = []
|
|
|
|
/** 벽취합이 있는 경우 소매가 있다면 지붕 형상을 변경해야 한다. */
|
|
baseLines
|
|
.filter((line) => line.attributes.type === LINE_TYPE.WALLLINE.WALL && line.attributes.offset > 0)
|
|
.forEach((currentLine) => {
|
|
const prevLine = baseLines.find((line) => line.x2 === currentLine.x1 && line.y2 === currentLine.y1)
|
|
const nextLine = baseLines.find((line) => line.x1 === currentLine.x2 && line.y1 === currentLine.y2)
|
|
|
|
const currentMidX = Big(currentLine.x1).plus(Big(currentLine.x2)).div(2).toNumber()
|
|
const currentMidY = Big(currentLine.y1).plus(Big(currentLine.y2)).div(2).toNumber()
|
|
const currentVectorX = Math.sign(currentLine.x2 - currentLine.x1)
|
|
const currentVectorY = Math.sign(currentLine.y2 - currentLine.y1)
|
|
|
|
/** 현재 라인의 지붕 라인을 찾는다. */
|
|
const intersectionRoofs = []
|
|
let currentRoof
|
|
if (currentVectorX === 0) {
|
|
const checkEdge = {
|
|
vertex1: { x: prevLine.x1, y: currentMidY },
|
|
vertex2: { x: currentMidX, y: currentMidY },
|
|
}
|
|
roof.lines
|
|
.filter((line) => Math.sign(line.x2 - line.x1) === currentVectorX && Math.sign(line.y2 - line.y1) === currentVectorY)
|
|
.forEach((line) => {
|
|
const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
|
|
const intersection = edgesIntersection(checkEdge, lineEdge)
|
|
if (intersection) {
|
|
if (isPointOnLine(line, intersection)) {
|
|
intersectionRoofs.push({
|
|
line,
|
|
intersection,
|
|
size: Big(intersection.x).minus(currentMidX).abs().pow(2).plus(Big(intersection.y).minus(currentMidY).abs().pow(2)).sqrt(),
|
|
})
|
|
}
|
|
}
|
|
})
|
|
} else {
|
|
const checkEdge = {
|
|
vertex1: { x: currentMidX, y: prevLine.y1 },
|
|
vertex2: { x: currentMidX, y: currentMidY },
|
|
}
|
|
roof.lines
|
|
.filter((line) => Math.sign(line.x2 - line.x1) === currentVectorX && Math.sign(line.y2 - line.y1) === currentVectorY)
|
|
.forEach((line) => {
|
|
const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
|
|
const intersection = edgesIntersection(checkEdge, lineEdge)
|
|
if (intersection) {
|
|
if (isPointOnLine(line, intersection)) {
|
|
intersectionRoofs.push({
|
|
line,
|
|
intersection,
|
|
size: Big(intersection.x).minus(currentMidX).abs().pow(2).plus(Big(intersection.y).minus(currentMidY).abs().pow(2)).sqrt(),
|
|
})
|
|
}
|
|
}
|
|
})
|
|
}
|
|
if (intersectionRoofs.length > 0) {
|
|
currentRoof = intersectionRoofs.sort((a, b) => a.size - b.size)[0].line
|
|
}
|
|
if (currentRoof) {
|
|
const prevRoof = roof.lines.find((line) => line.x2 === currentRoof.x1 && line.y2 === currentRoof.y1)
|
|
const nextRoof = roof.lines.find((line) => line.x1 === currentRoof.x2 && line.y1 === currentRoof.y2)
|
|
|
|
const prevOffset = prevLine.attributes.offset
|
|
const nextOffset = nextLine.attributes.offset
|
|
|
|
currentRoof.set({ x1: currentLine.x1, y1: currentLine.y1, x2: currentLine.x2, y2: currentLine.y2 })
|
|
|
|
if (prevLine.attributes.type !== LINE_TYPE.WALLLINE.WALL && prevOffset > 0) {
|
|
const addPoint1 = []
|
|
const addPoint2 = []
|
|
if (Math.sign(prevLine.y2 - prevLine.y1) === 0) {
|
|
addPoint1.push(prevRoof.x2, prevRoof.y2, prevRoof.x2, currentRoof.y1)
|
|
addPoint2.push(addPoint1[2], addPoint1[3], currentRoof.x1, currentRoof.y1)
|
|
} else {
|
|
addPoint1.push(prevRoof.x2, prevRoof.y2, currentRoof.x1, prevRoof.y2)
|
|
addPoint2.push(addPoint1[2], addPoint1[3], currentRoof.x1, currentRoof.y1)
|
|
}
|
|
const addRoofLine1 = new QLine(addPoint1, {
|
|
name: 'addRoofLine',
|
|
parentId: roof.id,
|
|
fontSize: roof.fontSize,
|
|
stroke: '#1083E3',
|
|
strokeWidth: 2,
|
|
textMode: textMode,
|
|
attributes: {
|
|
roofId: roofId,
|
|
type: LINE_TYPE.WALLLINE.ETC,
|
|
planeSize: calcLinePlaneSize({
|
|
x1: addPoint1[0],
|
|
y1: addPoint1[1],
|
|
x2: addPoint1[2],
|
|
y2: addPoint1[3],
|
|
}),
|
|
actualSize: calcLinePlaneSize({
|
|
x1: addPoint1[0],
|
|
y1: addPoint1[1],
|
|
x2: addPoint1[2],
|
|
y2: addPoint1[3],
|
|
}),
|
|
},
|
|
})
|
|
|
|
const addRoofLine2 = new QLine(addPoint2, {
|
|
name: 'addRoofLine',
|
|
parentId: roof.id,
|
|
fontSize: roof.fontSize,
|
|
stroke: '#1083E3',
|
|
strokeWidth: 2,
|
|
textMode: textMode,
|
|
attributes: {
|
|
roofId: roofId,
|
|
type: LINE_TYPE.WALLLINE.ETC,
|
|
planeSize: calcLinePlaneSize({
|
|
x1: addPoint2[0],
|
|
y1: addPoint2[1],
|
|
x2: addPoint2[2],
|
|
y2: addPoint2[3],
|
|
}),
|
|
actualSize: calcLinePlaneSize({
|
|
x1: addPoint2[0],
|
|
y1: addPoint2[1],
|
|
x2: addPoint2[2],
|
|
y2: addPoint2[3],
|
|
}),
|
|
},
|
|
})
|
|
canvas.add(addRoofLine1, addRoofLine2)
|
|
canvas.renderAll()
|
|
|
|
const prevIndex = roof.lines.indexOf(prevRoof)
|
|
if (prevIndex === roof.lines.length - 1) {
|
|
roof.lines.unshift(addRoofLine1, addRoofLine2)
|
|
} else {
|
|
roof.lines.splice(prevIndex + 1, 0, addRoofLine1, addRoofLine2)
|
|
}
|
|
} else if (prevLine.attributes.type === LINE_TYPE.WALLLINE.WALL && prevOffset > 0) {
|
|
if (Math.sign(prevLine.y2 - prevLine.y1) === 0) {
|
|
prevRoof.set({ x2: currentLine.x1, y2: prevRoof.y1 })
|
|
} else {
|
|
prevRoof.set({ x2: prevRoof.x1, y2: currentLine.y1 })
|
|
}
|
|
currentRoof.set({ x1: prevRoof.x2, y1: prevRoof.y2 })
|
|
} else if (prevLine.attributes.type === LINE_TYPE.WALLLINE.WALL || prevOffset === 0) {
|
|
prevRoof.set({ x2: currentLine.x1, y2: currentLine.y1 })
|
|
}
|
|
if (nextLine.attributes.type !== LINE_TYPE.WALLLINE.WALL && nextOffset > 0) {
|
|
const addPoint1 = []
|
|
const addPoint2 = []
|
|
if (Math.sign(nextLine.y2 - nextLine.y1) === 0) {
|
|
addPoint1.push(currentRoof.x2, currentRoof.y2, nextRoof.x1, currentRoof.y2)
|
|
addPoint2.push(addPoint1[2], addPoint1[3], nextRoof.x1, nextRoof.y1)
|
|
} else {
|
|
addPoint1.push(currentRoof.x2, currentRoof.y2, currentRoof.x2, nextRoof.y1)
|
|
addPoint2.push(addPoint1[2], addPoint1[3], nextRoof.x1, nextRoof.y1)
|
|
}
|
|
|
|
const addRoofLine1 = new QLine(addPoint1, {
|
|
name: 'addRoofLine',
|
|
parentId: roof.id,
|
|
fontSize: roof.fontSize,
|
|
stroke: '#1083E3',
|
|
strokeWidth: 2,
|
|
textMode: textMode,
|
|
attributes: {
|
|
roofId: roofId,
|
|
type: LINE_TYPE.WALLLINE.ETC,
|
|
planeSize: calcLinePlaneSize({
|
|
x1: addPoint1[0],
|
|
y1: addPoint1[1],
|
|
x2: addPoint1[2],
|
|
y2: addPoint1[3],
|
|
}),
|
|
actualSize: calcLinePlaneSize({
|
|
x1: addPoint1[0],
|
|
y1: addPoint1[1],
|
|
x2: addPoint1[2],
|
|
y2: addPoint1[3],
|
|
}),
|
|
},
|
|
})
|
|
|
|
const addRoofLine2 = new QLine(addPoint2, {
|
|
name: 'addRoofLine',
|
|
parentId: roof.id,
|
|
fontSize: roof.fontSize,
|
|
stroke: '#1083E3',
|
|
strokeWidth: 2,
|
|
textMode: textMode,
|
|
attributes: {
|
|
roofId: roofId,
|
|
type: LINE_TYPE.WALLLINE.ETC,
|
|
planeSize: calcLinePlaneSize({
|
|
x1: addPoint2[0],
|
|
y1: addPoint2[1],
|
|
x2: addPoint2[2],
|
|
y2: addPoint2[3],
|
|
}),
|
|
actualSize: calcLinePlaneSize({
|
|
x1: addPoint2[0],
|
|
y1: addPoint2[1],
|
|
x2: addPoint2[2],
|
|
y2: addPoint2[3],
|
|
}),
|
|
},
|
|
})
|
|
canvas.add(addRoofLine1, addRoofLine2)
|
|
canvas.renderAll()
|
|
|
|
const nextIndex = roof.lines.indexOf(nextRoof)
|
|
if (nextIndex === 0) {
|
|
roof.lines.push(addRoofLine1, addRoofLine2)
|
|
} else {
|
|
roof.lines.splice(nextIndex, 0, addRoofLine1, addRoofLine2)
|
|
}
|
|
} else if (nextLine.attributes.type === LINE_TYPE.WALLLINE.WALL && nextOffset > 0) {
|
|
if (Math.sign(nextLine.y2 - nextLine.y1) === 0) {
|
|
nextRoof.set({ x1: currentLine.x2, y1: nextRoof.y1 })
|
|
} else {
|
|
nextRoof.set({ x1: nextRoof.x1, y1: currentLine.y2 })
|
|
}
|
|
currentRoof.set({ x2: nextRoof.x1, y2: nextRoof.y1 })
|
|
} else if (nextLine.attributes.type === LINE_TYPE.WALLLINE.WALL || prevOffset === 0) {
|
|
nextRoof.set({ x1: currentLine.x2, y1: currentLine.y2 })
|
|
}
|
|
|
|
roof = reDrawPolygon(roof, canvas)
|
|
}
|
|
})
|
|
|
|
/**
|
|
* 라인의 속성을 분석한다.
|
|
* @param line
|
|
* @returns {{startPoint: {x: number, y}, endPoint: {x: number, y}, length: number, angleDegree: number, normalizedAngle: number, isHorizontal: boolean, isVertical: boolean, isDiagonal: boolean, directionVector: {x: number, y: number}, roofLine: *, roofVector: {x: number, y: number}}}
|
|
*/
|
|
const analyzeLine = (line) => {
|
|
const tolerance = 1
|
|
const dx = Big(line.x2).minus(Big(line.x1)).toNumber()
|
|
const dy = Big(line.y2).minus(Big(line.y1)).toNumber()
|
|
const length = Math.sqrt(dx * dx + dy * dy)
|
|
const angleDegree = (Math.atan2(dy, dx) * 180) / Math.PI
|
|
const normalizedAngle = Math.abs((Math.atan2(dy, dx) * 180) / Math.PI) % 180
|
|
const directionVector = { x: dx / length, y: dy / length }
|
|
let isHorizontal = false,
|
|
isVertical = false,
|
|
isDiagonal = false
|
|
if (normalizedAngle < tolerance || normalizedAngle >= 180 - tolerance) {
|
|
isHorizontal = true
|
|
} else if (Math.abs(normalizedAngle - 90) <= tolerance) {
|
|
isVertical = true
|
|
} else {
|
|
isDiagonal = true
|
|
}
|
|
|
|
const originPoint = line.attributes.originPoint
|
|
const midX = (originPoint.x1 + originPoint.x2) / 2
|
|
const midY = (originPoint.y1 + originPoint.y2) / 2
|
|
const offset = line.attributes.offset
|
|
|
|
const checkRoofLines = roof.lines.filter((roof) => {
|
|
const roofDx = Big(roof.x2).minus(Big(roof.x1)).toNumber()
|
|
const roofDy = Big(roof.y2).minus(Big(roof.y1)).toNumber()
|
|
const roofLength = Math.sqrt(roofDx * roofDx + roofDy * roofDy)
|
|
const roofVector = { x: roofDx / roofLength, y: roofDy / roofLength }
|
|
return directionVector.x === roofVector.x && directionVector.y === roofVector.y
|
|
})
|
|
|
|
let roofVector = { x: 0, y: 0 }
|
|
if (isHorizontal) {
|
|
const checkPoint = { x: midX, y: midY + offset }
|
|
if (wall.inPolygon(checkPoint)) {
|
|
roofVector = { x: 0, y: -1 }
|
|
} else {
|
|
roofVector = { x: 0, y: 1 }
|
|
}
|
|
}
|
|
if (isVertical) {
|
|
const checkPoint = { x: midX + offset, y: midY }
|
|
if (wall.inPolygon(checkPoint)) {
|
|
roofVector = { x: -1, y: 0 }
|
|
} else {
|
|
roofVector = { x: 1, y: 0 }
|
|
}
|
|
}
|
|
|
|
const findEdge = { vertex1: { x: midX, y: midY }, vertex2: { x: midX + roofVector.x * offset, y: midY + roofVector.y * offset } }
|
|
const edgeDx =
|
|
Big(findEdge.vertex2.x).minus(Big(findEdge.vertex1.x)).abs().toNumber() < 0.1
|
|
? 0
|
|
: Big(findEdge.vertex2.x).minus(Big(findEdge.vertex1.x)).toNumber()
|
|
const edgeDy =
|
|
Big(findEdge.vertex2.y).minus(Big(findEdge.vertex1.y)).abs().toNumber() < 0.1
|
|
? 0
|
|
: Big(findEdge.vertex2.y).minus(Big(findEdge.vertex1.y)).toNumber()
|
|
const edgeLength = Math.sqrt(edgeDx * edgeDx + edgeDy * edgeDy)
|
|
const edgeVector = { x: edgeDx / edgeLength, y: edgeDy / edgeLength }
|
|
|
|
const intersectRoofLines = []
|
|
checkRoofLines.forEach((roofLine) => {
|
|
const lineEdge = { vertex1: { x: roofLine.x1, y: roofLine.y1 }, vertex2: { x: roofLine.x2, y: roofLine.y2 } }
|
|
const intersect = edgesIntersection(lineEdge, findEdge)
|
|
if (intersect) {
|
|
const intersectDx =
|
|
Big(intersect.x).minus(Big(findEdge.vertex1.x)).abs().toNumber() < 0.1 ? 0 : Big(intersect.x).minus(Big(findEdge.vertex1.x)).toNumber()
|
|
const intersectDy =
|
|
Big(intersect.y).minus(Big(findEdge.vertex1.y)).abs().toNumber() < 0.1 ? 0 : Big(intersect.y).minus(Big(findEdge.vertex1.y)).toNumber()
|
|
const intersectLength = Math.sqrt(intersectDx * intersectDx + intersectDy * intersectDy)
|
|
const intersectVector = { x: intersectDx / intersectLength, y: intersectDy / intersectLength }
|
|
if (edgeVector.x === intersectVector.x && edgeVector.y === intersectVector.y) {
|
|
intersectRoofLines.push({ roofLine, intersect, length: intersectLength })
|
|
}
|
|
}
|
|
})
|
|
|
|
intersectRoofLines.sort((a, b) => a.length - b.length)
|
|
let currentRoof = intersectRoofLines.find((roof) => isPointOnLineNew(roof.roofLine, roof.intersect) && roof.length - offset < 0.1)
|
|
if (!currentRoof) {
|
|
currentRoof = intersectRoofLines[0]
|
|
}
|
|
|
|
let startPoint, endPoint
|
|
if (isHorizontal) {
|
|
startPoint = { x: Math.min(line.x1, line.x2, currentRoof.roofLine.x1, currentRoof.roofLine.x2), y: line.y1 }
|
|
endPoint = { x: Math.max(line.x1, line.x2, currentRoof.roofLine.x1, currentRoof.roofLine.x2), y: line.y2 }
|
|
}
|
|
if (isVertical) {
|
|
startPoint = { x: line.x1, y: Math.min(line.y1, line.y2, currentRoof.roofLine.y1, currentRoof.roofLine.y2) }
|
|
endPoint = { x: line.x2, y: Math.max(line.y1, line.y2, currentRoof.roofLine.y1, currentRoof.roofLine.y2) }
|
|
}
|
|
if (isDiagonal) {
|
|
startPoint = { x: line.x1, y: line.y1 }
|
|
endPoint = { x: line.x2, y: line.y2 }
|
|
}
|
|
|
|
return {
|
|
startPoint,
|
|
endPoint,
|
|
length,
|
|
angleDegree,
|
|
normalizedAngle,
|
|
isHorizontal,
|
|
isVertical,
|
|
isDiagonal,
|
|
directionVector: { x: dx / length, y: dy / length },
|
|
roofLine: currentRoof.roofLine,
|
|
roofVector,
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param testPoint
|
|
* @param prevLine
|
|
* @param nextLine
|
|
* @param baseLines
|
|
*/
|
|
const getRidgeDrivePoint = (testPoint = [], prevLine, nextLine, baseLines) => {
|
|
if (prevLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES && nextLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES) return null
|
|
let prevPoint = null,
|
|
nextPoint = null
|
|
|
|
const baseLinePoints = baseLines.map((line) => ({ x: line.x1, y: line.y1 }))
|
|
const checkWallPolygon = new QPolygon(baseLinePoints, {})
|
|
const checkEdge = { vertex1: { x: testPoint[0], y: testPoint[1] }, vertex2: { x: testPoint[2], y: testPoint[3] } }
|
|
|
|
let prevHasGable = false,
|
|
nextHasGable = false
|
|
if (prevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) {
|
|
const index = baseLines.findIndex((line) => line === prevLine)
|
|
const beforePrevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length]
|
|
if (!beforePrevLine) {
|
|
prevPoint = null
|
|
} else if (beforePrevLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) {
|
|
const analyze = analyzeLine(beforePrevLine)
|
|
const roofEdge = { vertex1: { x: analyze.roofLine.x1, y: analyze.roofLine.y1 }, vertex2: { x: analyze.roofLine.x2, y: analyze.roofLine.y2 } }
|
|
const intersection = edgesIntersection(checkEdge, roofEdge)
|
|
if (intersection) {
|
|
prevHasGable = true
|
|
const distance = Math.sqrt((intersection.x - testPoint[0]) ** 2 + (intersection.y - testPoint[1]) ** 2)
|
|
prevPoint = { intersection, distance }
|
|
}
|
|
} else if (beforePrevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) {
|
|
let prevVector = getHalfAngleVector(beforePrevLine, prevLine)
|
|
|
|
const prevCheckPoint = {
|
|
x: prevLine.x1 + prevVector.x * 10,
|
|
y: prevLine.y1 + prevVector.y * 10,
|
|
}
|
|
|
|
let prevHipVector
|
|
|
|
if (checkWallPolygon.inPolygon(prevCheckPoint)) {
|
|
prevHipVector = { x: prevVector.x, y: prevVector.y }
|
|
} else {
|
|
prevHipVector = { x: -prevVector.x, y: -prevVector.y }
|
|
}
|
|
|
|
const prevHipEdge = {
|
|
vertex1: { x: prevLine.x1, y: prevLine.y1 },
|
|
vertex2: { x: prevLine.x1 + prevHipVector.x * 10, y: prevLine.y1 + prevHipVector.y * 10 },
|
|
}
|
|
|
|
const intersection = edgesIntersection(prevHipEdge, checkEdge)
|
|
if (intersection) {
|
|
const checkVector = { x: Math.sign(testPoint[0] - testPoint[2]), y: Math.sign(testPoint[1] - testPoint[3]) }
|
|
const intersectVector = { x: Math.sign(testPoint[0] - intersection.x), y: Math.sign(testPoint[1] - intersection.y) }
|
|
if (checkVector.x === intersectVector.x && checkVector.y === intersectVector.y) {
|
|
const distance = Math.sqrt((intersection.x - testPoint[0]) ** 2 + (intersection.y - testPoint[1]) ** 2)
|
|
prevPoint = { intersection, distance }
|
|
}
|
|
|
|
if (almostEqual(intersection.x, testPoint[0]) && almostEqual(intersection.y, testPoint[1])) {
|
|
prevPoint = { intersection, distance: 0 }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (nextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) {
|
|
const index = baseLines.findIndex((line) => line === nextLine)
|
|
const afterNextLine = baseLines[(index + 1) % baseLines.length]
|
|
if (!afterNextLine) {
|
|
nextPoint = null
|
|
} else if (afterNextLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) {
|
|
const analyze = analyzeLine(afterNextLine)
|
|
const roofEdge = { vertex1: { x: analyze.roofLine.x1, y: analyze.roofLine.y1 }, vertex2: { x: analyze.roofLine.x2, y: analyze.roofLine.y2 } }
|
|
const intersection = edgesIntersection(checkEdge, roofEdge)
|
|
if (intersection) {
|
|
nextHasGable = true
|
|
const distance = Math.sqrt((intersection.x - testPoint[2]) ** 2 + (intersection.y - testPoint[3]) ** 2)
|
|
nextPoint = { intersection, distance }
|
|
}
|
|
} else if (afterNextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) {
|
|
let nextVector = getHalfAngleVector(nextLine, afterNextLine)
|
|
const nextCheckPoint = {
|
|
x: nextLine.x2 + nextVector.x * 10,
|
|
y: nextLine.y2 + nextVector.y * 10,
|
|
}
|
|
let nextHipVector
|
|
if (checkWallPolygon.inPolygon(nextCheckPoint)) {
|
|
nextHipVector = { x: nextVector.x, y: nextVector.y }
|
|
} else {
|
|
nextHipVector = { x: -nextVector.x, y: -nextVector.y }
|
|
}
|
|
|
|
const nextHipEdge = {
|
|
vertex1: { x: nextLine.x2, y: nextLine.y2 },
|
|
vertex2: { x: nextLine.x2 + nextHipVector.x * 10, y: nextLine.y2 + nextHipVector.y * 10 },
|
|
}
|
|
const intersection = edgesIntersection(nextHipEdge, checkEdge)
|
|
if (intersection) {
|
|
const checkVector = { x: Math.sign(testPoint[0] - testPoint[2]), y: Math.sign(testPoint[1] - testPoint[3]) }
|
|
const intersectVector = { x: Math.sign(testPoint[0] - intersection.x), y: Math.sign(testPoint[1] - intersection.y) }
|
|
if (checkVector.x === intersectVector.x && checkVector.y === intersectVector.y) {
|
|
const distance = Math.sqrt((intersection.x - testPoint[0]) ** 2 + (intersection.y - testPoint[1]) ** 2)
|
|
nextPoint = { intersection, distance }
|
|
}
|
|
if (almostEqual(intersection.x, testPoint[0]) && almostEqual(intersection.y, testPoint[1])) {
|
|
nextPoint = { intersection, distance: 0 }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (prevHasGable || nextHasGable) {
|
|
const prevLength = Math.sqrt((prevLine.x1 - prevLine.x2) ** 2 + (prevLine.y1 - prevLine.y2) ** 2)
|
|
const nextLength = Math.sqrt((nextLine.x1 - nextLine.x2) ** 2 + (nextLine.y1 - nextLine.y2) ** 2)
|
|
if (prevPoint && prevHasGable && prevLength <= nextLength) {
|
|
return prevPoint.intersection
|
|
}
|
|
if (nextPoint && nextHasGable && prevLength > nextLength) {
|
|
return nextPoint.intersection
|
|
}
|
|
}
|
|
|
|
if (prevPoint && nextPoint) {
|
|
return prevPoint.distance < nextPoint.distance ? prevPoint.intersection : nextPoint.intersection
|
|
} else if (prevPoint) {
|
|
return prevPoint.intersection
|
|
} else if (nextPoint) {
|
|
return nextPoint.intersection
|
|
} else {
|
|
return null
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 지붕의 모양을 판단하여 각 변에 맞는 라인을 처리 한다.
|
|
*/
|
|
const sheds = []
|
|
const hipAndGables = []
|
|
const eaves = []
|
|
const jerkinHeads = []
|
|
const gables = []
|
|
baseLines.forEach((baseLine) => {
|
|
switch (baseLine.attributes.type) {
|
|
case LINE_TYPE.WALLLINE.SHED:
|
|
sheds.push(baseLine)
|
|
break
|
|
case LINE_TYPE.WALLLINE.HIPANDGABLE:
|
|
hipAndGables.push(baseLine)
|
|
break
|
|
case LINE_TYPE.WALLLINE.EAVES:
|
|
eaves.push(baseLine)
|
|
break
|
|
case LINE_TYPE.WALLLINE.JERKINHEAD:
|
|
jerkinHeads.push(baseLine)
|
|
break
|
|
case LINE_TYPE.WALLLINE.GABLE:
|
|
gables.push(baseLine)
|
|
break
|
|
default:
|
|
break
|
|
}
|
|
})
|
|
|
|
//지붕선 내부에 보조선을 그리기 위한 analysis
|
|
let linesAnalysis = []
|
|
|
|
//1. 한쪽흐름(단면경사) 판단, 단면경사는 양옆이 케라바(일반)여야 한다. 맞은편 지붕이 처마여야 한다.
|
|
sheds.forEach((currentLine) => {
|
|
let prevLine, nextLine
|
|
baseLines.forEach((baseLine, index) => {
|
|
if (baseLine === currentLine) {
|
|
nextLine = baseLines[(index + 1) % baseLines.length]
|
|
prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length]
|
|
}
|
|
})
|
|
|
|
const analyze = analyzeLine(currentLine)
|
|
|
|
//양옆이 케라바가 아닐때 제외
|
|
if (prevLine.attributes.type !== LINE_TYPE.WALLLINE.GABLE || nextLine.attributes.type !== LINE_TYPE.WALLLINE.GABLE) {
|
|
return
|
|
}
|
|
|
|
const eavesLines = []
|
|
|
|
// 맞은편 처마 지붕을 찾는다. 흐름 각도를 확인하기 위함.
|
|
baseLines
|
|
.filter((baseLine) => baseLine.attributes.type === LINE_TYPE.WALLLINE.EAVES)
|
|
.forEach((baseLine) => {
|
|
const lineAnalyze = analyzeLine(baseLine)
|
|
//방향이 맞지 않으면 패스
|
|
if (
|
|
analyze.isHorizontal !== lineAnalyze.isHorizontal ||
|
|
analyze.isVertical !== lineAnalyze.isVertical ||
|
|
analyze.isDiagonal ||
|
|
lineAnalyze.isDiagonal
|
|
) {
|
|
return false
|
|
}
|
|
|
|
// 수평 일때
|
|
if (
|
|
analyze.isHorizontal &&
|
|
Math.min(baseLine.x1, baseLine.x2) <= Math.min(currentLine.x1, currentLine.x2) &&
|
|
Math.max(baseLine.x1, baseLine.x2) >= Math.max(currentLine.x1, currentLine.x2)
|
|
) {
|
|
eavesLines.push(baseLine)
|
|
}
|
|
|
|
//수직 일때
|
|
if (
|
|
analyze.isVertical &&
|
|
Math.min(baseLine.y1, baseLine.y2) <= Math.min(currentLine.y1, currentLine.y2) &&
|
|
Math.max(baseLine.y1, baseLine.y2) >= Math.max(currentLine.y1, currentLine.y2)
|
|
) {
|
|
eavesLines.push(baseLine)
|
|
}
|
|
})
|
|
|
|
let shedDegree = getDegreeByChon(4)
|
|
|
|
if (eavesLines.length > 0) {
|
|
shedDegree = getDegreeByChon(eavesLines[0].attributes.pitch)
|
|
}
|
|
|
|
//지붕좌표1에서 지붕 안쪽으로의 확인 방향
|
|
const checkRoofVector1 = {
|
|
vertex1: { x: analyze.roofLine.x1, y: analyze.roofLine.y1 },
|
|
vertex2: { x: analyze.roofLine.x1 + analyze.roofVector.x * -1, y: analyze.roofLine.y1 + analyze.roofVector.y * -1 },
|
|
}
|
|
//지붕좌표2에서 지붕 안쪽으로의 확인 방향
|
|
const checkRoofVector2 = {
|
|
vertex1: { x: analyze.roofLine.x2, y: analyze.roofLine.y2 },
|
|
vertex2: { x: analyze.roofLine.x2 + analyze.roofVector.x * -1, y: analyze.roofLine.y2 + analyze.roofVector.y * -1 },
|
|
}
|
|
|
|
//좌표1에서의 교차점
|
|
const intersectPoints1 = []
|
|
//좌료2에서의 교차점
|
|
const intersectPoints2 = []
|
|
roof.lines
|
|
.filter((roofLine) => roofLine !== analyze.roofLine) // 같은 지붕선 제외
|
|
.filter((roofLine) => {
|
|
const tolerance = 1
|
|
const dx = Big(roofLine.x2).minus(Big(roofLine.x1)).toNumber()
|
|
const dy = Big(roofLine.y2).minus(Big(roofLine.y1)).toNumber()
|
|
const angleDegree = (Math.atan2(dy, dx) * 180) / Math.PI
|
|
const normalizedAngle = Math.abs((Math.atan2(dy, dx) * 180) / Math.PI) % 180
|
|
let isHorizontal = false,
|
|
isVertical = false,
|
|
isDiagonal = false
|
|
if (normalizedAngle < tolerance || normalizedAngle >= 180 - tolerance) {
|
|
isHorizontal = true
|
|
} else if (Math.abs(normalizedAngle - 90) <= tolerance) {
|
|
isVertical = true
|
|
} else {
|
|
isDiagonal = true
|
|
}
|
|
let otherSide = false
|
|
|
|
// 수평 일때 반대쪽 라인이 현재 라인을 포함하는지 확인.
|
|
if (
|
|
analyze.isHorizontal &&
|
|
((Math.min(roofLine.x1, roofLine.x2) <= Math.min(analyze.roofLine.x1, analyze.roofLine.x2) &&
|
|
Math.max(roofLine.x1, roofLine.x2) >= Math.max(analyze.roofLine.x1, analyze.roofLine.x2)) ||
|
|
(Math.min(analyze.roofLine.x1, analyze.roofLine.x2) <= Math.min(roofLine.x1, roofLine.x2) &&
|
|
Math.max(analyze.roofLine.x1, analyze.roofLine.x2) >= Math.max(roofLine.x1, roofLine.x2))) &&
|
|
angleDegree !== analyze.angleDegree
|
|
) {
|
|
otherSide = true
|
|
}
|
|
|
|
//수직 일때 반대쪽 라인이 현재 라인을 포함하는지 확인.
|
|
if (
|
|
analyze.isVertical &&
|
|
((Math.min(roofLine.y1, roofLine.y2) <= Math.min(analyze.roofLine.y1, analyze.roofLine.y2) &&
|
|
Math.max(roofLine.y1, roofLine.y2) >= Math.max(analyze.roofLine.y1, analyze.roofLine.y2)) ||
|
|
(Math.min(analyze.roofLine.y1, analyze.roofLine.y2) <= Math.min(roofLine.y1, roofLine.y2) &&
|
|
Math.max(analyze.roofLine.y1, analyze.roofLine.y2) >= Math.max(roofLine.y1, roofLine.y2))) &&
|
|
angleDegree !== analyze.angleDegree
|
|
) {
|
|
otherSide = true
|
|
}
|
|
|
|
return analyze.isHorizontal === isHorizontal && analyze.isVertical === isVertical && analyze.isDiagonal === isDiagonal && otherSide
|
|
})
|
|
.forEach((roofLine) => {
|
|
const lineEdge = { vertex1: { x: roofLine.x1, y: roofLine.y1 }, vertex2: { x: roofLine.x2, y: roofLine.y2 } }
|
|
const intersect1 = edgesIntersection(lineEdge, checkRoofVector1)
|
|
const intersect2 = edgesIntersection(lineEdge, checkRoofVector2)
|
|
if (intersect1 && isPointOnLineNew(roofLine, intersect1)) {
|
|
const size = Math.sqrt(Math.pow(analyze.roofLine.x1 - intersect1.x, 2) + Math.pow(analyze.roofLine.y1 - intersect1.y, 2))
|
|
intersectPoints1.push({ intersect: intersect1, size })
|
|
}
|
|
if (intersect2 && isPointOnLineNew(roofLine, intersect2)) {
|
|
const size = Math.sqrt(Math.pow(analyze.roofLine.x2 - intersect2.x, 2) + Math.pow(analyze.roofLine.y2 - intersect2.y, 2))
|
|
intersectPoints2.push({ intersect: intersect2, size })
|
|
}
|
|
})
|
|
intersectPoints1.sort((a, b) => b.size - a.size)
|
|
intersectPoints2.sort((a, b) => b.size - a.size)
|
|
|
|
const points1 = [analyze.roofLine.x1, analyze.roofLine.y1, intersectPoints1[0].intersect.x, intersectPoints1[0].intersect.y]
|
|
const points2 = [analyze.roofLine.x2, analyze.roofLine.y2, intersectPoints2[0].intersect.x, intersectPoints2[0].intersect.y]
|
|
|
|
const prevIndex = baseLines.findIndex((baseLine) => baseLine === prevLine)
|
|
const beforePrevIndex = baseLines.findIndex((baseLine) => baseLine === baseLines[(prevIndex - 1 + baseLines.length) % baseLines.length])
|
|
const nextIndex = baseLines.findIndex((baseLine) => baseLine === nextLine)
|
|
const afterNextIndex = baseLines.findIndex((baseLine) => baseLine === baseLines[(nextIndex + 1) % baseLines.length])
|
|
|
|
linesAnalysis.push(
|
|
{
|
|
start: { x: points1[0], y: points1[1] },
|
|
end: { x: points1[2], y: points1[3] },
|
|
left: beforePrevIndex,
|
|
right: prevIndex,
|
|
type: TYPES.HIP,
|
|
degree: shedDegree,
|
|
},
|
|
{
|
|
start: { x: points2[0], y: points2[1] },
|
|
end: { x: points2[2], y: points2[3] },
|
|
left: nextIndex,
|
|
right: afterNextIndex,
|
|
type: TYPES.HIP,
|
|
degree: shedDegree,
|
|
},
|
|
)
|
|
})
|
|
|
|
//2. 팔작지붕(이리모야) 판단, 팔작지붕은 양옆이 처마여야만 한다.
|
|
hipAndGables.forEach((currentLine) => {
|
|
let prevLine, nextLine
|
|
baseLines.forEach((baseLine, index) => {
|
|
if (baseLine === currentLine) {
|
|
nextLine = baseLines[(index + 1) % baseLines.length]
|
|
prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length]
|
|
}
|
|
})
|
|
|
|
const analyze = analyzeLine(currentLine)
|
|
|
|
//양옆이 처마가 아닐때 패스
|
|
if (prevLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES || nextLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES) {
|
|
return
|
|
}
|
|
|
|
let beforePrevLine, afterNextLine
|
|
baseLines.forEach((baseLine, index) => {
|
|
if (baseLine === prevLine) {
|
|
beforePrevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length]
|
|
}
|
|
if (baseLine === nextLine) {
|
|
afterNextLine = baseLines[(index + 1) % baseLines.length]
|
|
}
|
|
})
|
|
|
|
const prevLineVector = { x: Math.sign(prevLine.x1 - prevLine.x2), y: Math.sign(prevLine.y1 - prevLine.y2) }
|
|
const nextLineVector = { x: Math.sign(nextLine.x1 - nextLine.x2), y: Math.sign(nextLine.y1 - nextLine.y2) }
|
|
//반절마루 생성불가이므로 지붕선만 추가하고 끝냄
|
|
if (prevLineVector.x === nextLineVector.x && prevLineVector.y === nextLineVector.y) {
|
|
return
|
|
}
|
|
|
|
const prevDegree = getDegreeByChon(prevLine.attributes.pitch)
|
|
const nextDegree = getDegreeByChon(nextLine.attributes.pitch)
|
|
|
|
const currentRoofLine = analyze.roofLine
|
|
let prevRoofLine, nextRoofLine
|
|
roof.lines.forEach((roofLine, index) => {
|
|
if (roofLine === currentRoofLine) {
|
|
nextRoofLine = roof.lines[(index + 1) % roof.lines.length]
|
|
prevRoofLine = roof.lines[(index - 1 + roof.lines.length) % roof.lines.length]
|
|
}
|
|
})
|
|
/** 팔작지붕 두께*/
|
|
const roofDx = currentRoofLine.x2 - currentRoofLine.x1
|
|
const roofDy = currentRoofLine.y2 - currentRoofLine.y1
|
|
const roofLength = Math.sqrt(roofDx * roofDx + roofDy * roofDy)
|
|
const lineWidth = currentLine.attributes.width <= roofLength / 2 ? currentLine.attributes.width : roofLength / 2
|
|
|
|
/** 이전, 다음라인의 사잇각의 vector를 구한다. */
|
|
let prevVector = getHalfAngleVector(prevRoofLine, currentRoofLine)
|
|
let nextVector = getHalfAngleVector(currentRoofLine, nextRoofLine)
|
|
|
|
/** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/
|
|
const prevCheckPoint = {
|
|
x: currentRoofLine.x1 + prevVector.x * lineWidth,
|
|
y: currentRoofLine.y1 + prevVector.y * lineWidth,
|
|
}
|
|
/** 다음 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/
|
|
const nextCheckPoint = {
|
|
x: currentRoofLine.x2 + nextVector.x * lineWidth,
|
|
y: currentRoofLine.y2 + nextVector.y * lineWidth,
|
|
}
|
|
|
|
let prevHipVector, nextHipVector
|
|
|
|
if (roof.inPolygon(prevCheckPoint)) {
|
|
prevHipVector = { x: prevVector.x, y: prevVector.y }
|
|
} else {
|
|
prevHipVector = { x: -prevVector.x, y: -prevVector.y }
|
|
}
|
|
|
|
/** 다음 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/
|
|
if (roof.inPolygon(nextCheckPoint)) {
|
|
nextHipVector = { x: nextVector.x, y: nextVector.y }
|
|
} else {
|
|
nextHipVector = { x: -nextVector.x, y: -nextVector.y }
|
|
}
|
|
|
|
const prevHipPoint = [
|
|
currentRoofLine.x1,
|
|
currentRoofLine.y1,
|
|
currentRoofLine.x1 + prevHipVector.x * lineWidth,
|
|
currentRoofLine.y1 + prevHipVector.y * lineWidth,
|
|
]
|
|
const nextHipPoint = [
|
|
currentRoofLine.x2,
|
|
currentRoofLine.y2,
|
|
currentRoofLine.x2 + nextHipVector.x * lineWidth,
|
|
currentRoofLine.y2 + nextHipVector.y * lineWidth,
|
|
]
|
|
const gablePoint = [prevHipPoint[2], prevHipPoint[3], nextHipPoint[2], nextHipPoint[3]]
|
|
|
|
innerLines.push(drawHipLine(prevHipPoint, canvas, roof, textMode, prevDegree, prevDegree))
|
|
innerLines.push(drawHipLine(nextHipPoint, canvas, roof, textMode, nextDegree, nextDegree))
|
|
// innerLines.push(drawRoofLine(gablePoint, canvas, roof, textMode))
|
|
|
|
//양옆이 처마일경우 두개의 선, 아닐때 한개의 선, 좌우가 처마가 아닐때 안그려져야하는데 기존에 그려지는 경우가 있음 이유를 알 수 없음.
|
|
if (prevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES && nextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) {
|
|
const midPoint = { x: (prevHipPoint[2] + nextHipPoint[2]) / 2, y: (prevHipPoint[3] + nextHipPoint[3]) / 2 }
|
|
const prevGablePoint = [gablePoint[0], gablePoint[1], midPoint.x, midPoint.y]
|
|
const nextGablePoint = [gablePoint[2], gablePoint[3], midPoint.x, midPoint.y]
|
|
const prevDx = prevGablePoint[0] - prevGablePoint[2]
|
|
const prevDy = prevGablePoint[1] - prevGablePoint[3]
|
|
const prevGableLength = Math.sqrt(prevDx * prevDx + prevDy * prevDy)
|
|
const nextDx = nextGablePoint[0] - nextGablePoint[2]
|
|
const nextDy = nextGablePoint[1] - nextGablePoint[3]
|
|
const nextGableLength = Math.sqrt(nextDx * nextDx + nextDy * nextDy)
|
|
if (prevGableLength >= 1) {
|
|
innerLines.push(drawHipLine(prevGablePoint, canvas, roof, textMode, prevDegree, prevDegree))
|
|
}
|
|
if (nextGableLength >= 1) {
|
|
innerLines.push(drawHipLine(nextGablePoint, canvas, roof, textMode, nextDegree, nextDegree))
|
|
}
|
|
const checkEdge = {
|
|
vertex1: { x: midPoint.x, y: midPoint.y },
|
|
vertex2: { x: midPoint.x + -analyze.roofVector.x * 10000, y: midPoint.y + -analyze.roofVector.y * 10000 },
|
|
}
|
|
const intersections = []
|
|
roof.lines
|
|
.filter((line) => line !== currentRoofLine)
|
|
.forEach((line) => {
|
|
const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
|
|
const intersect = edgesIntersection(lineEdge, checkEdge)
|
|
if (intersect && isPointOnLineNew(line, intersect)) {
|
|
const distance = Math.sqrt(Math.pow(midPoint.x - intersect.x, 2) + Math.pow(midPoint.y - intersect.y, 2))
|
|
intersections.push({ intersect, distance })
|
|
}
|
|
})
|
|
intersections.sort((a, b) => a.distance - b.distance)
|
|
if (intersections.length > 0) {
|
|
const intersect = intersections[0].intersect
|
|
const point = [midPoint.x, midPoint.y, intersect.x, intersect.y]
|
|
|
|
//마루가 최대로 뻗어나갈수 있는 포인트. null일때는 맞은편 지붕선 까지로 판단한다.
|
|
const drivePoint = getRidgeDrivePoint(point, prevLine, nextLine, baseLines)
|
|
|
|
const isOverlapBefore = analyze.isHorizontal
|
|
? almostEqual(Math.min(beforePrevLine.x1, beforePrevLine.x2), Math.min(currentLine.x1, currentLine.x2)) &&
|
|
almostEqual(Math.max(beforePrevLine.x1, beforePrevLine.x2), Math.max(currentLine.x1, currentLine.x2))
|
|
: almostEqual(Math.min(beforePrevLine.y1, beforePrevLine.y2), Math.min(currentLine.y1, currentLine.y2)) &&
|
|
almostEqual(Math.max(beforePrevLine.y1, beforePrevLine.y2), Math.max(currentLine.y1, currentLine.y2))
|
|
const isOverlapAfter = analyze.isHorizontal
|
|
? almostEqual(Math.min(afterNextLine.x1, afterNextLine.x2), Math.min(currentLine.x1, currentLine.x2)) &&
|
|
almostEqual(Math.max(afterNextLine.x1, afterNextLine.x2), Math.max(currentLine.x1, currentLine.x2))
|
|
: almostEqual(Math.min(afterNextLine.y1, afterNextLine.y2), Math.min(currentLine.y1, currentLine.y2)) &&
|
|
almostEqual(Math.max(afterNextLine.y1, afterNextLine.y2), Math.max(currentLine.y1, currentLine.y2))
|
|
|
|
if (isOverlapBefore || isOverlapAfter) {
|
|
const oppLine = isOverlapBefore ? beforePrevLine : afterNextLine
|
|
const otherGable = baseLines.find(
|
|
(l) => l !== currentLine && l !== prevLine && l !== nextLine && l !== oppLine && l.attributes.type === LINE_TYPE.WALLLINE.GABLE,
|
|
)
|
|
const pointVector = { x: Math.sign(clamp01(point[0] - point[2])), y: Math.sign(clamp01(point[1] - point[3])) }
|
|
if (!otherGable) {
|
|
let offset = 0
|
|
switch (oppLine.attributes.type) {
|
|
case LINE_TYPE.WALLLINE.HIPANDGABLE:
|
|
offset = oppLine.attributes.width
|
|
break
|
|
case LINE_TYPE.WALLLINE.JERKINHEAD:
|
|
offset = oppLine.attributes.width / 2
|
|
break
|
|
case LINE_TYPE.WALLLINE.WALL:
|
|
offset = 0
|
|
break
|
|
default:
|
|
break
|
|
}
|
|
point[2] += pointVector.x * offset
|
|
point[3] += pointVector.y * offset
|
|
} else {
|
|
if (drivePoint) {
|
|
point[2] = drivePoint.x
|
|
point[3] = drivePoint.y
|
|
}
|
|
}
|
|
} else if (drivePoint) {
|
|
point[2] = drivePoint.x
|
|
point[3] = drivePoint.y
|
|
}
|
|
|
|
linesAnalysis.push({
|
|
start: { x: point[0], y: point[1] },
|
|
end: { x: point[2], y: point[3] },
|
|
left: baseLines.findIndex((line) => line === prevLine),
|
|
right: baseLines.findIndex((line) => line === nextLine),
|
|
type: TYPES.RIDGE,
|
|
degree: 0,
|
|
})
|
|
}
|
|
} else {
|
|
const gableDx = gablePoint[0] - gablePoint[2]
|
|
const gableDy = gablePoint[1] - gablePoint[3]
|
|
const gableLength = Math.sqrt(gableDx * gableDx + gableDy * gableDy)
|
|
if (gableLength >= 1) {
|
|
innerLines.push(drawRoofLine(gablePoint, canvas, roof, textMode))
|
|
} else {
|
|
const midPoint = { x: gablePoint[2], y: gablePoint[3] }
|
|
const checkEdge = {
|
|
vertex1: { x: midPoint.x, y: midPoint.y },
|
|
vertex2: { x: midPoint.x + -analyze.roofVector.x * 10000, y: midPoint.y + -analyze.roofVector.y * 10000 },
|
|
}
|
|
const intersections = []
|
|
roof.lines
|
|
.filter((line) => line !== currentRoofLine)
|
|
.forEach((line) => {
|
|
const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
|
|
const intersect = edgesIntersection(lineEdge, checkEdge)
|
|
if (intersect && isPointOnLineNew(line, intersect)) {
|
|
const distance = Math.sqrt(Math.pow(midPoint.x - intersect.x, 2) + Math.pow(midPoint.y - intersect.y, 2))
|
|
intersections.push({ intersect, distance })
|
|
}
|
|
})
|
|
intersections.sort((a, b) => a.distance - b.distance)
|
|
if (intersections.length > 0) {
|
|
const intersect = intersections[0].intersect
|
|
linesAnalysis.push({
|
|
start: { x: midPoint.x, y: midPoint.y },
|
|
end: { x: intersect.x, y: intersect.y },
|
|
left: baseLines.findIndex((line) => line === prevLine),
|
|
right: baseLines.findIndex((line) => line === nextLine),
|
|
type: TYPES.RIDGE,
|
|
degree: 0,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
eaves.sort((a, b) => a.attributes.planeSize - b.attributes.planeSize)
|
|
//3. 처마지붕 판단, 처마는 옆이 처마여야한다.
|
|
|
|
const ridgeEaves = eaves.filter((currentLine) => {
|
|
let prevLine, nextLine
|
|
baseLines.forEach((baseLine, index) => {
|
|
if (baseLine === currentLine) {
|
|
nextLine = baseLines[(index + 1) % baseLines.length]
|
|
prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length]
|
|
}
|
|
})
|
|
const prevLineVector = { x: Math.sign(prevLine.x1 - prevLine.x2), y: Math.sign(prevLine.y1 - prevLine.y2) }
|
|
const nextLineVector = { x: Math.sign(nextLine.x1 - nextLine.x2), y: Math.sign(nextLine.y1 - nextLine.y2) }
|
|
const inPolygonPoint = {
|
|
x: (currentLine.x1 + currentLine.x2) / 2 + Math.sign(nextLine.x2 - nextLine.x1),
|
|
y: (currentLine.y1 + currentLine.y2) / 2 + Math.sign(nextLine.y2 - nextLine.y1),
|
|
}
|
|
|
|
//좌우 라인이 서로 다른방향이고 지붕 안쪽으로 들어가지 않을때
|
|
const isAbleShape = (prevLineVector.x !== nextLineVector.x || prevLineVector.y !== nextLineVector.y) && checkWallPolygon.inPolygon(inPolygonPoint)
|
|
const isAbleAttribute = prevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES && nextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES
|
|
return isAbleAttribute && isAbleShape
|
|
})
|
|
|
|
const hipedEaves = eaves.filter((line) => !ridgeEaves.includes(line))
|
|
|
|
ridgeEaves.sort((a, b) => a.attributes.planeSize - b.attributes.planeSize)
|
|
|
|
let proceedEaves = [] // left: 이전, right:다음, point:그려지는 포인트, length:길이
|
|
let proceedRidges = [] // left: 이전, right:다음, point:그려지는 포인트, length:길이
|
|
ridgeEaves.forEach((currentLine) => {
|
|
let prevLine, nextLine, currentI, prevI, nextI
|
|
baseLines.forEach((baseLine, index) => {
|
|
if (baseLine === currentLine) {
|
|
currentI = index
|
|
prevI = (index - 1 + baseLines.length) % baseLines.length
|
|
nextI = (index + 1) % baseLines.length
|
|
}
|
|
})
|
|
prevLine = baseLines[prevI]
|
|
nextLine = baseLines[nextI]
|
|
|
|
let beforePrevLine, afterNextLine
|
|
baseLines.forEach((baseLine, index) => {
|
|
if (baseLine === prevLine) {
|
|
beforePrevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length]
|
|
}
|
|
if (baseLine === nextLine) {
|
|
afterNextLine = baseLines[(index + 1) % baseLines.length]
|
|
}
|
|
})
|
|
|
|
const analyze = analyzeLine(currentLine)
|
|
let pHipVector = getHalfAngleVector(currentLine, prevLine)
|
|
let nHipVector = getHalfAngleVector(currentLine, nextLine)
|
|
const pCheckPoint = {
|
|
x: currentLine.x1 + (pHipVector.x * 10) / 2,
|
|
y: currentLine.y1 + (pHipVector.y * 10) / 2,
|
|
}
|
|
const nCheckPoint = {
|
|
x: currentLine.x2 + (nHipVector.x * 10) / 2,
|
|
y: currentLine.y2 + (nHipVector.y * 10) / 2,
|
|
}
|
|
|
|
if (!checkWallPolygon.inPolygon(pCheckPoint)) {
|
|
pHipVector = { x: -pHipVector.x, y: -pHipVector.y }
|
|
}
|
|
if (!checkWallPolygon.inPolygon(nCheckPoint)) {
|
|
nHipVector = { x: -nHipVector.x, y: -nHipVector.y }
|
|
}
|
|
|
|
const prevCheckPoint = [currentLine.x1, currentLine.y1, currentLine.x1 + pHipVector.x * 1000, currentLine.y1 + pHipVector.y * 1000]
|
|
const nextCheckPoint = [currentLine.x2, currentLine.y2, currentLine.x2 + nHipVector.x * 1000, currentLine.y2 + nHipVector.y * 1000]
|
|
|
|
const findRoofPoints = (points) => {
|
|
const hipEdge = { vertex1: { x: points[0], y: points[1] }, vertex2: { x: points[2], y: points[3] } }
|
|
const hipForwardVector = { x: Math.sign(hipEdge.vertex1.x - hipEdge.vertex2.x), y: Math.sign(hipEdge.vertex1.y - hipEdge.vertex2.y) }
|
|
const hipBackwardVector = { x: -hipForwardVector.x, y: -hipForwardVector.y }
|
|
const isForwardPoints = []
|
|
const isBackwardPoints = []
|
|
|
|
roof.lines.forEach((roofLine) => {
|
|
const lineEdge = { vertex1: { x: roofLine.x1, y: roofLine.y1 }, vertex2: { x: roofLine.x2, y: roofLine.y2 } }
|
|
const intersect = edgesIntersection(lineEdge, hipEdge)
|
|
if (intersect && isPointOnLineNew(roofLine, intersect)) {
|
|
const intersectVector = { x: Math.sign(hipEdge.vertex1.x - intersect.x), y: Math.sign(hipEdge.vertex1.y - intersect.y) }
|
|
if (
|
|
(intersectVector.x === hipForwardVector.x && intersectVector.y === hipForwardVector.y) ||
|
|
(intersectVector.x === 0 && intersectVector.y === 0)
|
|
) {
|
|
const dx = hipEdge.vertex1.x - intersect.x
|
|
const dy = hipEdge.vertex1.y - intersect.y
|
|
const length = Math.sqrt(dx * dx + dy * dy)
|
|
isForwardPoints.push({ intersect, length })
|
|
}
|
|
if (intersectVector.x === hipBackwardVector.x && intersectVector.y === hipBackwardVector.y) {
|
|
const dx = hipEdge.vertex2.x - intersect.x
|
|
const dy = hipEdge.vertex2.y - intersect.y
|
|
const length = Math.sqrt(dx * dx + dy * dy)
|
|
isBackwardPoints.push({ intersect, length })
|
|
}
|
|
}
|
|
})
|
|
isForwardPoints.sort((a, b) => a.length - b.length)
|
|
isBackwardPoints.sort((a, b) => a.length - b.length)
|
|
if (isForwardPoints.length > 0 && isBackwardPoints.length > 0) {
|
|
return { forward: isForwardPoints[0].intersect, backward: isBackwardPoints[0].intersect }
|
|
} else if (isForwardPoints.length === 0 && isBackwardPoints.length > 1) {
|
|
return { forward: isBackwardPoints[0].intersect, backward: isBackwardPoints[1].intersect }
|
|
} else if (isForwardPoints.length > 1 && isBackwardPoints.length === 0) {
|
|
return { forward: isForwardPoints[1].intersect, backward: isForwardPoints[0].intersect }
|
|
} else {
|
|
return null
|
|
}
|
|
}
|
|
|
|
const pRoofPoints = findRoofPoints(prevCheckPoint)
|
|
const nRoofPoints = findRoofPoints(nextCheckPoint)
|
|
|
|
let prevHipPoint = { x1: pRoofPoints.backward.x, y1: pRoofPoints.backward.y, x2: pRoofPoints.forward.x, y2: pRoofPoints.forward.y }
|
|
let nextHipPoint = { x1: nRoofPoints.backward.x, y1: nRoofPoints.backward.y, x2: nRoofPoints.forward.x, y2: nRoofPoints.forward.y }
|
|
|
|
const prevEdge = { vertex1: { x: prevHipPoint.x1, y: prevHipPoint.y1 }, vertex2: { x: prevHipPoint.x2, y: prevHipPoint.y2 } }
|
|
const nextEdge = { vertex1: { x: nextHipPoint.x1, y: nextHipPoint.y1 }, vertex2: { x: nextHipPoint.x2, y: nextHipPoint.y2 } }
|
|
const intersect = edgesIntersection(prevEdge, nextEdge)
|
|
if (intersect && isPointOnLineNew(prevHipPoint, intersect) && isPointOnLineNew(nextHipPoint, intersect)) {
|
|
prevHipPoint.x2 = intersect.x
|
|
prevHipPoint.y2 = intersect.y
|
|
nextHipPoint.x2 = intersect.x
|
|
nextHipPoint.y2 = intersect.y
|
|
}
|
|
|
|
let isRidgePrev, isRidgeNext
|
|
let minPrevDist = Infinity
|
|
let minNextDist = Infinity
|
|
proceedRidges.forEach((ridge) => {
|
|
const ridgeEdge = { vertex1: { x: ridge.point.x1, y: ridge.point.y1 }, vertex2: { x: ridge.point.x2, y: ridge.point.y2 } }
|
|
const isPrev = edgesIntersection(ridgeEdge, prevEdge)
|
|
const isNext = edgesIntersection(ridgeEdge, nextEdge)
|
|
if (
|
|
isPrev &&
|
|
isPointOnLineNew(prevHipPoint, isPrev) &&
|
|
(ridge.prev === prevI || ridge.prev === nextI || ridge.next === prevI || ridge.next === nextI)
|
|
) {
|
|
const distance = Math.sqrt((isPrev.x - prevHipPoint.x1) ** 2 + (isPrev.y - prevHipPoint.y1) ** 2)
|
|
if (distance < minPrevDist) {
|
|
minPrevDist = distance
|
|
isRidgePrev = isPrev
|
|
}
|
|
}
|
|
if (
|
|
isNext &&
|
|
isPointOnLineNew(nextHipPoint, isNext) &&
|
|
(ridge.prev === prevI || ridge.prev === nextI || ridge.next === prevI || ridge.next === nextI)
|
|
) {
|
|
const distance = Math.sqrt((isNext.x - nextHipPoint.x1) ** 2 + (isNext.y - nextHipPoint.y1) ** 2)
|
|
if (distance < minNextDist) {
|
|
minNextDist = distance
|
|
isRidgeNext = isNext
|
|
}
|
|
}
|
|
})
|
|
|
|
//접하는 라인에서 파생된 마루선이 겹칠경우 라인보정을 종료
|
|
if (isRidgePrev) {
|
|
prevHipPoint = { x1: pRoofPoints.backward.x, y1: pRoofPoints.backward.y, x2: pRoofPoints.forward.x, y2: pRoofPoints.forward.y }
|
|
}
|
|
if (isRidgeNext) {
|
|
nextHipPoint = { x1: nRoofPoints.backward.x, y1: nRoofPoints.backward.y, x2: nRoofPoints.forward.x, y2: nRoofPoints.forward.y }
|
|
}
|
|
|
|
let prevHipLength = Math.sqrt((prevHipPoint.x2 - prevHipPoint.x1) ** 2 + (prevHipPoint.y2 - prevHipPoint.y1) ** 2)
|
|
let nextHipLength = Math.sqrt((nextHipPoint.x2 - nextHipPoint.x1) ** 2 + (nextHipPoint.y2 - nextHipPoint.y1) ** 2)
|
|
|
|
const alreadyPrev = proceedEaves.filter((line) => almostEqual(line.point.x1, prevHipPoint.x1) && almostEqual(line.point.y1, prevHipPoint.y1))
|
|
const alreadyNext = proceedEaves.filter((line) => almostEqual(line.point.x1, nextHipPoint.x1) && almostEqual(line.point.y1, nextHipPoint.y1))
|
|
if (alreadyPrev.length) {
|
|
alreadyPrev.sort((a, b) => a.length - b.length)
|
|
const alrPrev = alreadyPrev[0]
|
|
if (isRidgePrev) {
|
|
alrPrev.prev = prevI
|
|
alrPrev.current = currentI
|
|
alrPrev.point = prevHipPoint
|
|
alrPrev.length = prevHipLength
|
|
} else {
|
|
if (prevHipLength < alrPrev.length) {
|
|
//겹치는데 지금 만들어지는 라인이 더 짧은경우 다른 라인제거 하고 현재 라인을 추가.
|
|
alrPrev.prev = prevI
|
|
alrPrev.current = currentI
|
|
alrPrev.point = prevHipPoint
|
|
alrPrev.length = prevHipLength
|
|
} else {
|
|
prevHipPoint = alrPrev.point
|
|
}
|
|
}
|
|
} else {
|
|
proceedEaves.push({ prev: prevI, current: currentI, point: prevHipPoint, length: prevHipLength })
|
|
}
|
|
if (alreadyNext.length) {
|
|
alreadyNext.sort((a, b) => a.length - b.length)
|
|
const alrNext = alreadyNext[0]
|
|
if (isRidgeNext) {
|
|
alrNext.prev = currentI
|
|
alrNext.current = nextI
|
|
alrNext.point = nextHipPoint
|
|
alrNext.length = nextHipLength
|
|
} else {
|
|
if (nextHipLength < alrNext.length) {
|
|
//겹치는데 지금 만들어지는 라인이 더 짧은경우 다른 라인제거 하고 현재 라인을 추가.
|
|
alrNext.prev = currentI
|
|
alrNext.current = nextI
|
|
alrNext.point = nextHipPoint
|
|
alrNext.length = nextHipLength
|
|
} else {
|
|
nextHipPoint = alrNext.point
|
|
}
|
|
}
|
|
} else {
|
|
proceedEaves.push({ prev: currentI, current: nextI, point: nextHipPoint, length: nextHipLength })
|
|
}
|
|
|
|
let ridgePoint
|
|
if (almostEqual(prevHipPoint.x2, nextHipPoint.x2) && almostEqual(prevHipPoint.y2, nextHipPoint.y2)) {
|
|
const ridgeStartPoint = { x: prevHipPoint.x2, y: prevHipPoint.y2 }
|
|
|
|
let ridgeVector = { x: Math.sign(clamp01(nextLine.x2 - nextLine.x1)), y: Math.sign(clamp01(nextLine.y2 - nextLine.y1)) }
|
|
const midX = (currentLine.x1 + currentLine.x2) / 2
|
|
const midY = (currentLine.y1 + currentLine.y2) / 2
|
|
let checkPoint = { x: midX + ridgeVector.x, y: midY + ridgeVector.y }
|
|
if (!checkWallPolygon.inPolygon(checkPoint)) {
|
|
ridgeVector = { x: -ridgeVector.x, y: -ridgeVector.y }
|
|
}
|
|
ridgePoint = { x1: ridgeStartPoint.x, y1: ridgeStartPoint.y, x2: ridgeStartPoint.x + ridgeVector.x, y2: ridgeStartPoint.y + ridgeVector.y }
|
|
const ridgeEdge = { vertex1: { x: ridgePoint.x1, y: ridgePoint.y1 }, vertex2: { x: ridgePoint.x2, y: ridgePoint.y2 } }
|
|
|
|
let roofIs
|
|
let minDistance = Infinity
|
|
roof.lines.forEach((line) => {
|
|
const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
|
|
const intersect = edgesIntersection(lineEdge, ridgeEdge)
|
|
if (intersect && isPointOnLineNew(line, intersect)) {
|
|
const isVector = { x: Math.sign(clamp01(intersect.x - ridgeStartPoint.x)), y: Math.sign(clamp01(intersect.y - ridgeStartPoint.y)) }
|
|
const distance = Math.sqrt(Math.pow(ridgeStartPoint.x - intersect.x, 2) + Math.pow(ridgeStartPoint.y - intersect.y, 2))
|
|
if (distance < minDistance && isVector.x === ridgeVector.x && isVector.y === ridgeVector.y) {
|
|
minDistance = distance
|
|
roofIs = intersect
|
|
}
|
|
}
|
|
})
|
|
if (roofIs) {
|
|
ridgePoint.x2 = roofIs.x
|
|
ridgePoint.y2 = roofIs.y
|
|
}
|
|
|
|
const drivePoint = getRidgeDrivePoint([ridgePoint.x1, ridgePoint.y1, ridgePoint.x2, ridgePoint.y2], prevLine, nextLine, baseLines)
|
|
const isOverlapBefore = analyze.isHorizontal
|
|
? almostEqual(Math.min(beforePrevLine.x1, beforePrevLine.x2), Math.min(currentLine.x1, currentLine.x2)) &&
|
|
almostEqual(Math.max(beforePrevLine.x1, beforePrevLine.x2), Math.max(currentLine.x1, currentLine.x2))
|
|
: almostEqual(Math.min(beforePrevLine.y1, beforePrevLine.y2), Math.min(currentLine.y1, currentLine.y2)) &&
|
|
almostEqual(Math.max(beforePrevLine.y1, beforePrevLine.y2), Math.max(currentLine.y1, currentLine.y2))
|
|
const isOverlapAfter = analyze.isHorizontal
|
|
? almostEqual(Math.min(afterNextLine.x1, afterNextLine.x2), Math.min(currentLine.x1, currentLine.x2)) &&
|
|
almostEqual(Math.max(afterNextLine.x1, afterNextLine.x2), Math.max(currentLine.x1, currentLine.x2))
|
|
: almostEqual(Math.min(afterNextLine.y1, afterNextLine.y2), Math.min(currentLine.y1, currentLine.y2)) &&
|
|
almostEqual(Math.max(afterNextLine.y1, afterNextLine.y2), Math.max(currentLine.y1, currentLine.y2))
|
|
if (isOverlapBefore || isOverlapAfter) {
|
|
const oppLine = isOverlapBefore ? beforePrevLine : afterNextLine
|
|
const otherGable = baseLines.find(
|
|
(l) => l !== currentLine && l !== prevLine && l !== nextLine && l !== oppLine && l.attributes.type === LINE_TYPE.WALLLINE.GABLE,
|
|
)
|
|
const pointVector = { x: Math.sign(clamp01(ridgePoint.x1 - ridgePoint.x2)), y: Math.sign(clamp01(ridgePoint.y1 - ridgePoint.y2)) }
|
|
let offset = 0
|
|
switch (oppLine.attributes.type) {
|
|
case LINE_TYPE.WALLLINE.HIPANDGABLE:
|
|
offset = oppLine.attributes.width
|
|
break
|
|
case LINE_TYPE.WALLLINE.JERKINHEAD:
|
|
offset = oppLine.attributes.width / 2
|
|
break
|
|
case LINE_TYPE.WALLLINE.WALL:
|
|
offset = 0
|
|
break
|
|
default:
|
|
break
|
|
}
|
|
if (!otherGable) {
|
|
if (oppLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) {
|
|
const oppIndex = baseLines.findIndex((l) => l === oppLine)
|
|
const oppPrevLine = baseLines[(oppIndex - 1 + baseLines.length) % baseLines.length]
|
|
const oppNextLine = baseLines[(oppIndex + 1) % baseLines.length]
|
|
|
|
if (oppPrevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES || oppNextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) {
|
|
const currentLength = Math.sqrt(Math.pow(currentLine.x1 - currentLine.x2, 2) + Math.pow(currentLine.y1 - currentLine.y2, 2))
|
|
const oppLength = Math.sqrt(Math.pow(oppLine.x1 - oppLine.x2, 2) + Math.pow(oppLine.y1 - oppLine.y2, 2))
|
|
if (almostEqual(currentLength, oppLength)) {
|
|
if (drivePoint) {
|
|
ridgePoint.x2 = drivePoint.x
|
|
ridgePoint.y2 = drivePoint.y
|
|
}
|
|
} else {
|
|
ridgePoint.x2 += pointVector.x * offset
|
|
ridgePoint.y2 += pointVector.y * offset
|
|
}
|
|
}
|
|
} else {
|
|
ridgePoint.x2 += pointVector.x * offset
|
|
ridgePoint.y2 += pointVector.y * offset
|
|
}
|
|
// }
|
|
} else if (drivePoint) {
|
|
ridgePoint.x2 = drivePoint.x
|
|
ridgePoint.y2 = drivePoint.y
|
|
}
|
|
} else if (drivePoint) {
|
|
ridgePoint.x2 = drivePoint.x
|
|
ridgePoint.y2 = drivePoint.y
|
|
}
|
|
}
|
|
if (ridgePoint) {
|
|
const alreadyRidge = proceedRidges.find(
|
|
(line) =>
|
|
almostEqual(line.point.x1, ridgePoint.x1) &&
|
|
almostEqual(line.point.y1, ridgePoint.y1) &&
|
|
almostEqual(line.point.x2, ridgePoint.x2) &&
|
|
almostEqual(line.point.y2, ridgePoint.y2),
|
|
)
|
|
if (!alreadyRidge) {
|
|
proceedRidges.push({ prev: prevI, next: nextI, point: ridgePoint })
|
|
}
|
|
}
|
|
canvas
|
|
.getObjects()
|
|
.filter((o) => o.name === 'check')
|
|
.forEach((o) => canvas.remove(o))
|
|
canvas.renderAll()
|
|
})
|
|
|
|
hipedEaves.forEach((currentLine) => {
|
|
let prevLine
|
|
baseLines.forEach((baseLine, index) => {
|
|
if (baseLine === currentLine) {
|
|
prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length]
|
|
}
|
|
})
|
|
|
|
const analyze = analyzeLine(currentLine)
|
|
|
|
// 옆이 처마가 아니면 패스
|
|
if (prevLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES) {
|
|
return
|
|
}
|
|
|
|
const currentDegree = getDegreeByChon(currentLine.attributes.pitch)
|
|
const checkVector = getHalfAngleVector(currentLine, prevLine)
|
|
const checkPoint = {
|
|
x: currentLine.x1 + (checkVector.x * 10) / 2,
|
|
y: currentLine.y1 + (checkVector.y * 10) / 2,
|
|
}
|
|
|
|
let hipVector
|
|
if (checkWallPolygon.inPolygon(checkPoint)) {
|
|
hipVector = { x: checkVector.x, y: checkVector.y }
|
|
} else {
|
|
hipVector = { x: -checkVector.x, y: -checkVector.y }
|
|
}
|
|
|
|
const hipEdge = {
|
|
vertex1: { x: currentLine.x1, y: currentLine.y1 },
|
|
vertex2: { x: currentLine.x1 + hipVector.x * analyze.length, y: currentLine.y1 + hipVector.y * analyze.length },
|
|
}
|
|
|
|
const hipForwardVector = { x: Math.sign(hipEdge.vertex1.x - hipEdge.vertex2.x), y: Math.sign(hipEdge.vertex1.y - hipEdge.vertex2.y) }
|
|
const hipBackwardVector = { x: -hipForwardVector.x, y: -hipForwardVector.y }
|
|
const isForwardPoints = []
|
|
const isBackwardPoints = []
|
|
|
|
roof.lines.forEach((roofLine) => {
|
|
const lineEdge = { vertex1: { x: roofLine.x1, y: roofLine.y1 }, vertex2: { x: roofLine.x2, y: roofLine.y2 } }
|
|
const intersect = edgesIntersection(lineEdge, hipEdge)
|
|
if (intersect && isPointOnLineNew(roofLine, intersect)) {
|
|
const intersectVector = { x: Math.sign(hipEdge.vertex1.x - intersect.x), y: Math.sign(hipEdge.vertex1.y - intersect.y) }
|
|
if (
|
|
(intersectVector.x === hipForwardVector.x && intersectVector.y === hipForwardVector.y) ||
|
|
(intersectVector.x === 0 && intersectVector.y === 0)
|
|
) {
|
|
const dx = hipEdge.vertex1.x - intersect.x
|
|
const dy = hipEdge.vertex1.y - intersect.y
|
|
const length = Math.sqrt(dx * dx + dy * dy)
|
|
isForwardPoints.push({ intersect, length })
|
|
}
|
|
if (intersectVector.x === hipBackwardVector.x && intersectVector.y === hipBackwardVector.y) {
|
|
const dx = hipEdge.vertex2.x - intersect.x
|
|
const dy = hipEdge.vertex2.y - intersect.y
|
|
const length = Math.sqrt(dx * dx + dy * dy)
|
|
isBackwardPoints.push({ intersect, length })
|
|
}
|
|
}
|
|
})
|
|
isForwardPoints.sort((a, b) => a.length - b.length)
|
|
isBackwardPoints.sort((a, b) => a.length - b.length)
|
|
|
|
let hipPoint
|
|
if (isForwardPoints.length > 0 && isBackwardPoints.length > 0) {
|
|
hipPoint = {
|
|
x1: isBackwardPoints[0].intersect.x,
|
|
y1: isBackwardPoints[0].intersect.y,
|
|
x2: isForwardPoints[0].intersect.x,
|
|
y2: isForwardPoints[0].intersect.y,
|
|
}
|
|
} else {
|
|
if (isBackwardPoints.length === 0 && isForwardPoints.length > 1) {
|
|
hipPoint = {
|
|
x1: isForwardPoints[0].intersect.x,
|
|
y1: isForwardPoints[0].intersect.y,
|
|
x2: isForwardPoints[1].intersect.x,
|
|
y2: isForwardPoints[1].intersect.y,
|
|
}
|
|
}
|
|
if (isForwardPoints.length === 0 && isBackwardPoints.length > 1) {
|
|
hipPoint = {
|
|
x1: isBackwardPoints[0].intersect.x,
|
|
y1: isBackwardPoints[0].intersect.y,
|
|
x2: isBackwardPoints[1].intersect.x,
|
|
y2: isBackwardPoints[1].intersect.y,
|
|
}
|
|
}
|
|
}
|
|
|
|
if (hipPoint) {
|
|
const alreadyLine = proceedEaves.filter((line) => almostEqual(line.point.x1, hipPoint.x1) && almostEqual(line.point.y1, hipPoint.y1))
|
|
//겹쳐지는 라인이 있는경우 조정한다.
|
|
if (alreadyLine.length === 0) {
|
|
linesAnalysis.push({
|
|
start: { x: hipPoint.x1, y: hipPoint.y1 },
|
|
end: { x: hipPoint.x2, y: hipPoint.y2 },
|
|
left: baseLines.findIndex((line) => line === prevLine),
|
|
right: baseLines.findIndex((line) => line === currentLine),
|
|
type: TYPES.HIP,
|
|
degree: currentDegree,
|
|
})
|
|
}
|
|
}
|
|
})
|
|
|
|
//작성된 라인을 analyze에 추가한다.
|
|
const pIndexEaves = []
|
|
proceedEaves.forEach((eaves, index) => {
|
|
if (pIndexEaves.includes(index)) return
|
|
const { prev, current, point } = eaves
|
|
const currentDegree = getDegreeByChon(baseLines[current].attributes.pitch)
|
|
const jointEaves = proceedEaves
|
|
.filter((e) => e !== eaves)
|
|
.filter((e) => almostEqual(e.point.x2, eaves.point.x2) && almostEqual(e.point.y2, eaves.point.y2))
|
|
if (jointEaves.length === 1 && ridgeEaves.length > 1) {
|
|
innerLines.push(drawHipLine([point.x1, point.y1, point.x2, point.y2], canvas, roof, textMode, currentDegree, currentDegree))
|
|
pIndexEaves.push(index)
|
|
} else if (jointEaves.length === 2) {
|
|
const jointIndex = [index]
|
|
let jointLines = []
|
|
jointEaves.forEach((e) => jointIndex.push(proceedEaves.findIndex((p) => p === e)))
|
|
const jointVectors = []
|
|
//연결된 라인 생성
|
|
jointIndex.forEach((i) => {
|
|
const ev = proceedEaves[i]
|
|
const degree = getDegreeByChon(baseLines[ev.current].attributes.pitch)
|
|
innerLines.push(drawHipLine([ev.point.x1, ev.point.y1, ev.point.x2, ev.point.y2], canvas, roof, textMode, degree, degree))
|
|
pIndexEaves.push(i)
|
|
jointLines.push(ev.prev, ev.current)
|
|
jointVectors.push({ x: Math.sign(ev.point.x2 - ev.point.x1), y: Math.sign(ev.point.y2 - ev.point.y1) })
|
|
})
|
|
//연결된 지점에서 파생된 마루선 제거
|
|
const removeRidge = proceedRidges.filter((ridge) => almostEqual(ridge.point.x1, eaves.point.x2) && almostEqual(ridge.point.y1, eaves.point.y2))
|
|
proceedRidges = proceedRidges.filter((ridge) => !removeRidge.includes(ridge))
|
|
let dneVector = jointVectors.find((v) => !jointVectors.find((v2) => v2.x === -v.x && v2.y === -v.y))
|
|
const findRoofEdge = {
|
|
vertex1: { x: eaves.point.x2, y: eaves.point.y2 },
|
|
vertex2: { x: eaves.point.x2 + dneVector.x, y: eaves.point.y2 + dneVector.y },
|
|
}
|
|
let minDistance = Infinity
|
|
let isPoint
|
|
roof.lines.forEach((line) => {
|
|
const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
|
|
const intersect = edgesIntersection(lineEdge, findRoofEdge)
|
|
if (intersect && isPointOnLineNew(line, intersect)) {
|
|
const distance = Math.sqrt(Math.pow(intersect.x - eaves.point.x2, 2) + Math.pow(intersect.y - eaves.point.y2, 2))
|
|
if (distance < minDistance) {
|
|
minDistance = distance
|
|
isPoint = intersect
|
|
}
|
|
}
|
|
})
|
|
if (isPoint) {
|
|
const countMap = new Map()
|
|
jointLines.forEach((value) => {
|
|
countMap.set(value, (countMap.get(value) || 0) + 1)
|
|
})
|
|
|
|
const uniqueLine = jointLines.filter((value) => countMap.get(value) === 1)
|
|
linesAnalysis.push({
|
|
start: { x: eaves.point.x2, y: eaves.point.y2 },
|
|
end: { x: isPoint.x, y: isPoint.y },
|
|
left: uniqueLine[0],
|
|
right: uniqueLine[1],
|
|
type: TYPES.HIP,
|
|
degree: currentDegree,
|
|
})
|
|
}
|
|
} else {
|
|
linesAnalysis.push({
|
|
start: { x: point.x1, y: point.y1 },
|
|
end: { x: point.x2, y: point.y2 },
|
|
left: prev,
|
|
right: current,
|
|
type: TYPES.HIP,
|
|
degree: currentDegree,
|
|
})
|
|
pIndexEaves.push(index)
|
|
}
|
|
})
|
|
proceedRidges.forEach(({ prev, next, point }) =>
|
|
linesAnalysis.push({
|
|
start: { x: point.x1, y: point.y1 },
|
|
end: { x: point.x2, y: point.y2 },
|
|
left: prev,
|
|
right: next,
|
|
type: TYPES.RIDGE,
|
|
degree: 0,
|
|
}),
|
|
)
|
|
|
|
//4. 반절처(반절마루) 판단, 반절마루는 양옆이 처마여야 한다.
|
|
jerkinHeads.forEach((currentLine) => {
|
|
let prevLine, nextLine
|
|
baseLines.forEach((baseLine, index) => {
|
|
if (baseLine === currentLine) {
|
|
nextLine = baseLines[(index + 1) % baseLines.length]
|
|
prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length]
|
|
}
|
|
})
|
|
|
|
//양옆이 처마가 아닐때 패스
|
|
if (prevLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES || nextLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES) {
|
|
return
|
|
}
|
|
|
|
let beforePrevLine, afterNextLine
|
|
baseLines.forEach((baseLine, index) => {
|
|
if (baseLine === prevLine) {
|
|
beforePrevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length]
|
|
}
|
|
if (baseLine === nextLine) {
|
|
afterNextLine = baseLines[(index + 1) % baseLines.length]
|
|
}
|
|
})
|
|
|
|
const analyze = analyzeLine(currentLine)
|
|
const roofPoints = [analyze.roofLine.x1, analyze.roofLine.y1, analyze.roofLine.x2, analyze.roofLine.y2]
|
|
|
|
const prevLineVector = { x: Math.sign(prevLine.x1 - prevLine.x2), y: Math.sign(prevLine.y1 - prevLine.y2) }
|
|
const nextLineVector = { x: Math.sign(nextLine.x1 - nextLine.x2), y: Math.sign(nextLine.y1 - nextLine.y2) }
|
|
if (prevLineVector.x === nextLineVector.x && prevLineVector.y === nextLineVector.y) {
|
|
//반절마루 생성불가이므로 지붕선만 추가하고 끝냄
|
|
// innerLines.push(drawRoofLine(roofPoints, canvas, roof, textMode))
|
|
return
|
|
}
|
|
const currentDegree = getDegreeByChon(currentLine.attributes.pitch)
|
|
const prevDegree = getDegreeByChon(prevLine.attributes.pitch)
|
|
const nextDegree = getDegreeByChon(nextLine.attributes.pitch)
|
|
const gableWidth = currentLine.attributes.width
|
|
|
|
const roofVector = { x: Math.sign(roofPoints[2] - roofPoints[0]), y: Math.sign(roofPoints[3] - roofPoints[1]) }
|
|
const roofDx = roofPoints[0] - roofPoints[2]
|
|
const roofDy = roofPoints[1] - roofPoints[3]
|
|
const roofLength = Math.sqrt(roofDx * roofDx + roofDy * roofDy)
|
|
const lineLength = (roofLength - gableWidth) / 2
|
|
const jerkinPoints = []
|
|
jerkinPoints.push(
|
|
{ x: roofPoints[0], y: roofPoints[1] },
|
|
{ x: roofPoints[0] + roofVector.x * lineLength, y: roofPoints[1] + roofVector.y * lineLength },
|
|
)
|
|
jerkinPoints.push({ x: jerkinPoints[1].x + roofVector.x * gableWidth, y: jerkinPoints[1].y + roofVector.y * gableWidth })
|
|
jerkinPoints.push({ x: jerkinPoints[2].x + roofVector.x * lineLength, y: jerkinPoints[2].y + roofVector.y * lineLength })
|
|
|
|
if (gableWidth >= roofLength) {
|
|
innerLines.push(drawRoofLine(roofPoints, canvas, roof, textMode))
|
|
} else if (jerkinPoints.length === 4) {
|
|
const gablePoint1 = [jerkinPoints[0].x, jerkinPoints[0].y, jerkinPoints[1].x, jerkinPoints[1].y]
|
|
const gablePoint2 = [jerkinPoints[1].x, jerkinPoints[1].y, jerkinPoints[2].x, jerkinPoints[2].y]
|
|
const gablePoint3 = [jerkinPoints[2].x, jerkinPoints[2].y, jerkinPoints[3].x, jerkinPoints[3].y]
|
|
const gableLength1 = Math.sqrt(Math.pow(gablePoint1[2] - gablePoint1[0], 2) + Math.pow(gablePoint1[3] - gablePoint1[1], 2))
|
|
const gableLength2 = Math.sqrt(Math.pow(gablePoint2[2] - gablePoint2[0], 2) + Math.pow(gablePoint2[3] - gablePoint2[1], 2))
|
|
const gableLength3 = Math.sqrt(Math.pow(gablePoint3[2] - gablePoint3[0], 2) + Math.pow(gablePoint3[3] - gablePoint3[1], 2))
|
|
if (gableLength1 >= 1) {
|
|
innerLines.push(drawHipLine(gablePoint1, canvas, roof, textMode, prevDegree, prevDegree))
|
|
}
|
|
if (gableLength2 >= 1) {
|
|
innerLines.push(drawRoofLine(gablePoint2, canvas, roof, textMode))
|
|
}
|
|
if (gableLength3 >= 1) {
|
|
innerLines.push(drawHipLine(gablePoint3, canvas, roof, textMode, nextDegree, nextDegree))
|
|
}
|
|
|
|
const currentRoofLine = analyze.roofLine
|
|
let prevRoofLine, nextRoofLine
|
|
roof.lines.forEach((roofLine, index) => {
|
|
if (roofLine === currentRoofLine) {
|
|
nextRoofLine = roof.lines[(index + 1) % roof.lines.length]
|
|
prevRoofLine = roof.lines[(index - 1 + roof.lines.length) % roof.lines.length]
|
|
}
|
|
})
|
|
|
|
/** 이전, 다음라인의 사잇각의 vector를 구한다. */
|
|
let prevVector = getHalfAngleVector(prevRoofLine, currentRoofLine)
|
|
let nextVector = getHalfAngleVector(currentRoofLine, nextRoofLine)
|
|
|
|
/** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/
|
|
const prevCheckPoint = {
|
|
x: currentRoofLine.x1 + prevVector.x * 10,
|
|
y: currentRoofLine.y1 + prevVector.y * 10,
|
|
}
|
|
/** 다음 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/
|
|
const nextCheckPoint = {
|
|
x: currentRoofLine.x2 + nextVector.x * 10,
|
|
y: currentRoofLine.y2 + nextVector.y * 10,
|
|
}
|
|
|
|
let prevHipVector, nextHipVector
|
|
|
|
if (roof.inPolygon(prevCheckPoint)) {
|
|
prevHipVector = { x: prevVector.x, y: prevVector.y }
|
|
} else {
|
|
prevHipVector = { x: -prevVector.x, y: -prevVector.y }
|
|
}
|
|
|
|
/** 다음 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/
|
|
if (roof.inPolygon(nextCheckPoint)) {
|
|
nextHipVector = { x: nextVector.x, y: nextVector.y }
|
|
} else {
|
|
nextHipVector = { x: -nextVector.x, y: -nextVector.y }
|
|
}
|
|
|
|
const prevHipEdge = {
|
|
vertex1: { x: gablePoint2[0], y: gablePoint2[1] },
|
|
vertex2: { x: gablePoint2[0] + prevHipVector.x * gableWidth, y: gablePoint2[1] + prevHipVector.y * gableWidth },
|
|
}
|
|
const nextHipEdge = {
|
|
vertex1: { x: gablePoint2[2], y: gablePoint2[3] },
|
|
vertex2: { x: gablePoint2[2] + nextHipVector.x * gableWidth, y: gablePoint2[3] + nextHipVector.y * gableWidth },
|
|
}
|
|
|
|
const hipIntersection = edgesIntersection(prevHipEdge, nextHipEdge)
|
|
if (hipIntersection) {
|
|
const prevHipPoint = [prevHipEdge.vertex1.x, prevHipEdge.vertex1.y, hipIntersection.x, hipIntersection.y]
|
|
const nextHipPoint = [nextHipEdge.vertex1.x, nextHipEdge.vertex1.y, hipIntersection.x, hipIntersection.y]
|
|
innerLines.push(drawHipLine(prevHipPoint, canvas, roof, textMode, currentDegree, currentDegree))
|
|
innerLines.push(drawHipLine(nextHipPoint, canvas, roof, textMode, currentDegree, currentDegree))
|
|
|
|
const midPoint = { x: hipIntersection.x, y: hipIntersection.y }
|
|
const checkEdge = {
|
|
vertex1: { x: midPoint.x, y: midPoint.y },
|
|
vertex2: { x: midPoint.x + -analyze.roofVector.x * 10000, y: midPoint.y + -analyze.roofVector.y * 10000 },
|
|
}
|
|
const intersections = []
|
|
roof.lines
|
|
.filter((line) => line !== currentRoofLine)
|
|
.forEach((line) => {
|
|
const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
|
|
const intersect = edgesIntersection(lineEdge, checkEdge)
|
|
if (intersect && isPointOnLineNew(line, intersect)) {
|
|
const distance = Math.sqrt(Math.pow(midPoint.x - intersect.x, 2) + Math.pow(midPoint.y - intersect.y, 2))
|
|
intersections.push({ intersect, distance })
|
|
}
|
|
})
|
|
intersections.sort((a, b) => a.distance - b.distance)
|
|
if (intersections.length > 0) {
|
|
const intersect = intersections[0].intersect
|
|
const point = [midPoint.x, midPoint.y, intersect.x, intersect.y]
|
|
|
|
//마루가 최대로 뻗어나갈수 있는 포인트. null일때는 맞은편 지붕선 까지로 판단한다.
|
|
const drivePoint = getRidgeDrivePoint(point, prevLine, nextLine, baseLines)
|
|
const isOverlapBefore = analyze.isHorizontal
|
|
? almostEqual(Math.min(beforePrevLine.x1, beforePrevLine.x2), Math.min(currentLine.x1, currentLine.x2)) &&
|
|
almostEqual(Math.max(beforePrevLine.x1, beforePrevLine.x2), Math.max(currentLine.x1, currentLine.x2))
|
|
: almostEqual(Math.min(beforePrevLine.y1, beforePrevLine.y2), Math.min(currentLine.y1, currentLine.y2)) &&
|
|
almostEqual(Math.max(beforePrevLine.y1, beforePrevLine.y2), Math.max(currentLine.y1, currentLine.y2))
|
|
const isOverlapAfter = analyze.isHorizontal
|
|
? almostEqual(Math.min(afterNextLine.x1, afterNextLine.x2), Math.min(currentLine.x1, currentLine.x2)) &&
|
|
almostEqual(Math.max(afterNextLine.x1, afterNextLine.x2), Math.max(currentLine.x1, currentLine.x2))
|
|
: almostEqual(Math.min(afterNextLine.y1, afterNextLine.y2), Math.min(currentLine.y1, currentLine.y2)) &&
|
|
almostEqual(Math.max(afterNextLine.y1, afterNextLine.y2), Math.max(currentLine.y1, currentLine.y2))
|
|
|
|
if (isOverlapBefore || isOverlapAfter) {
|
|
const oppLine = isOverlapBefore ? beforePrevLine : afterNextLine
|
|
const otherGable = baseLines.find(
|
|
(l) => l !== currentLine && l !== prevLine && l !== nextLine && l !== oppLine && l.attributes.type === LINE_TYPE.WALLLINE.GABLE,
|
|
)
|
|
if (!otherGable) {
|
|
const pointVector = { x: Math.sign(clamp01(point[0] - point[2])), y: Math.sign(clamp01(point[1] - point[3])) }
|
|
let offset = 0
|
|
switch (oppLine.attributes.type) {
|
|
case LINE_TYPE.WALLLINE.HIPANDGABLE:
|
|
offset = oppLine.attributes.width
|
|
break
|
|
case LINE_TYPE.WALLLINE.JERKINHEAD:
|
|
offset = oppLine.attributes.width / 2
|
|
break
|
|
case LINE_TYPE.WALLLINE.WALL:
|
|
offset = 0
|
|
break
|
|
default:
|
|
break
|
|
}
|
|
if (oppLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) {
|
|
if (drivePoint) {
|
|
point[2] = drivePoint.x
|
|
point[3] = drivePoint.y
|
|
} else {
|
|
point[2] += pointVector.x * offset
|
|
point[3] += pointVector.y * offset
|
|
}
|
|
} else {
|
|
point[2] += pointVector.x * offset
|
|
point[3] += pointVector.y * offset
|
|
}
|
|
} else if (drivePoint) {
|
|
point[2] = drivePoint.x
|
|
point[3] = drivePoint.y
|
|
}
|
|
} else if (drivePoint) {
|
|
point[2] = drivePoint.x
|
|
point[3] = drivePoint.y
|
|
}
|
|
|
|
linesAnalysis.push({
|
|
start: { x: point[0], y: point[1] },
|
|
end: { x: point[2], y: point[3] },
|
|
left: baseLines.findIndex((line) => line === prevLine),
|
|
right: baseLines.findIndex((line) => line === nextLine),
|
|
type: TYPES.RIDGE,
|
|
degree: 0,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
})
|
|
//5. 케라바 판단, 케라바는 양옆이 처마여야 한다.
|
|
gables.forEach((currentLine) => {
|
|
let prevLine, nextLine
|
|
baseLines.forEach((baseLine, index) => {
|
|
if (baseLine === currentLine) {
|
|
nextLine = baseLines[(index + 1) % baseLines.length]
|
|
prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length]
|
|
}
|
|
})
|
|
|
|
//양옆이 처마가 아닐때 패스
|
|
if (prevLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES || nextLine.attributes.type !== LINE_TYPE.WALLLINE.EAVES) {
|
|
return
|
|
}
|
|
|
|
const prevLineVector = { x: Math.sign(clamp01(prevLine.x1 - prevLine.x2)), y: Math.sign(clamp01(prevLine.y1 - prevLine.y2)) }
|
|
const nextLineVector = { x: Math.sign(clamp01(nextLine.x1 - nextLine.x2)), y: Math.sign(clamp01(nextLine.y1 - nextLine.y2)) }
|
|
|
|
const inPolygonPoint = {
|
|
x: (currentLine.x1 + currentLine.x2) / 2 + Math.sign(nextLine.x2 - nextLine.x1),
|
|
y: (currentLine.y1 + currentLine.y2) / 2 + Math.sign(nextLine.y2 - nextLine.y1),
|
|
}
|
|
|
|
//좌우 라인이 서로 다른방향일때
|
|
if ((prevLineVector.x !== nextLineVector.x || prevLineVector.y !== nextLineVector.y) && checkWallPolygon.inPolygon(inPolygonPoint)) {
|
|
const analyze = analyzeLine(currentLine)
|
|
|
|
let beforePrevLine, afterNextLine
|
|
baseLines.forEach((baseLine, index) => {
|
|
if (baseLine === prevLine) {
|
|
beforePrevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length]
|
|
}
|
|
if (baseLine === nextLine) {
|
|
afterNextLine = baseLines[(index + 1) % baseLines.length]
|
|
}
|
|
})
|
|
|
|
const currentVector = { x: Math.sign(clamp01(currentLine.x1 - currentLine.x2)), y: Math.sign(clamp01(currentLine.y1 - currentLine.y2)) }
|
|
// 마루 진행 최대 길이
|
|
let prevDistance = 0,
|
|
nextDistance = 0
|
|
|
|
// 반대쪽 라인이 특정조건일때 마루선을 반대쪽까지로 처리한다..
|
|
const isOverlapBefore = analyze.isHorizontal
|
|
? almostEqual(Math.min(beforePrevLine.x1, beforePrevLine.x2), Math.min(currentLine.x1, currentLine.x2)) &&
|
|
almostEqual(Math.max(beforePrevLine.x1, beforePrevLine.x2), Math.max(currentLine.x1, currentLine.x2))
|
|
: almostEqual(Math.min(beforePrevLine.y1, beforePrevLine.y2), Math.min(currentLine.y1, currentLine.y2)) &&
|
|
almostEqual(Math.max(beforePrevLine.y1, beforePrevLine.y2), Math.max(currentLine.y1, currentLine.y2))
|
|
const isOverlapAfter = analyze.isHorizontal
|
|
? almostEqual(Math.min(afterNextLine.x1, afterNextLine.x2), Math.min(currentLine.x1, currentLine.x2)) &&
|
|
almostEqual(Math.max(afterNextLine.x1, afterNextLine.x2), Math.max(currentLine.x1, currentLine.x2))
|
|
: almostEqual(Math.min(afterNextLine.y1, afterNextLine.y2), Math.min(currentLine.y1, currentLine.y2)) &&
|
|
almostEqual(Math.max(afterNextLine.y1, afterNextLine.y2), Math.max(currentLine.y1, currentLine.y2))
|
|
|
|
let isOverlap = false
|
|
if (isOverlapBefore || isOverlapAfter) {
|
|
const oppLine = isOverlapBefore ? beforePrevLine : afterNextLine
|
|
const otherGable = baseLines.find(
|
|
(l) => l !== currentLine && l !== prevLine && l !== nextLine && l !== oppLine && l.attributes.type === LINE_TYPE.WALLLINE.GABLE,
|
|
)
|
|
if (!otherGable) {
|
|
isOverlap = true
|
|
}
|
|
}
|
|
|
|
if (isOverlap) {
|
|
const oppLine = isOverlapBefore ? beforePrevLine : afterNextLine
|
|
const cMidX = (currentLine.x1 + currentLine.x2) / 2
|
|
const cMidY = (currentLine.y1 + currentLine.y2) / 2
|
|
const oMidX = (oppLine.x1 + oppLine.x2) / 2
|
|
const oMidY = (oppLine.y1 + oppLine.y2) / 2
|
|
const cOffset = currentLine.attributes.offset
|
|
const oOffset = oppLine.attributes.type === LINE_TYPE.WALLLINE.WALL ? 0 : oppLine.attributes.offset
|
|
const ridgeVector = { x: Math.sign(clamp01(cMidX - oMidX)), y: Math.sign(clamp01(cMidY - oMidY)) }
|
|
const ridgePoint = [
|
|
cMidX + ridgeVector.x * cOffset,
|
|
cMidY + ridgeVector.y * cOffset,
|
|
oMidX + -ridgeVector.x * oOffset,
|
|
oMidY + -ridgeVector.y * oOffset,
|
|
]
|
|
let offset = 0
|
|
switch (oppLine.attributes.type) {
|
|
case LINE_TYPE.WALLLINE.HIPANDGABLE:
|
|
offset = oppLine.attributes.width
|
|
break
|
|
case LINE_TYPE.WALLLINE.JERKINHEAD:
|
|
offset = oppLine.attributes.width / 2
|
|
break
|
|
case LINE_TYPE.WALLLINE.WALL:
|
|
offset = 0
|
|
break
|
|
default:
|
|
break
|
|
}
|
|
ridgePoint[2] += ridgeVector.x * offset
|
|
ridgePoint[3] += ridgeVector.y * offset
|
|
|
|
linesAnalysis.push({
|
|
start: { x: ridgePoint[0], y: ridgePoint[1] },
|
|
end: { x: ridgePoint[2], y: ridgePoint[3] },
|
|
left: baseLines.findIndex((line) => line === prevLine),
|
|
right: baseLines.findIndex((line) => line === nextLine),
|
|
type: TYPES.RIDGE,
|
|
degree: 0,
|
|
})
|
|
} else {
|
|
if (beforePrevLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) {
|
|
const beforeVector = {
|
|
x: Math.sign(clamp01(beforePrevLine.x1 - beforePrevLine.x2)),
|
|
y: Math.sign(clamp01(beforePrevLine.y1 - beforePrevLine.y2)),
|
|
}
|
|
prevDistance = Math.sqrt(Math.pow(prevLine.x2 - prevLine.x1, 2) + Math.pow(prevLine.y2 - prevLine.y1, 2)) + currentLine.attributes.offset
|
|
if (beforeVector.x === currentVector.x && beforeVector.y === currentVector.y) {
|
|
prevDistance = prevDistance - beforePrevLine.attributes.offset
|
|
} else {
|
|
prevDistance = prevDistance + beforePrevLine.attributes.offset
|
|
}
|
|
} else {
|
|
prevDistance =
|
|
Math.sqrt(Math.pow(prevLine.x2 - prevLine.x1, 2) + Math.pow(prevLine.y2 - prevLine.y1, 2)) +
|
|
currentLine.attributes.offset +
|
|
beforePrevLine.attributes.offset
|
|
}
|
|
|
|
if (afterNextLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) {
|
|
const afterVector = {
|
|
x: Math.sign(clamp01(afterNextLine.x1 - afterNextLine.x2)),
|
|
y: Math.sign(clamp01(afterNextLine.y1 - afterNextLine.y2)),
|
|
}
|
|
nextDistance = Math.sqrt(Math.pow(nextLine.x2 - nextLine.x1, 2) + Math.pow(nextLine.y2 - nextLine.y1, 2)) + currentLine.attributes.offset
|
|
if (afterVector.x === currentVector.x && afterVector.y === currentVector.y) {
|
|
nextDistance = nextDistance - afterNextLine.attributes.offset
|
|
} else {
|
|
nextDistance = nextDistance + afterNextLine.attributes.offset
|
|
}
|
|
} else {
|
|
nextDistance =
|
|
Math.sqrt(Math.pow(nextLine.x2 - nextLine.x1, 2) + Math.pow(nextLine.y2 - nextLine.y1, 2)) +
|
|
currentLine.attributes.offset +
|
|
afterNextLine.attributes.offset
|
|
}
|
|
|
|
//좌우 선분 의 이전 다음 선이 케라바인경우 둘 중 긴 쪽이 기준선이 된다. 아닌경우 짧은쪽
|
|
let stdLine
|
|
let stdFindOppVector
|
|
if (beforePrevLine.attributes.type === LINE_TYPE.WALLLINE.GABLE && afterNextLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) {
|
|
if (nextDistance <= prevDistance) {
|
|
stdLine = prevLine
|
|
if (prevLineVector.x === 0) {
|
|
stdFindOppVector = { x: Math.sign(clamp01(prevLine.x1 - nextLine.x1)), y: 0 }
|
|
} else {
|
|
stdFindOppVector = { x: 0, y: Math.sign(clamp01(prevLine.y1 - nextLine.y1)) }
|
|
}
|
|
} else {
|
|
stdLine = nextLine
|
|
if (nextLineVector.x === 0) {
|
|
stdFindOppVector = { x: Math.sign(clamp01(nextLine.x1 - prevLine.x1)), y: 0 }
|
|
} else {
|
|
stdFindOppVector = { x: 0, y: Math.sign(clamp01(nextLine.y1 - prevLine.y1)) }
|
|
}
|
|
}
|
|
} else {
|
|
if (nextDistance <= prevDistance) {
|
|
stdLine = nextLine
|
|
if (nextLineVector.x === 0) {
|
|
stdFindOppVector = { x: Math.sign(clamp01(nextLine.x1 - prevLine.x1)), y: 0 }
|
|
} else {
|
|
stdFindOppVector = { x: 0, y: Math.sign(clamp01(nextLine.y1 - prevLine.y1)) }
|
|
}
|
|
} else {
|
|
stdLine = prevLine
|
|
if (prevLineVector.x === 0) {
|
|
stdFindOppVector = { x: Math.sign(clamp01(prevLine.x1 - nextLine.x1)), y: 0 }
|
|
} else {
|
|
stdFindOppVector = { x: 0, y: Math.sign(clamp01(prevLine.y1 - nextLine.y1)) }
|
|
}
|
|
}
|
|
}
|
|
const stdAnalyze = analyzeLine(stdLine)
|
|
let stdPoints = []
|
|
|
|
const stdPrevLine = baseLines[(baseLines.indexOf(stdLine) - 1 + baseLines.length) % baseLines.length]
|
|
const stdNextLine = baseLines[(baseLines.indexOf(stdLine) + 1) % baseLines.length]
|
|
const stdVector = { x: Math.sign(clamp01(stdLine.x1 - stdLine.x2)), y: Math.sign(clamp01(stdLine.y1 - stdLine.y2)) }
|
|
const stdPrevVector = { x: Math.sign(clamp01(stdPrevLine.x1 - stdPrevLine.x2)), y: Math.sign(clamp01(stdPrevLine.y1 - stdPrevLine.y2)) }
|
|
const stdNextVector = { x: Math.sign(clamp01(stdNextLine.x1 - stdNextLine.x2)), y: Math.sign(clamp01(stdNextLine.y1 - stdNextLine.y2)) }
|
|
let stdAdjustVector = { x: 0, y: 0 }
|
|
|
|
if (stdPrevVector.x === stdNextVector.x && stdPrevVector.y === stdNextVector.y) {
|
|
if (stdAnalyze.isHorizontal) {
|
|
if (stdVector.x === 1) {
|
|
if (stdPrevLine.y1 > stdNextLine.y1) {
|
|
stdPoints.push(stdLine.x1 + stdPrevLine.attributes.offset, stdLine.y1)
|
|
if (stdNextLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) {
|
|
stdPoints.push(stdLine.x2 + stdNextLine.attributes.offset, stdLine.y2)
|
|
} else {
|
|
stdPoints.push(stdLine.x2, stdLine.y2)
|
|
}
|
|
} else {
|
|
if (stdPrevLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) {
|
|
stdPoints.push(stdLine.x1 - stdPrevLine.attributes.offset, stdLine.y1)
|
|
} else {
|
|
stdPoints.push(stdLine.x1, stdLine.y1)
|
|
}
|
|
stdPoints.push(stdLine.x2 - stdNextLine.attributes.offset, stdLine.y2)
|
|
}
|
|
} else {
|
|
if (stdPrevLine.y1 < stdNextLine.y1) {
|
|
stdPoints.push(stdLine.x1 - stdPrevLine.attributes.offset, stdLine.y1)
|
|
if (stdNextLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) {
|
|
stdPoints.push(stdLine.x2 - stdNextLine.attributes.offset, stdLine.y2)
|
|
} else {
|
|
stdPoints.push(stdLine.x2, stdLine.y2)
|
|
}
|
|
} else {
|
|
if (stdPrevLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) {
|
|
stdPoints.push(stdLine.x1 + stdPrevLine.attributes.offset, stdLine.y1)
|
|
} else {
|
|
stdPoints.push(stdLine.x1, stdLine.y1)
|
|
}
|
|
stdPoints.push(stdLine.x2 + stdNextLine.attributes.offset, stdLine.y2)
|
|
}
|
|
}
|
|
}
|
|
if (stdAnalyze.isVertical) {
|
|
if (stdVector.y === 1) {
|
|
if (stdPrevLine.x1 > stdNextLine.x1) {
|
|
if (stdPrevLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) {
|
|
stdPoints.push(stdLine.x1, stdLine.y1 - stdPrevLine.attributes.offset)
|
|
} else {
|
|
stdPoints.push(stdLine.x1, stdLine.y1)
|
|
}
|
|
stdPoints.push(stdLine.x2, stdLine.y2 - stdNextLine.attributes.offset)
|
|
} else {
|
|
stdPoints.push(stdLine.x1, stdLine.y1 + stdPrevLine.attributes.offset)
|
|
if (stdNextLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) {
|
|
stdPoints.push(stdLine.x2, stdLine.y2 + stdNextLine.attributes.offset)
|
|
} else {
|
|
stdPoints.push(stdLine.x2, stdLine.y2)
|
|
}
|
|
}
|
|
} else {
|
|
if (stdPrevLine.x1 < stdNextLine.x1) {
|
|
if (stdPrevLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) {
|
|
stdPoints.push(stdLine.x1, stdLine.y1 + stdPrevLine.attributes.offset)
|
|
} else {
|
|
stdPoints.push(stdLine.x1, stdLine.y1)
|
|
}
|
|
stdPoints.push(stdLine.x2, stdLine.y2 + stdNextLine.attributes.offset)
|
|
} else {
|
|
stdPoints.push(stdLine.x1, stdLine.y1 - stdPrevLine.attributes.offset)
|
|
if (stdNextLine.attributes.type === LINE_TYPE.WALLLINE.GABLE) {
|
|
stdPoints.push(stdLine.x2, stdLine.y2 - stdNextLine.attributes.offset)
|
|
} else {
|
|
stdPoints.push(stdLine.x2, stdLine.y2)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
const stdAddPrevVector = { x: Math.sign(clamp01(stdLine.x1 - stdLine.x2)), y: Math.sign(clamp01(stdLine.y1 - stdLine.y2)) }
|
|
const stdAddNextVector = { x: Math.sign(clamp01(stdLine.x2 - stdLine.x1)), y: Math.sign(clamp01(stdLine.y2 - stdLine.y1)) }
|
|
stdPoints = [
|
|
stdLine.x1 + stdAddPrevVector.x * stdPrevLine.attributes.offset,
|
|
stdLine.y1 + stdAddPrevVector.y * stdPrevLine.attributes.offset,
|
|
stdLine.x2 + stdAddNextVector.x * stdNextLine.attributes.offset,
|
|
stdLine.y2 + stdAddNextVector.y * stdNextLine.attributes.offset,
|
|
]
|
|
}
|
|
//기준지붕선의 반대쪽선
|
|
const oppositeLine = []
|
|
|
|
const startX = Math.min(stdLine.x1, stdLine.x2)
|
|
const endX = Math.max(stdLine.x1, stdLine.x2)
|
|
const startY = Math.min(stdLine.y1, stdLine.y2)
|
|
const endY = Math.max(stdLine.y1, stdLine.y2)
|
|
baseLines
|
|
.filter((line) => line !== stdLine && line !== currentLine && line.attributes.type !== LINE_TYPE.WALLLINE.SHED)
|
|
.filter((line) => {
|
|
const lineVector = { x: Math.sign(clamp01(line.x1 - line.x2)), y: Math.sign(clamp01(line.y1 - line.y2)) }
|
|
let oppVector = { x: 0, y: 0 }
|
|
if (stdVector.x === 0) {
|
|
oppVector = { x: Math.sign(clamp01(stdLine.x1 - line.x1)), y: 0 }
|
|
}
|
|
if (stdVector.y === 0) {
|
|
oppVector = { x: 0, y: Math.sign(clamp01(stdLine.y1 - line.y1)) }
|
|
}
|
|
const rightDirection =
|
|
(stdVector.x === lineVector.x && stdVector.y !== lineVector.y) || (stdVector.x !== lineVector.x && stdVector.y === lineVector.y)
|
|
const rightOpp = stdFindOppVector.x === oppVector.x && stdFindOppVector.y === oppVector.y
|
|
return rightDirection && rightOpp
|
|
})
|
|
.forEach((line) => {
|
|
const lineStartX = Math.min(line.x1, line.x2)
|
|
const lineEndX = Math.max(line.x1, line.x2)
|
|
const lineStartY = Math.min(line.y1, line.y2)
|
|
const lineEndY = Math.max(line.y1, line.y2)
|
|
|
|
if (stdAnalyze.isHorizontal) {
|
|
//full overlap
|
|
if (lineStartX <= startX && endX <= lineEndX) {
|
|
oppositeLine.push({ line, distance: Math.abs(lineStartY - startY) })
|
|
} else if (
|
|
(startX < lineStartX && lineStartX < endX) ||
|
|
(startX < lineStartX && lineEndX < endX) ||
|
|
(lineStartX === startX && lineEndX <= endX) ||
|
|
(lineEndX === endX && lineStartX <= startX)
|
|
) {
|
|
oppositeLine.push({ line, distance: Math.abs(lineStartY - startY) })
|
|
}
|
|
}
|
|
if (stdAnalyze.isVertical) {
|
|
//full overlap
|
|
if (lineStartY <= startY && endY <= lineEndY) {
|
|
oppositeLine.push({ line, distance: Math.abs(lineStartX - startX) })
|
|
} else if (
|
|
(startY < lineStartY && lineStartY < endY) ||
|
|
(startY < lineEndY && lineEndY < endY) ||
|
|
(lineStartY === startY && lineEndY <= endY) ||
|
|
(lineEndY === endY && lineStartY <= startY)
|
|
) {
|
|
oppositeLine.push({ line, distance: Math.abs(lineStartX - startX) })
|
|
}
|
|
}
|
|
})
|
|
|
|
if (oppositeLine.length > 0) {
|
|
const ridgePoints = []
|
|
|
|
oppositeLine.sort((a, b) => a.distance - b.distance)
|
|
oppositeLine.forEach((opposite) => {
|
|
const oppLine = opposite.line
|
|
const oppIndex = baseLines.findIndex((line) => line === oppLine)
|
|
//마주하는 라인의 이전 다음 라인.
|
|
const oppPrevLine = baseLines[(oppIndex - 1 + baseLines.length) % baseLines.length]
|
|
const oppNextLine = baseLines[(oppIndex + 1) % baseLines.length]
|
|
const oppVector = { x: Math.sign(oppLine.x2 - oppLine.x1), y: Math.sign(oppLine.y2 - oppLine.y1) }
|
|
let ridgePoint
|
|
|
|
const oppPrevVector = { x: Math.sign(oppPrevLine.x1 - oppPrevLine.x2), y: Math.sign(oppPrevLine.y1 - oppPrevLine.y2) }
|
|
const oppNextVector = { x: Math.sign(oppNextLine.x1 - oppNextLine.x2), y: Math.sign(oppNextLine.y1 - oppNextLine.y2) }
|
|
if (oppPrevVector.x === oppNextVector.x && oppPrevVector.y === oppNextVector.y) {
|
|
const addOffsetX1 = oppPrevVector.y * oppPrevLine.attributes.offset
|
|
const addOffsetY1 = -oppPrevVector.x * oppPrevLine.attributes.offset
|
|
const addOffsetX2 = oppPrevVector.y * oppNextLine.attributes.offset
|
|
const addOffsetY2 = -oppPrevVector.x * oppNextLine.attributes.offset
|
|
ridgePoint = [oppLine.x1 + addOffsetX1, oppLine.y1 + addOffsetY1, oppLine.x2 + addOffsetX2, oppLine.y2 + addOffsetY2]
|
|
} else {
|
|
const inPolygonPoint = {
|
|
x: (oppLine.x1 + oppLine.x2) / 2 + Math.sign(oppNextLine.x2 - oppNextLine.x1),
|
|
y: (oppLine.y1 + oppLine.y2) / 2 + Math.sign(oppNextLine.y2 - oppNextLine.y1),
|
|
}
|
|
if (checkWallPolygon.inPolygon(inPolygonPoint)) {
|
|
ridgePoint = [
|
|
oppLine.x1 + -oppVector.x * oppPrevLine.attributes.offset,
|
|
oppLine.y1 + -oppVector.y * oppPrevLine.attributes.offset,
|
|
oppLine.x2 + oppVector.x * oppNextLine.attributes.offset,
|
|
oppLine.y2 + oppVector.y * oppNextLine.attributes.offset,
|
|
]
|
|
} else {
|
|
ridgePoint = [
|
|
oppLine.x1 + oppVector.x * oppPrevLine.attributes.offset,
|
|
oppLine.y1 + oppVector.y * oppPrevLine.attributes.offset,
|
|
oppLine.x2 + -oppVector.x * oppNextLine.attributes.offset,
|
|
oppLine.y2 + -oppVector.y * oppNextLine.attributes.offset,
|
|
]
|
|
}
|
|
}
|
|
if (stdAnalyze.isHorizontal) {
|
|
ridgePoint[1] = (stdLine.y1 + oppLine.y1) / 2
|
|
ridgePoint[3] = (stdLine.y2 + oppLine.y2) / 2
|
|
}
|
|
if (stdAnalyze.isVertical) {
|
|
ridgePoint[0] = (stdLine.x1 + oppLine.x1) / 2
|
|
ridgePoint[2] = (stdLine.x2 + oppLine.x2) / 2
|
|
}
|
|
|
|
if (
|
|
(oppPrevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES || oppNextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) &&
|
|
!(oppPrevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES && oppNextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES)
|
|
) {
|
|
const oppLineLength = Math.sqrt(Math.pow(oppLine.x2 - oppLine.x1, 2) + Math.pow(oppLine.y2 - oppLine.y1, 2))
|
|
const stdLineLength = Math.sqrt(Math.pow(stdLine.x2 - stdLine.x1, 2) + Math.pow(stdLine.y2 - stdLine.y1, 2))
|
|
//기준선이 반대선보다 길이가 짧을때는 추녀마루에 대한 교점 처리 패스
|
|
if (stdLineLength >= oppLineLength) {
|
|
if (oppPrevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) {
|
|
//처마라인이 아닌 반대쪽부터 마루선을 시작하도록 포인트 변경
|
|
const oppNextAnalyze = analyzeLine(oppNextLine)
|
|
if (oppNextAnalyze.isHorizontal) {
|
|
const dist1 = Math.abs(oppNextLine.y1 - ridgePoint[1])
|
|
const dist2 = Math.abs(oppNextLine.y1 - ridgePoint[3])
|
|
if (dist1 > dist2) {
|
|
ridgePoint = [ridgePoint[2], ridgePoint[3], ridgePoint[0], ridgePoint[1]]
|
|
}
|
|
}
|
|
if (oppNextAnalyze.isVertical) {
|
|
const dist1 = Math.abs(oppNextLine.x1 - ridgePoint[0])
|
|
const dist2 = Math.abs(oppNextLine.x1 - ridgePoint[2])
|
|
if (dist1 > dist2) {
|
|
ridgePoint = [ridgePoint[2], ridgePoint[3], ridgePoint[0], ridgePoint[1]]
|
|
}
|
|
}
|
|
|
|
const checkEdge = { vertex1: { x: ridgePoint[0], y: ridgePoint[1] }, vertex2: { x: ridgePoint[2], y: ridgePoint[3] } }
|
|
const checkVector = getHalfAngleVector(oppLine, oppPrevLine)
|
|
const checkPoint = {
|
|
x: oppLine.x1 + (checkVector.x * 10) / 2,
|
|
y: oppLine.y1 + (checkVector.y * 10) / 2,
|
|
}
|
|
|
|
let hipVector
|
|
if (checkWallPolygon.inPolygon(checkPoint)) {
|
|
hipVector = { x: checkVector.x, y: checkVector.y }
|
|
} else {
|
|
hipVector = { x: -checkVector.x, y: -checkVector.y }
|
|
}
|
|
|
|
const ridgeVector = { x: Math.sign(ridgePoint[0] - ridgePoint[2]), y: Math.sign(ridgePoint[1] - ridgePoint[3]) }
|
|
const hipEdge = { vertex1: { x: oppLine.x1, y: oppLine.y1 }, vertex2: { x: oppLine.x1 + hipVector.x, y: oppLine.y1 + hipVector.y } }
|
|
const intersect = edgesIntersection(hipEdge, checkEdge)
|
|
if (intersect) {
|
|
const intersectVector = { x: Math.sign(ridgePoint[0] - intersect.x), y: Math.sign(ridgePoint[1] - intersect.y) }
|
|
if (ridgeVector.x === intersectVector.x && ridgeVector.y === intersectVector.y) {
|
|
ridgePoint = [ridgePoint[0], ridgePoint[1], intersect.x, intersect.y]
|
|
} else {
|
|
ridgePoint = [ridgePoint[2], ridgePoint[3], ridgePoint[2], ridgePoint[3]]
|
|
}
|
|
}
|
|
}
|
|
if (oppNextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) {
|
|
//처마라인이 아닌 반대쪽부터 마루선을 시작하도록 포인트 변경
|
|
const oppPrevAnalyze = analyzeLine(oppPrevLine)
|
|
if (oppPrevAnalyze.isHorizontal) {
|
|
const dist1 = Math.abs(oppPrevLine.y1 - ridgePoint[1])
|
|
const dist2 = Math.abs(oppPrevLine.y1 - ridgePoint[3])
|
|
if (dist1 > dist2) {
|
|
ridgePoint = [ridgePoint[2], ridgePoint[3], ridgePoint[0], ridgePoint[1]]
|
|
}
|
|
}
|
|
if (oppPrevAnalyze.isVertical) {
|
|
const dist1 = Math.abs(oppPrevLine.x1 - ridgePoint[0])
|
|
const dist2 = Math.abs(oppPrevLine.x1 - ridgePoint[2])
|
|
if (dist1 > dist2) {
|
|
ridgePoint = [ridgePoint[2], ridgePoint[3], ridgePoint[0], ridgePoint[1]]
|
|
}
|
|
}
|
|
const checkEdge = { vertex1: { x: ridgePoint[0], y: ridgePoint[1] }, vertex2: { x: ridgePoint[2], y: ridgePoint[3] } }
|
|
const checkVector = getHalfAngleVector(oppLine, oppNextLine)
|
|
const checkPoint = { x: oppLine.x2 + (checkVector.x * 10) / 2, y: oppLine.y2 + (checkVector.y * 10) / 2 }
|
|
|
|
let hipVector
|
|
if (checkWallPolygon.inPolygon(checkPoint)) {
|
|
hipVector = { x: checkVector.x, y: checkVector.y }
|
|
} else {
|
|
hipVector = { x: -checkVector.x, y: -checkVector.y }
|
|
}
|
|
|
|
const ridgeVector = { x: Math.sign(ridgePoint[0] - ridgePoint[2]), y: Math.sign(ridgePoint[1] - ridgePoint[3]) }
|
|
const hipEdge = { vertex1: { x: oppLine.x2, y: oppLine.y2 }, vertex2: { x: oppLine.x2 + hipVector.x, y: oppLine.y2 + hipVector.y } }
|
|
const intersect = edgesIntersection(hipEdge, checkEdge)
|
|
if (intersect) {
|
|
const intersectVector = { x: Math.sign(ridgePoint[0] - intersect.x), y: Math.sign(ridgePoint[1] - intersect.y) }
|
|
if (ridgeVector.x === intersectVector.x && ridgeVector.y === intersectVector.y) {
|
|
ridgePoint = [ridgePoint[0], ridgePoint[1], intersect.x, intersect.y]
|
|
} else {
|
|
ridgePoint = [ridgePoint[2], ridgePoint[3], ridgePoint[2], ridgePoint[3]]
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
if (stdPrevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES || stdNextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) {
|
|
const vectorLine = stdPrevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES ? stdPrevLine : stdNextLine
|
|
let driveVector = getHalfAngleVector(vectorLine, stdLine)
|
|
const allPoints = [
|
|
{ x: stdLine.x1, y: stdLine.y1 },
|
|
{ x: stdLine.x2, y: stdLine.y2 },
|
|
{ x: vectorLine.x1, y: vectorLine.y1 },
|
|
{ x: vectorLine.x2, y: vectorLine.y2 },
|
|
]
|
|
let startPoint
|
|
for (let i = 0; i < allPoints.length; i++) {
|
|
const point = allPoints[i]
|
|
for (let j = i + 1; j < allPoints.length; j++) {
|
|
if (i === j) continue
|
|
const point2 = allPoints[j]
|
|
if (almostEqual(point.x, point2.x) && almostEqual(point.y, point2.y)) {
|
|
startPoint = point2
|
|
break
|
|
}
|
|
}
|
|
if (startPoint) break
|
|
}
|
|
|
|
const checkDrivePoint = { x: startPoint.x + driveVector.x * 5, y: startPoint.y + driveVector.y * 5 }
|
|
if (!checkWallPolygon.inPolygon(checkDrivePoint)) {
|
|
driveVector = { x: -driveVector.x, y: -driveVector.y }
|
|
}
|
|
|
|
const driveEdge = {
|
|
vertex1: { x: startPoint.x, y: startPoint.y },
|
|
vertex2: { x: startPoint.x + driveVector.x, y: startPoint.y + driveVector.y },
|
|
}
|
|
const ridgeEdge = { vertex1: { x: ridgePoint[0], y: ridgePoint[1] }, vertex2: { x: ridgePoint[2], y: ridgePoint[3] } }
|
|
const intersect = edgesIntersection(driveEdge, ridgeEdge)
|
|
const rLength = Math.sqrt(Math.pow(ridgePoint[2] - ridgePoint[0], 2) + Math.pow(ridgePoint[3] - ridgePoint[1], 2))
|
|
if (intersect) {
|
|
const length1 = Math.sqrt(Math.pow(intersect.x - ridgePoint[0], 2) + Math.pow(intersect.y - ridgePoint[1], 2))
|
|
const length2 = Math.sqrt(Math.pow(intersect.x - ridgePoint[2], 2) + Math.pow(intersect.y - ridgePoint[3], 2))
|
|
if (rLength < length1 && rLength < length2) {
|
|
if (length1 >= length2) {
|
|
ridgePoint = [ridgePoint[0], ridgePoint[1], intersect.x, intersect.y]
|
|
} else {
|
|
ridgePoint = [ridgePoint[2], ridgePoint[3], intersect.x, intersect.y]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (stdPrevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES || stdNextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) {
|
|
const vectorLine = stdPrevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES ? stdPrevLine : stdNextLine
|
|
let driveVector = getHalfAngleVector(vectorLine, stdLine)
|
|
const allPoints = [
|
|
{ x: stdLine.x1, y: stdLine.y1 },
|
|
{ x: stdLine.x2, y: stdLine.y2 },
|
|
{ x: vectorLine.x1, y: vectorLine.y1 },
|
|
{ x: vectorLine.x2, y: vectorLine.y2 },
|
|
]
|
|
let startPoint
|
|
for (let i = 0; i < allPoints.length; i++) {
|
|
const point = allPoints[i]
|
|
for (let j = i + 1; j < allPoints.length; j++) {
|
|
if (i === j) continue
|
|
const point2 = allPoints[j]
|
|
if (almostEqual(point.x, point2.x) && almostEqual(point.y, point2.y)) {
|
|
startPoint = point2
|
|
break
|
|
}
|
|
}
|
|
if (startPoint) break
|
|
}
|
|
|
|
const checkDrivePoint = { x: startPoint.x + driveVector.x * 5, y: startPoint.y + driveVector.y * 5 }
|
|
if (!checkWallPolygon.inPolygon(checkDrivePoint)) {
|
|
driveVector = { x: -driveVector.x, y: -driveVector.y }
|
|
}
|
|
|
|
const driveEdge = {
|
|
vertex1: { x: startPoint.x, y: startPoint.y },
|
|
vertex2: { x: startPoint.x + driveVector.x, y: startPoint.y + driveVector.y },
|
|
}
|
|
const ridgeEdge = { vertex1: { x: ridgePoint[0], y: ridgePoint[1] }, vertex2: { x: ridgePoint[2], y: ridgePoint[3] } }
|
|
const intersect = edgesIntersection(driveEdge, ridgeEdge)
|
|
if (intersect) {
|
|
if (almostEqual(stdLine.x1, startPoint.x) && stdAnalyze.isHorizontal) {
|
|
stdPoints[0] = intersect.x
|
|
}
|
|
if (almostEqual(stdLine.y1, startPoint.y) && stdAnalyze.isVertical) {
|
|
stdPoints[1] = intersect.y
|
|
}
|
|
if (almostEqual(stdLine.x2, startPoint.x) && stdAnalyze.isHorizontal) {
|
|
stdPoints[2] = intersect.x
|
|
}
|
|
if (almostEqual(stdLine.y2, startPoint.y) && stdAnalyze.isVertical) {
|
|
stdPoints[3] = intersect.y
|
|
}
|
|
}
|
|
}
|
|
|
|
const stdMinX = Math.min(stdPoints[0], stdPoints[2])
|
|
const stdMaxX = Math.max(stdPoints[0], stdPoints[2])
|
|
const stdMinY = Math.min(stdPoints[1], stdPoints[3])
|
|
const stdMaxY = Math.max(stdPoints[1], stdPoints[3])
|
|
|
|
const rMinX = Math.min(ridgePoint[0], ridgePoint[2])
|
|
const rMaxX = Math.max(ridgePoint[0], ridgePoint[2])
|
|
const rMinY = Math.min(ridgePoint[1], ridgePoint[3])
|
|
const rMaxY = Math.max(ridgePoint[1], ridgePoint[3])
|
|
|
|
const overlapMinX = Math.max(stdMinX, rMinX)
|
|
const overlapMaxX = Math.min(stdMaxX, rMaxX)
|
|
const overlapMinY = Math.max(stdMinY, rMinY)
|
|
const overlapMaxY = Math.min(stdMaxY, rMaxY)
|
|
|
|
if (stdAnalyze.isHorizontal) {
|
|
if (overlapMinX > overlapMaxX) {
|
|
return
|
|
}
|
|
const dist1 = Math.abs(ridgePoint[0] - overlapMinX)
|
|
const dist2 = Math.abs(ridgePoint[0] - overlapMaxX)
|
|
if (dist1 < dist2) {
|
|
ridgePoint = [overlapMinX, ridgePoint[1], overlapMaxX, ridgePoint[3]]
|
|
} else {
|
|
ridgePoint = [overlapMaxX, ridgePoint[1], overlapMinX, ridgePoint[3]]
|
|
}
|
|
|
|
const ridgeVector = Math.sign(clamp01(ridgePoint[0] - ridgePoint[2]))
|
|
if (stdVector.x !== ridgeVector) {
|
|
ridgePoint = [ridgePoint[2], ridgePoint[3], ridgePoint[0], ridgePoint[1]]
|
|
}
|
|
}
|
|
if (stdAnalyze.isVertical) {
|
|
if (overlapMinY > overlapMaxY) {
|
|
return
|
|
}
|
|
const dist1 = Math.abs(ridgePoint[1] - overlapMinY)
|
|
const dist2 = Math.abs(ridgePoint[1] - overlapMaxY)
|
|
if (dist1 < dist2) {
|
|
ridgePoint = [ridgePoint[0], overlapMinY, ridgePoint[2], overlapMaxY]
|
|
} else {
|
|
ridgePoint = [ridgePoint[0], overlapMaxY, ridgePoint[2], overlapMinY]
|
|
}
|
|
const ridgeVector = Math.sign(clamp01(ridgePoint[1] - ridgePoint[3]))
|
|
if (stdVector.y !== ridgeVector) {
|
|
ridgePoint = [ridgePoint[2], ridgePoint[3], ridgePoint[0], ridgePoint[1]]
|
|
}
|
|
}
|
|
|
|
ridgePoints.push({ point: ridgePoint, left: stdLine, right: oppLine })
|
|
canvas
|
|
.getObjects()
|
|
.filter((obj) => obj.name === 'check')
|
|
.forEach((obj) => canvas.remove(obj))
|
|
canvas.renderAll()
|
|
})
|
|
|
|
ridgePoints.forEach((r) => {
|
|
let point = r.point
|
|
const inPolygon1 =
|
|
roof.inPolygon({ x: point[0], y: point[1] }) ||
|
|
roof.lines.find((line) => isPointOnLineNew(line, { x: point[0], y: point[1] })) !== undefined
|
|
const inPolygon2 =
|
|
roof.inPolygon({ x: point[2], y: point[3] }) ||
|
|
roof.lines.find((line) => isPointOnLineNew(line, { x: point[2], y: point[3] })) !== undefined
|
|
|
|
//시작점이 지붕 밖에 있을때 vector내의 가까운 지붕선으로 변경
|
|
if (!inPolygon1) {
|
|
const checkVector = { x: Math.sign(point[0] - point[2]), y: Math.sign(point[1] - point[3]) }
|
|
const checkEdge = { vertex1: { x: point[0], y: point[1] }, vertex2: { x: point[2], y: point[3] } }
|
|
let minDistance = Infinity
|
|
let correctPoint
|
|
roof.lines.forEach((line) => {
|
|
const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
|
|
const intersect = edgesIntersection(lineEdge, checkEdge)
|
|
if (intersect) {
|
|
const distance = Math.sqrt(Math.pow(intersect.x - point[0], 2) + Math.pow(intersect.y - point[1], 2))
|
|
const intersectVector = { x: Math.sign(point[0] - intersect.x), y: Math.sign(point[1] - intersect.y) }
|
|
if (distance < minDistance && intersectVector.x === checkVector.x && intersectVector.y === checkVector.y) {
|
|
minDistance = distance
|
|
correctPoint = intersect
|
|
}
|
|
}
|
|
})
|
|
if (correctPoint) {
|
|
point = [correctPoint.x, correctPoint.y, point[2], point[3]]
|
|
}
|
|
}
|
|
|
|
//종료점이 지붕밖에 있을때 vector내의 가까운 지붕선으로 변경
|
|
if (!inPolygon2) {
|
|
const checkVector = { x: Math.sign(point[2] - point[0]), y: Math.sign(point[3] - point[1]) }
|
|
const checkEdge = { vertex1: { x: point[2], y: point[3] }, vertex2: { x: point[0], y: point[1] } }
|
|
let minDistance = Infinity
|
|
let correctPoint
|
|
roof.lines.forEach((line) => {
|
|
const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
|
|
const intersect = edgesIntersection(lineEdge, checkEdge)
|
|
if (intersect) {
|
|
const distance = Math.sqrt(Math.pow(intersect.x - point[2], 2) + Math.pow(intersect.y - point[3], 2))
|
|
const intersectVector = { x: Math.sign(point[2] - intersect.x), y: Math.sign(point[3] - intersect.y) }
|
|
if (distance < minDistance && intersectVector.x === checkVector.x && intersectVector.y === checkVector.y) {
|
|
minDistance = distance
|
|
correctPoint = intersect
|
|
}
|
|
}
|
|
})
|
|
if (correctPoint) {
|
|
point = [point[0], point[1], correctPoint.x, correctPoint.y]
|
|
}
|
|
}
|
|
|
|
const ridgeLength = Math.sqrt(Math.pow(point[2] - point[0], 2) + Math.pow(point[3] - point[1], 2))
|
|
|
|
if (ridgeLength > EPSILON) {
|
|
//마루 포인트 중 1개만 지붕선에 붙어있을경우 해당 포인트를 시작점으로 한다.
|
|
const isPointOnRoof1 = roof.lines.find((line) => isPointOnLineNew(line, { x: point[0], y: point[1] })) !== undefined
|
|
const isPointOnRoof2 = roof.lines.find((line) => isPointOnLineNew(line, { x: point[2], y: point[3] })) !== undefined
|
|
|
|
let startPoint, endPoint
|
|
if ((isPointOnRoof1 && isPointOnRoof2) || (!isPointOnRoof1 && !isPointOnRoof2)) {
|
|
startPoint = { x: point[0], y: point[1] }
|
|
endPoint = { x: point[2], y: point[3] }
|
|
} else {
|
|
if (isPointOnRoof1) {
|
|
startPoint = { x: point[0], y: point[1] }
|
|
endPoint = { x: point[2], y: point[3] }
|
|
}
|
|
if (isPointOnRoof2) {
|
|
startPoint = { x: point[2], y: point[3] }
|
|
endPoint = { x: point[0], y: point[1] }
|
|
}
|
|
}
|
|
linesAnalysis.push({
|
|
start: startPoint,
|
|
end: endPoint,
|
|
left: baseLines.findIndex((line) => line === r.left),
|
|
right: baseLines.findIndex((line) => line === r.right),
|
|
type: TYPES.RIDGE,
|
|
degree: 0,
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
//반절마루 생성불가이므로 지붕선 분기를 해야하는지 확인 후 처리.
|
|
if (prevLineVector.x === nextLineVector.x && prevLineVector.y === nextLineVector.y) {
|
|
const analyze = analyzeLine(currentLine)
|
|
const roofLine = analyze.roofLine
|
|
|
|
const checkVector = { x: Math.sign(prevLine.y2 - prevLine.y1), y: Math.sign(prevLine.x1 - prevLine.x2) }
|
|
const checkEdge = { vertex1: { x: roofLine.x1, y: roofLine.y1 }, vertex2: { x: roofLine.x2, y: roofLine.y2 } }
|
|
let maxDistance = 0
|
|
let correctPoint
|
|
roof.lines
|
|
.filter((line) => {
|
|
const roofIndex = roof.lines.indexOf(roofLine)
|
|
const prevRoofLine = roof.lines[(roofIndex - 1 + roof.lines.length) % roof.lines.length]
|
|
const nextRoofLine = roof.lines[(roofIndex + 1) % roof.lines.length]
|
|
return line !== roofLine && line !== prevRoofLine && line !== nextRoofLine
|
|
})
|
|
.forEach((line) => {
|
|
const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
|
|
const intersect = edgesIntersection(lineEdge, checkEdge)
|
|
|
|
if (intersect && isPointOnLineNew(line, intersect)) {
|
|
intersect.x = almostEqual(intersect.x, roofLine.x1) ? roofLine.x1 : intersect.x
|
|
intersect.y = almostEqual(intersect.y, roofLine.y1) ? roofLine.y1 : intersect.y
|
|
const distance = Math.sqrt(Math.pow(intersect.x - roofLine.x1, 2) + Math.pow(intersect.y - roofLine.y1, 2))
|
|
const vector = { x: Math.sign(intersect.x - roofLine.x1), y: Math.sign(intersect.y - roofLine.y1) }
|
|
if (distance > maxDistance && vector.x === checkVector.x && vector.y === checkVector.y) {
|
|
maxDistance = distance
|
|
correctPoint = intersect
|
|
}
|
|
}
|
|
})
|
|
if (correctPoint) {
|
|
const startPoint =
|
|
Math.sqrt(Math.pow(correctPoint.x - roofLine.x1, 2) + Math.pow(correctPoint.y - roofLine.y1, 2)) >
|
|
Math.sqrt(Math.pow(correctPoint.x - roofLine.x2, 2) + Math.pow(correctPoint.y - roofLine.y2, 2))
|
|
? { x: roofLine.x1, y: roofLine.y1 }
|
|
: { x: roofLine.x2, y: roofLine.y2 }
|
|
linesAnalysis.push({
|
|
start: startPoint,
|
|
end: { x: correctPoint.x, y: correctPoint.y },
|
|
left: baseLines.findIndex((line) => line === prevLine),
|
|
right: baseLines.findIndex((line) => line === nextLine),
|
|
type: TYPES.GABLE_LINE,
|
|
degree: getDegreeByChon(prevLine.attributes.pitch),
|
|
gableId: baseLines.findIndex((line) => line === currentLine),
|
|
connectCnt: 0,
|
|
})
|
|
}
|
|
}
|
|
|
|
canvas
|
|
.getObjects()
|
|
.filter((obj) => obj.name === 'check')
|
|
.forEach((obj) => canvas.remove(obj))
|
|
canvas.renderAll()
|
|
})
|
|
|
|
//각 선분의 교차점을 찾아 선을 그린다.
|
|
const MAX_ITERATIONS = 1000 //무한루프 방지
|
|
let iterations = 0
|
|
|
|
while (linesAnalysis.length > 0 && iterations < MAX_ITERATIONS) {
|
|
iterations++
|
|
|
|
const intersections = []
|
|
linesAnalysis.forEach((currLine, i) => {
|
|
let minDistance = Infinity
|
|
let intersectPoint = null
|
|
let linePoint = null
|
|
let partner = null
|
|
|
|
const cLength = Math.sqrt(Math.pow(currLine.end.x - currLine.start.x, 2) + Math.pow(currLine.end.y - currLine.start.y, 2))
|
|
//남은 길이가 0이면 무시
|
|
if (cLength < EPSILON) return
|
|
// 하단레벨 케라바 라인인데 연결점이 2개 이상이면 무시
|
|
if (currLine.type === TYPES.GABLE_LINE && currLine.connectCnt > 1) return
|
|
|
|
linesAnalysis.forEach((nextLine, j) => {
|
|
if (i === j) return
|
|
if (currLine.type === TYPES.GABLE_LINE && nextLine.type === TYPES.GABLE_LINE && currLine.gableId === nextLine.gableId) return
|
|
if (nextLine.type === TYPES.GABLE_LINE && nextLine.connectCnt > 1) return
|
|
|
|
const intersect = lineIntersection(currLine.start, currLine.end, nextLine.start, nextLine.end, canvas)
|
|
if (intersect) {
|
|
let distance1 = Math.sqrt(Math.pow(intersect.x - currLine.start.x, 2) + Math.pow(intersect.y - currLine.start.y, 2))
|
|
let distance2 = Math.sqrt(Math.pow(intersect.x - nextLine.start.x, 2) + Math.pow(intersect.y - nextLine.start.y, 2))
|
|
let point = [currLine.start.x, currLine.start.y, intersect.x, intersect.y]
|
|
if (distance1 < EPSILON) {
|
|
distance1 = Math.sqrt(Math.pow(intersect.x - currLine.end.x, 2) + Math.pow(intersect.y - currLine.end.y, 2))
|
|
distance2 = Math.sqrt(Math.pow(intersect.x - nextLine.end.x, 2) + Math.pow(intersect.y - nextLine.end.y, 2))
|
|
point = [currLine.end.x, currLine.end.y, intersect.x, intersect.y]
|
|
}
|
|
// 하단레벨 케라바 라인인데 남은 길이가 없는 경우 무시
|
|
if (currLine.type === TYPES.GABLE_LINE && distance1 < EPSILON) return
|
|
//교점까지의 길이가 최소 길이보다 작을 경우, 최소 길이와 교점을 교체
|
|
if (distance1 < minDistance && !almostEqual(distance1, minDistance) && !(distance1 < EPSILON && distance2 < EPSILON)) {
|
|
minDistance = distance1
|
|
intersectPoint = intersect
|
|
linePoint = point
|
|
partner = j
|
|
} else if (almostEqual(distance1, minDistance)) {
|
|
const pLine = linesAnalysis[partner]
|
|
const isSameLine =
|
|
currLine.left === pLine.left || currLine.left === pLine.right || currLine.right === pLine.left || currLine.right === pLine.right
|
|
if (!isSameLine) {
|
|
partner = j
|
|
}
|
|
}
|
|
}
|
|
})
|
|
if (intersectPoint) {
|
|
intersections.push({ index: i, intersect: intersectPoint, linePoint, partner })
|
|
}
|
|
})
|
|
const intersectPoints = intersections
|
|
.map((item) => item.intersect)
|
|
.filter((point, index, self) => self.findIndex((p) => almostEqual(p.x, point.x) && almostEqual(p.y, point.y)) === index)
|
|
|
|
if (intersectPoints.length === 1 && intersections.length > 1) {
|
|
intersections[0].partner = intersections[1].index
|
|
intersections[1].partner = intersections[0].index
|
|
}
|
|
|
|
let newAnalysis = [] //신규 발생 선
|
|
const processed = new Set() //처리된 선
|
|
|
|
for (const { index, intersect, linePoint, partner } of intersections) {
|
|
const cLine = linesAnalysis[index]
|
|
const pLine = linesAnalysis[partner]
|
|
|
|
//교점이 없거나, 이미 처리된 선분이면 처리하지 않는다.
|
|
if (!intersect || processed.has(index)) continue
|
|
|
|
//교점이 없거나, 교점 선분이 없으면 처리하지 않는다.
|
|
const pIntersection = intersections.find((p) => p.index === partner)
|
|
if (!pIntersection || !pIntersection.intersect) continue
|
|
|
|
//상호 최단 교점이 아닐경우 처리하지 않는다.
|
|
if (pIntersection.partner !== index) continue
|
|
|
|
//공통선분이 없으면 처리하지 않는다.
|
|
const isSameLine = cLine.left === pLine.left || cLine.left === pLine.right || cLine.right === pLine.left || cLine.right === pLine.right
|
|
if (!isSameLine) continue
|
|
|
|
//처리 된 라인으로 설정
|
|
processed.add(index).add(partner)
|
|
|
|
let point1 = linePoint
|
|
let point2 = pIntersection.linePoint
|
|
let length1 = Math.sqrt(Math.pow(point1[2] - point1[0], 2) + Math.pow(point1[3] - point1[1], 2))
|
|
let length2 = Math.sqrt(Math.pow(point2[2] - point2[0], 2) + Math.pow(point2[3] - point2[1], 2))
|
|
|
|
//gable라인과 붙는경우 length가 0에 가까우면 포인트를 뒤집는다.
|
|
if (cLine.type === TYPES.GABLE_LINE && length2 < EPSILON) {
|
|
point2 = [pLine.end.x, pLine.end.y, intersect.x, intersect.y]
|
|
length2 = Math.sqrt((point2[2] - point2[0]) ** 2 + (point2[3] - point2[1]) ** 2)
|
|
}
|
|
if (pLine.type === TYPES.GABLE_LINE && length1 < EPSILON) {
|
|
point1 = [cLine.end.x, cLine.end.y, intersect.x, intersect.y]
|
|
length1 = Math.sqrt((point1[2] - point1[0]) ** 2 + (point1[3] - point1[1]) ** 2)
|
|
}
|
|
|
|
if (length1 > 0 && !alreadyPoints(innerLines, point1)) {
|
|
if (cLine.type === TYPES.HIP) {
|
|
innerLines.push(drawHipLine(point1, canvas, roof, textMode, cLine.degree, cLine.degree))
|
|
} else if (cLine.type === TYPES.RIDGE) {
|
|
innerLines.push(drawRidgeLine(point1, canvas, roof, textMode))
|
|
} else if (cLine.type === TYPES.NEW) {
|
|
const isDiagonal = Math.abs(point1[0] - point1[2]) >= 1 && Math.abs(point1[1] - point1[3]) >= 1
|
|
if (isDiagonal) {
|
|
innerLines.push(drawHipLine(point1, canvas, roof, textMode, cLine.degree, cLine.degree))
|
|
} else {
|
|
innerLines.push(drawRidgeLine(point1, canvas, roof, textMode))
|
|
}
|
|
} else if (cLine.type === TYPES.GABLE_LINE) {
|
|
if (cLine.degree > 0) {
|
|
innerLines.push(drawHipLine(point1, canvas, roof, textMode, cLine.degree, cLine.degree))
|
|
} else {
|
|
innerLines.push(drawRidgeLine(point1, canvas, roof, textMode))
|
|
}
|
|
}
|
|
}
|
|
|
|
if (length2 > 0 && !alreadyPoints(innerLines, point2)) {
|
|
if (pLine.type === TYPES.HIP) {
|
|
innerLines.push(drawHipLine(point2, canvas, roof, textMode, pLine.degree, pLine.degree))
|
|
} else if (pLine.type === TYPES.RIDGE) {
|
|
innerLines.push(drawRidgeLine(point2, canvas, roof, textMode))
|
|
} else if (pLine.type === TYPES.NEW) {
|
|
const isDiagonal = Math.abs(point2[0] - point2[2]) >= 1 && Math.abs(point2[1] - point2[3]) >= 1
|
|
if (isDiagonal) {
|
|
innerLines.push(drawHipLine(point2, canvas, roof, textMode, pLine.degree, pLine.degree))
|
|
} else {
|
|
innerLines.push(drawRidgeLine(point2, canvas, roof, textMode))
|
|
}
|
|
} else if (pLine.type === TYPES.GABLE_LINE) {
|
|
if (pLine.degree > 0) {
|
|
innerLines.push(drawHipLine(point2, canvas, roof, textMode, pLine.degree, pLine.degree))
|
|
} else {
|
|
innerLines.push(drawRidgeLine(point2, canvas, roof, textMode))
|
|
}
|
|
}
|
|
}
|
|
const otherIs = intersections
|
|
.filter((is) => is.index !== index && is.index !== partner)
|
|
.filter((is) => almostEqual(is.intersect.x, intersect.x) && almostEqual(is.intersect.y, intersect.y))
|
|
|
|
let relationBaseLines = [cLine.left, cLine.right, pLine.left, pLine.right]
|
|
if (otherIs.length > 0 && cLine.type !== TYPES.GABLE_LINE && pLine.type !== TYPES.GABLE_LINE) {
|
|
for (let i = 0; i < otherIs.length; i++) {
|
|
const oLine = linesAnalysis[otherIs[i].index]
|
|
if (oLine.type === TYPES.RIDGE && linesAnalysis.length > 3) continue
|
|
const isSameLine = relationBaseLines.includes(oLine.left) || relationBaseLines.includes(oLine.right)
|
|
if (isSameLine) {
|
|
let oPoint = otherIs[i].linePoint
|
|
let oLength = Math.sqrt(Math.pow(oPoint[2] - oPoint[0], 2) + Math.pow(oPoint[3] - oPoint[1], 2))
|
|
if (oLength > 0 && !alreadyPoints(innerLines, oPoint)) {
|
|
if (oLine.type === TYPES.HIP) {
|
|
innerLines.push(drawHipLine(oPoint, canvas, roof, textMode, null, oLine.degree, oLine.degree))
|
|
} else if (oLine.type === TYPES.RIDGE) {
|
|
innerLines.push(drawRidgeLine(oPoint, canvas, roof, textMode))
|
|
} else if (oLine.type === TYPES.NEW) {
|
|
const isDiagonal = Math.abs(oPoint[0] - oPoint[2]) >= 1 && Math.abs(oPoint[1] - oPoint[3]) >= 1
|
|
if (isDiagonal) {
|
|
innerLines.push(drawHipLine(oPoint, canvas, roof, textMode, oLine.degree, oLine.degree))
|
|
} else {
|
|
innerLines.push(drawRidgeLine(oPoint, canvas, roof, textMode))
|
|
}
|
|
} else if (oLine.type === TYPES.GABLE_LINE) {
|
|
if (oLine.degree > 0) {
|
|
innerLines.push(drawHipLine(oPoint, canvas, roof, textMode, oLine.degree, oLine.degree))
|
|
} else {
|
|
innerLines.push(drawRidgeLine(oPoint, canvas, roof, textMode))
|
|
}
|
|
}
|
|
}
|
|
processed.add(otherIs[i].index)
|
|
relationBaseLines.push(oLine.left, oLine.right)
|
|
}
|
|
}
|
|
}
|
|
if (cLine.type === TYPES.GABLE_LINE || pLine.type === TYPES.GABLE_LINE) {
|
|
relationBaseLines = [cLine.left, cLine.right, pLine.left, pLine.right]
|
|
const gableLine = cLine.type === TYPES.GABLE_LINE ? cLine : pLine
|
|
gableLine.connectCnt++
|
|
|
|
const uniqueBaseLines = [...new Set(relationBaseLines)]
|
|
// 연결점에서 새로운 가선분을 생성
|
|
if (uniqueBaseLines.length > 2) {
|
|
const linesCounts = new Map()
|
|
relationBaseLines.forEach((e) => {
|
|
linesCounts.set(e, (linesCounts.get(e) || 0) + 1)
|
|
})
|
|
|
|
const uniqueLines = Array.from(linesCounts.entries())
|
|
.filter(([_, count]) => count === 1)
|
|
.map(([line, _]) => line)
|
|
|
|
if (uniqueLines.length === 2) {
|
|
if (gableLine.connectCnt < 2) {
|
|
newAnalysis.push({
|
|
start: { x: intersect.x, y: intersect.y },
|
|
end: { x: gableLine.end.x, y: gableLine.end.y },
|
|
left: gableLine.left,
|
|
right: gableLine.right,
|
|
type: TYPES.GABLE_LINE,
|
|
degree: getDegreeByChon(baseLines[gableLine.right].attributes.pitch),
|
|
gableId: gableLine.gableId,
|
|
connectCnt: gableLine.connectCnt,
|
|
})
|
|
}
|
|
if (gableLine.connectCnt >= 2) {
|
|
//가선분 발생만 시키는 더미 생성.
|
|
newAnalysis.push({
|
|
start: { x: intersect.x, y: intersect.y },
|
|
end: { x: intersect.x, y: intersect.y },
|
|
left: gableLine.gableId,
|
|
right: gableLine.gableId,
|
|
type: TYPES.HIP,
|
|
degree: 0,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
const uniqueBaseLines = [...new Set(relationBaseLines)]
|
|
// 연결점에서 새로운 가선분을 생성
|
|
if (uniqueBaseLines.length > 2) {
|
|
const linesCounts = new Map()
|
|
relationBaseLines.forEach((e) => {
|
|
linesCounts.set(e, (linesCounts.get(e) || 0) + 1)
|
|
})
|
|
|
|
const uniqueLines = Array.from(linesCounts.entries())
|
|
.filter(([_, count]) => count === 1)
|
|
.map(([line, _]) => line)
|
|
|
|
if (uniqueLines.length === 2) {
|
|
// 두 변의 이등분선 방향 계산
|
|
// uniqueLines.sort((a, b) => a - b)
|
|
const baseLine1 = baseLines[uniqueLines[0]]
|
|
const baseLine2 = baseLines[uniqueLines[1]]
|
|
|
|
let bisector
|
|
if (isParallel(baseLine1, baseLine2)) {
|
|
let cPoint = [cLine.start.x, cLine.start.y, cLine.end.x, cLine.end.y]
|
|
let pPoint = [pLine.start.x, pLine.start.y, pLine.end.x, pLine.end.y]
|
|
const length1 = Math.sqrt(Math.pow(intersect.x - cPoint[2], 2) + Math.pow(intersect.y - cPoint[3], 2))
|
|
const length2 = Math.sqrt(Math.pow(intersect.x - pPoint[2], 2) + Math.pow(intersect.y - pPoint[3], 2))
|
|
|
|
//교점 밖으로 뻗어나가는 선의 길이가 다를 경우 이등분선 계산이 틀어져서 조정
|
|
if (!almostEqual(length1, length2)) {
|
|
const vector1 = { x: Math.sign(clamp01(cPoint[2] - cPoint[0])), y: Math.sign(clamp01(cPoint[3] - cPoint[1])) }
|
|
const vector2 = { x: Math.sign(clamp01(pPoint[2] - pPoint[0])), y: Math.sign(clamp01(pPoint[3] - pPoint[1])) }
|
|
const addLength = 100
|
|
cPoint[2] = intersect.x + vector1.x * addLength
|
|
cPoint[3] = intersect.y + vector1.y * addLength
|
|
pPoint[2] = intersect.x + vector2.x * addLength
|
|
pPoint[3] = intersect.y + vector2.y * addLength
|
|
}
|
|
|
|
bisector = getBisectLines(
|
|
{ x1: cPoint[0], y1: cPoint[1], x2: cPoint[2], y2: cPoint[3] },
|
|
{ x1: pPoint[0], y1: pPoint[1], x2: pPoint[2], y2: pPoint[3] },
|
|
)
|
|
} else {
|
|
bisector = getBisectBaseLines(baseLine1, baseLine2, intersect, canvas)
|
|
}
|
|
|
|
//마주하는 지붕선을 찾는다.
|
|
const intersectionsByRoof = []
|
|
const checkEdge = {
|
|
vertex1: { x: intersect.x, y: intersect.y },
|
|
vertex2: { x: intersect.x + bisector.x, y: intersect.y + bisector.y },
|
|
}
|
|
const checkVector = {
|
|
x: Math.sign(checkEdge.vertex1.x - checkEdge.vertex2.x),
|
|
y: Math.sign(checkEdge.vertex1.y - checkEdge.vertex2.y),
|
|
}
|
|
roof.lines.forEach((line) => {
|
|
const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
|
|
const is = edgesIntersection(lineEdge, checkEdge)
|
|
if (is && isPointOnLineNew(line, is)) {
|
|
const distance = Math.sqrt((is.x - intersect.x) ** 2 + (is.y - intersect.y) ** 2)
|
|
const isVector = { x: Math.sign(intersect.x - is.x), y: Math.sign(intersect.y - is.y) }
|
|
if (isVector.x === checkVector.x && isVector.y === checkVector.y) {
|
|
intersectionsByRoof.push({ is, distance })
|
|
}
|
|
}
|
|
})
|
|
intersectionsByRoof.sort((a, b) => a.distance - b.distance)
|
|
|
|
//기 존재하는 analyze 라인이 있으면 newAnalyzeLine을 생성하지 않고 교체한다.
|
|
const otherIs = intersections.filter(
|
|
(is) => !processed.has(is.index) && almostEqual(is.intersect.x, intersect.x) && almostEqual(is.intersect.y, intersect.y),
|
|
)
|
|
|
|
if (otherIs.length > 0) {
|
|
const analyze = linesAnalysis[otherIs[0].index]
|
|
processed.add(otherIs[0].index)
|
|
newAnalysis.push({
|
|
start: { x: intersect.x, y: intersect.y },
|
|
end: { x: analyze.end.x, y: analyze.end.y },
|
|
left: analyze.left,
|
|
right: analyze.right,
|
|
type: analyze.type,
|
|
degree: analyze.degree,
|
|
gableId: analyze.gableId,
|
|
connectCnt: analyze.connectCnt,
|
|
})
|
|
} else if (intersectionsByRoof.length > 0) {
|
|
//지붕선과 교점이 발생할때
|
|
let is = intersectionsByRoof[0].is
|
|
let linePoint = [intersect.x, intersect.y, is.x, is.y]
|
|
const isDiagonal = Math.abs(is.x - intersect.x) >= 1 && Math.abs(is.y - intersect.y) >= 1
|
|
const length = Math.sqrt((linePoint[2] - linePoint[0]) ** 2 + (linePoint[3] - linePoint[1]) ** 2)
|
|
|
|
const line1 = baseLines[uniqueLines[0]]
|
|
const line2 = baseLines[uniqueLines[1]]
|
|
const vector1 = { x: Math.sign(line1.x1 - line1.x2), y: Math.sign(line1.y1 - line1.y2) }
|
|
|
|
const prevLine = checkVector.x === vector1.x && checkVector.y === vector1.y ? line2 : line1
|
|
const nextLine = checkVector.x === vector1.x && checkVector.y === vector1.y ? line1 : line2
|
|
|
|
if (!isDiagonal) {
|
|
const drivePoint = getRidgeDrivePoint(linePoint, prevLine, nextLine, baseLines)
|
|
if (drivePoint !== null) {
|
|
const driveLength = Math.sqrt((drivePoint.x - intersect.x) ** 2 + (drivePoint.y - intersect.y) ** 2)
|
|
if (driveLength < length) {
|
|
linePoint = [intersect.x, intersect.y, drivePoint.x, drivePoint.y]
|
|
}
|
|
}
|
|
}
|
|
|
|
newAnalysis.push({
|
|
start: { x: linePoint[0], y: linePoint[1] },
|
|
end: { x: linePoint[2], y: linePoint[3] },
|
|
left: uniqueLines[0],
|
|
right: uniqueLines[1],
|
|
type: TYPES.NEW,
|
|
degree: isDiagonal ? cLine.degree : 0,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
canvas
|
|
.getObjects()
|
|
.filter((object) => object.name === 'check' || object.name === 'checkAnalysis')
|
|
.forEach((object) => canvas.remove(object))
|
|
canvas.renderAll()
|
|
|
|
if (newAnalysis.length > 0) break
|
|
}
|
|
|
|
// 처리된 가선분 제외
|
|
linesAnalysis = newAnalysis.concat(linesAnalysis.filter((_, index) => !processed.has(index)))
|
|
|
|
canvas
|
|
.getObjects()
|
|
.filter((object) => object.name === 'check' || object.name === 'checkAnalysis')
|
|
.forEach((object) => canvas.remove(object))
|
|
canvas.renderAll()
|
|
if (newAnalysis.length === 0) break
|
|
}
|
|
|
|
// 가선분 중 처리가 안되어있는 붙어있는 라인에 대한 예외처리.
|
|
const proceedAnalysis = []
|
|
linesAnalysis
|
|
.filter((line) => {
|
|
const dx = line.end.x - line.start.x
|
|
const dy = line.end.y - line.start.y
|
|
const length = Math.sqrt(dx ** 2 + dy ** 2)
|
|
return length > 0
|
|
})
|
|
.forEach((currentLine) => {
|
|
if (proceedAnalysis.find((p) => p === currentLine)) return
|
|
//현재와 같으면 제외, 이미 처리된 라인은 제외, 현재와 공통선분이 존재하지 않으면 제외
|
|
linesAnalysis
|
|
.filter((line) => {
|
|
const dx = line.end.x - line.start.x
|
|
const dy = line.end.y - line.start.y
|
|
const length = Math.sqrt(dx ** 2 + dy ** 2)
|
|
return length > 0
|
|
})
|
|
.filter(
|
|
(partnerLine) =>
|
|
partnerLine !== currentLine &&
|
|
!proceedAnalysis.find((p) => p === partnerLine) &&
|
|
(currentLine.left === partnerLine.left ||
|
|
currentLine.left === partnerLine.right ||
|
|
partnerLine.left === currentLine.left ||
|
|
partnerLine.left === currentLine.right),
|
|
)
|
|
.forEach((partnerLine) => {
|
|
const dx1 = currentLine.end.x - currentLine.start.x
|
|
const dy1 = currentLine.end.y - currentLine.start.y
|
|
const dx2 = partnerLine.end.x - partnerLine.start.x
|
|
const dy2 = partnerLine.end.y - partnerLine.start.y
|
|
const denominator = dy2 * dx1 - dx2 * dy1
|
|
const isOpposite = dx1 * dx2 + dy1 * dy2 < 0
|
|
//평행하지 않으면 제외
|
|
if (Math.abs(denominator) > EPSILON) return
|
|
|
|
const currentDegree = getDegreeByChon(baseLines[currentLine.left].attributes.pitch)
|
|
if (isOpposite) {
|
|
const points = [currentLine.start.x, currentLine.start.y, partnerLine.start.x, partnerLine.start.y]
|
|
const length = Math.sqrt((points[0] - points[2]) ** 2 + (points[1] - points[3]) ** 2)
|
|
|
|
if (length > 0) {
|
|
if (currentLine.type === TYPES.HIP) {
|
|
innerLines.push(drawHipLine(points, canvas, roof, textMode, currentDegree, currentDegree))
|
|
} else if (currentLine.type === TYPES.RIDGE) {
|
|
innerLines.push(drawRidgeLine(points, canvas, roof, textMode))
|
|
} else if (currentLine.type === TYPES.NEW) {
|
|
const isDiagonal = Math.abs(points[0] - points[2]) >= 1 && Math.abs(points[1] - points[3]) >= 1
|
|
if (isDiagonal && almostEqual(Math.abs(points[0] - points[2]), Math.abs(points[1] - points[3]))) {
|
|
innerLines.push(drawHipLine(points, canvas, roof, textMode, currentDegree, currentDegree))
|
|
}
|
|
if (!isDiagonal) {
|
|
innerLines.push(drawRidgeLine(points, canvas, roof, textMode))
|
|
}
|
|
}
|
|
proceedAnalysis.push(currentLine, partnerLine)
|
|
}
|
|
} else {
|
|
const allPoints = [currentLine.start, currentLine.end, partnerLine.start, partnerLine.end]
|
|
let points = []
|
|
allPoints.forEach((point) => {
|
|
let count = 0
|
|
allPoints.forEach((p) => {
|
|
if (almostEqual(point.x, p.x) && almostEqual(point.y, p.y)) count++
|
|
})
|
|
if (count === 1) points.push(point)
|
|
})
|
|
|
|
if (points.length === 2) {
|
|
const length = Math.sqrt((points[0].x - points[1].x) ** 2 + (points[0].y - points[1].y) ** 2)
|
|
if (length < EPSILON) return
|
|
const isDiagonal = Math.abs(points[0].x - points[1].x) >= 1 && Math.abs(points[0].y - points[1].y) >= 1
|
|
if (isDiagonal) {
|
|
innerLines.push(
|
|
drawHipLine([points[0].x, points[0].y, points[1].x, points[1].y], canvas, roof, textMode, currentDegree, currentDegree),
|
|
)
|
|
} else {
|
|
innerLines.push(drawRidgeLine([points[0].x, points[0].y, points[1].x, points[1].y], canvas, roof, textMode))
|
|
}
|
|
proceedAnalysis.push(currentLine, partnerLine)
|
|
}
|
|
}
|
|
})
|
|
})
|
|
linesAnalysis = linesAnalysis.filter((line) => !proceedAnalysis.find((p) => p === line))
|
|
|
|
// 최종 라인중 벽에서 시작해서 벽에서 끝나는 라인이 남을 경우(벽취함)
|
|
if (linesAnalysis.length > 0) {
|
|
linesAnalysis
|
|
.filter((line) => {
|
|
const dx = line.end.x - line.start.x
|
|
const dy = line.end.y - line.start.y
|
|
const length = Math.sqrt(dx ** 2 + dy ** 2)
|
|
return length > 0
|
|
})
|
|
.forEach((line) => {
|
|
const startOnLine = roof.lines.find((l) => isPointOnLineNew(l, line.start))
|
|
const endOnLine = roof.lines.find((l) => isPointOnLineNew(l, line.end))
|
|
const allLinesPoints = []
|
|
innerLines.forEach((innerLine) => {
|
|
allLinesPoints.push({ x: innerLine.x1, y: innerLine.y1 }, { x: innerLine.x2, y: innerLine.y2 })
|
|
})
|
|
|
|
if (
|
|
allLinesPoints.filter((p) => almostEqual(p.x, line.start.x) && almostEqual(p.y, line.start.y)).length < 3 &&
|
|
allLinesPoints.filter((p) => almostEqual(p.x, line.end.x) && almostEqual(p.y, line.end.y)).length === 0
|
|
) {
|
|
if (line.type === TYPES.RIDGE && baseLines.length === 4 && (startOnLine || endOnLine)) {
|
|
innerLines.push(drawRidgeLine([line.start.x, line.start.y, line.end.x, line.end.y], canvas, roof, textMode))
|
|
} else {
|
|
if (startOnLine && endOnLine) {
|
|
if (line.degree === 0) {
|
|
innerLines.push(drawRoofLine([line.start.x, line.start.y, line.end.x, line.end.y], canvas, roof, textMode))
|
|
} else {
|
|
innerLines.push(drawHipLine([line.start.x, line.start.y, line.end.x, line.end.y], canvas, roof, textMode, line.degree, line.degree))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
//케라바에서 파생된 하단 지붕 라인처리
|
|
const downRoofGable = []
|
|
//처마에서 파생된 하단 지붕 라인 처리
|
|
const downRoofEaves = []
|
|
//roof lines에 조정해야하는 라인 처리
|
|
// const adjustRoofLines = []
|
|
baseLines.forEach((baseLine, index) => {
|
|
const nextLine = baseLines[(index + 1) % baseLines.length]
|
|
const prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length]
|
|
|
|
const prevLineVector = { x: Math.sign(prevLine.x1 - prevLine.x2), y: Math.sign(prevLine.y1 - prevLine.y2) }
|
|
const nextLineVector = { x: Math.sign(nextLine.x1 - nextLine.x2), y: Math.sign(nextLine.y1 - nextLine.y2) }
|
|
|
|
const midX = (baseLine.x1 + baseLine.x2) / 2
|
|
const midY = (baseLine.y1 + baseLine.y2) / 2
|
|
const checkPoint = { x: midX + nextLineVector.x, y: midY + nextLineVector.y }
|
|
//반절마루 생성불가이므로 지붕선 분기를 해야하는지 확인 후 처리.
|
|
if (
|
|
prevLineVector.x === nextLineVector.x &&
|
|
prevLineVector.y === nextLineVector.y &&
|
|
baseLine.attributes.type === LINE_TYPE.WALLLINE.EAVES &&
|
|
(prevLine.attributes.type === LINE_TYPE.WALLLINE.GABLE || nextLine.attributes.type === LINE_TYPE.WALLLINE.GABLE)
|
|
) {
|
|
downRoofGable.push({ currLine: baseLine, currIndex: index, type: 'A' })
|
|
}
|
|
|
|
if (
|
|
(prevLineVector.x !== nextLineVector.x || prevLineVector.y !== nextLineVector.y) &&
|
|
checkWallPolygon.inPolygon(checkPoint) &&
|
|
prevLine.attributes.type === LINE_TYPE.WALLLINE.GABLE &&
|
|
nextLine.attributes.type === LINE_TYPE.WALLLINE.GABLE
|
|
) {
|
|
downRoofGable.push({ currLine: baseLine, currIndex: index, type: 'B' })
|
|
}
|
|
|
|
const originPoint = baseLine.attributes.originPoint
|
|
if (
|
|
prevLineVector.x === nextLineVector.x &&
|
|
prevLineVector.y === nextLineVector.y &&
|
|
baseLine.attributes.type === LINE_TYPE.WALLLINE.EAVES &&
|
|
(prevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES || nextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) &&
|
|
((originPoint.x1 !== baseLine.x1 && originPoint.x2 !== baseLine.x2) || (originPoint.y1 !== baseLine.y1 && originPoint.y2 !== baseLine.y2))
|
|
) {
|
|
downRoofEaves.push({ currLine: baseLine, currIndex: index })
|
|
}
|
|
})
|
|
|
|
const downRoofLines = [] // 하단지붕 파생 라인 처리후 innerLines에 추가.
|
|
//A타입 하단 지붕 prevLine과 nextLine이 같은 방향으로 가는 경우
|
|
downRoofGable
|
|
.filter((l) => l.type === 'A')
|
|
.forEach(({ currLine, currIndex }) => {
|
|
// 라인의 방향
|
|
const currVector = { x: Math.sign(clamp01(currLine.x1 - currLine.x2)), y: Math.sign(clamp01(currLine.y1 - currLine.y2)) }
|
|
|
|
//어느쪽이 기준인지 확인.
|
|
//대각선 제외
|
|
if (currVector.x !== 0 && currVector.y !== 0) return
|
|
|
|
const prevLine = baseLines[(currIndex - 1 + baseLines.length) % baseLines.length]
|
|
const nextLine = baseLines[(currIndex + 1) % baseLines.length]
|
|
|
|
const prevVector = { x: Math.sign(clamp01(prevLine.x1 - prevLine.x2)), y: Math.sign(clamp01(prevLine.y1 - prevLine.y2)) }
|
|
const nextVector = { x: Math.sign(clamp01(nextLine.x1 - nextLine.x2)), y: Math.sign(clamp01(nextLine.y1 - nextLine.y2)) }
|
|
|
|
let gableLine
|
|
//가로선
|
|
if (currVector.y === 0) {
|
|
if (currVector.x === 1) {
|
|
if (prevVector.y === 1 && nextVector.y === 1) {
|
|
gableLine = nextLine
|
|
}
|
|
if (prevVector.y === -1 && nextVector.y === -1) {
|
|
gableLine = prevLine
|
|
}
|
|
}
|
|
if (currVector.x === -1) {
|
|
if (prevVector.y === 1 && nextVector.y === 1) {
|
|
gableLine = prevLine
|
|
}
|
|
if (prevVector.y === -1 && nextVector.y === -1) {
|
|
gableLine = nextLine
|
|
}
|
|
}
|
|
}
|
|
|
|
//세로선
|
|
if (currVector.x === 0) {
|
|
if (currVector.y === 1) {
|
|
if (prevVector.x === 1 && nextVector.x === 1) {
|
|
gableLine = prevLine
|
|
}
|
|
if (prevVector.x === -1 && nextVector.x === -1) {
|
|
gableLine = nextLine
|
|
}
|
|
}
|
|
if (currVector.y === -1) {
|
|
if (prevVector.x === 1 && nextVector.x === 1) {
|
|
gableLine = nextLine
|
|
}
|
|
if (prevVector.x === -1 && nextVector.x === -1) {
|
|
gableLine = prevLine
|
|
}
|
|
}
|
|
}
|
|
|
|
if (gableLine.attributes.type !== LINE_TYPE.WALLLINE.GABLE) return
|
|
|
|
//기준점
|
|
let stdPoint = []
|
|
//반대쪽 라인을 찾기위한 vector
|
|
let oppFindVector
|
|
if (gableLine === prevLine) {
|
|
stdPoint.push(currLine.x1, currLine.y1)
|
|
stdPoint.push(currLine.x2 + -currVector.x * nextLine.attributes.offset, currLine.y2 + -currVector.y * nextLine.attributes.offset)
|
|
oppFindVector = { x: Math.sign(clamp01(nextLine.x2 - nextLine.x1)), y: Math.sign(clamp01(nextLine.y2 - nextLine.y1)) }
|
|
} else {
|
|
stdPoint.push(currLine.x2, currLine.y2)
|
|
stdPoint.push(currLine.x1 + currVector.x * prevLine.attributes.offset, currLine.y1 + currVector.y * prevLine.attributes.offset)
|
|
oppFindVector = { x: Math.sign(clamp01(prevLine.x1 - prevLine.x2)), y: Math.sign(clamp01(prevLine.y1 - prevLine.y2)) }
|
|
}
|
|
|
|
//반대쪽 라인들 (마루선을 위함)
|
|
const oppLines = baseLines.filter((line) => {
|
|
const lineVector = { x: Math.sign(clamp01(line.x1 - line.x2)), y: Math.sign(clamp01(line.y1 - line.y2)) }
|
|
const oppVector =
|
|
lineVector.x === 0 ? { x: Math.sign(clamp01(line.x1 - stdPoint[0])), y: 0 } : { x: 0, y: Math.sign(clamp01(line.y1 - stdPoint[1])) }
|
|
|
|
const rightDirection =
|
|
(currVector.x === lineVector.x && currVector.y !== lineVector.y) || (currVector.x !== lineVector.x && currVector.y === lineVector.y)
|
|
const rightOpp = oppFindVector.x === oppVector.x && oppFindVector.y === oppVector.y
|
|
return rightDirection && rightOpp
|
|
})
|
|
|
|
const innerRidge = innerLines
|
|
.filter((line) => line.name === LINE_TYPE.SUBLINE.RIDGE)
|
|
.filter((line) => {
|
|
const lineVector = { x: Math.sign(clamp01(line.x1 - line.x2)), y: Math.sign(clamp01(line.y1 - line.y2)) }
|
|
//마루선을 찾는다.
|
|
if ((currVector.x === 0 && lineVector.x === 0) || (currVector.y === 0 && lineVector.y === 0)) {
|
|
//세로선
|
|
if (lineVector.x === 0) {
|
|
const minY = Math.min(line.y1, line.y2)
|
|
const maxY = Math.max(line.y1, line.y2)
|
|
// 기준 라인 안에 들어있는 경우에만 처리.
|
|
if (
|
|
Math.min(stdPoint[1], stdPoint[3]) <= minY &&
|
|
maxY <= Math.max(stdPoint[1], stdPoint[3]) &&
|
|
Math.min(stdPoint[0], ...oppLines.map((line) => line.x1)) <= line.x1 &&
|
|
line.x1 <= Math.max(stdPoint[0], ...oppLines.map((line) => line.x1))
|
|
) {
|
|
return true
|
|
}
|
|
}
|
|
//가로선
|
|
if (lineVector.y === 0) {
|
|
const minX = Math.min(line.x1, line.x2)
|
|
const maxX = Math.max(line.x1, line.x2)
|
|
// 기준 라인 안에 들어있는 경우에만 처리
|
|
if (
|
|
Math.min(stdPoint[0], stdPoint[2]) <= minX &&
|
|
maxX <= Math.max(stdPoint[0], stdPoint[2]) &&
|
|
Math.min(stdPoint[1], ...oppLines.map((line) => line.y1)) <= line.y1 &&
|
|
line.y1 <= Math.max(stdPoint[1], ...oppLines.map((line) => line.y1))
|
|
) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
//1. 현재 라인을 기준으로 지붕선 추가.
|
|
//1-1 stdPoint을 현재라인의 지붕 출폭 만큼 조정
|
|
const currOffset = currLine.attributes.offset
|
|
|
|
let roofLinePoint = stdPoint
|
|
if (currVector.x === 0) {
|
|
//세로일때
|
|
//x축 조정
|
|
roofLinePoint[0] = roofLinePoint[0] + currVector.y * currOffset
|
|
roofLinePoint[2] = roofLinePoint[2] + currVector.y * currOffset
|
|
} else if (currVector.y === 0) {
|
|
//가로일때
|
|
//y축 조정
|
|
roofLinePoint[1] = roofLinePoint[1] - currVector.x * currOffset
|
|
roofLinePoint[3] = roofLinePoint[3] - currVector.x * currOffset
|
|
}
|
|
|
|
//지붕선추가.
|
|
downRoofLines.push(drawRoofLine(roofLinePoint, canvas, roof, textMode))
|
|
|
|
//1-2 지붕선에서 oppLine으로 향하는 중 가장 가까운 마루선까지의 연결선 생성
|
|
const findRidgeEdge = {
|
|
vertex1: { x: roofLinePoint[0], y: roofLinePoint[1] },
|
|
vertex2: { x: roofLinePoint[0] + oppFindVector.x, y: roofLinePoint[1] + oppFindVector.y },
|
|
}
|
|
let minDistance = Infinity
|
|
let minPoint
|
|
let isLine
|
|
innerRidge.forEach((line) => {
|
|
const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
|
|
const intersect = edgesIntersection(lineEdge, findRidgeEdge)
|
|
if (intersect) {
|
|
let distance = Infinity
|
|
if (currVector.x === 0) {
|
|
const lineDistance1 = Math.abs(line.y1 - roofLinePoint[1])
|
|
const lineDistance2 = Math.abs(line.y2 - roofLinePoint[1])
|
|
distance = Math.min(lineDistance1, lineDistance2)
|
|
} else if (currVector.y === 0) {
|
|
const lineDistance1 = Math.abs(line.x1 - roofLinePoint[0])
|
|
const lineDistance2 = Math.abs(line.x2 - roofLinePoint[0])
|
|
distance = Math.min(lineDistance1, lineDistance2)
|
|
}
|
|
if (distance < minDistance) {
|
|
minDistance = distance
|
|
minPoint = intersect
|
|
isLine = line
|
|
}
|
|
}
|
|
})
|
|
if (minPoint) {
|
|
const hipPoint = [roofLinePoint[0], roofLinePoint[1], minPoint.x, minPoint.y]
|
|
downRoofLines.push(
|
|
drawHipLine(hipPoint, canvas, roof, textMode, getDegreeByChon(currLine.attributes.pitch), getDegreeByChon(currLine.attributes.pitch)),
|
|
)
|
|
|
|
if (isLine) {
|
|
const newRidgePoint = [minPoint.x, minPoint.y]
|
|
const distance1 = Math.sqrt(Math.pow(minPoint.x - isLine.x1, 2) + Math.pow(minPoint.y - isLine.y1, 2))
|
|
const distance2 = Math.sqrt(Math.pow(minPoint.x - isLine.x2, 2) + Math.pow(minPoint.y - isLine.y2, 2))
|
|
if (distance2 < distance1) {
|
|
newRidgePoint.push(isLine.x1, isLine.y1)
|
|
} else {
|
|
newRidgePoint.push(isLine.x2, isLine.y2)
|
|
}
|
|
downRoofLines.push(drawRoofLine(newRidgePoint, canvas, roof, textMode))
|
|
}
|
|
}
|
|
})
|
|
|
|
// B타입 하단지붕 prevLine과 nextLine의 방향이 반대인데 현재 라인이 지붕 안쪽으로 들어가는 모양.
|
|
downRoofGable
|
|
.filter((l) => l.type === 'B')
|
|
.forEach(({ currLine, currIndex }) => {
|
|
// 라인의 방향
|
|
const currVector = { x: Math.sign(clamp01(currLine.x1 - currLine.x2)), y: Math.sign(clamp01(currLine.y1 - currLine.y2)) }
|
|
|
|
//어느쪽이 기준인지 확인.
|
|
//대각선 제외
|
|
if (currVector.x !== 0 && currVector.y !== 0) return
|
|
|
|
const prevLine = baseLines[(currIndex - 1 + baseLines.length) % baseLines.length]
|
|
const nextLine = baseLines[(currIndex + 1) % baseLines.length]
|
|
|
|
const prevVector = { x: Math.sign(clamp01(prevLine.x1 - prevLine.x2)), y: Math.sign(clamp01(prevLine.y1 - prevLine.y2)) }
|
|
const nextVector = { x: Math.sign(clamp01(nextLine.x1 - nextLine.x2)), y: Math.sign(clamp01(nextLine.y1 - nextLine.y2)) }
|
|
|
|
const minX = Math.min(currLine.x1, currLine.x2)
|
|
const maxX = Math.max(currLine.x1, currLine.x2)
|
|
const minY = Math.min(currLine.y1, currLine.y2)
|
|
const maxY = Math.max(currLine.y1, currLine.y2)
|
|
const midX = (currLine.x1 + currLine.x2) / 2
|
|
const midY = (currLine.y1 + currLine.y2) / 2
|
|
let ridgeFindVector = { x: nextVector.x, y: nextVector.y }
|
|
if (!checkWallPolygon.inPolygon({ x: midX, y: midY })) {
|
|
ridgeFindVector = { x: prevVector.x, y: prevVector.y }
|
|
}
|
|
|
|
// 마루를 따라 생성되어야 하는 지붕선 추가.
|
|
const oppLines = innerLines
|
|
.filter((l) => l.name === LINE_TYPE.SUBLINE.RIDGE)
|
|
.filter((line) => {
|
|
const lineVector = { x: Math.sign(clamp01(line.x1 - line.x2)), y: Math.sign(clamp01(line.y1 - line.y2)) }
|
|
let oppVector
|
|
if (currVector.x === 0) {
|
|
if (currVector.y === 1) {
|
|
oppVector = { x: Math.sign(clamp01(currLine.x1 - line.x1)), y: 0 }
|
|
} else if (currVector.y === -1) {
|
|
oppVector = { x: Math.sign(clamp01(line.x1 - currLine.x1)), y: 0 }
|
|
}
|
|
} else if (currVector.y === 0) {
|
|
if (currVector.x === 1) {
|
|
oppVector = { x: 0, y: Math.sign(clamp01(line.y1 - currLine.y1)) }
|
|
} else if (currVector.x === -1) {
|
|
oppVector = { x: 0, y: Math.sign(clamp01(currLine.y1 - line.y1)) }
|
|
}
|
|
}
|
|
|
|
const lineMinX = Math.min(line.x1, line.x2)
|
|
const lineMaxX = Math.max(line.x1, line.x2)
|
|
const lineMinY = Math.min(line.y1, line.y2)
|
|
const lineMaxY = Math.max(line.y1, line.y2)
|
|
|
|
const isInside = lineVector.y === 0 ? minX <= lineMinX && lineMaxX <= maxX : minY <= lineMinY && lineMaxY <= maxY
|
|
|
|
const rightOpp = ridgeFindVector.x === oppVector.x && ridgeFindVector.y === oppVector.y
|
|
return rightOpp && isInside
|
|
})
|
|
|
|
// 현재 라인의 지붕선 추가.
|
|
const currOffset = currLine.attributes.offset
|
|
const roofPoint = [
|
|
currLine.x1 + -nextVector.x * currOffset,
|
|
currLine.y1 + -nextVector.y * currOffset,
|
|
currLine.x2 + -nextVector.x * currOffset,
|
|
currLine.y2 + -nextVector.y * currOffset,
|
|
]
|
|
downRoofLines.push(drawRoofLine(roofPoint, canvas, roof, textMode))
|
|
|
|
// 현재 라인 좌표의 시작과 끝 포인트에서 직교하는 포인트와 라인을 찾는다
|
|
let orthogonalStartPoint,
|
|
orthogonalStartDistance = Infinity,
|
|
orthogonalStartLine
|
|
let orthogonalEndPoint,
|
|
orthogonalEndDistance = Infinity,
|
|
orthogonalEndLine
|
|
oppLines.forEach((line) => {
|
|
if (currVector.x === 0) {
|
|
//세로선
|
|
// 시작포인트와 가까운 포인트의 길이
|
|
const lineStartDist =
|
|
Math.abs(currLine.y1 - line.y1) < Math.abs(currLine.y1 - line.y2) ? Math.abs(currLine.y1 - line.y1) : Math.abs(currLine.y1 - line.y2)
|
|
const lineStartPoint =
|
|
Math.abs(currLine.y1 - line.y1) < Math.abs(currLine.y1 - line.y2) ? { x: line.x1, y: currLine.y1 } : { x: line.x2, y: currLine.y1 }
|
|
if (lineStartDist < orthogonalStartDistance) {
|
|
orthogonalStartDistance = lineStartDist
|
|
orthogonalStartPoint = lineStartPoint
|
|
orthogonalStartLine = line
|
|
}
|
|
// 종료포인트와 가까운 포인트의 길이
|
|
const lineEndDist =
|
|
Math.abs(currLine.y2 - line.y1) < Math.abs(currLine.y2 - line.y2) ? Math.abs(currLine.y2 - line.y2) : Math.abs(currLine.y2 - line.y1)
|
|
const lineEndPoint =
|
|
Math.abs(currLine.y2 - line.y1) < Math.abs(currLine.y2 - line.y2) ? { x: line.x2, y: currLine.y2 } : { x: line.x1, y: currLine.y2 }
|
|
if (lineEndDist < orthogonalEndDistance) {
|
|
orthogonalEndDistance = lineEndDist
|
|
orthogonalEndPoint = lineEndPoint
|
|
orthogonalEndLine = line
|
|
}
|
|
} else if (currVector.y === 0) {
|
|
//가로선
|
|
// 시작포인트와 가까운 포인트의 길이
|
|
const lineStartDist =
|
|
Math.abs(currLine.x1 - line.x1) < Math.abs(currLine.x1 - line.x2) ? Math.abs(currLine.x1 - line.x1) : Math.abs(currLine.x1 - line.x2)
|
|
const lineStartPoint =
|
|
Math.abs(currLine.x1 - line.x1) < Math.abs(currLine.x1 - line.x2) ? { x: currLine.x1, y: line.y1 } : { x: currLine.x1, y: line.y2 }
|
|
if (lineStartDist < orthogonalStartDistance) {
|
|
orthogonalStartDistance = lineStartDist
|
|
orthogonalStartPoint = lineStartPoint
|
|
orthogonalStartLine = line
|
|
}
|
|
//종료포인트와 가까운 포인트의 길이
|
|
const lineEndDist =
|
|
Math.abs(currLine.x2 - line.x1) < Math.abs(currLine.x2 - line.x2) ? Math.abs(currLine.x2 - line.x1) : Math.abs(currLine.x2 - line.x2)
|
|
const lineEndPoint =
|
|
Math.abs(currLine.x2 - line.x1) < Math.abs(currLine.x2 - line.x2) ? { x: currLine.x2, y: line.y1 } : { x: currLine.x2, y: line.y2 }
|
|
if (lineEndDist < orthogonalEndDistance) {
|
|
orthogonalEndDistance = lineEndDist
|
|
orthogonalEndPoint = lineEndPoint
|
|
orthogonalEndLine = line
|
|
}
|
|
}
|
|
})
|
|
|
|
//직교 라인이 있는 경우
|
|
if (orthogonalStartLine !== undefined && orthogonalEndLine !== undefined) {
|
|
if (orthogonalStartLine === orthogonalEndLine) {
|
|
//직교 라인이 1개일때 처리
|
|
downRoofLines.push(
|
|
drawRoofLine([orthogonalStartPoint.x, orthogonalStartPoint.y, orthogonalEndPoint.x, orthogonalEndPoint.y], canvas, roof, textMode),
|
|
)
|
|
} else {
|
|
//직교 라인이 2개일때 처리
|
|
// 시작 라인 처리
|
|
const startDist1 = Math.sqrt(
|
|
Math.pow(orthogonalStartPoint.x - orthogonalStartLine.x1, 2) + Math.pow(orthogonalStartPoint.y - orthogonalStartLine.y1, 2),
|
|
)
|
|
const startDist2 = Math.sqrt(
|
|
Math.pow(orthogonalStartPoint.x - orthogonalStartLine.x2, 2) + Math.pow(orthogonalStartPoint.y - orthogonalStartLine.y2, 2),
|
|
)
|
|
const otherStartPoint =
|
|
startDist1 > startDist2
|
|
? { x: orthogonalStartLine.x1, y: orthogonalStartLine.y1 }
|
|
: { x: orthogonalStartLine.x2, y: orthogonalStartLine.y2 }
|
|
downRoofLines.push(
|
|
drawRoofLine([orthogonalStartPoint.x, orthogonalStartPoint.y, otherStartPoint.x, otherStartPoint.y], canvas, roof, textMode),
|
|
)
|
|
|
|
const endDist1 = Math.sqrt(
|
|
Math.pow(orthogonalEndPoint.x - orthogonalEndLine.x1, 2) + Math.pow(orthogonalEndPoint.y - orthogonalEndLine.y1, 2),
|
|
)
|
|
const endDist2 = Math.sqrt(
|
|
Math.pow(orthogonalEndPoint.x - orthogonalEndLine.x2, 2) + Math.pow(orthogonalEndPoint.y - orthogonalEndLine.y2, 2),
|
|
)
|
|
const otherEndPoint =
|
|
endDist1 > endDist2 ? { x: orthogonalEndLine.x1, y: orthogonalEndLine.y1 } : { x: orthogonalEndLine.x2, y: orthogonalEndLine.y2 }
|
|
downRoofLines.push(drawRoofLine([orthogonalEndPoint.x, orthogonalEndPoint.y, otherEndPoint.x, otherEndPoint.y], canvas, roof, textMode))
|
|
}
|
|
|
|
//지붕선(roofPoint)에서 직교포인트까지 연결하는 라인을 추가한다.
|
|
const orthogonalPoint1 = [roofPoint[0], roofPoint[1], orthogonalStartPoint.x, orthogonalStartPoint.y]
|
|
const orthogonalPoint2 = [roofPoint[2], roofPoint[3], orthogonalEndPoint.x, orthogonalEndPoint.y]
|
|
downRoofLines.push(
|
|
drawHipLine(
|
|
orthogonalPoint1,
|
|
canvas,
|
|
roof,
|
|
textMode,
|
|
|
|
getDegreeByChon(currLine.attributes.pitch),
|
|
getDegreeByChon(currLine.attributes.pitch),
|
|
),
|
|
)
|
|
downRoofLines.push(
|
|
drawHipLine(
|
|
orthogonalPoint2,
|
|
canvas,
|
|
roof,
|
|
textMode,
|
|
|
|
getDegreeByChon(currLine.attributes.pitch),
|
|
getDegreeByChon(currLine.attributes.pitch),
|
|
),
|
|
)
|
|
}
|
|
})
|
|
|
|
//처마에서 파생된 하단 지붕 라인 처리.
|
|
downRoofEaves.forEach(({ currLine, currIndex }) => {
|
|
const prevIndex = (currIndex - 1 + baseLines.length) % baseLines.length
|
|
const nextIndex = (currIndex + 1) % baseLines.length
|
|
|
|
const analyze = analyzeLine(currLine)
|
|
const currMidX = (currLine.x1 + currLine.x2) / 2
|
|
const currMidY = (currLine.y1 + currLine.y2) / 2
|
|
const roofLine = analyze.roofLine
|
|
const roofVector = { x: Math.sign(clamp01(roofLine.x2 - roofLine.x1)), y: Math.sign(clamp01(roofLine.y2 - roofLine.y1)) }
|
|
const originPoint = currLine.attributes.originPoint
|
|
|
|
let isFlowInside // 조정된 형이 안쪽인지 바깥쪽인지 판단.
|
|
let flowDistance = 0 // 조정된 거리
|
|
if (analyze.isVertical) {
|
|
//세로선
|
|
flowDistance = Math.abs(currLine.x1 - originPoint.x1)
|
|
const isLeftIn = checkWallPolygon.inPolygon({ x: currMidX - 1, y: currMidY })
|
|
const vector = Math.sign(currLine.x1 - originPoint.x1)
|
|
isFlowInside = isLeftIn ? vector < 0 : vector > 0
|
|
} else if (analyze.isHorizontal) {
|
|
//가로선
|
|
flowDistance = Math.abs(currLine.y1 - originPoint.y1)
|
|
const isTopIn = checkWallPolygon.inPolygon({ x: currMidX, y: currMidY - 1 })
|
|
const vector = Math.sign(currLine.y1 - originPoint.y1)
|
|
isFlowInside = isTopIn ? vector < 0 : vector > 0
|
|
}
|
|
const roofCheckPoint = { x: roofLine.x2 + roofVector.x, y: roofLine.y2 + roofVector.y }
|
|
let otherLine = roof.inPolygon(roofCheckPoint) ? baseLines[nextIndex] : baseLines[prevIndex]
|
|
|
|
//상단 지붕
|
|
const upLine = isFlowInside ? otherLine : currLine
|
|
//하단 지붕
|
|
const downLine = isFlowInside ? currLine : otherLine
|
|
|
|
const allLinePoint = [
|
|
{ x: upLine.x1, y: upLine.y1 },
|
|
{ x: upLine.x2, y: upLine.y2 },
|
|
{ x: downLine.x1, y: downLine.y1 },
|
|
{ x: downLine.x2, y: downLine.y2 },
|
|
]
|
|
|
|
//두 라인이 만나는 포인트
|
|
let joinPoint
|
|
allLinePoint.forEach((point, i) => {
|
|
allLinePoint.forEach((point2, j) => {
|
|
if (i !== j && point.x === point2.x && point.y === point2.y) {
|
|
joinPoint = point
|
|
}
|
|
})
|
|
})
|
|
|
|
const upAnalyze = analyzeLine(upLine)
|
|
const upRoofLine = upAnalyze.roofLine
|
|
const addUpOffset = flowDistance //상단 지붕선의 추가길이
|
|
const upRoofVector = { x: Math.sign(clamp01(upRoofLine.x2 - upRoofLine.x1)), y: Math.sign(clamp01(upRoofLine.y2 - upRoofLine.y1)) }
|
|
const upRoofPoint = [] //상단 지붕선
|
|
let upHipStartPoint
|
|
if (roof.inPolygon({ x: upRoofLine.x2 + upRoofVector.x * addUpOffset, y: upRoofLine.y2 + upRoofVector.y * addUpOffset })) {
|
|
upRoofPoint.push(upRoofLine.x1, upRoofLine.y1, upRoofLine.x2 + upRoofVector.x * addUpOffset, upRoofLine.y2 + upRoofVector.y * addUpOffset)
|
|
upHipStartPoint = { x: upRoofLine.x1, y: upRoofLine.y1 }
|
|
} else {
|
|
upRoofPoint.push(upRoofLine.x1 - upRoofVector.x * addUpOffset, upRoofLine.y1 - upRoofVector.y * addUpOffset, upRoofLine.x2, upRoofLine.y2)
|
|
upHipStartPoint = { x: upRoofLine.x2, y: upRoofLine.y2 }
|
|
}
|
|
|
|
const downDegree = getDegreeByChon(downLine.attributes.pitch)
|
|
const downAnalyze = analyzeLine(downLine)
|
|
const downRoofLine = downAnalyze.roofLine
|
|
const addDownOffset = upLine.attributes.offset // 하단 지붕선의 추가길이
|
|
const downRoofVector = { x: Math.sign(clamp01(downRoofLine.x2 - downRoofLine.x1)), y: Math.sign(clamp01(downRoofLine.y2 - downRoofLine.y1)) }
|
|
const downRoofPoint = [] // 하단 지붕선
|
|
let downHipStartPoint
|
|
if (roof.inPolygon({ x: downRoofLine.x2 + downRoofVector.x * addDownOffset, y: downRoofLine.y2 + downRoofVector.y * addDownOffset })) {
|
|
downRoofPoint.push(
|
|
downRoofLine.x1,
|
|
downRoofLine.y1,
|
|
downRoofLine.x2 + downRoofVector.x * addDownOffset,
|
|
downRoofLine.y2 + downRoofVector.y * addDownOffset,
|
|
)
|
|
downHipStartPoint = { x: downRoofPoint[2], y: downRoofPoint[3] }
|
|
} else {
|
|
downRoofPoint.push(
|
|
downRoofLine.x1 - downRoofVector.x * addDownOffset,
|
|
downRoofLine.y1 - downRoofVector.y * addDownOffset,
|
|
downRoofLine.x2,
|
|
downRoofLine.y2,
|
|
)
|
|
downHipStartPoint = { x: downRoofPoint[0], y: downRoofPoint[1] }
|
|
}
|
|
|
|
//상단지붕선과 만나는 innerLines 조정
|
|
const upRoofEdge = { vertex1: { x: upRoofPoint[0], y: upRoofPoint[1] }, vertex2: { x: upRoofPoint[2], y: upRoofPoint[3] } }
|
|
|
|
let upHipVector = getHalfAngleVector(upLine, downLine)
|
|
const vectorCheckPoint = { x: joinPoint.x + upHipVector.x, y: joinPoint.y + upHipVector.y }
|
|
if (!checkWallPolygon.inPolygon(vectorCheckPoint)) {
|
|
upHipVector = { x: -upHipVector.x, y: -upHipVector.y }
|
|
}
|
|
|
|
const findUpP1 = { x: joinPoint.x, y: joinPoint.y }
|
|
const findUpP2 = { x: joinPoint.x + upHipVector.x, y: joinPoint.y + upHipVector.y }
|
|
|
|
// 상하단 지붕이 만나는 교점에서 파생되는 추녀마루 확인.
|
|
let joinHipLine = innerLines.find((line) => {
|
|
return isPointOnLineNew(line, findUpP1) && isPointOnLineNew(line, findUpP2)
|
|
})
|
|
|
|
if (joinHipLine) {
|
|
// 상단 지붕선과 추녀마루의 교점 파악.
|
|
const joinHipEdge = { vertex1: { x: joinHipLine.x1, y: joinHipLine.y1 }, vertex2: { x: joinHipLine.x2, y: joinHipLine.y2 } }
|
|
const intersectJoin = edgesIntersection(upRoofEdge, joinHipEdge)
|
|
|
|
// 연결된 추녀마루 포인트 조정
|
|
const pointVector1 = { x: Math.sign(clamp01(joinHipLine.x1 - intersectJoin.x)), y: Math.sign(clamp01(joinHipLine.y1 - intersectJoin.y)) }
|
|
const pointVector2 = { x: Math.sign(clamp01(joinHipLine.x2 - intersectJoin.x)), y: Math.sign(clamp01(joinHipLine.y2 - intersectJoin.y)) }
|
|
let joinEndPoint
|
|
if (pointVector1.x === upHipVector.x && pointVector1.y === upHipVector.y) {
|
|
joinHipLine.set({
|
|
x1: intersectJoin.x,
|
|
y1: intersectJoin.y,
|
|
x2: joinHipLine.x1,
|
|
y2: joinHipLine.y1,
|
|
})
|
|
joinHipLine.setCoords()
|
|
joinEndPoint = { x: joinHipLine.x1, y: joinHipLine.y1 }
|
|
} else if (pointVector2.x === upHipVector.x && pointVector2.y === upHipVector.y) {
|
|
joinHipLine.set({
|
|
x1: intersectJoin.x,
|
|
y1: intersectJoin.y,
|
|
x2: joinHipLine.x2,
|
|
y2: joinHipLine.y2,
|
|
})
|
|
joinHipLine.setCoords()
|
|
joinEndPoint = { x: joinHipLine.x2, y: joinHipLine.y2 }
|
|
}
|
|
|
|
let upSideLine
|
|
baseLines.forEach((line, index) => {
|
|
if (line === upLine) {
|
|
const upPrevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length]
|
|
const upNextLine = baseLines[(index + 1) % baseLines.length]
|
|
upSideLine = upPrevLine === downLine ? upNextLine : upPrevLine
|
|
}
|
|
})
|
|
//상단 지붕선과 붙어있는 다른 지붕선이 처마일때 추녀마루가 포함될수 있기 때문에 확인하여 라인조정
|
|
if (upSideLine && upSideLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) {
|
|
const hipDegree = getDegreeByChon(upSideLine.attributes.pitch)
|
|
let minDistance = Infinity
|
|
let connectPoint
|
|
innerLines
|
|
.filter((line) => line !== joinHipLine)
|
|
.forEach((line) => {
|
|
const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
|
|
const intersect = edgesIntersection(upRoofEdge, lineEdge)
|
|
if (
|
|
intersect &&
|
|
isPointOnLineNew(line, intersect) &&
|
|
isPointOnLineNew({ x1: upRoofPoint[0], y1: upRoofPoint[1], x2: upRoofPoint[2], y2: upRoofPoint[3] }, intersect)
|
|
) {
|
|
const distance = Math.sqrt(Math.pow(intersect.x - upHipStartPoint.x, 2) + Math.pow(intersect.y - upHipStartPoint.y, 2))
|
|
if (distance < minDistance) {
|
|
minDistance = distance
|
|
connectPoint = intersect
|
|
}
|
|
}
|
|
})
|
|
if (connectPoint && minDistance > EPSILON) {
|
|
let upHipPoint
|
|
if (upHipStartPoint.x === upRoofLine.x1 && upHipStartPoint.y === upRoofLine.y1) {
|
|
upHipPoint = [upHipStartPoint.x, upHipStartPoint.y, connectPoint.x, connectPoint.y]
|
|
} else {
|
|
upHipPoint = [connectPoint.x, connectPoint.y, upHipStartPoint.x, upHipStartPoint.y]
|
|
}
|
|
downRoofLines.push(drawHipLine(upHipPoint, canvas, roof, textMode, hipDegree, hipDegree)) //각도 있는 처마지붕선
|
|
downRoofLines.push(drawRoofLine([connectPoint.x, connectPoint.y, intersectJoin.x, intersectJoin.y], canvas, roof, textMode)) //각도 없는 처마지붕선
|
|
|
|
roof.adjustRoofLines.push({ point: upHipPoint, roofIdx: upRoofLine.idx })
|
|
roof.adjustRoofLines.push({ point: [connectPoint.x, connectPoint.y, intersectJoin.x, intersectJoin.y], roofIdx: upRoofLine.idx })
|
|
} else {
|
|
downRoofLines.push(drawRoofLine([upHipStartPoint.x, upHipStartPoint.y, intersectJoin.x, intersectJoin.y], canvas, roof, textMode))
|
|
roof.adjustRoofLines.push({ point: [upHipStartPoint.x, upHipStartPoint.y, intersectJoin.x, intersectJoin.y], roofIdx: upRoofLine.idx })
|
|
}
|
|
} else {
|
|
downRoofLines.push(drawRoofLine([upHipStartPoint.x, upHipStartPoint.y, intersectJoin.x, intersectJoin.y], canvas, roof, textMode))
|
|
roof.adjustRoofLines.push({ point: [upHipStartPoint.x, upHipStartPoint.y, intersectJoin.x, intersectJoin.y], roofIdx: upRoofLine.idx })
|
|
}
|
|
|
|
//하단지붕선 추가.
|
|
const downHipEdge = {
|
|
vertex1: { x: downHipStartPoint.x, y: downHipStartPoint.y },
|
|
vertex2: { x: downHipStartPoint.x + upRoofVector.x, y: downHipStartPoint.y + upRoofVector.y },
|
|
}
|
|
const intersectDownJoin = edgesIntersection(downHipEdge, joinHipEdge)
|
|
//하단 지붕선에는 지붕선, 추녀마루1, 추녀마루2 가 생성되는것이 기본모양임.
|
|
if (intersectDownJoin && isPointOnLineNew(joinHipLine, intersectDownJoin)) {
|
|
//지붕선
|
|
downRoofLines.push(drawRoofLine(downRoofPoint, canvas, roof, textMode))
|
|
//지붕선에서 올라가는 추녀마루1
|
|
downRoofLines.push(
|
|
drawHipLine(
|
|
[downHipStartPoint.x, downHipStartPoint.y, intersectDownJoin.x, intersectDownJoin.y],
|
|
canvas,
|
|
roof,
|
|
textMode,
|
|
downDegree,
|
|
downDegree,
|
|
),
|
|
)
|
|
//추녀마루에서 마루로 연결되는 추녀마루2
|
|
downRoofLines.push(
|
|
drawHipLine([intersectDownJoin.x, intersectDownJoin.y, joinEndPoint.x, joinEndPoint.y], canvas, roof, textMode, downDegree, downDegree),
|
|
)
|
|
roof.adjustRoofLines.push({ point: downRoofPoint, roofIdx: downRoofLine.idx })
|
|
}
|
|
}
|
|
})
|
|
|
|
//추가된 하단 지붕 라인 innerLines에 추가.
|
|
innerLines.push(...downRoofLines)
|
|
|
|
//지붕선에 따라 라인추가 작업 처리.
|
|
const innerLinesPoints = []
|
|
innerLines.forEach((line) => {
|
|
const hasCoord1 = innerLinesPoints.find((p) => p.x === line.x1 && p.y === line.y1)
|
|
const hasCoord2 = innerLinesPoints.find((p) => p.x === line.x2 && p.y === line.y2)
|
|
if (!hasCoord1) innerLinesPoints.push({ x: line.x1, y: line.y1 })
|
|
if (!hasCoord2) innerLinesPoints.push({ x: line.x2, y: line.y2 })
|
|
})
|
|
roof.lines.forEach((currentLine, index) => {
|
|
const prevLine = roof.lines[(index - 1 + roof.lines.length) % roof.lines.length]
|
|
const nextLine = roof.lines[(index + 1) % roof.lines.length]
|
|
const prevDegree = prevLine.attributes.pitch ? getDegreeByChon(prevLine.attributes.pitch) : 0
|
|
const nextDegree = nextLine.attributes.pitch ? getDegreeByChon(nextLine.attributes.pitch) : 0
|
|
|
|
const splitPoint = []
|
|
let hasOverlapLine = false
|
|
const minX = Math.min(currentLine.x1, currentLine.x2)
|
|
const maxX = Math.max(currentLine.x1, currentLine.x2)
|
|
const minY = Math.min(currentLine.y1, currentLine.y2)
|
|
const maxY = Math.max(currentLine.y1, currentLine.y2)
|
|
innerLines.forEach((innerLine) => {
|
|
const innerLineMinX = Math.min(innerLine.x1, innerLine.x2)
|
|
const innerLineMaxX = Math.max(innerLine.x1, innerLine.x2)
|
|
const innerLineMinY = Math.min(innerLine.y1, innerLine.y2)
|
|
const innerLineMaxY = Math.max(innerLine.y1, innerLine.y2)
|
|
if (innerLineMinX <= minX && innerLineMaxX >= maxX && innerLineMinY <= minY && innerLineMaxY >= maxY) {
|
|
hasOverlapLine = true
|
|
}
|
|
if (minX <= innerLineMinX && maxX >= innerLineMaxX && minY <= innerLineMinY && maxY >= innerLineMaxY) {
|
|
hasOverlapLine = true
|
|
}
|
|
})
|
|
if (hasOverlapLine) return
|
|
|
|
innerLinesPoints.forEach((point) => {
|
|
if (
|
|
isPointOnLineNew(currentLine, point) &&
|
|
!(almostEqual(currentLine.x1, point.x) && almostEqual(currentLine.y1, point.y)) &&
|
|
!(almostEqual(currentLine.x2, point.x) && almostEqual(currentLine.y2, point.y))
|
|
) {
|
|
const distance = Math.sqrt((point.x - currentLine.x1) ** 2 + (point.y - currentLine.y1) ** 2)
|
|
splitPoint.push({ point, distance })
|
|
}
|
|
})
|
|
if (splitPoint.length > 0) {
|
|
splitPoint.sort((a, b) => a[1] - b[1])
|
|
let startPoint = { x: currentLine.x1, y: currentLine.y1 }
|
|
for (let i = 0; i < splitPoint.length; i++) {
|
|
const point = splitPoint[i].point
|
|
if (i === 0) {
|
|
innerLines.push(drawHipLine([startPoint.x, startPoint.y, point.x, point.y], canvas, roof, textMode, prevDegree, prevDegree))
|
|
} else {
|
|
innerLines.push(drawRoofLine([startPoint.x, startPoint.y, point.x, point.y], canvas, roof, textMode))
|
|
}
|
|
startPoint = point
|
|
}
|
|
if (splitPoint.length === 1) {
|
|
innerLines.push(drawRoofLine([startPoint.x, startPoint.y, currentLine.x2, currentLine.y2], canvas, roof, textMode))
|
|
} else {
|
|
innerLines.push(drawHipLine([startPoint.x, startPoint.y, currentLine.x2, currentLine.y2], canvas, roof, textMode, nextDegree, nextDegree))
|
|
}
|
|
} else {
|
|
innerLines.push(drawRoofLine([currentLine.x1, currentLine.y1, currentLine.x2, currentLine.y2], canvas, roof, textMode))
|
|
}
|
|
})
|
|
|
|
//지붕 innerLines에 추가.
|
|
roof.innerLines = innerLines
|
|
|
|
canvas
|
|
.getObjects()
|
|
.filter((object) => object.name === 'check')
|
|
.forEach((object) => canvas.remove(object))
|
|
canvas.renderAll()
|
|
}
|
|
|
|
/**
|
|
* 라인 방향 확인 용
|
|
* @param x1
|
|
* @param y1
|
|
* @param x2
|
|
* @param y2
|
|
* @param color
|
|
* @param roofId
|
|
* @returns {*}
|
|
*/
|
|
export const createArrow = ({ x1, y1, x2, y2 }, color, roofId) => {
|
|
const angle = Math.atan2(y2 - y1, x2 - x1)
|
|
const headLength = 15
|
|
|
|
const points = [
|
|
{ x: x1, y: y1 },
|
|
{ x: x2, y: y2 },
|
|
{
|
|
x: x2 - headLength * Math.cos(angle - Math.PI / 7),
|
|
y: y2 - headLength * Math.sin(angle - Math.PI / 7),
|
|
},
|
|
{ x: x2, y: y2 },
|
|
{
|
|
x: x2 - headLength * Math.cos(angle + Math.PI / 7),
|
|
y: y2 - headLength * Math.sin(angle + Math.PI / 7),
|
|
},
|
|
]
|
|
|
|
return new fabric.Polyline(points, {
|
|
fill: 'transparent',
|
|
stroke: color,
|
|
strokeWidth: 3,
|
|
selectable: false,
|
|
parentId: roofId,
|
|
name: 'check',
|
|
})
|
|
}
|
|
/**
|
|
* 선분과 선분의 교점을 구한다.
|
|
* @param line1Start
|
|
* @param line1End
|
|
* @param line2Start
|
|
* @param line2End
|
|
*/
|
|
const lineIntersection = (line1Start, line1End, line2Start, line2End) => {
|
|
const dx1 = line1End.x - line1Start.x
|
|
const dy1 = line1End.y - line1Start.y
|
|
const dx2 = line2End.x - line2Start.x
|
|
const dy2 = line2End.y - line2Start.y
|
|
// const denominator = (line2End.y - line2Start.y) * (line1End.x - line1Start.x) - (line2End.x - line2Start.x) * (line1End.y - line1Start.y)
|
|
const denominator = dy2 * dx1 - dx2 * dy1
|
|
if (Math.abs(denominator) < EPSILON) {
|
|
const isOpposite = dx1 * dx2 + dy1 * dy2 < 0
|
|
|
|
const isOverlap = isPointOnLineNew({ x1: line1Start.x, y1: line1Start.y, x2: line1End.x, y2: line1End.y }, { x: line2Start.x, y: line2Start.y })
|
|
|
|
if (isOpposite && isOverlap) {
|
|
return { x: line2Start.x, y: line2Start.y }
|
|
}
|
|
return null
|
|
} // 평행
|
|
|
|
let ua = (dx2 * (line1Start.y - line2Start.y) - dy2 * (line1Start.x - line2Start.x)) / denominator
|
|
let ub = (dx1 * (line1Start.y - line2Start.y) - dy1 * (line1Start.x - line2Start.x)) / denominator
|
|
|
|
ua = clamp01(ua)
|
|
ub = clamp01(ub)
|
|
|
|
if (ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1) {
|
|
return {
|
|
x: line1Start.x + ua * dx1,
|
|
y: line1Start.y + ua * dy1,
|
|
}
|
|
}
|
|
return null
|
|
}
|
|
|
|
/**
|
|
* 경계값 보정용 헬퍼
|
|
* @param t
|
|
* @returns {*|number}
|
|
*/
|
|
const clamp01 = (t) => {
|
|
if (almostEqual(t, 0, EPSILON)) return 0
|
|
if (almostEqual(t, 1, EPSILON)) return 1
|
|
return t
|
|
}
|
|
|
|
/**
|
|
* 두 라인이 평행한지 확인한다.
|
|
* @param line1
|
|
* @param line2
|
|
* @returns {boolean}
|
|
*/
|
|
const isParallel = (line1, line2) => {
|
|
// 벡터 계산
|
|
const v1x = line1.x2 - line1.x1
|
|
const v1y = line1.y2 - line1.y1
|
|
const v2x = line2.x2 - line2.x1
|
|
const v2y = line2.y2 - line2.y1
|
|
|
|
// 벡터의 크기 계산
|
|
const length1 = Math.sqrt(v1x ** 2 + v1y ** 2)
|
|
const length2 = Math.sqrt(v2x ** 2 + v2y ** 2)
|
|
|
|
// 벡터가 너무 작으면 평행 판단 불가
|
|
if (length1 < EPSILON || length2 < EPSILON) {
|
|
return false
|
|
}
|
|
|
|
// 정규화된 벡터
|
|
const norm1x = v1x / length1
|
|
const norm1y = v1y / length1
|
|
const norm2x = v2x / length2
|
|
const norm2y = v2y / length2
|
|
|
|
// 외적(cross product)을 이용한 평행 판단
|
|
// 외적의 크기가 0에 가까우면 평행
|
|
const crossProduct = Math.abs(norm1x * norm2y - norm1y * norm2x)
|
|
const isParallel = crossProduct < EPSILON
|
|
|
|
// 또는 내적(dot product)을 이용한 방법
|
|
// 내적의 절대값이 1에 가까우면 평행 또는 반평행
|
|
const dotProduct = norm1x * norm2x + norm1y * norm2y
|
|
const isParallelOrAntiParallel = Math.abs(Math.abs(dotProduct) - 1) < EPSILON
|
|
|
|
return isParallel || isParallelOrAntiParallel
|
|
}
|
|
|
|
/**
|
|
* 두 라인의 방향에 따라 이등분선의 vector를 구한다.
|
|
* @param line1
|
|
* @param line2
|
|
*/
|
|
const getBisectLines = (line1, line2) => {
|
|
const bisector = { x: 0, y: 0 }
|
|
|
|
// 벡터 계산
|
|
const v1x = line1.x2 - line1.x1
|
|
const v1y = line1.y2 - line1.y1
|
|
const v2x = line2.x2 - line2.x1
|
|
const v2y = line2.y2 - line2.y1
|
|
|
|
// 벡터의 크기 계산
|
|
const length1 = Math.sqrt(v1x ** 2 + v1y ** 2)
|
|
const length2 = Math.sqrt(v2x ** 2 + v2y ** 2)
|
|
|
|
// 벡터가 너무 작으면 평행 판단 불가
|
|
if (length1 < EPSILON || length2 < EPSILON) {
|
|
return bisector
|
|
}
|
|
|
|
const lineEdge1 = { vertex1: { x: line1.x1, y: line1.y1 }, vertex2: { x: line1.x2, y: line1.y2 } }
|
|
const lineEdge2 = { vertex1: { x: line2.x1, y: line2.y1 }, vertex2: { x: line2.x2, y: line2.y2 } }
|
|
const is = edgesIntersection(lineEdge1, lineEdge2)
|
|
if (is) {
|
|
const dir1 = getDirection(line1, is)
|
|
const dir2 = getDirection(line2, is)
|
|
|
|
// 이등분선 계산
|
|
const bisectorX = dir1.x + dir2.x
|
|
const bisectorY = dir1.y + dir2.y
|
|
const length = Math.sqrt(bisectorX * bisectorX + bisectorY * bisectorY)
|
|
|
|
bisector.x = bisectorX / length
|
|
bisector.y = bisectorY / length
|
|
}
|
|
|
|
return bisector
|
|
}
|
|
|
|
/**
|
|
* BaseLine의 교차상태에서의 bisect를 구한다.
|
|
* @param line1
|
|
* @param line2
|
|
* @param intersection
|
|
*/
|
|
const getBisectBaseLines = (line1, line2, intersection) => {
|
|
let bisector = { x: 0, y: 0 }
|
|
if (isParallel(line1, line2)) return bisector
|
|
|
|
const lineEdge1 = { vertex1: { x: line1.x1, y: line1.y1 }, vertex2: { x: line1.x2, y: line1.y2 } }
|
|
const lineEdge2 = { vertex1: { x: line2.x1, y: line2.y1 }, vertex2: { x: line2.x2, y: line2.y2 } }
|
|
const is = edgesIntersection(lineEdge1, lineEdge2)
|
|
if (is) {
|
|
bisector = { x: Math.sign(intersection.x - is.x), y: Math.sign(intersection.y - is.y) }
|
|
}
|
|
return bisector
|
|
}
|
|
|
|
/**
|
|
* a 와 b 가 epsilon내 오차인지 확인한다.
|
|
* @param a
|
|
* @param b
|
|
* @param epsilon
|
|
* @returns {boolean}
|
|
*/
|
|
const almostEqual = (a, b, epsilon = 1e-10) => {
|
|
if (a === Infinity || b === Infinity) return false
|
|
return Math.abs(a - b) <= epsilon
|
|
}
|
|
|
|
/**
|
|
* segment가 fromPoint에서 떨어진 방향을 구한다.
|
|
* @param segment
|
|
* @param fromPoint
|
|
* @returns {{x, y}}
|
|
*/
|
|
const getDirection = (segment, fromPoint) => {
|
|
const target = { x: segment.x2, y: segment.y2 }
|
|
|
|
const dx = target.x - fromPoint.x
|
|
const dy = target.y - fromPoint.y
|
|
const length = Math.sqrt(dx * dx + dy * dy)
|
|
|
|
return { x: dx / length, y: dy / length }
|
|
}
|
|
|
|
/**
|
|
* lines중 points가 이미 존재하는지 확인한다.
|
|
* @param lines
|
|
* @param points
|
|
* @returns {boolean}
|
|
*/
|
|
const alreadyPoints = (lines, points) => {
|
|
let has = false
|
|
|
|
lines.forEach((line) => {
|
|
const linePoints = [line.x1, line.y1, line.x2, line.y2]
|
|
if (
|
|
(almostEqual(linePoints[0], points[0], 1) &&
|
|
almostEqual(linePoints[1], points[1], 1) &&
|
|
almostEqual(linePoints[2], points[2], 1) &&
|
|
almostEqual(linePoints[3], points[3], 1)) ||
|
|
(almostEqual(linePoints[0], points[2], 1) &&
|
|
almostEqual(linePoints[1], points[3], 1) &&
|
|
almostEqual(linePoints[2], points[0], 1) &&
|
|
almostEqual(linePoints[3], points[1], 1))
|
|
) {
|
|
has = true
|
|
}
|
|
})
|
|
|
|
return has
|
|
}
|
|
|
|
/**
|
|
* 추녀 마루를 그린다.
|
|
* @param points
|
|
* @param canvas
|
|
* @param roof
|
|
* @param textMode
|
|
* @param prevDegree
|
|
* @param currentDegree
|
|
*/
|
|
const drawHipLine = (points, canvas, roof, textMode, prevDegree, currentDegree) => {
|
|
/** 대각선인 경우 경사를 조정해서 계산*/
|
|
const baseX = Big(points[0]).minus(Big(points[2])).abs()
|
|
const baseY = Big(points[1]).minus(Big(points[3])).abs()
|
|
if (baseX.gt(1) && baseY.gt(1)) {
|
|
const hypotenuse = calcLinePlaneSize({ x1: points[0], y1: points[1], x2: points[2], y2: points[3] })
|
|
const base = getAdjacent(hypotenuse)
|
|
const heightX = base * Math.tan((currentDegree * Math.PI) / 180)
|
|
const heightY = base * Math.tan((prevDegree * Math.PI) / 180)
|
|
const degreeX = Math.atan2(heightX, hypotenuse) * (180 / Math.PI)
|
|
const degreeY = Math.atan2(heightY, hypotenuse) * (180 / Math.PI)
|
|
if (Math.abs(degreeX - degreeY) < 1) {
|
|
currentDegree = degreeX
|
|
prevDegree = degreeY
|
|
}
|
|
}
|
|
|
|
const hip = new QLine(points, {
|
|
parentId: roof.id,
|
|
fontSize: roof.fontSize,
|
|
stroke: '#1083E3',
|
|
strokeWidth: 2,
|
|
name: LINE_TYPE.SUBLINE.HIP,
|
|
textMode: textMode,
|
|
attributes: {
|
|
roofId: roof.id,
|
|
planeSize: calcLinePlaneSize({
|
|
x1: points[0],
|
|
y1: points[1],
|
|
x2: points[2],
|
|
y2: points[3],
|
|
}),
|
|
actualSize:
|
|
prevDegree === currentDegree
|
|
? calcLineActualSize(
|
|
{
|
|
x1: points[0],
|
|
y1: points[1],
|
|
x2: points[2],
|
|
y2: points[3],
|
|
},
|
|
currentDegree,
|
|
)
|
|
: 0,
|
|
},
|
|
})
|
|
|
|
canvas.add(hip)
|
|
hip.bringToFront()
|
|
canvas.renderAll()
|
|
return hip
|
|
}
|
|
|
|
/**
|
|
* 마루를 그린다.
|
|
* @param points
|
|
* @param canvas
|
|
* @param roof
|
|
* @param textMode
|
|
* @returns {*}
|
|
*/
|
|
const drawRidgeLine = (points, canvas, roof, textMode) => {
|
|
const ridge = new QLine(points, {
|
|
parentId: roof.id,
|
|
fontSize: roof.fontSize,
|
|
stroke: '#1083E3',
|
|
strokeWidth: 2,
|
|
name: LINE_TYPE.SUBLINE.RIDGE,
|
|
textMode: textMode,
|
|
attributes: {
|
|
roofId: roof.id,
|
|
planeSize: calcLinePlaneSize({
|
|
x1: points[0],
|
|
y1: points[1],
|
|
x2: points[2],
|
|
y2: points[3],
|
|
}),
|
|
actualSize: calcLinePlaneSize({
|
|
x1: points[0],
|
|
y1: points[1],
|
|
x2: points[2],
|
|
y2: points[3],
|
|
}),
|
|
},
|
|
})
|
|
canvas.add(ridge)
|
|
ridge.bringToFront()
|
|
canvas.renderAll()
|
|
|
|
return ridge
|
|
}
|
|
|
|
/**
|
|
* 지붕선을 그린다.
|
|
* @param points
|
|
* @param canvas
|
|
* @param roof
|
|
* @param textMode
|
|
* @returns {*}
|
|
*/
|
|
const drawRoofLine = (points, canvas, roof, textMode) => {
|
|
const ridge = new QLine(points, {
|
|
parentId: roof.id,
|
|
fontSize: roof.fontSize,
|
|
stroke: '#1083E3',
|
|
strokeWidth: 2,
|
|
name: LINE_TYPE.SUBLINE.HIP,
|
|
textMode: textMode,
|
|
attributes: {
|
|
roofId: roof.id,
|
|
planeSize: calcLinePlaneSize({
|
|
x1: points[0],
|
|
y1: points[1],
|
|
x2: points[2],
|
|
y2: points[3],
|
|
}),
|
|
actualSize: calcLinePlaneSize({
|
|
x1: points[0],
|
|
y1: points[1],
|
|
x2: points[2],
|
|
y2: points[3],
|
|
}),
|
|
},
|
|
})
|
|
canvas.add(ridge)
|
|
ridge.bringToFront()
|
|
canvas.renderAll()
|
|
|
|
return ridge
|
|
}
|
|
|
|
/**
|
|
* 벡터를 정규화(Normalization)하는 함수
|
|
* @param v
|
|
* @returns {{x: *, y: *}|{x: number, y: number}}
|
|
*/
|
|
const normalizeVector = (v) => {
|
|
/** 벡터의 크기(길이)*/
|
|
const magnitude = Big(v.x).pow(2).plus(Big(v.y).pow(2)).sqrt()
|
|
if (magnitude.eq(0)) return { x: 0, y: 0 } // 크기가 0일 경우 (예외 처리)
|
|
return { x: Math.sign(Big(v.x).div(magnitude).toNumber()), y: Math.sign(Big(v.y).div(magnitude).toNumber()) }
|
|
}
|
|
|
|
/**
|
|
* 사잇각의 절반 방향 벡터 계산 함수
|
|
* @param line1
|
|
* @param line2
|
|
* @returns {{x: *, y: *}|{x: number, y: number}}
|
|
*/
|
|
const getHalfAngleVector = (line1, line2) => {
|
|
const v1 = { x: Big(line1.x2).minus(Big(line1.x1)).toNumber(), y: Big(line1.y1).minus(Big(line1.y2)).toNumber() }
|
|
const v2 = { x: Big(line2.x2).minus(Big(line2.x1)).toNumber(), y: Big(line2.y1).minus(Big(line2.y2)).toNumber() }
|
|
|
|
/**
|
|
* 벡터 정규화
|
|
* @type {{x: *, y: *}|{x: number, y: number}}
|
|
*/
|
|
const unitV1 = normalizeVector(v1) // 첫 번째 벡터를 정규화
|
|
const unitV2 = normalizeVector(v2) // 두 번째 벡터를 정규화
|
|
|
|
/**
|
|
* 두 벡터를 더합니다
|
|
* @type {{x: *, y: *}}
|
|
*/
|
|
const summedVector = {
|
|
x: Big(unitV1.x).plus(Big(unitV2.x)).toNumber(),
|
|
y: Big(unitV1.y).plus(Big(unitV2.y)).toNumber(),
|
|
}
|
|
|
|
/** 결과 벡터를 정규화하여 사잇각 벡터를 반환합니다 */
|
|
return normalizeVector(summedVector)
|
|
}
|
|
|
|
/**
|
|
* 지붕 모양 을 변경한다.
|
|
* @param polygon
|
|
* @param canvas
|
|
*/
|
|
const reDrawPolygon = (polygon, canvas) => {
|
|
const lines = polygon.lines
|
|
let point = []
|
|
lines.forEach((line) => point.push({ x: line.x1, y: line.y1 }))
|
|
|
|
canvas?.remove(polygon)
|
|
|
|
const newPolygon = new QPolygon(point, {
|
|
id: polygon.id,
|
|
name: polygon.name,
|
|
fill: polygon.fill,
|
|
stroke: polygon.stroke,
|
|
strokeWidth: polygon.strokeWidth,
|
|
selectable: polygon.selectable,
|
|
fontSize: polygon.fontSize,
|
|
wall: polygon.wall !== undefined ? polygon.wall : null,
|
|
originX: polygon.originX,
|
|
originY: polygon.originY,
|
|
})
|
|
|
|
const newLines = newPolygon.lines
|
|
|
|
newLines.forEach((line) => {
|
|
lines.forEach((l) => {
|
|
if (line.x1 === l.x1 && line.y1 === l.y1) {
|
|
line.id = l.id
|
|
line.attributes = l.attributes
|
|
}
|
|
})
|
|
const lineLength = calcLinePlaneSize({ x1: line.x1, y1: line.y1, x2: line.x2, y2: line.y2 })
|
|
if (line.attributes !== undefined) {
|
|
line.attributes.planeSize = lineLength
|
|
line.attributes.actualSize = lineLength
|
|
} else {
|
|
line.attributes = {
|
|
roofId: newPolygon.id,
|
|
planeSize: lineLength,
|
|
actualSize: lineLength,
|
|
}
|
|
}
|
|
})
|
|
|
|
canvas.add(newPolygon)
|
|
canvas.renderAll()
|
|
|
|
return newPolygon
|
|
}
|
|
|
|
/**
|
|
* 지붕의 centerLine을 그린다.
|
|
* @param roof
|
|
* @param canvas
|
|
* @param textMode
|
|
*/
|
|
/*const drawCenterLine = (roof, canvas, textMode) => {
|
|
//현재 지붕의 centerLine을 다 지운다.
|
|
canvas
|
|
.getObjects()
|
|
.filter((object) => object.attributes?.roofId === roof.id)
|
|
.filter((line) => line.attributes?.type === 'pitchSizeLine')
|
|
.forEach((line) => canvas.remove(line))
|
|
|
|
const roofLines = roof.lines
|
|
roofLines.forEach((currentRoof) => {
|
|
const hips = roof.innerLines.filter(
|
|
(line) => (currentRoof.x1 === line.x1 && currentRoof.y1 === line.y1) || (currentRoof.x2 === line.x1 && currentRoof.y2 === line.y1),
|
|
)
|
|
|
|
const ridge = roof.innerLines
|
|
.filter((line) => line.name === LINE_TYPE.SUBLINE.RIDGE)
|
|
.filter((line) => {
|
|
if (currentRoof.x1 === currentRoof.x2) {
|
|
if (line.x1 === line.x2) {
|
|
return line
|
|
}
|
|
} else {
|
|
if (line.y1 === line.y2) {
|
|
return line
|
|
}
|
|
}
|
|
})
|
|
.reduce((prev, current) => {
|
|
let currentDistance, prevDistance
|
|
if (Math.sign(currentRoof.x1 - currentRoof.x2) === 0) {
|
|
currentDistance = Math.abs(currentRoof.y1 - current.y1)
|
|
prevDistance = prev ? Math.abs(currentRoof.y1 - prev.y1) : Infinity
|
|
} else {
|
|
currentDistance = Math.abs(currentRoof.y1 - current.y2)
|
|
prevDistance = prev ? Math.abs(currentRoof.y1 - current.y2) : Infinity
|
|
}
|
|
return prevDistance < currentDistance ? prev : current
|
|
}, null)
|
|
|
|
let points = []
|
|
if (hips.length === 2 && Math.abs(hips[0].x2 - hips[1].x2) < 1 && Math.abs(hips[0].y2 - hips[1].y2) < 1) {
|
|
const x1 = (currentRoof.x1 + currentRoof.x2) / 2
|
|
const y1 = (currentRoof.y1 + currentRoof.y2) / 2
|
|
points.push(x1, y1, hips[0].x2, hips[0].y2)
|
|
} else if (hips.length > 1) {
|
|
if (
|
|
((ridge?.x1 === hips[0].x2 && ridge?.y1 === hips[0].y2) || (ridge?.x2 === hips[0].x2 && ridge?.y2 === hips[0].y2)) &&
|
|
((ridge?.x1 === hips[1].x2 && ridge?.y1 === hips[1].y2) || (ridge?.x2 === hips[1].x2 && ridge?.y2 === hips[1].y2))
|
|
) {
|
|
//사각이면 마루와 현재 라인 사이에 길이를 구한다
|
|
if (Math.sign(currentRoof.x1 - currentRoof.x2) === 0) {
|
|
const yPoints = [currentRoof.y1, currentRoof.y2, ridge.y1, ridge.y2].sort((a, b) => a - b)
|
|
const x1 = (currentRoof.x1 + currentRoof.x2) / 2
|
|
const x2 = (ridge.x1 + ridge.x2) / 2
|
|
const y = (yPoints[1] + yPoints[2]) / 2
|
|
if (
|
|
((currentRoof.y1 <= y && y <= currentRoof.y2) || (currentRoof.y2 <= y && y <= currentRoof.y1)) &&
|
|
((ridge.y1 <= y && y <= ridge.y2) || (ridge.y2 <= y && y <= ridge.y1))
|
|
) {
|
|
points.push(x1, y, x2, y)
|
|
}
|
|
} else {
|
|
const xPoints = [currentRoof.x1, currentRoof.x2, ridge.x1, ridge.x2].sort((a, b) => a - b)
|
|
const y1 = (currentRoof.y1 + currentRoof.y2) / 2
|
|
const y2 = (ridge.y1 + ridge.y2) / 2
|
|
const x = (xPoints[1] + xPoints[2]) / 2
|
|
if (
|
|
((currentRoof.x1 <= x && x <= currentRoof.x2) || (currentRoof.x2 <= x && x <= currentRoof.x1)) &&
|
|
((ridge.x1 <= x && x <= ridge.x2) || (ridge.x2 <= x && x <= ridge.x1))
|
|
) {
|
|
points.push(x, y1, x, y2)
|
|
}
|
|
}
|
|
} else {
|
|
if (Math.sign(currentRoof.x1 - currentRoof.x2) === 0) {
|
|
let xPoints = []
|
|
xPoints.push(ridge?.x1, ridge?.x2)
|
|
hips.forEach((hip) => {
|
|
xPoints.push(hip.x2)
|
|
})
|
|
let maxPoint = xPoints.reduce((prev, current) => {
|
|
const currentDistance = Math.abs(currentRoof.x1 - current)
|
|
const prevDistance = prev ? Math.abs(currentRoof.x1 - prev) : 0
|
|
return prevDistance > currentDistance ? prev : current
|
|
}, null)
|
|
|
|
xPoints = xPoints.filter((point) => point === maxPoint)
|
|
|
|
let oppositeLine
|
|
if (xPoints.length === 1) {
|
|
if (ridge?.x1 === xPoints[0] || ridge?.x2 === xPoints[0]) {
|
|
oppositeLine = ridge
|
|
}
|
|
if (oppositeLine === undefined) {
|
|
oppositeLine = hips.find((hip) => hip.x2 === xPoints[0])
|
|
}
|
|
points.push(currentRoof.x1, oppositeLine.y2, oppositeLine.x2, oppositeLine.y2)
|
|
} else if (xPoints.length > 1) {
|
|
xPoints = [...new Set(xPoints)] // 중복제거
|
|
if (ridge?.length > 0) {
|
|
let boolX1 = xPoints.some((x) => x === ridge.x1)
|
|
let boolX2 = xPoints.some((x) => x === ridge.x2)
|
|
if (boolX1 && boolX2) {
|
|
oppositeLine = ridge
|
|
}
|
|
if (oppositeLine) {
|
|
const sortPoints = [currentRoof.y1, currentRoof.y2, oppositeLine.y1, oppositeLine.y2].sort((a, b) => a - b)
|
|
const y = (sortPoints[1] + sortPoints[2]) / 2
|
|
if (
|
|
((currentRoof.y1 <= y && y <= currentRoof.y2) || (currentRoof.y2 <= y && y <= currentRoof.y1)) &&
|
|
((oppositeLine.y1 <= y && y <= oppositeLine.y2) || (oppositeLine.y2 <= y && y <= oppositeLine.y1))
|
|
) {
|
|
points.push(currentRoof.x1, y, oppositeLine.x1, y)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
let yPoints = []
|
|
yPoints.push(ridge?.y1, ridge?.y2)
|
|
hips.forEach((hip) => {
|
|
yPoints.push(hip.y2)
|
|
})
|
|
const maxPoint = yPoints.reduce((prev, current) => {
|
|
const currentDistance = Math.abs(currentRoof.y1 - current)
|
|
const prevDistance = prev ? Math.abs(currentRoof.y1 - prev) : 0
|
|
return prevDistance > currentDistance ? prev : current
|
|
})
|
|
yPoints = yPoints.filter((y) => y === maxPoint)
|
|
|
|
let oppositeLine
|
|
if (yPoints.length === 1) {
|
|
if (ridge?.y1 === yPoints[0] || ridge?.y2 === yPoints[0]) {
|
|
oppositeLine = ridge
|
|
}
|
|
if (oppositeLine === undefined) {
|
|
oppositeLine = hips.find((hip) => hip.y2 === yPoints[0])
|
|
}
|
|
points.push(oppositeLine.x2, currentRoof.y1, oppositeLine.x2, oppositeLine.y2)
|
|
} else if (yPoints.length > 1) {
|
|
let boolY1 = yPoints.some((y) => y === ridge?.y1)
|
|
let boolY2 = yPoints.some((y) => y === ridge?.y2)
|
|
if (boolY1 && boolY2) {
|
|
oppositeLine = ridge
|
|
}
|
|
if (oppositeLine) {
|
|
const sortPoints = [currentRoof.x1, currentRoof.x2, oppositeLine.x1, oppositeLine.x2].sort((a, b) => a - b)
|
|
const x = (sortPoints[1] + sortPoints[2]) / 2
|
|
if (
|
|
((currentRoof.x1 <= x && x <= currentRoof.x2) || (currentRoof.x2 <= x && x <= currentRoof.x1)) &&
|
|
((oppositeLine.x1 <= x && x <= oppositeLine.x2) || (oppositeLine.x2 <= x && x <= oppositeLine.x1))
|
|
) {
|
|
points.push(x, currentRoof.y1, x, oppositeLine.y1)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
if (currentRoof.attributes.type === LINE_TYPE.WALLLINE.JERKINHEAD) {
|
|
const gables = canvas
|
|
.getObjects()
|
|
.filter((object) => object.attributes?.currentRoofId === currentRoof.id && object.name === LINE_TYPE.SUBLINE.GABLE)
|
|
|
|
let x1, y1, x2, y2
|
|
x1 = (currentRoof.x1 + currentRoof.x2) / 2
|
|
y1 = (currentRoof.y1 + currentRoof.y2) / 2
|
|
|
|
if (currentRoof.x1 === currentRoof.x2) {
|
|
const xPoints = []
|
|
gables.forEach((gable) => {
|
|
if (gable.x1 !== x1) {
|
|
xPoints.push(gable.x1)
|
|
} else if (gable.x2 !== x1) {
|
|
xPoints.push(gable.x2)
|
|
}
|
|
})
|
|
x2 = xPoints.reduce((sum, current) => sum + current, 0) / xPoints.length
|
|
y2 = y1
|
|
} else {
|
|
const yPoints = []
|
|
gables.forEach((gable) => {
|
|
if (gable.y1 !== y1) {
|
|
yPoints.push(gable.y1)
|
|
} else if (gable.y2 !== y1) {
|
|
yPoints.push(gable.y2)
|
|
}
|
|
})
|
|
x2 = x1
|
|
y2 = yPoints.reduce((sum, current) => sum + current, 0) / yPoints.length
|
|
}
|
|
points.push(x1, y1, x2, y2)
|
|
}
|
|
}
|
|
if (points?.length > 0) {
|
|
const currentDegree = getDegreeByChon(currentRoof.attributes.pitch)
|
|
const length =
|
|
currentDegree !== undefined && currentDegree > 0
|
|
? calcLineActualSize({ x1: points[0], y1: points[1], x2: points[2], y2: points[3] }, currentDegree)
|
|
: calcLinePlaneSize({ x1: points[0], y1: points[1], x2: points[2], y2: points[3] })
|
|
const pitchSizeLine = new QLine(points, {
|
|
parentId: roof.id,
|
|
stroke: '#000000',
|
|
strokeWidth: 2,
|
|
strokeDashArray: [5, 5],
|
|
selectable: false,
|
|
fontSize: roof.fontSize,
|
|
textMode: textMode,
|
|
attributes: {
|
|
roofId: roof.id,
|
|
type: 'pitchSizeLine',
|
|
planeSize: length,
|
|
actualSize: length,
|
|
},
|
|
})
|
|
if (length > 0) {
|
|
canvas.add(pitchSizeLine)
|
|
canvas.renderAll()
|
|
}
|
|
}
|
|
})
|
|
}*/
|
|
|
|
function arePointsEqual(point1, point2) {
|
|
return Math.abs(point1.x - point2.x) <= 1 && Math.abs(point1.y - point2.y) <= 1
|
|
}
|
|
|
|
export const toGeoJSON = (pointsArray) => {
|
|
// 객체 배열을 GeoJSON 형식의 좌표 배열로 변환
|
|
const coordinates = pointsArray.map((point) => [point.x, point.y])
|
|
|
|
// 닫힌 다각형을 만들기 위해 첫 번째 점을 마지막에 추가
|
|
coordinates.push([pointsArray[0].x, pointsArray[0].y])
|
|
|
|
return coordinates
|
|
}
|
|
|
|
/*export const inPolygon = (polygonPoints, rectPoints) => {
|
|
const polygonCoordinates = toGeoJSON(polygonPoints)
|
|
const rectCoordinates = toGeoJSON(rectPoints)
|
|
|
|
const polygonFeature = turf.polygon([polygonCoordinates])
|
|
const rectFeature = turf.polygon([rectCoordinates])
|
|
|
|
// 사각형의 모든 꼭짓점이 다각형 내부에 있는지 확인
|
|
const allPointsInsidePolygon = rectCoordinates.every((coordinate) => {
|
|
const point = turf.point(coordinate)
|
|
return turf.booleanPointInPolygon(point, polygonFeature)
|
|
})
|
|
|
|
// 다각형의 모든 점이 사각형 내부에 있지 않은지 확인
|
|
const noPolygonPointsInsideRect = polygonCoordinates.every((coordinate) => {
|
|
const point = turf.point(coordinate)
|
|
return !turf.booleanPointInPolygon(point, rectFeature)
|
|
})
|
|
|
|
return allPointsInsidePolygon && noPolygonPointsInsideRect
|
|
}*/
|
|
|
|
/**
|
|
* 포인트를 기준으로 선의 길이를 구한다. 선의 길이는 10을 곱하여 사용한다.
|
|
* @param points
|
|
* @returns number
|
|
*/
|
|
export const calcLinePlaneSize = (points) => {
|
|
const { x1, y1, x2, y2 } = points
|
|
return Big(x1).minus(x2).abs().pow(2).plus(Big(y1).minus(y2).abs().pow(2)).sqrt().times(10).round().toNumber()
|
|
}
|
|
|
|
/**
|
|
* 포인트와 기울기를 기준으로 선의 길이를 구한다.
|
|
* @param points
|
|
* @param degree
|
|
* @returns number
|
|
*/
|
|
export const calcLineActualSize = (points, degree = 0) => {
|
|
const planeSize = calcLinePlaneSize(points)
|
|
const theta = Big(Math.cos(Big(degree).times(Math.PI).div(180)))
|
|
return Big(planeSize).div(theta).round().toNumber()
|
|
}
|
|
|
|
export const createLinesFromPolygon = (points) => {
|
|
const lines = []
|
|
for (let i = 0; i < points.length; i++) {
|
|
const nextIndex = (i + 1) % points.length
|
|
const line = new fabric.Line([points[i].x, points[i].y, points[nextIndex].x, points[nextIndex].y], {
|
|
stroke: 'red',
|
|
strokeWidth: 2,
|
|
selectable: false,
|
|
evented: false,
|
|
})
|
|
lines.push(line)
|
|
}
|
|
return lines
|
|
}
|
|
|
|
/** 포인트 정렬 가장왼쪽, 가장위 부터 */
|
|
/*const getSortedPoint = (points, lines) => {
|
|
const startPoint = points
|
|
.filter((point) => point.x === Math.min(...points.map((point) => point.x)))
|
|
.reduce((prev, curr) => {
|
|
return prev.y < curr.y ? prev : curr
|
|
})
|
|
const sortedPoints = []
|
|
sortedPoints.push(startPoint)
|
|
|
|
let prevPoint = startPoint
|
|
let prevDirection
|
|
|
|
for (let i = 0; i < points.length - 1; i++) {
|
|
const samePoint = []
|
|
if (i === 0) {
|
|
points.forEach((point) => {
|
|
if (point.x === prevPoint.x && point.y > prevPoint.y) {
|
|
samePoint.push({ point, direction: 'bottom', size: Math.abs(prevPoint.y - point.y) })
|
|
}
|
|
})
|
|
if (samePoint.length > 0) {
|
|
samePoint.sort((a, b) => a.size - b.size)
|
|
sortedPoints.push(samePoint[0].point)
|
|
prevDirection = samePoint[0].direction
|
|
prevPoint = samePoint[0].point
|
|
} else {
|
|
points.forEach((point) => {
|
|
if (point.y === prevPoint.y && point.x > prevPoint.x) {
|
|
samePoint.push({ point, direction: 'right', size: Math.abs(prevPoint.x - point.x) })
|
|
}
|
|
})
|
|
if (samePoint.length > 0) {
|
|
samePoint.sort((a, b) => a.size - b.size)
|
|
sortedPoints.push(samePoint[0].point)
|
|
prevDirection = samePoint[0].direction
|
|
prevPoint = samePoint[0].point
|
|
}
|
|
}
|
|
} else {
|
|
points
|
|
.filter((point) => !sortedPoints.includes(point))
|
|
.forEach((point) => {
|
|
if ((prevDirection === 'top' || prevDirection === 'bottom') && point.y === prevPoint.y) {
|
|
const direction = point.x > prevPoint.x ? 'right' : 'left'
|
|
const size = Math.abs(point.x - prevPoint.x)
|
|
samePoint.push({ point, direction, size })
|
|
}
|
|
if ((prevDirection === 'left' || prevDirection === 'right') && point.x === prevPoint.x) {
|
|
const direction = point.y > prevPoint.y ? 'bottom' : 'top'
|
|
const size = Math.abs(point.y - prevPoint.y)
|
|
samePoint.push({ point, direction, size })
|
|
}
|
|
if (Math.round(Math.abs(point.x - prevPoint.x)) === Math.round(Math.abs(point.y - prevPoint.y))) {
|
|
const isLinePoint =
|
|
lines.filter(
|
|
(line) =>
|
|
(line.x1 === prevPoint.x && line.y1 === prevPoint.y && line.x2 === point.x && line.y2 === point.y) ||
|
|
(line.x2 === prevPoint.x && line.y2 === prevPoint.y && line.x1 === point.x && line.y1 === point.y),
|
|
).length > 0
|
|
if (isLinePoint) {
|
|
const direction = prevDirection
|
|
const size = Big(point.x).minus(prevPoint.x).abs().pow(2).plus(Big(point.y).minus(prevPoint.y).abs().pow(2)).sqrt().round().toNumber()
|
|
samePoint.push({ point, direction, size })
|
|
}
|
|
}
|
|
})
|
|
if (samePoint.length > 0) {
|
|
samePoint.sort((a, b) => a.size - b.size)
|
|
sortedPoints.push(samePoint[0].point)
|
|
prevDirection = samePoint[0].direction
|
|
prevPoint = samePoint[0].point
|
|
}
|
|
}
|
|
}
|
|
return sortedPoints
|
|
}*/
|
|
|
|
/*
|
|
const reCalculateSize = (line) => {
|
|
const oldPlaneSize = line.attributes.planeSize
|
|
const oldActualSize = line.attributes.actualSize
|
|
const theta = Big(
|
|
Math.acos(Big(oldPlaneSize).div(oldActualSize === 0 || oldActualSize === '' || oldActualSize === undefined ? oldPlaneSize : oldActualSize)),
|
|
)
|
|
.times(180)
|
|
.div(Math.PI)
|
|
const planeSize = calcLinePlaneSize({
|
|
x1: line.x1,
|
|
y1: line.y1,
|
|
x2: line.x2,
|
|
y2: line.y2,
|
|
})
|
|
const actualSize =
|
|
planeSize === oldActualSize
|
|
? 0
|
|
: calcLineActualSize(
|
|
{
|
|
x1: line.x1,
|
|
y1: line.y1,
|
|
x2: line.x2,
|
|
y2: line.y2,
|
|
},
|
|
theta,
|
|
)
|
|
return { planeSize, actualSize }
|
|
}
|
|
*/
|
|
|
|
/**
|
|
* 직교 다각형(축에 평행한 변들로만 구성된 다각형)의 점들을 시계방향으로 정렬
|
|
* @param points 정렬할 점들의 배열
|
|
* @returns {[sortedPoints]} 정렬된 점들의 배열
|
|
*/
|
|
const getSortedOrthogonalPoints = (points) => {
|
|
if (points.length < 3) return points
|
|
/**
|
|
* @param currentDirection 현재 방향
|
|
* @param nextDirection 다음 방향
|
|
* @param isClockWise 흐름 방향
|
|
* @returns {boolean} 유효한 방향 전환인지 여부
|
|
*/
|
|
const isValidNextDirection = (currentDirection, nextDirection, isClockWise = false) => {
|
|
if (!currentDirection) return true
|
|
|
|
let validTransitions
|
|
if (!isClockWise) {
|
|
// 반 시계방향 진행 규칙
|
|
validTransitions = {
|
|
right: ['up'],
|
|
down: ['right'],
|
|
left: ['down'],
|
|
up: ['left'],
|
|
}
|
|
} else {
|
|
// 시계방향 진행 규칙
|
|
validTransitions = {
|
|
right: ['down'],
|
|
down: ['left'],
|
|
left: ['up'],
|
|
up: ['right'],
|
|
}
|
|
}
|
|
return !validTransitions[currentDirection] || validTransitions[currentDirection].includes(nextDirection)
|
|
}
|
|
|
|
// 시작점: 왼쪽 위 점 (x가 최소이면서 y도 최소인 점)
|
|
const startPoint = points.reduce((min, point) => {
|
|
if (point.x < min.x || (point.x === min.x && point.y < min.y)) {
|
|
return point
|
|
}
|
|
return min
|
|
})
|
|
|
|
const sortedPoints = [startPoint]
|
|
const remainingPoints = points.filter((p) => p !== startPoint)
|
|
|
|
let currentPoint = startPoint
|
|
let currentDirection = null
|
|
|
|
while (remainingPoints.length > 0) {
|
|
let nextPoint = null
|
|
let nextDirection = null
|
|
let minDistance = Infinity
|
|
|
|
// 현재 방향을 고려하여 다음 점 찾기
|
|
for (const point of remainingPoints) {
|
|
const dx = point.x - currentPoint.x
|
|
const dy = point.y - currentPoint.y
|
|
|
|
// 직교 다각형이므로 수직 또는 수평 방향만 고려
|
|
if (Math.abs(dx) < 1e-10 && Math.abs(dy) > 1e-10) {
|
|
// 수직 이동
|
|
const direction = dy > 0 ? 'down' : 'up'
|
|
const distance = Math.abs(dy)
|
|
|
|
if (isValidNextDirection(currentDirection, direction) && distance < minDistance) {
|
|
nextPoint = point
|
|
nextDirection = direction
|
|
minDistance = distance
|
|
}
|
|
} else if (Math.abs(dy) < 1e-10 && Math.abs(dx) > 1e-10) {
|
|
// 수평 이동
|
|
const direction = dx > 0 ? 'right' : 'left'
|
|
const distance = Math.abs(dx)
|
|
|
|
if (isValidNextDirection(currentDirection, direction) && distance < minDistance) {
|
|
nextPoint = point
|
|
nextDirection = direction
|
|
minDistance = distance
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!nextPoint) {
|
|
for (const point of remainingPoints) {
|
|
const dx = point.x - currentPoint.x
|
|
const dy = point.y - currentPoint.y
|
|
|
|
// 직교 다각형이므로 수직 또는 수평 방향만 고려
|
|
if (Math.abs(dx) < 1e-10 && Math.abs(dy) > 1e-10) {
|
|
// 수직 이동
|
|
const direction = dy > 0 ? 'down' : 'up'
|
|
const distance = Math.abs(dy)
|
|
|
|
if (isValidNextDirection(currentDirection, direction, true) && distance < minDistance) {
|
|
nextPoint = point
|
|
nextDirection = direction
|
|
minDistance = distance
|
|
}
|
|
} else if (Math.abs(dy) < 1e-10 && Math.abs(dx) > 1e-10) {
|
|
// 수평 이동
|
|
const direction = dx > 0 ? 'right' : 'left'
|
|
const distance = Math.abs(dx)
|
|
|
|
if (isValidNextDirection(currentDirection, direction, true) && distance < minDistance) {
|
|
nextPoint = point
|
|
nextDirection = direction
|
|
minDistance = distance
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (nextPoint) {
|
|
sortedPoints.push(nextPoint)
|
|
remainingPoints.splice(remainingPoints.indexOf(nextPoint), 1)
|
|
currentPoint = nextPoint
|
|
currentDirection = nextDirection
|
|
} else {
|
|
// 다음 점을 찾을 수 없는 경우, 가장 가까운 점 선택
|
|
const nearestPoint = remainingPoints.reduce((nearest, point) => {
|
|
const currentDist = Math.sqrt(Math.pow(point.x - currentPoint.x, 2) + Math.pow(point.y - currentPoint.y, 2))
|
|
const nearestDist = Math.sqrt(Math.pow(nearest.x - currentPoint.x, 2) + Math.pow(nearest.y - currentPoint.y, 2))
|
|
return currentDist < nearestDist ? point : nearest
|
|
})
|
|
|
|
sortedPoints.push(nearestPoint)
|
|
remainingPoints.splice(remainingPoints.indexOf(nearestPoint), 1)
|
|
currentPoint = nearestPoint
|
|
currentDirection = null
|
|
}
|
|
}
|
|
return sortedPoints
|
|
}
|