qcast-front/src/util/qpolygon-utils.js

13785 lines
587 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 * as turf from '@turf/turf'
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
}
/**
* 용마루 지붕
* @param roofId
* @param canvas
* @param textMode
*/
export const drawEavesRoof = (roofId, canvas, textMode) => {}
/**
* 박공지붕(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, null, currentDegree, currentDegree))
}
} else if (analyze.isVertical) {
//현재라인이 수직선일때
if (isVertical) {
//같은방향 처리
innerLines.push(drawRoofLine(points, canvas, roof, textMode))
} else {
//다른방향 처리
innerLines.push(drawHipLine(points, canvas, roof, textMode, null, 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()
}
const getInwardNormal = (v1, v2, isCCW) => {
const dx = v2.x - v1.x
const dy = v2.y - v1.y
const length = Math.sqrt(dx * dx + dy * dy)
if (length === 0) return { x: 0, y: 0 }
if (isCCW) {
return { x: -dy / length, y: dx / length }
} else {
return { x: dy / length, y: -dx / length }
}
}
const isCounterClockwise = (vertices) => {
let sum = 0
for (let i = 0; i < vertices.length; i++) {
const v1 = vertices[i]
const v2 = vertices[(i + 1) % vertices.length]
sum += (v2.x - v1.x) * (v2.y + v1.y)
}
return sum < 0
}
const isPointInPolygon = (point, polygon) => {
let inside = false
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
const xi = polygon[i].x,
yi = polygon[i].y
const xj = polygon[j].x,
yj = polygon[j].y
const intersect = yi > point.y !== yj > point.y && point.x < ((xj - xi) * (point.y - yi)) / (yj - yi) + xi
if (intersect) inside = !inside
}
return inside
}
const calculateAngleBisector = (prevVertex, currentVertex, nextVertex, polygonVertices) => {
const isCCW = isCounterClockwise(polygonVertices)
// 이전 변의 내향 법선
const norm1 = getInwardNormal(prevVertex, currentVertex, isCCW)
// 다음 변의 내향 법선
const norm2 = getInwardNormal(currentVertex, nextVertex, isCCW)
// 이등분선 계산
let bisectorX = norm1.x + norm2.x
let bisectorY = norm1.y + norm2.y
const length = Math.sqrt(bisectorX * bisectorX + bisectorY * bisectorY)
if (length < 1e-10) {
// 180도인 경우
bisectorX = norm1.x
bisectorY = norm1.y
} else {
bisectorX /= length
bisectorY /= length
}
const testPoint = {
x: currentVertex.x + bisectorX * 0.1,
y: currentVertex.y + bisectorY * 0.1,
}
if (isPointInPolygon(testPoint, polygonVertices)) {
// 방향이 외부를 향하면 반전
bisectorX = -bisectorX
bisectorY = -bisectorY
}
return { x: bisectorX, y: bisectorY }
}
const lineSegmentIntersection = (p1, p2, p3, p4) => {
const x1 = p1.x,
y1 = p1.y
const x2 = p2.x,
y2 = p2.y
const x3 = p3.x,
y3 = p3.y
const x4 = p4.x,
y4 = p4.y
const denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)
if (Math.abs(denom) < EPSILON) {
return null // 평행
}
const t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / denom
const u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / denom
if (t >= 0 && t <= 1 && u >= 0 && u <= 1) {
return {
x: Number((x1 + t * (x2 - x1)).toFixed(1)),
y: Number((y1 + t * (y2 - y1)).toFixed(1)),
}
}
return null
}
/**
* 두 점 사이의 거리 계산
*/
const distanceBetweenPoints = (p1, p2) => {
const dx = p2.x - p1.x
const dy = p2.y - p1.y
return Math.sqrt(dx * dx + dy * dy)
}
const findIntersectionPoint = (startPoint, direction, polygonVertices, divisionLines) => {
const rayEnd = {
x: startPoint.x + direction.x * 10000,
y: startPoint.y + direction.y * 10000,
}
let closestIntersection = null
let minDistance = Infinity
// 다각형 변과의 교점
for (let i = 0; i < polygonVertices.length; i++) {
const v1 = polygonVertices[i]
const v2 = polygonVertices[(i + 1) % polygonVertices.length]
const intersection = lineSegmentIntersection(startPoint, rayEnd, v1, v2)
if (intersection) {
const dist = distanceBetweenPoints(startPoint, intersection)
if (dist > 0.1 && dist < minDistance) {
minDistance = dist
closestIntersection = intersection
}
}
}
// 분할선분과의 교점
for (const divLine of divisionLines) {
const intersection = lineSegmentIntersection(startPoint, rayEnd, { x: divLine.x1, y: divLine.y1 }, { x: divLine.x2, y: divLine.y2 })
if (intersection) {
const dist = distanceBetweenPoints(startPoint, intersection)
if (dist > 0.1 && dist < minDistance) {
minDistance = dist
closestIntersection = intersection
}
}
}
return closestIntersection
}
/**
* 변별로 설정된 지붕을 그린다.
* @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 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
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 currentVector = { x: Math.sign(clamp01(currentLine.x1 - currentLine.x2)), y: Math.sign(clamp01(currentLine.y1 - currentLine.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) {
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, null, prevDegree, prevDegree))
innerLines.push(drawHipLine(nextHipPoint, canvas, roof, textMode, null, 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, null, prevDegree, prevDegree))
}
if (nextGableLength >= 1) {
innerLines.push(drawHipLine(nextGablePoint, canvas, roof, textMode, null, 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:길이
let hipLines = []
ridgeEaves.forEach((currentLine) => {
/*const checkLine = new fabric.Line([currentLine.x1, currentLine.y1, currentLine.x2, currentLine.y2], {
stroke: 'red',
strokeWidth: 4,
parentId: roofId,
name: 'check',
})
canvas.add(checkLine).renderAll()*/
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)
const currentDegree = getDegreeByChon(currentLine.attributes.pitch)
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)
return { forward: isForwardPoints[0].intersect, backward: isBackwardPoints[0].intersect }
}
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) => {
console.log('ridge : ', 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 })
}
/*const checkRidgeStart = new fabric.Circle({ left: ridgePoint.x1, top: ridgePoint.y1, radius: 4, parentId: roofId, name: 'check' })
const checkRidgeLine = new fabric.Line([ridgePoint.x1, ridgePoint.y1, ridgePoint.x2, ridgePoint.y2], {
stroke: 'yellow',
strokeWidth: 4,
parentId: roofId,
name: 'check',
})
canvas.add(checkRidgeLine, checkRidgeStart).renderAll()*/
}
/*const checkLine1 = new fabric.Line([prevHipPoint.x1, prevHipPoint.y1, prevHipPoint.x2, prevHipPoint.y2], {
stroke: 'red',
strokeWidth: 4,
parentId: roofId,
name: 'check',
})
const checkLine2 = new fabric.Line([nextHipPoint.x1, nextHipPoint.y1, nextHipPoint.x2, nextHipPoint.y2], {
stroke: 'blue',
strokeWidth: 4,
parentId: roofId,
name: 'check',
})
canvas.add(checkLine1, checkLine2).renderAll()
*/
canvas
.getObjects()
.filter((o) => o.name === 'check')
.forEach((o) => canvas.remove(o))
canvas.renderAll()
})
console.log('hipedEaves', hipedEaves)
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 hipLength = Math.sqrt((hipPoint.x2 - hipPoint.x1) ** 2 + (hipPoint.y2 - hipPoint.y1) ** 2)
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, null, currentDegree, currentDegree))
pIndexEaves.push(index)
} else if (jointEaves.length === 2) {
console.log('jointEaves : ', jointEaves)
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, null, 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))
console.log('pIndexEaves : ', pIndexEaves)
console.log('jointLines : ', jointLines)
console.log('jointVectors : ', jointVectors)
let dneVector = jointVectors.find((v) => !jointVectors.find((v2) => v2.x === -v.x && v2.y === -v.y))
console.log('dneVector : ', dneVector)
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)
console.log('uniqueLine : ', uniqueLine)
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,
})
}
console.log('=============')
} 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,
}),
)
console.log('proceedEaves :', proceedEaves)
console.log('proceedRidges :', proceedRidges)
//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, null, prevDegree, prevDegree))
}
if (gableLength2 >= 1) {
innerLines.push(drawRoofLine(gablePoint2, canvas, roof, textMode))
}
if (gableLength3 >= 1) {
innerLines.push(drawHipLine(gablePoint3, canvas, roof, textMode, null, 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, null, currentDegree, currentDegree))
innerLines.push(drawHipLine(nextHipPoint, canvas, roof, textMode, null, 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),
}
/* const checkCurrLine = new fabric.Line([currentLine.x1, currentLine.y1, currentLine.x2, currentLine.y2], {
stroke: 'cyan',
strokeWidth: 4,
parentId: roofId,
name: 'check',
})
canvas.add(checkCurrLine)
canvas.renderAll()*/
//좌우 라인이 서로 다른방향일때
if ((prevLineVector.x !== nextLineVector.x || prevLineVector.y !== nextLineVector.y) && checkWallPolygon.inPolygon(inPolygonPoint)) {
const analyze = analyzeLine(currentLine)
const roofLine = analyze.roofLine
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
}
}
console.log('isOverlap : ', isOverlap)
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)) }
}
}
}
let stdDistance = nextDistance <= prevDistance ? prevDistance : nextDistance
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)
}
}
}
}
console.log('stdAdjustVector', stdAdjustVector)
} 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)) }
console.log('stdAddPrevVector', stdAddPrevVector)
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 checkLine = new fabric.Line([stdLine.x1, stdLine.y1, stdLine.x2, stdLine.y2], {
stroke: 'red',
strokeWidth: 4,
parentId: roofId,
name: 'check',
})
const checkLine2 = new fabric.Line(stdPoints, {
stroke: 'green',
strokeWidth: 4,
parentId: roofId,
name: 'check',
})
canvas.add(checkLine, checkLine2).renderAll()
*/
//기준지붕선의 반대쪽선
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)
console.log('stdFindOppVector', stdFindOppVector)
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) })
}
}
})
console.log('oppositeLine', oppositeLine)
if (oppositeLine.length > 0) {
const ridgePoints = []
//지붕선 출발 지점 확인을 위한 기준 포인트
let ridgeStdPoint = { x: (currentLine.x1 + currentLine.x2) / 2, y: (currentLine.y1 + currentLine.y2) / 2 }
oppositeLine.sort((a, b) => a.distance - b.distance)
oppositeLine.forEach((opposite) => {
const oppLine = opposite.line
/*const checkOppLine = new fabric.Line([oppLine.x1, oppLine.y1, oppLine.x2, oppLine.y2], {
stroke: 'yellow',
strokeWidth: 6,
parentId: roofId,
name: 'check',
})
canvas.add(checkOppLine)
canvas.renderAll()*/
const oppIndex = baseLines.findIndex((line) => line === oppLine)
//마주하는 라인의 이전 다음 라인.
const oppPrevLine = baseLines[(oppIndex - 1 + baseLines.length) % baseLines.length]
const oppNextLine = baseLines[(oppIndex + 1) % baseLines.length]
const oppAnalyze = analyzeLine(oppLine)
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
}
// 지붕선 출발 지점과의 거리를 통해 가까운쪽이 start point로 처리.
/*const distRidgeStandard1 = Math.sqrt(Math.pow(ridgeStdPoint.x - ridgePoint[0], 2) + Math.pow(ridgeStdPoint.y - ridgePoint[1], 2))
const distRidgeStandard2 = Math.sqrt(Math.pow(ridgeStdPoint.x - ridgePoint[2], 2) + Math.pow(ridgeStdPoint.y - ridgePoint[3], 2))
if (distRidgeStandard1 > distRidgeStandard2) {
ridgePoint = [ridgePoint[2], ridgePoint[3], ridgePoint[0], ridgePoint[1]]
}*/
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 checkLine1 = new fabric.Line(stdPoints, { stroke: 'red', strokeWidth: 4, parentId: roofId, name: 'check' })
const checkLine2 = new fabric.Line(ridgePoint, { stroke: 'cyan', strokeWidth: 4, parentId: roofId, name: 'check' })
canvas.add(checkLine1, checkLine2).renderAll()*/
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) {
linesAnalysis.push({
start: { x: point[0], y: point[1] },
end: { x: point[2], y: point[3] },
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 checkRoof = new fabric.Line([roofLine.x1, roofLine.y1, roofLine.x2, roofLine.y2], {
stroke: 'red',
strokeWidth: 4,
parentId: roofId,
name: 'check',
})
canvas.add(checkRoof)
const checkLine = new fabric.Line([currentLine.x1, currentLine.y1, currentLine.x2, currentLine.y2], {
stroke: 'blue',
strokeWidth: 4,
parentId: roofId,
name: 'check',
})
canvas.add(checkLine)
canvas.renderAll()*/
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 checkLine = new fabric.Line([line.x1, line.y1, line.x2, line.y2], {
stroke: 'red',
strokeWidth: 4,
parentId: roofId,
name: 'check',
})
canvas.add(checkLine).renderAll()*/
const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
const intersect = edgesIntersection(lineEdge, checkEdge)
/* if (intersect) {
const checkCircle = new fabric.Circle({
left: intersect.x,
top: intersect.y,
radius: 5,
fill: 'blue',
parentId: roofId,
name: 'check',
})
console.log('isPointOnLineNew(line, intersect)', isPointOnLineNew(line, intersect))
canvas.add(checkCircle).renderAll()
}*/
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) }
console.log('vector', vector, 'checkVector', checkVector)
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
console.log('baseLines', baseLines)
console.log('linesAnalysis', linesAnalysis)
while (linesAnalysis.length > 0 && iterations < MAX_ITERATIONS) {
iterations++
/*linesAnalysis.forEach((line) => {
const point = [line.start.x, line.start.y, line.end.x, line.end.y]
const checkLine = new fabric.Line(point, {
stroke: 'red',
strokeWidth: 2,
parentId: roofId,
name: 'check',
})
canvas.add(checkLine).renderAll()
// canvas.remove(checkLine).renderAll()
})*/
const intersections = []
linesAnalysis.forEach((currLine, i) => {
let minDistance = Infinity
let intersectPoint = null
let linePoint = null
let partner = null
/*const checkCLine = new fabric.Line([currLine.start.x, currLine.start.y, currLine.end.x, currLine.end.y], {
stroke: 'red',
strokeWidth: 4,
parentId: roofId,
name: 'check',
})
canvas.add(checkCLine).renderAll()*/
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 checkNLine = new fabric.Line([nextLine.start.x, nextLine.start.y, nextLine.end.x, nextLine.end.y], {
stroke: 'green',
strokeWidth: 4,
parentId: roofId,
name: 'check',
})
canvas.add(checkNLine).renderAll()*/
const intersect = lineIntersection(currLine.start, currLine.end, nextLine.start, nextLine.end, canvas)
if (intersect) {
/*const checkCircle = new fabric.Circle({ left: intersect.x, top: intersect.y, radius: 5, fill: 'blue', parentId: roofId, name: 'check' })
canvas.add(checkCircle).renderAll()*/
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
}
}
// canvas.remove(checkCircle).renderAll()
}
// canvas.remove(checkNLine).renderAll()
})
// canvas.remove(checkCLine).renderAll()
if (intersectPoint) {
intersections.push({ index: i, intersect: intersectPoint, linePoint, partner })
}
})
console.log('intersections', intersections)
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)
console.log('intersectPoints', intersectPoints)
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, null, 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, null, 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, null, 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, null, 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, null, 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, null, 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, null, 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, null, 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]
console.log('gableLine newAnalyze start')
const gableLine = cLine.type === TYPES.GABLE_LINE ? cLine : pLine
gableLine.connectCnt++
/*const checkLine = new fabric.Line([gableLine.start.x, gableLine.start.y, gableLine.end.x, gableLine.end.y], {
stroke: 'red',
strokeWidth: 4,
parentId: roofId,
name: 'check',
})
const checkCircle = new fabric.Circle({
left: intersect.x,
top: intersect.y,
radius: 5,
parentId: roofId,
name: 'check',
})
canvas.add(checkLine, checkCircle)
canvas.renderAll()*/
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,
})
}
}
}
console.log('gableLine newAnalyze end')
} 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)
console.log('uniqueLines', uniqueLines)
if (uniqueLines.length === 2) {
// 두 변의 이등분선 방향 계산
// uniqueLines.sort((a, b) => a - b)
console.log('uniqueLines : ', uniqueLines)
const baseLine1 = baseLines[uniqueLines[0]]
const baseLine2 = baseLines[uniqueLines[1]]
let bisector
console.log('isParallel(baseLine1, baseLine2)', isParallel(baseLine1, baseLine2))
if (isParallel(baseLine1, baseLine2)) {
bisector = getBisectLines(
{ x1: cLine.start.x, x2: cLine.end.x, y1: cLine.start.y, y2: cLine.end.y },
{ x1: pLine.start.x, y1: pLine.start.y, x2: pLine.end.x, y2: pLine.end.y },
)
} 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)
console.log('intersectionsByRoof : ', intersectionsByRoof)
//기 존재하는 analyze 라인이 있으면 newAnalyzeLine을 생성하지 않고 교체한다.
const otherIs = intersections.filter(
(is) => !processed.has(is.index) && almostEqual(is.intersect.x, intersect.x) && almostEqual(is.intersect.y, intersect.y),
)
console.log('otherIs', otherIs)
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
/*const checkPrevLine = new fabric.Line([prevLine.x1, prevLine.y1, prevLine.x2, prevLine.y2], {
stroke: 'yellow',
strokeWidth: 4,
parentId: roofId,
name: 'check',
})
const checkNextLine = new fabric.Line([nextLine.x1, nextLine.y1, nextLine.x2, nextLine.y2], {
stroke: 'red',
strokeWidth: 4,
parentId: roofId,
name: 'check',
})
canvas.add(checkPrevLine, checkNextLine).renderAll()*/
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]
}
}
}
const checkNewLine = new fabric.Line(linePoint, { stroke: 'cyan', strokeWidth: 4, parentId: roofId, name: 'check' })
canvas.add(checkNewLine).renderAll()
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)))
console.log('lineAnalysis: ', linesAnalysis)
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, null, 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, null, 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, null, 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))
console.log('startOnLine, endOnLine: ', startOnLine, endOnLine)
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, null, line.degree, line.degree),
)
}
}
}
}
})
}
//하단 지붕 라인처리
const downRoofLines = []
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) }
//반절마루 생성불가이므로 지붕선 분기를 해야하는지 확인 후 처리.
if (
prevLineVector.x === nextLineVector.x &&
prevLineVector.y === nextLineVector.y &&
(prevLine.attributes.type === LINE_TYPE.WALLLINE.GABLE || nextLine.attributes.type === LINE_TYPE.WALLLINE.GABLE)
) {
downRoofLines.push(index)
}
})
//지붕선에 따라 라인추가 작업 처리.
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 = getDegreeByChon(prevLine.attributes.pitch)
const nextDegree = getDegreeByChon(nextLine.attributes.pitch)
console.log('prevDegree, nextDegree: ', prevDegree, nextDegree)
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, null, prevDegree, prevDegree))
} else {
innerLines.push(drawRoofLine([startPoint.x, startPoint.y, point.x, point.y], canvas, roof, textMode))
}
startPoint = point
}
innerLines.push(drawHipLine([startPoint.x, startPoint.y, currentLine.x2, currentLine.y2], canvas, roof, textMode, null, 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()
//return
/*while (linesAnalysis.length > 0 && iterations < MAX_ITERATIONS) {
iterations++
linesAnalysis.forEach((line) => {
const point = [line.start.x, line.start.y, line.end.x, line.end.y]
const checkLine = new fabric.Line(point, {
stroke: 'red',
strokeWidth: 2,
parentId: roofId,
name: 'check',
})
canvas.add(checkLine)
canvas.renderAll()
})
// 각 가선분의 최단 교점 찾기
const intersections = []
for (let i = 0; i < linesAnalysis.length; i++) {
let minDistance = Infinity //최단거리
let intersectPoint = null //교점 좌표
let partners = new Set() //교점 선분의 index
const lineI = linesAnalysis[i]
const lengthI = Math.sqrt(Math.pow(lineI.end.x - lineI.start.x, 2) + Math.pow(lineI.end.y - lineI.start.y, 2))
console.log('lengthI', lengthI)
const checkLineI = new fabric.Line([lineI.start.x, lineI.start.y, lineI.end.x, lineI.end.y], {
stroke: 'blue',
strokeWidth: 2,
parentId: roofId,
name: 'check',
})
canvas.add(checkLineI).renderAll()
if (lineI.type === TYPES.GABLE_LINE && lineI.connectCnt > 1) continue
if (lengthI > EPSILON) {
const otherLines = linesAnalysis.filter((j) => j !== lineI)
const zeroLines = linesAnalysis.filter((j) => Math.sqrt(Math.pow(j.end.x - j.start.x, 2) + Math.pow(j.end.y - j.start.y, 2)) < EPSILON)
if (otherLines.length === zeroLines.length) {
zeroLines.forEach((j) => {
const jIndex = linesAnalysis.indexOf(j)
if (isPointOnLineNew({ x1: lineI.start.x, y1: lineI.start.y, x2: lineI.end.x, y2: lineI.end.y }, { x: j.start.x, y: j.start.y })) {
const distance = Math.sqrt(Math.pow(j.start.x - lineI.start.x, 2) + Math.pow(j.start.y - lineI.start.y, 2))
if (distance < minDistance) {
minDistance = distance
intersectPoint = { x: j.start.x, y: j.start.y }
partners = new Set([jIndex])
} else if (almostEqual(distance, minDistance)) {
partners.add(jIndex)
}
}
})
if (intersectPoint) {
intersections.push({ index: i, intersectPoint, partners, distance: minDistance })
partners.forEach((j) => {
const p = new Set([i])
intersections.push({ index: j, intersectPoint, partners: p, distance: 0 })
})
continue
}
}
}
for (let j = 0; j < linesAnalysis.length; j++) {
const lineJ = linesAnalysis[j]
if (lineI === lineJ) continue
if (lineI.type === TYPES.GABLE_LINE && lineJ.type === TYPES.GABLE_LINE && i.gableId === lineJ.gableId) continue
if (lineJ.type === TYPES.GABLE_LINE && lineJ.connectCnt > 1) continue
const intersection = lineIntersection(lineI.start, lineI.end, lineJ.start, lineJ.end, canvas)
if (intersection) {
const distance = Math.sqrt((intersection.x - lineI.start.x) ** 2 + (intersection.y - lineI.start.y) ** 2)
const distance2 = Math.sqrt((intersection.x - lineJ.start.x) ** 2 + (intersection.y - lineJ.start.y) ** 2)
if (lineI.type === TYPES.GABLE_LINE && distance < EPSILON) continue
if (distance < minDistance && !almostEqual(distance, minDistance) && !(distance < EPSILON && distance2 < EPSILON)) {
minDistance = distance
intersectPoint = intersection
partners = new Set([j])
} else if (almostEqual(distance, minDistance)) {
partners.add(j)
}
}
}
if (intersectPoint) {
intersections.push({ index: i, intersectPoint, partners, distance: minDistance })
}
canvas.remove(checkLineI).renderAll()
}
// 제일 가까운 교점부터 처리
intersections.sort((a, b) => a.distance - b.distance)
console.log('intersections', intersections)
// 교점에 대한 적합 여부 판단 및 처리.
let newAnalysis = [] //신규 발생 선
const processed = new Set() //처리된 선
for (const { index, intersectPoint, partners } of intersections) {
canvas
.getObjects()
.filter((object) => object.name === 'check')
.forEach((object) => canvas.remove(object))
canvas.renderAll()
let isProceed = false
for (const pIndex of partners) {
console.log('pIndex : ', pIndex)
const check1 = linesAnalysis[index]
const checkLine1 = new fabric.Line([check1.start.x, check1.start.y, check1.end.x, check1.end.y], {
stroke: 'red',
strokeWidth: 2,
parentId: roofId,
name: 'check',
})
const checkCircle1 = new fabric.Circle({ left: check1.start.x, top: check1.start.y, radius: 5, fill: 'red', parentId: roofId, name: 'check' })
const checkCircle2 = new fabric.Circle({ left: check1.end.x, top: check1.end.y, radius: 5, fill: 'green', parentId: roofId, name: 'check' })
canvas.add(checkLine1, checkCircle1, checkCircle2)
canvas.renderAll()
const check2 = linesAnalysis[pIndex]
const checkLine2 = new fabric.Line([check2.start.x, check2.start.y, check2.end.x, check2.end.y], {
stroke: 'green',
strokeWidth: 2,
parentId: roofId,
name: 'check',
})
const checkCircle3 = new fabric.Circle({ left: check2.start.x, top: check2.start.y, radius: 5, fill: 'red', parentId: roofId, name: 'check' })
const checkCircle4 = new fabric.Circle({ left: check2.end.x, top: check2.end.y, radius: 5, fill: 'green', parentId: roofId, name: 'check' })
canvas.add(checkLine2, checkCircle3, checkCircle4)
canvas.renderAll()
console.log('!intersectPoint || processed.has(index)', !intersectPoint, processed.has(index))
//교점이 없거나, 이미 처리된 선분이면 처리하지 않는다.
if (!intersectPoint || processed.has(index)) continue
const partner = intersections.find((p) => p.index === pIndex)
//교점이 없거나, 교점 선분이 없으면 처리하지 않는다.
if (!partner || !partner.intersectPoint) continue
//상호 최단 교점 여부 확인.
if (partner.partners.has(index)) {
const line1 = linesAnalysis[index]
const line2 = linesAnalysis[pIndex]
//좌,우 선 중 공통 선 존재 확인.
const isSameLine = line1.left === line2.left || line1.left === line2.right || line1.right === line2.left || line1.right === line2.right
if (isSameLine) {
// 현재 선이 처리 되었음을 표기
let point1 = [line1.start.x, line1.start.y, intersectPoint.x, intersectPoint.y]
let point2 = [line2.start.x, line2.start.y, intersectPoint.x, intersectPoint.y]
let length1 = Math.sqrt((point1[2] - point1[0]) ** 2 + (point1[3] - point1[1]) ** 2)
let length2 = Math.sqrt((point2[2] - point2[0]) ** 2 + (point2[3] - point2[1]) ** 2)
if (length1 < EPSILON && length2 < EPSILON) continue
isProceed = true
//gable라인과 붙는경우 length가 0에 가까우면 포인트를 뒤집는다.
if (line1.type === TYPES.GABLE_LINE && length2 < EPSILON) {
point2 = [line2.end.x, line2.end.y, intersectPoint.x, intersectPoint.y]
length2 = Math.sqrt((point2[2] - point2[0]) ** 2 + (point2[3] - point2[1]) ** 2)
}
if (line2.type === TYPES.GABLE_LINE && length1 < EPSILON) {
point1 = [line1.end.x, line1.end.y, intersectPoint.x, intersectPoint.y]
length1 = Math.sqrt((point1[2] - point1[0]) ** 2 + (point1[3] - point1[1]) ** 2)
}
if (length1 > 0 && !alreadyPoints(innerLines, point1)) {
if (line1.type === TYPES.HIP) {
innerLines.push(drawHipLine(point1, canvas, roof, textMode, null, line1.degree, line1.degree))
} else if (line1.type === TYPES.RIDGE) {
innerLines.push(drawRidgeLine(point1, canvas, roof, textMode))
} else if (line1.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, null, line1.degree, line1.degree))
} else {
innerLines.push(drawRidgeLine(point1, canvas, roof, textMode))
}
} else if (line1.type === TYPES.GABLE_LINE) {
if (line1.degree > 0) {
innerLines.push(drawHipLine(point1, canvas, roof, textMode, null, line1.degree, line1.degree))
} else {
innerLines.push(drawRidgeLine(point1, canvas, roof, textMode))
}
}
}
if (length2 > 0 && !alreadyPoints(innerLines, point2)) {
if (line2.type === TYPES.HIP) {
innerLines.push(drawHipLine(point2, canvas, roof, textMode, null, line2.degree, line2.degree))
} else if (line2.type === TYPES.RIDGE) {
innerLines.push(drawRidgeLine(point2, canvas, roof, textMode))
} else if (line2.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, null, line2.degree, line2.degree))
} else {
innerLines.push(drawRidgeLine(point2, canvas, roof, textMode))
}
} else if (line2.type === TYPES.GABLE_LINE) {
if (line2.degree > 0) {
innerLines.push(drawHipLine(point2, canvas, roof, textMode, null, line2.degree, line2.degree))
} else {
innerLines.push(drawRidgeLine(point2, canvas, roof, textMode))
}
}
}
if (line1.type === TYPES.GABLE_LINE || line2.type === TYPES.GABLE_LINE) {
console.log('gableLine newAnalyze start')
const gableLine = line1.type === TYPES.GABLE_LINE ? line1 : line2
gableLine.connectCnt++
const checkLine = new fabric.Line([gableLine.start.x, gableLine.start.y, gableLine.end.x, gableLine.end.y], {
stroke: 'red',
strokeWidth: 4,
parentId: roofId,
name: 'check',
})
const checkCircle = new fabric.Circle({
left: intersectPoint.x,
top: intersectPoint.y,
radius: 5,
parentId: roofId,
name: 'check',
})
canvas.add(checkLine, checkCircle)
canvas.renderAll()
const relationBaseLines = [line1.left, line1.right, line2.left, line2.right]
const uniqueBaseLines = [...new Set(relationBaseLines)]
if (uniqueBaseLines.length === 3) {
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: intersectPoint.x, y: intersectPoint.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: intersectPoint.x, y: intersectPoint.y },
end: { x: intersectPoint.x, y: intersectPoint.y },
left: gableLine.gableId,
right: gableLine.gableId,
type: TYPES.HIP,
degree: 0,
})
}
}
}
console.log('gableLine newAnalyze end')
} else {
// 연결점에서 새로운 가선분을 생성
const relationBaseLines = [line1.left, line1.right, line2.left, line2.right]
const uniqueBaseLines = [...new Set(relationBaseLines)]
if (uniqueBaseLines.length === 3) {
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)
console.log('uniqueLines : ', uniqueLines)
const baseLine1 = baseLines[uniqueLines[0]]
const baseLine2 = baseLines[uniqueLines[1]]
const checkLine1 = new fabric.Line([baseLine1.x1, baseLine1.y1, baseLine1.x2, baseLine1.y2], {
stroke: 'yellow',
strokeWidth: 4,
parentId: roofId,
name: 'check',
})
const checkLine2 = new fabric.Line([baseLine2.x1, baseLine2.y1, baseLine2.x2, baseLine2.y2], {
stroke: 'blue',
strokeWidth: 4,
parentId: roofId,
name: 'check',
})
canvas.add(checkLine1, checkLine2)
canvas.renderAll()
let bisector
console.log('isParallel(baseLine1, baseLine2)', isParallel(baseLine1, baseLine2))
if (isParallel(baseLine1, baseLine2)) {
bisector = getBisectLines(
{ x1: line1.start.x, x2: line1.end.x, y1: line1.start.y, y2: line1.end.y },
{ x1: line2.start.x, y1: line2.start.y, x2: line2.end.x, y2: line2.end.y },
)
} else {
//이등분선
bisector = getBisectBaseLines(baseLine1, baseLine2, intersectPoint, canvas)
}
//마주하는 지붕선을 찾는다.
const intersectionsByRoof = []
const checkEdge = {
vertex1: { x: intersectPoint.x, y: intersectPoint.y },
vertex2: { x: intersectPoint.x + bisector.x, y: intersectPoint.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 - intersectPoint.x) ** 2 + (is.y - intersectPoint.y) ** 2)
const isVector = { x: Math.sign(intersectPoint.x - is.x), y: Math.sign(intersectPoint.y - is.y) }
if (isVector.x === checkVector.x && isVector.y === checkVector.y) {
intersectionsByRoof.push({ is, distance })
}
}
})
intersectionsByRoof.sort((a, b) => a.distance - b.distance)
//지붕 선과의 교점이 존재 할때
if (intersectionsByRoof.length > 0) {
let is = intersectionsByRoof[0].is
let linePoint = [intersectPoint.x, intersectPoint.y, is.x, is.y]
const isDiagonal = Math.abs(is.x - intersectPoint.x) >= 1 && Math.abs(is.y - intersectPoint.y) >= 1
const length = Math.sqrt((linePoint[2] - linePoint[0]) ** 2 + (linePoint[3] - linePoint[1]) ** 2)
if (!isDiagonal) {
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
const drivePoint = getRidgeDrivePoint(linePoint, prevLine, nextLine, baseLines)
if (drivePoint !== null) {
const driveLength = Math.sqrt((drivePoint.x - intersectPoint.x) ** 2 + (drivePoint.y - intersectPoint.y) ** 2)
if (driveLength < length) {
linePoint = [intersectPoint.x, intersectPoint.y, drivePoint.x, drivePoint.y]
}
}
}
const checkNewLine = new fabric.Line(linePoint, { stroke: 'cyan', strokeWidth: 4, parentId: roofId, name: 'check' })
canvas.add(checkNewLine).renderAll()
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 ? line1.degree : 0,
})
}
}
canvas
.getObjects()
.filter((object) => object.name === 'check')
.forEach((object) => canvas.remove(object))
canvas.renderAll()
}
}
processed.add(pIndex)
}
}
canvas
.getObjects()
.filter((object) => object.name === 'check')
.forEach((object) => canvas.remove(object))
canvas.renderAll()
}
if (isProceed) {
processed.add(index)
break
}
}
// 처리된 가선분 제외
linesAnalysis = newAnalysis.concat(linesAnalysis.filter((_, index) => !processed.has(index)))
console.log('lineAnalysis: ', linesAnalysis)
canvas
.getObjects()
.filter((object) => object.name === 'check' || object.name === 'checkAnalysis')
.forEach((object) => canvas.remove(object))
canvas.renderAll()
// 새로운 가선분이 없을때 종료
console.log('newAnalysis.length : ', newAnalysis.length)
if (newAnalysis.length === 0) break
}
console.log('lineAnalysis: end ', linesAnalysis)*/
// 가선분 중 처리가 안되어있는 붙어있는 라인에 대한 예외처리.
/* 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, null, 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, null, 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, null, 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))*/
//하단 지붕 라인처리
/* const downRoofLines = []
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) }
//반절마루 생성불가이므로 지붕선 분기를 해야하는지 확인 후 처리.
if (
prevLineVector.x === nextLineVector.x &&
prevLineVector.y === nextLineVector.y &&
(prevLine.attributes.type === LINE_TYPE.WALLLINE.GABLE || nextLine.attributes.type === LINE_TYPE.WALLLINE.GABLE)
) {
downRoofLines.push(index)
}
})*/
/*if (downRoofLines.length > 0) {
downRoofLines.forEach((index) => {
const currentLine = baseLines[index]
// const nextLine = baseLines[(index + 1) % baseLines.length]
// const prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length]
const analyze = analyzeLine(currentLine)
if (analyze.isDiagonal) {
return
}
const roofLine = analyze.roofLine
let roofPoint = [analyze.startPoint.x, analyze.startPoint.y, analyze.endPoint.x, analyze.endPoint.y]
if (analyze.isVertical) {
roofPoint[0] = roofLine.x1
roofPoint[2] = roofLine.x2
}
if (analyze.isHorizontal) {
roofPoint[1] = roofLine.y1
roofPoint[3] = roofLine.y2
}
console.log('analyze: ', analyze)
const findRidgeVector = { x: 0, y: 0 }
if (analyze.isVertical) {
// noinspection JSSuspiciousNameCombination
findRidgeVector.x = analyze.directionVector.y
}
if (analyze.isHorizontal) {
// noinspection JSSuspiciousNameCombination
findRidgeVector.y = analyze.directionVector.x
}
console.log('findRidgeVector: ', findRidgeVector)
innerLines
.filter((line) => {
if (line.name === LINE_TYPE.SUBLINE.RIDGE) {
if (analyze.isVertical) {
const signX = Math.sign(currentLine.x1 - line.x1)
console.log('signX: ', signX)
return signX === findRidgeVector.x
}
if (analyze.isHorizontal) {
const signY = Math.sign(currentLine.y1 - line.y1)
console.log('signY: ', signY)
return signY === findRidgeVector.y
}
return false
}
return false
})
.forEach((line) => {
if (line.name === LINE_TYPE.SUBLINE.RIDGE) {
const checkLine = new fabric.Line([line.x1, line.y1, line.x2, line.y2], {
stroke: 'red',
strokeWidth: 4,
parentId: roofId,
name: 'check',
})
canvas.add(checkLine)
canvas.renderAll()
}
})
/!*const oppositeLines = []
baseLines
.filter((line) => {
//평행한 반대방향 라인인지 확인.
const lineAnalyze = analyzeLine(line)
return (
(analyze.isHorizontal &&
lineAnalyze.directionVector.x !== analyze.directionVector.x &&
lineAnalyze.directionVector.y === analyze.directionVector.y) ||
(analyze.isVertical &&
lineAnalyze.directionVector.x === analyze.directionVector.x &&
lineAnalyze.directionVector.y !== analyze.directionVector.y)
)
})
.filter((line) => {
//라인이 현재라인에 overlap 되거나, 현재 라인을 full overlap하는지 확인.
if (analyze.isHorizontal) {
const currentMinX = Math.min(currentLine.x1, currentLine.x2)
const currentMaxX = Math.max(currentLine.x1, currentLine.x2)
const minX = Math.min(line.x1, line.x2)
const maxX = Math.max(line.x1, line.x2)
//full overlap
if (minX <= currentMinX && maxX >= currentMaxX) {
return true
}
//라인 포인트중 하나라도 현재 라인의 범위에 들어와있으면 overlap으로 판단.
if ((currentMinX <= minX && minX <= currentMaxX) || (currentMinX <= maxX && maxX <= currentMaxX)) {
return true
}
}
if (analyze.isVertical) {
const currentMinY = Math.min(currentLine.y1, currentLine.y2)
const currentMaxY = Math.max(currentLine.y1, currentLine.y2)
const minY = Math.min(line.y1, line.y2)
const maxY = Math.max(line.y1, line.y2)
//full overlap
if (minY <= currentMinY && maxY >= currentMaxY) {
return true
}
//라인 포인트중 하나라도 현재 라인의 범위에 들어와있으면 overlap으로 판단.
if ((currentMinY <= minY && minY <= currentMaxY) || (currentMinY <= maxY && maxY <= currentMaxY)) {
return true
}
}
return false
})
.forEach((line, i) => {
const checkLine = new fabric.Line([line.x1, line.y1, line.x2, line.y2], {
stroke: 'green',
strokeWidth: 4,
parentId: roofId,
name: 'check',
})
canvas.add(checkLine)
canvas.renderAll()
})*!/
// innerLines.push(drawRoofLine(roofPoint, canvas, roof, textMode))
})
}*/
/*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))
console.log('startOnLine, endOnLine: ', startOnLine, endOnLine)
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 (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, null, line.degree, line.degree),
)
}
}
}
})
}*/
//지붕선에 따라 라인추가 작업 처리.
/*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((line) => {
const splitPoint = []
let hasOverlapLine = false
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)
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(line, point) &&
!(almostEqual(line.x1, point.x) && almostEqual(line.y1, point.y)) &&
!(almostEqual(line.x2, point.x) && almostEqual(line.y2, point.y))
) {
const distance = Math.sqrt((point.x - line.x1) ** 2 + (point.y - line.y1) ** 2)
splitPoint.push({ point, distance })
}
})
if (splitPoint.length > 0) {
splitPoint.sort((a, b) => a[1] - b[1])
let startPoint = { x: line.x1, y: line.y1 }
for (let i = 0; i < splitPoint.length; i++) {
const point = splitPoint[i].point
innerLines.push(drawRoofLine([startPoint.x, startPoint.y, point.x, point.y], canvas, roof, textMode))
startPoint = point
}
innerLines.push(drawRoofLine([startPoint.x, startPoint.y, line.x2, line.y2], canvas, roof, textMode))
} else {
innerLines.push(drawRoofLine([line.x1, line.y1, line.x2, line.y2], canvas, roof, textMode))
}
})*/
}
/**
* 선분과 선분의 교점을 구한다.
* @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 points
* @param degree
*/
const getRealDegree = (points, degree) => {
const deltaX = Math.abs(points[2] - points[0])
const deltaY = Math.abs(points[3] - points[1])
if (deltaX < 1 || deltaY < 1) {
return degree
}
const hypotenuse = Math.sqrt(deltaX ** 2 + deltaY ** 2)
const adjacent = Math.sqrt(Math.pow(hypotenuse, 2) / 2)
const height = adjacent * Math.tan((degree * Math.PI) / 180)
return Math.atan2(height, hypotenuse) * (180 / Math.PI)
}
/**
* 두 라인이 평행한지 확인한다.
* @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 roofId
* @param canvas
* @param textMode
*/
export const drawRidgeRoof = (roofId, canvas, textMode) => {
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 hasNonParallelLines = roof.lines.filter((line) => Big(line.x1).minus(Big(line.x2)).gt(1) && Big(line.y1).minus(Big(line.y2)).gt(1))
if (hasNonParallelLines.length > 0) {
return
}
const eavesType = [LINE_TYPE.WALLLINE.EAVES, LINE_TYPE.WALLLINE.HIPANDGABLE]
const gableType = [LINE_TYPE.WALLLINE.GABLE, LINE_TYPE.WALLLINE.JERKINHEAD]
/** 외벽선 */
const baseLines = wall.baseLines.filter((line) => line.attributes.planeSize > 0)
const baseLinePoints = baseLines.map((line) => ({ x: line.x1, y: line.y1 }))
/** 벽취합이 있는 경우 소매가 있다면 지붕 형상을 변경해야 한다. */
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) {
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) {
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 (nextOffset === 0) {
nextRoof.set({ x1: currentLine.x2, y1: currentLine.y2 })
}
roof = reDrawPolygon(roof, canvas)
}
})
/** 모양 판단을 위한 라인 처리.
* 평행한 라인이 나누어져 있는 경우 하나의 선으로 판단 한다.
*/
const drawBaseLines = []
baseLines.forEach((currentLine, index) => {
let nextLine = baseLines[(index + 1) % baseLines.length]
let prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length]
const currentAngle = calculateAngle(currentLine.startPoint, currentLine.endPoint)
const nextAngle = calculateAngle(nextLine.startPoint, nextLine.endPoint)
const prevAngle = calculateAngle(prevLine.startPoint, prevLine.endPoint)
let { x1, y1, x2, y2 } = currentLine
if (currentAngle !== prevAngle || (currentAngle === prevAngle && currentLine.attributes.type !== prevLine.attributes.type)) {
if (currentAngle === nextAngle && currentLine.attributes.type === nextLine.attributes.type) {
let nextIndex = baseLines.findIndex((line) => line === nextLine)
while (nextIndex !== index) {
const checkNextLine = baseLines[(nextIndex + 1 + baseLines.length) % baseLines.length]
const checkAngle = calculateAngle(checkNextLine.startPoint, checkNextLine.endPoint)
if (currentAngle !== checkAngle) {
x2 = checkNextLine.x1
y2 = checkNextLine.y1
break
} else {
nextIndex = nextIndex + 1
}
}
}
drawBaseLines.push({ x1, y1, x2, y2, line: currentLine, size: calcLinePlaneSize({ x1, y1, x2, y2 }) })
}
})
/** baseLine을 기준으로 확인용 polygon 작성 */
const checkWallPolygon = new QPolygon(baseLinePoints, {})
const drawEavesFirstLines = []
const drawEavesSecondLines = []
const drawGableRidgeFirst = []
const drawGableRidgeSecond = []
const drawGablePolygonFirst = []
const drawGablePolygonSecond = []
const drawHipAndGableFirst = []
const drawWallRidgeLine = []
/** 모양을 판단한다. */
drawBaseLines.forEach((currentBaseLine, index) => {
let prevBaseLine = drawBaseLines[(index - 1 + drawBaseLines.length) % drawBaseLines.length]
let nextBaseLine = drawBaseLines[(index + 1) % drawBaseLines.length]
const currentLine = currentBaseLine.line
const prevLine = prevBaseLine.line
const nextLine = nextBaseLine.line
const currentAngle = calculateAngle(currentLine.startPoint, currentLine.endPoint)
const prevAngle = calculateAngle(prevLine.startPoint, prevLine.endPoint)
const nextAngle = calculateAngle(nextLine.startPoint, nextLine.endPoint)
// const checkLine = new fabric.Line([currentLine.x1, currentLine.y1, currentLine.x2, currentLine.y2], {
// stroke: 'red',
// strokeWidth: 4,
// parentId: roofId,
// name: 'checkLine',
// })
// canvas.add(checkLine)
// canvas.renderAll()
const checkScale = Big(10)
const xVector = Big(nextLine.x2).minus(Big(nextLine.x1))
const yVector = Big(nextLine.y2).minus(Big(nextLine.y1))
const currentMidX = Big(currentLine.x1).plus(Big(currentLine.x2)).div(2)
const currentMidY = Big(currentLine.y1).plus(Big(currentLine.y2)).div(2)
const checkPoints = {
x: currentMidX.plus(checkScale.times(Math.sign(xVector.toNumber()))).toNumber(),
y: currentMidY.plus(checkScale.times(Math.sign(yVector.toNumber()))).toNumber(),
}
/** 현재 라인이 처마유형일 경우 */
if (currentLine.attributes?.type === LINE_TYPE.WALLLINE.EAVES) {
if (nextLine.attributes?.type === LINE_TYPE.WALLLINE.EAVES) {
/**
* 이전, 다음 라인이 처마일때 라인의 방향이 반대면 ㄷ 모양으로 판단한다.
*/
if (prevLine.attributes?.type === LINE_TYPE.WALLLINE.EAVES && nextLine.attributes?.type === LINE_TYPE.WALLLINE.EAVES) {
if (Big(prevAngle).minus(Big(nextAngle)).abs().eq(180)) {
/**
* 다음라인 방향에 포인트를 확인해서 역방향 ㄷ 모양인지 판단한다.
* @type {{x: *, y: *}}
*/
if (checkWallPolygon.inPolygon(checkPoints)) {
drawEavesFirstLines.push({ currentBaseLine, prevBaseLine, nextBaseLine })
} else {
drawEavesSecondLines.push({ currentBaseLine, prevBaseLine, nextBaseLine })
}
} else {
drawEavesSecondLines.push({ currentBaseLine, prevBaseLine, nextBaseLine })
}
} else if (eavesType.includes(nextLine.attributes?.type)) {
drawEavesSecondLines.push({ currentBaseLine, prevBaseLine, nextBaseLine })
}
} else if (gableType.includes(nextLine.attributes?.type) && gableType.includes(prevLine.attributes?.type)) {
if (Big(prevAngle).minus(Big(nextAngle)).abs().eq(180)) {
if (checkWallPolygon.inPolygon(checkPoints)) {
drawGablePolygonFirst.push({ currentBaseLine, prevBaseLine, nextBaseLine })
} else {
drawGablePolygonSecond.push({ currentBaseLine, prevBaseLine, nextBaseLine })
}
drawGableRidgeSecond.push({ currentBaseLine, prevBaseLine, nextBaseLine })
} else {
if (currentAngle !== prevAngle && currentAngle !== nextAngle) {
drawGablePolygonSecond.push({ currentBaseLine, prevBaseLine, nextBaseLine })
}
}
}
}
if (gableType.includes(currentLine.attributes?.type)) {
if (
eavesType.includes(prevLine.attributes?.type) &&
eavesType.includes(nextLine.attributes?.type) &&
Big(prevAngle).minus(Big(nextAngle)).abs().eq(180)
) {
if (checkWallPolygon.inPolygon(checkPoints)) {
drawGableRidgeFirst.push({ currentBaseLine, prevBaseLine, nextBaseLine })
} else {
drawGableRidgeSecond.push({ currentBaseLine, prevBaseLine, nextBaseLine })
}
}
}
if (
LINE_TYPE.WALLLINE.HIPANDGABLE === currentLine.attributes?.type &&
eavesType.includes(nextLine.attributes?.type) &&
eavesType.includes(prevLine.attributes?.type)
) {
drawHipAndGableFirst.push({ currentBaseLine, prevBaseLine, nextBaseLine })
}
if (
LINE_TYPE.WALLLINE.WALL === currentLine.attributes?.type &&
eavesType.includes(nextLine.attributes?.type) &&
eavesType.includes(prevLine.attributes?.type)
) {
if (Big(prevAngle).minus(Big(nextAngle)).abs().eq(180) && checkWallPolygon.inPolygon(checkPoints)) {
drawWallRidgeLine.push({ currentBaseLine, prevBaseLine, nextBaseLine })
}
}
})
drawEavesFirstLines.sort((a, b) => a.currentBaseLine.size - b.currentBaseLine.size)
drawGableRidgeFirst.sort((a, b) => a.currentBaseLine.size - b.currentBaseLine.size)
drawGableRidgeSecond.sort((a, b) => a.currentBaseLine.size - b.currentBaseLine.size)
drawWallRidgeLine.sort((a, b) => a.currentBaseLine.size - b.currentBaseLine.size)
/** 추녀마루 */
let baseHipLines = []
/** 용마루 */
let baseRidgeLines = []
/** 박공지붕 마루*/
let baseGableRidgeLines = []
/** 박공지붕 라인*/
let baseGableLines = []
/** 용마루의 갯수*/
let baseRidgeCount = 0
// console.log('drawEavesFirstLines :', drawEavesFirstLines)
// console.log('drawEavesSecondLines :', drawEavesSecondLines)
console.log('drawGableRidgeFirst: ', drawGableRidgeFirst)
console.log('drawGableRidgeSecond:', drawGableRidgeSecond)
console.log('drawGablePolygonFirst :', drawGablePolygonFirst)
console.log('drawGablePolygonSecond :', drawGablePolygonSecond)
// console.log('drawHipAndGableFirst :', drawHipAndGableFirst)
// console.log('drawWallLines :', drawWallRidgeLine)
/** 박공지붕에서 파생되는 마루를 그린다. ㄷ 형태*/
drawGableRidgeFirst.forEach((current) => {
const { currentBaseLine, prevBaseLine, nextBaseLine } = current
const currentLine = currentBaseLine.line
const prevLine = prevBaseLine.line
const nextLine = nextBaseLine.line
if (prevBaseLine.size !== prevLine.attributes.planeSize) {
prevLine.x1 = prevBaseLine.x1
prevLine.y1 = prevBaseLine.y1
prevLine.x2 = prevBaseLine.x2
prevLine.y2 = prevBaseLine.y2
prevLine.setCoords()
}
if (nextBaseLine.size !== nextLine.attributes.planeSize) {
nextLine.x1 = nextBaseLine.x1
nextLine.y1 = nextBaseLine.y1
nextLine.x2 = nextBaseLine.x2
nextLine.y2 = nextBaseLine.y2
nextLine.setCoords()
}
let { x1, x2, y1, y2, size } = currentBaseLine
let beforePrevBaseLine, afterNextBaseLine
/** 이전 라인의 경사 */
const prevDegree = getDegreeByChon(prevLine.attributes.pitch)
/** 다음 라인의 경사 */
const nextDegree = getDegreeByChon(nextLine.attributes.pitch)
/** 이전 라인의 전라인, 다음 라인의 다음라인을 찾는다 */
drawBaseLines.forEach((line, index) => {
if (line === prevBaseLine) {
beforePrevBaseLine = drawBaseLines[(index - 1 + drawBaseLines.length) % drawBaseLines.length]
}
if (line === nextBaseLine) {
afterNextBaseLine = drawBaseLines[(index + 1) % drawBaseLines.length]
}
})
const beforePrevLine = beforePrevBaseLine?.line
const afterNextLine = afterNextBaseLine?.line
/** 각 라인의 흐름 방향을 확인한다. */
const currentAngle = calculateAngle(currentLine.startPoint, currentLine.endPoint)
const prevAngle = calculateAngle(prevLine.startPoint, prevLine.endPoint)
const nextAngle = calculateAngle(nextLine.startPoint, nextLine.endPoint)
const beforePrevAngle = calculateAngle(beforePrevLine.startPoint, beforePrevLine.endPoint)
const afterNextAngle = calculateAngle(afterNextLine.startPoint, afterNextLine.endPoint)
/** 현재라인의 vector*/
const currentVectorX = Math.sign(Big(x2).minus(Big(x1)).toNumber())
const currentVectorY = Math.sign(Big(y2).minus(Big(y1)).toNumber())
/** 이전라인의 vector*/
const prevVectorX = Math.sign(Big(prevLine.x2).minus(Big(prevLine.x1)))
const prevVectorY = Math.sign(Big(prevLine.y2).minus(Big(prevLine.y1)))
/** 다음라인의 vector*/
const nextVectorX = Math.sign(Big(nextLine.x2).minus(Big(nextLine.x1)))
const nextVectorY = Math.sign(Big(nextLine.y2).minus(Big(nextLine.y1)))
/** 현재라인의 기준점*/
let currentMidX = Big(x1).plus(Big(x2)).div(2).plus(Big(prevVectorX).times(currentLine.attributes.offset))
let currentMidY = Big(y1).plus(Big(y2)).div(2).plus(Big(prevVectorY).times(currentLine.attributes.offset))
/** 마루 반대 좌표*/
let oppositeMidX = currentMidX,
oppositeMidY = currentMidY
/** 현재 라인의 지붕 라인을 찾는다. */
const intersectionRoofs = []
let currentRoof
if (currentVectorX === 0) {
const checkEdge = {
vertex1: { x: prevLine.x1, y: currentMidY.toNumber() },
vertex2: { x: currentMidX.toNumber(), y: currentMidY.toNumber() },
}
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.toNumber(), y: prevLine.y1 },
vertex2: { x: currentMidX.toNumber(), y: currentMidY.toNumber() },
}
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
}
/** 현재 라인의 지붕선에서 이전 지붕선, 다음 지붕선으로 향하는 vector*/
const prevRoofVectorX = Math.sign(currentRoof.x2 - currentRoof.x1)
const prevRoofVectorY = Math.sign(currentRoof.y2 - currentRoof.y1)
const nextRoofVectorX = Math.sign(currentRoof.x1 - currentRoof.x2)
const nextRoofVectorY = Math.sign(currentRoof.y1 - currentRoof.y2)
/** 한개의 지붕선을 둘로 나누어서 처리 하는 경우 */
if (prevAngle === beforePrevAngle || nextAngle === afterNextAngle) {
if (currentVectorX === 0) {
const addLength = Big(currentLine.y1).minus(Big(currentLine.y2)).abs().div(2)
const ridgeVector = Math.sign(prevLine.x1 - currentLine.x1)
oppositeMidX = Big(prevLine.x1).plus(Big(addLength).times(ridgeVector))
const ridgeEdge = {
vertex1: { x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() },
vertex2: { x: currentMidX.toNumber(), y: currentMidY.toNumber() },
}
const ridgeVectorX = Math.sign(ridgeEdge.vertex1.x - ridgeEdge.vertex2.x)
roof.lines
.filter((line) => line.x1 === line.x2)
.forEach((line) => {
const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
const is = edgesIntersection(ridgeEdge, lineEdge)
if (is) {
const isVectorX = Math.sign(ridgeEdge.vertex1.x - is.x)
if (
isVectorX === ridgeVectorX &&
((line.x1 <= currentMidX && line.x2 >= currentMidX) || (line.x2 <= currentMidX && line.x1 >= currentMidX))
) {
currentMidX = Big(is.x)
currentMidY = Big(is.y)
}
}
})
} else {
const addLength = Big(currentLine.x1).minus(Big(currentLine.x2)).abs().div(2)
const ridgeVector = Math.sign(prevLine.y1 - currentLine.y1)
oppositeMidY = Big(prevLine.y1).plus(addLength.times(ridgeVector))
const ridgeEdge = {
vertex1: { x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() },
vertex2: { x: currentMidX.toNumber(), y: currentMidY.toNumber() },
}
const ridgeVectorY = Math.sign(ridgeEdge.vertex1.y - ridgeEdge.vertex2.y)
roof.lines
.filter((line) => line.y1 === line.y2)
.forEach((line) => {
const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
const is = edgesIntersection(ridgeEdge, lineEdge)
if (is) {
const isVectorY = Math.sign(ridgeEdge.vertex1.y - is.y)
if (
isVectorY === ridgeVectorY &&
((line.x1 <= currentMidX && line.x2 >= currentMidX) || (line.x2 <= currentMidX && line.x1 >= currentMidX))
) {
currentMidX = Big(is.x)
currentMidY = Big(is.y)
}
}
})
}
const prevHipEdge = {
vertex1: { x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() },
vertex2: { x: prevLine.x1, y: prevLine.y1 },
}
const prevHipVectorX = Math.sign(prevHipEdge.vertex1.x - prevHipEdge.vertex2.x)
const prevHipVectorY = Math.sign(prevHipEdge.vertex1.y - prevHipEdge.vertex2.y)
const prevIsPoints = []
roof.lines
.filter((line) => (Math.sign(prevLine.x1 - prevLine.x2) === 0 ? line.x1 === line.x2 : line.y1 === line.y2))
.forEach((line) => {
const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
const is = edgesIntersection(prevHipEdge, lineEdge)
if (is) {
const isVectorX = Math.sign(prevHipEdge.vertex1.x - is.x)
const isVectorY = Math.sign(prevHipEdge.vertex1.y - is.y)
if (isVectorX === prevHipVectorX && isVectorY === prevHipVectorY) {
const size = Big(prevHipEdge.vertex1.x)
.minus(Big(is.x))
.abs()
.pow(2)
.plus(Big(prevHipEdge.vertex1.y).minus(Big(is.y)).abs().pow(2))
.sqrt()
.toNumber()
prevIsPoints.push({ is, size })
}
}
})
if (prevIsPoints.length > 0) {
const prevIs = prevIsPoints.sort((a, b) => a.size - b.size)[0].is
const prevHipLine = drawHipLine(
[prevIs.x, prevIs.y, oppositeMidX.toNumber(), oppositeMidY.toNumber()],
canvas,
roof,
textMode,
null,
prevDegree,
prevDegree,
)
baseHipLines.push({
x1: prevLine.x1,
y1: prevLine.y1,
x2: oppositeMidX.toNumber(),
y2: oppositeMidY.toNumber(),
line: prevHipLine,
})
}
const nextHipEdge = {
vertex1: { x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() },
vertex2: { x: nextLine.x2, y: nextLine.y2 },
}
const nextHipVectorX = Math.sign(nextHipEdge.vertex1.x - nextHipEdge.vertex2.x)
const nextHipVectorY = Math.sign(nextHipEdge.vertex1.y - nextHipEdge.vertex2.y)
const nextIsPoints = []
roof.lines
.filter((line) => (Math.sign(nextLine.x1 - nextLine.x2) === 0 ? line.x1 === line.x2 : line.y1 === line.y2))
.forEach((line) => {
const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
const is = edgesIntersection(nextHipEdge, lineEdge)
if (is) {
const isVectorX = Math.sign(nextHipEdge.vertex1.x - is.x)
const isVectorY = Math.sign(nextHipEdge.vertex1.y - is.y)
if (isVectorX === nextHipVectorX && isVectorY === nextHipVectorY) {
const size = Big(nextHipEdge.vertex1.x)
.minus(Big(is.x))
.abs()
.pow(2)
.plus(Big(nextHipEdge.vertex1.y).minus(Big(is.y)).abs().pow(2))
.sqrt()
.toNumber()
nextIsPoints.push({ is, size })
}
}
})
if (nextIsPoints.length > 0) {
const nextIs = nextIsPoints.sort((a, b) => a.size - b.size)[0].is
const nextHipLine = drawHipLine(
[nextIs.x, nextIs.y, oppositeMidX.toNumber(), oppositeMidY.toNumber()],
canvas,
roof,
textMode,
null,
nextDegree,
nextDegree,
)
baseHipLines.push({
x1: nextLine.x2,
y1: nextLine.y2,
x2: oppositeMidX.toNumber(),
y2: oppositeMidY.toNumber(),
line: nextHipLine,
})
}
const vectorOppositeX = Math.sign(currentMidX.minus(oppositeMidX))
const vectorOppositeY = Math.sign(currentMidY.minus(oppositeMidY))
/** 반철처 인 경우 처리 */
if (currentLine.attributes.type === LINE_TYPE.WALLLINE.JERKINHEAD) {
const width = Big(currentLine.attributes.width).div(2)
const degree = getDegreeByChon(currentLine.attributes.pitch)
const prevDegree = getDegreeByChon(prevLine.attributes.pitch)
const nextDegree = getDegreeByChon(nextLine.attributes.pitch)
if (vectorOppositeY === 0) {
currentMidX = currentMidX.minus(Big(width).times(vectorOppositeX))
} else {
currentMidY = currentMidY.minus(Big(width).times(vectorOppositeY))
}
/** 현재 라인에서 반철처 부분을 그린다.*/
let firstHipPoint, secondHipPoint, connectHipPoint, firstRoofPoint, secondRoofPoint
if (vectorOppositeY === 0) {
firstHipPoint = [
currentMidX.plus(Big(width).times(vectorOppositeX)).toNumber(),
currentMidY.minus(Big(width).times(prevRoofVectorY)).toNumber(),
currentMidX.toNumber(),
currentMidY.toNumber(),
]
secondHipPoint = [
currentMidX.plus(Big(width).times(vectorOppositeX)).toNumber(),
currentMidY.minus(Big(width).times(nextRoofVectorY)).toNumber(),
currentMidX.toNumber(),
currentMidY.toNumber(),
]
} else {
firstHipPoint = [
currentMidX.plus(Big(width).times(prevRoofVectorX)).toNumber(),
currentMidY.plus(Big(width).times(vectorOppositeY)).toNumber(),
currentMidX.toNumber(),
currentMidY.toNumber(),
]
secondHipPoint = [
currentMidX.plus(Big(width).times(nextRoofVectorX)).toNumber(),
currentMidY.plus(Big(width).times(vectorOppositeY)).toNumber(),
currentMidX.toNumber(),
currentMidY.toNumber(),
]
}
connectHipPoint = [firstHipPoint[0], firstHipPoint[1], secondHipPoint[0], secondHipPoint[1]]
firstRoofPoint = [currentRoof.x1, currentRoof.y1, firstHipPoint[0], firstHipPoint[1]]
secondRoofPoint = [currentRoof.x2, currentRoof.y2, secondHipPoint[0], secondHipPoint[1]]
const firstHipLine = drawHipLine(firstHipPoint, canvas, roof, textMode, null, degree, degree)
const firstRoofLine = drawHipLine(firstRoofPoint, canvas, roof, textMode, null, prevDegree, prevDegree)
const secondHipLine = drawHipLine(secondHipPoint, canvas, roof, textMode, null, degree, degree)
const secondRoofLine = drawHipLine(secondRoofPoint, canvas, roof, textMode, null, nextDegree, nextDegree)
const connectHipLine = drawRoofLine(connectHipPoint, canvas, roof, textMode)
baseHipLines.push({ x1: firstHipLine.x1, y1: firstHipLine.y1, x2: firstHipLine.x2, y2: firstHipLine.y2, line: firstHipLine })
baseHipLines.push({ x1: firstRoofLine.x1, y1: firstRoofLine.y1, x2: firstRoofLine.x2, y2: firstRoofLine.y2, line: firstRoofLine })
baseHipLines.push({ x1: secondHipLine.x1, y1: secondHipLine.y1, x2: secondHipLine.x2, y2: secondHipLine.y2, line: secondHipLine })
baseHipLines.push({ x1: secondRoofLine.x1, y1: secondRoofLine.y1, x2: secondRoofLine.x2, y2: secondRoofLine.y2, line: secondRoofLine })
baseHipLines.push({ x1: connectHipLine.x1, y1: connectHipLine.y1, x2: connectHipLine.x2, y2: connectHipLine.y2, line: connectHipLine })
} else {
const firstHipPoint = [currentRoof.x1, currentRoof.y1, currentMidX.toNumber(), currentMidY.toNumber()]
const secondHipPoint = [currentRoof.x2, currentRoof.y2, currentMidX.toNumber(), currentMidY.toNumber()]
const firstHipLine = drawHipLine(firstHipPoint, canvas, roof, textMode, null, prevDegree, prevDegree)
const secondHipLine = drawHipLine(secondHipPoint, canvas, roof, textMode, null, nextDegree, nextDegree)
baseHipLines.push({ x1: firstHipLine.x1, y1: firstHipLine.y1, x2: firstHipLine.x2, y2: firstHipLine.y2, line: firstHipLine })
baseHipLines.push({ x1: secondHipLine.x1, y1: secondHipLine.y1, x2: secondHipLine.x2, y2: secondHipLine.y2, line: secondHipLine })
}
if (baseRidgeCount < getMaxRidge(baseLines.length)) {
const ridgeLine = drawRidgeLine(
[currentMidX.toNumber(), currentMidY.toNumber(), oppositeMidX.toNumber(), oppositeMidY.toNumber()],
canvas,
roof,
textMode,
)
baseGableRidgeLines.push(ridgeLine)
baseRidgeCount++
}
} else {
if (beforePrevBaseLine === afterNextBaseLine) {
const afterNextMidX = Big(afterNextLine.x1).plus(Big(afterNextLine.x2)).div(2)
const afterNextMidY = Big(afterNextLine.y1).plus(Big(afterNextLine.y2)).div(2)
const vectorMidX = Math.sign(currentMidX.minus(afterNextMidX))
const vectorMidY = Math.sign(currentMidY.minus(afterNextMidY))
let oppositeMidX, oppositeMidY
if (afterNextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) {
const checkSize = currentMidX
.minus(afterNextMidX)
.pow(2)
.plus(currentMidY.minus(afterNextMidY).pow(2))
.sqrt()
.minus(Big(afterNextLine.attributes.planeSize).div(20))
.round(1)
oppositeMidX = currentMidX.plus(checkSize.times(vectorMidX).neg())
oppositeMidY = currentMidY.plus(checkSize.times(vectorMidY).neg())
const xVector1 = Math.sign(Big(oppositeMidX).minus(Big(afterNextLine.x1)).neg().toNumber())
const yVector1 = Math.sign(Big(oppositeMidY).minus(Big(afterNextLine.y1)).neg().toNumber())
const xVector2 = Math.sign(Big(oppositeMidX).minus(Big(afterNextLine.x2)).neg().toNumber())
const yVector2 = Math.sign(Big(oppositeMidY).minus(Big(afterNextLine.y2)).neg().toNumber())
let addOppositeX1 = 0,
addOppositeY1 = 0,
addOppositeX2 = 0,
addOppositeY2 = 0
if (!checkWallPolygon.inPolygon({ x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() })) {
const checkScale = currentMidX.minus(oppositeMidX).pow(2).plus(currentMidY.minus(oppositeMidY).pow(2)).sqrt()
addOppositeX1 = checkScale.times(xVector1).toNumber()
addOppositeY1 = checkScale.times(yVector1).toNumber()
addOppositeX2 = checkScale.times(xVector2).toNumber()
addOppositeY2 = checkScale.times(yVector2).toNumber()
}
let scale1 = Big(afterNextLine.attributes.offset).pow(2).plus(Big(nextLine.attributes.offset).pow(2)).sqrt()
scale1 = scale1.eq(0) ? Big(1) : scale1
let scale2 = Big(afterNextLine.attributes.offset).pow(2).plus(Big(prevLine.attributes.offset).pow(2)).sqrt()
scale2 = scale2.eq(0) ? Big(1) : scale2
const checkHip1 = {
x1: Big(afterNextLine.x1).plus(scale1.times(xVector1)).toNumber(),
y1: Big(afterNextLine.y1).plus(scale1.times(yVector1)).toNumber(),
x2: oppositeMidX.plus(addOppositeX1).toNumber(),
y2: oppositeMidY.plus(addOppositeY1).toNumber(),
}
const checkHip2 = {
x1: Big(afterNextLine.x2).plus(scale2.times(xVector2)).toNumber(),
y1: Big(afterNextLine.y2).plus(scale2.times(yVector2)).toNumber(),
x2: oppositeMidX.plus(addOppositeX2).toNumber(),
y2: oppositeMidY.plus(addOppositeY2).toNumber(),
}
const intersection1 = findRoofIntersection(roof, checkHip1, {
x: oppositeMidX.plus(addOppositeX1),
y: oppositeMidY.plus(addOppositeY1),
})
const intersection2 = findRoofIntersection(roof, checkHip2, {
x: oppositeMidX.plus(addOppositeX2),
y: oppositeMidY.plus(addOppositeY2),
})
const afterNextDegree = getDegreeByChon(afterNextLine.attributes.pitch)
if (intersection1) {
const hipLine = drawHipLine(
[intersection1.intersection.x, intersection1.intersection.y, oppositeMidX.plus(addOppositeX1), oppositeMidY.plus(addOppositeY1)],
canvas,
roof,
textMode,
null,
nextDegree,
afterNextDegree,
)
baseHipLines.push({
x1: afterNextLine.x1,
y1: afterNextLine.y1,
x2: oppositeMidX.plus(addOppositeX1).toNumber(),
y2: oppositeMidY.plus(addOppositeY1).toNumber(),
line: hipLine,
})
}
if (intersection2) {
const hipLine = drawHipLine(
[intersection2.intersection.x, intersection2.intersection.y, oppositeMidX.plus(addOppositeX2), oppositeMidY.plus(addOppositeY2)],
canvas,
roof,
textMode,
null,
prevDegree,
afterNextDegree,
)
baseHipLines.push({
x1: afterNextLine.x2,
y1: afterNextLine.y2,
x2: oppositeMidX.plus(addOppositeX2).toNumber(),
y2: oppositeMidY.plus(addOppositeY2).toNumber(),
line: hipLine,
})
}
} else {
oppositeMidX = Big(afterNextLine.x1).plus(Big(afterNextLine.x2)).div(2).plus(Big(prevVectorX).neg().times(afterNextLine.attributes.offset))
oppositeMidY = Big(afterNextLine.y1).plus(Big(afterNextLine.y2)).div(2).plus(Big(prevVectorY).neg().times(afterNextLine.attributes.offset))
}
const vectorOppositeX = Math.sign(currentMidX.minus(oppositeMidX))
const vectorOppositeY = Math.sign(currentMidY.minus(oppositeMidY))
if (vectorMidX === vectorOppositeX && vectorMidY === vectorOppositeY) {
if (!roof.inPolygon({ x: currentMidX.toNumber(), y: currentMidY.toNumber() })) {
const checkEdge = {
vertex1: { x: currentMidX.toNumber(), y: currentMidY.toNumber() },
vertex2: { x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() },
}
const intersectionPoints = []
roof.lines
.filter((line) => (currentVectorX === 0 ? line.x1 === line.x2 : line.y1 === line.y2))
.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 &&
((line.x1 <= intersection.x && line.x2 >= intersection.x && line.y1 <= intersection.y && line.y2 >= intersection.y) ||
(line.x2 <= intersection.x && line.x1 >= intersection.x && line.y2 <= intersection.y && line.y1 >= intersection.y))
) {
const size = Big(intersection.x)
.minus(currentMidX)
.abs()
.pow(2)
.plus(Big(intersection.y).minus(currentMidY).abs().pow(2))
.sqrt()
.toNumber()
intersectionPoints.push({ intersection, size })
}
})
if (intersectionPoints.length > 0) {
const intersection = intersectionPoints.sort((a, b) => a.size - b.size)[0].intersection
currentMidX = Big(intersection.x)
currentMidY = Big(intersection.y)
}
}
if (!roof.inPolygon({ x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() })) {
const checkEdge = {
vertex1: { x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() },
vertex2: { x: currentMidX.toNumber(), y: currentMidY.toNumber() },
}
const intersectionPoints = []
roof.lines
.filter((line) => (currentVectorX === 0 ? line.x1 === line.x2 : line.y1 === line.y2))
.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 &&
((line.x1 <= intersection.x && line.x2 >= intersection.x && line.y1 <= intersection.y && line.y2 >= intersection.y) ||
(line.x2 <= intersection.x && line.x1 >= intersection.x && line.y2 <= intersection.y && line.y1 >= intersection.y))
) {
const size = Big(intersection.x)
.minus(oppositeMidX)
.abs()
.pow(2)
.plus(Big(intersection.y).minus(oppositeMidY).abs().pow(2))
.sqrt()
.toNumber()
intersectionPoints.push({ intersection, size })
}
})
if (intersectionPoints.length > 0) {
const intersection = intersectionPoints.sort((a, b) => a.size - b.size)[0].intersection
oppositeMidX = Big(intersection.x)
oppositeMidY = Big(intersection.y)
}
}
if (afterNextLine.attributes.type === LINE_TYPE.WALLLINE.HIPANDGABLE) {
const width = afterNextLine.attributes.width
if (vectorOppositeY === 0) {
oppositeMidX = oppositeMidX.plus(Big(width).times(vectorOppositeX))
} else {
oppositeMidY = oppositeMidY.plus(Big(width).times(vectorOppositeY))
}
}
if (afterNextLine.attributes.type === LINE_TYPE.WALLLINE.JERKINHEAD) {
const width = Big(afterNextLine.attributes.width).div(2).toNumber()
if (vectorOppositeY === 0) {
oppositeMidX = oppositeMidX.plus(Big(width).times(vectorOppositeX))
} else {
oppositeMidY = oppositeMidY.plus(Big(width).times(vectorOppositeY))
}
}
/** 반철처 인 경우 처리 */
if (currentLine.attributes.type === LINE_TYPE.WALLLINE.JERKINHEAD) {
const width = Big(currentLine.attributes.width).div(2)
const degree = getDegreeByChon(currentLine.attributes.pitch)
const prevDegree = getDegreeByChon(prevLine.attributes.pitch)
const nextDegree = getDegreeByChon(nextLine.attributes.pitch)
if (vectorMidY === 0) {
currentMidX = currentMidX.minus(Big(width).times(vectorOppositeX))
} else {
currentMidY = currentMidY.minus(Big(width).times(vectorOppositeY))
}
/** 현재 라인에서 반철처 부분을 그린다.*/
let firstHipPoint, secondHipPoint, connectHipPoint, firstRoofPoint, secondRoofPoint
if (vectorMidY === 0) {
firstHipPoint = [
currentMidX.plus(Big(width).times(vectorOppositeX)).toNumber(),
currentMidY.minus(Big(width).times(prevRoofVectorY)).toNumber(),
currentMidX.toNumber(),
currentMidY.toNumber(),
]
secondHipPoint = [
currentMidX.plus(Big(width).times(vectorOppositeX)).toNumber(),
currentMidY.minus(Big(width).times(nextRoofVectorY)).toNumber(),
currentMidX.toNumber(),
currentMidY.toNumber(),
]
} else {
firstHipPoint = [
currentMidX.minus(Big(width).times(prevRoofVectorX)).toNumber(),
currentMidY.plus(Big(width).times(vectorOppositeY)).toNumber(),
currentMidX.toNumber(),
currentMidY.toNumber(),
]
secondHipPoint = [
currentMidX.minus(Big(width).times(nextRoofVectorX)).toNumber(),
currentMidY.plus(Big(width).times(vectorOppositeY)).toNumber(),
currentMidX.toNumber(),
currentMidY.toNumber(),
]
}
connectHipPoint = [firstHipPoint[0], firstHipPoint[1], secondHipPoint[0], secondHipPoint[1]]
firstRoofPoint = [currentRoof.x1, currentRoof.y1, firstHipPoint[0], firstHipPoint[1]]
secondRoofPoint = [currentRoof.x2, currentRoof.y2, secondHipPoint[0], secondHipPoint[1]]
const firstHipLine = drawHipLine(firstHipPoint, canvas, roof, textMode, null, degree, degree)
const firstRoofLine = drawHipLine(firstRoofPoint, canvas, roof, textMode, null, prevDegree, prevDegree)
const secondHipLine = drawHipLine(secondHipPoint, canvas, roof, textMode, null, degree, degree)
const secondRoofLine = drawHipLine(secondRoofPoint, canvas, roof, textMode, null, nextDegree, nextDegree)
const connectHipLine = drawRoofLine(connectHipPoint, canvas, roof, textMode)
baseHipLines.push({ x1: firstHipLine.x1, y1: firstHipLine.y1, x2: firstHipLine.x2, y2: firstHipLine.y2, line: firstHipLine })
baseHipLines.push({ x1: firstRoofLine.x1, y1: firstRoofLine.y1, x2: firstRoofLine.x2, y2: firstRoofLine.y2, line: firstRoofLine })
baseHipLines.push({ x1: secondHipLine.x1, y1: secondHipLine.y1, x2: secondHipLine.x2, y2: secondHipLine.y2, line: secondHipLine })
baseHipLines.push({ x1: secondRoofLine.x1, y1: secondRoofLine.y1, x2: secondRoofLine.x2, y2: secondRoofLine.y2, line: secondRoofLine })
baseHipLines.push({ x1: connectHipLine.x1, y1: connectHipLine.y1, x2: connectHipLine.x2, y2: connectHipLine.y2, line: connectHipLine })
} else {
const firstHipPoint = [currentRoof.x1, currentRoof.y1, currentMidX.toNumber(), currentMidY.toNumber()]
const secondHipPoint = [currentRoof.x2, currentRoof.y2, currentMidX.toNumber(), currentMidY.toNumber()]
const firstHipLine = drawHipLine(firstHipPoint, canvas, roof, textMode, null, prevDegree, prevDegree)
const secondHipLine = drawHipLine(secondHipPoint, canvas, roof, textMode, null, nextDegree, nextDegree)
baseHipLines.push({ x1: firstHipLine.x1, y1: firstHipLine.y1, x2: firstHipLine.x2, y2: firstHipLine.y2, line: firstHipLine })
baseHipLines.push({ x1: secondHipLine.x1, y1: secondHipLine.y1, x2: secondHipLine.x2, y2: secondHipLine.y2, line: secondHipLine })
}
if (baseRidgeCount < getMaxRidge(baseLines.length)) {
const ridge = drawRidgeLine(
[currentMidX.toNumber(), currentMidY.toNumber(), oppositeMidX.toNumber(), oppositeMidY.toNumber()],
canvas,
roof,
textMode,
)
baseGableRidgeLines.push(ridge)
baseRidgeCount++
}
}
} else {
const vectorMidX = Math.sign(Big(nextLine.x2).minus(nextLine.x1))
const vectorMidY = Math.sign(Big(nextLine.y2).minus(nextLine.y1))
let prevOppositeMidX, prevOppositeMidY, nextOppositeMidX, nextOppositeMidY
const beforePrevOffset =
currentAngle === beforePrevAngle
? Big(beforePrevLine.attributes.offset)
: Big(beforePrevLine.attributes.offset).plus(currentLine.attributes.offset)
const afterNextOffset =
currentAngle === afterNextAngle
? Big(afterNextLine.attributes.offset)
: Big(afterNextLine.attributes.offset).plus(currentLine.attributes.offset)
const prevSize = Big(calcLinePlaneSize(prevLine)).div(10)
const nextSize = Big(calcLinePlaneSize(nextLine)).div(10)
let prevHipCoords, nextHipCoords
/** 다음 라인이 그 다음 라인과의 사이에 추녀마루가 존재 하는지 확인. 처마-처마 인 경우 추녀마루*/
if (eavesType.includes(afterNextLine.attributes?.type)) {
/** 현재 라인의 길이를 기준으로 추녀 마루의 길이를 삼각함수를 사용하여 판단한다.*/
let hipLength = Big(size).div(10).div(2).pow(2).plus(Big(size).div(10).div(2).pow(2)).sqrt()
const nextHalfVector = getHalfAngleVector(nextLine, afterNextLine)
let nextHipVector = { x: nextHalfVector.x, y: nextHalfVector.y }
/** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/
const nextCheckPoint = {
x: Big(nextLine.x2).plus(Big(nextHalfVector.x).times(10)),
y: Big(nextLine.y2).plus(Big(nextHalfVector.y).times(10)),
}
if (!checkWallPolygon.inPolygon(nextCheckPoint)) {
nextHipVector = { x: Big(nextHipVector.x).neg().toNumber(), y: Big(nextHipVector.y).neg().toNumber() }
}
const nextEndPoint = {
x: Big(nextLine.x2).plus(Big(nextHipVector.x).times(hipLength)),
y: Big(nextLine.y2).plus(Big(nextHipVector.y).times(hipLength)),
}
let ridgeEdge = {
vertex1: { x: currentMidX.toNumber(), y: currentMidY.toNumber() },
vertex2: {
x: currentMidX.plus(Big(nextVectorX).times(nextBaseLine.size)).toNumber(),
y: currentMidY.plus(Big(nextVectorY).times(nextBaseLine.size)).toNumber(),
},
}
let hipEdge = {
vertex1: { x: nextLine.x2, y: nextLine.y2 },
vertex2: { x: nextEndPoint.x, y: nextEndPoint.y },
}
let intersection = edgesIntersection(ridgeEdge, hipEdge)
if (intersection) {
nextHipCoords = { x1: nextLine.x2, y1: nextLine.y2, x2: intersection.x, y2: intersection.y }
nextOppositeMidY = Big(intersection.y)
nextOppositeMidX = Big(intersection.x)
}
} else {
if (vectorMidX === 0) {
nextOppositeMidY = currentMidY.plus(nextSize.plus(afterNextOffset).times(vectorMidY))
nextOppositeMidX = currentMidX
} else {
nextOppositeMidX = currentMidX.plus(nextSize.plus(afterNextOffset).times(vectorMidX))
nextOppositeMidY = currentMidY
}
}
/** 이전 라인이 그 이전 라인과의 사이에 추녀마루가 존재 하는지 확인. 처마-처마 인 경우 추녀마루*/
if (eavesType.includes(beforePrevLine.attributes?.type)) {
/** 현재 라인의 길이를 기준으로 추녀 마루의 길이를 삼각함수를 사용하여 판단한다.*/
let hipLength = Big(size).div(10).div(2).pow(2).plus(Big(size).div(10).div(2).pow(2)).sqrt()
const prevHalfVector = getHalfAngleVector(prevLine, beforePrevLine)
let prevHipVector = { x: prevHalfVector.x, y: prevHalfVector.y }
/** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/
const prevCheckPoint = {
x: Big(prevLine.x1).plus(Big(prevHalfVector.x).times(10)),
y: Big(prevLine.y1).plus(Big(prevHalfVector.y).times(10)),
}
if (!checkWallPolygon.inPolygon(prevCheckPoint)) {
prevHipVector = { x: Big(prevHipVector.x).neg().toNumber(), y: Big(prevHipVector.y).neg().toNumber() }
}
const prevEndPoint = {
x: Big(prevLine.x1).plus(Big(prevHipVector.x).times(hipLength)),
y: Big(prevLine.y1).plus(Big(prevHipVector.y).times(hipLength)),
}
let ridgeEdge = {
vertex1: { x: currentMidX.toNumber(), y: currentMidY.toNumber() },
vertex2: {
x: currentMidX.plus(Big(prevVectorX).times(prevBaseLine.size)).toNumber(),
y: currentMidY.plus(Big(prevVectorY).times(prevBaseLine.size)).toNumber(),
},
}
let hipEdge = {
vertex1: { x: prevLine.x1, y: prevLine.y1 },
vertex2: { x: prevEndPoint.x, y: prevEndPoint.y },
}
let intersection = edgesIntersection(ridgeEdge, hipEdge)
if (intersection) {
prevHipCoords = { x1: prevLine.x1, y1: prevLine.y1, x2: intersection.x, y2: intersection.y }
prevOppositeMidY = Big(intersection.y)
prevOppositeMidX = Big(intersection.x)
}
} else {
if (vectorMidX === 0) {
prevOppositeMidY = currentMidY.plus(prevSize.plus(beforePrevOffset).times(vectorMidY))
prevOppositeMidX = currentMidX
} else {
prevOppositeMidX = currentMidX.plus(prevSize.plus(beforePrevOffset).times(vectorMidX))
prevOppositeMidY = currentMidY
}
}
const currentMidEdge = {
vertex1: { x: currentMidX.toNumber(), y: currentMidY.toNumber() },
vertex2: {
x: currentVectorX === 0 ? nextLine.x2 : currentMidX.toNumber(),
y: currentVectorX === 0 ? currentMidY.toNumber() : nextLine.y2,
},
}
let oppositeLines = []
drawBaseLines
.filter((line, index) => {
const currentLine = line.line
const nextLine = drawBaseLines[(index + 1) % drawBaseLines.length].line
const prevLine = drawBaseLines[(index - 1 + drawBaseLines.length) % drawBaseLines.length].line
const angle = calculateAngle(currentLine.startPoint, currentLine.endPoint)
const prevAngle = calculateAngle(prevLine.startPoint, prevLine.endPoint)
const nextAngle = calculateAngle(nextLine.startPoint, nextLine.endPoint)
if (angle === prevAngle || angle === nextAngle) {
const sameAngleLine = angle === prevAngle ? prevLine : nextLine
if (gableType.includes(currentLine.attributes.type) && !gableType.includes(sameAngleLine.attributes.type)) {
switch (currentAngle) {
case 90:
return angle === -90
case -90:
return angle === 90
case 0:
return angle === 180
case 180:
return angle === 0
}
}
}
return false
})
.forEach((line) => {
const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
const intersection = edgesIntersection(currentMidEdge, lineEdge)
if (intersection) {
if (line.x1 <= intersection.x && line.x2 >= intersection.x && line.y1 <= intersection.y && line.y2 >= intersection.y) {
oppositeLines.push({
line,
intersection,
size: Big(intersection.x)
.minus(currentMidX)
.abs()
.pow(2)
.plus(Big(intersection.y).minus(currentMidY).abs().pow(2))
.sqrt()
.toNumber(),
})
}
}
})
if (oppositeLines.length > 0) {
const oppositePoint = oppositeLines.sort((a, b) => a.size - b.size)[0].intersection
const checkEdge = {
vertex1: { x: currentMidX.toNumber(), y: currentMidY.toNumber() },
vertex2: { x: oppositePoint.x, y: oppositePoint.y },
}
const oppositeRoofPoints = []
roof.lines
.filter((line) => {
const angle = calculateAngle(line.startPoint, line.endPoint)
switch (currentAngle) {
case 90:
return angle === -90
case -90:
return angle === 90
case 0:
return angle === 180
case 180:
return angle === 0
}
})
.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 &&
((line.x1 <= intersection.x && line.x2 >= intersection.x && line.y1 <= intersection.y && line.y2 >= intersection.y) ||
(line.x1 >= intersection.x && line.x2 <= intersection.x && line.y1 >= intersection.y && line.y2 <= intersection.y))
) {
oppositeRoofPoints.push({
line,
intersection,
size: Big(intersection.x)
.minus(currentMidX.toNumber())
.abs()
.pow(2)
.plus(Big(intersection.y).minus(currentMidY.toNumber()).abs().pow(2))
.sqrt()
.toNumber(),
})
}
})
const oppositeRoofPoint = oppositeRoofPoints.sort((a, b) => a.size - b.size)[0].intersection
oppositeMidX = Big(oppositeRoofPoint.x)
oppositeMidY = Big(oppositeRoofPoint.y)
const currentRoofPoints = []
roof.lines
.filter((line) => {
const angle = calculateAngle(line.startPoint, line.endPoint)
return currentAngle === angle
})
.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 &&
((line.x1 <= intersection.x && line.x2 >= intersection.x && line.y1 <= intersection.y && line.y2 >= intersection.y) ||
(line.x1 >= intersection.x && line.x2 <= intersection.x && line.y1 >= intersection.y && line.y2 <= intersection.y))
) {
currentRoofPoints.push({
line,
intersection,
size: Big(intersection.x)
.minus(currentMidX.toNumber())
.abs()
.pow(2)
.plus(Big(intersection.y).minus(currentMidY.toNumber()).abs().pow(2))
.sqrt()
.toNumber(),
})
}
})
const currentRoofPoint = currentRoofPoints.sort((a, b) => a.size - b.size)[0].intersection
currentMidX = Big(currentRoofPoint.x)
currentMidY = Big(currentRoofPoint.y)
} else {
const checkPrevSize = currentMidX.minus(prevOppositeMidX).pow(2).plus(currentMidY.minus(prevOppositeMidY).pow(2)).sqrt()
const checkNextSize = currentMidX.minus(nextOppositeMidX).pow(2).plus(currentMidY.minus(nextOppositeMidY).pow(2)).sqrt()
/** 두 포인트 중에 current와 가까운 포인트를 사용*/
if (checkPrevSize.gt(checkNextSize)) {
if (nextHipCoords) {
let intersectPoints = []
const hipEdge = {
vertex1: { x: nextHipCoords.x2, y: nextHipCoords.y2 },
vertex2: { x: nextHipCoords.x1, y: nextHipCoords.y1 },
}
/** 외벽선에서 라인 겹치는 경우에 대한 확인*/
roof.lines.forEach((line) => {
const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
const intersection = edgesIntersection(hipEdge, lineEdge)
if (
intersection &&
Math.sign(nextHipCoords.x2 - nextHipCoords.x1) === Math.sign(nextHipCoords.x2 - intersection.x) &&
Math.sign(nextHipCoords.y2 - nextHipCoords.y1) === Math.sign(nextHipCoords.y2 - intersection.y)
) {
const intersectEdge = {
vertex1: { x: nextHipCoords.x2, y: nextHipCoords.y2 },
vertex2: { x: intersection.x, y: intersection.y },
}
const is = edgesIntersection(intersectEdge, lineEdge)
if (!is.isIntersectionOutside) {
const intersectSize = Big(nextHipCoords.x2)
.minus(Big(intersection.x))
.pow(2)
.plus(Big(nextHipCoords.y2).minus(Big(intersection.y)).pow(2))
.abs()
.sqrt()
.toNumber()
intersectPoints.push({
intersection,
size: intersectSize,
line,
})
}
}
})
const intersect = intersectPoints.sort((a, b) => a.size - b.size)[0]
if (intersect) {
const degree = getDegreeByChon(intersect.line.attributes.pitch)
const hipLine = drawHipLine(
[intersect.intersection.x, intersect.intersection.y, nextOppositeMidX.toNumber(), nextOppositeMidY.toNumber()],
canvas,
roof,
textMode,
null,
degree,
degree,
)
baseHipLines.push({
x1: nextHipCoords.x1,
y1: nextHipCoords.y1,
x2: nextHipCoords.x2,
y2: nextHipCoords.y2,
line: hipLine,
})
}
}
oppositeMidY = nextOppositeMidY
oppositeMidX = nextOppositeMidX
} else {
if (prevHipCoords) {
let intersectPoints = []
const hipEdge = {
vertex1: { x: prevHipCoords.x2, y: prevHipCoords.y2 },
vertex2: { x: prevHipCoords.x1, y: prevHipCoords.y1 },
}
/** 외벽선에서 라인 겹치는 경우에 대한 확인*/
roof.lines.forEach((line) => {
const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
const intersection = edgesIntersection(hipEdge, lineEdge)
if (
intersection &&
Math.sign(prevHipCoords.x2 - prevHipCoords.x1) === Math.sign(prevHipCoords.x2 - intersection.x) &&
Math.sign(prevHipCoords.y2 - prevHipCoords.y1) === Math.sign(prevHipCoords.y2 - intersection.y)
) {
const intersectEdge = {
vertex1: { x: prevHipCoords.x2, y: prevHipCoords.y2 },
vertex2: { x: intersection.x, y: intersection.y },
}
const is = edgesIntersection(intersectEdge, lineEdge)
if (!is.isIntersectionOutside) {
const intersectSize = Big(prevHipCoords.x2)
.minus(Big(intersection.x))
.pow(2)
.plus(Big(prevHipCoords.y2).minus(Big(intersection.y)).pow(2))
.abs()
.sqrt()
.toNumber()
intersectPoints.push({
intersection,
size: intersectSize,
line,
})
}
}
})
const intersect = intersectPoints.sort((a, b) => a.size - b.size)[0]
if (intersect) {
const degree = getDegreeByChon(intersect.line.attributes.pitch)
const hipLine = drawHipLine(
[intersect.intersection.x, intersect.intersection.y, prevOppositeMidX.toNumber(), prevOppositeMidY.toNumber()],
canvas,
roof,
textMode,
null,
degree,
degree,
)
baseHipLines.push({
x1: prevHipCoords.x1,
y1: prevHipCoords.y1,
x2: prevHipCoords.x2,
y2: prevHipCoords.y2,
line: hipLine,
})
}
}
oppositeMidY = prevOppositeMidY
oppositeMidX = prevOppositeMidX
}
}
/** 포인트가 지붕 밖에 있는 경우 조정 */
if (!roof.inPolygon({ x: currentMidX.toNumber(), y: currentMidY.toNumber() })) {
const checkEdge = {
vertex1: { x: currentMidX.toNumber(), y: currentMidY.toNumber() },
vertex2: { x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() },
}
const intersectionPoints = []
roof.lines
.filter((line) => (currentVectorX === 0 ? line.x1 === line.x2 : line.y1 === line.y2))
.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 &&
((line.x1 <= intersection.x && line.x2 >= intersection.x && line.y1 <= intersection.y && line.y2 >= intersection.y) ||
(line.x2 <= intersection.x && line.x1 >= intersection.x && line.y2 <= intersection.y && line.y1 >= intersection.y))
) {
const size = Big(intersection.x)
.minus(currentMidX)
.abs()
.pow(2)
.plus(Big(intersection.y).minus(currentMidY).abs().pow(2))
.sqrt()
.toNumber()
intersectionPoints.push({ intersection, size })
}
})
if (intersectionPoints.length > 0) {
const intersection = intersectionPoints.sort((a, b) => a.size - b.size)[0].intersection
currentMidX = Big(intersection.x)
currentMidY = Big(intersection.y)
}
}
if (!roof.inPolygon({ x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() })) {
const checkEdge = {
vertex1: { x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() },
vertex2: { x: currentMidX.toNumber(), y: currentMidY.toNumber() },
}
const intersectionPoints = []
roof.lines
.filter((line) => (currentVectorX === 0 ? line.x1 === line.x2 : line.y1 === line.y2))
.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 &&
((line.x1 <= intersection.x && line.x2 >= intersection.x && line.y1 <= intersection.y && line.y2 >= intersection.y) ||
(line.x2 <= intersection.x && line.x1 >= intersection.x && line.y2 <= intersection.y && line.y1 >= intersection.y))
) {
const size = Big(intersection.x)
.minus(oppositeMidX)
.abs()
.pow(2)
.plus(Big(intersection.y).minus(oppositeMidY).abs().pow(2))
.sqrt()
.toNumber()
intersectionPoints.push({ intersection, size })
}
})
if (intersectionPoints.length > 0) {
const intersection = intersectionPoints.sort((a, b) => a.size - b.size)[0].intersection
oppositeMidX = Big(intersection.x)
oppositeMidY = Big(intersection.y)
}
}
const vectorOppositeX = Math.sign(currentMidX.minus(oppositeMidX))
const vectorOppositeY = Math.sign(currentMidY.minus(oppositeMidY))
/** 반철처 인 경우 처리 */
if (currentLine.attributes.type === LINE_TYPE.WALLLINE.JERKINHEAD) {
const width = Big(currentLine.attributes.width).div(2)
const degree = getDegreeByChon(currentLine.attributes.pitch)
const prevDegree = getDegreeByChon(prevLine.attributes.pitch)
const nextDegree = getDegreeByChon(nextLine.attributes.pitch)
if (vectorOppositeY === 0) {
currentMidX = currentMidX.minus(Big(width).times(vectorOppositeX))
} else {
currentMidY = currentMidY.minus(Big(width).times(vectorOppositeY))
}
/** 현재 라인에서 반철처 부분을 그린다.*/
let firstHipPoint, secondHipPoint, connectHipPoint, firstRoofPoint, secondRoofPoint
if (vectorOppositeY === 0) {
firstHipPoint = [
currentMidX.plus(Big(width).times(vectorOppositeX)).toNumber(),
currentMidY.minus(Big(width).times(prevRoofVectorY)).toNumber(),
currentMidX.toNumber(),
currentMidY.toNumber(),
]
secondHipPoint = [
currentMidX.plus(Big(width).times(vectorOppositeX)).toNumber(),
currentMidY.minus(Big(width).times(nextRoofVectorY)).toNumber(),
currentMidX.toNumber(),
currentMidY.toNumber(),
]
} else {
firstHipPoint = [
currentMidX.minus(Big(width).times(prevRoofVectorX)).toNumber(),
currentMidY.plus(Big(width).times(vectorOppositeY)).toNumber(),
currentMidX.toNumber(),
currentMidY.toNumber(),
]
secondHipPoint = [
currentMidX.minus(Big(width).times(nextRoofVectorX)).toNumber(),
currentMidY.plus(Big(width).times(vectorOppositeY)).toNumber(),
currentMidX.toNumber(),
currentMidY.toNumber(),
]
}
connectHipPoint = [firstHipPoint[0], firstHipPoint[1], secondHipPoint[0], secondHipPoint[1]]
firstRoofPoint = [currentRoof.x1, currentRoof.y1, firstHipPoint[0], firstHipPoint[1]]
secondRoofPoint = [currentRoof.x2, currentRoof.y2, secondHipPoint[0], secondHipPoint[1]]
const firstHipLine = drawHipLine(firstHipPoint, canvas, roof, textMode, null, degree, degree)
const firstRoofLine = drawHipLine(firstRoofPoint, canvas, roof, textMode, null, prevDegree, prevDegree)
const secondHipLine = drawHipLine(secondHipPoint, canvas, roof, textMode, null, degree, degree)
const secondRoofLine = drawHipLine(secondRoofPoint, canvas, roof, textMode, null, nextDegree, nextDegree)
const connectHipLine = drawRoofLine(connectHipPoint, canvas, roof, textMode)
baseHipLines.push({ x1: firstHipLine.x1, y1: firstHipLine.y1, x2: firstHipLine.x2, y2: firstHipLine.y2, line: firstHipLine })
baseHipLines.push({ x1: firstRoofLine.x1, y1: firstRoofLine.y1, x2: firstRoofLine.x2, y2: firstRoofLine.y2, line: firstRoofLine })
baseHipLines.push({ x1: secondHipLine.x1, y1: secondHipLine.y1, x2: secondHipLine.x2, y2: secondHipLine.y2, line: secondHipLine })
baseHipLines.push({ x1: secondRoofLine.x1, y1: secondRoofLine.y1, x2: secondRoofLine.x2, y2: secondRoofLine.y2, line: secondRoofLine })
baseHipLines.push({ x1: connectHipLine.x1, y1: connectHipLine.y1, x2: connectHipLine.x2, y2: connectHipLine.y2, line: connectHipLine })
} else {
const firstHipPoint = [currentRoof.x1, currentRoof.y1, currentMidX.toNumber(), currentMidY.toNumber()]
const secondHipPoint = [currentRoof.x2, currentRoof.y2, currentMidX.toNumber(), currentMidY.toNumber()]
const firstHipLine = drawHipLine(firstHipPoint, canvas, roof, textMode, null, prevDegree, prevDegree)
const secondHipLine = drawHipLine(secondHipPoint, canvas, roof, textMode, null, nextDegree, nextDegree)
baseHipLines.push({ x1: firstHipLine.x1, y1: firstHipLine.y1, x2: firstHipLine.x2, y2: firstHipLine.y2, line: firstHipLine })
baseHipLines.push({ x1: secondHipLine.x1, y1: secondHipLine.y1, x2: secondHipLine.x2, y2: secondHipLine.y2, line: secondHipLine })
}
/** 마루가 맞은편 외벽선에 닿는 경우 해당 부분까지로 한정한다. */
const ridgeEdge = {
vertex1: { x: currentMidX.toNumber(), y: currentMidY.toNumber() },
vertex2: { x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() },
}
const ridgeVectorX = Math.sign(currentMidX.minus(oppositeMidX).toNumber())
const ridgeVectorY = Math.sign(currentMidY.minus(oppositeMidY).toNumber())
roof.lines
.filter((line) => {
const lineVectorX = Math.sign(Big(line.x2).minus(Big(line.x1)).toNumber())
const lineVectorY = Math.sign(Big(line.y2).minus(Big(line.y1)).toNumber())
return (
(lineVectorX === currentVectorX && lineVectorY !== currentVectorY) || (lineVectorX !== currentVectorX && lineVectorY === currentVectorY)
)
})
.forEach((line) => {
const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
const intersection = edgesIntersection(ridgeEdge, lineEdge)
if (intersection && !intersection.isIntersectionOutside) {
const isVectorX = Math.sign(Big(currentMidX).minus(intersection.x).toNumber())
const isVectorY = Math.sign(Big(currentMidY).minus(intersection.y).toNumber())
if (isVectorX === ridgeVectorX && isVectorY === ridgeVectorY) {
oppositeMidX = Big(intersection.x)
oppositeMidY = Big(intersection.y)
}
}
})
if (baseRidgeCount < getMaxRidge(baseLines.length)) {
const ridgeLine = drawRidgeLine(
[currentMidX.toNumber(), currentMidY.toNumber(), oppositeMidX.toNumber(), oppositeMidY.toNumber()],
canvas,
roof,
textMode,
)
baseGableRidgeLines.push(ridgeLine)
baseRidgeCount++
}
}
}
})
/** 박공지붕에서 파생되는 마루를 그린다. 첫번째에서 처리 하지 못한 라인이 있는 경우 */
drawGableRidgeSecond.forEach((current) => {
const { currentBaseLine, prevBaseLine, nextBaseLine } = current
const currentLine = currentBaseLine.line
const prevLine = prevBaseLine.line
const nextLine = nextBaseLine.line
/** 이전 라인의 경사 */
const prevDegree = getDegreeByChon(prevLine.attributes.pitch)
/** 다음 라인의 경사 */
const nextDegree = getDegreeByChon(nextLine.attributes.pitch)
const currentAngle = calculateAngle(currentLine.startPoint, currentLine.endPoint)
const currentVectorX = Big(currentLine.x2).minus(currentLine.x1)
const currentVectorY = Big(currentLine.y2).minus(currentLine.y1)
const checkVectorX = Big(nextLine.x2).minus(Big(nextLine.x1))
const checkVectorY = Big(nextLine.y2).minus(Big(nextLine.y1))
const currentMidX = Big(currentLine.x1).plus(Big(currentLine.x2)).div(2)
const currentMidY = Big(currentLine.y1).plus(Big(currentLine.y2)).div(2)
const checkSize = Big(10)
/** 현재 라인의 지붕선을 찾는다. */
const intersectionRoofs = []
if (currentVectorX.eq(0)) {
const checkEdge = {
vertex1: { x: prevLine.x1, y: currentMidY.toNumber() },
vertex2: { x: currentMidX.toNumber(), y: currentMidY.toNumber() },
}
roof.lines
.filter(
(line) =>
Math.sign(line.x2 - line.x1) === Math.sign(currentVectorX.toNumber()) &&
Math.sign(line.y2 - line.y1) === Math.sign(currentVectorY.toNumber()),
)
.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().toNumber(),
})
}
}
})
} else {
const checkEdge = {
vertex1: { x: currentMidX.toNumber(), y: prevLine.y1 },
vertex2: { x: currentMidX.toNumber(), y: currentMidY.toNumber() },
}
roof.lines
.filter(
(line) =>
Math.sign(line.x2 - line.x1) === Math.sign(currentVectorX.toNumber()) &&
Math.sign(line.y2 - line.y1) === Math.sign(currentVectorY.toNumber()),
)
.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().toNumber(),
})
}
}
})
}
let currentRoof
if (intersectionRoofs.length > 0) {
currentRoof = intersectionRoofs.sort((a, b) => a.size - b.size)[0].line
}
if (currentLine.attributes.type === LINE_TYPE.WALLLINE.EAVES || currentLine.attributes.type === LINE_TYPE.WALLLINE.HIPANDGABLE) {
const currentMidEdge = {
vertex1: { x: currentMidX.toNumber(), y: currentMidY.toNumber() },
vertex2: {
x: currentMidX.plus(checkSize.times(Math.sign(checkVectorX.neg().toNumber()))).toNumber(),
y: currentMidY.plus(checkSize.times(Math.sign(checkVectorY.neg().toNumber()))).toNumber(),
},
}
let oppositeLines = []
baseLines
.filter((line) => {
if (eavesType.includes(line.attributes.type)) {
const angle = calculateAngle(line.startPoint, line.endPoint)
switch (currentAngle) {
case 90:
return angle === -90
case -90:
return angle === 90
case 0:
return angle === 180
case 180:
return angle === 0
default:
return false
}
} else {
return false
}
})
.forEach((line) => {
const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
const intersection = edgesIntersection(currentMidEdge, lineEdge)
if (intersection) {
oppositeLines.push({
line,
intersection,
size: Big(intersection.x).minus(currentMidX).abs().pow(2).plus(Big(intersection.y).minus(currentMidY).abs().pow(2)).sqrt(),
})
}
})
if (oppositeLines.length === 0) {
return
}
const oppositeLine = oppositeLines.sort((a, b) => b.size - a.size)[0].line
let ridgePoint
if (currentVectorY.eq(0)) {
const ridgeY = Big(currentLine.y1).plus(Big(oppositeLine.y1)).div(2).round()
ridgePoint = [currentRoof.x1, ridgeY.toNumber(), currentRoof.x2, ridgeY.toNumber()]
} else {
const ridgeX = Big(currentLine.x1).plus(Big(oppositeLine.x1)).div(2).round()
ridgePoint = [ridgeX.toNumber(), currentRoof.y1, ridgeX.toNumber(), currentRoof.y2]
}
const isAlreadyRidge = baseGableRidgeLines.find(
(line) =>
(line.x1 === ridgePoint[0] && line.y1 === ridgePoint[1] && line.x2 === ridgePoint[2] && line.y2 === ridgePoint[3]) ||
(line.x1 === ridgePoint[2] && line.y1 === ridgePoint[3] && line.x2 === ridgePoint[0] && line.y2 === ridgePoint[1]) ||
segmentsOverlap(line, { x1: ridgePoint[0], y1: ridgePoint[1], x2: ridgePoint[2], y2: ridgePoint[3] }),
)
if (baseRidgeCount < getMaxRidge(baseLines.length) && !isAlreadyRidge) {
const ridgeLine = drawRidgeLine(ridgePoint, canvas, roof, textMode)
baseGableRidgeLines.push(ridgeLine)
baseRidgeCount++
}
} else {
const checkPoints = {
x: currentMidX.plus(checkSize.times(Math.sign(checkVectorX.toNumber()))).toNumber(),
y: currentMidY.plus(checkSize.times(Math.sign(checkVectorY.toNumber()))).toNumber(),
}
if (!checkWallPolygon.inPolygon(checkPoints)) {
const currentMidEdge = {
vertex1: { x: currentMidX.toNumber(), y: currentMidY.toNumber() },
vertex2: {
x: currentMidX.plus(checkSize.times(Math.sign(checkVectorX.neg().toNumber()))).toNumber(),
y: currentMidY.plus(checkSize.times(Math.sign(checkVectorY.neg().toNumber()))).toNumber(),
},
}
let oppositeLines = []
baseLines
.filter((line, index) => {
let nextLine = baseLines[(index + 1) % baseLines.length]
let prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length]
if (
(gableType.includes(nextLine.attributes.type) && gableType.includes(prevLine.attributes.type)) ||
(eavesType.includes(nextLine.attributes.type) && eavesType.includes(prevLine.attributes.type))
) {
const angle = calculateAngle(line.startPoint, line.endPoint)
switch (currentAngle) {
case 90:
return angle === -90
case -90:
return angle === 90
case 0:
return angle === 180
case 180:
return angle === 0
}
}
return false
})
.forEach((line) => {
const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
const intersection = edgesIntersection(currentMidEdge, lineEdge)
if (intersection) {
oppositeLines.push({
line,
intersection,
size: Big(intersection.x).minus(currentMidX).abs().pow(2).plus(Big(intersection.y).minus(currentMidY).abs().pow(2)).sqrt(),
})
}
})
if (oppositeLines.length === 0) {
return
}
const oppositeLine = oppositeLines.sort((a, b) => a.size - b.size)[0]
let points = []
if (eavesType.includes(oppositeLine.line.attributes.type)) {
const oppositeCurrentLine = oppositeLine.line
let oppositePrevLine, oppositeNextLine
baseLines.forEach((line, index) => {
if (line === oppositeCurrentLine) {
oppositePrevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length]
oppositeNextLine = baseLines[(index + 1) % baseLines.length]
}
})
if (gableType.includes(oppositeNextLine.attributes.type) && gableType.includes(oppositePrevLine.attributes.type)) {
if (currentVectorX.eq(0)) {
const centerX = currentMidX.plus(oppositeLine.intersection.x).div(2).toNumber()
points = [centerX, currentLine.y1, centerX, currentLine.y2]
} else {
const centerY = currentMidY.plus(oppositeLine.intersection.y).div(2).toNumber()
points = [currentLine.x1, centerY, currentLine.x2, centerY]
}
}
if (eavesType.includes(oppositeNextLine.attributes.type) && eavesType.includes(oppositePrevLine.attributes.type)) {
/** 이전, 다음라인의 사잇각의 vector를 구한다. */
let prevVector = getHalfAngleVector(oppositePrevLine, oppositeCurrentLine)
let nextVector = getHalfAngleVector(oppositeCurrentLine, oppositeNextLine)
let prevHipVector = { x: Big(prevVector.x), y: Big(prevVector.y) }
let nextHipVector = { x: Big(nextVector.x), y: Big(nextVector.y) }
/** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/
const prevCheckPoint = {
x: Big(oppositeCurrentLine.x1).plus(Big(prevHipVector.x).times(10)),
y: Big(oppositeCurrentLine.y1).plus(Big(prevHipVector.y).times(10)),
}
if (!checkWallPolygon.inPolygon(prevCheckPoint)) {
prevHipVector = { x: Big(prevHipVector.x).neg(), y: Big(prevHipVector.y).neg() }
}
/** 다음 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/
const nextCheckPoint = {
x: Big(oppositeCurrentLine.x2).plus(Big(nextHipVector.x).times(10)),
y: Big(oppositeCurrentLine.y2).plus(Big(nextHipVector.y).times(10)),
}
if (!checkWallPolygon.inPolygon(nextCheckPoint)) {
nextHipVector = { x: Big(nextHipVector.x).neg(), y: Big(nextHipVector.y).neg() }
}
/** 현재 라인의 길이를 기준으로 추녀 마루의 길이를 삼각함수를 사용하여 판단한다.*/
let hipLength = Big(oppositeCurrentLine.attributes.planeSize)
.div(2)
.pow(2)
.plus(Big(oppositeCurrentLine.attributes.planeSize).div(2).pow(2))
.sqrt()
.div(10)
.round(2)
const ridgeEndPoint = {
x: Big(oppositeCurrentLine.x1).plus(hipLength.times(prevHipVector.x)).round(1),
y: Big(oppositeCurrentLine.y1).plus(hipLength.times(prevHipVector.y)).round(1),
}
const prevHypotenuse = Big(oppositePrevLine.attributes.offset).pow(2).plus(Big(oppositeCurrentLine.attributes.offset).pow(2)).sqrt()
const prevHipPoints = {
x1: Big(oppositeCurrentLine.x1).plus(prevHypotenuse.times(prevHipVector.x.neg())).round(1).toNumber(),
y1: Big(oppositeCurrentLine.y1).plus(prevHypotenuse.times(prevHipVector.y.neg())).round(1).toNumber(),
x2: ridgeEndPoint.x.toNumber(),
y2: ridgeEndPoint.y.toNumber(),
}
const nextHypotenuse = Big(oppositeNextLine.attributes.offset).pow(2).plus(Big(oppositeCurrentLine.attributes.offset).pow(2)).sqrt()
const nextHipPoints = {
x1: Big(oppositeCurrentLine.x2).plus(nextHypotenuse.times(nextHipVector.x.neg())).round(1).toNumber(),
y1: Big(oppositeCurrentLine.y2).plus(nextHypotenuse.times(nextHipVector.y.neg())).round(1).toNumber(),
x2: ridgeEndPoint.x.toNumber(),
y2: ridgeEndPoint.y.toNumber(),
}
const prevIntersection = findRoofIntersection(roof, prevHipPoints, ridgeEndPoint)
const nextIntersection = findRoofIntersection(roof, nextHipPoints, ridgeEndPoint)
if (prevIntersection) {
const prevHip = drawHipLine(
[prevIntersection.intersection.x, prevIntersection.intersection.y, ridgeEndPoint.x.toNumber(), ridgeEndPoint.y.toNumber()],
canvas,
roof,
textMode,
null,
prevDegree,
prevDegree,
)
baseHipLines.push({
x1: oppositeCurrentLine.x1,
y1: oppositeCurrentLine.y1,
x2: ridgeEndPoint.x,
y2: ridgeEndPoint.y,
line: prevHip,
})
}
if (nextIntersection) {
const nextHip = drawHipLine(
[nextIntersection.intersection.x, nextIntersection.intersection.y, ridgeEndPoint.x.toNumber(), ridgeEndPoint.y.toNumber()],
canvas,
roof,
textMode,
null,
nextDegree,
nextDegree,
)
baseHipLines.push({
x1: ridgeEndPoint.x,
y1: ridgeEndPoint.y,
x2: oppositeCurrentLine.x2,
y2: oppositeCurrentLine.y2,
line: nextHip,
})
}
const ridgeVectorX = Math.sign(currentMidX.minus(ridgeEndPoint.x).toNumber())
const ridgeVectorY = Math.sign(currentMidY.minus(ridgeEndPoint.y).toNumber())
const ridgePoints = {
x1: currentMidX.plus(Big(currentLine.attributes.offset).times(ridgeVectorX)).toNumber(),
y1: currentMidY.plus(Big(currentLine.attributes.offset).times(ridgeVectorY)).toNumber(),
x2: ridgeEndPoint.x.toNumber(),
y2: ridgeEndPoint.y.toNumber(),
}
const ridgeIntersection = findRoofIntersection(roof, ridgePoints, {
x: Big(ridgePoints.x2),
y: Big(ridgePoints.y2),
})
if (ridgeIntersection) {
points = [ridgeIntersection.intersection.x, ridgeIntersection.intersection.y, ridgeEndPoint.x, ridgeEndPoint.y]
}
}
} else {
if (currentVectorX.eq(0)) {
points = [oppositeLine.intersection.x, currentLine.y1, oppositeLine.intersection.x, currentLine.y2]
} else {
points = [currentLine.x1, oppositeLine.intersection.y, currentLine.x2, oppositeLine.intersection.y]
}
}
const isAlreadyRidge = baseGableRidgeLines.find(
(line) =>
(line.x1 === points[0] && line.y1 === points[1] && line.x2 === points[2] && line.y2 === points[3]) ||
(line.x1 === points[2] && line.y1 === points[3] && line.x2 === points[0] && line.y2 === points[1]),
)
if (baseRidgeCount < getMaxRidge(baseLines.length) && !isAlreadyRidge) {
const ridgeLine = drawRidgeLine(points, canvas, roof, textMode)
baseGableRidgeLines.push(ridgeLine)
baseRidgeCount++
}
} else {
const oppositeLines = baseLines.filter((line) => {
const lineAngle = calculateAngle(line.startPoint, line.endPoint)
switch (currentAngle) {
case 90:
return lineAngle === -90
case -90:
return lineAngle === 90
case 0:
return lineAngle === 180
case 180:
return lineAngle === 0
}
})
if (oppositeLines.length > 0 && baseRidgeCount < getMaxRidge(baseLines.length)) {
let ridgePoints = []
const oppositeLine = oppositeLines.sort((a, b) => {
let diffCurrentA, diffCurrentB
if (Math.sign(currentVectorX) === 0) {
diffCurrentA = currentMidY.minus(a.y1).abs()
diffCurrentB = currentMidY.minus(b.y1).abs()
} else {
diffCurrentA = currentMidX.minus(a.x1).abs()
diffCurrentB = currentMidX.minus(b.x1).abs()
}
return diffCurrentA.minus(diffCurrentB).toNumber()
})[0]
const prevOffset = prevLine.attributes.offset
const nextOffset = nextLine.attributes.offset
if (Math.sign(currentVectorX) === 0) {
const prevY = Big(currentLine.y1)
.plus(Big(Math.sign(currentVectorY)).neg().times(prevOffset))
.toNumber()
const nextY = Big(currentLine.y2)
.plus(Big(Math.sign(currentVectorY)).times(nextOffset))
.toNumber()
const midX = Big(currentLine.x1).plus(oppositeLine.x1).div(2).toNumber()
ridgePoints = [midX, prevY, midX, nextY]
} else {
const prevX = Big(currentLine.x1)
.plus(Big(Math.sign(currentVectorX)).neg().times(prevOffset))
.toNumber()
const nextX = Big(currentLine.x2)
.plus(Big(Math.sign(currentVectorX)).times(nextOffset))
.toNumber()
const midY = Big(currentLine.y1).plus(oppositeLine.y1).div(2).toNumber()
ridgePoints = [prevX, midY, nextX, midY]
}
const isAlreadyRidge = baseGableRidgeLines.find(
(line) =>
(line.x1 === ridgePoints[0] && line.y1 === ridgePoints[1] && line.x2 === ridgePoints[2] && line.y2 === ridgePoints[3]) ||
(line.x1 === ridgePoints[2] && line.y1 === ridgePoints[3] && line.x2 === ridgePoints[0] && line.y2 === ridgePoints[1]),
)
if (!isAlreadyRidge) {
const ridge = drawRidgeLine(ridgePoints, canvas, roof, textMode)
baseGableRidgeLines.push(ridge)
baseRidgeCount++
}
}
}
}
})
const uniqueRidgeLines = []
/** 중복제거 */
baseGableRidgeLines.forEach((currentLine, index) => {
if (index === 0) {
uniqueRidgeLines.push(currentLine)
} else {
const duplicateLines = uniqueRidgeLines.filter(
(line) =>
(currentLine.x1 === line.x1 && currentLine.y1 === line.y1 && currentLine.x2 === line.x2 && currentLine.y2 === line.y2) ||
(currentLine.x1 === line.x2 && currentLine.y1 === line.y2 && currentLine.x2 === line.x1 && currentLine.y2 === line.y1),
)
if (duplicateLines.length === 0) {
uniqueRidgeLines.push(currentLine)
}
}
})
baseGableRidgeLines = uniqueRidgeLines
/** 박공지붕 polygon 생성 */
drawGablePolygonFirst.forEach((current) => {
const { currentBaseLine, prevBaseLine, nextBaseLine } = current
const currentLine = currentBaseLine.line
const prevLine = prevBaseLine.line
const nextLine = nextBaseLine.line
const currentVectorX = Math.sign(currentLine.x2 - currentLine.x1)
const currentVectorY = Math.sign(currentLine.y2 - currentLine.y1)
const nextVectorX = Math.sign(nextLine.x1 - nextLine.x2)
const nextVectorY = Math.sign(nextLine.y1 - nextLine.y2)
const currentDegree = getDegreeByChon(currentLine.attributes.pitch)
const currentMidX = Big(currentLine.x1).plus(Big(currentLine.x2)).div(2)
const currentMidY = Big(currentLine.y1).plus(Big(currentLine.y2)).div(2)
const intersectionRoofs = []
if (currentVectorX === 0) {
const checkEdge = {
vertex1: { x: prevLine.x1, y: currentMidY.toNumber() },
vertex2: { x: currentMidX.toNumber(), y: currentMidY.toNumber() },
}
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.toNumber(), y: prevLine.y1 },
vertex2: { x: currentMidX.toNumber(), y: currentMidY.toNumber() },
}
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(),
})
}
}
})
}
let currentRoof, prevRoof, nextRoof
if (intersectionRoofs.length > 0) {
currentRoof = intersectionRoofs.sort((a, b) => a.size - b.size)[0].line
}
if (currentRoof) {
prevRoof = roof.lines.find((line) => line.x2 === currentRoof.x1 && line.y2 === currentRoof.y1)
nextRoof = roof.lines.find((line) => line.x1 === currentRoof.x2 && line.y1 === currentRoof.y2)
const prevRoofEdge = {
vertex1: { x: prevRoof.x2, y: prevRoof.y2 },
vertex2: { x: prevRoof.x1, y: prevRoof.y1 },
}
const nextRoofEdge = {
vertex1: { x: nextRoof.x1, y: nextRoof.y1 },
vertex2: { x: nextRoof.x2, y: nextRoof.y2 },
}
let polygonPoints = [
{ x: currentRoof.x1, y: currentRoof.y1 },
{ x: currentRoof.x2, y: currentRoof.y2 },
]
const prevHipLines = []
const nextHipLines = []
let prevLineRidge, nextLineRidge
baseHipLines.forEach((current) => {
const { line } = current
if (
(Math.abs(line.x1 - currentRoof.x1) <= 1 &&
Math.abs(line.y1 - currentRoof.y1) <= 1 &&
isPointOnLine(prevRoof, { x: line.x2, y: line.y2 })) ||
(Math.abs(line.x2 - currentRoof.x1) <= 1 &&
Math.abs(line.y2 - currentRoof.y1) <= 1 &&
isPointOnLine(prevRoof, {
x: line.x1,
y: line.y1,
}))
) {
prevHipLines.push(current)
}
if (
(Math.abs(line.x1 - currentRoof.x2) <= 1 &&
Math.abs(line.y1 - currentRoof.y2) <= 1 &&
isPointOnLine(nextRoof, { x: line.x2, y: line.y2 })) ||
(Math.abs(line.x2 - currentRoof.x2) <= 1 &&
Math.abs(line.y2 - currentRoof.y2) <= 1 &&
isPointOnLine(nextRoof, {
x: line.x1,
y: line.y1,
}))
) {
nextHipLines.push(current)
}
})
prevHipLines.forEach((current) => {
if (prevLine.attributes.type === LINE_TYPE.WALLLINE.JERKINHEAD) {
let findPoint
if (Math.abs(current.x1 - currentRoof.x1) <= 1 && Math.abs(current.y1 - currentRoof.y1) <= 1) {
findPoint = { x: current.x2, y: current.y2 }
} else {
findPoint = { x: current.x1, y: current.y1 }
}
baseHipLines
.filter(
(line) =>
((Math.abs(line.x1 - findPoint.x) <= 1 && Math.abs(line.y1 - findPoint.y) <= 1) ||
(Math.abs(line.x2 - findPoint.x) <= 1 && Math.abs(line.y2 - findPoint.y) <= 1)) &&
line.x1 !== line.x2 &&
line.y1 !== line.y2,
)
.forEach((line) => {
polygonPoints.push({ x: line.x1, y: line.y1 }, { x: line.x2, y: line.y2 })
let ridgePoint
if (Math.abs(line.x1 - findPoint.x) <= 1 && Math.abs(line.y1 - findPoint.y) <= 1) {
ridgePoint = { x: line.x2, y: line.y2 }
} else {
ridgePoint = { x: line.x1, y: line.y1 }
}
prevLineRidge = baseGableRidgeLines.find(
(ridge) =>
(Math.abs(ridge.x1 - ridgePoint.x) <= 1 && Math.abs(ridge.y1 - ridgePoint.y) <= 1) ||
(Math.abs(ridge.x2 - ridgePoint.x) <= 1 && Math.abs(ridge.y2 - ridgePoint.y) <= 1),
)
})
} else {
let ridgePoint
if (Math.abs(current.x1 - currentRoof.x1) <= 1 && Math.abs(current.y1 - currentRoof.y1) <= 1) {
ridgePoint = { x: current.x2, y: current.y2 }
} else {
ridgePoint = { x: current.x1, y: current.y1 }
}
prevLineRidge = baseGableRidgeLines.find((ridge) => {
return (
(Math.abs(ridge.x1 - ridgePoint.x) <= 1 && Math.abs(ridge.y1 - ridgePoint.y) <= 1) ||
(Math.abs(ridge.x2 - ridgePoint.x) <= 1 && Math.abs(ridge.y2 - ridgePoint.y) <= 1)
)
})
}
})
nextHipLines.forEach((current) => {
if (nextLine.attributes.type === LINE_TYPE.WALLLINE.JERKINHEAD) {
let findPoint
if (Math.abs(current.x1 - currentRoof.x2) <= 1 && Math.abs(current.y1 - currentRoof.y2) <= 1) {
findPoint = { x: current.x2, y: current.y2 }
} else {
findPoint = { x: current.x1, y: current.y1 }
}
baseHipLines
.filter(
(line) =>
((Math.abs(line.x1 - findPoint.x) <= 1 && Math.abs(line.y1 - findPoint.y) <= 1) ||
(Math.abs(line.x2 - findPoint.x) <= 1 && Math.abs(line.y2 - findPoint.y) <= 1)) &&
line.x1 !== line.x2 &&
line.y1 !== line.y2,
)
.forEach((line) => {
polygonPoints.push({ x: line.x1, y: line.y1 }, { x: line.x2, y: line.y2 })
let ridgePoint
if (Math.abs(line.x1 - findPoint.x) <= 1 && Math.abs(line.y1 - findPoint.y) <= 1) {
ridgePoint = { x: line.x2, y: line.y2 }
} else {
ridgePoint = { x: line.x1, y: line.y1 }
}
nextLineRidge = baseGableRidgeLines.find(
(ridge) =>
(Math.abs(ridge.x1 - ridgePoint.x) <= 1 && Math.abs(ridge.y1 - ridgePoint.y) <= 1) ||
(Math.abs(ridge.x2 - ridgePoint.x) <= 1 && Math.abs(ridge.y2 - ridgePoint.y) <= 1),
)
})
} else {
let ridgePoint
if (Math.abs(current.x1 - currentRoof.x2) <= 1 && Math.abs(current.y1 - currentRoof.y2) <= 1) {
ridgePoint = { x: current.x2, y: current.y2 }
} else {
ridgePoint = { x: current.x1, y: current.y1 }
}
nextLineRidge = baseGableRidgeLines.find(
(ridge) =>
(Math.abs(ridge.x1 - ridgePoint.x) <= 1 && Math.abs(ridge.y1 - ridgePoint.y) <= 1) ||
(Math.abs(ridge.x2 - ridgePoint.x) <= 1 && Math.abs(ridge.y2 - ridgePoint.y) <= 1),
)
}
})
if (!prevLineRidge) {
if (prevLine.attributes.type === LINE_TYPE.WALLLINE.JERKINHEAD) {
} else {
const isRidgePoints = []
baseGableRidgeLines.forEach((ridge) => {
const ridgeEdge = { vertex1: { x: ridge.x1, y: ridge.y1 }, vertex2: { x: ridge.x2, y: ridge.y2 } }
const intersection = edgesIntersection(prevRoofEdge, ridgeEdge)
if (
intersection &&
((ridge.x1 <= intersection.x && intersection.x <= ridge.x2 && ridge.y1 <= intersection.y && intersection.y <= ridge.y2) ||
(ridge.x2 <= intersection.x && intersection.x <= ridge.x1 && ridge.y2 <= intersection.y && intersection.y <= ridge.y1))
) {
const size = Big(intersection.x)
.minus(Big(currentRoof.x1))
.abs()
.pow(2)
.plus(Big(intersection.y).minus(Big(currentRoof.y1)).abs().pow(2))
.sqrt()
isRidgePoints.push({ intersection, ridge, size })
}
})
if (isRidgePoints.length > 0) {
const sortedRidgePoints = isRidgePoints.sort((a, b) => a.size - b.size)
prevLineRidge = sortedRidgePoints[0].ridge
}
}
}
if (!nextLineRidge) {
if (nextLine.attributes.type === LINE_TYPE.WALLLINE.JERKINHEAD) {
} else {
const isRidgePoints = []
baseGableRidgeLines.forEach((ridge) => {
const ridgeEdge = { vertex1: { x: ridge.x1, y: ridge.y1 }, vertex2: { x: ridge.x2, y: ridge.y2 } }
const intersection = edgesIntersection(nextRoofEdge, ridgeEdge)
if (
intersection &&
((ridge.x1 <= intersection.x && intersection.x <= ridge.x2 && ridge.y1 <= intersection.y && intersection.y <= ridge.y2) ||
(ridge.x2 <= intersection.x && intersection.x <= ridge.x1 && ridge.y2 <= intersection.y && intersection.y <= ridge.y1))
) {
const size = Big(intersection.x)
.minus(Big(currentRoof.x2))
.abs()
.pow(2)
.plus(Big(intersection.y).minus(Big(currentRoof.y2)).abs().pow(2))
.sqrt()
isRidgePoints.push({ intersection, ridge, size })
}
})
if (isRidgePoints.length > 0) {
const sortedRidgePoints = isRidgePoints.sort((a, b) => a.size - b.size)
nextLineRidge = sortedRidgePoints[0].ridge
}
}
}
const ridgeLine = prevLineRidge === undefined ? nextLineRidge : prevLineRidge
if (prevLineRidge !== undefined && nextLineRidge !== undefined) {
/** 4각*/
if (prevLineRidge === nextLineRidge) {
polygonPoints.push({ x: ridgeLine.x1, y: ridgeLine.y1 }, { x: ridgeLine.x2, y: ridgeLine.y2 })
/** 포인트가 직각 사각형인지 확인하여 아닌경우 직각인 다각형 포인트로 변경한다.*/
const checkPoints = getSortedPoint(polygonPoints, baseHipLines)
let hasDiagonal = false
if (checkPoints < 4) {
hasDiagonal = true
} else {
checkPoints.forEach((point, index) => {
const nextPoint = checkPoints[(index + 1) % checkPoints.length]
if (point.x !== nextPoint.x && point.y !== nextPoint.y) {
hasDiagonal = true
}
})
}
if (hasDiagonal) {
const vectorX = Math.sign(currentRoof.x1 - ridgeLine.x1)
const vectorY = Math.sign(currentRoof.y1 - ridgeLine.y1)
const ridgeMinX = Math.min(ridgeLine.x1, ridgeLine.x2)
const ridgeMaxX = Math.max(ridgeLine.x1, ridgeLine.x2)
const ridgeMinY = Math.min(ridgeLine.y1, ridgeLine.y2)
const ridgeMaxY = Math.max(ridgeLine.y1, ridgeLine.y2)
if (
(!isPointOnLineNew(prevRoof, { x: ridgeLine.x1, y: ridgeLine.y1 }) &&
!isPointOnLineNew(nextRoof, { x: ridgeLine.x1, y: ridgeLine.y1 })) ||
(!isPointOnLineNew(prevRoof, { x: ridgeLine.x2, y: ridgeLine.y2 }) && !isPointOnLineNew(nextRoof, { x: ridgeLine.x2, y: ridgeLine.y2 }))
) {
roof.lines
.filter((line) => line !== currentRoof && line !== prevRoof && line !== nextRoof)
.filter((line) =>
ridgeLine.y1 === ridgeLine.y2
? vectorY === Math.sign(line.y1 - ridgeLine.y1) &&
ridgeMinX <= line.x1 &&
line.x1 <= ridgeMaxX &&
ridgeMinX <= line.x2 &&
line.x2 <= ridgeMaxX
: vectorX === Math.sign(line.x1 - ridgeLine.x1) &&
ridgeMinY <= line.y1 &&
line.y1 <= ridgeMaxY &&
ridgeMinY <= line.y2 &&
line.y2 <= ridgeMaxY,
)
.forEach((line) => {
if (ridgeLine.y1 === ridgeLine.y2) {
if (vectorY === Math.sign(line.y1 - ridgeLine.y1)) {
polygonPoints.push({ x: line.x1, y: line.y1 })
}
if (vectorY === Math.sign(line.y2 - ridgeLine.y1)) {
polygonPoints.push({ x: line.x2, y: line.y2 })
}
} else {
if (vectorX === Math.sign(line.x1 - ridgeLine.x1)) {
polygonPoints.push({ x: line.x1, y: line.y1 })
}
if (vectorX === Math.sign(line.x2 - ridgeLine.x1)) {
polygonPoints.push({ x: line.x2, y: line.y2 })
}
}
})
}
if (
!isPointOnLineNew(prevRoof, { x: ridgeLine.x2, y: ridgeLine.y2 }) &&
!isPointOnLineNew(nextRoof, { x: ridgeLine.x2, y: ridgeLine.y2 })
) {
}
}
} else {
/** 6각이상*/
let isOverLap =
currentVectorX === 0
? (prevLineRidge.y1 <= nextLineRidge.y1 && prevLineRidge.y2 >= nextLineRidge.y1) ||
(prevLineRidge.y1 >= nextLineRidge.y1 && prevLineRidge.y2 <= nextLineRidge.y1) ||
(prevLineRidge.y1 <= nextLineRidge.y2 && prevLineRidge.y2 >= nextLineRidge.y2) ||
(prevLineRidge.y1 >= nextLineRidge.y2 && prevLineRidge.y2 <= nextLineRidge.y2)
: (prevLineRidge.x1 <= nextLineRidge.x1 && prevLineRidge.x2 >= nextLineRidge.x1) ||
(prevLineRidge.x1 >= nextLineRidge.x1 && prevLineRidge.x2 <= nextLineRidge.x1) ||
(prevLineRidge.x1 <= nextLineRidge.x2 && prevLineRidge.x2 >= nextLineRidge.x2) ||
(prevLineRidge.x1 >= nextLineRidge.x2 && prevLineRidge.x2 <= nextLineRidge.x2)
if (isOverLap) {
const prevDistance = currentVectorX === 0 ? Math.abs(prevLineRidge.x1 - currentRoof.x1) : Math.abs(prevLineRidge.y1 - currentRoof.y1)
const nextDistance = currentVectorX === 0 ? Math.abs(nextLineRidge.x1 - currentRoof.x1) : Math.abs(nextLineRidge.y1 - currentRoof.y1)
/** 현재 지붕 라인과 먼 라인의 포인트를 온전히 사용한다. */
if (Math.abs(prevDistance - nextDistance) < 1) {
const minX = Math.min(currentRoof.x1, currentRoof.x2, currentLine.x1, currentLine.x2)
const maxX = Math.max(currentRoof.x1, currentRoof.x2, currentLine.x1, currentLine.x2)
const minY = Math.min(currentRoof.y1, currentRoof.y2, currentLine.y1, currentLine.y2)
const maxY = Math.max(currentRoof.y1, currentRoof.y2, currentLine.y1, currentLine.y2)
if (currentVectorX === 0) {
polygonPoints.push({ x: prevLineRidge.x1, y: minY }, { x: prevLineRidge.x1, y: maxY })
} else {
polygonPoints.push({ x: minX, y: prevLineRidge.y1 }, { x: maxX, y: prevLineRidge.y1 })
}
} else if (prevDistance < nextDistance) {
polygonPoints.push({ x: nextLineRidge.x1, y: nextLineRidge.y1 }, { x: nextLineRidge.x2, y: nextLineRidge.y2 })
/** 이전라인과 교차한 마루의 포인트*/
let prevRidgePoint1
if (prevLine.attributes.type === LINE_TYPE.WALLLINE.JERKINHEAD) {
prevRidgePoint1 = polygonPoints.find(
(point) =>
(point.x === prevLineRidge.x1 && point.y === prevLineRidge.y1) || (point.x === prevLineRidge.x2 && point.y === prevLineRidge.y2),
)
} else {
prevRidgePoint1 =
currentVectorX === 0
? currentRoof.y1 === prevLineRidge.y1
? { x: prevLineRidge.x1, y: prevLineRidge.y1 }
: { x: prevLineRidge.x2, y: prevLineRidge.y2 }
: currentRoof.x1 === prevLineRidge.x1
? { x: prevLineRidge.x1, y: prevLineRidge.y1 }
: { x: prevLineRidge.x2, y: prevLineRidge.y2 }
polygonPoints.push(prevRidgePoint1)
}
/** 다음 라인과 교차한 마루의 포인트 중 라인과 접하지 않은 포인트*/
let checkRidgePoint
if (nextLine.attributes.type === LINE_TYPE.WALLLINE.JERKINHEAD) {
const ridgePoint1 = polygonPoints.filter((point) => point.x === nextLineRidge.x1 && point.y === nextLineRidge.y1)
checkRidgePoint = ridgePoint1.length > 0 ? { x: nextLineRidge.x2, y: nextLineRidge.y2 } : { x: nextLineRidge.x1, y: nextLineRidge.y1 }
} else {
checkRidgePoint =
currentVectorX === 0
? currentRoof.y2 !== nextLineRidge.y1
? { x: nextLineRidge.x1, y: nextLineRidge.y1 }
: { x: nextLineRidge.x2, y: nextLineRidge.y2 }
: currentRoof.x2 !== nextLineRidge.x1
? { x: nextLineRidge.x1, y: nextLineRidge.y1 }
: { x: nextLineRidge.x2, y: nextLineRidge.y2 }
}
const prevRidgePoint2 =
currentVectorX === 0 ? { x: prevRidgePoint1.x, y: checkRidgePoint.y } : { x: checkRidgePoint.x, y: prevRidgePoint1.y }
polygonPoints.push(prevRidgePoint2)
} else {
polygonPoints.push({ x: prevLineRidge.x1, y: prevLineRidge.y1 }, { x: prevLineRidge.x2, y: prevLineRidge.y2 })
/** 다음라인과 교차한 마루의 포인트*/
let nextRidgePoint1
if (nextLine.attributes.type === LINE_TYPE.WALLLINE.JERKINHEAD) {
nextRidgePoint1 = polygonPoints.find(
(point) =>
(point.x === nextLineRidge.x1 && point.y === nextLineRidge.y1) || (point.x === nextLineRidge.x2 && point.y === nextLineRidge.y2),
)
} else {
nextRidgePoint1 =
currentVectorX === 0
? currentRoof.y2 === nextLineRidge.y1
? { x: nextLineRidge.x1, y: nextLineRidge.y1 }
: { x: nextLineRidge.x2, y: nextLineRidge.y2 }
: currentRoof.x2 === nextLineRidge.x1
? { x: nextLineRidge.x1, y: nextLineRidge.y1 }
: { x: nextLineRidge.x2, y: nextLineRidge.y2 }
polygonPoints.push(nextRidgePoint1)
}
/** 이전 라인과 교차한 마루의 포인트 중 라인과 접하지 않은 포인트*/
let checkRidgePoint
if (prevLine.attributes.type === LINE_TYPE.WALLLINE.JERKINHEAD) {
const ridgePoint1 = polygonPoints.filter((point) => point.x === prevLineRidge.x1 && point.y === prevLineRidge.y1)
checkRidgePoint = ridgePoint1.length > 0 ? { x: prevLineRidge.x2, y: prevLineRidge.y2 } : { x: prevLineRidge.x1, y: prevLineRidge.y1 }
} else {
checkRidgePoint =
currentVectorX === 0
? currentRoof.y1 !== prevLineRidge.y1
? { x: prevLineRidge.x1, y: prevLineRidge.y1 }
: { x: prevLineRidge.x2, y: prevLineRidge.y2 }
: currentRoof.x1 !== prevLineRidge.x1
? { x: prevLineRidge.x1, y: prevLineRidge.y1 }
: { x: prevLineRidge.x2, y: prevLineRidge.y2 }
}
const nextRidgePoint2 =
currentVectorX === 0 ? { x: nextRidgePoint1.x, y: checkRidgePoint.y } : { x: checkRidgePoint.x, y: nextRidgePoint1.y }
polygonPoints.push(nextRidgePoint2)
}
} else {
/** 마루가 겹치지 않을때 */
const otherRidgeLines = []
baseGableRidgeLines
.filter((ridge) => ridge !== prevLineRidge && ridge !== nextLineRidge)
.filter((ridge) => (currentVectorX === 0 ? ridge.x1 === ridge.x2 : ridge.y1 === ridge.y2))
.filter((ridge) =>
currentVectorX === 0 ? nextVectorX === Math.sign(nextLine.x1 - ridge.x1) : nextVectorY === Math.sign(nextLine.y1 - ridge.y1),
)
.forEach((ridge) => {
const size = currentVectorX === 0 ? Math.abs(nextLine.x1 - ridge.x1) : Math.abs(nextLine.y1 - ridge.y1)
otherRidgeLines.push({ ridge, size })
})
if (otherRidgeLines.length > 0) {
const otherRidge = otherRidgeLines.sort((a, b) => a.size - b.size)[0].ridge
/**
* otherRidge이 prevRidgeLine, nextRidgeLine 과 currentLine의 사이에 있는지 확인해서 분할하여 작업
* 지붕의 덮힘이 다르기 때문
*/
const isInside =
currentVectorX === 0
? Math.abs(currentLine.x1 - otherRidge.x1) < Math.abs(currentLine.x1 - prevLineRidge.x1) &&
Math.abs(currentLine.x1 - otherRidge.x1) < Math.abs(currentLine.x1 - nextLineRidge.x1)
: Math.abs(currentLine.y1 - otherRidge.y1) < Math.abs(currentLine.y1 - prevLineRidge.y1) &&
Math.abs(currentLine.y1 - otherRidge.y1) < Math.abs(currentLine.y1 - nextLineRidge.y1)
if (isInside) {
polygonPoints.push(
{ x: prevLineRidge.x1, y: prevLineRidge.y1 },
{ x: prevLineRidge.x2, y: prevLineRidge.y2 },
{ x: nextLineRidge.x1, y: nextLineRidge.y1 },
{ x: nextLineRidge.x2, y: nextLineRidge.y2 },
)
let ridgeAllPoints = [
{ x: prevLineRidge.x1, y: prevLineRidge.y1 },
{ x: prevLineRidge.x2, y: prevLineRidge.y2 },
{ x: nextLineRidge.x1, y: nextLineRidge.y1 },
{ x: nextLineRidge.x2, y: nextLineRidge.y2 },
]
let ridgePoints = []
ridgeAllPoints.forEach((point) => {
let isOnLine = false
roof.lines.forEach((line) => {
if (isPointOnLine({ x1: line.x1, y1: line.y1, x2: line.x2, y2: line.y2 }, point)) {
isOnLine = true
}
})
if (!isOnLine) {
ridgePoints.push(point)
}
})
if (ridgePoints.length === 2) {
if (Math.sign(otherRidge.x1 - otherRidge.x2) === 0) {
polygonPoints.push(
{ x: otherRidge.x1, y: ridgePoints[0].y },
{
x: otherRidge.x1,
y: ridgePoints[1].y,
},
)
} else {
polygonPoints.push(
{ x: ridgePoints[0].x, y: otherRidge.y1 },
{
x: ridgePoints[1].x,
y: otherRidge.y1,
},
)
}
}
} else {
polygonPoints.push({ x: otherRidge.x1, y: otherRidge.y1 }, { x: otherRidge.x2, y: otherRidge.y2 })
let ridgePoints = [
{ x: prevLineRidge.x1, y: prevLineRidge.y1 },
{ x: prevLineRidge.x2, y: prevLineRidge.y2 },
{ x: nextLineRidge.x1, y: nextLineRidge.y1 },
{ x: nextLineRidge.x2, y: nextLineRidge.y2 },
]
ridgePoints.forEach((point) => {
let isOnLine = false
roof.lines.forEach((line) => {
if (isPointOnLine({ x1: line.x1, y1: line.y1, x2: line.x2, y2: line.y2 }, point)) {
isOnLine = true
}
})
if (isOnLine) {
polygonPoints.push(point)
}
})
if (Math.sign(otherRidge.x1 - otherRidge.x2) === 0) {
const prevY =
(prevLineRidge.y1 <= otherRidge.y1 && otherRidge.y1 <= prevLineRidge.y2) ||
(prevLineRidge.y1 >= otherRidge.y1 && otherRidge.y1 >= prevLineRidge.y2)
? otherRidge.y1
: otherRidge.y2
const nextY =
(nextLineRidge.y1 <= otherRidge.y1 && otherRidge.y1 <= nextLineRidge.y2) ||
(nextLineRidge.y1 >= otherRidge.y1 && otherRidge.y1 >= nextLineRidge.y2)
? otherRidge.y1
: otherRidge.y2
polygonPoints.push({ x: prevLineRidge.x1, y: prevY }, { x: nextLineRidge.x1, y: nextY })
} else {
const prevX =
(prevLineRidge.x1 <= otherRidge.x1 && otherRidge.x1 <= prevLineRidge.x2) ||
(prevLineRidge.x1 >= otherRidge.x1 && otherRidge.x1 >= prevLineRidge.x2)
? otherRidge.x1
: otherRidge.x2
const nextX =
(nextLineRidge.x1 <= otherRidge.x1 && otherRidge.x1 <= nextLineRidge.x2) ||
(nextLineRidge.x1 >= otherRidge.x1 && otherRidge.x1 >= nextLineRidge.x2)
? otherRidge.x1
: otherRidge.x2
polygonPoints.push({ x: prevX, y: prevLineRidge.y1 }, { x: nextX, y: nextLineRidge.y1 })
}
}
}
}
}
} else {
if (ridgeLine) {
const ridgeEdge = { vertex1: { x: ridgeLine.x1, y: ridgeLine.y1 }, vertex2: { x: ridgeLine.x2, y: ridgeLine.y2 } }
const prevRoofEdge = { vertex1: { x: prevRoof.x1, y: prevRoof.y1 }, vertex2: { x: prevRoof.x2, y: prevRoof.y2 } }
const nextRoofEdge = { vertex1: { x: nextRoof.x1, y: nextRoof.y1 }, vertex2: { x: nextRoof.x2, y: nextRoof.y2 } }
const isPrevRoof = edgesIntersection(prevRoofEdge, ridgeEdge)
const isNextRoof = edgesIntersection(nextRoofEdge, ridgeEdge)
if (isPrevRoof && isPointOnLine(ridgeLine, isPrevRoof)) {
polygonPoints.push({ x: isPrevRoof.x, y: isPrevRoof.y })
} else {
polygonPoints.push({ x: prevRoof.x1, y: prevRoof.y1 })
}
if (isNextRoof && isPointOnLine(ridgeLine, isNextRoof)) {
polygonPoints.push({ x: isNextRoof.x, y: isNextRoof.y })
} else {
polygonPoints.push({ x: nextRoof.x2, y: nextRoof.y2 })
}
roof.lines
.filter((line) => line !== currentRoof && line !== prevRoof && line !== nextRoof)
.forEach((line) => {
const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
const intersection = edgesIntersection(ridgeEdge, lineEdge)
if (intersection && isPointOnLine(ridgeLine, intersection)) {
const size1 = Math.sqrt(Math.pow(line.x1 - intersection.x, 2) + Math.pow(line.y1 - intersection.y, 2))
const size2 = Math.sqrt(Math.pow(line.x2 - intersection.x, 2) + Math.pow(line.y2 - intersection.y, 2))
if (size1 < size2) {
polygonPoints.push({ x: line.x2, y: line.y2 })
} else {
polygonPoints.push({ x: line.x1, y: line.y1 })
}
polygonPoints.push({ x: intersection.x, y: intersection.y })
}
})
}
}
/** 중복되는 포인트 제거 */
const uniquePoints = []
polygonPoints.forEach((point) => {
const isAlready = uniquePoints.find((uniquePoint) => uniquePoint.x === point.x && uniquePoint.y === point.y)
if (!isAlready) {
uniquePoints.push(point)
}
})
polygonPoints = getSortedPoint(uniquePoints, baseHipLines)
/*polygonPoints.forEach((point) => {
const checkCircle = new fabric.Circle({
left: point.x,
top: point.y,
radius: 4,
fill: 'red',
parentId: roofId,
name: 'checkCircle',
})
canvas.add(checkCircle)
canvas.renderAll()
/!** 확인용 라인 제거 *!/
canvas
.getObjects()
.filter((obj) => obj.name === 'checkCircle' || obj.name === 'checkLine')
.forEach((obj) => canvas.remove(obj))
canvas.renderAll()
})*/
polygonPoints.forEach((currentPoint, index) => {
const nextPoint = polygonPoints[(index + 1) % polygonPoints.length]
const points = [currentPoint.x, currentPoint.y, nextPoint.x, nextPoint.y]
const isParallel = ridgeLine.x1 === ridgeLine.x2 ? currentPoint.x === nextPoint.x : currentPoint.y === nextPoint.y
const ridgeLines = baseGableRidgeLines.filter(
(line) =>
(line.x1 === points[0] && line.y1 === points[1] && line.x2 === points[2] && line.y2 === points[3]) ||
(line.x1 === points[2] && line.y1 === points[3] && line.x2 === points[0] && line.y2 === points[1]),
)
const hipLine = baseHipLines.filter(
(line) =>
(line.x1 === points[0] && line.y1 === points[1] && line.x2 === points[2] && line.y2 === points[3]) ||
(line.x1 === points[2] && line.y1 === points[3] && line.x2 === points[0] && line.y2 === points[1]),
)
/** 이미 존재하는 라인이면 넘긴다.*/
if (ridgeLines.length > 0 || hipLine.length > 0) {
return
}
let line
if (isParallel) {
line = drawRoofLine(points, canvas, roof, textMode)
baseGableLines.push(line)
} else {
line = drawHipLine(points, canvas, roof, textMode, null, currentDegree, currentDegree)
baseHipLines.push({ x1: line.x1, y1: line.y1, x2: line.x2, y2: line.y2, line })
}
})
}
})
drawGablePolygonSecond.forEach((current) => {
const { currentBaseLine, prevBaseLine, nextBaseLine } = current
const currentLine = currentBaseLine.line
const prevLine = prevBaseLine.line
const nextLine = nextBaseLine.line
let { x1, x2, y1, y2 } = currentBaseLine
/*const checkLine = new fabric.Line([x1, y1, x2, y2], {
stroke: 'red',
strokeWidth: 4,
parentId: roofId,
name: 'checkLine',
})
canvas.add(checkLine)
canvas.renderAll()*/
const currentVectorX = Math.sign(currentLine.x2 - currentLine.x1)
const currentVectorY = Math.sign(currentLine.y2 - currentLine.y1)
const currentDegree = getDegreeByChon(currentLine.attributes.pitch)
const prevAngle = calculateAngle(prevLine.startPoint, prevLine.endPoint)
const nextAngle = calculateAngle(nextLine.startPoint, nextLine.endPoint)
const currentMidX = Big(currentLine.x1).plus(Big(currentLine.x2)).div(2)
const currentMidY = Big(currentLine.y1).plus(Big(currentLine.y2)).div(2)
const intersectionRoofs = []
if (currentVectorX === 0) {
const checkEdge = {
vertex1: { x: prevLine.x1, y: currentMidY.toNumber() },
vertex2: { x: currentMidX.toNumber(), y: currentMidY.toNumber() },
}
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 && isPointOnLine(line, intersection)) {
intersectionRoofs.push({
line,
intersection,
size: Math.sqrt(Math.pow(intersection.x - currentMidX.toNumber(), 2) + Math.pow(intersection.y - currentMidY.toNumber(), 2)),
})
}
})
} else {
const checkEdge = {
vertex1: { x: currentMidX.toNumber(), y: prevLine.y1 },
vertex2: { x: currentMidX.toNumber(), y: currentMidY.toNumber() },
}
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 && isPointOnLine(line, intersection)) {
intersectionRoofs.push({
line,
intersection,
size: Math.sqrt(Math.pow(intersection.x - currentMidX.toNumber(), 2) + Math.pow(intersection.y - currentMidY.toNumber(), 2)),
})
}
})
}
let currentRoof, prevRoof, nextRoof
if (intersectionRoofs.length > 0) {
currentRoof = intersectionRoofs.sort((a, b) => a.size - b.size)[0].line
prevRoof = roof.lines.find((line) => line.x2 === currentRoof.x1 && line.y2 === currentRoof.y1)
nextRoof = roof.lines.find((line) => line.x1 === currentRoof.x2 && line.y1 === currentRoof.y2)
}
let polygonPoints = []
if (Big(prevAngle).minus(Big(nextAngle)).abs().eq(180)) {
const xVector = Big(nextLine.x2).minus(Big(nextLine.x1))
const yVector = Big(nextLine.y2).minus(Big(nextLine.y1))
const currentMidX = Big(currentLine.x1).plus(Big(currentLine.x2)).div(2)
const currentMidY = Big(currentLine.y1).plus(Big(currentLine.y2)).div(2)
const checkPoints = {
x: currentMidX.plus(Big(10).times(Math.sign(xVector.toNumber()))).toNumber(),
y: currentMidY.plus(Big(10).times(Math.sign(yVector.toNumber()))).toNumber(),
}
if (checkWallPolygon.inPolygon(checkPoints)) {
const currentRidge = baseGableRidgeLines.find((line) =>
currentVectorX === 0
? (line.y1 === y1 && line.y2 === y2) || (line.y1 === y2 && line.y2 === y1)
: (line.x1 === x1 && line.x2 === x2) || (line.x1 === x2 && line.x2 === x1),
)
if (currentRidge) {
const ridgeVectorX = Math.sign(currentRidge.x1 - currentRidge.x2)
const ridgeVectorY = Math.sign(currentRidge.y1 - currentRidge.y2)
let checkEdge
if (currentVectorX === 0) {
checkEdge = {
vertex1: { x: currentRidge.x1, y: currentRidge.y1 },
vertex2: { x: currentLine.x1, y: currentRidge.y1 },
}
} else {
checkEdge = {
vertex1: { x: currentRidge.x1, y: currentRidge.y1 },
vertex2: { x: currentRidge.x1, y: currentLine.y1 },
}
}
const isRoofLines = []
roof.lines
.filter((line) => (currentVectorX === 0 ? line.x1 === line.x2 : line.y1 === line.y2))
.forEach((line) => {
const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
const is = edgesIntersection(checkEdge, lineEdge)
if (is) {
const checkVectorX = Math.sign(checkEdge.vertex1.x - checkEdge.vertex2.x)
const checkVectorY = Math.sign(checkEdge.vertex1.y - checkEdge.vertex2.y)
const isVectorX = Math.sign(checkEdge.vertex1.x - is.x)
const isVectorY = Math.sign(checkEdge.vertex1.y - is.y)
if ((ridgeVectorX === 0 && checkVectorX === isVectorX) || (ridgeVectorY === 0 && checkVectorY === isVectorY)) {
const size = ridgeVectorX === 0 ? Math.abs(checkEdge.vertex1.x - is.x) : Math.abs(checkEdge.vertex1.y - is.y)
isRoofLines.push({ line, size })
}
}
})
isRoofLines.sort((a, b) => a.size - b.size)
const roofLine = isRoofLines[0].line
polygonPoints.push({ x: currentRidge.x1, y: currentRidge.y1 }, { x: currentRidge.x2, y: currentRidge.y2 })
if (ridgeVectorX === 0) {
polygonPoints.push({ x: roofLine.x1, y: currentRidge.y1 }, { x: roofLine.x1, y: currentRidge.y2 })
} else {
polygonPoints.push({ x: currentRidge.x1, y: roofLine.y1 }, { x: currentRidge.x2, y: roofLine.y1 })
}
}
} else {
const currentRidge = baseGableRidgeLines.find((line) =>
currentVectorX === 0
? (Math.abs(line.y1 - currentRoof.y1) < 1 && Math.abs(line.y2 - currentRoof.y2) < 1) ||
(Math.abs(line.y1 - currentRoof.y2) < 1 && Math.abs(line.y2 - currentRoof.y1) < 1)
: (Math.abs(line.x1 - currentRoof.x1) < 1 && Math.abs(line.x2 - currentRoof.x2) < 1) ||
(Math.abs(line.x1 - currentRoof.x2) < 1 && Math.abs(line.x2 - currentRoof.x1) < 1),
)
if (currentRidge) {
if (currentVectorX === 0) {
polygonPoints.push({ x: currentRoof.x1, y: currentLine.y1 }, { x: currentRoof.x2, y: currentLine.y2 })
polygonPoints.push({ x: currentRidge.x1, y: currentLine.y1 }, { x: currentRidge.x1, y: currentLine.y2 })
} else {
polygonPoints.push({ x: currentLine.x1, y: currentRoof.y1 }, { x: currentLine.x2, y: currentRoof.y2 })
polygonPoints.push({ x: currentLine.x1, y: currentRidge.y1 }, { x: currentLine.x2, y: currentRidge.y1 })
}
}
}
} else {
const prevEdge = { vertex1: { x: prevRoof.x2, y: prevRoof.y2 }, vertex2: { x: prevRoof.x1, y: prevRoof.y1 } }
const prevVectorX = Math.sign(prevRoof.x1 - prevRoof.x2)
const prevVectorY = Math.sign(prevRoof.y1 - prevRoof.y2)
let prevRidge, nextRidge
if (prevLine.attributes.type === LINE_TYPE.WALLLINE.JERKINHEAD) {
const hipLines = []
baseHipLines.forEach((line) => {
const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
const is = edgesIntersection(prevEdge, lineEdge)
if (is) {
const size = Big(is.x).minus(prevRoof.x2).abs().pow(2).plus(Big(is.y).minus(prevRoof.y2).abs().pow(2)).sqrt().toNumber()
hipLines.push({ line, is, size })
}
})
if (hipLines.length > 0) {
hipLines.sort((a, b) => a.size - b.size)
const hipLine = hipLines[0]
polygonPoints.push({ x: hipLine.is.x, y: hipLine.is.y })
const ridgePoint =
hipLine.is.x === hipLine.line.x1 && hipLine.is.y === hipLine.line.y1
? { x: hipLine.line.x2, y: hipLine.line.y2 }
: { x: hipLine.line.x1, y: hipLine.line.y1 }
prevRidge = baseGableRidgeLines.find(
(line) => (line.x1 === ridgePoint.x && line.y1 === ridgePoint.y) || (line.x2 === ridgePoint.x && line.y2 === ridgePoint.y),
)
}
} else {
const prevRidges = []
baseGableRidgeLines.forEach((ridge) => {
const ridgeEdge = { vertex1: { x: ridge.x1, y: ridge.y1 }, vertex2: { x: ridge.x2, y: ridge.y2 } }
const is = edgesIntersection(prevEdge, ridgeEdge)
if (is) {
const isVectorX = Math.sign(prevRoof.x1 - is.x)
const isVectorY = Math.sign(prevRoof.y1 - is.y)
if (
isVectorX === prevVectorX &&
isVectorY === prevVectorY &&
((Math.abs(ridge.x1 - is.x) < 1 && Math.abs(ridge.y1 - is.y) < 1) || (Math.abs(ridge.x2 - is.x) < 1 && Math.abs(ridge.y2 - is.y) < 1))
) {
const size = Big(prevRoof.x1).minus(is.x).abs().pow(2).plus(Big(prevRoof.y1).minus(is.y).abs().pow(2)).sqrt().toNumber()
prevRidges.push({ ridge, size })
}
}
})
if (prevRidges.length > 0) {
prevRidges.sort((a, b) => a.size - b.size)
prevRidge = prevRidges[0].ridge
}
}
const nextEdge = { vertex1: { x: nextRoof.x1, y: nextRoof.y1 }, vertex2: { x: nextRoof.x2, y: nextRoof.y2 } }
const nextVectorX = Math.sign(nextLine.x1 - nextLine.x2)
const nextVectorY = Math.sign(nextLine.y1 - nextLine.y2)
if (nextLine.attributes.type === LINE_TYPE.WALLLINE.JERKINHEAD) {
const hipLines = []
baseHipLines.forEach((line) => {
const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
const is = edgesIntersection(nextEdge, lineEdge)
if (is) {
const size = Big(is.x).minus(nextRoof.x1).abs().pow(2).plus(Big(is.y).minus(nextRoof.y1).abs().pow(2)).sqrt().toNumber()
hipLines.push({ line, is, size })
}
})
if (hipLines.length > 0) {
hipLines.sort((a, b) => a.size - b.size)
const hipLine = hipLines[0]
polygonPoints.push({ x: hipLine.is.x, y: hipLine.is.y })
const ridgePoint =
hipLine.is.x === hipLine.line.x1 && hipLine.is.y === hipLine.line.y1
? { x: hipLine.line.x2, y: hipLine.line.y2 }
: { x: hipLine.line.x1, y: hipLine.line.y1 }
nextRidge = baseGableRidgeLines.find(
(line) => (line.x1 === ridgePoint.x && line.y1 === ridgePoint.y) || (line.x2 === ridgePoint.x && line.y2 === ridgePoint.y),
)
}
} else {
const nextRidges = []
baseGableRidgeLines.forEach((ridge) => {
const ridgeEdge = { vertex1: { x: ridge.x1, y: ridge.y1 }, vertex2: { x: ridge.x2, y: ridge.y2 } }
const is = edgesIntersection(nextEdge, ridgeEdge)
if (is) {
const isVectorX = Math.sign(nextRoof.x1 - is.x)
const isVectorY = Math.sign(nextRoof.y1 - is.y)
if (
isVectorX === nextVectorX &&
isVectorY === nextVectorY &&
((Math.abs(ridge.x1 - is.x) < 1 && Math.abs(ridge.y1 - is.y) < 1) || (Math.abs(ridge.x2 - is.x) < 1 && Math.abs(ridge.y2 - is.y) < 1))
) {
const size = Big(nextLine.x1).minus(is.x).abs().pow(2).plus(Big(nextLine.y1).minus(is.y).abs().pow(2)).sqrt().toNumber()
nextRidges.push({ ridge, size })
}
}
})
if (nextRidges.length > 0) {
nextRidges.sort((a, b) => a.size - b.size)
nextRidge = nextRidges[0].ridge
}
}
let currentRidge
if (prevRidge) {
if (currentVectorX === 0) {
const minY = Math.min(currentLine.y1, currentLine.y2, currentRoof.y1, currentRoof.y2)
const maxY = Math.max(currentLine.y1, currentLine.y2, currentRoof.y1, currentRoof.y2)
if (minY <= prevRidge.y1 && maxY >= prevRidge.y1 && minY <= prevRidge.y2 && maxY >= prevRidge.y2) {
currentRidge = prevRidge
}
}
if (currentVectorY === 0) {
const minX = Math.min(currentLine.x1, currentLine.x2, currentRoof.x1, currentRoof.x2)
const maxX = Math.max(currentLine.x1, currentLine.x2, currentRoof.x1, currentRoof.x2)
if (minX <= prevRidge.x1 && maxX >= prevRidge.x1 && minX <= prevRidge.x2 && maxX >= prevRidge.x2) {
currentRidge = prevRidge
}
}
}
if (nextRidge) {
if (currentVectorX === 0) {
const minY = Math.min(currentLine.y1, currentLine.y2, currentRoof.y1, currentRoof.y2)
const maxY = Math.max(currentLine.y1, currentLine.y2, currentRoof.y1, currentRoof.y2)
if (minY <= nextRidge.y1 && maxY >= nextRidge.y1 && minY <= nextRidge.y2 && maxY >= nextRidge.y2) {
currentRidge = nextRidge
}
}
if (currentVectorY === 0) {
const minX = Math.min(currentLine.x1, currentLine.x2, currentRoof.x1, currentRoof.x2)
const maxX = Math.max(currentLine.x1, currentLine.x2, currentRoof.x1, currentRoof.x2)
if (minX <= nextRidge.x1 && maxX >= nextRidge.x1 && minX <= nextRidge.x2 && maxX >= nextRidge.x2) {
currentRidge = nextRidge
}
}
}
if (currentRidge) {
polygonPoints.push({ x: currentRidge.x1, y: currentRidge.y1 }, { x: currentRidge.x2, y: currentRidge.y2 })
/** 기준점이 될 포인트를 찾는다. 기준점 = 지붕선이나 hip 등에 붙지 않은 포인트 */
const checkRidgePoints = [
{ x: currentRidge.x1, y: currentRidge.y1, checkPoint: true },
{ x: currentRidge.x2, y: currentRidge.y2, checkPoint: true },
]
const ridgeEdge = { vertex1: { x: currentRidge.x1, y: currentRidge.y1 }, vertex2: { x: currentRidge.x2, y: currentRidge.y2 } }
/** 포인트가 지붕선과 붙은 경우 */
roof.lines.forEach((line) => {
const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
const is = edgesIntersection(ridgeEdge, lineEdge)
if (is) {
if (Math.abs(is.x - currentRidge.x1) < 1 && Math.abs(is.y - currentRidge.y1) < 1) {
checkRidgePoints[0].checkPoint = false
}
if (Math.abs(is.x - currentRidge.x2) < 1 && Math.abs(is.y - currentRidge.y2) < 1) {
checkRidgePoints[1].checkPoint = false
}
}
})
/** 포인트가 hip과 붙은 경우 */
baseHipLines
.filter((line) => line.x1 !== line.x2 && line.y1 !== line.y2)
.forEach((line) => {
if (
(Math.abs(line.x1 - currentRidge.x1) < 1 && Math.abs(line.y1 - currentRidge.y1) < 1) ||
(Math.abs(line.x2 - currentRidge.x1) < 1 && Math.abs(line.y2 - currentRidge.y1) < 1)
) {
checkRidgePoints[0].checkPoint = false
}
if (
(Math.abs(line.x1 - currentRidge.x2) < 1 && Math.abs(line.y1 - currentRidge.y2) < 1) ||
(Math.abs(line.x2 - currentRidge.x2) < 1 && Math.abs(line.y2 - currentRidge.y2) < 1)
) {
checkRidgePoints[1].checkPoint = false
}
})
const checkRidgePoint = checkRidgePoints.find((point) => point.checkPoint)
if (!checkRidgePoint) return
/** 마루에서 현재라인으로 향하는 외벽선까지의 포인트를 확인 하여 처리*/
let checkEdge
if (currentVectorX === 0) {
checkEdge = {
vertex1: { x: checkRidgePoint.x, y: checkRidgePoint.y },
vertex2: { x: currentRoof.x1, y: checkRidgePoint.y },
}
} else {
checkEdge = {
vertex1: { x: checkRidgePoint.x, y: checkRidgePoint.y },
vertex2: { x: checkRidgePoint.x, y: currentRoof.y1 },
}
}
const currentRoofEdge = { vertex1: { x: currentRoof.x1, y: currentRoof.y1 }, vertex2: { x: currentRoof.x2, y: currentRoof.y2 } }
const isRoofPoint = edgesIntersection(checkEdge, currentRoofEdge)
if (isRoofPoint) {
if (currentVectorX === 0) {
const minY = Math.min(currentRoof.y1, currentRoof.y2, isRoofPoint.y)
const maxY = Math.max(currentRoof.y1, currentRoof.y2, isRoofPoint.y)
polygonPoints.push({ x: isRoofPoint.x, y: minY }, { x: isRoofPoint.x, y: maxY })
} else {
const minX = Math.min(currentRoof.x1, currentRoof.x2, isRoofPoint.x)
const maxX = Math.max(currentRoof.x1, currentRoof.x2, isRoofPoint.x)
polygonPoints.push({ x: minX, y: isRoofPoint.y }, { x: maxX, y: isRoofPoint.y })
}
}
}
}
const uniquePoints = []
polygonPoints.forEach((point) => {
const isAlready = uniquePoints.find((uniquePoint) => uniquePoint.x === point.x && uniquePoint.y === point.y)
if (!isAlready) {
uniquePoints.push(point)
}
})
polygonPoints = getSortedPoint(uniquePoints, baseHipLines)
polygonPoints.forEach((currentPoint, index) => {
const nextPoint = polygonPoints[(index + 1) % polygonPoints.length]
const points = [currentPoint.x, currentPoint.y, nextPoint.x, nextPoint.y]
const ridgeLines = baseGableRidgeLines.filter(
(line) =>
(line.x1 === points[0] && line.y1 === points[1] && line.x2 === points[2] && line.y2 === points[3]) ||
(line.x1 === points[2] && line.y1 === points[3] && line.x2 === points[0] && line.y2 === points[1]),
)
const hipLines = baseHipLines.filter(
(line) =>
(line.x1 === points[0] && line.y1 === points[1] && line.x2 === points[2] && line.y2 === points[3]) ||
(line.x1 === points[2] && line.y1 === points[3] && line.x2 === points[0] && line.y2 === points[1]),
)
/** 이미 존재하는 라인이면 넘긴다.*/
if (ridgeLines.length > 0 || hipLines.length > 0) {
return
}
const hipLine = drawHipLine(points, canvas, roof, textMode, null, currentDegree, currentDegree)
if (currentVectorX === 0) {
if (Math.sign(currentPoint.x - nextPoint.x) === 0) {
hipLine.attributes.actualSize = hipLine.attributes.planeSize
}
} else {
if (Math.sign(currentPoint.y - nextPoint.y) === 0) {
hipLine.attributes.actualSize = hipLine.attributes.planeSize
}
}
baseHipLines.push({ x1: hipLine.x1, y1: hipLine.y1, x2: hipLine.x2, y2: hipLine.y2, line: hipLine })
})
})
/** 케라바 지붕에 연결된 마루 중 처마라인이 그려지지 않은 경우 확*/
baseGableRidgeLines.forEach((ridge) => {
const ridgeVectorX = Math.sign(ridge.x1 - ridge.x2)
const ridgeVectorY = Math.sign(ridge.y1 - ridge.y2)
const firstGableLines = []
const secondGableLines = []
baseGableLines
.filter((line) => (ridgeVectorX === 0 ? line.x1 !== line.x2 : line.y1 !== line.y2))
.filter((line) => (line.x1 === ridge.x1 && line.y1 === ridge.y1) || (line.x2 === ridge.x1 && line.y2 === ridge.y1))
.forEach((line) => firstGableLines.push(line))
baseGableLines
.filter((line) => (ridgeVectorX === 0 ? line.x1 !== line.x2 : line.y1 !== line.y2))
.filter((line) => (line.x1 === ridge.x2 && line.y1 === ridge.y2) || (line.x2 === ridge.x2 && line.y2 === ridge.y2))
.forEach((line) => secondGableLines.push(line))
.filter((line) => (line.x1 === ridge.x1 && line.y1 === ridge.y1) || (line.x2 === ridge.x1 && line.y2 === ridge.y1))
.forEach((line) => firstGableLines.push(line))
baseHipLines
.filter((line) => (ridgeVectorX === 0 ? line.x1 !== line.x2 : line.y1 !== line.y2))
.filter((line) => (line.x1 === ridge.x2 && line.y1 === ridge.y2) || (line.x2 === ridge.x2 && line.y2 === ridge.y2))
.forEach((line) => secondGableLines.push(line))
let degree1, degree2
if (firstGableLines.length < 2 || secondGableLines.length < 2) {
drawBaseLines.forEach((currentBaseLine, index) => {
let prevBaseLine = drawBaseLines[(index - 1 + drawBaseLines.length) % drawBaseLines.length]
let nextBaseLine = drawBaseLines[(index + 1) % drawBaseLines.length]
const currentLine = currentBaseLine.line
const prevLine = prevBaseLine.line
const nextLine = nextBaseLine.line
const prevAngle = calculateAngle(prevLine.startPoint, prevLine.endPoint)
const nextAngle = calculateAngle(nextLine.startPoint, nextLine.endPoint)
if (
gableType.includes(currentLine.attributes?.type) &&
eavesType.includes(prevLine.attributes?.type) &&
eavesType.includes(nextLine.attributes?.type) &&
Big(prevAngle).minus(Big(nextAngle)).abs().eq(180)
) {
if (
ridgeVectorX === 0 &&
currentLine.x1 !== currentLine.x2 &&
((currentLine.x1 <= ridge.x1 && ridge.x1 <= currentLine.x2) || (currentLine.x2 <= ridge.x1 && ridge.x1 <= currentLine.x1))
) {
degree1 = getDegreeByChon(prevLine.attributes.pitch)
degree2 = getDegreeByChon(nextLine.attributes.pitch)
}
if (
ridgeVectorY === 0 &&
currentLine.y1 !== currentLine.y2 &&
((currentLine.y1 <= ridge.y1 && ridge.y1 <= currentLine.y2) || (currentLine.y2 <= ridge.y1 && ridge.y1 <= currentLine.y1))
) {
degree1 = getDegreeByChon(prevLine.attributes.pitch)
degree2 = getDegreeByChon(nextLine.attributes.pitch)
}
}
})
}
if (firstGableLines.length < 2) {
const connectRoof = roof.lines.find(
(line) =>
(line.x1 <= ridge.x1 && line.x2 >= ridge.x1 && line.y1 <= ridge.y1 && line.y2 >= ridge.y1) ||
(line.x2 <= ridge.x1 && line.x1 >= ridge.x1 && line.y2 <= ridge.y1 && line.y1 >= ridge.y1),
)
if (connectRoof) {
let hipPoint1 = [connectRoof.x1, connectRoof.y1, ridge.x1, ridge.y1]
let hipPoint2 = [connectRoof.x2, connectRoof.y2, ridge.x1, ridge.y1]
let intersectPoints1
let intersectPoints2
baseHipLines
.filter((line) => line.x1 !== line.x2 && line.y1 !== line.y2)
.forEach((hip) => {
const line = hip.line
if (
(hipPoint1[0] <= line.x1 && line.x1 <= hipPoint1[2] && hipPoint1[1] <= line.y1 && line.y1 <= hipPoint1[3]) ||
(hipPoint1[2] <= line.x1 && line.x1 <= hipPoint1[0] && hipPoint1[3] <= line.y1 && line.y1 <= hipPoint1[1])
) {
intersectPoints1 = { x: line.x1, y: line.y1 }
}
if (
(hipPoint1[0] <= line.x2 && line.x2 <= hipPoint1[2] && hipPoint1[1] <= line.y2 && line.y2 <= hipPoint1[3]) ||
(hipPoint1[2] <= line.x2 && line.x2 <= hipPoint1[0] && hipPoint1[3] <= line.y2 && line.y2 <= hipPoint1[1])
) {
intersectPoints1 = { x: line.x2, y: line.y2 }
}
if (
(hipPoint2[0] <= line.x1 && line.x1 <= hipPoint2[2] && hipPoint2[1] <= line.y1 && line.y1 <= hipPoint2[3]) ||
(hipPoint2[2] <= line.x1 && line.x1 <= hipPoint2[0] && hipPoint2[3] <= line.y1 && line.y1 <= hipPoint2[1])
) {
intersectPoints2 = { x: line.x1, y: line.y1 }
}
if (
(hipPoint2[0] <= line.x2 && line.x2 <= hipPoint2[2] && hipPoint2[1] <= line.y2 && line.y2 <= hipPoint2[3]) ||
(hipPoint2[2] <= line.x2 && line.x2 <= hipPoint2[0] && hipPoint2[3] <= line.y2 && line.y2 <= hipPoint2[1])
) {
intersectPoints2 = { x: line.x2, y: line.y2 }
}
})
if (intersectPoints1) {
hipPoint1 = [intersectPoints1.x, intersectPoints1.y, ridge.x1, ridge.y1]
}
if (intersectPoints2) {
hipPoint2 = [intersectPoints2.x, intersectPoints2.y, ridge.x1, ridge.y1]
}
if (hipPoint1) {
const hipLines = baseHipLines.filter(
(line) =>
(line.line.x1 === hipPoint1[0] && line.line.y1 === hipPoint1[1] && line.line.x2 === hipPoint1[2] && line.line.y2 === hipPoint1[3]) ||
(line.line.x2 === hipPoint1[0] && line.line.y2 === hipPoint1[1] && line.line.x1 === hipPoint1[2] && line.line.y1 === hipPoint1[3]),
)
const gableLines = baseGableLines.filter(
(line) =>
(line.x1 === hipPoint1[0] && line.y1 === hipPoint1[1] && line.x2 === hipPoint1[2] && line.y2 === hipPoint1[3]) ||
(line.x2 === hipPoint1[0] && line.y2 === hipPoint1[1] && line.x1 === hipPoint1[2] && line.y1 === hipPoint1[3]),
)
if (gableLines.length === 0 && hipLines.length === 0) {
const hipLine1 = drawHipLine(hipPoint1, canvas, roof, textMode, null, degree1, degree1)
baseHipLines.push({ x1: hipLine1.x1, y1: hipLine1.y1, x2: hipLine1.x2, y2: hipLine1.y2, line: hipLine1 })
}
}
if (hipPoint2) {
const hipLines = baseHipLines.filter(
(line) =>
(line.line.x1 === hipPoint2[0] && line.line.y1 === hipPoint2[1] && line.line.x2 === hipPoint2[2] && line.line.y2 === hipPoint2[3]) ||
(line.line.x2 === hipPoint2[0] && line.line.y2 === hipPoint2[1] && line.line.x1 === hipPoint2[2] && line.line.y1 === hipPoint2[3]),
)
const gableLines = baseGableLines.filter(
(line) =>
(line.x1 === hipPoint2[0] && line.y1 === hipPoint2[1] && line.x2 === hipPoint2[2] && line.y2 === hipPoint2[3]) ||
(line.x2 === hipPoint2[0] && line.y2 === hipPoint2[1] && line.x1 === hipPoint2[2] && line.y1 === hipPoint2[3]),
)
if (hipLines.length === 0 && gableLines.length === 0) {
const hipLine2 = drawHipLine(hipPoint2, canvas, roof, textMode, null, degree2, degree2)
baseHipLines.push({ x1: hipLine2.x1, y1: hipLine2.y1, x2: hipLine2.x2, y2: hipLine2.y2, line: hipLine2 })
}
}
}
}
if (secondGableLines.length < 2) {
const connectRoof = roof.lines.find(
(line) =>
(line.x1 <= ridge.x2 && line.x2 >= ridge.x2 && line.y1 <= ridge.y2 && line.y2 >= ridge.y2) ||
(line.x2 <= ridge.x2 && line.x1 >= ridge.x2 && line.y2 <= ridge.y2 && line.y1 >= ridge.y2),
)
if (connectRoof) {
let hipPoint1 = [connectRoof.x1, connectRoof.y1, ridge.x2, ridge.y2]
let hipPoint2 = [connectRoof.x2, connectRoof.y2, ridge.x2, ridge.y2]
let intersectPoints1
let intersectPoints2
baseHipLines
.filter((line) => line.x1 !== line.x2 && line.y1 !== line.y2)
.forEach((hip) => {
const line = hip.line
if (
(hipPoint1[0] <= line.x1 && line.x1 <= hipPoint1[2] && hipPoint1[1] <= line.y1 && line.y1 <= hipPoint1[3]) ||
(hipPoint1[2] <= line.x1 && line.x1 <= hipPoint1[0] && hipPoint1[3] <= line.y1 && line.y1 <= hipPoint1[1])
) {
intersectPoints1 = { x: line.x1, y: line.y1 }
}
if (
(hipPoint1[0] <= line.x2 && line.x2 <= hipPoint1[2] && hipPoint1[1] <= line.y2 && line.y2 <= hipPoint1[3]) ||
(hipPoint1[2] <= line.x2 && line.x2 <= hipPoint1[0] && hipPoint1[3] <= line.y2 && line.y2 <= hipPoint1[1])
) {
intersectPoints1 = { x: line.x2, y: line.y2 }
}
if (
(hipPoint2[0] <= line.x1 && line.x1 <= hipPoint2[2] && hipPoint2[1] <= line.y1 && line.y1 <= hipPoint2[3]) ||
(hipPoint2[2] <= line.x1 && line.x1 <= hipPoint2[0] && hipPoint2[3] <= line.y1 && line.y1 <= hipPoint2[1])
) {
intersectPoints2 = { x: line.x1, y: line.y1 }
}
if (
(hipPoint2[0] <= line.x2 && line.x2 <= hipPoint2[2] && hipPoint2[1] <= line.y2 && line.y2 <= hipPoint2[3]) ||
(hipPoint2[2] <= line.x2 && line.x2 <= hipPoint2[0] && hipPoint2[3] <= line.y2 && line.y2 <= hipPoint2[1])
) {
intersectPoints2 = { x: line.x2, y: line.y2 }
}
})
if (intersectPoints1) {
hipPoint1 = [intersectPoints1.x, intersectPoints1.y, ridge.x2, ridge.y2]
}
if (intersectPoints2) {
hipPoint2 = [intersectPoints2.x, intersectPoints2.y, ridge.x2, ridge.y2]
}
if (hipPoint1) {
const hipLines = baseHipLines.filter(
(line) =>
(line.line.x1 === hipPoint1[0] && line.line.y1 === hipPoint1[1] && line.line.x2 === hipPoint1[2] && line.line.y2 === hipPoint1[3]) ||
(line.line.x2 === hipPoint1[0] && line.line.y2 === hipPoint1[1] && line.line.x1 === hipPoint1[2] && line.line.y1 === hipPoint1[3]),
)
const gableLines = baseGableLines.filter(
(line) =>
(line.x1 === hipPoint1[0] && line.y1 === hipPoint1[1] && line.x2 === hipPoint1[2] && line.y2 === hipPoint1[3]) ||
(line.x2 === hipPoint1[0] && line.y2 === hipPoint1[1] && line.x1 === hipPoint1[2] && line.y1 === hipPoint1[3]),
)
if (hipLines.length === 0 && gableLines.length === 0) {
const hipLine1 = drawHipLine(hipPoint1, canvas, roof, textMode, null, degree1, degree1)
baseHipLines.push({ x1: hipLine1.x1, y1: hipLine1.y1, x2: hipLine1.x2, y2: hipLine1.y2, line: hipLine1 })
}
}
if (hipPoint2) {
const hipLines = baseHipLines.filter(
(line) =>
(line.line.x1 === hipPoint2[0] && line.line.y1 === hipPoint2[1] && line.line.x2 === hipPoint2[2] && line.line.y2 === hipPoint2[3]) ||
(line.line.x2 === hipPoint2[0] && line.line.y2 === hipPoint2[1] && line.line.x1 === hipPoint2[2] && line.line.y1 === hipPoint2[3]),
)
const gableLines = baseGableLines.filter(
(line) =>
(line.x1 === hipPoint2[0] && line.y1 === hipPoint2[1] && line.x2 === hipPoint2[2] && line.y2 === hipPoint2[3]) ||
(line.x2 === hipPoint2[0] && line.y2 === hipPoint2[1] && line.x1 === hipPoint2[2] && line.y1 === hipPoint2[3]),
)
if (hipLines.length === 0 && gableLines.length === 0) {
const hipLine2 = drawHipLine(hipPoint2, canvas, roof, textMode, null, degree2, degree2)
baseHipLines.push({ x1: hipLine2.x1, y1: hipLine2.y1, x2: hipLine2.x2, y2: hipLine2.y2, line: hipLine2 })
}
}
}
}
})
/** 팔작지붕 */
drawHipAndGableFirst.forEach((current) => {
const { currentBaseLine, prevBaseLine, nextBaseLine } = current
const currentLine = currentBaseLine.line
const prevLine = prevBaseLine.line
const nextLine = nextBaseLine.line
let { x1, x2, y1, y2 } = currentBaseLine
const currentDegree = getDegreeByChon(currentLine.attributes.pitch)
const prevDegree = getDegreeByChon(prevLine.attributes.pitch)
const nextDegree = getDegreeByChon(nextLine.attributes.pitch)
let beforePrevLine, afterNextLine
drawBaseLines.forEach((line, index) => {
if (line === prevBaseLine) {
beforePrevLine = drawBaseLines[(index - 1 + drawBaseLines.length) % drawBaseLines.length]
}
if (line === nextBaseLine) {
afterNextLine = drawBaseLines[(index + 1) % drawBaseLines.length]
}
})
/** 팔작지붕 두께*/
const lineWidth =
currentLine.attributes.width < Big(currentLine.attributes.planeSize).div(20).toNumber()
? currentLine.attributes.width
: Big(currentLine.attributes.planeSize).div(20).toNumber()
const currentVectorX = Math.sign(currentLine.x2 - currentLine.x1)
const currentVectorY = Math.sign(currentLine.y2 - currentLine.y1)
const currentMidX = Big(currentLine.x1).plus(Big(currentLine.x2)).div(2)
const currentMidY = Big(currentLine.y1).plus(Big(currentLine.y2)).div(2)
const intersectionRoofs = []
if (currentVectorX === 0) {
const checkEdge = {
vertex1: { x: prevLine.x1, y: currentMidY.toNumber() },
vertex2: { x: currentMidX.toNumber(), y: currentMidY.toNumber() },
}
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.toNumber(), y: prevLine.y1 },
vertex2: { x: currentMidX.toNumber(), y: currentMidY.toNumber() },
}
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(),
})
}
}
})
}
let currentRoof, prevRoof, nextRoof
if (intersectionRoofs.length > 0) {
currentRoof = intersectionRoofs.sort((a, b) => a.size - b.size)[0].line
}
roof.lines.forEach((line, index) => {
if (line === currentRoof) {
prevRoof = roof.lines[(index - 1 + roof.lines.length) % roof.lines.length]
nextRoof = roof.lines[(index + 1) % roof.lines.length]
}
})
// /** 이전, 다음라인의 사잇각의 vector를 구한다. */
let prevVector = getHalfAngleVector(prevRoof, currentRoof)
let nextVector = getHalfAngleVector(currentRoof, nextRoof)
let prevHipVector = { x: Math.sign(prevVector.x), y: Math.sign(prevVector.y) }
let nextHipVector = { x: Math.sign(nextVector.x), y: Math.sign(nextVector.y) }
/** 현재 라인의 길이 추녀마루 길이의 기준이 된다. */
let hipSize = Big(lineWidth).pow(2).plus(Big(lineWidth).pow(2)).sqrt()
/** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/
const prevCheckPoint = {
x: Big(currentRoof.x1).plus(Big(prevHipVector.x).times(lineWidth)),
y: Big(currentRoof.y1).plus(Big(prevHipVector.y).times(lineWidth)),
}
if (!roof.inPolygon(prevCheckPoint)) {
prevHipVector = { x: Math.sign(prevHipVector.x * -1), y: Math.sign(prevHipVector.y * -1) }
}
/** 다음 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/
const nextCheckPoint = {
x: Big(currentRoof.x2).plus(Big(nextHipVector.x).times(lineWidth)),
y: Big(currentRoof.y2).plus(Big(nextHipVector.y).times(lineWidth)),
}
if (!roof.inPolygon(nextCheckPoint)) {
nextHipVector = { x: Math.sign(nextHipVector.x * -1), y: Math.sign(nextHipVector.y * -1) }
}
/** 처마끝에서 올라오는 라인*/
const prevPoint = [
currentRoof.x1,
currentRoof.y1,
Big(currentRoof.x1).plus(Big(prevHipVector.x).times(lineWidth)).toNumber(),
Big(currentRoof.y1).plus(Big(prevHipVector.y).times(lineWidth)).toNumber(),
]
const nextPoint = [
currentRoof.x2,
currentRoof.y2,
Big(currentRoof.x2).plus(Big(nextHipVector.x).times(lineWidth)).toNumber(),
Big(currentRoof.y2).plus(Big(nextHipVector.y).times(lineWidth)).toNumber(),
]
const prevHipLine = drawHipLine(prevPoint, canvas, roof, textMode, null, prevDegree, currentDegree)
const nextHipLine = drawHipLine(nextPoint, canvas, roof, textMode, null, currentDegree, nextDegree)
baseHipLines.push({ x1, y1, x2: prevHipLine.x1, y2: prevHipLine.y1, line: prevHipLine })
baseHipLines.push({ x1: x2, y1: y2, x2: nextHipLine.x1, y2: nextHipLine.y1, line: nextHipLine })
/** 처마끝에서 올라오는 라인에서 가운데로 모이는 라인*/
let midX, midY
if (currentVectorX === 0) {
midX = prevHipLine.x2
midY = currentMidY.toNumber()
} else {
midX = currentMidX.toNumber()
midY = prevHipLine.y2
}
const prevGablePoint = [prevHipLine.x2, prevHipLine.y2, midX, midY]
const nextGablePoint = [nextHipLine.x2, nextHipLine.y2, midX, midY]
const prevGableLine = drawHipLine(prevGablePoint, canvas, roof, textMode, null, prevDegree, currentDegree)
const nextGableLine = drawHipLine(nextGablePoint, canvas, roof, textMode, null, currentDegree, nextDegree)
baseHipLines.push({ x1: prevGableLine.x1, y1: prevGableLine.y1, x2: prevGableLine.x2, y2: prevGableLine.y2, line: prevGableLine })
baseHipLines.push({ x1: nextGableLine.x1, y1: nextGableLine.y1, x2: nextGableLine.x2, y2: nextGableLine.y2, line: nextGableLine })
let oppositeLine
baseLines
.filter((line) => line !== currentLine)
.filter((line) => (currentVectorX === 0 ? line.x1 === line.x2 : line.y1 === line.y2))
.forEach((oppCurrLine) => {
let checkEdge
if (currentVectorX === 0) {
checkEdge = {
vertex1: { x: midX, y: midY },
vertex2: { x: oppCurrLine.x1, y: midY },
}
} else {
checkEdge = {
vertex1: { x: midX, y: midY },
vertex2: { x: midX, y: oppCurrLine.y1 },
}
}
const lineEdge = { vertex1: { x: oppCurrLine.x1, y: oppCurrLine.y1 }, vertex2: { x: oppCurrLine.x2, y: oppCurrLine.y2 } }
const intersection = edgesIntersection(checkEdge, lineEdge)
if (intersection && isPointOnLine(oppCurrLine, intersection)) {
const size =
calcLinePlaneSize({
x1: checkEdge.vertex1.x,
y1: checkEdge.vertex1.y,
x2: intersection.x,
y2: intersection.y,
}) / 10
oppositeLine = { intersection, size, oppCurrLine }
}
})
if (oppositeLine) {
const { intersection, size, oppCurrLine } = oppositeLine
let oppPrevLine, oppNextLine
let oppRoofLine
let ridgePoints = []
let ridgeSize
const ridgeEdge = {
vertex1: { x: currentMidX.toNumber(), y: currentMidY.toNumber() },
vertex2: { x: intersection.x, y: intersection.y },
}
if (prevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES && beforePrevLine.line.attributes.type === LINE_TYPE.WALLLINE.EAVES) {
let beforePrevVector = getHalfAngleVector(prevLine, beforePrevLine)
/** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/
const beforePrevCheckPoint = {
x: Big(prevLine.x1).plus(Big(beforePrevVector.x).times(10)),
y: Big(prevLine.y1).plus(Big(beforePrevVector.y).times(10)),
}
if (!checkWallPolygon.inPolygon(beforePrevCheckPoint)) {
beforePrevVector = { x: -beforePrevVector.x, y: -beforePrevVector.y }
}
const checkBeforeEdge = {
vertex1: { x: prevLine.x1, y: prevLine.y1 },
vertex2: {
x: Big(prevLine.x1).plus(Big(beforePrevVector.x).times(10)).toNumber(),
y: Big(prevLine.y1).plus(Big(beforePrevVector.y).times(10)).toNumber(),
},
}
const isBefore = edgesIntersection(checkBeforeEdge, ridgeEdge)
if (isBefore) {
const size = Big(isBefore.x).minus(midX).abs().pow(2).plus(Big(isBefore.y).minus(midY).abs().pow(2)).sqrt().toNumber()
ridgePoints.push({ x: isBefore.x, y: isBefore.y, size })
}
}
if (nextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES && afterNextLine.line.attributes.type === LINE_TYPE.WALLLINE.EAVES) {
let afterNextVector = getHalfAngleVector(nextLine, afterNextLine)
/** 다음 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/
const afterNextCheckPoint = {
x: Big(nextLine.x2).plus(Big(afterNextVector.x).times(10)),
y: Big(nextLine.y2).plus(Big(afterNextVector.y).times(10)),
}
if (!checkWallPolygon.inPolygon(afterNextCheckPoint)) {
afterNextVector = { x: -afterNextVector.x, y: -afterNextVector.y }
}
const checkAfterEdge = {
vertex1: { x: nextLine.x2, y: nextLine.y2 },
vertex2: {
x: Big(nextLine.x2).plus(Big(afterNextVector.x).times(10)).toNumber(),
y: Big(nextLine.y2).plus(Big(afterNextVector.y).times(10)).toNumber(),
},
}
const isAfter = edgesIntersection(checkAfterEdge, ridgeEdge)
if (isAfter) {
const size = Big(isAfter.x).minus(midX).abs().pow(2).plus(Big(isAfter.y).minus(midY).abs().pow(2)).sqrt().toNumber()
ridgePoints.push({ x: isAfter.x, y: isAfter.y, size })
}
}
baseLines.forEach((line, index) => {
if (line === oppCurrLine) {
oppPrevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length]
oppNextLine = baseLines[(index + 1) % baseLines.length]
}
})
const oppositeRoofs = []
const oppositeMidX = Big(oppCurrLine.x1).plus(Big(oppCurrLine.x2)).div(2)
const oppositeMidY = Big(oppCurrLine.y1).plus(Big(oppCurrLine.y2)).div(2)
const oppositeVectorX = Math.sign(oppCurrLine.x2 - oppCurrLine.x1)
const oppositeVectorY = Math.sign(oppCurrLine.y2 - oppCurrLine.y1)
if (Math.sign(oppCurrLine.x1 - oppCurrLine.x2) === 0) {
const checkEdge = {
vertex1: { x: oppPrevLine.x1, y: oppositeMidY.toNumber() },
vertex2: { x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() },
}
roof.lines
.filter((line) => Math.sign(line.x2 - line.x1) === oppositeVectorX && Math.sign(line.y2 - line.y1) === oppositeVectorY)
.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)) {
oppositeRoofs.push({
line,
intersection,
size: Big(intersection.x).minus(oppositeMidX).abs().pow(2).plus(Big(intersection.y).minus(oppositeMidY).abs().pow(2)).sqrt(),
})
}
}
})
} else {
const checkEdge = {
vertex1: { x: oppositeMidX.toNumber(), y: oppPrevLine.y1 },
vertex2: { x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() },
}
roof.lines
.filter((line) => Math.sign(line.x2 - line.x1) === oppositeVectorX && Math.sign(line.y2 - line.y1) === oppositeVectorY)
.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)) {
oppositeRoofs.push({
line,
intersection,
size: Big(intersection.x).minus(oppositeMidX).abs().pow(2).plus(Big(intersection.y).minus(oppositeMidY).abs().pow(2)).sqrt(),
})
}
}
})
}
if (oppositeRoofs.length > 0) {
oppRoofLine = oppositeRoofs.sort((a, b) => a.size - b.size)[0].line
}
if (oppCurrLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) {
if (oppRoofLine) {
const oppRoofSize = Big(oppRoofLine.attributes.planeSize).div(20)
const ridgeEdge = {
vertex1: { x: currentMidX.toNumber(), y: currentMidY.toNumber() },
vertex2: { x: intersection.x, y: intersection.y },
}
if (oppPrevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) {
let oppPrevVector = getHalfAngleVector(oppCurrLine, oppPrevLine)
oppPrevVector = { x: oppPrevVector.x, y: oppPrevVector.y }
/** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/
const prevCheckPoint = {
x: Big(oppCurrLine.x1).plus(Big(oppPrevVector.x).times(10)),
y: Big(oppCurrLine.y1).plus(Big(oppPrevVector.y).times(10)),
}
if (!checkWallPolygon.inPolygon(prevCheckPoint)) {
oppPrevVector = { x: -oppPrevVector.x, y: -oppPrevVector.y }
}
const oppPrevHipEdge = {
vertex1: {
x: oppRoofLine.x1,
y: oppRoofLine.y1,
},
vertex2: {
x: Big(oppRoofLine.x1).plus(Big(oppPrevVector.x).times(oppRoofSize)).toNumber(),
y: Big(oppRoofLine.y1).plus(Big(oppPrevVector.y).times(oppRoofSize)).toNumber(),
},
}
const isOppPrev = edgesIntersection(oppPrevHipEdge, ridgeEdge)
if (
isOppPrev &&
isPointOnLine(
{
x1: oppPrevHipEdge.vertex1.x,
y1: oppPrevHipEdge.vertex1.y,
x2: oppPrevHipEdge.vertex2.x,
y2: oppPrevHipEdge.vertex2.y,
},
isOppPrev,
)
) {
const size = Big(isOppPrev.x).minus(midX).abs().pow(2).plus(Big(isOppPrev.y).minus(midY).abs().pow(2)).sqrt().toNumber()
ridgePoints.push({ x: isOppPrev.x, y: isOppPrev.y, size })
}
}
if (oppNextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) {
let oppNextVector = getHalfAngleVector(oppCurrLine, oppNextLine)
oppNextVector = { x: oppNextVector.x, y: oppNextVector.y }
/** 다음 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/
const nextCheckPoint = {
x: Big(oppCurrLine.x2).plus(Big(oppNextVector.x).times(10)),
y: Big(oppCurrLine.y2).plus(Big(oppNextVector.y).times(10)),
}
if (!checkWallPolygon.inPolygon(nextCheckPoint)) {
oppNextVector = { x: -oppNextVector.x, y: -oppNextVector.y }
}
const oppNextHipEdge = {
vertex1: {
x: oppRoofLine.x2,
y: oppRoofLine.y2,
},
vertex2: {
x: Big(oppRoofLine.x2).plus(Big(oppNextVector.x).times(oppRoofSize)).toNumber(),
y: Big(oppRoofLine.y2).plus(Big(oppNextVector.y).times(oppRoofSize)).toNumber(),
},
}
const isOppNext = edgesIntersection(oppNextHipEdge, ridgeEdge)
if (
isOppNext &&
isPointOnLine(
{
x1: oppNextHipEdge.vertex1.x,
y1: oppNextHipEdge.vertex1.y,
x2: oppNextHipEdge.vertex2.x,
y2: oppNextHipEdge.vertex2.y,
},
isOppNext,
)
) {
const size = Big(isOppNext.x).minus(midX).abs().pow(2).plus(Big(isOppNext.y).minus(midY).abs().pow(2)).sqrt().toNumber()
ridgePoints.push({ x: isOppNext.x, y: isOppNext.y, size })
}
}
}
} else if (oppCurrLine.attributes.type === LINE_TYPE.WALLLINE.HIPANDGABLE) {
ridgeSize = Big(size).minus(Big(oppCurrLine.attributes.width)).plus(Big(oppCurrLine.attributes.offset)).toNumber()
}
if (ridgePoints.length > 0) {
ridgeSize = ridgePoints.sort((a, b) => a.size - b.size)[0].size
}
if (ridgeSize > 0 && baseRidgeCount < getMaxRidge(baseLines.length)) {
const ridgeVectorX = Math.sign(midX - intersection.x)
const ridgeVectorY = Math.sign(midY - intersection.y)
const ridgePoint = [
midX,
midY,
Big(midX).minus(Big(ridgeSize).times(ridgeVectorX)).toNumber(),
Big(midY).minus(Big(ridgeSize).times(ridgeVectorY)).toNumber(),
]
const ridgeLine = drawRidgeLine(ridgePoint, canvas, roof, textMode)
baseRidgeLines.push(ridgeLine)
baseRidgeCount++
} else {
let ridges = []
baseGableRidgeLines
.filter(
(ridge) =>
(Math.abs(ridge.x1 - midX) < 1 && Math.abs(ridge.y1 - midY) < 1) || (Math.abs(ridge.x2 - midX) < 1 && Math.abs(ridge.y2 - midY) < 1),
)
.forEach((ridge) => ridges.push(ridge))
baseRidgeLines
.filter(
(ridge) =>
(Math.abs(ridge.x1 - midX) < 1 && Math.abs(ridge.y1 - midY) < 1) || (Math.abs(ridge.x2 - midX) < 1 && Math.abs(ridge.y2 - midY) < 1),
)
.forEach((ridge) => ridges.push(ridge))
if (ridges.length > 0) {
return
}
// canvas.remove(prevGableLine)
// canvas.remove(nextGableLine)
baseHipLines = baseHipLines.filter((base) => base.line !== prevGableLine && base.line !== nextGableLine)
const points = [prevGableLine.x1, prevGableLine.y1, nextGableLine.x1, nextGableLine.y1]
const gableLine = drawRoofLine(points, canvas, roof, textMode)
baseHipLines.push({ x1: gableLine.x1, y1: gableLine.y1, x2: gableLine.x2, y2: gableLine.y2, line: gableLine })
}
}
})
/** 벽과 이어진 마루를 그린다. */
drawWallRidgeLine.forEach((current) => {
const { currentBaseLine, prevBaseLine, nextBaseLine } = current
const currentLine = currentBaseLine.line
const prevLine = prevBaseLine.line
const nextLine = nextBaseLine.line
let beforePrevLine, afterNextLine
const prevDegree = getDegreeByChon(prevLine.attributes.pitch)
const nextDegree = getDegreeByChon(nextLine.attributes.pitch)
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)
/** 이전 라인의 전라인, 다음 라인의 다음라인을 찾는다 */
drawBaseLines.forEach((line, index) => {
if (line === prevBaseLine) {
beforePrevLine = drawBaseLines[(index - 1 + drawBaseLines.length) % drawBaseLines.length]
}
if (line === nextBaseLine) {
afterNextLine = drawBaseLines[(index + 1) % drawBaseLines.length]
}
})
let oppositeLine
baseLines
.filter((line) => line !== currentLine)
.filter((line) => (currentVectorX === 0 ? line.x1 === line.x2 : line.y1 === line.y2))
.forEach((oppCurrLine) => {
let checkEdge
if (currentVectorX === 0) {
checkEdge = {
vertex1: { x: currentMidX, y: currentMidY },
vertex2: { x: oppCurrLine.x1, y: currentMidY },
}
} else {
checkEdge = {
vertex1: { x: currentMidX, y: currentMidY },
vertex2: { x: currentMidX, y: oppCurrLine.y1 },
}
}
const lineEdge = { vertex1: { x: oppCurrLine.x1, y: oppCurrLine.y1 }, vertex2: { x: oppCurrLine.x2, y: oppCurrLine.y2 } }
const intersection = edgesIntersection(checkEdge, lineEdge)
if (intersection && isPointOnLine(oppCurrLine, intersection)) {
const size =
calcLinePlaneSize({
x1: checkEdge.vertex1.x,
y1: checkEdge.vertex1.y,
x2: intersection.x,
y2: intersection.y,
}) / 10
oppositeLine = { intersection, size, oppCurrLine }
}
})
const ridgePoints = []
if (oppositeLine) {
const { intersection, size, oppCurrLine } = oppositeLine
ridgePoints.push({ x: intersection.x, y: intersection.y, size })
const oppPrevLine = baseLines.find((line) => line.x2 === oppCurrLine.x1 && line.y2 === oppCurrLine.y1)
const oppNextLine = baseLines.find((line) => line.x1 === oppCurrLine.x2 && line.y1 === oppCurrLine.y2)
const oppositeRoofs = []
const oppositeMidX = Big(oppCurrLine.x1).plus(Big(oppCurrLine.x2)).div(2)
const oppositeMidY = Big(oppCurrLine.y1).plus(Big(oppCurrLine.y2)).div(2)
const oppositeVectorX = Math.sign(oppCurrLine.x2 - oppCurrLine.x1)
const oppositeVectorY = Math.sign(oppCurrLine.y2 - oppCurrLine.y1)
let oppRoofLine
if (Math.sign(oppCurrLine.x1 - oppCurrLine.x2) === 0) {
const checkEdge = {
vertex1: { x: oppPrevLine.x1, y: oppositeMidY.toNumber() },
vertex2: { x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() },
}
roof.lines
.filter((line) => Math.sign(line.x2 - line.x1) === oppositeVectorX && Math.sign(line.y2 - line.y1) === oppositeVectorY)
.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)) {
oppositeRoofs.push({
line,
intersection,
size: Big(intersection.x).minus(oppositeMidX).abs().pow(2).plus(Big(intersection.y).minus(oppositeMidY).abs().pow(2)).sqrt(),
})
}
}
})
} else {
const checkEdge = {
vertex1: { x: oppositeMidX.toNumber(), y: oppPrevLine.y1 },
vertex2: { x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() },
}
roof.lines
.filter((line) => Math.sign(line.x2 - line.x1) === oppositeVectorX && Math.sign(line.y2 - line.y1) === oppositeVectorY)
.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)) {
oppositeRoofs.push({
line,
intersection,
size: Big(intersection.x).minus(oppositeMidX).abs().pow(2).plus(Big(intersection.y).minus(oppositeMidY).abs().pow(2)).sqrt(),
})
}
}
})
}
if (oppositeRoofs.length > 0) {
oppRoofLine = oppositeRoofs.sort((a, b) => a.size - b.size)[0].line
}
if (oppRoofLine) {
const ridgeEdge = { vertex1: { x: currentMidX, y: currentMidY }, vertex2: { x: intersection.x, y: intersection.y } }
if (oppCurrLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) {
const oppRoofSize = Big(oppRoofLine.attributes.planeSize).div(20)
if (oppPrevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) {
let oppPrevVector = getHalfAngleVector(oppCurrLine, oppPrevLine)
oppPrevVector = { x: oppPrevVector.x, y: oppPrevVector.y }
/** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/
const prevCheckPoint = {
x: Big(oppCurrLine.x1).plus(Big(oppPrevVector.x).times(10)),
y: Big(oppCurrLine.y1).plus(Big(oppPrevVector.y).times(10)),
}
if (!checkWallPolygon.inPolygon(prevCheckPoint)) {
oppPrevVector = { x: -oppPrevVector.x, y: -oppPrevVector.y }
}
const oppPrevHipEdge = {
vertex1: {
x: oppRoofLine.x1,
y: oppRoofLine.y1,
},
vertex2: {
x: Big(oppRoofLine.x1).plus(Big(oppPrevVector.x).times(oppRoofSize)).toNumber(),
y: Big(oppRoofLine.y1).plus(Big(oppPrevVector.y).times(oppRoofSize)).toNumber(),
},
}
const isOppPrev = edgesIntersection(oppPrevHipEdge, ridgeEdge)
if (
isOppPrev &&
isPointOnLine(
{
x1: oppPrevHipEdge.vertex1.x,
y1: oppPrevHipEdge.vertex1.y,
x2: oppPrevHipEdge.vertex2.x,
y2: oppPrevHipEdge.vertex2.y,
},
isOppPrev,
)
) {
const size = Big(isOppPrev.x).minus(currentMidX).abs().pow(2).plus(Big(isOppPrev.y).minus(currentMidY).abs().pow(2)).sqrt().toNumber()
ridgePoints.push({ x: isOppPrev.x, y: isOppPrev.y, size })
}
}
if (oppNextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) {
let oppNextVector = getHalfAngleVector(oppCurrLine, oppNextLine)
oppNextVector = { x: oppNextVector.x, y: oppNextVector.y }
/** 다음 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/
const nextCheckPoint = {
x: Big(oppCurrLine.x2).plus(Big(oppNextVector.x).times(10)),
y: Big(oppCurrLine.y2).plus(Big(oppNextVector.y).times(10)),
}
if (!checkWallPolygon.inPolygon(nextCheckPoint)) {
oppNextVector = { x: -oppNextVector.x, y: -oppNextVector.y }
}
const oppNextHipEdge = {
vertex1: {
x: oppRoofLine.x2,
y: oppRoofLine.y2,
},
vertex2: {
x: Big(oppRoofLine.x2).plus(Big(oppNextVector.x).times(oppRoofSize)).toNumber(),
y: Big(oppRoofLine.y2).plus(Big(oppNextVector.y).times(oppRoofSize)).toNumber(),
},
}
const isOppNext = edgesIntersection(oppNextHipEdge, ridgeEdge)
if (
isOppNext &&
isPointOnLine(
{
x1: oppNextHipEdge.vertex1.x,
y1: oppNextHipEdge.vertex1.y,
x2: oppNextHipEdge.vertex2.x,
y2: oppNextHipEdge.vertex2.y,
},
isOppNext,
)
) {
const size = Big(isOppNext.x).minus(currentMidX).abs().pow(2).plus(Big(isOppNext.y).minus(currentMidY).abs().pow(2)).sqrt().toNumber()
ridgePoints.push({ x: isOppNext.x, y: isOppNext.y, size })
}
}
} else {
baseHipLines.forEach((line) => {
const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
const intersection = edgesIntersection(ridgeEdge, lineEdge)
if (intersection) {
if (isPointOnLine(line, intersection)) {
const size = Big(intersection.x).minus(currentMidX).abs().pow(2).plus(Big(intersection.y).minus(currentMidY).abs().pow(2)).sqrt()
ridgePoints.push({ x: intersection.x, y: intersection.y, size })
}
}
})
}
if (prevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES && beforePrevLine.line.attributes.type === LINE_TYPE.WALLLINE.EAVES) {
let beforePrevVector = getHalfAngleVector(prevLine, beforePrevLine)
const checkPoint = {
x: Big(prevLine.x1).plus(Big(beforePrevVector.x).times(10)),
y: Big(prevLine.y1).plus(Big(beforePrevVector.y).times(10)),
}
if (!checkWallPolygon.inPolygon(checkPoint)) {
beforePrevVector = { x: -beforePrevVector.x, y: -beforePrevVector.y }
}
const hipEdge = {
vertex1: { x: prevLine.x1, y: prevLine.y1 },
vertex2: {
x: Big(prevLine.x1).plus(Big(beforePrevVector.x).times(prevLine.attributes.planeSize)).toNumber(),
y: Big(prevLine.y1).plus(Big(beforePrevVector.y).times(prevLine.attributes.planeSize)).toNumber(),
},
}
const isLines = []
drawBaseLines
.filter((base) => base !== currentBaseLine && base !== prevBaseLine && base !== beforePrevLine)
.forEach((base) => {
const lineEdge = { vertex1: { x: base.line.x1, y: base.line.y1 }, vertex2: { x: base.line.x2, y: base.line.y2 } }
const intersection = edgesIntersection(hipEdge, lineEdge)
if (intersection && isPointOnLine(base.line, intersection)) {
const size = Big(intersection.x)
.minus(prevLine.x1)
.abs()
.pow(2)
.plus(Big(intersection.y).minus(prevLine.y1).abs().pow(2))
.sqrt()
.toNumber()
isLines.push({ intersection, size })
}
})
if (isLines.length > 0) {
const hipSize = getAdjacent(isLines.sort((a, b) => a.size - b.size)[0].size / 2)
const hipPoint = [
prevLine.x1,
prevLine.y1,
Big(prevLine.x1).plus(Big(beforePrevVector.x).times(hipSize)).toNumber(),
Big(prevLine.y1).plus(Big(beforePrevVector.y).times(hipSize)).toNumber(),
]
const hipEdge = { vertex1: { x: hipPoint[0], y: hipPoint[1] }, vertex2: { x: hipPoint[2], y: hipPoint[3] } }
const intersection = edgesIntersection(ridgeEdge, hipEdge)
if (
intersection &&
(isPointOnLine({ x1: hipPoint[0], y1: hipPoint[1], x2: hipPoint[2], y2: hipPoint[3] }, intersection) ||
(Math.abs(hipPoint[0] - intersection.x) < 1 && Math.abs(hipPoint[1] - intersection.y) < 1) ||
(Math.abs(hipPoint[2] - intersection.x) < 1 && Math.abs(hipPoint[3] - intersection.y) < 1))
) {
const size = Big(intersection.x)
.minus(currentMidX)
.abs()
.pow(2)
.plus(Big(intersection.y).minus(currentMidY).abs().pow(2))
.sqrt()
.toNumber()
ridgePoints.push({ x: intersection.x, y: intersection.y, size })
}
}
}
if (nextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES && afterNextLine.line.attributes.type === LINE_TYPE.WALLLINE.EAVES) {
let afterNextVector = getHalfAngleVector(nextLine, afterNextLine)
const checkPoint = {
x: Big(nextLine.x1).plus(Big(afterNextVector.x).times(10)),
y: Big(nextLine.y1).plus(Big(afterNextVector.y).times(10)),
}
if (!checkWallPolygon.inPolygon(checkPoint)) {
afterNextVector = { x: -afterNextVector.x, y: -afterNextVector.y }
}
const hipEdge = {
vertex1: { x: nextLine.x2, y: nextLine.y2 },
vertex2: {
x: Big(nextLine.x2).plus(Big(afterNextVector.x).times(nextLine.attributes.planeSize)).toNumber(),
y: Big(nextLine.y2).plus(Big(afterNextVector.y).times(nextLine.attributes.planeSize)).toNumber(),
},
}
const isLines = []
drawBaseLines
.filter((base) => base !== currentBaseLine && base !== nextBaseLine && base !== afterNextLine)
.forEach((base) => {
const lineEdge = { vertex1: { x: base.line.x1, y: base.line.y1 }, vertex2: { x: base.line.x2, y: base.line.y2 } }
const intersection = edgesIntersection(hipEdge, lineEdge)
if (intersection && isPointOnLine(base.line, intersection)) {
const size = Big(intersection.x)
.minus(nextLine.x2)
.abs()
.pow(2)
.plus(Big(intersection.y).minus(nextLine.y2).abs().pow(2))
.sqrt()
.toNumber()
isLines.push({ intersection, size })
}
})
if (isLines.length > 0) {
const hipSize = getAdjacent(isLines.sort((a, b) => a.size - b.size)[0].size / 2)
const hipPoint = [
nextLine.x2,
nextLine.y2,
Big(nextLine.x2).plus(Big(afterNextVector.x).times(hipSize)).toNumber(),
Big(nextLine.y2).plus(Big(afterNextVector.y).times(hipSize)).toNumber(),
]
const hipEdge = { vertex1: { x: hipPoint[0], y: hipPoint[1] }, vertex2: { x: hipPoint[2], y: hipPoint[3] } }
const intersection = edgesIntersection(ridgeEdge, hipEdge)
if (
intersection &&
(isPointOnLine({ x1: hipPoint[0], y1: hipPoint[1], x2: hipPoint[2], y2: hipPoint[3] }, intersection) ||
(Math.abs(hipPoint[0] - intersection.x) < 1 && Math.abs(hipPoint[1] - intersection.y) < 1) ||
(Math.abs(hipPoint[2] - intersection.x) < 1 && Math.abs(hipPoint[3] - intersection.y) < 1))
) {
const size = Big(intersection.x)
.minus(currentMidX)
.abs()
.pow(2)
.plus(Big(intersection.y).minus(currentMidY).abs().pow(2))
.sqrt()
.toNumber()
ridgePoints.push({ x: intersection.x, y: intersection.y, size })
}
}
}
}
}
if (ridgePoints.length === 0 || baseRidgeCount >= getMaxRidge(baseLines.length)) return
const ridgeEndPoint = ridgePoints.sort((a, b) => a.size - b.size)[0]
const ridgePoint = { x1: currentMidX, y1: currentMidY, x2: ridgeEndPoint.x, y2: ridgeEndPoint.y }
/** 포인트가 지붕밖에 있는 경우 조정*/
if (!roof.inPolygon({ x: ridgePoint.x1, y: ridgePoint.y1 })) {
const checkEdge = { vertex1: { x: ridgePoint.x2, y: ridgePoint.y2 }, vertex2: { x: ridgePoint.x1, y: ridgePoint.y1 } }
const isPoints = []
roof.lines.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 && isPointOnLine(line, intersection)) {
if (
Math.sign(checkEdge.vertex1.x - checkEdge.vertex2.x) === Math.sign(checkEdge.vertex1.x - intersection.x) &&
Math.sign(checkEdge.vertex1.y - checkEdge.vertex2.y) === Math.sign(checkEdge.vertex1.y - intersection.y)
) {
const size = Big(intersection.x)
.minus(ridgePoint.x2)
.abs()
.pow(2)
.plus(Big(intersection.y).minus(ridgePoint.y2).abs().pow(2))
.sqrt()
.toNumber()
isPoints.push({ x: intersection.x, y: intersection.y, size })
}
}
})
if (isPoints.length > 0) {
const newPoint = isPoints.sort((a, b) => a.size - b.size)[0]
ridgePoint.x1 = newPoint.x
ridgePoint.y1 = newPoint.y
}
}
if (!roof.inPolygon({ x: ridgePoint.x2, y: ridgePoint.y2 })) {
const checkEdge = { vertex1: { x: ridgePoint.x1, y: ridgePoint.y1 }, vertex2: { x: ridgePoint.x2, y: ridgePoint.y2 } }
const isPoints = []
roof.lines.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 && isPointOnLine(line, intersection)) {
if (
Math.sign(checkEdge.vertex1.x - checkEdge.vertex2.x) === Math.sign(checkEdge.vertex1.x - intersection.x) &&
Math.sign(checkEdge.vertex1.y - checkEdge.vertex2.y) === Math.sign(checkEdge.vertex1.y - intersection.y)
) {
const size = Big(intersection.x)
.minus(ridgePoint.x1)
.abs()
.pow(2)
.plus(Big(intersection.y).minus(ridgePoint.y1).abs().pow(2))
.sqrt()
.toNumber()
isPoints.push({ x: intersection.x, y: intersection.y, size })
}
}
})
if (isPoints.length > 0) {
const newPoint = isPoints.sort((a, b) => a.size - b.size)[0]
ridgePoint.x2 = newPoint.x
ridgePoint.y2 = newPoint.y
}
}
const ridge = drawRidgeLine([ridgePoint.x1, ridgePoint.y1, ridgePoint.x2, ridgePoint.y2], canvas, roof, textMode)
baseRidgeLines.push(ridge)
baseRidgeCount++
/** 현재 라인의 지붕 라인을 찾는다. */
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 prevHipPoint = [currentRoof.x1, currentRoof.y1, currentMidX, currentMidY]
const nextHipPoint = [currentRoof.x2, currentRoof.y2, currentMidX, currentMidY]
const prevHipLine = drawHipLine(prevHipPoint, canvas, roof, textMode, null, prevDegree, prevDegree)
const nextHipLine = drawHipLine(nextHipPoint, canvas, roof, textMode, null, nextDegree, nextDegree)
baseHipLines.push({ x1: prevHipLine.x1, y1: prevHipLine.y1, x2: prevHipLine.x2, y2: prevHipLine.y2, line: prevHipLine })
baseHipLines.push({ x1: nextHipLine.x1, y1: nextHipLine.y1, x2: nextHipLine.x2, y2: nextHipLine.y2, line: nextHipLine })
}
})
/** ⨆ 모양 처마에 추녀마루를 그린다. */
drawEavesFirstLines.forEach((current) => {
// 확인용 라인, 포인트 제거
const { currentBaseLine, prevBaseLine, nextBaseLine } = current
const currentLine = currentBaseLine.line
const prevLine = prevBaseLine.line
const nextLine = nextBaseLine.line
let { x1, x2, y1, y2, size } = currentBaseLine
let beforePrevLine, afterNextLine
/** 이전 라인의 경사 */
const prevDegree = getDegreeByChon(prevLine.attributes.pitch)
/** 다음 라인의 경사 */
const currentDegree = getDegreeByChon(currentLine.attributes.pitch)
/** 이전 라인의 전라인, 다음 라인의 다음라인을 찾는다 */
drawBaseLines.forEach((line, index) => {
if (line === prevBaseLine) {
beforePrevLine = drawBaseLines[(index - 1 + drawBaseLines.length) % drawBaseLines.length]
}
if (line === nextBaseLine) {
afterNextLine = drawBaseLines[(index + 1) % drawBaseLines.length]
}
})
/** 이전, 다음라인의 사잇각의 vector를 구한다. */
let prevVector = getHalfAngleVector(prevLine, currentLine)
let nextVector = getHalfAngleVector(currentLine, nextLine)
let prevHipVector = { x: prevVector.x, y: prevVector.y }
let nextHipVector = { x: nextVector.x, y: nextVector.y }
/** 각 라인의 흐름 방향을 확인한다. */
const currentAngle = calculateAngle(currentLine.startPoint, currentLine.endPoint)
const prevAngle = calculateAngle(prevLine.startPoint, prevLine.endPoint)
const nextAngle = calculateAngle(nextLine.startPoint, nextLine.endPoint)
const beforePrevAngle = calculateAngle(beforePrevLine.line.startPoint, beforePrevLine.line.endPoint)
const afterNextAngle = calculateAngle(afterNextLine.line.startPoint, afterNextLine.line.endPoint)
/** 현재 라인의 길이 추녀마루 길이의 기준이 된다. */
let currentSize = Big(size).div(10)
/** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/
const prevCheckPoint = {
x: Big(x1).plus(Big(prevHipVector.x).times(10)),
y: Big(y1).plus(Big(prevHipVector.y).times(10)),
}
if (!checkWallPolygon.inPolygon(prevCheckPoint)) {
prevHipVector = { x: -prevHipVector.x, y: -prevHipVector.y }
}
/** 다음 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/
const nextCheckPoint = {
x: Big(x2).plus(Big(nextHipVector.x).times(10)),
y: Big(y2).plus(Big(nextHipVector.y).times(10)),
}
if (!checkWallPolygon.inPolygon(nextCheckPoint)) {
nextHipVector = { x: -nextHipVector.x, y: -nextHipVector.y }
}
/** 현재 라인의 길이를 기준으로 추녀 마루의 길이를 삼각함수를 사용하여 판단한다.*/
let hipLength = currentSize.div(2).pow(2).plus(currentSize.div(2).pow(2)).sqrt()
/**
* 현재 라인에서 2번째 전 라인과 2번째 후 라인의 각도가 같을때 -_- 와 같은 형태로 판단하고
* 맞은 편 외벽선까지의 거리를 확인후 currentSize 를 조정한다.
*/
if (beforePrevLine === afterNextLine || (currentAngle === beforePrevAngle && currentAngle === afterNextAngle)) {
console.log('4각 ::::::::: ')
const xVector = Big(nextLine.x2).minus(Big(nextLine.x1))
const yVector = Big(nextLine.y2).minus(Big(nextLine.y1))
const currentMidX = Big(x1).plus(Big(x2)).div(2)
const currentMidY = Big(y1).plus(Big(y2)).div(2)
const midLineEdge = {
vertex1: { x: currentMidX.toNumber(), y: currentMidY.toNumber() },
vertex2: {
x: currentMidX.plus(currentSize.times(Math.sign(xVector))).toNumber(),
y: currentMidY.plus(currentSize.times(Math.sign(yVector))).toNumber(),
},
}
/** 현재 라인의 중심 지점에서 현재라인의 길이만큼 다음라인의 방향만큼 거리를 확인한다*/
baseLines
.filter((line) => line !== currentLine)
.forEach((line) => {
const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
const intersection = edgesIntersection(midLineEdge, lineEdge)
/** 현재라인의 길이만큼 거리가 모자라면 해당 길이 만큼을 현재라인의 길이로 판단하고 나머지 계산을 진행한다.*/
if (intersection && !intersection.isIntersectionOutside) {
const intersectionSize = Big(intersection.x)
.minus(Big(currentMidX))
.plus(Big(intersection.y).minus(Big(currentMidY)))
.abs()
if (intersectionSize.lt(currentSize)) {
currentSize = intersectionSize
}
}
})
hipLength = currentSize.div(2).pow(2).plus(currentSize.div(2).pow(2)).sqrt()
} else {
if (currentAngle !== beforePrevAngle && currentAngle !== afterNextAngle && beforePrevLine !== afterNextLine) {
const beforePrevX1 = beforePrevLine.x1,
beforePrevY1 = beforePrevLine.y1
const afterNextX2 = afterNextLine.x2,
afterNextY2 = afterNextLine.y2
/** beforePrevLine 과 afterNextLine 을 연결하는 사각형의 경우 6각으로 판단 */
const connectBAPoint = { x1: afterNextX2, y1: afterNextY2, x2: beforePrevX1, y2: beforePrevY1 }
const isConnect =
baseLines.filter(
(line) =>
line.x1 === connectBAPoint.x1 && line.y1 === connectBAPoint.y1 && line.x2 === connectBAPoint.x2 && line.y2 === connectBAPoint.y2,
).length > 0
/** 6각 */
if (isConnect) {
const checkScale = currentSize.pow(2).plus(currentSize.pow(2)).sqrt()
const intersectBaseLine = []
if (baseHipLines.filter((line) => line.x1 === x1 && line.y1 === y1).length === 0) {
const prevEndPoint = {
x: Big(x1).plus(Big(prevHipVector.x).times(checkScale)),
y: Big(y1).plus(Big(prevHipVector.y).times(checkScale)),
}
baseLines
.filter((line) => line !== currentLine && line !== prevLine && line !== nextLine)
.forEach((line) => {
const intersection = edgesIntersection(
{ vertex1: { x: x1, y: y1 }, vertex2: prevEndPoint },
{ vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } },
)
if (intersection && !intersection.isIntersectionOutside) {
intersectBaseLine.push({
intersection,
distance: Big(intersection.x)
.minus(Big(x1))
.pow(2)
.plus(Big(intersection.y).minus(Big(y1)).pow(2))
.sqrt(),
})
}
})
}
if (baseHipLines.filter((line) => line.x1 === x2 && line.y1 === y2).length === 0) {
const nextEndPoint = {
x: Big(x2).plus(Big(nextHipVector.x).times(checkScale)),
y: Big(y2).plus(Big(nextHipVector.y).times(checkScale)),
}
baseLines
.filter((line) => line !== currentLine && line !== prevLine && line !== nextLine)
.forEach((line) => {
const intersection = edgesIntersection(
{ vertex1: { x: x2, y: y2 }, vertex2: nextEndPoint },
{ vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } },
)
if (intersection && !intersection.isIntersectionOutside) {
intersectBaseLine.push({
intersection,
distance: Big(intersection.x)
.minus(Big(x2))
.pow(2)
.plus(Big(intersection.y).minus(Big(y2)).pow(2))
.sqrt(),
})
}
})
}
const intersection = intersectBaseLine.reduce((prev, current) => (prev.distance < current.distance ? prev : current), intersectBaseLine[0])
if (intersection) {
hipLength = intersection.distance
}
} else {
const rightAngleLine = baseLines
.filter(
(line) =>
line !== currentLine &&
line !== prevLine &&
line !== nextLine &&
(prevAngle === calculateAngle(line.startPoint, line.endPoint) || nextAngle === calculateAngle(line.startPoint, line.endPoint)),
)
.filter((line) => {
const index = baseLines.indexOf(line)
const prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length]
const nextLine = baseLines[(index + 1) % baseLines.length]
const prevAngle = calculateAngle(prevLine.startPoint, prevLine.endPoint)
const nextAngle = calculateAngle(nextLine.startPoint, nextLine.endPoint)
switch (prevAngle) {
case 90:
return nextAngle === -90
case -90:
return nextAngle === 90
case 0:
return nextAngle === 180
case 180:
return nextAngle === 0
}
})
const oppositeCurrentLine = baseLines
.filter((line) => {
const angle = calculateAngle(line.startPoint, line.endPoint)
switch (currentAngle) {
case 90:
return angle === -90
case -90:
return angle === 90
case 0:
return angle === 180
case 180:
return angle === 0
}
})
.filter((line) => {
const index = baseLines.indexOf(line)
const prevLine = baseLines[(index - 1 + baseLines.length) % baseLines.length]
const nextLine = baseLines[(index + 1) % baseLines.length]
const prevAngle = calculateAngle(prevLine.startPoint, prevLine.endPoint)
const nextAngle = calculateAngle(nextLine.startPoint, nextLine.endPoint)
switch (prevAngle) {
case 90:
return nextAngle === -90
case -90:
return nextAngle === 90
case 0:
return nextAngle === 180
case 180:
return nextAngle === 0
}
})
let checkHipPoints
if (baseHipLines.filter((line) => line.x1 === x1 && line.y1 === y1).length === 0) {
checkHipPoints = [
x1,
y1,
Big(x1).plus(Big(prevHipVector.x).times(hipLength)).round(2).toNumber(),
Big(y1).plus(Big(prevHipVector.y).times(hipLength)).round(2).toNumber(),
]
}
if (baseHipLines.filter((line) => line.x1 === x2 && line.y1 === y2).length === 0) {
checkHipPoints = [
x2,
y2,
Big(x2).plus(Big(nextHipVector.x).times(hipLength)).round(2).toNumber(),
Big(y2).plus(Big(nextHipVector.y).times(hipLength)).round(2).toNumber(),
]
}
if (checkHipPoints) {
const intersectPoints = []
rightAngleLine.forEach((line) => {
const intersection = edgesIntersection(
{
vertex1: { x: checkHipPoints[0], y: checkHipPoints[1] },
vertex2: { x: checkHipPoints[2], y: checkHipPoints[3] },
},
{ vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } },
)
if (intersection) {
intersectPoints.push({
intersection,
distance: Big(intersection.x)
.minus(Big(checkHipPoints[0]))
.pow(2)
.plus(Big(intersection.y).minus(Big(checkHipPoints[1])).pow(2))
.sqrt()
.toNumber(),
})
}
})
oppositeCurrentLine.forEach((line) => {
const intersection = edgesIntersection(
{
vertex1: { x: checkHipPoints[0], y: checkHipPoints[1] },
vertex2: { x: checkHipPoints[2], y: checkHipPoints[3] },
},
{ vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } },
)
if (intersection) {
intersectPoints.push({
intersection,
distance: Big(intersection.x)
.minus(Big(checkHipPoints[0]))
.pow(2)
.plus(Big(intersection.y).minus(Big(checkHipPoints[1])).pow(2))
.sqrt()
.toNumber(),
})
}
})
if (intersectPoints.length > 0) {
const intersection = intersectPoints.sort((a, b) => a.distance - b.distance)[0]
hipLength = getAdjacent(intersection.distance) / 2
}
}
}
}
}
let prevHipLine, nextHipLine
/** 이전라인과의 연결지점에 추녀마루를 그린다. */
if (baseHipLines.filter((line) => line.x1 === x1 && line.y1 === y1).length === 0) {
const prevEndPoint = {
x: Big(x1).plus(Big(prevHipVector.x).times(hipLength)).round(1),
y: Big(y1).plus(Big(prevHipVector.y).times(hipLength)).round(1),
}
if (!roof.inPolygon({ x: prevEndPoint.x.toNumber(), y: prevEndPoint.y.toNumber() })) {
const checkEdge = { vertex1: { x: prevEndPoint.x.toNumber(), y: prevEndPoint.y.toNumber() }, vertex2: { x: x1, y: y1 } }
const intersectionPoints = []
roof.lines.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 && !intersection.isIntersectionOutside) {
const size = Big(intersection.x)
.minus(Big(prevEndPoint.x))
.pow(2)
.plus(Big(intersection.y).minus(Big(prevEndPoint.y)).pow(2))
.sqrt()
.toNumber()
intersectionPoints.push({ intersection, size })
}
})
if (intersectionPoints.length > 0) {
const intersection = intersectionPoints.sort((a, b) => a.size - b.size)[0].intersection
prevEndPoint.x = Big(intersection.x)
prevEndPoint.y = Big(intersection.y)
}
}
const overlapLine = baseHipLines.find(
(line) =>
isPointOnLineNew({ x1: x1, y1: y1, x2: prevEndPoint.x.toNumber(), y2: prevEndPoint.y.toNumber() }, { x: line.x1, y: line.y1 }) ||
isPointOnLineNew({ x1: x1, y1: y1, x2: prevEndPoint.x.toNumber(), y2: prevEndPoint.y.toNumber() }, { x: line.x2, y: line.y2 }),
)
if (overlapLine) {
let size1, size2
if (
isPointOnLineNew({ x1: x1, y1: y1, x2: prevEndPoint.x.toNumber(), y2: prevEndPoint.y.toNumber() }, { x: overlapLine.x1, y: overlapLine.y1 })
) {
size1 = Math.sqrt(Math.pow(x1 - overlapLine.x1, 2) + Math.pow(y1 - overlapLine.y1, 2))
}
if (
isPointOnLineNew({ x1: x1, y1: y1, x2: prevEndPoint.x.toNumber(), y2: prevEndPoint.y.toNumber() }, { x: overlapLine.x2, y: overlapLine.y2 })
) {
size2 = Math.sqrt(Math.pow(x1 - overlapLine.x2, 2) + Math.pow(y1 - overlapLine.y2, 2))
}
if (size1 && size2) {
if (size1 < size2) {
prevEndPoint.x = Big(overlapLine.x1)
prevEndPoint.y = Big(overlapLine.y1)
} else {
prevEndPoint.x = Big(overlapLine.x2)
prevEndPoint.y = Big(overlapLine.y2)
}
} else {
if (size1) {
prevEndPoint.x = Big(overlapLine.x1)
prevEndPoint.y = Big(overlapLine.y1)
}
if (size2) {
prevEndPoint.x = Big(overlapLine.x2)
prevEndPoint.y = Big(overlapLine.y2)
}
}
}
const intersectRidgeLine = []
baseRidgeLines.forEach((line) => {
const intersection = edgesIntersection(
{ vertex1: { x: x1, y: y1 }, vertex2: prevEndPoint },
{ vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } },
)
if (intersection && !intersection.isIntersectionOutside) {
intersectRidgeLine.push({
intersection,
distance: Big(intersection.x)
.minus(Big(x1))
.pow(2)
.plus(Big(intersection.y).minus(Big(y1)).pow(2))
.sqrt(),
})
}
})
const intersectRidge = intersectRidgeLine.reduce((prev, current) => (prev.distance < current.distance ? prev : current), intersectRidgeLine[0])
if (intersectRidge) {
prevEndPoint.x = Big(intersectRidge.intersection.x).round(1)
prevEndPoint.y = Big(intersectRidge.intersection.y).round(1)
}
const xVector = Math.sign(Big(prevEndPoint.x).minus(Big(x1)).neg().toNumber())
const yVector = Math.sign(Big(prevEndPoint.y).minus(Big(y1)).neg().toNumber())
/** 지붕 선까지의 곂침에 따른 길이를 파악하기 위한 scale*/
let scale = Big(currentLine.attributes.offset).pow(2).plus(Big(prevLine.attributes.offset).pow(2)).sqrt()
scale = scale.eq(0) ? Big(1) : scale
/** scale 만큼 추녀마루를 늘려서 겹치는 포인트를 확인한다. */
const hipEdge = {
vertex1: { x: Big(x1).plus(scale.times(xVector)).toNumber(), y: Big(y1).plus(scale.times(yVector)).toNumber() },
vertex2: prevEndPoint,
}
let intersectPoints = []
/** 외벽선에서 추녀 마루가 겹치는 경우에 대한 확인*/
roof.lines.forEach((line) => {
const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
const intersection = edgesIntersection(hipEdge, lineEdge)
if (
intersection &&
isPointOnLineNew(line, { x: intersection.x, y: intersection.y }) &&
Math.sign(prevEndPoint.x - x1) === Math.sign(prevEndPoint.x - intersection.x) &&
Math.sign(prevEndPoint.y - y1) === Math.sign(prevEndPoint.y - intersection.y)
) {
const intersectSize = prevEndPoint.x
.minus(Big(intersection.x))
.pow(2)
.plus(prevEndPoint.y.minus(Big(intersection.y)).pow(2))
.abs()
.sqrt()
.toNumber()
intersectPoints.push({
intersection,
size: intersectSize,
})
}
})
intersectPoints = intersectPoints.reduce((prev, current) => {
return prev.size < current.size ? prev : current
}, intersectPoints[0])
/** 겹치는 외벽선이 있을때 추녀마루를 외벽선 까지 늘려서 수정*/
if (intersectPoints && intersectPoints.intersection) {
prevHipLine = drawHipLine(
[intersectPoints.intersection.x, intersectPoints.intersection.y, prevEndPoint.x.toNumber(), prevEndPoint.y.toNumber()],
canvas,
roof,
textMode,
null,
prevDegree,
currentDegree,
)
baseHipLines.push({ x1, y1, x2: prevEndPoint.x.toNumber(), y2: prevEndPoint.y.toNumber(), line: prevHipLine })
}
}
if (baseHipLines.filter((line) => line.x1 === x2 && line.y1 === y2).length === 0) {
const nextEndPoint = {
x: Big(x2).plus(Big(nextHipVector.x).times(hipLength)).round(1),
y: Big(y2).plus(Big(nextHipVector.y).times(hipLength)).round(1),
}
if (!roof.inPolygon({ x: nextEndPoint.x.toNumber(), y: nextEndPoint.y.toNumber() })) {
const checkEdge = { vertex1: { x: nextEndPoint.x.toNumber(), y: nextEndPoint.y.toNumber() }, vertex2: { x: x2, y: y2 } }
const intersectionPoints = []
roof.lines.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 && !intersection.isIntersectionOutside) {
const size = Big(intersection.x)
.minus(Big(nextEndPoint.x))
.pow(2)
.plus(Big(intersection.y).minus(Big(nextEndPoint.y)).pow(2))
.sqrt()
.toNumber()
intersectionPoints.push({ intersection, size })
}
})
if (intersectionPoints.length > 0) {
const intersection = intersectionPoints.sort((a, b) => a.size - b.size)[0].intersection
nextEndPoint.x = Big(intersection.x)
nextEndPoint.y = Big(intersection.y)
}
}
// const overlapLine = baseHipLines.find((line) => isPointOnLine(line, { x: nextEndPoint.x.toNumber(), y: nextEndPoint.y.toNumber() }))
const overlapLine = baseHipLines.find(
(line) =>
isPointOnLineNew({ x1: x2, y1: y2, x2: nextEndPoint.x.toNumber(), y2: nextEndPoint.y.toNumber() }, { x: line.x1, y: line.y1 }) ||
isPointOnLineNew({ x1: x2, y1: y2, x2: nextEndPoint.x.toNumber(), y2: nextEndPoint.y.toNumber() }, { x: line.x2, y: line.y2 }),
)
if (overlapLine) {
let size1, size2
if (
isPointOnLineNew({ x1: x2, y1: y2, x2: nextEndPoint.x.toNumber(), y2: nextEndPoint.y.toNumber() }, { x: overlapLine.x1, y: overlapLine.y1 })
) {
size1 = Math.sqrt(Math.pow(x2 - overlapLine.x1, 2) + Math.pow(y2 - overlapLine.y1, 2))
}
if (
isPointOnLineNew({ x1: x2, y1: y2, x2: nextEndPoint.x.toNumber(), y2: nextEndPoint.y.toNumber() }, { x: overlapLine.x2, y: overlapLine.y2 })
) {
size2 = Math.sqrt(Math.pow(x2 - overlapLine.x2, 2) + Math.pow(y2 - overlapLine.y2, 2))
}
if (size1 && size2) {
if (size1 < size2) {
nextEndPoint.x = Big(overlapLine.x1)
nextEndPoint.y = Big(overlapLine.y1)
} else {
nextEndPoint.x = Big(overlapLine.x2)
nextEndPoint.y = Big(overlapLine.y2)
}
} else {
if (size1) {
nextEndPoint.x = Big(overlapLine.x1)
nextEndPoint.y = Big(overlapLine.y1)
}
if (size2) {
nextEndPoint.x = Big(overlapLine.x2)
nextEndPoint.y = Big(overlapLine.y2)
}
}
}
const intersectRidgeLine = []
baseRidgeLines.forEach((line) => {
const intersection = edgesIntersection(
{ vertex1: { x: x2, y: y2 }, vertex2: nextEndPoint },
{ vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } },
)
if (intersection && !intersection.isIntersectionOutside) {
intersectRidgeLine.push({
intersection,
distance: Big(intersection.x)
.minus(Big(x1))
.pow(2)
.plus(Big(intersection.y).minus(Big(y1)).pow(2))
.sqrt(),
})
}
})
const intersectRidge = intersectRidgeLine.reduce((prev, current) => (prev.distance < current.distance ? prev : current), intersectRidgeLine[0])
if (intersectRidge) {
nextEndPoint.x = Big(intersectRidge.intersection.x).round(1)
nextEndPoint.y = Big(intersectRidge.intersection.y).round(1)
}
const xVector = Math.sign(Big(nextEndPoint.x).minus(Big(x2)).neg().toNumber())
const yVector = Math.sign(Big(nextEndPoint.y).minus(Big(y2)).neg().toNumber())
let scale = Big(currentLine.attributes.offset).pow(2).plus(Big(nextLine.attributes.offset).pow(2)).sqrt()
scale = scale.eq(0) ? Big(1) : scale
const hipEdge = {
vertex1: { x: Big(x2).plus(scale.times(xVector)).toNumber(), y: Big(y2).plus(scale.times(yVector)).toNumber() },
vertex2: nextEndPoint,
}
let intersectPoints = []
roof.lines.forEach((line) => {
const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
const intersection = edgesIntersection(hipEdge, lineEdge)
if (
intersection &&
isPointOnLineNew(line, { x: intersection.x, y: intersection.y }) &&
Math.sign(nextEndPoint.x - x2) === Math.sign(nextEndPoint.x - intersection.x) &&
Math.sign(nextEndPoint.y - y2) === Math.sign(nextEndPoint.y - intersection.y)
) {
const intersectSize = nextEndPoint.x
.minus(Big(intersection.x))
.pow(2)
.plus(nextEndPoint.y.minus(Big(intersection.y)).pow(2))
.abs()
.sqrt()
.toNumber()
intersectPoints.push({
intersection,
size: intersectSize,
})
}
})
intersectPoints = intersectPoints.reduce((prev, current) => {
return prev.size < current.size ? prev : current
}, intersectPoints[0])
if (intersectPoints && intersectPoints.intersection) {
nextHipLine = drawHipLine(
[intersectPoints.intersection.x, intersectPoints.intersection.y, nextEndPoint.x.toNumber(), nextEndPoint.y.toNumber()],
canvas,
roof,
textMode,
null,
prevDegree,
currentDegree,
)
baseHipLines.push({
x1: x2,
y1: y2,
x2: nextEndPoint.x.toNumber(),
y2: nextEndPoint.y.toNumber(),
line: nextHipLine,
})
}
}
/** 두 선이 교차하면 해당 포인트까지로 선 조정*/
if (prevHipLine !== undefined && nextHipLine !== undefined) {
const prevEdge = {
vertex1: { x: prevHipLine.x1, y: prevHipLine.y1 },
vertex2: { x: prevHipLine.x2, y: prevHipLine.y2 },
}
const nextEdge = {
vertex1: { x: nextHipLine.x1, y: nextHipLine.y1 },
vertex2: { x: nextHipLine.x2, y: nextHipLine.y2 },
}
const intersection = edgesIntersection(prevEdge, nextEdge)
if (intersection) {
/** 포인트 조정*/
baseHipLines
.filter((line) => line.line === prevHipLine || line.line === nextHipLine)
.forEach((line) => {
line.x2 = intersection.x
line.y2 = intersection.y
line.line.set({ x2: intersection.x, y2: intersection.y })
})
prevHipLine.x2 = intersection.x
prevHipLine.y2 = intersection.y
const prevSize = reCalculateSize(prevHipLine)
prevHipLine.attributes.planeSize = prevSize.planeSize
prevHipLine.attributes.actualSize = prevSize.actualSize
prevHipLine.fire('modified')
nextHipLine.x2 = intersection.x
nextHipLine.y2 = intersection.y
const nextSize = reCalculateSize(nextHipLine)
nextHipLine.attributes.planeSize = nextSize.planeSize
nextHipLine.attributes.actualSize = nextSize.actualSize
nextHipLine.fire('modified')
canvas.renderAll()
}
}
/** 두 추녀마루가 한점에서 만나는 경우 해당 점을 기점으로 마루를 작성한다.*/
if (
prevHipLine !== undefined &&
nextHipLine !== undefined &&
Big(prevHipLine.x2).minus(Big(nextHipLine.x2)).abs().lte(1) &&
Big(prevHipLine.y2).minus(Big(nextHipLine.y2)).abs().lte(1)
) {
const startPoint = { x: prevHipLine.x2, y: prevHipLine.y2 }
let ridgeSize = 0
const currentMidX = Big(currentLine.x2).plus(Big(currentLine.x1)).div(2)
const currentMidY = Big(currentLine.y2).plus(Big(currentLine.y1)).div(2)
const xVector = Big(currentMidX).minus(Big(startPoint.x)).round(0, Big.roundDown)
const yVector = Big(currentMidY).minus(Big(startPoint.y)).round(0, Big.roundDown)
if (beforePrevLine === afterNextLine) {
console.log('4각 :::::::: ')
const oppositeMidX = Big(beforePrevLine.x2).plus(Big(beforePrevLine.x1)).div(2)
const oppositeMidY = Big(beforePrevLine.y2).plus(Big(beforePrevLine.y1)).div(2)
if (beforePrevLine.line.attributes.type === LINE_TYPE.WALLLINE.EAVES) {
ridgeSize = oppositeMidX
.minus(Big(startPoint.x))
.abs()
.pow(2)
.plus(oppositeMidY.minus(Big(startPoint.y)).abs().pow(2))
.sqrt()
.minus(Big(beforePrevLine.line.attributes.planeSize).div(20))
} else {
let width = 0
if (beforePrevLine.line.attributes.type === LINE_TYPE.WALLLINE.JERKINHEAD) {
width = beforePrevLine.line.attributes.width / 2
} else if (beforePrevLine.line.attributes.type === LINE_TYPE.WALLLINE.HIPANDGABLE) {
width = beforePrevLine.line.attributes.width
}
const checkEdge = { vertex1: { x: startPoint.x, y: startPoint.y }, vertex2: { x: oppositeMidX.toNumber(), y: oppositeMidY.toNumber() } }
const vectorX = Math.sign(startPoint.x - oppositeMidX.toNumber())
const vectorY = Math.sign(startPoint.y - oppositeMidY.toNumber())
const oppositeRoofPoints = []
roof.lines.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 && Math.sign(startPoint.x - intersection.x) === vectorX && Math.sign(startPoint.y - intersection.y) === vectorY) {
const size = Big(intersection.x)
.minus(Big(startPoint.x))
.pow(2)
.plus(Big(intersection.y).minus(Big(startPoint.y)).pow(2))
.sqrt()
.toNumber()
oppositeRoofPoints.push({ intersection, size })
}
})
if (oppositeRoofPoints.length > 0) {
const oppositeRoofPoint = oppositeRoofPoints.sort((a, b) => a.size - b.size)[0].intersection
ridgeSize = Big(oppositeRoofPoint.x)
.minus(Big(startPoint.x))
.abs()
.pow(2)
.plus(Big(oppositeRoofPoint.y).minus(Big(startPoint.y)).abs().pow(2))
.minus(width)
.sqrt()
}
}
} else {
if (gableType.includes(beforePrevLine.line.attributes.type) || gableType.includes(afterNextLine.line.attributes.type)) {
const oppositeLine = gableType.includes(beforePrevLine.line.attributes.type) ? beforePrevLine.line : afterNextLine.line
const oppositeAngle = calculateAngle(oppositeLine.startPoint, oppositeLine.endPoint)
let checkEdge
if (Math.sign(oppositeLine.x1 - oppositeLine.x2) === 0) {
checkEdge = { vertex1: startPoint, vertex2: { x: oppositeLine.x1, y: startPoint.y } }
} else {
checkEdge = { vertex1: startPoint, vertex2: { x: startPoint.x, y: oppositeLine.y1 } }
}
if (currentAngle === oppositeAngle) {
const oppositeEdge = {
vertex1: { x: oppositeLine.x1, y: oppositeLine.y1 },
vertex2: { x: oppositeLine.x2, y: oppositeLine.y2 },
}
const intersection = edgesIntersection(oppositeEdge, checkEdge)
if (intersection) {
ridgeSize = Big(intersection.x)
.minus(Big(startPoint.x))
.pow(2)
.plus(Big(intersection.y).minus(Big(startPoint.y)).pow(2))
.sqrt()
}
} else {
const intersectPoints = []
roof.lines
.filter(
(line) =>
Math.sign(oppositeLine.x1 - oppositeLine.x2) === Math.sign(line.x1 - line.x2) &&
Math.sign(oppositeLine.y1 - oppositeLine.y2) === Math.sign(line.y1 - line.y2),
)
.forEach((line) => {
const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
const intersection = edgesIntersection(lineEdge, checkEdge)
if (intersection) {
const size = Big(startPoint.x)
.minus(Big(intersection.x))
.pow(2)
.plus(Big(startPoint.y).minus(Big(intersection.y)).pow(2))
.sqrt()
.toNumber()
intersectPoints.push({ intersection, size })
}
})
intersectPoints.sort((a, b) => a.size - b.size)
if (intersectPoints.length > 0) {
ridgeSize = Big(intersectPoints[0].size)
}
}
} else {
/** baseLines 에서 가장 작은 x1과 가장 큰 x1, 가장 작은 y1과 가장 큰 y1을 계산*/
let minX = Infinity
let maxX = -Infinity
let minY = Infinity
let maxY = -Infinity
baseLines.forEach((line) => {
if (line.x1 < minX) {
minX = line.x1
}
if (line.x1 > maxX) {
maxX = line.x1
}
if (line.y1 < minY) {
minY = line.y1
}
if (line.y1 > maxY) {
maxY = line.y1
}
})
const checkLength = Big(maxX)
.minus(Big(minX))
.pow(2)
.plus(Big(maxY).minus(Big(minY)).pow(2))
.sqrt()
const checkEdges = {
vertex1: { x: startPoint.x, y: startPoint.y },
vertex2: {
x: Big(startPoint.x).minus(checkLength.times(Math.sign(xVector))),
y: Big(startPoint.y).minus(checkLength.times(Math.sign(yVector))),
},
}
/** 맞은편 벽 까지의 길이 판단을 위한 교차되는 line*/
const intersectBaseLine = []
baseLines
.filter((line) => {
/** currentAngle 의 반대 각도인 라인 */
const angle = calculateAngle(line.startPoint, line.endPoint)
switch (currentAngle) {
case 90:
return angle === -90
case -90:
return angle === 90
case 0:
return angle === 180
case 180:
return angle === 0
}
})
.filter((line) => {
const currentMinX = Math.min(x1, x2)
const currentMaxX = Math.max(x1, x2)
const currentMinY = Math.min(y1, y2)
const currentMaxY = Math.max(y1, y2)
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)
/** currentLine 의 안쪽에 있거나 currentLine이 line의 안쪽에 있는 라인 */
if (Big(currentLine.y1).minus(Big(currentLine.y2)).abs().lte(1)) {
return (
(currentMinX <= lineMinX && lineMinX <= currentMaxX) ||
(currentMinX <= lineMaxX && lineMaxX <= currentMaxX) ||
(lineMinX <= currentMinX && currentMinX <= lineMaxX) ||
(lineMinX <= currentMaxX && currentMaxX <= lineMaxX)
)
} else {
return (
(currentMinY <= lineMinY && lineMinY <= currentMaxY) ||
(currentMinY <= lineMaxY && lineMaxY <= currentMaxY) ||
(lineMinY <= currentMinY && currentMinY <= lineMaxY) ||
(lineMinY <= currentMaxY && currentMaxY <= lineMaxY)
)
}
})
.forEach((line) => {
const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
const intersection = edgesIntersection(checkEdges, lineEdge)
if (intersection) {
intersectBaseLine.push({ intersection, line })
}
})
/** 맞은편 라인 */
const oppositeLine = intersectBaseLine.reduce((prev, current) => {
const prevDistance = Big(prev.intersection.x)
.minus(Big(startPoint.x))
.pow(2)
.plus(Big(prev.intersection.y).minus(Big(startPoint.y)).pow(2))
.sqrt()
const currentDistance = Big(current.intersection.x)
.minus(Big(startPoint.x))
.pow(2)
.plus(Big(current.intersection.y).minus(Big(startPoint.y)).pow(2))
.sqrt()
return prevDistance < currentDistance ? prev : current
}, intersectBaseLine[0])
/** 맞은편 라인까지의 길이 = 전체 길이 - 현재라인의 길이 */
const oppositeSize = oppositeLine
? Big(oppositeLine.intersection.x)
.minus(Big(startPoint.x))
.pow(2)
.plus(Big(oppositeLine.intersection.y).minus(Big(startPoint.y)).pow(2))
.sqrt()
.minus(currentSize.div(2))
.round(1)
.toNumber()
: Infinity
/** 이전, 다음 라인중 길이가 짧은 길이*/
const lineMinSize =
prevBaseLine.size < nextBaseLine.size ? Big(prevBaseLine.size).div(10).toNumber() : Big(nextBaseLine.size).div(10).toNumber()
/** 마루의 길이는 이전 다음 라인중 짧은것의 길이와 현재라인부터 맞은편 라인까지의 길이에서 현재 라인의 길이를 뺀 것중 짧은 길이 */
ridgeSize = Big(Math.min(oppositeSize, lineMinSize))
}
}
if (ridgeSize.gt(0) && baseRidgeCount < getMaxRidge(baseLines.length)) {
const points = [
startPoint.x,
startPoint.y,
Big(startPoint.x)
.minus(ridgeSize.times(Math.sign(xVector)))
.toNumber(),
Big(startPoint.y)
.minus(ridgeSize.times(Math.sign(yVector)))
.toNumber(),
]
const oppositeHipPoints = []
const ridgeEdge = { vertex1: { x: points[0], y: points[1] }, vertex2: { x: points[2], y: points[3] } }
if (prevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES && beforePrevLine.line.attributes.type === LINE_TYPE.WALLLINE.EAVES) {
let hipVector = getHalfAngleVector(prevLine, beforePrevLine.line)
/** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/
const checkPoint = {
x: Big(prevLine.x1).plus(Big(hipVector.x).times(10)),
y: Big(prevLine.y1).plus(Big(hipVector.y).times(10)),
}
if (!checkWallPolygon.inPolygon(checkPoint)) {
hipVector = { x: -hipVector.x, y: -hipVector.y }
}
const hipEdge = {
vertex1: { x: prevLine.x1, y: prevLine.y1 },
vertex2: {
x: Big(prevLine.x1).plus(Big(hipVector.x).times(prevLine.attributes.planeSize)).toNumber(),
y: Big(prevLine.y1).plus(Big(hipVector.y).times(prevLine.attributes.planeSize)).toNumber(),
},
}
const intersection = edgesIntersection(ridgeEdge, hipEdge)
if (intersection) {
const size = Big(points[0] - intersection.x)
.abs()
.pow(2)
.plus(
Big(points[1] - intersection.y)
.abs()
.pow(2),
)
.sqrt()
.toNumber()
oppositeHipPoints.push({ x: intersection.x, y: intersection.y, size })
}
}
if (nextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES && afterNextLine.line.attributes.type === LINE_TYPE.WALLLINE.EAVES) {
let hipVector = getHalfAngleVector(nextLine, afterNextLine.line)
/** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/
const checkPoint = {
x: Big(nextLine.x1).plus(Big(hipVector.x).times(10)),
y: Big(nextLine.y1).plus(Big(hipVector.y).times(10)),
}
if (!checkWallPolygon.inPolygon(checkPoint)) {
hipVector = { x: -hipVector.x, y: -hipVector.y }
}
const hipEdge = {
vertex1: { x: nextLine.x2, y: nextLine.y2 },
vertex2: {
x: Big(nextLine.x2).plus(Big(hipVector.x).times(nextLine.attributes.planeSize)).toNumber(),
y: Big(nextLine.y2).plus(Big(hipVector.y).times(nextLine.attributes.planeSize)).toNumber(),
},
}
const intersection = edgesIntersection(ridgeEdge, hipEdge)
if (intersection) {
const size = Big(points[0] - intersection.x)
.abs()
.pow(2)
.plus(
Big(points[1] - intersection.y)
.abs()
.pow(2),
)
.sqrt()
.toNumber()
oppositeHipPoints.push({ x: intersection.x, y: intersection.y, size })
}
}
if (oppositeHipPoints.length > 0) {
const oppositeHipPoint = oppositeHipPoints.sort((a, b) => a.size - b.size)[0]
points[2] = oppositeHipPoint.x
points[3] = oppositeHipPoint.y
}
/** 동일 라인이 있는지 확인. */
if (
baseRidgeLines.filter((line) => {
const ridgeMinX = Math.min(points[0], points[2])
const ridgeMaxX = Math.max(points[0], points[2])
const ridgeMinY = Math.min(points[1], points[3])
const ridgeMaxY = Math.max(points[1], points[3])
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)
return ridgeMinX === lineMinX && ridgeMaxX === lineMaxX && ridgeMinY === lineMinY && ridgeMaxY === lineMaxY
}).length > 0
) {
return
}
const ridgeLine = drawRidgeLine(points, canvas, roof, textMode)
if (
oppositeHipPoints.length === 0 &&
(gableType.includes(beforePrevLine.line.attributes.type) || gableType.includes(afterNextLine.line.attributes.type))
) {
baseGableRidgeLines.push(ridgeLine)
} else {
baseRidgeLines.push(ridgeLine)
}
baseRidgeCount = baseRidgeCount + 1
/** 포인트 조정*/
baseHipLines
.filter((line) => line.line === prevHipLine || line.line === nextHipLine)
.forEach((line) => {
line.x2 = points[0]
line.y2 = points[1]
line.line.set({ x2: points[0], y2: points[1] })
// line.line.fire('modified')
})
prevHipLine.x2 = points[0]
prevHipLine.y2 = points[1]
const prevSize = reCalculateSize(prevHipLine)
prevHipLine.attributes.planeSize = prevSize.planeSize
prevHipLine.attributes.actualSize = prevSize.actualSize
prevHipLine.fire('modified')
nextHipLine.x2 = points[0]
nextHipLine.y2 = points[1]
const nextSize = reCalculateSize(nextHipLine)
nextHipLine.attributes.planeSize = nextSize.planeSize
nextHipLine.attributes.actualSize = nextSize.actualSize
nextHipLine.fire('modified')
canvas.renderAll()
if (
oppositeHipPoints.length === 0 &&
(gableType.includes(beforePrevLine.line.attributes.type) || gableType.includes(afterNextLine.line.attributes.type))
) {
const oppositeLine = gableType.includes(beforePrevLine.line.attributes.type) ? beforePrevLine.line : afterNextLine.line
if (Math.sign(ridgeLine.x1 - ridgeLine.x2) === 0) {
const gableVector = Math.sign(ridgeLine.x1 - oppositeLine.x1)
const prevVector = ridgeLine.x1 === prevHipLine.x1 ? Math.sign(ridgeLine.x1 - prevHipLine.x2) : Math.sign(ridgeLine.x2 - prevHipLine.x1)
const firstHipLine = gableVector === prevVector ? prevHipLine : nextHipLine
const firstDegree =
gableVector === Math.sign(ridgeLine.x1 - prevLine.x1)
? getDegreeByChon(prevLine.attributes.pitch)
: getDegreeByChon(nextLine.attributes.pitch)
const oppositeRoofPoints = [
ridgeLine.x2,
ridgeLine.y2,
ridgeLine.x1 === firstHipLine.x1 ? firstHipLine.x2 : firstHipLine.x1,
ridgeLine.y2,
]
const oppositeRoofLine = drawHipLine(oppositeRoofPoints, canvas, roof, textMode, null, firstDegree, firstDegree)
baseHipLines.push({
x1: oppositeRoofLine.x1,
y1: oppositeRoofLine.y1,
x2: oppositeRoofLine.x2,
y2: oppositeRoofLine.y2,
line: oppositeRoofLine,
})
const connectRoofPoints = [oppositeRoofLine.x2, oppositeRoofLine.y2]
if (ridgeLine.x1 === firstHipLine.x1) {
connectRoofPoints.push(firstHipLine.x2, firstHipLine.y2)
} else {
connectRoofPoints.push(firstHipLine.x1, firstHipLine.y1)
}
const connectRoofLine = drawRoofLine(connectRoofPoints, canvas, roof, textMode)
baseHipLines.push({
x1: connectRoofLine.x1,
y1: connectRoofLine.y1,
x2: connectRoofLine.x2,
y2: connectRoofLine.y2,
line: connectRoofLine,
})
/** 다른 방향의 추녀마루 */
const secondHipLine = gableVector === prevVector ? nextHipLine : prevHipLine
const secondDegree =
gableVector === Math.sign(ridgeLine.x1 - prevLine.x1)
? getDegreeByChon(nextLine.attributes.pitch)
: getDegreeByChon(prevLine.attributes.pitch)
const intersections = []
const checkEdge = {
vertex1: { x: ridgeLine.x2, y: ridgeLine.y2 },
vertex2: { x: ridgeLine.x1 === secondHipLine.x1 ? secondHipLine.x2 : secondHipLine.x1, y: ridgeLine.y2 },
}
baseGableRidgeLines
.filter((ridge) => ridge !== ridgeLine)
.forEach((ridge) => {
const ridgeEdge = { vertex1: { x: ridge.x1, y: ridge.y1 }, vertex2: { x: ridge.x2, y: ridge.y2 } }
const intersection = edgesIntersection(ridgeEdge, checkEdge)
if (intersection && !intersections.includes(intersection)) {
const size = Big(intersection.x)
.minus(Big(ridgeLine.x2))
.pow(2)
.plus(Big(intersection.y).minus(Big(ridgeLine.y2)).pow(2))
.sqrt()
.toNumber()
intersections.push({ intersection, size, ridge })
}
})
intersections.sort((a, b) => a.size - b.size)
if (intersections.length > 0) {
const intersection = intersections[0].intersection
const intersectRidge = intersections[0].ridge
const oppositeRoofPoints = [ridgeLine.x2, ridgeLine.y2, intersection.x, intersection.y]
const oppositeRoofLine = drawHipLine(oppositeRoofPoints, canvas, roof, textMode, null, secondDegree, secondDegree)
baseHipLines.push({
x1: oppositeLine.x1,
y1: oppositeLine.y1,
x2: oppositeLine.x2,
y2: oppositeLine.y2,
line: oppositeRoofLine,
})
const ridgeVector = Math.sign(ridgeLine.y1 - ridgeLine.y2)
const connectRoofPoints = [oppositeRoofLine.x2, oppositeRoofLine.y2]
if (ridgeVector === Math.sign(oppositeRoofLine.y1 - intersectRidge.y2)) {
connectRoofPoints.push(intersectRidge.x2, intersectRidge.y2)
} else {
connectRoofPoints.push(intersectRidge.x1, intersectRidge.y1)
}
const connectRoofLine = drawRoofLine(connectRoofPoints, canvas, roof, textMode)
baseHipLines.push({
x1: connectRoofLine.x1,
y1: connectRoofLine.y1,
x2: connectRoofLine.x2,
y2: connectRoofLine.y2,
line: connectRoofLine,
})
}
} else {
const gableVector = Math.sign(ridgeLine.y1 - oppositeLine.y1)
const prevVector = ridgeLine.y1 === prevHipLine.y1 ? Math.sign(ridgeLine.y1 - prevHipLine.y2) : Math.sign(ridgeLine.y1 - prevHipLine.y1)
/** 마루와 박공지붕을 연결하기위한 추녀마루 라인 */
const firstHipLine = gableVector === prevVector ? prevHipLine : nextHipLine
const firstDegree =
gableVector === Math.sign(ridgeLine.y1 - prevLine.y1)
? getDegreeByChon(prevLine.attributes.pitch)
: getDegreeByChon(nextLine.attributes.pitch)
const oppositeRoofPoints = [
ridgeLine.x2,
ridgeLine.y2,
ridgeLine.x2,
ridgeLine.y1 === firstHipLine.y1 ? firstHipLine.y2 : firstHipLine.y1,
]
const oppositeRoofLine = drawHipLine(oppositeRoofPoints, canvas, roof, textMode, null, firstDegree, firstDegree)
baseHipLines.push({
x1: oppositeRoofLine.x1,
y1: oppositeRoofLine.y1,
x2: oppositeRoofLine.x2,
y2: oppositeRoofLine.y2,
line: oppositeRoofLine,
})
const connectRoofPoints = [oppositeRoofLine.x2, oppositeRoofLine.y2]
if (ridgeLine.y1 === firstHipLine.y1) {
connectRoofPoints.push(firstHipLine.x2, firstHipLine.y2)
} else {
connectRoofPoints.push(firstHipLine.x1, firstHipLine.y1)
}
const connectRoofLine = drawRoofLine(connectRoofPoints, canvas, roof, textMode)
baseHipLines.push({
x1: connectRoofLine.x1,
y1: connectRoofLine.y1,
x2: connectRoofLine.x2,
y2: connectRoofLine.y2,
line: connectRoofLine,
})
/** 다른 방향의 추녀마루 */
const secondHipLine = gableVector === prevVector ? nextHipLine : prevHipLine
const secondDegree =
gableVector === Math.sign(ridgeLine.y1 - prevLine.y1)
? getDegreeByChon(nextLine.attributes.pitch)
: getDegreeByChon(prevLine.attributes.pitch)
const intersections = []
const checkEdge = {
vertex1: { x: ridgeLine.x2, y: ridgeLine.y2 },
vertex2: { x: ridgeLine.x2, y: ridgeLine.y1 === secondHipLine.y1 ? secondHipLine.y2 : secondHipLine.y1 },
}
baseGableRidgeLines
.filter((ridge) => ridge !== ridgeLine)
.forEach((ridge) => {
const ridgeEdge = { vertex1: { x: ridge.x1, y: ridge.y1 }, vertex2: { x: ridge.x2, y: ridge.y2 } }
const intersection = edgesIntersection(ridgeEdge, checkEdge)
if (intersection && !intersections.includes(intersection)) {
const size = Big(intersection.x)
.minus(Big(ridgeLine.x2))
.pow(2)
.plus(Big(intersection.y).minus(Big(ridgeLine.y2)).pow(2))
.sqrt()
.toNumber()
intersections.push({ intersection, size, ridge })
}
})
intersections.sort((a, b) => a.size - b.size)
if (intersections.length > 0) {
const intersection = intersections[0].intersection
const intersectRidge = intersections[0].ridge
const oppositeRoofPoints = [ridgeLine.x2, ridgeLine.y2, intersection.x, intersection.y]
const oppositeRoofLine = drawHipLine(oppositeRoofPoints, canvas, roof, textMode, null, secondDegree, secondDegree)
baseHipLines.push({
x1: oppositeLine.x1,
y1: oppositeLine.y1,
x2: oppositeLine.x2,
y2: oppositeLine.y2,
line: oppositeRoofLine,
})
const ridgeVector = Math.sign(ridgeLine.x1 - ridgeLine.x2)
const connectRoofPoints = [oppositeRoofLine.x2, oppositeRoofLine.y2]
if (ridgeVector === Math.sign(oppositeRoofLine.x1 - intersectRidge.x2)) {
connectRoofPoints.push(intersectRidge.x2, intersectRidge.y2)
} else {
connectRoofPoints.push(intersectRidge.x1, intersectRidge.y1)
}
const connectRoofLine = drawRoofLine(connectRoofPoints, canvas, roof, textMode)
baseHipLines.push({
x1: connectRoofLine.x1,
y1: connectRoofLine.y1,
x2: connectRoofLine.x2,
y2: connectRoofLine.y2,
line: connectRoofLine,
})
}
}
}
}
}
})
/** 중복제거 */
baseRidgeLines.forEach((ridge) => {
baseRidgeLines
.filter((ridge2) => ridge !== ridge2)
.forEach((ridge2) => {
let overlap = segmentsOverlap(ridge, ridge2)
if (overlap) {
roof.canvas.remove(ridge)
roof.canvas.remove(ridge2)
baseRidgeLines = baseRidgeLines.filter((r) => r !== ridge && r !== ridge2)
baseRidgeCount = baseRidgeCount - 2
let x1 = Math.min(ridge.x1, ridge2.x1, ridge.x2, ridge2.x2)
let x2 = Math.max(ridge.x1, ridge2.x1, ridge.x2, ridge2.x2)
let y1 = Math.min(ridge.y1, ridge2.y1, ridge.y2, ridge2.y2)
let y2 = Math.max(ridge.y1, ridge2.y1, ridge.y2, ridge2.y2)
const newRidge = drawRidgeLine([x1, y1, x2, y2], canvas, roof, textMode)
baseRidgeLines.push(newRidge)
baseRidgeCount = baseRidgeCount + 1
}
})
})
/** ㄴ 모양 처마에 추녀마루를 그린다. */
drawEavesSecondLines.forEach((current) => {
const { currentBaseLine, prevBaseLine, nextBaseLine } = current
const currentLine = currentBaseLine.line
const prevLine = prevBaseLine.line
const nextLine = nextBaseLine.line
let { x1, x2, y1, y2 } = currentBaseLine
/** 이전 라인의 경사 */
const prevDegree = getDegreeByChon(prevLine.attributes.pitch)
/** 다음 라인의 경사 */
const currentDegree = getDegreeByChon(currentLine.attributes.pitch)
/** 이전, 다음라인의 사잇각의 vector를 구한다. */
let prevVector = getHalfAngleVector(prevLine, currentLine)
let nextVector = getHalfAngleVector(currentLine, nextLine)
let prevHipVector = { x: prevVector.x, y: prevVector.y }
let nextHipVector = { x: nextVector.x, y: nextVector.y }
/** 각 라인의 흐름 방향을 확인한다. */
const currentAngle = calculateAngle(currentLine.startPoint, currentLine.endPoint)
/** 현재 라인의 길이 추녀마루 길이의 기준이 된다. */
let hipLength = Big(x2)
.minus(Big(x1))
.plus(Big(y2).minus(Big(y1)))
.abs()
/** 이전 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/
const prevCheckPoint = {
x: Big(x1).plus(Big(prevHipVector.x).times(10)),
y: Big(y1).plus(Big(prevHipVector.y).times(10)),
}
if (!checkWallPolygon.inPolygon(prevCheckPoint)) {
prevHipVector = { x: -prevHipVector.x, y: -prevHipVector.y }
}
/** 다음 라인과의 사이 추녀마루의 각도를 확인한다, 각도가 지붕안쪽으로 향하지 않을때 반대로 처리한다.*/
const nextCheckPoint = {
x: Big(x2).plus(Big(nextHipVector.x).times(10)),
y: Big(y2).plus(Big(nextHipVector.y).times(10)),
}
if (!checkWallPolygon.inPolygon(nextCheckPoint)) {
nextHipVector = { x: -nextHipVector.x, y: -nextHipVector.y }
}
let prevHipLine, nextHipLine
/** 이전라인과의 연결지점에 추녀마루를 그린다. */
if (baseHipLines.filter((line) => line.x1 === x1 && line.y1 === y1).length === 0 && prevLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) {
let prevEndPoint = {
x: Big(x1).plus(Big(prevHipVector.x).times(hipLength)).round(2),
y: Big(y1).plus(Big(prevHipVector.y).times(hipLength)).round(2),
}
const prevEndEdge = { vertex1: { x: x1, y: y1 }, vertex2: prevEndPoint }
const intersectBaseLine = []
baseLines
.filter((line) => line !== currentLine && line !== prevLine && line !== nextLine)
.filter((line) => {
if (currentAngle === 0 || currentAngle === 180) {
return Math.sign(line.y1 - y1) === nextHipVector.y || Math.sign(line.y2 - y1) === nextHipVector.y
} else {
return Math.sign(line.x1 - x1) === nextHipVector.x || Math.sign(line.x2 - x1) === nextHipVector.x
}
})
.forEach((line) => {
const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
const intersection = edgesIntersection(prevEndEdge, lineEdge)
if (intersection && Math.sign(intersection.x - x1) === nextHipVector.x && Math.sign(intersection.y - y1) === nextHipVector.y) {
const size = Big(intersection.x - x1)
.abs()
.pow(2)
.plus(
Big(intersection.y - y1)
.pow(2)
.abs(),
)
.sqrt()
if (size.gt(0)) {
intersectBaseLine.push({
intersection,
size,
})
}
}
})
const intersectBase = intersectBaseLine.reduce((prev, current) => {
return prev.size < current.size ? prev : current
}, intersectBaseLine[0])
if (intersectBase) {
prevEndPoint = {
x: Big(x1)
.plus(Big(nextHipVector.x).times(intersectBase.size.div(2)))
.round(2),
y: Big(y1)
.plus(Big(nextHipVector.y).times(intersectBase.size.div(2)))
.round(2),
}
}
const intersectRidgeLine = []
baseRidgeLines.forEach((line) => {
const intersection = edgesIntersection(
{ vertex1: { x: x1, y: y1 }, vertex2: prevEndPoint },
{ vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } },
)
if (intersection && !intersection.isIntersectionOutside) {
intersectRidgeLine.push({
intersection,
distance: Big(intersection.x)
.minus(Big(x1))
.abs()
.pow(2)
.plus(Big(intersection.y).minus(Big(y1)).pow(2).abs())
.sqrt(),
})
}
})
const intersectRidge = intersectRidgeLine.reduce((prev, current) => (prev.distance < current.distance ? prev : current), intersectRidgeLine[0])
if (intersectRidge) {
prevEndPoint.x = Big(intersectRidge.intersection.x).round(1)
prevEndPoint.y = Big(intersectRidge.intersection.y).round(1)
}
const xVector = Math.sign(Big(prevEndPoint.x).minus(Big(x1)).neg().toNumber())
const yVector = Math.sign(Big(prevEndPoint.y).minus(Big(y1)).neg().toNumber())
/** 지붕 선까지의 곂침에 따른 길이를 파악하기 위한 scale*/
let scale = Big(currentLine.attributes.offset).pow(2).plus(Big(prevLine.attributes.offset).pow(2)).sqrt()
scale = scale.eq(0) ? Big(1) : scale
/** scale 만큼 추녀마루를 늘려서 겹치는 포인트를 확인한다. */
const hipEdge = {
vertex1: { x: Big(x1).plus(scale.times(xVector)).toNumber(), y: Big(y1).plus(scale.times(yVector)).toNumber() },
vertex2: prevEndPoint,
}
let intersectPoints = []
/** 외벽선에서 추녀 마루가 겹치는 경우에 대한 확인*/
roof.lines.forEach((line) => {
const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
const intersection = edgesIntersection(hipEdge, lineEdge)
if (
intersection &&
!intersection.isIntersectionOutside &&
Math.sign(prevEndPoint.x - x1) === Math.sign(prevEndPoint.x - intersection.x) &&
Math.sign(prevEndPoint.y - y1) === Math.sign(prevEndPoint.y - intersection.y)
) {
const intersectSize = prevEndPoint.x
.minus(Big(intersection.x))
.abs()
.pow(2)
.plus(prevEndPoint.y.minus(Big(intersection.y)).pow(2).abs())
.sqrt()
.toNumber()
intersectPoints.push({
intersection,
size: intersectSize,
})
}
})
intersectPoints = intersectPoints.reduce((prev, current) => {
return prev.size < current.size ? prev : current
}, intersectPoints[0])
/** 겹치는 외벽선이 있을때 추녀마루를 외벽선 까지 늘려서 수정*/
if (intersectPoints && intersectPoints.intersection) {
prevHipLine = drawHipLine(
[intersectPoints.intersection.x, intersectPoints.intersection.y, prevEndPoint.x.toNumber(), prevEndPoint.y.toNumber()],
canvas,
roof,
textMode,
null,
prevDegree,
currentDegree,
)
baseHipLines.push({ x1, y1, x2: prevEndPoint.x.toNumber(), y2: prevEndPoint.y.toNumber(), line: prevHipLine })
}
}
/** 다음라인과의 연결지점에 추녀마루를 그린다. */
if (baseHipLines.filter((line) => line.x1 === x2 && line.y1 === y2).length === 0 && nextLine.attributes.type === LINE_TYPE.WALLLINE.EAVES) {
let nextEndPoint = {
x: Big(x2).plus(Big(nextHipVector.x).times(hipLength)).round(2),
y: Big(y2).plus(Big(nextHipVector.y).times(hipLength)).round(2),
}
const nextEndEdge = {
vertex1: { x: x2, y: y2 },
vertex2: nextEndPoint,
}
const intersectBaseLine = []
baseLines
.filter((line) => line !== currentLine && line !== prevLine && line !== nextLine)
.filter((line) => {
if (currentAngle === 0 || currentAngle === 180) {
return Math.sign(line.y1 - y1) === nextHipVector.y || Math.sign(line.y2 - y1) === nextHipVector.y
} else {
return Math.sign(line.x1 - x1) === nextHipVector.x || Math.sign(line.x2 - x1) === nextHipVector.x
}
})
.forEach((line) => {
const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
const intersection = edgesIntersection(nextEndEdge, lineEdge)
if (intersection && Math.sign(intersection.x - x2) === nextHipVector.x && Math.sign(intersection.y - y2) === nextHipVector.y) {
const size = Big(intersection.x - x2)
.abs()
.pow(2)
.plus(
Big(intersection.y - y2)
.abs()
.pow(2),
)
.sqrt()
if (size.gt(0)) {
intersectBaseLine.push({
intersection,
size,
})
}
}
})
const intersectBase = intersectBaseLine.reduce((prev, current) => {
return prev.size.lt(current.size) ? prev : current
}, intersectBaseLine[0])
if (intersectBase) {
const size = Big(getAdjacent(intersectBase.size))
nextEndPoint = {
x: Big(x2)
.plus(Big(nextHipVector.x).times(size.div(2)))
.round(2),
y: Big(y2)
.plus(Big(nextHipVector.y).times(size.div(2)))
.round(2),
}
}
const intersectRidgeLine = []
baseRidgeLines.forEach((line) => {
const intersection = edgesIntersection(
{ vertex1: { x: x2, y: y2 }, vertex2: nextEndPoint },
{ vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } },
)
if (
intersection &&
((line.x1 <= intersection.x && line.x2 >= intersection.x && line.y1 <= intersection.y && line.y2 >= intersection.y) ||
(line.x2 <= intersection.x && line.x1 >= intersection.x && line.y2 <= intersection.y && line.y1 >= intersection.y))
) {
intersectRidgeLine.push({
intersection,
distance: Big(intersection.x)
.minus(Big(x1))
.pow(2)
.plus(Big(intersection.y).minus(Big(y1)).pow(2))
.sqrt(),
})
}
})
const intersectRidge = intersectRidgeLine.reduce((prev, current) => (prev.distance < current.distance ? prev : current), intersectRidgeLine[0])
if (intersectRidge) {
nextEndPoint.x = Big(intersectRidge.intersection.x).round(1)
nextEndPoint.y = Big(intersectRidge.intersection.y).round(1)
}
const xVector = Math.sign(Big(nextEndPoint.x).minus(Big(x2)).neg().toNumber())
const yVector = Math.sign(Big(nextEndPoint.y).minus(Big(y2)).neg().toNumber())
let scale = Big(currentLine.attributes.offset).pow(2).plus(Big(nextLine.attributes.offset).pow(2)).sqrt()
scale = scale.eq(0) ? Big(1) : scale
const hipEdge = {
vertex1: { x: Big(x2).plus(scale.times(xVector)).toNumber(), y: Big(y2).plus(scale.times(yVector)).toNumber() },
vertex2: nextEndPoint,
}
let intersectPoints = []
roof.lines.forEach((line) => {
const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
const intersection = edgesIntersection(hipEdge, lineEdge)
if (
intersection &&
!intersection.isIntersectionOutside &&
Math.sign(nextEndPoint.x - x2) === Math.sign(nextEndPoint.x - intersection.x) &&
Math.sign(nextEndPoint.y - y2) === Math.sign(nextEndPoint.y - intersection.y)
) {
const intersectSize = nextEndPoint.x
.minus(Big(intersection.x))
.pow(2)
.plus(nextEndPoint.y.minus(Big(intersection.y)).pow(2))
.abs()
.sqrt()
.toNumber()
intersectPoints.push({
intersection,
size: intersectSize,
})
}
})
intersectPoints = intersectPoints.reduce((prev, current) => {
return prev.size < current.size ? prev : current
}, intersectPoints[0])
if (intersectPoints && intersectPoints.intersection) {
nextHipLine = drawHipLine(
[intersectPoints.intersection.x, intersectPoints.intersection.y, nextEndPoint.x.toNumber(), nextEndPoint.y.toNumber()],
canvas,
roof,
textMode,
null,
prevDegree,
currentDegree,
)
baseHipLines.push({
x1: x2,
y1: y2,
x2: nextEndPoint.x.toNumber(),
y2: nextEndPoint.y.toNumber(),
line: nextHipLine,
})
}
}
})
/** baseHipLine 이 ridge에 붙지 않은 경우 확인 */
baseHipLines.forEach((hipLine) => {
const ridgeCount = baseRidgeLines.filter(
(ridgeLine) => (hipLine.x2 === ridgeLine.x1 && hipLine.y2 === ridgeLine.y1) || (hipLine.x2 === ridgeLine.x2 && hipLine.y2 === ridgeLine.y2),
).length
if (ridgeCount === 0) {
const hipXVector = Big(hipLine.x2).minus(hipLine.x1)
const hipYVector = Big(hipLine.y2).minus(hipLine.y1)
const hipSize = hipXVector.abs().pow(2).plus(hipYVector.abs().pow(2)).sqrt()
const intersectRidgePoints = []
const hipLineEdge = { vertex1: { x: hipLine.x1, y: hipLine.y1 }, vertex2: { x: hipLine.x2, y: hipLine.y2 } }
baseRidgeLines.forEach((ridgeLine) => {
const ridgeLineEdge = {
vertex1: { x: ridgeLine.x1, y: ridgeLine.y1 },
vertex2: { x: ridgeLine.x2, y: ridgeLine.y2 },
}
const intersection = edgesIntersection(hipLineEdge, ridgeLineEdge)
if (intersection) {
const intersectXVector = Big(intersection.x).minus(Big(hipLine.x1))
const intersectYVector = Big(intersection.y).minus(Big(hipLine.y1))
const intersectSize = intersectXVector.pow(2).plus(intersectYVector.pow(2)).sqrt()
if (!intersection.isIntersectionOutside) {
intersectRidgePoints.push({
x: intersection.x,
y: intersection.y,
size: intersectSize,
})
} else if (
((intersection.x === ridgeLine.x1 && intersection.y === ridgeLine.y1) ||
(intersection.x === ridgeLine.x2 && intersection.y === ridgeLine.y2)) &&
Math.sign(hipXVector.toNumber()) === Math.sign(intersectXVector.toNumber()) &&
Math.sign(hipYVector.toNumber()) === Math.sign(intersectYVector.toNumber()) &&
intersectSize.gt(0) &&
intersectSize.lt(hipSize)
) {
intersectRidgePoints.push({
x: intersection.x,
y: intersection.y,
size: intersectSize,
})
}
}
})
intersectRidgePoints.sort((prev, current) => prev.size.minus(current.size).toNumber())
if (intersectRidgePoints.length > 0) {
hipLine.x2 = intersectRidgePoints[0].x
hipLine.y2 = intersectRidgePoints[0].y
hipLine.line.set({ x2: intersectRidgePoints[0].x, y2: intersectRidgePoints[0].y })
const hipSize = reCalculateSize(hipLine.line)
hipLine.line.attributes.planeSize = hipSize.planeSize
hipLine.line.attributes.actualSize = hipSize.actualSize
hipLine.line.fire('modified')
}
}
})
/** 지붕선에 맞닫지 않은 부분을 확인하여 처리 한다.*/
/** 1. 그려진 마루 중 추녀마루가 부재하는 경우를 판단. 부재는 연결된 라인이 홀수인 경우로 판단한다.*/
let unFinishedRidge = []
baseRidgeLines.forEach((current) => {
let checkPoint = [
{ x: current.x1, y: current.y1, line: current, cnt: 0, onRoofLine: false },
{ x: current.x2, y: current.y2, line: current, cnt: 0, onRoofLine: false },
]
baseHipLines.forEach((line) => {
if ((line.x1 === current.x1 && line.y1 === current.y1) || (line.x2 === current.x1 && line.y2 === current.y1)) {
checkPoint[0].cnt = checkPoint[0].cnt + 1
}
if ((line.x1 === current.x2 && line.y1 === current.y2) || (line.x2 === current.x2 && line.y2 === current.y2)) {
checkPoint[1].cnt = checkPoint[1].cnt + 1
}
})
/** 마루의 포인트가 지붕선 위에 있는경우는 제외 (케라바 등)*/
roof.lines.forEach((line) => {
if (
line.x1 === line.x2 &&
checkPoint[0].x === line.x1 &&
((line.y1 <= checkPoint[0].y && line.y2 >= checkPoint[0].y) || (line.y1 >= checkPoint[0].y && line.y2 <= checkPoint[0].y))
) {
checkPoint[0].onRoofLine = true
}
if (
line.y1 === line.y2 &&
checkPoint[0].y === line.y1 &&
((line.x1 <= checkPoint[0].x && line.x2 >= checkPoint[0].x) || (line.x1 >= checkPoint[0].x && line.x2 <= checkPoint[0].x))
) {
checkPoint[0].onRoofLine = true
}
if (
line.x1 === line.x2 &&
checkPoint[1].x === line.x1 &&
((line.y1 <= checkPoint[1].y && line.y2 >= checkPoint[1].y) || (line.y1 >= checkPoint[1].y && line.y2 <= checkPoint[1].y))
) {
checkPoint[1].onRoofLine = true
}
if (
line.y1 === line.y2 &&
checkPoint[1].y === line.y1 &&
((line.x1 <= checkPoint[1].x && line.x2 >= checkPoint[1].x) || (line.x1 >= checkPoint[1].x && line.x2 <= checkPoint[1].x))
) {
checkPoint[1].onRoofLine = true
}
})
if ((checkPoint[0].cnt === 0 || checkPoint[0].cnt % 2 !== 0) && !checkPoint[0].onRoofLine) {
unFinishedRidge.push(checkPoint[0])
}
if ((checkPoint[1].cnt === 0 || checkPoint[1].cnt % 2 !== 0) && !checkPoint[1].onRoofLine) {
unFinishedRidge.push(checkPoint[1])
}
})
/** 2. 그려진 추녀마루 중 완성되지 않은 것을 찾는다. 완성되지 않았다는 것은 연결된 포인트가 홀수인 경우로 판단한다.*/
const findUnFinishedPoints = (baseHipLines) => {
let unFinishedPoint = []
baseHipLines.forEach((current) => {
let checkPoint = [
{ x: current.x1, y: current.y1, checked: true, line: current.line },
{ x: current.x2, y: current.y2, checked: true, line: current.line },
]
baseLines.forEach((line) => {
if ((line.x1 === current.x1 && line.y1 === current.y1) || (line.x2 === current.x1 && line.y2 === current.y1)) {
checkPoint[0].checked = false
}
if ((line.x1 === current.x2 && line.y1 === current.y2) || (line.x2 === current.x2 && line.y2 === current.y2)) {
checkPoint[1].checked = false
}
})
const samePoints = []
checkPoint
.filter((point) => point.checked)
.forEach((point) => {
baseHipLines.forEach((line) => {
if (line.x1 === point.x && line.y1 === point.y) {
samePoints.push({ x: point.x, y: point.y, line: line })
}
if (line.x2 === point.x && line.y2 === point.y) {
samePoints.push({ x: point.x, y: point.y, line: line })
}
})
})
if (samePoints.length > 0 && samePoints.length % 2 !== 0) {
unFinishedPoint.push(samePoints[0])
}
})
return unFinishedPoint
}
let unFinishedPoint = findUnFinishedPoints(baseHipLines)
/**3. 라인이 부재인 마루의 모자란 라인을 찾는다. 라인은 그려진 추녀마루 중에 완성되지 않은 추녀마루와 확인한다.*/
/**3-1 라인을 그릴때 각도가 필요하기 때문에 각도를 구한다. 각도는 전체 지부의 각도가 같다면 하나로 처리 */
let degreeAllLine = []
baseLines
.filter((line) => eavesType.includes(line.attributes.type))
.forEach((line) => {
const pitch = line.attributes.pitch
degreeAllLine.push(getDegreeByChon(pitch))
})
let currentDegree, prevDegree
degreeAllLine = [...new Set(degreeAllLine)]
currentDegree = degreeAllLine[0]
if (degreeAllLine.length === 1) {
prevDegree = currentDegree
} else {
prevDegree = degreeAllLine[1]
}
/** 라인이 부재한 마루에 라인을 찾아 그린다.*/
unFinishedRidge.forEach((current) => {
let checkPoints = []
unFinishedPoint
.filter(
(point) =>
point.x !== current.x &&
point.y !== current.y &&
Big(point.x)
.minus(Big(current.x))
.abs()
.minus(Big(point.y).minus(Big(current.y)).abs())
.abs()
.lt(1),
)
.forEach((point) => {
const pointEdge = { vertex1: { x: point.x, y: point.y }, vertex2: { x: current.x, y: current.y } }
let isIntersection = false
baseLines.forEach((line) => {
const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
const intersection = edgesIntersection(pointEdge, lineEdge)
if (intersection && !intersection.isIntersectionOutside) {
isIntersection = true
}
})
if (!isIntersection) {
checkPoints.push({
point,
size: Big(point.x)
.minus(Big(current.x))
.abs()
.pow(2)
.plus(Big(point.y).minus(Big(current.y)).abs().pow(2))
.sqrt(),
})
}
})
if (checkPoints.length > 0) {
checkPoints.sort((a, b) => a.size - b.size)
const maxCnt = Big(2).minus(Big(current.cnt)).toNumber()
for (let i = 0; i < maxCnt; i++) {
const checkPoint = checkPoints[i]
if (checkPoint) {
let point = [checkPoint.point.x, checkPoint.point.y, current.x, current.y]
let hipBasePoint
baseHipLines.forEach((line) => {
const checkAngel1 = calculateAngle({ x: point[0], y: point[1] }, { x: point[2], y: point[3] })
const checkAngel2 = calculateAngle(
{ x: line.line.x1, y: line.line.y1 },
{
x: line.line.x2,
y: line.line.y2,
},
)
const checkPointCnt = checkPoints.filter((point) => point.point.x === checkPoint.point.x && point.point.y === checkPoint.point.y).length
const isConnectLine =
((line.line.x1 === point[0] && line.line.y1 === point[1]) || (line.line.x2 === point[0] && line.line.y2 === point[1])) &&
checkAngel1 === checkAngel2
const isOverlap = segmentsOverlap(line.line, { x1: point[0], y1: point[1], x2: point[2], y2: point[3] })
const isSameLine =
(point[0] === line.x2 && point[1] === line.y2 && point[2] === line.x1 && point[3] === line.y1) ||
(point[0] === line.x1 && point[1] === line.y1 && point[2] === line.x2 && point[3] === line.y2)
if (checkPointCnt === 1 && (isConnectLine || isOverlap || isSameLine)) {
/** 겹치는 추녀마루와 하나의 선으로 변경*/
const mergePoint = [
{ x: point[0], y: point[1] },
{ x: point[2], y: point[3] },
{ x: line.line.x1, y: line.line.y1 },
{ x: line.line.x2, y: line.line.y2 },
]
/** baseHipLines도 조정*/
mergePoint.sort((a, b) => a.x - b.x)
hipBasePoint = { x1: line.x1, y1: line.y1, x2: line.x2, y2: line.y2 }
point = [mergePoint[0].x, mergePoint[0].y, mergePoint[3].x, mergePoint[3].y]
const theta = Big(
Math.acos(
Big(line.line.attributes.planeSize).div(
line.line.attributes.actualSize === 0 || line.line.attributes.actualSize === '' || line.line.attributes.actualSize === undefined
? line.line.attributes.planeSize
: line.line.attributes.actualSize,
),
),
)
.times(180)
.div(Math.PI)
.round(1)
prevDegree = theta.toNumber()
currentDegree = theta.toNumber()
canvas.remove(line.line)
baseHipLines = baseHipLines.filter((baseLine) => baseLine.line !== line.line)
}
})
const hipLine = drawHipLine(point, canvas, roof, textMode, null, prevDegree, currentDegree)
if (hipBasePoint) {
baseHipLines.push({ x1: hipBasePoint.x1, y1: hipBasePoint.y1, x2: point[2], y2: point[3], line: hipLine })
} else {
baseHipLines.push({ x1: point[0], y1: point[1], x2: point[2], y2: point[3], line: hipLine })
}
current.cnt = current.cnt + 1
}
}
}
/** 라인이 다 그려지지 않은 경우 */
if (current.cnt % 2 !== 0) {
let basePoints = baseLinePoints
.filter((point) =>
Big(point.x)
.minus(Big(current.x))
.abs()
.minus(Big(point.y).minus(Big(current.y)).abs())
.abs()
.lt(1),
)
.filter((point) => {
const pointEdge = { vertex1: { x: current.x, y: current.y }, vertex2: { x: point.x, y: point.y } }
const intersectPoints = []
baseLines.forEach((line) => {
const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
const intersection = edgesIntersection(pointEdge, lineEdge)
if (intersection && !intersection.isIntersectionOutside) {
intersectPoints.push(intersection)
}
})
return (
!intersectPoints.filter(
(intersect) => !(Big(intersect.x).minus(Big(point.x)).abs().lt(1) && Big(intersect.y).minus(Big(point.y)).abs().lt(1)),
).length > 0
)
})
/** hip을 그리기 위한 기본 길이*/
let hipSize = Big(0)
if (basePoints.length > 0) {
basePoints.sort((a, b) => {
const aSize = Big(a.x)
.minus(Big(current.x))
.abs()
.pow(2)
.plus(Big(a.y).minus(Big(current.y)).abs().pow(2))
.sqrt()
const bSize = Big(b.x)
.minus(Big(current.x))
.abs()
.pow(2)
.plus(Big(b.y).minus(Big(current.y)).abs().pow(2))
.sqrt()
return aSize - bSize
})
const baseHips = baseHipLines.filter((line) => line.x1 === basePoints[0].x && line.y1 === basePoints[0].y)
if (baseHips.length > 0) {
hipSize = Big(baseHips[0].line.attributes.planeSize)
}
}
if (hipSize.eq(0)) {
const ridge = current.line
basePoints = baseHipLines
.filter((line) => (line.x2 === ridge.x1 && line.y2 === ridge.y1) || (line.x2 === ridge.x2 && line.y2 === ridge.y2))
.filter((line) => baseLines.filter((baseLine) => baseLine.x1 === line.x1 && baseLine.y1 === line.y1).length > 0)
basePoints.sort((a, b) => a.line.attributes.planeSize - b.line.attributes.planeSize)
if (basePoints.length > 0 && basePoints[0].line) {
hipSize = Big(basePoints[0].line.attributes.planeSize)
} else {
hipSize = Big(0) // 또는 기본값 설정
}
}
hipSize = hipSize.pow(2).div(2).sqrt().round().div(10).toNumber()
/** 현재 라인을 기준으로 45, 135, 225, 315 방향을 확인하기 위한 좌표, hip은 45도 방향으로만 그린다. */
const checkEdge45 = {
vertex1: { x: current.x, y: current.y },
vertex2: { x: current.x + hipSize, y: current.y - hipSize },
}
const checkEdge135 = {
vertex1: { x: current.x, y: current.y },
vertex2: { x: current.x + hipSize, y: current.y + hipSize },
}
const checkEdge225 = {
vertex1: { x: current.x, y: current.y },
vertex2: { x: current.x - hipSize, y: current.y + hipSize },
}
const checkEdge315 = {
vertex1: { x: current.x, y: current.y },
vertex2: { x: current.x - hipSize, y: current.y - hipSize },
}
let intersectPoints = []
let notIntersect45 = true,
notIntersect135 = true,
notIntersect225 = true,
notIntersect315 = true
baseLines.forEach((line) => {
const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
const intersection45 = edgesIntersection(checkEdge45, lineEdge)
const intersection135 = edgesIntersection(checkEdge135, lineEdge)
const intersection225 = edgesIntersection(checkEdge225, lineEdge)
const intersection315 = edgesIntersection(checkEdge315, lineEdge)
if (intersection45 && !intersection45.isIntersectionOutside) {
intersectPoints.push(intersection45)
notIntersect45 = false
}
if (intersection135 && !intersection135.isIntersectionOutside) {
intersectPoints.push(intersection135)
notIntersect135 = false
}
if (intersection225 && !intersection225.isIntersectionOutside) {
intersectPoints.push(intersection225)
notIntersect225 = false
}
if (intersection315 && !intersection315.isIntersectionOutside) {
intersectPoints.push(intersection315)
notIntersect315 = false
}
})
/** baseLine과 교차하지 않는 포인트를 추가한다.*/
if (notIntersect45) {
intersectPoints.push(checkEdge45.vertex2)
}
if (notIntersect135) {
intersectPoints.push(checkEdge135.vertex2)
}
if (notIntersect225) {
intersectPoints.push(checkEdge225.vertex2)
}
if (notIntersect315) {
intersectPoints.push(checkEdge315.vertex2)
}
/** baseLine의 각 좌표와 교차하는 경우로 한정*/
intersectPoints = intersectPoints.filter((is) => baseLinePoints.filter((point) => point.x === is.x && point.y === is.y).length > 0)
/** baseLine과 교차하는 좌표의 경우 이미 그려진 추녀마루가 존재한다면 제외한다. */
intersectPoints = intersectPoints.filter((point) => baseHipLines.filter((hip) => hip.x1 === point.x && hip.y1 === point.y).length === 0)
/** 중복제거 */
intersectPoints = intersectPoints.filter((point, index, self) => index === self.findIndex((p) => p.x === point.x && p.y === point.y))
intersectPoints.forEach((is) => {
const points = [current.x, current.y, is.x, is.y]
/** 추녀마루의 연결점이 처마라인이 아닌경우 return */
let hasGable = false
baseLines
.filter((line) => (line.x1 === points[2] && line.y1 === points[3]) || (line.x2 === points[2] && line.y2 === points[3]))
.forEach((line) => {
if (!eavesType.includes(line.attributes.type)) {
hasGable = true
}
})
if (hasGable) return
const pointEdge = { vertex1: { x: points[0], y: points[1] }, vertex2: { x: points[2], y: points[3] } }
const vectorX = Math.sign(Big(points[2]).minus(Big(points[0])).toNumber())
const vectorY = Math.sign(Big(points[3]).minus(Big(points[1])).toNumber())
const roofIntersections = []
roof.lines.forEach((line) => {
const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
const intersection = edgesIntersection(pointEdge, lineEdge)
if (intersection) {
const vectorIntersectionX = Math.sign(Big(intersection.x).minus(Big(points[0])).toNumber())
const vectorIntersectionY = Math.sign(Big(intersection.y).minus(Big(points[1])).toNumber())
if (vectorIntersectionX === vectorX && vectorIntersectionY === vectorY) {
roofIntersections.push({
x: intersection.x,
y: intersection.y,
size: calcLinePlaneSize({ x1: points[0], y1: points[1], x2: intersection.x, y2: intersection.y }),
})
}
}
})
roofIntersections.sort((a, b) => a.size - b.size)
const hipLine = drawHipLine(
[points[0], points[1], roofIntersections[0].x, roofIntersections[0].y],
canvas,
roof,
textMode,
null,
prevDegree,
currentDegree,
)
baseHipLines.push({ x1: points[0], y1: points[1], x2: points[2], y2: points[3], line: hipLine })
current.cnt = current.cnt + 1
})
}
})
/** hip이 짝수개가 맞다아있는데 마루와 연결되지 않는 포인트를 찾는다. 그려지지 않은 마루를 찾기 위함.*/
let noRidgeHipPoints = baseHipLines
.filter((current) => current.x1 !== current.x2 && current.y1 !== current.y2)
.filter((current) => {
const lines = baseHipLines
.filter((line) => line !== current)
.filter((line) => (line.x1 === current.x2 && line.y1 === current.y2) || (line.x2 === current.x2 && line.y2 === current.y2))
return lines.length !== 0 && lines.length % 2 !== 0
})
.filter(
(current) =>
baseRidgeLines.filter((line) => (line.x1 === current.x2 && line.y1 === current.y2) || (line.x2 === current.x2 && line.y2 === current.y2))
.length === 0 &&
baseGableRidgeLines.filter((line) => (line.x1 === current.x2 && line.y1 === current.y2) || (line.x2 === current.x2 && line.y2 === current.y2))
.length === 0,
)
noRidgeHipPoints.forEach((current) => {
const orthogonalPoints = noRidgeHipPoints.filter((point) => {
if (point !== current && ((current.x2 === point.x2 && current.y2 !== point.y2) || (current.x2 !== point.x2 && current.y2 === point.y2))) {
return true
}
})
/** 직교 하는 포인트가 존재 할때 마루를 그린다. */
if (orthogonalPoints.length > 0 && baseRidgeCount < getMaxRidge(baseLines.length)) {
baseRidgeCount = baseRidgeCount + 1
const points = [current.x2, current.y2, orthogonalPoints[0].x2, orthogonalPoints[0].y2]
const ridgeLine = drawRidgeLine(points, canvas, roof, textMode)
baseRidgeLines.push(ridgeLine)
}
})
/** 중복제거*/
baseRidgeLines.forEach((current) => {
const sameRidge = baseRidgeLines.filter(
(line) =>
line !== current &&
((line.x1 === current.x1 && line.y1 === current.y1 && line.x2 === current.x2 && line.y2 === current.y2) ||
(line.x1 === current.x2 && line.y1 === current.y2 && line.x2 === current.x1 && line.y2 === current.y1)),
)
if (sameRidge.length > 0) {
sameRidge.forEach((duplicateLine) => {
const index = baseRidgeLines.indexOf(duplicateLine)
if (index !== -1) {
baseRidgeLines.splice(index, 1)
}
canvas.remove(duplicateLine)
})
}
})
/** 직교 하는 포인트가 없는 경우 남은 포인트 처리 */
const checkEdgeLines = []
noRidgeHipPoints.forEach((current) => {
noRidgeHipPoints
.filter((point) => point !== current)
.forEach((point) => {
checkEdgeLines.push(
{ vertex1: { x: current.x2, y: current.y2 }, vertex2: { x: current.x2, y: point.y2 } },
{ vertex1: { x: current.x2, y: point.y2 }, vertex2: { x: point.x2, y: point.y2 } },
{ vertex1: { x: point.x2, y: point.y2 }, vertex2: { x: point.x2, y: current.y2 } },
{ vertex1: { x: point.x2, y: current.y2 }, vertex2: { x: current.x2, y: current.y2 } },
)
})
})
/** 연결되지 않은 포인트를 찾아서 해당 포인트를 가지고 있는 라인을 찾는다. */
let unFinishedPoints = []
let unFinishedLines = []
let intersectPoints = []
baseHipLines.forEach((line) => {
if (baseLinePoints.filter((point) => point.x === line.x1 && point.y === line.y1).length === 0) {
unFinishedPoints.push({ x: line.x1, y: line.y1 })
}
if (baseLinePoints.filter((point) => point.x === line.x2 && point.y === line.y2).length === 0) {
unFinishedPoints.push({ x: line.x2, y: line.y2 })
}
})
unFinishedPoints
.filter((point) => unFinishedPoints.filter((p) => p !== point && p.x === point.x && p.y === point.y).length === 0)
.forEach((point) => {
baseHipLines
.filter((line) => (line.x1 === point.x && line.y1 === point.y) || (line.x2 === point.x && line.y2 === point.y))
.forEach((line) => unFinishedLines.push(line))
})
unFinishedLines.forEach((line) => {
const xVector = Math.sign(Big(line.x2).minus(Big(line.x1)))
const yVector = Math.sign(Big(line.y2).minus(Big(line.y1)))
let lineIntersectPoints = []
checkEdgeLines.forEach((edge) => {
const intersectEdge = edgesIntersection(edge, {
vertex1: { x: line.x1, y: line.y1 },
vertex2: { x: line.x2, y: line.y2 },
})
if (intersectEdge) {
const isXVector = Math.sign(Big(intersectEdge.x).minus(Big(line.x1))) === xVector
const isYVector = Math.sign(Big(intersectEdge.y).minus(Big(line.y1))) === yVector
if (isXVector && isYVector) {
lineIntersectPoints.push({
intersection: intersectEdge,
size: Big(intersectEdge.x)
.minus(Big(line.x1))
.abs()
.pow(2)
.plus(Big(intersectEdge.y).minus(Big(line.y1)).abs().pow(2))
.sqrt()
.round(1)
.toNumber(),
})
}
}
})
lineIntersectPoints = lineIntersectPoints.filter(
(point, index, self) => index === self.findIndex((p) => p.intersection.x === point.intersection.x && p.intersection.y === point.intersection.y),
)
lineIntersectPoints.sort((a, b) => a.size - b.size)
if (lineIntersectPoints.length > 0) {
intersectPoints.push({ intersection: lineIntersectPoints[0].intersection, line })
}
})
/** 마루를 그릴수 있는지 찾는다. */
noRidgeHipPoints.forEach((hipPoint) => {
const ridgePoints = []
intersectPoints
.filter(
(intersectPoint) =>
(intersectPoint.intersection.x !== hipPoint.x2 && intersectPoint.intersection.y === hipPoint.y2) ||
(intersectPoint.intersection.x === hipPoint.x2 && intersectPoint.intersection.y !== hipPoint.y2),
)
.forEach((intersectPoint) => {
ridgePoints.push({
intersection: intersectPoint,
distance: Big(intersectPoint.intersection.x)
.minus(Big(hipPoint.x2))
.abs()
.pow(2)
.plus(Big(intersectPoint.intersection.y).minus(Big(hipPoint.y2)).abs().pow(2))
.sqrt()
.round(1)
.toNumber(),
})
})
ridgePoints.sort((a, b) => a.distance - b.distance)
if (ridgePoints.length > 0 && baseRidgeCount < getMaxRidge(baseLines.length)) {
const intersection = ridgePoints[0].intersection
const isPoint = intersection.intersection
const points = [hipPoint.x2, hipPoint.y2, isPoint.x, isPoint.y]
const ridgeLine = drawRidgeLine(points, canvas, roof, textMode)
baseRidgeCount = baseRidgeCount + 1
baseRidgeLines.push(ridgeLine)
let baseHipLine = baseHipLines.filter((line) => line === intersection.line)[0]
baseHipLine.x2 = isPoint.x
baseHipLines.y2 = isPoint.y
/** 보조선 라인 조정*/
const hipLine = intersection.line.line
/** 평면길이 */
const planeSize = calcLinePlaneSize({
x1: hipLine.x1,
y1: hipLine.y1,
x2: isPoint.x,
y2: isPoint.y,
})
/** 실제길이 */
const actualSize =
prevDegree === currentDegree
? calcLineActualSize(
{
x1: hipLine.x1,
y1: hipLine.y1,
x2: isPoint.x,
y2: isPoint.y,
},
currentDegree,
)
: 0
hipLine.set({
x2: isPoint.x,
y2: isPoint.y,
attributes: { roofId: roof.id, planeSize, actualSize },
})
hipLine.fire('modified')
intersectPoints = intersectPoints.filter((isp) => isp !== intersection)
noRidgeHipPoints = noRidgeHipPoints.filter((hip) => hip !== hipPoint)
} else {
const linePoints = []
intersectPoints.forEach((intersectPoint) => {
const intersection = intersectPoint.intersection
const xVector = Math.sign(Big(intersection.x).minus(Big(hipPoint.x2)))
const yVector = Math.sign(Big(intersection.y).minus(Big(hipPoint.y2)))
const checkEdge = {
vertex1: { x: intersection.x, y: intersection.y },
vertex2: {
x: Big(intersection.x).plus(Big(xVector).times(10)).toNumber(),
y: Big(intersection.y).plus(Big(yVector).times(10)).toNumber(),
},
}
const intersectX = edgesIntersection(
{
vertex1: { x: hipPoint.x2, y: hipPoint.y2 },
vertex2: { x: Big(hipPoint.x2).plus(Big(xVector).neg().times(10)).toNumber(), y: hipPoint.y2 },
},
checkEdge,
)
const intersectY = edgesIntersection(
{
vertex1: { x: hipPoint.x2, y: hipPoint.y2 },
vertex2: { x: hipPoint.x2, y: Big(hipPoint.y2).plus(Big(yVector).neg().times(10)).toNumber() },
},
checkEdge,
)
let distanceX = Infinity,
distanceY = Infinity
if (intersectX) {
distanceX = Big(intersectX.x)
.minus(Big(intersection.x))
.abs()
.pow(2)
.plus(Big(intersectX.y).minus(Big(intersection.y)).abs().pow(2))
.sqrt()
.round(1)
.toNumber()
}
if (intersectY) {
distanceY = Big(intersectY.x)
.minus(Big(intersection.x))
.abs()
.pow(2)
.plus(Big(intersectY.y).minus(Big(intersection.y)).abs().pow(2))
.sqrt()
.round(1)
.toNumber()
}
if (distanceX < distanceY) {
linePoints.push({ intersection: intersectX, intersectPoint })
}
if (distanceX > distanceY) {
linePoints.push({ intersection: intersectY, intersectPoint })
}
})
const linePoint = linePoints.reduce((prev, current) => {
const prevDistance = Big(prev.intersection.x)
.minus(Big(hipPoint.x2))
.abs()
.pow(2)
.plus(Big(prev.intersection.y).minus(Big(hipPoint.y2)).abs().pow(2))
.sqrt()
const currentDistance = Big(current.intersection.x)
.minus(Big(hipPoint.x2))
.abs()
.pow(2)
.plus(Big(current.intersection.y).minus(Big(hipPoint.y2)).abs().pow(2))
.sqrt()
if (prevDistance < currentDistance) {
return prev
} else {
return current
}
}, linePoints[0])
if (!linePoint) return
const hipStartPoint = [hipPoint.x2, hipPoint.y2, linePoint.intersection.x, linePoint.intersection.y]
/** 직선인 경우 마루를 그린다.*/
if (
((hipStartPoint[0] === hipStartPoint[2] && hipStartPoint[1] !== hipStartPoint[3]) ||
(hipStartPoint[0] !== hipStartPoint[2] && hipStartPoint[1] === hipStartPoint[3])) &&
baseRidgeCount < getMaxRidge(baseLines.length)
) {
const ridgeLine = drawRidgeLine(hipStartPoint, canvas, roof, textMode)
baseRidgeCount = baseRidgeCount + 1
baseRidgeLines.push(ridgeLine)
noRidgeHipPoints = noRidgeHipPoints.filter((hip) => hip !== hipPoint)
}
/** 대각선인경우 hip을 그린다. */
if (
Big(hipStartPoint[0])
.minus(Big(hipStartPoint[2]))
.abs()
.minus(Big(hipStartPoint[1]).minus(Big(hipStartPoint[3])).abs())
.abs()
.lt(1)
) {
// console.log('힙1')
const hipLine = drawHipLine(hipStartPoint, canvas, roof, textMode, null, prevDegree, currentDegree)
baseHipLines.push({
x1: hipStartPoint[0],
y1: hipStartPoint[1],
x2: hipStartPoint[2],
y2: hipStartPoint[3],
line: hipLine,
})
noRidgeHipPoints = noRidgeHipPoints.filter((hip) => hip !== hipPoint)
}
const isStartPoint = [
linePoint.intersection.x,
linePoint.intersection.y,
linePoint.intersectPoint.intersection.x,
linePoint.intersectPoint.intersection.y,
]
if (
((isStartPoint[0] === isStartPoint[2] && isStartPoint[1] !== isStartPoint[3]) ||
(isStartPoint[0] !== isStartPoint[2] && isStartPoint[1] === isStartPoint[3])) &&
baseRidgeCount < getMaxRidge(baseLines.length)
) {
const ridgeLine = drawRidgeLine(isStartPoint, canvas, roof, textMode)
baseRidgeCount = baseRidgeCount + 1
baseRidgeLines.push(ridgeLine)
}
if (
Big(isStartPoint[0])
.minus(Big(isStartPoint[2]))
.abs()
.minus(Big(isStartPoint[1]).minus(Big(isStartPoint[3])).abs())
.abs()
.lt(1)
) {
const hipLine = drawHipLine(isStartPoint, canvas, roof, textMode, null, prevDegree, currentDegree)
baseHipLines.push({
x1: isStartPoint[0],
y1: isStartPoint[1],
x2: isStartPoint[2],
y2: isStartPoint[3],
line: hipLine,
})
}
}
})
const ridgeAllPoints = []
baseRidgeLines.forEach((line) => ridgeAllPoints.push({ x: line.x1, y: line.y1 }, { x: line.x2, y: line.y2 }))
baseGableRidgeLines.forEach((line) => ridgeAllPoints.push({ x: line.x1, y: line.y1 }, { x: line.x2, y: line.y2 }))
/** hip 중에 지붕의 라인과 만나지 않은 선을 확인.*/
baseHipLines
.filter(
(hip) => baseLinePoints.filter((point) => (point.x === hip.x1 && point.y === hip.y1) || (point.x === hip.x2 && point.y === hip.y2)).length > 0,
)
.filter((hip) => {
const hipEdge = { vertex1: { x: hip.line.x1, y: hip.line.y1 }, vertex2: { x: hip.line.x2, y: hip.line.y2 } }
const hipVectorX = Math.sign(Big(hip.x1).minus(Big(hip.x2)))
const hipVectorY = Math.sign(Big(hip.y1).minus(Big(hip.y2)))
let isIntersect = false
roof.lines.forEach((line) => {
const edge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
const intersection = edgesIntersection(edge, hipEdge)
if (intersection && !intersection.isIntersectionOutside) {
const isVectorX = Math.sign(Big(intersection.x).minus(Big(hip.x2)))
const isVectorY = Math.sign(Big(intersection.y).minus(Big(hip.y2)))
if (isVectorX === hipVectorX && isVectorY === hipVectorY) {
isIntersect = true
}
}
})
return !isIntersect
})
.forEach((hip) => {
const hipLine = hip.line
if (hipLine) {
const hipVectorX = Big(hipLine.x2).plus(Big(hipLine.attributes.planeSize).times(Big(hipLine.x1).minus(Big(hipLine.x2)).neg().s))
const hipVectorY = Big(hipLine.y2).plus(Big(hipLine.attributes.planeSize).times(Big(hipLine.y1).minus(Big(hipLine.y2)).neg().s))
const overlapLineX = roof.lines
.filter((roofLine) => roofLine.x1 !== roofLine.x2 && roofLine.y1 === hipLine.y2 && roofLine.y2 === hipLine.y2)
.filter((roofLine) => {
const minX = Math.min(roofLine.x1, roofLine.x2, hipLine.x2)
const maxX = Math.max(roofLine.x1, roofLine.x2, hipLine.x2)
const checkLineEdge = { vertex1: { x: minX, y: hipLine.y2 }, vertex2: { x: maxX, y: hipLine.y2 } }
let isIntersect = false
baseHipLines.forEach((baseHipLine) => {
const edge = {
vertex1: { x: baseHipLine.x1, y: baseHipLine.y1 },
vertex2: { x: baseHipLine.x2, y: baseHipLine.y2 },
}
const intersection = edgesIntersection(edge, checkLineEdge)
if (intersection && !intersection.isIntersectionOutside) {
const isVectorX = Math.sign(Big(intersection.x).minus(Big(baseHipLine.x2)))
const isVectorY = Math.sign(Big(intersection.y).minus(Big(baseHipLine.y2)))
if (isVectorX === hipVectorX && isVectorY === hipVectorY) {
isIntersect = true
}
}
})
baseRidgeLines.forEach((baseRidgeLine) => {
const edge = {
vertex1: { x: baseRidgeLine.x1, y: baseRidgeLine.y1 },
vertex2: { x: baseRidgeLine.x2, y: baseRidgeLine.y2 },
}
const intersection = edgesIntersection(edge, checkLineEdge)
if (intersection && !intersection.isIntersectionOutside) {
const isVectorX = Math.sign(Big(intersection.x).minus(Big(hipLine.x2)))
const isVectorY = Math.sign(Big(intersection.y).minus(Big(hipLine.y2)))
if (isVectorX === hipVectorX && isVectorY === hipVectorY) {
isIntersect = true
}
}
})
return !isIntersect
})
const overlapLineY = roof.lines
.filter((roofLine) => roofLine.y1 !== roofLine.y2 && roofLine.x1 === hipLine.x2 && roofLine.x2 === hipLine.x2)
.filter((roofLine) => {
const minY = Math.min(roofLine.y1, roofLine.y2, hipLine.y2)
const maxY = Math.max(roofLine.y1, roofLine.y2, hipLine.y2)
const checkLineEdge = { vertex1: { x: hipLine.x2, y: minY }, vertex2: { x: hipLine.x2, y: maxY } }
let isIntersect = false
baseHipLines.forEach((baseHipLine) => {
const edge = {
vertex1: { x: baseHipLine.x1, y: baseHipLine.y1 },
vertex2: { x: baseHipLine.x2, y: baseHipLine.y2 },
}
const intersection = edgesIntersection(edge, checkLineEdge)
if (intersection && !intersection.isIntersectionOutside) {
const isVectorX = Math.sign(Big(intersection.x).minus(Big(hipLine.x2)))
const isVectorY = Math.sign(Big(intersection.y).minus(Big(hipLine.y2)))
if (isVectorX === hipVectorX && isVectorY === hipVectorY) {
isIntersect = true
}
}
})
baseRidgeLines.forEach((baseRidgeLine) => {
const edge = {
vertex1: { x: baseRidgeLine.x1, y: baseRidgeLine.y1 },
vertex2: { x: baseRidgeLine.x2, y: baseRidgeLine.y2 },
}
const intersection = edgesIntersection(edge, checkLineEdge)
if (intersection && !intersection.isIntersectionOutside) {
const isVectorX = Math.sign(Big(intersection.x).minus(Big(hipLine.x2)))
const isVectorY = Math.sign(Big(intersection.y).minus(Big(hipLine.y2)))
if (isVectorX === hipVectorX && isVectorY === hipVectorY) {
isIntersect = true
}
}
})
return !isIntersect
})
overlapLineX.reduce((prev, current) => {
const prevDistance = Big(prev.x1)
.minus(Big(hipLine.x2))
.abs()
.lt(Big(prev.x2).minus(Big(hipLine.x2)).abs())
? Big(prev.x1).minus(Big(hipLine.x2)).abs()
: Big(prev.x2).minus(Big(hipLine.x2)).abs()
const currentDistance = Big(current.x1)
.minus(Big(hipLine.x2))
.abs()
.lt(Big(current.x2).minus(Big(hipLine.x2)).abs())
? Big(current.x1).minus(Big(hipLine.x2)).abs()
: Big(current.x2).minus(Big(hipLine.x2)).abs()
return prevDistance.lt(currentDistance) ? prev : current
}, overlapLineX[0])
overlapLineY.reduce((prev, current) => {
const prevDistance = Big(prev.y1)
.minus(Big(hipLine.y2))
.abs()
.lt(Big(prev.y2).minus(Big(hipLine.y2)).abs())
? Big(prev.y1).minus(Big(hipLine.y2)).abs()
: Big(prev.y2).minus(Big(hipLine.y2)).abs()
const currentDistance = Big(current.y1)
.minus(Big(hipLine.y2))
.abs()
.lt(Big(current.y2).minus(Big(hipLine.y2)).abs())
? Big(current.y1).minus(Big(hipLine.y2)).abs()
: Big(current.y2).minus(Big(hipLine.y2)).abs()
return prevDistance.lt(currentDistance) ? prev : current
}, overlapLineY[0])
if (overlapLineX.length > 0) {
const overlapLine = overlapLineX[0]
const maxX = Math.max(overlapLine.x1, overlapLine.x2)
const point = [hipLine.x2, hipLine.y2, maxX, hipLine.y2]
const addLine = drawHipLine(point, canvas, roof, textMode, null, prevDegree, currentDegree)
baseHipLines.push({ x1: point[0], y1: point[1], x2: point[2], y2: point[3], line: addLine })
}
if (overlapLineY.length > 0) {
const overlapLine = overlapLineY[0]
const maxY = Math.max(overlapLine.y1, overlapLine.y2)
const point = [hipLine.x2, hipLine.y2, hipLine.x2, maxY]
const addLine = drawHipLine(point, canvas, roof, textMode, null, prevDegree, currentDegree)
baseHipLines.push({ x1: point[0], y1: point[1], x2: point[2], y2: point[3], line: addLine })
}
}
const modifiedBaseLine = baseLines.filter((line) => (hip.x2 === line.x1 && hip.y2 === line.y1) || (hip.x2 === line.x2 && hip.y2 === line.y2))
if (modifiedBaseLine.length === 0) return
const verticalLine = modifiedBaseLine.find(
(line) =>
(hip.x2 === line.attributes.originPoint.x1 && hip.x2 === line.attributes.originPoint.x2) ||
(hip.y2 === line.attributes.originPoint.y1 && hip.y2 === line.attributes.originPoint.y2),
)
const horizonLine = modifiedBaseLine.find((line) => line !== verticalLine)
const horizonRoof = roof.lines.find((line) => {
const originPoint = horizonLine.attributes.originPoint
if (originPoint.y1 === originPoint.y2) {
return (
line.y1 === line.y2 &&
Math.sign(originPoint.x1 - originPoint.x2) === Math.sign(line.x1 - line.x2) &&
Big(originPoint.y1).minus(Big(line.y1)).abs().minus(horizonLine.attributes.offset).abs().lt(1)
)
} else {
return (
line.x1 === line.x2 &&
Math.sign(originPoint.y1 - originPoint.y2) === Math.sign(line.y1 - line.y2) &&
Big(originPoint.x1).minus(Big(line.x1)).abs().minus(horizonLine.attributes.offset).abs().lt(1)
)
}
})
if (horizonRoof) {
let horizonPoint
if (horizonRoof.y1 === horizonRoof.y2) {
const minX = Math.min(horizonRoof.x1, horizonRoof.x2, horizonLine.attributes.originPoint.x1, horizonLine.attributes.originPoint.x2)
const maxX = Math.max(horizonRoof.x1, horizonRoof.x2, horizonLine.attributes.originPoint.x1, horizonLine.attributes.originPoint.x2)
horizonPoint = [minX, horizonRoof.y1, maxX, horizonRoof.y1]
} else {
const minY = Math.min(horizonRoof.y1, horizonRoof.y2, horizonLine.attributes.originPoint.y1, horizonLine.attributes.originPoint.y2)
const maxY = Math.max(horizonRoof.y1, horizonRoof.y2, horizonLine.attributes.originPoint.y1, horizonLine.attributes.originPoint.y2)
horizonPoint = [horizonRoof.x1, minY, horizonRoof.x1, maxY]
}
let addLine
const alreadyHorizonLines = baseHipLines.find(
(hipLine) =>
hipLine.x1 === horizonPoint[0] && hipLine.y1 === horizonPoint[1] && hipLine.x2 === horizonPoint[2] && hipLine.y2 === horizonPoint[3],
)
if (!alreadyHorizonLines) {
addLine = drawHipLine(horizonPoint, canvas, roof, textMode, null, prevDegree, currentDegree)
baseHipLines.push({
x1: horizonPoint[0],
y1: horizonPoint[1],
x2: horizonPoint[2],
y2: horizonPoint[3],
line: addLine,
})
} else {
addLine = alreadyHorizonLines
}
let verticalPoint
if (addLine.y1 === addLine.y2) {
verticalPoint = [hip.x2, hip.y2, hip.x2, addLine.y1]
} else {
verticalPoint = [hip.x2, hip.y2, addLine.x1, hip.y2]
}
const alreadyVerticalLine = baseHipLines.find(
(hipLine) =>
hipLine.x1 === verticalPoint[0] && hipLine.y1 === verticalPoint[1] && hipLine.x2 === verticalPoint[2] && hipLine.y2 === verticalPoint[3],
)
if (!alreadyVerticalLine) {
addLine = drawHipLine(verticalPoint, canvas, roof, textMode, null, prevDegree, currentDegree)
baseHipLines.push({
x1: verticalPoint[0],
y1: verticalPoint[1],
x2: verticalPoint[2],
y2: verticalPoint[3],
line: addLine,
})
}
}
})
ridgeAllPoints.forEach((current) => {
ridgeAllPoints
.filter((point) => point !== current)
.forEach((point) => {
let checkRidgeLine, checkHipLine
/** 직선인 경우 마루 확인*/
if (
baseRidgeCount < getMaxRidge(baseLines.length) &&
((point.x === current.x && point.y !== current.y) || (point.x !== current.x && point.y === current.y))
) {
checkRidgeLine = { x1: current.x, y1: current.y, x2: point.x, y2: point.y }
}
/** 대각선인 경우 hip 확인*/
const hipX = Big(current.x).minus(Big(point.x)).abs()
const hipY = Big(current.y).minus(Big(point.y)).abs()
if (hipX.eq(hipY) && hipX.gt(0) && hipY.gt(0)) {
checkHipLine = { x1: current.x, y1: current.y, x2: point.x, y2: point.y }
}
if (checkRidgeLine) {
const ridgePoints = [checkRidgeLine.x1, checkRidgeLine.y1, checkRidgeLine.x2, checkRidgeLine.y2]
let baseIntersection = false
const ridgeInterSection = []
const ridgeEdge = {
vertex1: { x: ridgePoints[0], y: ridgePoints[1] },
vertex2: { x: ridgePoints[2], y: ridgePoints[3] },
}
baseLines.forEach((line) => {
const intersection = edgesIntersection(ridgeEdge, {
vertex1: { x: line.x1, y: line.y1 },
vertex2: { x: line.x2, y: line.y2 },
})
if (intersection && !intersection.isIntersectionOutside) {
ridgeInterSection.push(intersection)
}
})
baseRidgeLines.forEach((line) => {
const intersection = edgesIntersection(ridgeEdge, {
vertex1: { x: line.x1, y: line.y1 },
vertex2: { x: line.x2, y: line.y2 },
})
if (intersection && !intersection.isIntersectionOutside) {
ridgeInterSection.push(intersection)
}
})
baseHipLines.forEach((line) => {
const intersection = edgesIntersection(ridgeEdge, {
vertex1: { x: line.x1, y: line.y1 },
vertex2: { x: line.x2, y: line.y2 },
})
if (intersection && !intersection.isIntersectionOutside) {
ridgeInterSection.push(intersection)
}
})
const otherRidgeInterSection = ridgeInterSection.filter(
(intersection) =>
!(
(intersection.x === ridgePoints[0] && intersection.y === ridgePoints[1]) ||
(intersection.x === ridgePoints[2] && intersection.y === ridgePoints[3])
),
)
const alreadyRidges = baseRidgeLines.filter(
(line) =>
(line.x1 === ridgePoints[0] && line.y1 === ridgePoints[1] && line.x2 === ridgePoints[2] && line.y2 === ridgePoints[3]) ||
(line.x1 === ridgePoints[2] && line.y1 === ridgePoints[3] && line.x2 === ridgePoints[0] && line.y2 === ridgePoints[1]),
)
if (
!baseIntersection &&
alreadyRidges.length === 0 &&
otherRidgeInterSection.length === 0 &&
baseRidgeCount < getMaxRidge(baseLines.length)
) {
baseRidgeCount = baseRidgeCount + 1
const ridgeLine = drawRidgeLine(ridgePoints, canvas, roof, textMode)
baseRidgeLines.push(ridgeLine)
}
}
if (checkHipLine) {
const hipPoints = [checkHipLine.x1, checkHipLine.y1, checkHipLine.x2, checkHipLine.y2]
let baseIntersection = false
let hipInterSection = []
const hipEdge = {
vertex1: { x: hipPoints[0], y: hipPoints[1] },
vertex2: { x: hipPoints[2], y: hipPoints[3] },
}
baseLines.forEach((line) => {
const intersection = edgesIntersection(hipEdge, {
vertex1: { x: line.x1, y: line.y1 },
vertex2: { x: line.x2, y: line.y2 },
})
if (intersection && !intersection.isIntersectionOutside && eavesType.includes(line.attributes.type)) {
baseIntersection = true
}
})
baseRidgeLines.forEach((line) => {
const intersection = edgesIntersection(hipEdge, {
vertex1: { x: line.x1, y: line.y1 },
vertex2: { x: line.x2, y: line.y2 },
})
if (intersection && !intersection.isIntersectionOutside) {
hipInterSection.push(intersection)
}
})
baseHipLines.forEach((line) => {
const intersection = edgesIntersection(hipEdge, {
vertex1: { x: line.x1, y: line.y1 },
vertex2: { x: line.x2, y: line.y2 },
})
if (intersection && !intersection.isIntersectionOutside) {
hipInterSection.push(intersection)
}
})
const otherHipInterSection = hipInterSection.filter(
(intersection) =>
!(
(intersection.x === hipPoints[0] && intersection.y === hipPoints[1]) ||
(intersection.x === hipPoints[2] && intersection.y === hipPoints[3])
),
)
const alreadyHips = baseHipLines.filter(
(line) =>
(line.x1 === hipPoints[0] && line.y1 === hipPoints[1] && line.x2 === hipPoints[2] && line.y2 === hipPoints[3]) ||
(line.x1 === hipPoints[2] && line.y1 === hipPoints[3] && line.x2 === hipPoints[0] && line.y2 === hipPoints[1]),
)
if (!baseIntersection && alreadyHips.length === 0 && otherHipInterSection.length === 0) {
const hipLine = drawHipLine(hipPoints, canvas, roof, textMode, null, prevDegree, currentDegree)
baseHipLines.push({ x1: hipPoints[0], y1: hipPoints[1], x2: hipPoints[2], y2: hipPoints[3], line: hipLine })
}
}
})
})
const innerLines = [...baseRidgeLines, ...baseGableRidgeLines, ...baseGableLines, ...baseHipLines.map((line) => line.line)]
const uniqueInnerLines = []
innerLines.forEach((currentLine) => {
if (currentLine.length === 0) {
canvas.remove(currentLine)
} else {
const sameLines = uniqueInnerLines.filter(
(line) =>
line !== currentLine &&
((line.x1 === currentLine.x1 && line.y1 === currentLine.y1 && line.x2 === currentLine.x2 && line.y2 === currentLine.y2) ||
(line.x1 === currentLine.x2 && line.y1 === currentLine.y2 && line.x2 === currentLine.x1 && line.y2 === currentLine.y1)),
)
if (sameLines.length === 0) {
uniqueInnerLines.push(currentLine)
} else {
canvas.remove(currentLine)
}
}
})
canvas.renderAll()
roof.innerLines = uniqueInnerLines
roof.innerLines.forEach((line) => {
line.bringToFront()
})
/** 확인용 라인 제거 */
canvas
.getObjects()
.filter((obj) => obj.name === 'checkCircle' || obj.name === 'checkLine')
.forEach((obj) => canvas.remove(obj))
canvas.renderAll()
/*
drawCenterLine(roof, canvas, textMode)
*/
}
/**
* 추녀 마루를 그린다.
* @param points
* @param canvas
* @param roof
* @param textMode
* @param currentRoof
* @param prevDegree
* @param currentDegree
*/
const drawHipLine = (points, canvas, roof, textMode, currentRoof, 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,
// currentRoofId: currentRoof.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 roof
* @param baseLine
* @param endPoint
* @returns {*}
*/
const findRoofIntersection = (roof, baseLine, endPoint) => {
let intersectPoints = []
const { x1, y1, x2, y2 } = baseLine
const baseEdge = {
vertex1: { x: x1, y: y1 },
vertex2: { x: x2, y: y2 },
}
/** 외벽선에서 라인 겹치는 경우에 대한 확인*/
roof.lines.forEach((line) => {
const lineEdge = { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }
const intersection = edgesIntersection(baseEdge, lineEdge)
if (
intersection &&
!intersection.isIntersectionOutside &&
Math.sign(endPoint.x - baseLine.x1) === Math.sign(endPoint.x - intersection.x) &&
Math.sign(endPoint.y - baseLine.y1) === Math.sign(endPoint.y - intersection.y)
) {
const intersectSize = endPoint.x
.minus(Big(intersection.x))
.pow(2)
.plus(endPoint.y.minus(Big(intersection.y)).pow(2))
.abs()
.sqrt()
.toNumber()
intersectPoints.push({
intersection,
size: intersectSize,
})
}
})
return intersectPoints.reduce((prev, current) => {
return prev.size < current.size ? prev : current
}, intersectPoints[0])
}
/**
* 마루를 그린다.
* @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 line1
* @param line2
* @returns {boolean}
*/
export const segmentsOverlap = (line1, line2) => {
if (line1.y1 === line1.y2 && line2.y1 === line2.y2 && line1.y1 === line2.y1) {
if ((line1.x1 <= line2.x1 && line1.x2 >= line2.x1) || (line1.x1 <= line2.x2 && line1.x2 >= line2.x2)) {
return true
}
}
if (line1.x1 === line1.x2 && line2.x1 === line2.x2 && line1.x1 === line2.x1) {
if ((line1.y1 <= line2.y1 && line1.y2 >= line2.y1) || (line1.y1 <= line2.y2 && line1.y2 >= line2.y2)) {
return true
}
}
return false
}
/**
* 최대 생성 마루 갯수
* @param length
* @returns {number}
*/
const getMaxRidge = (length) => {
return (length - 4) / 2 + 1
}
/**
* 지붕 모양 을 변경한다.
* @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
}