3557 lines
135 KiB
JavaScript
3557 lines
135 KiB
JavaScript
import { fabric } from 'fabric'
|
|
import { QLine } from '@/components/fabric/QLine'
|
|
import { getDegreeByChon } 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 { OUTER_LINE_TYPE } from '@/store/outerLineAtom'
|
|
import Big from 'big.js'
|
|
|
|
const TWO_PI = Math.PI * 2
|
|
|
|
export const defineQPloygon = () => {
|
|
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 !== undefined ? point2.x : 0)
|
|
.minus(point1?.x !== undefined ? point1.x : 0)
|
|
.toNumber()
|
|
const deltaY = Big(point2?.y !== undefined ? point2.y : 0)
|
|
.minus(point1?.y !== undefined ? point1.y : 0)
|
|
.toNumber()
|
|
const angleInRadians = Math.atan2(deltaY, deltaX)
|
|
return angleInRadians * (180 / Math.PI)
|
|
}
|
|
|
|
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) {
|
|
const uniquePolygons = []
|
|
|
|
polygons.forEach((polygon) => {
|
|
const isDuplicate = uniquePolygons.some((uniquePolygon) => arePolygonsEqual(polygon, uniquePolygon))
|
|
if (!isDuplicate) {
|
|
uniquePolygons.push(polygon)
|
|
}
|
|
})
|
|
|
|
return uniquePolygons
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
/**
|
|
* 박공지붕(templateA, templateB)을 그린다.
|
|
* @param roofId
|
|
* @param canvas
|
|
* @param textMode
|
|
*/
|
|
export const drawGabledRoof = (roofId, canvas, textMode) => {
|
|
const roof = canvas?.getObjects().find((object) => object.id === roofId)
|
|
const roofLines = roof.lines
|
|
const wallLines = canvas?.getObjects().find((object) => object.name === POLYGON_TYPE.WALL && object.attributes.roofId === roof.id).lines
|
|
const hasNonParallelLines = roofLines.filter((line) => line.x1 !== line.x2 && line.y1 !== line.y2)
|
|
if (hasNonParallelLines.length > 0) {
|
|
// alert('대각선이 존재합니다.')
|
|
return
|
|
}
|
|
|
|
// 처마라인의 기본속성 입력
|
|
const eaves = []
|
|
roofLines.forEach((currentRoof, index) => {
|
|
if (currentRoof.attributes?.type === LINE_TYPE.WALLLINE.EAVES) {
|
|
eaves.push({ index: index, roof: currentRoof, length: currentRoof.attributes.planeSize })
|
|
}
|
|
})
|
|
const ridges = []
|
|
eaves.sort((a, b) => a.length - b.length)
|
|
|
|
eaves.forEach((eave) => {
|
|
const index = eave.index,
|
|
currentRoof = eave.roof
|
|
const currentWall = wallLines[index]
|
|
|
|
const oppositeLine = roofLines
|
|
.filter((line) => line !== currentRoof) // 현재 벽라인을 제외한 나머지 벽라인
|
|
.filter((line) => {
|
|
if (currentRoof.x1 === currentRoof.x2) {
|
|
const vector = Math.sign(currentRoof.y1 - currentRoof.y2)
|
|
const vector2 = Math.sign(currentRoof.x1 - currentWall.x1)
|
|
return line.x1 === line.x2 && Math.sign(line.y1 - line.y2) === -vector && Math.sign(currentRoof.x1 - line.x1) === vector2
|
|
}
|
|
if (currentRoof.y1 === currentRoof.y2) {
|
|
const vector = Math.sign(currentRoof.x1 - currentRoof.x2)
|
|
const vector2 = Math.sign(currentRoof.y1 - currentWall.y1)
|
|
return line.y1 === line.y2 && Math.sign(line.x1 - line.x2) === -vector && Math.sign(currentRoof.y1 - line.y1) === vector2
|
|
}
|
|
}) // 현재 벽라인과 직교하는 벽라인
|
|
|
|
// 현재 벽라인과 직교하는 벽라인 사이에 마루를 그린다.
|
|
oppositeLine.forEach((line) => {
|
|
let points // 마루의 시작점과 끝점
|
|
if (currentRoof.x1 === currentRoof.x2) {
|
|
const currentRoofYRange = [Math.min(currentRoof.y1, currentRoof.y2), Math.max(currentRoof.y1, currentRoof.y2)]
|
|
const lineYRange = [Math.min(line.y1, line.y2), Math.max(line.y1, line.y2)]
|
|
const overlapYRange = [Math.max(currentRoofYRange[0], lineYRange[0]), Math.min(currentRoofYRange[1], lineYRange[1])]
|
|
if (overlapYRange[1] - overlapYRange[0] > 0) {
|
|
const centerX = Math.round(((currentRoof.x1 + line.x1) / 2) * 10) / 10
|
|
points = [centerX, overlapYRange[0], centerX, overlapYRange[1]]
|
|
}
|
|
}
|
|
if (currentRoof.y1 === currentRoof.y2) {
|
|
const currentRoofXRange = [Math.min(currentRoof.x1, currentRoof.x2), Math.max(currentRoof.x1, currentRoof.x2)]
|
|
const lineXRange = [Math.min(line.x1, line.x2), Math.max(line.x1, line.x2)]
|
|
const overlapXRange = [Math.max(currentRoofXRange[0], lineXRange[0]), Math.min(currentRoofXRange[1], lineXRange[1])]
|
|
if (overlapXRange[1] - overlapXRange[0] > 0) {
|
|
const centerY = Math.round(((currentRoof.y1 + line.y1) / 2) * 10) / 10
|
|
points = [overlapXRange[0], centerY, overlapXRange[1], centerY]
|
|
}
|
|
}
|
|
// 마루를 그린다.
|
|
if (points) {
|
|
const ridge = new QLine(points, {
|
|
parentId: roof.id,
|
|
fontSize: roof.fontSize,
|
|
stroke: '#1083E3',
|
|
strokeWidth: 1,
|
|
name: LINE_TYPE.SUBLINE.RIDGE,
|
|
attributes: { roofId: roof.id, currentRoofId: [currentRoof.id] },
|
|
visible: false,
|
|
})
|
|
const duplicateRidge = ridges.find(
|
|
(ridge) => ridge.x1 === points[0] && ridge.y1 === points[1] && ridge.x2 === points[2] && ridge.y2 === points[3],
|
|
)
|
|
// 중복된 마루는 제외한다.
|
|
if (duplicateRidge) {
|
|
duplicateRidge.attributes.currentRoofId.push(currentRoof.id)
|
|
} else {
|
|
canvas.add(ridge)
|
|
canvas.renderAll()
|
|
ridges.push(ridge)
|
|
}
|
|
}
|
|
})
|
|
})
|
|
// 처마마다 지붕 polygon 을 그린다.
|
|
eaves.forEach((eave) => {
|
|
const index = eave.index,
|
|
currentRoof = eave.roof
|
|
const currentWall = wallLines[index]
|
|
const currentRidges = ridges.filter((ridge) => ridge.attributes.currentRoofId.includes(eave.roof.id))
|
|
let points = []
|
|
const signX = Math.sign(currentRoof.x1 - currentRoof.x2)
|
|
let currentX1 = currentRoof.x1,
|
|
currentY1 = currentRoof.y1,
|
|
currentX2 = currentRoof.x2,
|
|
currentY2 = currentRoof.y2
|
|
let changeX1 = [Math.min(currentRoof.x1, currentRoof.x2), Math.min(currentRoof.x1, currentRoof.x2)],
|
|
changeY1 = [Math.min(currentRoof.y1, currentRoof.y2), Math.min(currentRoof.y1, currentRoof.y2)],
|
|
changeX2 = [Math.max(currentRoof.x2, currentRoof.x1), Math.max(currentRoof.x2, currentRoof.x1)],
|
|
changeY2 = [Math.max(currentRoof.y2, currentRoof.y1), Math.max(currentRoof.y2, currentRoof.y1)]
|
|
|
|
if (signX === 0) {
|
|
currentY1 = Math.min(currentRoof.y1, currentRoof.y2, currentWall.y1, currentWall.y2)
|
|
changeY1[1] = currentY1
|
|
currentY2 = Math.max(currentRoof.y1, currentRoof.y2, currentWall.y1, currentWall.y2)
|
|
changeY2[1] = currentY2
|
|
} else {
|
|
currentX1 = Math.min(currentRoof.x1, currentRoof.x2, currentWall.x1, currentWall.x2)
|
|
changeX1[1] = currentX1
|
|
currentX2 = Math.max(currentRoof.x1, currentRoof.x2, currentWall.x1, currentWall.x2)
|
|
changeX2[1] = currentX2
|
|
}
|
|
points.push({ x: currentX1, y: currentY1 }, { x: currentX2, y: currentY2 })
|
|
|
|
currentRidges.forEach((ridge) => {
|
|
let ridgeX1 = ridge.x1,
|
|
ridgeY1 = ridge.y1,
|
|
ridgeX2 = ridge.x2,
|
|
ridgeY2 = ridge.y2
|
|
if (signX === 0) {
|
|
ridgeY1 = Math.min(ridge.y1, ridge.y2)
|
|
ridgeY2 = Math.max(ridge.y1, ridge.y2)
|
|
} else {
|
|
ridgeX1 = Math.min(ridge.x1, ridge.x2)
|
|
ridgeX2 = Math.max(ridge.x1, ridge.x2)
|
|
}
|
|
points.push({ x: ridgeX1, y: ridgeY1 }, { x: ridgeX2, y: ridgeY2 })
|
|
})
|
|
|
|
points.forEach((point) => {
|
|
if (point.x === changeX1[0] && changeX1[0] !== changeX1[1]) {
|
|
point.x = changeX1[1]
|
|
}
|
|
if (point.x === changeX2[0] && changeX2[0] !== changeX2[1]) {
|
|
point.x = changeX2[1]
|
|
}
|
|
if (point.y === changeY1[0] && changeY1[0] !== changeY1[1]) {
|
|
point.y = changeY1[1]
|
|
}
|
|
if (point.y === changeY2[0] && changeY2[0] !== changeY2[1]) {
|
|
point.y = changeY2[1]
|
|
}
|
|
})
|
|
//중복된 point 제거
|
|
points = points.filter((point, index, self) => index === self.findIndex((p) => p.x === point.x && p.y === point.y))
|
|
//point 정렬 (가장 좌측, 최상단의 점을 기준으로 삼는다.)
|
|
const startPoint = points
|
|
.filter((point) => point.x === Math.min(...points.map((point) => point.x)))
|
|
.reduce((prev, current) => {
|
|
return prev.y < current.y ? prev : current
|
|
})
|
|
|
|
const sortedPoints = []
|
|
sortedPoints.push(startPoint)
|
|
|
|
points.forEach((p, index) => {
|
|
if (index === 0) {
|
|
//시작점 다음 점 찾기, y좌표가 startPoint.y 보다 큰 점 중 x좌표가 가까운 점
|
|
const underStartPoint = points.filter((point) => point.y > startPoint.y)
|
|
const nextPoint = underStartPoint
|
|
.filter((point) => point.x === startPoint.x)
|
|
.reduce((prev, current) => {
|
|
if (prev === undefined) {
|
|
return current
|
|
}
|
|
return Math.abs(prev.y - startPoint.y) < Math.abs(current.y - startPoint.y) ? prev : current
|
|
}, undefined)
|
|
if (nextPoint) {
|
|
sortedPoints.push(nextPoint)
|
|
} else {
|
|
const nextPoint = underStartPoint.reduce((prev, current) => {
|
|
const prevHypos = Math.sqrt(Math.abs(Math.pow(prev.x - startPoint.x, 2)) + Math.abs(Math.pow(prev.y - startPoint.y, 2)))
|
|
const currentHypos = Math.sqrt(Math.abs(Math.pow(current.x - startPoint.x, 2)) + Math.abs(Math.pow(current.y - startPoint.y, 2)))
|
|
return prevHypos < currentHypos ? prev : current
|
|
}, undefined)
|
|
sortedPoints.push(nextPoint)
|
|
}
|
|
} else {
|
|
const lastPoint = sortedPoints[sortedPoints.length - 1]
|
|
const prevPoint = sortedPoints[sortedPoints.length - 2]
|
|
const otherPoints = points.filter((point) => sortedPoints.includes(point) === false)
|
|
const nextPoint = otherPoints.reduce((prev, current) => {
|
|
if (prev === undefined) {
|
|
const height = Math.abs(Math.sqrt(Math.abs(Math.pow(prevPoint.x - lastPoint.x, 2)) + Math.abs(Math.pow(prevPoint.y - lastPoint.y, 2))))
|
|
const adjacent = Math.abs(Math.sqrt(Math.abs(Math.pow(current.x - lastPoint.x, 2)) + Math.abs(Math.pow(current.y - lastPoint.y, 2))))
|
|
const hypotenuse = Math.abs(Math.sqrt(Math.abs(Math.pow(current.x - prevPoint.x, 2)) + Math.abs(Math.pow(current.y - prevPoint.y, 2))))
|
|
|
|
const angle = Math.round(
|
|
Math.acos((Math.pow(adjacent, 2) + Math.pow(height, 2) - Math.pow(hypotenuse, 2)) / (2 * adjacent * height)) * (180 / Math.PI),
|
|
)
|
|
if (angle === 90) {
|
|
return current
|
|
}
|
|
} else {
|
|
return prev
|
|
}
|
|
}, undefined)
|
|
if (nextPoint) {
|
|
sortedPoints.push(nextPoint)
|
|
} else {
|
|
const nextPoint = otherPoints.reduce((prev, current) => {
|
|
if (prev !== undefined) {
|
|
const height = Math.abs(Math.sqrt(Math.abs(Math.pow(prevPoint.x - lastPoint.x, 2)) + Math.abs(Math.pow(prevPoint.y - lastPoint.y, 2))))
|
|
const adjacentC = Math.abs(Math.sqrt(Math.abs(Math.pow(current.x - lastPoint.x, 2)) + Math.abs(Math.pow(current.y - lastPoint.y, 2))))
|
|
const hypotenuseC = Math.abs(Math.sqrt(Math.abs(Math.pow(current.x - prevPoint.x, 2)) + Math.abs(Math.pow(current.y - prevPoint.y, 2))))
|
|
const angleC = Math.round(
|
|
Math.acos((Math.pow(adjacentC, 2) + Math.pow(height, 2) - Math.pow(hypotenuseC, 2)) / (2 * adjacentC * height)) * (180 / Math.PI),
|
|
)
|
|
const adjacentP = Math.abs(Math.sqrt(Math.abs(Math.pow(prev.x - lastPoint.x, 2)) + Math.abs(Math.pow(prev.y - lastPoint.y, 2))))
|
|
const hypotenuseP = Math.abs(Math.sqrt(Math.abs(Math.pow(prev.x - prevPoint.x, 2)) + Math.abs(Math.pow(prev.y - prevPoint.y, 2))))
|
|
const angleP = Math.round(
|
|
Math.acos((Math.pow(adjacentP, 2) + Math.pow(height, 2) - Math.pow(hypotenuseP, 2)) / (2 * adjacentP * height)) * (180 / Math.PI),
|
|
)
|
|
if (Math.abs(90 - angleC) < Math.abs(90 - angleP)) {
|
|
return current
|
|
} else {
|
|
return prev
|
|
}
|
|
} else {
|
|
return current
|
|
}
|
|
}, undefined)
|
|
if (nextPoint) {
|
|
sortedPoints.push(nextPoint)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
if (sortedPoints.length > 0) {
|
|
const roofPolygon = new QPolygon(sortedPoints, {
|
|
parentId: roof.id,
|
|
fill: 'transparent',
|
|
stroke: '#1083E3',
|
|
strokeWidth: 2,
|
|
selectable: false,
|
|
fontSize: roof.fontSize,
|
|
name: 'roofPolygon',
|
|
attributes: {
|
|
roofId: roof.id,
|
|
currentRoofId: currentRoof.id,
|
|
pitch: currentRoof.attributes.pitch,
|
|
degree: currentRoof.attributes.degree,
|
|
direction: currentRoof.direction,
|
|
},
|
|
originX: 'center',
|
|
originY: 'center',
|
|
})
|
|
const currentDegree = currentRoof.attributes.pitch > 0 ? getDegreeByChon(currentRoof.attributes.pitch) : currentRoof.attributes.degree
|
|
//지붕 각도에 따른 실측치 조정
|
|
roofPolygon.lines.forEach((line) => {
|
|
line.attributes.planeSize = Math.round(Math.sqrt(Math.pow(line.x2 - line.x1, 2) + Math.pow(line.y2 - line.y1, 2)) * 10)
|
|
const slope = (line) => (line.x2 - line.x1 === 0 ? Infinity : (line.y2 - line.y1) / (line.x2 - line.x1))
|
|
|
|
if (currentDegree > 0 && slope(line) !== slope(currentRoof)) {
|
|
const height = Math.tan(currentDegree * (Math.PI / 180)) * line.attributes.planeSize
|
|
line.attributes.actualSize = Math.round(Math.sqrt(Math.pow(line.attributes.planeSize, 2) + Math.pow(height, 2)))
|
|
} else {
|
|
line.attributes.actualSize = line.attributes.planeSize
|
|
}
|
|
})
|
|
roof.separatePolygon.push(roofPolygon)
|
|
canvas.add(roofPolygon)
|
|
canvas.renderAll()
|
|
}
|
|
})
|
|
|
|
if (ridges.length > 0) {
|
|
ridges.forEach((ridge) => roof.innerLines.push(ridge))
|
|
}
|
|
//기준선 제거
|
|
// ridges.forEach((ridge) => canvas.remove(ridge))
|
|
|
|
eaves.forEach((eave) => {
|
|
const currentRoof = eave.roof
|
|
let currentRidges = ridges.filter((ridge) => ridge.attributes.currentRoofId.includes(currentRoof.id))
|
|
if (Math.sign(currentRoof.x1 - currentRoof.x2) === 0) {
|
|
currentRidges = currentRidges.reduce((farthest, ridge) => {
|
|
const currentDistance = Math.abs(ridge.x1 - currentRoof.x1)
|
|
const farthestDistance = farthest ? Math.abs(farthest.x1 - currentRoof.x1) : 0
|
|
return currentDistance > farthestDistance ? ridge : farthest
|
|
}, null)
|
|
} else {
|
|
currentRidges = currentRidges.reduce((farthest, ridge) => {
|
|
const currentDistance = Math.abs(ridge.y1 - currentRoof.y1)
|
|
const farthestDistance = farthest ? Math.abs(farthest.y1 - currentRoof.y1) : 0
|
|
return currentDistance > farthestDistance ? ridge : farthest
|
|
}, null)
|
|
}
|
|
if (currentRidges) {
|
|
const ridgeMidX = (currentRidges.x1 + currentRidges.x2) / 2
|
|
const ridgeMidY = (currentRidges.y1 + currentRidges.y2) / 2
|
|
const x2 = Math.sign(currentRidges.x1 - currentRidges.x2) === 0 ? currentRoof.x1 : ridgeMidX
|
|
const y2 = Math.sign(currentRidges.x1 - currentRidges.x2) === 0 ? ridgeMidY : currentRoof.y1
|
|
|
|
const pitchSizeLine = new QLine([ridgeMidX, ridgeMidY, x2, y2], {
|
|
parentId: roof.id,
|
|
stroke: '#000000',
|
|
strokeWidth: 2,
|
|
strokeDashArray: [5, 5],
|
|
selectable: false,
|
|
fontSize: roof.fontSize,
|
|
attributes: {
|
|
roofId: roof.id,
|
|
type: 'pitchSizeLine',
|
|
},
|
|
})
|
|
canvas.add(pitchSizeLine)
|
|
canvas.renderAll()
|
|
|
|
const adjust = Math.sqrt(
|
|
Math.pow(Math.round(Math.abs(pitchSizeLine.x1 - pitchSizeLine.x2) * 10), 2) +
|
|
Math.pow(Math.round(Math.abs(pitchSizeLine.y1 - pitchSizeLine.y2) * 10), 2),
|
|
)
|
|
const currentDegree = currentRoof.attributes.pitch > 0 ? getDegreeByChon(currentRoof.attributes.pitch) : currentRoof.attributes.degree
|
|
const height = Math.tan(currentDegree * (Math.PI / 180)) * adjust
|
|
const lengthText = Math.round(Math.sqrt(Math.pow(adjust, 2) + Math.pow(height, 2)))
|
|
pitchSizeLine.setLengthText(lengthText)
|
|
}
|
|
})
|
|
}
|
|
|
|
/**
|
|
* 한쪽흐름 지붕
|
|
* @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)
|
|
|
|
let shedDegree = sheds[0].attributes.degree || 0
|
|
const shedChon = sheds[0].attributes.pitch || 0
|
|
|
|
if (shedDegree === 0) {
|
|
shedDegree = getDegreeByChon(shedChon)
|
|
}
|
|
const getHeight = function (adjust, degree) {
|
|
return Math.tan(degree * (Math.PI / 180)) * adjust
|
|
}
|
|
|
|
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 planeSize = Math.round(Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)) * 10)
|
|
const planeSize = calcLinePlaneSize({ x1, y1, x2, y2 })
|
|
// const actualSize = Math.round(Math.sqrt (Math.pow(planeSize, 2) + Math.pow(getHeight(planeSize, shedDegree), 2)) * 10)
|
|
const actualSize = calcLineActualSize({ x1, y1, x2, y2 }, shedDegree)
|
|
|
|
const line = new QLine([x1, y1, x2, y2], {
|
|
parentId: roof.id,
|
|
stroke: '#000000',
|
|
strokeWidth: 2,
|
|
strokeDashArray: [5, 5],
|
|
selectable: false,
|
|
fontSize: roof.fontSize,
|
|
textMode: textMode,
|
|
attributes: {
|
|
roofId: roof.id,
|
|
type: 'pitchSizeLine',
|
|
planeSize: actualSize,
|
|
actualSize: actualSize,
|
|
},
|
|
})
|
|
pitchSizeLines.push(line)
|
|
})
|
|
})
|
|
|
|
const maxLine = pitchSizeLines.reduce((prev, current) => (prev.length > current.length ? prev : current), pitchSizeLines[0])
|
|
canvas.add(maxLine)
|
|
canvas.renderAll()
|
|
}
|
|
|
|
/**
|
|
* 마루가 있는 지붕을 그린다.
|
|
* @param roofId
|
|
* @param canvas
|
|
* @param textMode
|
|
*/
|
|
export const drawRidgeRoof = (roofId, canvas, textMode) => {
|
|
const roof = canvas?.getObjects().find((object) => object.id === roofId)
|
|
|
|
//Math.abs(line.x1 - line.x2) > 1 && Math.abs(line.y1 - line.y2) > 1
|
|
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) {
|
|
// alert('대각선이 존재ㄴ합니다.')
|
|
return
|
|
}
|
|
|
|
drawRidge(roof, canvas, textMode)
|
|
drawHips(roof, canvas, textMode)
|
|
connectLinePoint(roof, canvas, textMode)
|
|
modifyRidge(roof, canvas, textMode)
|
|
drawCenterLine(roof, canvas, textMode)
|
|
}
|
|
|
|
/**
|
|
* 마루가 존재하면 그린다. 마루는 지붕의 중간에 위치한다.
|
|
*
|
|
* @param roof
|
|
* @param canvas
|
|
* @param textMode
|
|
*/
|
|
const drawRidge = (roof, canvas, textMode) => {
|
|
console.log('roof.lines : ', roof.lines)
|
|
const wallLines = canvas?.getObjects().find((object) => object.name === POLYGON_TYPE.WALL && object.attributes.roofId === roof.id).lines // 외벽의 라인
|
|
const roofLines = roof.lines // 지붕의 라인
|
|
let ridgeRoof = []
|
|
|
|
roofLines.forEach((currentRoof, index) => {
|
|
let prevRoof,
|
|
nextRoof,
|
|
currentWall = wallLines[index]
|
|
|
|
prevRoof = index === 0 ? wallLines[wallLines.length - 1] : wallLines[index - 1]
|
|
nextRoof = index === wallLines.length - 1 ? wallLines[0] : index === wallLines.length ? wallLines[1] : wallLines[index + 1]
|
|
|
|
const angle1 = Big(calculateAngle(prevRoof.startPoint, prevRoof.endPoint))
|
|
const angle2 = Big(calculateAngle(nextRoof.startPoint, nextRoof.endPoint))
|
|
if (
|
|
angle1.minus(angle2).abs().round(Big.roundHalfUp).toNumber() === 180 &&
|
|
currentWall.attributes.planeSize <= currentRoof.attributes.planeSize
|
|
) {
|
|
ridgeRoof.push({ index: index, roof: currentRoof, length: currentRoof.attributes.planeSize })
|
|
}
|
|
})
|
|
|
|
// 지붕의 길이가 짧은 순으로 정렬
|
|
ridgeRoof.sort((a, b) => a.length - b.length)
|
|
|
|
ridgeRoof.forEach((item) => {
|
|
if (getMaxRidge(roofLines.length) > roof.ridges.length) {
|
|
const index = item.index,
|
|
currentRoof = item.roof
|
|
const prevRoof = index === 0 ? roofLines[wallLines.length - 1] : roofLines[index - 1]
|
|
const nextRoof = index === roofLines.length - 1 ? roofLines[0] : index === roofLines.length ? roofLines[1] : roofLines[index + 1]
|
|
let startXPoint, startYPoint, endXPoint, endYPoint
|
|
|
|
let wallLine = wallLines.filter((w) => w.id === currentRoof.attributes.wallLine)
|
|
if (wallLine.length > 0) {
|
|
wallLine = wallLine[0]
|
|
}
|
|
|
|
const anotherRoof = roofLines.filter((roof) => roof !== currentRoof && roof !== prevRoof && roof !== nextRoof)
|
|
|
|
let currentX1 = Big(currentRoof.x1),
|
|
currentY1 = Big(currentRoof.y1),
|
|
currentX2 = Big(currentRoof.x2),
|
|
currentY2 = Big(currentRoof.y2)
|
|
let ridgeMaxLength = Big(Math.max(prevRoof.attributes.planeSize, nextRoof.attributes.planeSize)).div(10)
|
|
let ridgeMinLength = Big(Math.min(prevRoof.attributes.planeSize, nextRoof.attributes.planeSize)).div(10)
|
|
|
|
const currentAngle = Math.atan2(currentY2.minus(currentY1).toNumber(), currentX2.minus(currentX1).toNumber()) * (180 / Math.PI)
|
|
anotherRoof
|
|
.filter((roof) => isInnerLine(prevRoof, currentRoof, nextRoof, roof))
|
|
.forEach((innerRoof) => {
|
|
const vector1 = { x: currentX2.minus(currentX1), y: currentY2.minus(currentY1) }
|
|
const vector2 = {
|
|
x: Big(innerRoof.x2).minus(Big(innerRoof.x1)),
|
|
y: Big(innerRoof.y2).minus(Big(innerRoof.y1)),
|
|
}
|
|
|
|
const dotProduct = vector1.x.times(vector2.x).plus(vector1.y.times(vector2.y))
|
|
const magnitude1 = vector1.x.pow(2).plus(vector1.y.pow(2)).sqrt()
|
|
const magnitude2 = vector2.x.pow(2).plus(vector2.y.pow(2)).sqrt()
|
|
const angle = (Math.acos(dotProduct.div(magnitude1.times(magnitude2).toNumber())) * 180) / Math.PI
|
|
|
|
// 현재 지붕선과 직각인 선
|
|
if (Math.abs(angle) === 90) {
|
|
const innerBefore = roofLines.find((line) => innerRoof.x1 === line.x2 && innerRoof.y1 === line.y2)
|
|
const ibAngle =
|
|
Math.atan2(Big(innerBefore.y2).minus(Big(innerBefore.y1)).toNumber(), Big(innerBefore.x2).minus(Big(innerBefore.x1)).toNumber()) *
|
|
(180 / Math.PI)
|
|
if (Math.abs(Big(currentAngle).minus(Big(ibAngle))) === 180) {
|
|
if (currentAngle === 0 || currentAngle === 180) {
|
|
currentX2 = innerRoof.x1
|
|
ridgeMinLength = Big(nextRoof.x1)
|
|
.minus(Big(nextRoof.x2))
|
|
.times(10)
|
|
.round()
|
|
.pow(2)
|
|
.plus(Big(nextRoof.y1).minus(Big(nextRoof.y2)).times(10).round().pow(2))
|
|
.sqrt()
|
|
.div(10)
|
|
}
|
|
if (currentAngle === 90 || currentAngle === 270) {
|
|
currentY2 = innerRoof.y1
|
|
ridgeMinLength = Big(nextRoof.x1)
|
|
.minus(Big(innerRoof.x2))
|
|
.times(10)
|
|
.round()
|
|
.pow(2)
|
|
.plus(Big(nextRoof.y1).minus(Big(nextRoof.y2)).times(10).round().pow(2))
|
|
.sqrt()
|
|
.div(10)
|
|
}
|
|
}
|
|
if (Math.abs(currentAngle - ibAngle) === 0) {
|
|
if (currentAngle === 0 || currentAngle === 180) {
|
|
currentX1 = innerRoof.x2
|
|
ridgeMinLength = Big(prevRoof.x1)
|
|
.minus(Big(prevRoof.x2))
|
|
.times(10)
|
|
.round()
|
|
.pow(2)
|
|
.plus(Big(prevRoof.y1).minus(Big(innerRoof.y1)).times(10).round().pow(2))
|
|
.sqrt()
|
|
.div(10)
|
|
}
|
|
if (currentAngle === 90 || currentAngle === 270) {
|
|
currentY1 = innerRoof.y2
|
|
ridgeMinLength = Big(prevRoof.x1)
|
|
.minus(innerRoof.x1)
|
|
.times(10)
|
|
.round()
|
|
.pow(2)
|
|
.plus(Big(prevRoof.y2).minus(prevRoof.y2).times(10).round().pow(2))
|
|
.sqrt()
|
|
.div(10)
|
|
}
|
|
}
|
|
}
|
|
// 현재 지붕선과 반대인 선
|
|
if (Math.abs(angle) === 180) {
|
|
if (currentAngle === 0 || currentAngle === 180) {
|
|
}
|
|
if (currentAngle === 90 || currentAngle === 270) {
|
|
}
|
|
}
|
|
})
|
|
|
|
// 지붕선의 X 중심
|
|
const midX = currentX1.plus(currentX2).div(2)
|
|
// 지붕선의 Y 중심
|
|
const midY = currentY1.plus(currentY2).div(2)
|
|
|
|
// 외선벽과 지붕선의 X 거리
|
|
const alpha = Big(currentRoof.x1)
|
|
.plus(Big(currentRoof.x2))
|
|
.div(2)
|
|
.minus(Big(wallLine.x1).plus(Big(wallLine.x2)).div(2))
|
|
// 외벽벽과 지붕선의 Y 거리
|
|
const beta = Big(currentRoof.y1)
|
|
.plus(Big(currentRoof.y2))
|
|
.div(2)
|
|
.minus(Big(wallLine.y1).plus(Big(wallLine.y2)).div(2))
|
|
// 외벽벽과 지붕선의 거리
|
|
const hypotenuse = alpha.pow(2).plus(beta.pow(2)).sqrt()
|
|
|
|
// 지붕선의 평면길이
|
|
const currentPlaneSize = Big(calcLinePlaneSize({ x1: currentX1, y1: currentY1, x2: currentX2, y2: currentY2 }))
|
|
// 용마루의 기반길이(지붕선에서 떨어진 정도)
|
|
let ridgeBaseLength = currentPlaneSize.div(2).round().div(10)
|
|
|
|
// 시작점은 현재 지붕선을 대각선으로 한 직각이등변삼각형으로 판단하고 현재 지붕선의 가운데에서 지붕에서 90도방향으로 지붕선의 절반만큼 떨어진 방향으로 설정한다.
|
|
startXPoint = midX.plus(alpha.div(hypotenuse).times(currentPlaneSize.div(2)).neg().div(10))
|
|
startYPoint = midY.plus(beta.div(hypotenuse).times(currentPlaneSize.div(2)).neg().div(10))
|
|
|
|
// 종료점은 시작점에서 용마루의 최대길이 + 기반길이 만큼 그려진것으로 판단하여 늘린다.
|
|
const checkEndXPoint = startXPoint.plus(Big(Math.sign(alpha.toNumber())).times(ridgeMaxLength.plus(ridgeBaseLength)).neg())
|
|
const checkEndYPoint = startYPoint.plus(Big(Math.sign(beta.toNumber())).times(ridgeMaxLength.plus(ridgeBaseLength)).neg())
|
|
|
|
// 맞은편 외벽선을 확인하기 위한 라인을 생성
|
|
const checkLine = new QLine([startXPoint.toNumber(), startYPoint.toNumber(), checkEndXPoint.toNumber(), checkEndYPoint.toNumber()], {
|
|
parentId: roof.id,
|
|
fontSize: roof.fontSize,
|
|
stroke: 'red',
|
|
strokeWidth: 2,
|
|
name: LINE_TYPE.SUBLINE.HIP,
|
|
attributes: { roofId: roof.id, currentRoofId: currentRoof.id, actualSize: 0 },
|
|
})
|
|
// 디버그시 주석제거해서 라인 확인
|
|
// canvas.add(checkLine)
|
|
// canvas.renderAll()
|
|
|
|
// checkLine 과 마주치는 지붕선을 모두 찾아낸다.
|
|
const intersectLines = []
|
|
roofLines.forEach((line) => {
|
|
const intersection = edgesIntersection(
|
|
{ vertex1: { x: checkLine.x1, y: checkLine.y1 }, vertex2: { x: checkLine.x2, y: checkLine.y2 } },
|
|
{ vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } },
|
|
)
|
|
if (intersection && !intersection.isIntersectionOutside) {
|
|
intersectLines.push({ x: intersection.x, y: intersection.y, line: line })
|
|
}
|
|
})
|
|
// intersectLines 중 현재 지붕선과 가장 가까운 지붕선을 찾는다.
|
|
if (intersectLines.length > 0) {
|
|
intersectLines.reduce((prev, current) => {
|
|
if (prev !== undefined) {
|
|
const prevDistance = Big(prev.x).minus(startXPoint).pow(2).plus(Big(prev.y).minus(startYPoint).pow(2)).sqrt()
|
|
const currentDistance = Big(current.x).minus(startXPoint).pow(2).plus(Big(current.y).minus(startYPoint).pow(2)).sqrt()
|
|
return prevDistance > currentDistance ? current : prev
|
|
} else {
|
|
return current
|
|
}
|
|
}, undefined)
|
|
}
|
|
// 현재 지붕선과 마주하는 지붕선이 있는 경우
|
|
if (intersectLines.length > 0) {
|
|
// 마주하는 지붕선
|
|
const intersectLine = intersectLines[0]
|
|
|
|
//지붕선에서 마주하는 지붕선까지의 x,y 좌표의 차이
|
|
|
|
const diffX = Big(intersectLine.x).minus(startXPoint).round(1).abs().lt(1) ? Big(0) : Big(intersectLine.x).minus(startXPoint).round(1)
|
|
const diffY = Big(intersectLine.y).minus(startYPoint).round(1).abs().lt(1) ? Big(0) : Big(intersectLine.y).minus(startYPoint).round(1)
|
|
// 시작점에서 ridgeBaseLength 만큼 떨어진 지점까지 용마루 길이로 본다.
|
|
endXPoint = startXPoint.plus(Big(Math.sign(diffX.toNumber())).times(diffX.abs().minus(ridgeBaseLength)))
|
|
endYPoint = startYPoint.plus(Big(Math.sign(diffY.toNumber())).times(diffY.abs().minus(ridgeBaseLength)))
|
|
|
|
// 시작점에서 종료점까지의 평면길이
|
|
//startXPoint.minus(endXPoint).abs().pow(2).plus(startYPoint.minus(endYPoint).abs().pow(2)).sqrt()
|
|
const hypo = Big(
|
|
calcLinePlaneSize({
|
|
x1: startXPoint.toNumber(),
|
|
y1: startYPoint.toNumber(),
|
|
x2: endXPoint.toNumber(),
|
|
y2: endYPoint.toNumber(),
|
|
}),
|
|
).div(10)
|
|
// 현재 지붕선과 마주하는 지붕선을 잇는 선분의 교차점까지의 길이
|
|
const intersectLength = Big(
|
|
calcLinePlaneSize({
|
|
x1: midX.toNumber(),
|
|
y1: midY.toNumber(),
|
|
x2: intersectLine.x,
|
|
y2: intersectLine.y,
|
|
}),
|
|
).div(10)
|
|
//마주하는 지붕선까지의 길이가 현재 지붕선의 이전, 다음 지붕선의 길이보다 작을때
|
|
if (intersectLength.lt(Big(prevRoof.attributes.planeSize).div(10)) && intersectLength.lt(Big(nextRoof.attributes.planeSize).div(10))) {
|
|
endXPoint = startXPoint
|
|
endYPoint = startYPoint
|
|
} else {
|
|
if (ridgeMinLength.lt(hypo)) {
|
|
endXPoint = startXPoint.plus(Big(Math.sign(diffX.toNumber())).times(ridgeMinLength))
|
|
endYPoint = startYPoint.plus(Big(Math.sign(diffY.toNumber())).times(ridgeMinLength))
|
|
}
|
|
}
|
|
} else {
|
|
endXPoint = startXPoint.plus(Big(Math.sign(alpha.toNumber())).times(ridgeMinLength).neg())
|
|
endYPoint = startYPoint.plus(Big(Math.sign(beta.toNumber())).times(ridgeMinLength).neg())
|
|
}
|
|
|
|
// 용마루 선을 그린다.
|
|
const ridge = new QLine([startXPoint.toNumber(), startYPoint.toNumber(), endXPoint.toNumber(), endYPoint.toNumber()], {
|
|
parentId: roof.id,
|
|
fontSize: roof.fontSize,
|
|
stroke: '#1083E3',
|
|
strokeWidth: 2,
|
|
name: LINE_TYPE.SUBLINE.RIDGE,
|
|
textMode: textMode,
|
|
attributes: {
|
|
roofId: roof.id,
|
|
planeSize: calcLinePlaneSize({
|
|
x1: startXPoint.toNumber(),
|
|
y1: startYPoint.toNumber(),
|
|
x2: endXPoint.toNumber(),
|
|
y2: endYPoint.toNumber(),
|
|
}),
|
|
actualSize: calcLinePlaneSize({
|
|
x1: startXPoint.toNumber(),
|
|
y1: startYPoint.toNumber(),
|
|
x2: endXPoint.toNumber(),
|
|
y2: endYPoint.toNumber(),
|
|
}),
|
|
},
|
|
})
|
|
|
|
// 용마루의 길이가 0보다 클때만 canvas 에 추가 한다.
|
|
if (ridge.attributes.planeSize > 0) {
|
|
canvas.add(ridge)
|
|
roof.ridges.push(ridge)
|
|
roof.innerLines.push(ridge)
|
|
|
|
const distance = (x1, y1, x2, y2) => x2.minus(x1).pow(2).plus(y2.minus(y1).pow(2)).sqrt()
|
|
const dist1 = distance(startXPoint, startYPoint, Big(currentRoof.x1), Big(currentRoof.y1))
|
|
const dist2 = distance(endXPoint, endYPoint, Big(currentRoof.x1), Big(currentRoof.y1))
|
|
|
|
currentRoof.attributes.ridgeCoordinate = {
|
|
x1: dist1 < dist2 ? startXPoint : endXPoint,
|
|
y1: dist1 < dist2 ? startYPoint : endYPoint,
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
//겹쳐지는 마루는 하나로 합침
|
|
roof.ridges.forEach((ridge) => {
|
|
roof.ridges
|
|
.filter((ridge2) => !(ridge.x1 === ridge2.x1 && ridge.y1 === ridge2.y1 && ridge.x2 === ridge2.x2 && ridge.y2 === ridge2.y2))
|
|
.forEach((ridge2) => {
|
|
let overlap = segmentsOverlap(ridge, ridge2)
|
|
if (overlap) {
|
|
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 = new QLine([x1, y1, x2, y2], {
|
|
parentId: roof.id,
|
|
fontSize: roof.fontSize,
|
|
stroke: '#1083E3',
|
|
strokeWidth: 2,
|
|
name: LINE_TYPE.SUBLINE.RIDGE,
|
|
textMode: textMode,
|
|
attributes: {
|
|
roofId: roof.id,
|
|
planeSize: calcLinePlaneSize({ x1, y1, x2, y2 }),
|
|
actualSize: calcLinePlaneSize({ x1, y1, x2, y2 }),
|
|
},
|
|
})
|
|
//겹치는 용마루를 제거한다.
|
|
roof.canvas.remove(ridge)
|
|
roof.canvas.remove(ridge2)
|
|
|
|
roof.ridges = roof.ridges.filter((r) => !(ridge.x1 === r.x1 && ridge.y1 === r.y1 && ridge.x2 === r.x2 && ridge.y2 === r.y2))
|
|
roof.ridges = roof.ridges.filter((r) => !(ridge2.x1 === r.x1 && ridge2.y1 === r.y1 && ridge2.x2 === r.x2 && ridge2.y2 === r.y2))
|
|
roof.innerLines = roof.innerLines.filter((r) => !(ridge.x1 === r.x1 && ridge.y1 === r.y1 && ridge.x2 === r.x2 && ridge.y2 === r.y2))
|
|
roof.innerLines = roof.innerLines.filter((r) => !(ridge2.x1 === r.x1 && ridge2.y1 === r.y1 && ridge2.x2 === r.x2 && ridge2.y2 === r.y2))
|
|
canvas.add(newRidge)
|
|
roof.ridges.push(newRidge)
|
|
roof.innerLines.push(newRidge)
|
|
}
|
|
})
|
|
})
|
|
canvas?.renderAll()
|
|
}
|
|
|
|
/**
|
|
* line 이 세 라인 사이에 존재하는지 확인한다.
|
|
* @param prevLine
|
|
* @param currentLine
|
|
* @param nextLine
|
|
* @param line
|
|
*/
|
|
const isInnerLine = (prevLine, currentLine, nextLine, line) => {
|
|
let inside = false
|
|
let minX = Math.min(currentLine.x1, currentLine.x2, prevLine.x1, nextLine.x2)
|
|
let maxX = Math.max(currentLine.x1, currentLine.x2, prevLine.x1, nextLine.x2)
|
|
let minY = Math.min(currentLine.y1, currentLine.y2, prevLine.y1, nextLine.y2)
|
|
let maxY = Math.max(currentLine.y1, currentLine.y2, prevLine.y1, nextLine.y2)
|
|
|
|
if (minX < line.x1 && line.x1 < maxX && minY < line.y1 && line.y1 < maxY && minX < line.x2 && line.x2 < maxX && minY < line.y2 && line.y2 < maxY) {
|
|
inside = true
|
|
}
|
|
|
|
return inside
|
|
}
|
|
|
|
/**
|
|
* 두 선분이 겹치는지 확인
|
|
* @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 roof
|
|
* @param canvas
|
|
* @param textMode
|
|
*/
|
|
const drawHips = (roof, canvas, textMode) => {
|
|
const roofLines = roof.lines
|
|
const ridgeLines = canvas?.getObjects().filter((object) => object.name === LINE_TYPE.SUBLINE.RIDGE && object.attributes.roofId === roof.id)
|
|
|
|
//마루에서 시작되는 hip 을 먼저 그립니다.
|
|
roofLines
|
|
.filter((roof) => roof.attributes.type === LINE_TYPE.WALLLINE.EAVES && roof.attributes.ridgeCoordinate !== undefined)
|
|
.forEach((currentRoof, index) => {
|
|
const prevRoof = roof.lines[index === 0 ? roof.lines.length - 1 : index - 1]
|
|
const nextRoof = roof.lines[index === roof.lines.length - 1 ? 0 : index + 1]
|
|
const prevDegree = prevRoof.attributes.pitch > 0 ? getDegreeByChon(prevRoof.attributes.pitch) : prevRoof.attributes.degree
|
|
const currentDegree = currentRoof.attributes.pitch > 0 ? getDegreeByChon(currentRoof.attributes.pitch) : currentRoof.attributes.degree
|
|
const nextDegree = nextRoof.attributes.pitch > 0 ? getDegreeByChon(nextRoof.attributes.pitch) : nextRoof.attributes.degree
|
|
|
|
//용마루 작성시 입력된 용마루 좌표가 있는 경우 처리
|
|
const ridgeCoordinate = currentRoof.attributes.ridgeCoordinate
|
|
|
|
const vectorX1 = Big(ridgeCoordinate.x1).minus(Big(currentRoof.x1))
|
|
const vectorY1 = Big(ridgeCoordinate.y1).minus(Big(currentRoof.y1))
|
|
//현재 지붕선의 좌표와 용마루 좌표의 각도를 구한다.
|
|
const angle1 = Big(Math.atan2(vectorY1.toNumber(), vectorX1.toNumber())).times(Big(180).div(Math.PI))
|
|
|
|
// 용마루 까지의 각도가 45도 인 경우 작성.
|
|
if (Big(angle1.abs().toNumber()).mod(45).eq(0)) {
|
|
const hip1 = new QLine([currentRoof.x1, currentRoof.y1, ridgeCoordinate.x1, ridgeCoordinate.y1], {
|
|
parentId: roof.id,
|
|
fontSize: roof.fontSize,
|
|
stroke: '#1083E3',
|
|
strokeWidth: 2,
|
|
name: LINE_TYPE.SUBLINE.HIP,
|
|
attributes: {
|
|
roofId: roof.id,
|
|
currentRoofId: currentRoof.id,
|
|
planeSize: calcLinePlaneSize({
|
|
x1: currentRoof.x1,
|
|
y1: currentRoof.y1,
|
|
x2: ridgeCoordinate.x1,
|
|
y2: ridgeCoordinate.y1,
|
|
}),
|
|
actualSize:
|
|
prevDegree === currentDegree
|
|
? calcLineActualSize(
|
|
{
|
|
x1: currentRoof.x1,
|
|
y1: currentRoof.y1,
|
|
x2: ridgeCoordinate.x1,
|
|
y2: ridgeCoordinate.y1,
|
|
},
|
|
currentDegree,
|
|
)
|
|
: 0,
|
|
},
|
|
})
|
|
canvas.add(hip1)
|
|
roof.hips.push(hip1)
|
|
roof.innerLines.push(hip1)
|
|
}
|
|
|
|
const vectorX2 = Big(ridgeCoordinate.x1).minus(Big(currentRoof.x2))
|
|
const vectorY2 = Big(ridgeCoordinate.y1).minus(Big(currentRoof.y2))
|
|
const angle2 = Big(Math.atan2(vectorY2.toNumber(), vectorX2.toNumber())).times(Big(180).div(Math.PI))
|
|
if (Big(angle2.abs().toNumber()).mod(45).eq(0)) {
|
|
const hip2 = new QLine([currentRoof.x2, currentRoof.y2, ridgeCoordinate.x1, ridgeCoordinate.y1], {
|
|
parentId: roof.id,
|
|
fontSize: roof.fontSize,
|
|
stroke: '#1083E3',
|
|
strokeWidth: 2,
|
|
name: LINE_TYPE.SUBLINE.HIP,
|
|
attributes: {
|
|
roofId: roof.id,
|
|
currentRoofId: currentRoof.id,
|
|
planeSize: calcLinePlaneSize({
|
|
x1: currentRoof.x2,
|
|
y1: currentRoof.y2,
|
|
x2: ridgeCoordinate.x1,
|
|
y2: ridgeCoordinate.y1,
|
|
}),
|
|
actualSize:
|
|
prevDegree === currentDegree
|
|
? calcLineActualSize(
|
|
{
|
|
x1: currentRoof.x2,
|
|
y1: currentRoof.y2,
|
|
x2: ridgeCoordinate.x1,
|
|
y2: ridgeCoordinate.y1,
|
|
},
|
|
currentDegree,
|
|
)
|
|
: 0,
|
|
},
|
|
})
|
|
canvas.add(hip2)
|
|
roof.hips.push(hip2)
|
|
roof.innerLines.push(hip2)
|
|
}
|
|
})
|
|
|
|
const hipLines = canvas?.getObjects().filter((object) => object.name === LINE_TYPE.SUBLINE.HIP && object.attributes.roofId === roof.id)
|
|
|
|
//마루에서 시작되지 않는 hip 을 그립니다.
|
|
roofLines
|
|
.filter((roof) => {
|
|
let isHip = false
|
|
if (hipLines.some((hip) => hip.x1 === roof.x1 && hip.y1 === roof.y1)) {
|
|
isHip = true
|
|
}
|
|
return !isHip
|
|
})
|
|
.forEach((currentRoof) => {
|
|
let prevRoof
|
|
roofLines.forEach((roof, index) => {
|
|
if (roof === currentRoof) {
|
|
prevRoof = index === 0 ? roofLines[roofLines.length - 1] : roofLines[index - 1]
|
|
}
|
|
})
|
|
const prevDegree = prevRoof.attributes.pitch > 0 ? getDegreeByChon(prevRoof.attributes.pitch) : prevRoof.attributes.degree
|
|
const currentDegree = currentRoof.attributes.pitch > 0 ? getDegreeByChon(currentRoof.attributes.pitch) : currentRoof.attributes.degree
|
|
|
|
let ridgePoints = []
|
|
ridgeLines.forEach((ridge) => {
|
|
const deltaX1 = Big(ridge.x1).minus(Big(currentRoof.x1))
|
|
const deltaY1 = Big(ridge.y1).minus(Big(currentRoof.y1))
|
|
const deltaX2 = Big(ridge.x2).minus(Big(currentRoof.x1))
|
|
const deltaY2 = Big(ridge.y2).minus(Big(currentRoof.y1))
|
|
|
|
if (deltaY1.div(deltaX1).abs().round(1).eq(1)) {
|
|
ridgePoints.push({ x: ridge.x1, y: ridge.y1 })
|
|
}
|
|
if (deltaY2.div(deltaX2).abs().round(1).eq(1)) {
|
|
ridgePoints.push({ x: ridge.x2, y: ridge.y2 })
|
|
}
|
|
})
|
|
|
|
ridgePoints = ridgePoints.reduce((prev, current) => {
|
|
if (prev !== undefined) {
|
|
// Math.abs(prev.x - currentRoof.x1)
|
|
const deltaPrevX = Big(prev.x).minus(Big(currentRoof.x1)).abs()
|
|
const deltaPrevY = Big(prev.y).minus(Big(currentRoof.y1)).abs()
|
|
const deltaCurrentX = Big(current.x).minus(Big(currentRoof.x1)).abs()
|
|
const deltaCurrentY = Big(current.y).minus(Big(currentRoof.y1)).abs()
|
|
if (deltaPrevX.lt(deltaCurrentX) && deltaPrevY.lt(deltaCurrentY)) {
|
|
return prev
|
|
} else {
|
|
return current
|
|
}
|
|
} else {
|
|
return current
|
|
}
|
|
}, undefined)
|
|
|
|
if (ridgePoints !== undefined) {
|
|
const hip = new QLine([currentRoof.x1, currentRoof.y1, ridgePoints.x, ridgePoints.y], {
|
|
parentId: roof.id,
|
|
fontSize: roof.fontSize,
|
|
stroke: '#1083E3',
|
|
strokeWidth: 2,
|
|
name: LINE_TYPE.SUBLINE.HIP,
|
|
attributes: {
|
|
roofId: roof.id,
|
|
currentRoofId: currentRoof.id,
|
|
planeSize: calcLinePlaneSize({
|
|
x1: currentRoof.x1,
|
|
y1: currentRoof.y1,
|
|
x2: ridgePoints.x,
|
|
y2: ridgePoints.y,
|
|
}),
|
|
actualSize:
|
|
prevDegree === currentDegree
|
|
? calcLineActualSize(
|
|
{
|
|
x1: currentRoof.x1,
|
|
y1: currentRoof.y1,
|
|
x2: ridgePoints.x,
|
|
y2: ridgePoints.y,
|
|
},
|
|
currentDegree,
|
|
)
|
|
: 0,
|
|
},
|
|
})
|
|
canvas.add(hip)
|
|
roof.hips.push(hip)
|
|
roof.innerLines.push(hip)
|
|
}
|
|
})
|
|
canvas?.renderAll()
|
|
}
|
|
|
|
/**
|
|
* 3개 이상 이어지지 않은 라인 포인트 계산
|
|
* 모임지붕에서 point 는 3개 이상의 라인과 접해야 함.
|
|
* @param polygon
|
|
* @param canvas
|
|
* @param textMode
|
|
*/
|
|
const connectLinePoint = (polygon, canvas, textMode) => {
|
|
// 연결되지 않은 모든 라인의 포인트를 구한다.
|
|
let missedPoints = []
|
|
|
|
const lineDegrees = [
|
|
...new Set(polygon.lines.map((line) => (line.attributes.pitch > 0 ? getDegreeByChon(line.attributes.pitch) : line.attributes.degree))),
|
|
]
|
|
|
|
//마루
|
|
polygon.ridges.forEach((ridge) => {
|
|
if (ridge.x1 === ridge.x2) {
|
|
if (
|
|
polygon.lines
|
|
.filter((roof) => roof.y1 === roof.y2)
|
|
.filter((roof) => roof.y1 === ridge.y1 || roof.y1 === ridge.y2 || roof.y2 === ridge.y1 || roof.y2 === ridge.y2).length > 0
|
|
) {
|
|
return
|
|
}
|
|
}
|
|
if (ridge.y1 === ridge.y2) {
|
|
if (
|
|
polygon.lines
|
|
.filter((roof) => roof.x1 === roof.x2)
|
|
.filter((roof) => roof.x1 === ridge.x1 || roof.x1 === ridge.x2 || roof.x2 === ridge.x1 || roof.x2 === ridge.x2).length > 0
|
|
) {
|
|
return
|
|
}
|
|
}
|
|
if (polygon.hips.filter((hip) => hip.x2 === ridge.x1 && hip.y2 === ridge.y1).length < 2) {
|
|
missedPoints.push({ x: ridge.x1, y: ridge.y1 })
|
|
}
|
|
if (polygon.hips.filter((hip) => hip.x2 === ridge.x2 && hip.y2 === ridge.y2).length < 2) {
|
|
missedPoints.push({ x: ridge.x2, y: ridge.y2 })
|
|
}
|
|
})
|
|
|
|
//추녀마루
|
|
polygon.hips.forEach((hip) => {
|
|
let count = 0
|
|
count += polygon.ridges.filter((ridge) => (ridge.x1 === hip.x2 && ridge.y1 === hip.y2) || (ridge.x2 === hip.x2 && ridge.y2 === hip.y2)).length
|
|
|
|
count += polygon.hips.filter((hip2) => (hip2.x1 === hip.x2 && hip2.y1 === hip.y2) || (hip2.x2 === hip.x2 && hip2.y2 === hip.y2)).length
|
|
if (count < 3) {
|
|
missedPoints.push({ x: hip.x2, y: hip.y2 })
|
|
}
|
|
})
|
|
|
|
let missedLine = []
|
|
|
|
//중복포인트제거
|
|
missedPoints = [...new Set(missedPoints.map((line) => JSON.stringify(line)))].map((line) => JSON.parse(line))
|
|
|
|
missedPoints.forEach((p1) => {
|
|
let p2 = missedPoints
|
|
.filter((p) => p.x !== p1.x && p.y !== p1.y)
|
|
.reduce((prev, current) => {
|
|
if (prev !== undefined) {
|
|
return Math.sqrt(Math.pow(Math.abs(current.x - p1.x), 2) + Math.pow(Math.abs(current.y - p1.y), 2)) <
|
|
Math.sqrt(Math.pow(Math.abs(prev.x - p1.x), 2) + Math.pow(Math.abs(prev.y - p1.y), 2))
|
|
? current
|
|
: prev
|
|
} else {
|
|
return current
|
|
}
|
|
}, undefined)
|
|
if (p2 !== undefined) {
|
|
if (p1.x < p2.x && p1.y < p2.y) {
|
|
missedLine.push({ x1: p1.x, y1: p1.y, x2: p2.x, y2: p2.y })
|
|
}
|
|
if (p1.x > p2.x && p1.y < p2.y) {
|
|
missedLine.push({ x1: p2.x, y1: p2.y, x2: p1.x, y2: p1.y })
|
|
}
|
|
if (p1.x > p2.x && p1.y > p2.y) {
|
|
missedLine.push({ x1: p2.x, y1: p2.y, x2: p1.x, y2: p1.y })
|
|
}
|
|
if (p1.x < p2.x && p1.y > p2.y) {
|
|
missedLine.push({ x1: p1.x, y1: p1.y, x2: p2.x, y2: p2.y })
|
|
}
|
|
}
|
|
})
|
|
|
|
//중복라인제거
|
|
missedLine = [...new Set(missedLine.map((line) => JSON.stringify(line)))].map((line) => JSON.parse(line))
|
|
|
|
missedLine.forEach((p) => {
|
|
const line = new QLine([p.x1, p.y1, p.x2, p.y2], {
|
|
parentId: polygon.id,
|
|
attributes: {
|
|
roofId: polygon.id,
|
|
planeSize: calcLinePlaneSize(p),
|
|
actualSize: lineDegrees.length === 0 ? 0 : calcLineActualSize(p, lineDegrees[0]),
|
|
},
|
|
fontSize: polygon.fontSize,
|
|
stroke: '#1083E3',
|
|
strokeWidth: 2,
|
|
textMode: textMode,
|
|
})
|
|
polygon.canvas.add(line)
|
|
polygon.innerLines.push(line)
|
|
})
|
|
|
|
missedPoints = []
|
|
missedLine = []
|
|
|
|
polygon.innerLines.forEach((line) => {
|
|
if (
|
|
polygon.innerLines.filter(
|
|
(innerLine) => (line.x2 === innerLine.x1 && line.y2 === innerLine.y1) || (line.x2 === innerLine.x2 && line.y2 === innerLine.y2),
|
|
).length < 3
|
|
) {
|
|
missedPoints.push({ x: line.x2, y: line.y2 })
|
|
}
|
|
})
|
|
|
|
missedPoints = [...new Set(missedPoints.map((line) => JSON.stringify(line)))].map((line) => JSON.parse(line))
|
|
|
|
missedPoints.forEach((p1) => {
|
|
let p2 = missedPoints
|
|
.filter((p) => !(p.x === p1.x && p.y === p1.y))
|
|
.reduce((prev, current) => {
|
|
if (prev !== undefined) {
|
|
return Math.abs(current.x - p1.x) + Math.abs(current.y - p1.y) < Math.abs(prev.x - p1.x) + Math.abs(prev.y - p1.y) ? current : prev
|
|
} else {
|
|
return current
|
|
}
|
|
}, undefined)
|
|
|
|
if (p2 !== undefined) {
|
|
if (p1.x === p2.x && p1.y < p2.y) {
|
|
missedLine.push({ x1: p1.x, y1: p1.y, x2: p2.x, y2: p2.y })
|
|
}
|
|
if (p1.x === p2.x && p1.y > p2.y) {
|
|
missedLine.push({ x1: p1.x, y1: p2.y, x2: p2.x, y2: p1.y })
|
|
}
|
|
if (p1.x < p2.x && p1.y === p2.y) {
|
|
missedLine.push({ x1: p1.x, y1: p1.y, x2: p2.x, y2: p2.y })
|
|
}
|
|
if (p1.x > p2.x && p1.y === p2.y) {
|
|
missedLine.push({ x1: p2.x, y1: p1.y, x2: p1.x, y2: p2.y })
|
|
}
|
|
}
|
|
})
|
|
|
|
//중복라인제거
|
|
missedLine = [...new Set(missedLine.map((line) => JSON.stringify(line)))].map((line) => JSON.parse(line))
|
|
|
|
missedLine.forEach((p) => {
|
|
const line = new QLine([p.x1, p.y1, p.x2, p.y2], {
|
|
attributes: {
|
|
roofId: polygon.id,
|
|
planeSize: calcLinePlaneSize(p),
|
|
actualSize: lineDegrees.length === 0 ? 0 : calcLineActualSize(p, lineDegrees[0]),
|
|
},
|
|
fontSize: polygon.fontSize,
|
|
stroke: '#1083E3',
|
|
strokeWidth: 2,
|
|
textMode: textMode,
|
|
})
|
|
line.attributes.planeSize = Math.round(Math.sqrt(Math.pow(line.x1 - line.x2, 2) + Math.pow(line.y1 - line.y2, 2)) * 10)
|
|
line.attributes.actualSize = Math.round(Math.sqrt(Math.pow(line.x1 - line.x2, 2) + Math.pow(line.y1 - line.y2, 2)) * 10)
|
|
polygon.canvas.add(line)
|
|
polygon.innerLines.push(line)
|
|
})
|
|
|
|
//마지막으로 연결되지 않고 떨어져있는 마루를 확인한다.
|
|
let missedRidge = []
|
|
polygon.ridges.forEach((ridge) => {
|
|
let lineCheck1 = polygon.innerLines.filter((line) => {
|
|
if (
|
|
!(line.x1 === ridge.x1 && line.y1 === ridge.y1 && line.x2 === ridge.x2 && line.y2 === ridge.y2) &&
|
|
((line.x1 === ridge.x1 && line.y1 === ridge.y1) || (line.x2 === ridge.x1 && line.y2 === ridge.y1))
|
|
) {
|
|
return line
|
|
}
|
|
})
|
|
|
|
let lineCheck2 = polygon.innerLines.filter((line) => {
|
|
if (
|
|
!(line.x1 === ridge.x1 && line.y1 === ridge.y1 && line.x2 === ridge.x2 && line.y2 === ridge.y2) &&
|
|
((line.x1 === ridge.x2 && line.y1 === ridge.y2) || (line.x2 === ridge.x2 && line.y2 === ridge.y2))
|
|
) {
|
|
return line
|
|
}
|
|
})
|
|
if (lineCheck1.length === 0 || lineCheck2.length === 0) {
|
|
missedRidge.push(ridge)
|
|
}
|
|
})
|
|
|
|
missedRidge.forEach((ridge) => {
|
|
let missedRidge2 = missedRidge.filter(
|
|
(ridge2) => !(ridge.x1 === ridge2.x1 && ridge.y1 === ridge2.y1 && ridge.x2 === ridge2.x2 && ridge.y2 === ridge2.y2),
|
|
)
|
|
|
|
missedRidge2.forEach((ridge2) => {
|
|
let overlap = false
|
|
if (ridge.x1 === ridge.x2 && ridge2.x1 === ridge2.x2 && ridge2.x1 === ridge.x1) {
|
|
overlap = true
|
|
}
|
|
if (ridge.y1 === ridge.y2 && ridge2.y1 === ridge2.y2 && ridge2.y1 === ridge.y1) {
|
|
overlap = true
|
|
}
|
|
if (overlap) {
|
|
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 = new QLine([x1, y1, x2, y2], {
|
|
fontSize: polygon.fontSize,
|
|
stroke: '#1083E3',
|
|
strokeWidth: 2,
|
|
name: LINE_TYPE.SUBLINE.RIDGE,
|
|
textMode: textMode,
|
|
attributes: {
|
|
roofId: polygon.id,
|
|
planeSize: calcLinePlaneSize(ridge),
|
|
actualSize: lineDegrees.length > 0 ? 0 : calcLineActualSize(ridge, lineDegrees[0]),
|
|
},
|
|
})
|
|
if (polygon.ridges.filter((r) => newRidge.x1 === r.x1 && newRidge.y1 === r.y1 && newRidge.x2 === r.x2 && newRidge.y2 === r.y2).length === 0) {
|
|
polygon.canvas.remove(ridge)
|
|
polygon.canvas.remove(ridge2)
|
|
polygon.ridges = polygon.ridges.filter((r) => !(ridge.x1 === r.x1 && ridge.y1 === r.y1 && ridge.x2 === r.x2 && ridge.y2 === r.y2))
|
|
polygon.ridges = polygon.ridges.filter((r) => !(ridge2.x1 === r.x1 && ridge2.y1 === r.y1 && ridge2.x2 === r.x2 && ridge2.y2 === r.y2))
|
|
polygon.innerLines = polygon.innerLines.filter((r) => !(ridge.x1 === r.x1 && ridge.y1 === r.y1 && ridge.x2 === r.x2 && ridge.y2 === r.y2))
|
|
polygon.innerLines = polygon.innerLines.filter(
|
|
(r) => !(ridge2.x1 === r.x1 && ridge2.y1 === r.y1 && ridge2.x2 === r.x2 && ridge2.y2 === r.y2),
|
|
)
|
|
|
|
polygon.canvas.add(newRidge)
|
|
polygon.ridges.push(newRidge)
|
|
polygon.innerLines.push(newRidge)
|
|
}
|
|
}
|
|
})
|
|
})
|
|
polygon.canvas.renderAll()
|
|
}
|
|
|
|
/**
|
|
* 외벽선 속성에 따라서 모양을 수정한다.
|
|
* @param roof
|
|
* @param canvas
|
|
* @param textMode
|
|
*/
|
|
const modifyRidge = (roof, canvas, textMode) => {
|
|
const ridgeLines = canvas?.getObjects().filter((object) => object.name === LINE_TYPE.SUBLINE.RIDGE && object.attributes.roofId === roof.id)
|
|
const hipLines = canvas?.getObjects().filter((object) => object.name === LINE_TYPE.SUBLINE.HIP && object.attributes.roofId === roof.id)
|
|
|
|
ridgeLines.forEach((ridge) => {
|
|
let ridgeHip1 = hipLines.filter((hip) => hip.x2 === ridge.x1 && hip.y2 === ridge.y1)
|
|
let ridgeHip2 = hipLines.filter((hip) => hip.x2 === ridge.x2 && hip.y2 === ridge.y2)
|
|
if (ridgeHip1.length >= 2) {
|
|
let currentRoof = roof.lines
|
|
.filter((roofLine) => roofLine.attributes !== undefined && roofLine.attributes.ridgeCoordinate !== undefined)
|
|
.find((roofLine) => roofLine.attributes.ridgeCoordinate.x1 === ridge.x1 && roofLine.attributes.ridgeCoordinate.y1 === ridge.y1)
|
|
if (currentRoof === undefined) {
|
|
currentRoof = roof.lines.find(
|
|
(roofLine) =>
|
|
(roofLine.x1 === ridgeHip1[0].x1 &&
|
|
roofLine.y1 === ridgeHip1[0].y1 &&
|
|
roofLine.x2 === ridgeHip1[1].x1 &&
|
|
roofLine.y2 === ridgeHip1[1].y1) ||
|
|
(roofLine.x1 === ridgeHip1[1].x1 &&
|
|
roofLine.y1 === ridgeHip1[1].y1 &&
|
|
roofLine.x2 === ridgeHip1[0].x1 &&
|
|
roofLine.y2 === ridgeHip1[0].y1),
|
|
)
|
|
if (currentRoof !== undefined) {
|
|
currentRoof.attributes.ridgeCoordinate = { x1: ridge.x1, y1: ridge.y1 }
|
|
}
|
|
}
|
|
if (currentRoof !== undefined) {
|
|
switch (currentRoof.attributes.type) {
|
|
case LINE_TYPE.WALLLINE.EAVES:
|
|
changeEavesRoof(currentRoof, canvas, textMode)
|
|
break
|
|
case LINE_TYPE.WALLLINE.GABLE:
|
|
changeGableRoof(currentRoof, canvas, textMode)
|
|
break
|
|
case LINE_TYPE.WALLLINE.HIPANDGABLE:
|
|
changeHipAndGableRoof(currentRoof, canvas, textMode)
|
|
break
|
|
case LINE_TYPE.WALLLINE.JERKINHEAD:
|
|
changeJerkInHeadRoof(currentRoof, canvas, textMode)
|
|
break
|
|
case LINE_TYPE.WALLLINE.WALL:
|
|
changeWallRoof(currentRoof, canvas, textMode)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if (ridgeHip2.length >= 2) {
|
|
let currentRoof = roof.lines
|
|
.filter((roofLine) => roofLine.attributes !== undefined && roofLine.attributes.ridgeCoordinate !== undefined)
|
|
.find((roofLine) => roofLine.attributes.ridgeCoordinate.x1 === ridge.x2 && roofLine.attributes.ridgeCoordinate.y1 === ridge.y2)
|
|
if (currentRoof === undefined) {
|
|
currentRoof = roof.lines.find(
|
|
(roofLine) =>
|
|
(roofLine.x1 === ridgeHip2[0].x1 &&
|
|
roofLine.y1 === ridgeHip2[0].y1 &&
|
|
roofLine.x2 === ridgeHip2[1].x1 &&
|
|
roofLine.y2 === ridgeHip2[1].y1) ||
|
|
(roofLine.x1 === ridgeHip2[1].x1 &&
|
|
roofLine.y1 === ridgeHip2[1].y1 &&
|
|
roofLine.x2 === ridgeHip2[0].x1 &&
|
|
roofLine.y2 === ridgeHip2[0].y1),
|
|
)
|
|
if (currentRoof !== undefined) {
|
|
currentRoof.attributes.ridgeCoordinate = { x1: ridge.x2, y1: ridge.y2 }
|
|
}
|
|
}
|
|
if (currentRoof !== undefined) {
|
|
switch (currentRoof.attributes.type) {
|
|
case LINE_TYPE.WALLLINE.EAVES:
|
|
changeEavesRoof(currentRoof, canvas, textMode)
|
|
break
|
|
case LINE_TYPE.WALLLINE.GABLE:
|
|
changeGableRoof(currentRoof, canvas, textMode)
|
|
break
|
|
case LINE_TYPE.WALLLINE.HIPANDGABLE:
|
|
changeHipAndGableRoof(currentRoof, canvas, textMode)
|
|
break
|
|
case LINE_TYPE.WALLLINE.JERKINHEAD:
|
|
changeJerkInHeadRoof(currentRoof, canvas, textMode)
|
|
break
|
|
case LINE_TYPE.WALLLINE.WALL:
|
|
changeWallRoof(currentRoof, canvas, textMode)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
/*
|
|
최대 생성 마루 갯수
|
|
*/
|
|
const getMaxRidge = (length) => {
|
|
return (length - 4) / 2 + 1
|
|
}
|
|
|
|
/**
|
|
* 처마지붕으로 변경
|
|
* @param currentRoof
|
|
* @param canvas
|
|
* @param textMode
|
|
*/
|
|
const changeEavesRoof = (currentRoof, canvas, textMode) => {
|
|
if (currentRoof.attributes.type === LINE_TYPE.WALLLINE.EAVES) {
|
|
const roofId = currentRoof.attributes.roofId
|
|
const wall = canvas?.getObjects().find((object) => object.name === POLYGON_TYPE.WALL && object.attributes.roofId === roofId)
|
|
const roof = canvas?.getObjects().find((object) => object.name === POLYGON_TYPE.ROOF && object.id === roofId)
|
|
let hipLines = canvas?.getObjects().filter((object) => object.name === LINE_TYPE.SUBLINE.HIP && object.attributes.roofId === roofId)
|
|
let ridgeLines = canvas?.getObjects().filter((object) => object.name === LINE_TYPE.SUBLINE.RIDGE && object.attributes.roofId === roofId)
|
|
let wallLine = wall.lines.filter((w) => w.id === currentRoof.attributes.wallLine)
|
|
if (wallLine.length > 0) {
|
|
wallLine = wallLine[0]
|
|
}
|
|
let prevRoof, nextRoof
|
|
roof.lines.forEach((r, index) => {
|
|
if (r.id === currentRoof.id) {
|
|
currentRoof = r
|
|
prevRoof = roof.lines[index === 0 ? roof.lines.length - 1 : index - 1]
|
|
nextRoof = roof.lines[index === roof.lines.length - 1 ? 0 : index + 1]
|
|
}
|
|
})
|
|
|
|
const midX = Big(currentRoof.x1).plus(Big(currentRoof.x2)).div(2) // 지붕의 X 중심
|
|
const midY = Big(currentRoof.y1).plus(Big(currentRoof.y2)).div(2) // 지붕의 Y 중심
|
|
const midWallX = Big(wallLine.x1).plus(Big(wallLine.x2)).div(2)
|
|
const midWallY = Big(wallLine.y1).plus(Big(wallLine.y2)).div(2) // 벽의 Y 중심
|
|
const alpha = midX.minus(midWallX) // 벽과 지붕의 X 거리
|
|
const beta = midY.minus(midWallY) // 벽과 지붕의 Y 거리
|
|
const hypotenuse = alpha.pow(2).plus(beta.pow(2)).sqrt() // 벽과 지붕의 거리
|
|
const hipX2 = midX.plus(alpha.div(hypotenuse).times(Big(currentRoof.length).div(2)).neg())
|
|
const hipY2 = midY.plus(beta.div(hypotenuse).times(Big(currentRoof.length).div(2)).neg())
|
|
|
|
const innerLines = canvas
|
|
?.getObjects()
|
|
.filter(
|
|
(object) =>
|
|
object.attributes?.roofId === roofId &&
|
|
object.attributes?.currentRoofId === currentRoof.id &&
|
|
object.x1 !== undefined &&
|
|
object.x2 !== undefined,
|
|
)
|
|
|
|
innerLines
|
|
.filter(
|
|
(line) =>
|
|
line.name !== LINE_TYPE.SUBLINE.RIDGE &&
|
|
line.name !== LINE_TYPE.SUBLINE.HIP &&
|
|
line.name !== LINE_TYPE.SUBLINE.VALLEY &&
|
|
line.name !== OUTER_LINE_TYPE.OUTER_LINE &&
|
|
line.name !== 'wallLine',
|
|
)
|
|
.forEach((line) => {
|
|
roof.innerLines = roof.innerLines.filter((l) => l.id !== line.id)
|
|
canvas?.remove(line)
|
|
})
|
|
|
|
ridgeLines = ridgeLines.filter(
|
|
(ridge) =>
|
|
(ridge.x1 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y1 === currentRoof.attributes.ridgeCoordinate.y1) ||
|
|
(ridge.x2 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y2 === currentRoof.attributes.ridgeCoordinate.y1),
|
|
)
|
|
hipLines = hipLines.filter(
|
|
(hip) => (hip.x1 === currentRoof.x1 && hip.y1 === currentRoof.y1) || (hip.x1 === currentRoof.x2 && hip.y1 === currentRoof.y2),
|
|
)
|
|
|
|
if (hipLines === undefined || hipLines.length === 0) {
|
|
hipLines = innerLines.filter(
|
|
(line) =>
|
|
(line.x1 === currentRoof.x1 && line.y1 === currentRoof.y1) ||
|
|
(line.x2 === currentRoof.x1 && line.y2 === currentRoof.y1) ||
|
|
(line.x1 === currentRoof.x2 && line.y1 === currentRoof.y2) ||
|
|
(line.x2 === currentRoof.x2 && line.y2 === currentRoof.y2),
|
|
)
|
|
}
|
|
|
|
if ((ridgeLines === undefined || ridgeLines.length === 0) && hipLines.length >= 2) {
|
|
let points = []
|
|
hipLines.forEach((hip) => {
|
|
points.push({ x: hip.x1, y: hip.y1 })
|
|
points.push({ x: hip.x2, y: hip.y2 })
|
|
})
|
|
|
|
const pointSet = new Set()
|
|
const duplicatePoints = []
|
|
|
|
points.forEach((point) => {
|
|
const pointKey = `${point.x},${point.y}`
|
|
if (pointSet.has(pointKey)) {
|
|
duplicatePoints.push(point)
|
|
} else {
|
|
pointSet.add(pointKey)
|
|
}
|
|
})
|
|
|
|
ridgeLines = innerLines
|
|
.filter((r) => r !== hipLines[0] && r !== hipLines[1])
|
|
.filter(
|
|
(r) => (r.x1 === duplicatePoints[0].x && r.y1 === duplicatePoints[0].y) || (r.x2 === duplicatePoints[0].x && r.y2 === duplicatePoints[0].y),
|
|
)
|
|
if (ridgeLines.length > 0) {
|
|
currentRoof.attributes.ridgeCoordinate = { x1: duplicatePoints[0].x, y1: duplicatePoints[0].y }
|
|
}
|
|
}
|
|
|
|
if (ridgeLines.length > 0) {
|
|
const ridge = ridgeLines[0]
|
|
if (ridge.x1 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y1 === currentRoof.attributes.ridgeCoordinate.y1) {
|
|
ridge.set({
|
|
x1: hipX2.toNumber(),
|
|
y1: hipY2.toNumber(),
|
|
x2: ridge.x2,
|
|
y2: ridge.y2,
|
|
})
|
|
currentRoof.attributes.ridgeCoordinate = { x1: ridge.x1, y1: ridge.y1 }
|
|
}
|
|
if (ridge.x2 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y2 === currentRoof.attributes.ridgeCoordinate.y1) {
|
|
ridge.set({
|
|
x1: ridge.x1,
|
|
y1: ridge.y1,
|
|
x2: hipX2.toNumber(),
|
|
y2: hipY2.toNumber(),
|
|
})
|
|
currentRoof.attributes.ridgeCoordinate = { x1: ridge.x2, y1: ridge.y2 }
|
|
}
|
|
//Math.round(Math.sqrt(Math.pow(ridge.x1 - ridge.x2, 2) + Math.pow(ridge.y1 - ridge.y2, 2)) * 10)
|
|
ridge.attributes.planeSize = calcLinePlaneSize({ x1: ridge.x1, y1: ridge.y1, x2: ridge.x2, y2: ridge.y2 })
|
|
ridge.attributes.actualSize = calcLinePlaneSize({ x1: ridge.x1, y1: ridge.y1, x2: ridge.x2, y2: ridge.y2 })
|
|
}
|
|
|
|
hipLines.forEach((hip) => {
|
|
roof.innerLines = roof.innerLines.filter((h) => h.id !== hip.id)
|
|
canvas.remove(hip)
|
|
})
|
|
|
|
canvas?.renderAll()
|
|
|
|
const prevDegree = prevRoof.attributes.pitch > 0 ? getDegreeByChon(prevRoof.attributes.pitch) : prevRoof.attributes.degree
|
|
const currentDegree = currentRoof.attributes.pitch > 0 ? getDegreeByChon(currentRoof.attributes.pitch) : currentRoof.attributes.degree
|
|
const nextDegree = nextRoof.attributes.pitch > 0 ? getDegreeByChon(nextRoof.attributes.pitch) : nextRoof.attributes.degree
|
|
|
|
const hip1 = new QLine([currentRoof.x1, currentRoof.y1, hipX2.toNumber(), hipY2.toNumber()], {
|
|
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: currentRoof.x1,
|
|
y1: currentRoof.y1,
|
|
x2: hipX2.toNumber(),
|
|
y2: hipY2.toNumber(),
|
|
}),
|
|
actualSize:
|
|
prevDegree === currentDegree
|
|
? calcLineActualSize(
|
|
{
|
|
x1: currentRoof.x1,
|
|
y1: currentRoof.y1,
|
|
x2: hipX2.toNumber(),
|
|
y2: hipY2.toNumber(),
|
|
},
|
|
currentDegree,
|
|
)
|
|
: 0,
|
|
},
|
|
})
|
|
const hip1Base = ((Math.abs(hip1.x1 - hip1.x2) + Math.abs(hip1.y1 - hip1.y2)) / 2) * 10
|
|
const hip1Height = Math.round(hip1Base / Math.tan(((90 - currentDegree) * Math.PI) / 180))
|
|
canvas?.add(hip1)
|
|
roof.innerLines.push(hip1)
|
|
|
|
const hip2 = new QLine([currentRoof.x2, currentRoof.y2, hipX2.toNumber(), hipY2.toNumber()], {
|
|
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: currentRoof.x2,
|
|
y1: currentRoof.y2,
|
|
x2: hipX2.toNumber(),
|
|
y2: hipY2.toNumber(),
|
|
}),
|
|
actualSize:
|
|
currentDegree === nextDegree
|
|
? calcLineActualSize(
|
|
{
|
|
x1: currentRoof.x2,
|
|
y1: currentRoof.y2,
|
|
x2: hipX2.toNumber(),
|
|
y2: hipY2.toNumber(),
|
|
},
|
|
currentDegree,
|
|
)
|
|
: 0,
|
|
},
|
|
})
|
|
canvas?.add(hip2)
|
|
roof.innerLines.push(hip2)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 박공지붕으로 변경
|
|
* @param currentRoof
|
|
* @param canvas
|
|
* @param textMode
|
|
*/
|
|
const changeGableRoof = (currentRoof, canvas, textMode) => {
|
|
if (currentRoof.attributes.type === LINE_TYPE.WALLLINE.GABLE) {
|
|
const roofId = currentRoof.attributes.roofId
|
|
const roof = canvas?.getObjects().find((object) => object.name === POLYGON_TYPE.ROOF && object.id === roofId)
|
|
let hipLines = canvas?.getObjects().filter((object) => object.name === LINE_TYPE.SUBLINE.HIP && object.attributes.roofId === roofId)
|
|
let ridgeLines = canvas?.getObjects().filter((object) => object.name === LINE_TYPE.SUBLINE.RIDGE && object.attributes.roofId === roofId)
|
|
|
|
const midX = Big(currentRoof.x1).plus(Big(currentRoof.x2)).div(2) // 지붕의 X 중심
|
|
const midY = Big(currentRoof.y1).plus(Big(currentRoof.y2)).div(2) // 지붕의 Y 중심
|
|
|
|
const innerLines = canvas
|
|
?.getObjects()
|
|
.filter(
|
|
(object) =>
|
|
object.attributes?.roofId === roofId &&
|
|
object.attributes?.currentRoofId === currentRoof.id &&
|
|
object.x1 !== undefined &&
|
|
object.x2 !== undefined,
|
|
)
|
|
|
|
let prevRoof, nextRoof
|
|
roof.lines.forEach((r, index) => {
|
|
if (r.id === currentRoof.id) {
|
|
currentRoof = r
|
|
prevRoof = roof.lines[index === 0 ? roof.lines.length - 1 : index - 1]
|
|
nextRoof = roof.lines[index === roof.lines.length - 1 ? 0 : index + 1]
|
|
}
|
|
})
|
|
|
|
innerLines
|
|
.filter(
|
|
(line) =>
|
|
line.name !== LINE_TYPE.SUBLINE.RIDGE &&
|
|
line.name !== LINE_TYPE.SUBLINE.HIP &&
|
|
line.name !== LINE_TYPE.SUBLINE.VALLEY &&
|
|
line.name !== OUTER_LINE_TYPE.OUTER_LINE &&
|
|
line.name !== 'wallLine',
|
|
)
|
|
.forEach((line) => {
|
|
roof.innerLines = roof.innerLines.filter((l) => l.id !== line.id)
|
|
canvas?.remove(line)
|
|
})
|
|
canvas.renderAll()
|
|
|
|
ridgeLines = ridgeLines.filter(
|
|
(ridge) =>
|
|
(ridge.x1 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y1 === currentRoof.attributes.ridgeCoordinate.y1) ||
|
|
(ridge.x2 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y2 === currentRoof.attributes.ridgeCoordinate.y1),
|
|
)
|
|
hipLines = hipLines.filter(
|
|
(hip) => (hip.x1 === currentRoof.x1 && hip.y1 === currentRoof.y1) || (hip.x1 === currentRoof.x2 && hip.y1 === currentRoof.y2),
|
|
)
|
|
|
|
if (hipLines === undefined || hipLines.length === 0) {
|
|
hipLines = innerLines.filter(
|
|
(line) =>
|
|
(line.x1 === currentRoof.x1 && line.y1 === currentRoof.y1) ||
|
|
(line.x2 === currentRoof.x1 && line.y2 === currentRoof.y1) ||
|
|
(line.x1 === currentRoof.x2 && line.y1 === currentRoof.y2) ||
|
|
(line.x2 === currentRoof.x2 && line.y2 === currentRoof.y2),
|
|
)
|
|
}
|
|
|
|
hipLines.forEach((hip) => {
|
|
roof.innerLines = roof.innerLines.filter((h) => h.id !== hip.id)
|
|
canvas.remove(hip)
|
|
})
|
|
|
|
if ((ridgeLines === undefined || ridgeLines.length === 0) && hipLines.length >= 2) {
|
|
let points = []
|
|
hipLines.forEach((hip) => {
|
|
points.push({ x: hip.x1, y: hip.y1 })
|
|
points.push({ x: hip.x2, y: hip.y2 })
|
|
})
|
|
|
|
const pointSet = new Set()
|
|
const duplicatePoints = []
|
|
|
|
points.forEach((point) => {
|
|
const pointKey = `${point.x},${point.y}`
|
|
if (pointSet.has(pointKey)) {
|
|
duplicatePoints.push(point)
|
|
} else {
|
|
pointSet.add(pointKey)
|
|
}
|
|
})
|
|
|
|
ridgeLines = innerLines
|
|
.filter((r) => r !== hipLines[0] && r !== hipLines[1])
|
|
.filter(
|
|
(r) => (r.x1 === duplicatePoints[0].x && r.y1 === duplicatePoints[0].y) || (r.x2 === duplicatePoints[0].x && r.y2 === duplicatePoints[0].y),
|
|
)
|
|
if (ridgeLines.length > 0) {
|
|
currentRoof.attributes.ridgeCoordinate = { x1: duplicatePoints[0].x, y1: duplicatePoints[0].y }
|
|
}
|
|
}
|
|
|
|
if (ridgeLines !== undefined && ridgeLines.length > 0) {
|
|
const prevDegree = prevRoof.attributes.pitch > 0 ? getDegreeByChon(prevRoof.attributes.pitch) : prevRoof.attributes.degree
|
|
const nextDegree = nextRoof.attributes.pitch > 0 ? getDegreeByChon(nextRoof.attributes.pitch) : nextRoof.attributes.degree
|
|
|
|
const ridge = ridgeLines[0]
|
|
if (ridge.x1 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y1 === currentRoof.attributes.ridgeCoordinate.y1) {
|
|
ridge.set({
|
|
x1: midX.toNumber(),
|
|
y1: midY.toNumber(),
|
|
x2: ridge.x2,
|
|
y2: ridge.y2,
|
|
})
|
|
currentRoof.attributes.ridgeCoordinate = { x1: ridge.x1, y1: ridge.y1 }
|
|
}
|
|
if (ridge.x2 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y2 === currentRoof.attributes.ridgeCoordinate.y1) {
|
|
ridge.set({
|
|
x1: ridge.x1,
|
|
y1: ridge.y1,
|
|
x2: midX.toNumber(),
|
|
y2: midY.toNumber(),
|
|
})
|
|
currentRoof.attributes.ridgeCoordinate = { x1: ridge.x2, y1: ridge.y2 }
|
|
}
|
|
ridge.attributes.planeSize = calcLinePlaneSize({ x1: ridge.x1, y1: ridge.y1, x2: ridge.x2, y2: ridge.y2 })
|
|
ridge.attributes.actualSize = calcLinePlaneSize({ x1: ridge.x1, y1: ridge.y1, x2: ridge.x2, y2: ridge.y2 })
|
|
|
|
let hip1 = new QLine([currentRoof.x1, currentRoof.y1, midX.toNumber(), midY.toNumber()], {
|
|
parentId: roof.id,
|
|
fontSize: roof.fontSize,
|
|
stroke: '#1083E3',
|
|
strokeWidth: 2,
|
|
name: LINE_TYPE.SUBLINE.HIP,
|
|
textMode: textMode,
|
|
attributes: {
|
|
roofId: roofId,
|
|
currentRoofId: currentRoof.id,
|
|
planeSize: calcLinePlaneSize({
|
|
x1: currentRoof.x1,
|
|
y1: currentRoof.y1,
|
|
x2: midX.toNumber(),
|
|
y2: midY.toNumber(),
|
|
}),
|
|
actualSize: calcLineActualSize(
|
|
{
|
|
x1: currentRoof.x1,
|
|
y1: currentRoof.y1,
|
|
x2: midX.toNumber(),
|
|
y2: midY.toNumber(),
|
|
},
|
|
prevDegree,
|
|
),
|
|
},
|
|
})
|
|
canvas?.add(hip1)
|
|
// const hip1Base = ((Math.abs(hip1.x1 - hip1.x2) + Math.abs(hip1.y1 - hip1.y2)) / 2) * 10
|
|
// const hip1Height = Math.round(hip1Base / Math.tan(((90 - prevDegree) * Math.PI) / 180))
|
|
// hip1.attributes.planeSize = Math.round(Math.sqrt(Math.pow(hip1.x1 - hip1.x2, 2) + Math.pow(hip1.y1 - hip1.y2, 2))) * 10
|
|
// hip1.attributes.actualSize = Math.round(Math.sqrt(Math.pow(hip1.attributes.planeSize, 2) + Math.pow(hip1Height, 2)))
|
|
roof.innerLines.push(hip1)
|
|
|
|
let hip2 = new QLine([currentRoof.x2, currentRoof.y2, midX.toNumber(), midY.toNumber()], {
|
|
parentId: roof.id,
|
|
fontSize: roof.fontSize,
|
|
stroke: '#1083E3',
|
|
strokeWidth: 2,
|
|
name: LINE_TYPE.SUBLINE.HIP,
|
|
attributes: {
|
|
roofId: roofId,
|
|
currentRoofId: currentRoof.id,
|
|
planeSize: calcLinePlaneSize({
|
|
x1: currentRoof.x2,
|
|
y1: currentRoof.y2,
|
|
x2: midX.toNumber(),
|
|
y2: midY.toNumber(),
|
|
}),
|
|
actualSize: calcLineActualSize({ x1: currentRoof.x2, y1: currentRoof.y2, x2: midX.toNumber(), y2: midY.toNumber() }, nextDegree),
|
|
},
|
|
})
|
|
canvas?.add(hip2)
|
|
// const hip2Base = ((Math.abs(hip2.x1 - hip2.x2) + Math.abs(hip2.y1 - hip2.y2)) / 2) * 10
|
|
// const hip2Height = Math.round(hip2Base / Math.tan(((90 - nextDegree) * Math.PI) / 180))
|
|
// hip2.attributes.planeSize = Math.round(Math.sqrt(Math.pow(hip2.x1 - hip2.x2, 2) + Math.pow(hip2.y1 - hip2.y2, 2))) * 10
|
|
// hip2.attributes.actualSize = Math.round(Math.sqrt(Math.pow(hip2.attributes.planeSize, 2) + Math.pow(hip2Height, 2)))
|
|
roof.innerLines.push(hip2)
|
|
canvas?.renderAll()
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 팔작지붕으로 변경
|
|
* @param currentRoof
|
|
* @param canvas
|
|
* @param textMode
|
|
*/
|
|
const changeHipAndGableRoof = (currentRoof, canvas, textMode) => {
|
|
if (
|
|
currentRoof.attributes.type === LINE_TYPE.WALLLINE.HIPANDGABLE &&
|
|
currentRoof.attributes.width !== undefined &&
|
|
currentRoof.attributes.width > 0
|
|
) {
|
|
const roofId = currentRoof.attributes.roofId
|
|
const wall = canvas?.getObjects().find((object) => object.name === POLYGON_TYPE.WALL && object.attributes.roofId === roofId)
|
|
const roof = canvas?.getObjects().find((object) => object.name === POLYGON_TYPE.ROOF && object.id === roofId)
|
|
let hipLines = canvas?.getObjects().filter((object) => object.name === LINE_TYPE.SUBLINE.HIP && object.attributes.roofId === roofId)
|
|
let ridgeLines = canvas?.getObjects().filter((object) => object.name === LINE_TYPE.SUBLINE.RIDGE && object.attributes.roofId === roofId)
|
|
let wallLine = wall.lines.filter((w) => w.id === currentRoof.attributes.wallLine)
|
|
if (wallLine.length > 0) {
|
|
wallLine = wallLine[0]
|
|
}
|
|
let prevRoof, nextRoof
|
|
roof.lines.forEach((r, index) => {
|
|
if (r.id === currentRoof.id) {
|
|
currentRoof = r
|
|
prevRoof = roof.lines[index === 0 ? roof.lines.length - 1 : index - 1]
|
|
nextRoof = roof.lines[index === roof.lines.length - 1 ? 0 : index + 1]
|
|
}
|
|
})
|
|
|
|
const midX = Big(currentRoof.x1).plus(Big(currentRoof.x2)).div(2) // 지붕의 X 중심
|
|
const midY = Big(currentRoof.y1).plus(Big(currentRoof.y2)).div(2) // 지붕의 Y 중심
|
|
const midWallX = Big(wallLine.x1).plus(Big(wallLine.x2)).div(2) // 벽의 X 중심
|
|
const midWallY = Big(wallLine.y1).plus(Big(wallLine.y2)).div(2) // 벽의 Y 중심
|
|
const alpha = midX.minus(midWallX) // 벽과 지붕의 X 거리
|
|
const beta = midY.minus(midWallY) // 벽과 지붕의 Y 거리
|
|
// Math.sqrt(Math.pow(alpha, 2) + Math.pow(beta, 2))
|
|
const hypotenuse = alpha.pow(2).plus(beta.pow(2)).sqrt() // 벽과 지붕의 거리
|
|
const xWidth = Big(Math.sign(midX.minus(midWallX).toNumber()))
|
|
.times(alpha.div(hypotenuse))
|
|
.times(currentRoof.attributes.width) // 지붕의 X 너비
|
|
const yWidth = Big(Math.sign(midY.minus(midWallY)))
|
|
.times(beta.div(hypotenuse))
|
|
.times(currentRoof.attributes.width) // 지붕의 Y 너비
|
|
const hipX2 = Big(Math.sign(midX.minus(midWallX).toNumber()))
|
|
.times(alpha.div(hypotenuse))
|
|
.times(currentRoof.length / 2) // 추녀마루의 X 너비
|
|
const hipY2 = Big(Math.sign(midY.minus(midWallY)))
|
|
.times(beta.div(hypotenuse))
|
|
.times(currentRoof.length / 2) // 추녀마루의 Y 너비
|
|
|
|
// if (Math.sqrt(Math.pow(xWidth, 2) + Math.pow(yWidth, 2)) < Math.sqrt(Math.pow(hipX2, 2) + Math.pow(hipY2, 2))) {
|
|
if (
|
|
xWidth
|
|
.pow(2)
|
|
.plus(yWidth.pow(2))
|
|
.sqrt()
|
|
.lt(hipX2.pow(2).plus(hipY2.pow(2)).sqrt())
|
|
) {
|
|
const innerLines = canvas
|
|
?.getObjects()
|
|
.filter(
|
|
(object) =>
|
|
object.attributes?.roofId === roofId &&
|
|
object.attributes?.currentRoofId === currentRoof.id &&
|
|
object.x1 !== undefined &&
|
|
object.x2 !== undefined,
|
|
)
|
|
|
|
innerLines
|
|
.filter(
|
|
(line) =>
|
|
line.name !== LINE_TYPE.SUBLINE.RIDGE &&
|
|
line.name !== LINE_TYPE.SUBLINE.HIP &&
|
|
line.name !== LINE_TYPE.SUBLINE.VALLEY &&
|
|
line.name !== OUTER_LINE_TYPE.OUTER_LINE &&
|
|
line.name !== 'wallLine',
|
|
)
|
|
.forEach((line) => {
|
|
roof.innerLines = roof.innerLines.filter((l) => l.id !== line.id)
|
|
canvas?.remove(line)
|
|
})
|
|
|
|
ridgeLines = ridgeLines.filter(
|
|
(ridge) =>
|
|
(ridge.x1 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y1 === currentRoof.attributes.ridgeCoordinate.y1) ||
|
|
(ridge.x2 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y2 === currentRoof.attributes.ridgeCoordinate.y1),
|
|
)
|
|
hipLines = hipLines.filter(
|
|
(hip) => (hip.x1 === currentRoof.x1 && hip.y1 === currentRoof.y1) || (hip.x1 === currentRoof.x2 && hip.y1 === currentRoof.y2),
|
|
)
|
|
|
|
if (hipLines === undefined || hipLines.length === 0) {
|
|
hipLines = innerLines.filter(
|
|
(line) =>
|
|
(line.x1 === currentRoof.x1 && line.y1 === currentRoof.y1) ||
|
|
(line.x2 === currentRoof.x1 && line.y2 === currentRoof.y1) ||
|
|
(line.x1 === currentRoof.x2 && line.y1 === currentRoof.y2) ||
|
|
(line.x2 === currentRoof.x2 && line.y2 === currentRoof.y2),
|
|
)
|
|
}
|
|
|
|
if ((ridgeLines === undefined || ridgeLines.length === 0) && hipLines.length >= 2) {
|
|
let points = []
|
|
hipLines.forEach((hip) => {
|
|
points.push({ x: hip.x1, y: hip.y1 })
|
|
points.push({ x: hip.x2, y: hip.y2 })
|
|
})
|
|
|
|
const pointSet = new Set()
|
|
const duplicatePoints = []
|
|
|
|
points.forEach((point) => {
|
|
const pointKey = `${point.x},${point.y}`
|
|
if (pointSet.has(pointKey)) {
|
|
duplicatePoints.push(point)
|
|
} else {
|
|
pointSet.add(pointKey)
|
|
}
|
|
})
|
|
|
|
ridgeLines = innerLines
|
|
.filter((r) => r !== hipLines[0] && r !== hipLines[1])
|
|
.filter(
|
|
(r) =>
|
|
(r.x1 === duplicatePoints[0].x && r.y1 === duplicatePoints[0].y) || (r.x2 === duplicatePoints[0].x && r.y2 === duplicatePoints[0].y),
|
|
)
|
|
if (ridgeLines.length > 0) {
|
|
currentRoof.attributes.ridgeCoordinate = { x1: duplicatePoints[0].x, y1: duplicatePoints[0].y }
|
|
}
|
|
}
|
|
|
|
hipLines.forEach((hip) => {
|
|
roof.innerLines = roof.innerLines.filter((h) => h.id !== hip.id)
|
|
canvas.remove(hip)
|
|
})
|
|
hipLines = []
|
|
|
|
if (ridgeLines.length > 0) {
|
|
const ridge = ridgeLines[0]
|
|
if (ridge.x1 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y1 === currentRoof.attributes.ridgeCoordinate.y1) {
|
|
const signX = Math.sign(midX.minus(ridge.x1).toNumber())
|
|
const signY = Math.sign(midY.minus(ridge.y1).toNumber())
|
|
ridge.set({
|
|
x1: midX.minus(xWidth.abs().times(signX)).toNumber(),
|
|
y1: midY.minus(yWidth.abs().times(signY)).toNumber(),
|
|
x2: ridge.x2,
|
|
y2: ridge.y2,
|
|
})
|
|
currentRoof.attributes.ridgeCoordinate = { x1: ridge.x1, y1: ridge.y1 }
|
|
}
|
|
if (ridge.x2 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y2 === currentRoof.attributes.ridgeCoordinate.y1) {
|
|
const signX = Math.sign(midX.minus(ridge.x2).toNumber())
|
|
const signY = Math.sign(midY.minus(ridge.y2).toNumber())
|
|
ridge.set({
|
|
x1: ridge.x1,
|
|
y1: ridge.y1,
|
|
x2: midX.minus(xWidth.abs().times(signX)).toNumber(),
|
|
y2: midY.minus(yWidth.abs().times(signY)).toNumber(),
|
|
})
|
|
currentRoof.attributes.ridgeCoordinate = { x1: ridge.x2, y1: ridge.y2 }
|
|
}
|
|
ridge.attributes.planeSize = calcLinePlaneSize({ x1: ridge.x1, y1: ridge.y1, x2: ridge.x2, y2: ridge.y2 })
|
|
ridge.attributes.actualSize = calcLinePlaneSize({ x1: ridge.x1, y1: ridge.y1, x2: ridge.x2, y2: ridge.y2 })
|
|
}
|
|
|
|
const currentDegree = currentRoof.attributes.pitch > 0 ? getDegreeByChon(currentRoof.attributes.pitch) : currentRoof.attributes.degree
|
|
|
|
const hip1 = new QLine([currentRoof.x1, currentRoof.y1, midX.plus(hipX2).toNumber(), midY.plus(hipY2).toNumber()], {
|
|
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: currentRoof.x1,
|
|
y1: currentRoof.y1,
|
|
x2: midX.plus(hipX2).toNumber(),
|
|
y2: midY.plus(hipY2).toNumber(),
|
|
}),
|
|
actualSize: calcLineActualSize(
|
|
{
|
|
x1: currentRoof.x1,
|
|
y1: currentRoof.y1,
|
|
x2: midX.plus(hipX2).toNumber(),
|
|
y2: midY.plus(hipY2).toNumber(),
|
|
},
|
|
currentDegree,
|
|
),
|
|
},
|
|
})
|
|
|
|
// const hip1Base = ((Math.abs(hip1.x1 - hip1.x2) + Math.abs(hip1.y1 - hip1.y2)) / 2) * 10
|
|
// const hip1Height = Math.round(hip1Base / Math.tan(((90 - prevDegree) * Math.PI) / 180))
|
|
// hip1.attributes.planeSize = Math.round(Math.sqrt(Math.pow(hip1.x1 - hip1.x2, 2) + Math.pow(hip1.y1 - hip1.y2, 2))) * 10
|
|
// hip1.attributes.actualSize = Math.round(Math.sqrt(Math.pow(hip1.attributes.planeSize, 2) + Math.pow(hip1Height, 2)))
|
|
canvas?.add(hip1)
|
|
roof.innerLines.push(hip1)
|
|
|
|
const hip2 = new QLine([currentRoof.x2, currentRoof.y2, midX.plus(hipX2).toNumber(), midY.plus(hipY2).toNumber()], {
|
|
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: currentRoof.x2,
|
|
y1: currentRoof.y2,
|
|
x2: midX.plus(hipX2).toNumber(),
|
|
y2: midY.plus(hipY2).toNumber(),
|
|
}),
|
|
actualSize: calcLineActualSize(
|
|
{
|
|
x1: currentRoof.x2,
|
|
y1: currentRoof.y2,
|
|
x2: midX.plus(hipX2).toNumber(),
|
|
y2: midY.plus(hipY2).toNumber(),
|
|
},
|
|
currentDegree,
|
|
),
|
|
},
|
|
})
|
|
// const hip2Base = ((Math.abs(hip2.x1 - hip2.x2) + Math.abs(hip2.y1 - hip2.y2)) / 2) * 10
|
|
// const hip2Height = Math.round(hip2Base / Math.tan(((90 - nextDegree) * Math.PI) / 180))
|
|
// hip2.attributes.planeSize = Math.round(Math.sqrt(Math.pow(hip2.x1 - hip2.x2, 2) + Math.pow(hip2.y1 - hip2.y2, 2))) * 10
|
|
// hip2.attributes.actualSize = Math.round(Math.sqrt(Math.pow(hip2.attributes.planeSize, 2) + Math.pow(hip2Height, 2)))
|
|
canvas?.add(hip2)
|
|
roof.innerLines.push(hip2)
|
|
|
|
hipLines.push(hip1)
|
|
hipLines.push(hip2)
|
|
|
|
hipLines.forEach((hip) => {
|
|
const singHipX = Math.sign(hip.x1 - midWallX.toNumber())
|
|
const singHipY = Math.sign(hip.y1 - midWallY.toNumber())
|
|
|
|
hip.set({
|
|
x1: hip.x1,
|
|
y1: hip.y1,
|
|
x2: hip.x1 - singHipX * currentRoof.attributes.width,
|
|
y2: hip.y1 - singHipY * currentRoof.attributes.width,
|
|
})
|
|
})
|
|
|
|
hipLines.forEach((hip, i) => {
|
|
const gableLine = new QLine([hip.x2, hip.y2, currentRoof.attributes.ridgeCoordinate.x1, currentRoof.attributes.ridgeCoordinate.y1], {
|
|
parentId: roof.id,
|
|
fontSize: roof.fontSize,
|
|
stroke: '#1083E3',
|
|
strokeWidth: 2,
|
|
name: LINE_TYPE.SUBLINE.GABLE,
|
|
attributes: {
|
|
roofId: roof.id,
|
|
currentRoofId: currentRoof.id,
|
|
textMode: textMode,
|
|
planeSize: calcLinePlaneSize({
|
|
x1: hip.x2,
|
|
y1: hip.y2,
|
|
x2: currentRoof.attributes.ridgeCoordinate.x1,
|
|
y2: currentRoof.attributes.ridgeCoordinate.y1,
|
|
}),
|
|
actualSize: calcLineActualSize(
|
|
{
|
|
x1: hip.x2,
|
|
y1: hip.y2,
|
|
x2: currentRoof.attributes.ridgeCoordinate.x1,
|
|
y2: currentRoof.attributes.ridgeCoordinate.y1,
|
|
},
|
|
currentDegree,
|
|
),
|
|
},
|
|
})
|
|
|
|
// const gableBase = ((Math.abs(gableLine.x1 - gableLine.x2) + Math.abs(gableLine.y1 - gableLine.y2)) / 2) * 10
|
|
// const gableHeight = Math.round(gableBase / Math.tan(((90 - gableDegree) * Math.PI) / 180))
|
|
// gableLine.attributes.planeSize =
|
|
// Math.round(Math.sqrt(Math.pow(gableLine.x1 - gableLine.x2, 2) + Math.pow(gableLine.y1 - gableLine.y2, 2))) * 10
|
|
// gableLine.attributes.actualSize = Math.round(Math.sqrt(Math.pow(gableLine.attributes.planeSize, 2) + Math.pow(gableHeight, 2)))
|
|
canvas?.add(gableLine)
|
|
roof.innerLines.push(gableLine)
|
|
})
|
|
}
|
|
}
|
|
canvas?.renderAll()
|
|
}
|
|
|
|
/**
|
|
* 반절처 지붕으로 변경
|
|
* @param currentRoof
|
|
* @param canvas
|
|
* @param textMode
|
|
*/
|
|
const changeJerkInHeadRoof = (currentRoof, canvas, textMode) => {
|
|
if (
|
|
currentRoof.attributes.type === LINE_TYPE.WALLLINE.JERKINHEAD &&
|
|
currentRoof.attributes.width !== undefined &&
|
|
currentRoof.attributes.width > 0
|
|
) {
|
|
const roofId = currentRoof.attributes.roofId
|
|
const wall = canvas?.getObjects().find((object) => object.name === POLYGON_TYPE.WALL && object.attributes.roofId === roofId)
|
|
const roof = canvas?.getObjects().find((object) => object.name === POLYGON_TYPE.ROOF && object.id === roofId)
|
|
let hipLines = canvas?.getObjects().filter((object) => object.name === LINE_TYPE.SUBLINE.HIP && object.attributes.roofId === roofId)
|
|
let ridgeLines = canvas?.getObjects().filter((object) => object.name === LINE_TYPE.SUBLINE.RIDGE && object.attributes.roofId === roofId)
|
|
let wallLine = wall.lines.filter((w) => w.id === currentRoof.attributes.wallLine)
|
|
if (wallLine.length > 0) {
|
|
wallLine = wallLine[0]
|
|
}
|
|
|
|
let prevRoof, nextRoof
|
|
roof.lines.forEach((r, index) => {
|
|
if (r.id === currentRoof.id) {
|
|
currentRoof = r
|
|
prevRoof = roof.lines[index === 0 ? roof.lines.length - 1 : index - 1]
|
|
nextRoof = roof.lines[index === roof.lines.length - 1 ? 0 : index + 1]
|
|
}
|
|
})
|
|
|
|
const prevDegree = prevRoof.attributes.pitch > 0 ? getDegreeByChon(prevRoof.attributes.pitch) : prevRoof.attributes.degree
|
|
const nextDegree = nextRoof.attributes.pitch > 0 ? getDegreeByChon(nextRoof.attributes.pitch) : nextRoof.attributes.degree
|
|
|
|
const midX = Big(currentRoof.x1).plus(Big(currentRoof.x2)).div(2) // 지붕의 X 중심
|
|
const midY = Big(currentRoof.y1).plus(Big(currentRoof.y2)).div(2) // 지붕의 Y 중심
|
|
const midWallX = Big(wallLine.x1).plus(Big(wallLine.x2)).div(2) // 벽의 X 중심
|
|
const midWallY = Big(wallLine.y1).plus(Big(wallLine.y2)).div(2) // 벽의 Y 중심
|
|
const alpha = midX.minus(midWallX) // 벽과 지붕의 X 거리
|
|
const beta = midY.minus(midWallY) // 벽과 지붕의 Y 거리
|
|
// Math.sqrt(Math.pow(alpha, 2) + Math.pow(beta, 2))
|
|
const hypotenuse = alpha.pow(2).plus(beta.pow(2)).sqrt() // 벽과 지붕의 거리
|
|
const xWidth = Big(Math.sign(midX.minus(midWallX).toNumber()))
|
|
.times(alpha.div(hypotenuse))
|
|
.times(currentRoof.attributes.width / 2) // 지붕의 X 너비
|
|
const yWidth = Big(Math.sign(midY.minus(midWallY)))
|
|
.times(beta.div(hypotenuse))
|
|
.times(currentRoof.attributes.width / 2) // 지붕의 Y 너비
|
|
const addHipX2 = Big(Math.sign(midX.minus(midWallX).toNumber()))
|
|
.times(alpha.div(hypotenuse))
|
|
.times(currentRoof.length / 2) // 추녀마루의 X 너비
|
|
const addHipY2 = Big(Math.sign(midY.minus(midWallY)))
|
|
.times(beta.div(hypotenuse))
|
|
.times(currentRoof.length / 2) // 추녀마루의 Y 너비
|
|
let hipX2 = 0
|
|
let hipY2 = 0
|
|
|
|
// if (Math.sqrt(Math.pow(xWidth, 2) + Math.pow(yWidth, 2)) < Math.sqrt(Math.pow(addHipX2, 2) + Math.pow(addHipY2, 2))) {
|
|
if (
|
|
xWidth
|
|
.pow(2)
|
|
.plus(yWidth.pow(2))
|
|
.lt(addHipX2.pow(2).plus(addHipY2.pow(2)))
|
|
) {
|
|
// reDrawPolygon(roof, canvas)
|
|
|
|
const innerLines = canvas
|
|
?.getObjects()
|
|
.filter(
|
|
(object) =>
|
|
object.attributes !== undefined &&
|
|
object.attributes.roofId === roofId &&
|
|
object.attributes.currentRoofId === currentRoof.id &&
|
|
object.x1 !== undefined &&
|
|
object.x2 !== undefined,
|
|
)
|
|
|
|
innerLines
|
|
.filter(
|
|
(line) =>
|
|
line.name !== LINE_TYPE.SUBLINE.RIDGE &&
|
|
line.name !== LINE_TYPE.SUBLINE.HIP &&
|
|
line.name !== LINE_TYPE.SUBLINE.VALLEY &&
|
|
line.name !== OUTER_LINE_TYPE.OUTER_LINE &&
|
|
line.name !== 'wallLine',
|
|
)
|
|
.forEach((line) => {
|
|
roof.innerLines = roof.innerLines.filter((l) => l.id !== line.id)
|
|
canvas?.remove(line)
|
|
})
|
|
|
|
ridgeLines = ridgeLines.filter(
|
|
(ridge) =>
|
|
(ridge.x1 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y1 === currentRoof.attributes.ridgeCoordinate.y1) ||
|
|
(ridge.x2 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y2 === currentRoof.attributes.ridgeCoordinate.y1),
|
|
)
|
|
hipLines = hipLines.filter(
|
|
(hip) => (hip.x1 === currentRoof.x1 && hip.y1 === currentRoof.y1) || (hip.x1 === currentRoof.x2 && hip.y1 === currentRoof.y2),
|
|
)
|
|
|
|
if (hipLines === undefined || hipLines.length === 0) {
|
|
hipLines = innerLines.filter(
|
|
(line) =>
|
|
(line.x1 === currentRoof.x1 && line.y1 === currentRoof.y1) ||
|
|
(line.x2 === currentRoof.x1 && line.y2 === currentRoof.y1) ||
|
|
(line.x1 === currentRoof.x2 && line.y1 === currentRoof.y2) ||
|
|
(line.x2 === currentRoof.x2 && line.y2 === currentRoof.y2),
|
|
)
|
|
}
|
|
|
|
if ((ridgeLines === undefined || ridgeLines.length === 0) && hipLines.length >= 2) {
|
|
let points = []
|
|
hipLines.forEach((hip) => {
|
|
points.push({ x: hip.x1, y: hip.y1 })
|
|
points.push({ x: hip.x2, y: hip.y2 })
|
|
})
|
|
|
|
const pointSet = new Set()
|
|
const duplicatePoints = []
|
|
|
|
points.forEach((point) => {
|
|
const pointKey = `${point.x},${point.y}`
|
|
if (pointSet.has(pointKey)) {
|
|
duplicatePoints.push(point)
|
|
} else {
|
|
pointSet.add(pointKey)
|
|
}
|
|
})
|
|
|
|
ridgeLines = innerLines
|
|
.filter((r) => r !== hipLines[0] && r !== hipLines[1])
|
|
.filter(
|
|
(r) =>
|
|
(r.x1 === duplicatePoints[0].x && r.y1 === duplicatePoints[0].y) || (r.x2 === duplicatePoints[0].x && r.y2 === duplicatePoints[0].y),
|
|
)
|
|
if (ridgeLines.length > 0) {
|
|
currentRoof.attributes.ridgeCoordinate = { x1: duplicatePoints[0].x, y1: duplicatePoints[0].y }
|
|
}
|
|
}
|
|
|
|
hipLines.forEach((hip) => {
|
|
roof.innerLines = roof.innerLines.filter((h) => h.id !== hip.id)
|
|
canvas.remove(hip)
|
|
})
|
|
|
|
if (ridgeLines.length > 0) {
|
|
const ridge = ridgeLines[0]
|
|
if (ridge.x1 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y1 === currentRoof.attributes.ridgeCoordinate.y1) {
|
|
const signX = Math.sign(midX.minus(ridge.x1).toNumber())
|
|
const signY = Math.sign(midY.minus(ridge.y1).toNumber())
|
|
ridge.set({
|
|
x1: midX.minus(xWidth.abs().times(signX)).toNumber(),
|
|
y1: midY.minus(yWidth.abs().times(signY)).toNumber(),
|
|
x2: ridge.x2,
|
|
y2: ridge.y2,
|
|
})
|
|
|
|
currentRoof.attributes.ridgeCoordinate = { x1: ridge.x1, y1: ridge.y1 }
|
|
hipX2 = ridge.x1
|
|
hipY2 = ridge.y1
|
|
}
|
|
if (ridge.x2 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y2 === currentRoof.attributes.ridgeCoordinate.y1) {
|
|
const signX = Math.sign(midX - ridge.x2)
|
|
const signY = Math.sign(midY - ridge.y2)
|
|
ridge.set({
|
|
x1: ridge.x1,
|
|
y1: ridge.y1,
|
|
x2: midX.minus(xWidth.abs().times(signX)).toNumber(),
|
|
y2: midY.minus(yWidth.abs().times(signY)).toNumber(),
|
|
})
|
|
|
|
currentRoof.attributes.ridgeCoordinate = { x1: ridge.x2, y1: ridge.y2 }
|
|
hipX2 = ridge.x2
|
|
hipY2 = ridge.y2
|
|
}
|
|
ridge.attributes.planeSize = calcLinePlaneSize({ x1: ridge.x1, y1: ridge.y1, x2: ridge.x2, y2: ridge.y2 })
|
|
ridge.attributes.actualSize = calcLinePlaneSize({ x1: ridge.x1, y1: ridge.y1, x2: ridge.x2, y2: ridge.y2 })
|
|
}
|
|
|
|
let hipX1 = (Math.sign(currentRoof.x1 - midX) * currentRoof.attributes.width) / 2
|
|
let hipY1 = (Math.sign(currentRoof.y1 - midY) * currentRoof.attributes.width) / 2
|
|
|
|
const gableDegree = currentRoof.attributes.degree > 0 ? currentRoof.attributes.degree : getDegreeByChon(currentRoof.attributes.pitch)
|
|
const gable1 = new QLine([midX.plus(hipX1).toNumber(), midY.plus(hipY1).toNumber(), hipX2, hipY2], {
|
|
parentId: roof.id,
|
|
fontSize: roof.fontSize,
|
|
stroke: '#1083E3',
|
|
strokeWidth: 2,
|
|
name: LINE_TYPE.SUBLINE.GABLE,
|
|
textMode: textMode,
|
|
attributes: {
|
|
roofId: roof.id,
|
|
currentRoofId: currentRoof.id,
|
|
planeSize: calcLinePlaneSize({
|
|
x1: midX.plus(hipX1).toNumber(),
|
|
y1: midY.plus(hipY1).toNumber(),
|
|
x2: hipX2,
|
|
y2: hipY2,
|
|
}),
|
|
actualSize: calcLineActualSize(
|
|
{
|
|
x1: midX.plus(hipX1).toNumber(),
|
|
y1: midY.plus(hipY1).toNumber(),
|
|
x2: hipX2,
|
|
y2: hipY2,
|
|
},
|
|
gableDegree,
|
|
),
|
|
},
|
|
})
|
|
// const gable1Base = ((Math.abs(gable1.x1 - gable1.x2) + Math.abs(gable1.y1 - gable1.y2)) / 2) * 10
|
|
// const gable1Height = Math.round(gable1Base / Math.tan(((90 - gableDegree) * Math.PI) / 180))
|
|
// gable1.attributes.planeSize = Math.round(Math.sqrt(Math.pow(gable1.x1 - gable1.x2, 2) + Math.pow(gable1.y1 - gable1.y2, 2))) * 10
|
|
// gable1.attributes.actualSize = Math.round(Math.sqrt(Math.pow(gable1.attributes.planeSize, 2) + Math.pow(gable1Height, 2)))
|
|
canvas?.add(gable1)
|
|
roof.innerLines.push(gable1)
|
|
|
|
hipX1 = (Math.sign(currentRoof.x2 - midX) * currentRoof.attributes.width) / 2
|
|
hipY1 = (Math.sign(currentRoof.y2 - midY) * currentRoof.attributes.width) / 2
|
|
|
|
const gable2 = new QLine([midX.plus(hipX1).toNumber(), midY.plus(hipY1).toNumber(), hipX2, hipY2], {
|
|
parentId: roof.id,
|
|
fontSize: roof.fontSize,
|
|
stroke: '#1083E3',
|
|
strokeWidth: 2,
|
|
name: LINE_TYPE.SUBLINE.GABLE,
|
|
textMode: textMode,
|
|
attributes: {
|
|
roofId: roof.id,
|
|
currentRoofId: currentRoof.id,
|
|
planeSize: calcLinePlaneSize({
|
|
x1: midX.plus(hipX1).toNumber(),
|
|
y1: midY.plus(hipY1).toNumber(),
|
|
x2: hipX2,
|
|
y2: hipY2,
|
|
}),
|
|
actualSize: calcLineActualSize(
|
|
{
|
|
x1: midX.plus(hipX1).toNumber(),
|
|
y1: midY.plus(hipY1).toNumber(),
|
|
x2: hipX2,
|
|
y2: hipY2,
|
|
},
|
|
gableDegree,
|
|
),
|
|
},
|
|
})
|
|
// const gable2Base = ((Math.abs(gable2.x1 - gable2.x2) + Math.abs(gable2.y1 - gable2.y2)) / 2) * 10
|
|
// const gable2Height = Math.round(gable2Base / Math.tan(((90 - gableDegree) * Math.PI) / 180))
|
|
// gable2.attributes.planeSize = Math.round(Math.sqrt(Math.pow(gable2.x1 - gable2.x2, 2) + Math.pow(gable2.y1 - gable2.y2, 2))) * 10
|
|
// gable2.attributes.actualSize = Math.round(Math.sqrt(Math.pow(gable2.attributes.planeSize, 2) + Math.pow(gable2Height, 2)))
|
|
canvas?.add(gable2)
|
|
roof.innerLines.push(gable2)
|
|
|
|
const gable3 = new QLine([gable1.x1, gable1.y1, gable2.x1, gable2.y1], {
|
|
parentId: roof.id,
|
|
fontSize: roof.fontSize,
|
|
stroke: '#1083E3',
|
|
strokeWidth: 2,
|
|
name: LINE_TYPE.SUBLINE.GABLE,
|
|
textMode: textMode,
|
|
visible: false,
|
|
attributes: {
|
|
roofId: roof.id,
|
|
currentRoofId: currentRoof.id,
|
|
planeSize: calcLinePlaneSize({ x1: gable1.x1, y1: gable1.y1, x2: gable2.x1, y2: gable2.y1 }),
|
|
actualSize: calcLinePlaneSize({ x1: gable1.x1, y1: gable1.y1, x2: gable2.x1, y2: gable2.y1 }),
|
|
},
|
|
})
|
|
// gable3.attributes.planeSize = Math.round(Math.sqrt(Math.pow(gable3.x1 - gable3.x2, 2) + Math.pow(gable3.y1 - gable3.y2, 2))) * 10
|
|
// gable3.attributes.actualSize = Math.round(Math.sqrt(Math.pow(gable3.x1 - gable3.x2, 2) + Math.pow(gable3.y1 - gable3.y2, 2))) * 10
|
|
canvas?.add(gable3)
|
|
// roof.innerLines.push(gable3)
|
|
|
|
const hip1 = new QLine([currentRoof.x1, currentRoof.y1, gable1.x1, gable1.y1], {
|
|
parentId: roof.id,
|
|
fontSize: roof.fontSize,
|
|
stroke: '#1083E3',
|
|
strokeWidth: 2,
|
|
name: LINE_TYPE.SUBLINE.HIP,
|
|
textMode: textMode,
|
|
visible: false,
|
|
attributes: {
|
|
roofId: roof.id,
|
|
currentRoofId: currentRoof.id,
|
|
planeSize: calcLinePlaneSize({ x1: currentRoof.x1, y1: currentRoof.y1, x2: gable1.x1, y2: gable1.y1 }),
|
|
actualSize: calcLineActualSize(
|
|
{
|
|
x1: currentRoof.x1,
|
|
y1: currentRoof.y1,
|
|
x2: gable1.x1,
|
|
y2: gable1.y1,
|
|
},
|
|
prevDegree,
|
|
),
|
|
},
|
|
})
|
|
// const hip1Base = ((Math.abs(hip1.x1 - hip1.x2) + Math.abs(hip1.y1 - hip1.y2)) / 2) * 10
|
|
// const hip1Height = Math.round(hip1Base / Math.tan(((90 - prevDegree) * Math.PI) / 180))
|
|
// hip1.attributes.planeSize = Math.round(Math.sqrt(Math.pow(hip1.x1 - hip1.x2, 2) + Math.pow(hip1.y1 - hip1.y2, 2))) * 10
|
|
// hip1.attributes.actualSize = Math.round(Math.sqrt(Math.pow(hip1.attributes.planeSize, 2) + Math.pow(hip1Height, 2)))
|
|
canvas?.add(hip1)
|
|
// roof.innerLines.push(hip1)
|
|
|
|
const hip2 = new QLine([currentRoof.x2, currentRoof.y2, gable2.x1, gable2.y1], {
|
|
parentId: roof.id,
|
|
fontSize: roof.fontSize,
|
|
stroke: '#1083E3',
|
|
strokeWidth: 2,
|
|
name: LINE_TYPE.SUBLINE.HIP,
|
|
textMode: textMode,
|
|
visible: false,
|
|
attributes: {
|
|
roofId: roof.id,
|
|
currentRoofId: currentRoof.id,
|
|
planeSize: calcLinePlaneSize({ x1: currentRoof.x2, y1: currentRoof.y2, x2: gable2.x1, y2: gable2.y1 }),
|
|
actualSize: calcLineActualSize(
|
|
{
|
|
x1: currentRoof.x2,
|
|
y1: currentRoof.y2,
|
|
x2: gable2.x1,
|
|
y2: gable2.y1,
|
|
},
|
|
nextDegree,
|
|
),
|
|
},
|
|
})
|
|
// const hip2Base = ((Math.abs(hip2.x1 - hip2.x2) + Math.abs(hip2.y1 - hip2.y2)) / 2) * 10
|
|
// const hip2Height = Math.round(hip2Base / Math.tan(((90 - nextDegree) * Math.PI) / 180))
|
|
// hip2.attributes.planeSize = Math.round(Math.sqrt(Math.pow(hip2.x1 - hip2.x2, 2) + Math.pow(hip2.y1 - hip2.y2, 2))) * 10
|
|
// hip2.attributes.actualSize = Math.round(Math.sqrt(Math.pow(hip2.attributes.planeSize, 2) + Math.pow(hip2Height, 2)))
|
|
canvas?.add(hip2)
|
|
|
|
// hip1.set({ visible: false })
|
|
hip1.setViewLengthText(false)
|
|
// gable3.set({ visible: false })
|
|
gable3.setViewLengthText(false)
|
|
// hip2.set({ visible: false })
|
|
hip2.setViewLengthText(false)
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 벽지붕으로 변경
|
|
* @param currentRoof
|
|
* @param canvas
|
|
* @param textMode
|
|
*/
|
|
const changeWallRoof = (currentRoof, canvas, textMode) => {
|
|
const roofId = currentRoof.attributes.roofId
|
|
const roof = canvas?.getObjects().find((object) => object.name === 'roof' && object.id === roofId)
|
|
const roofLines = roof.lines
|
|
let prevRoof, nextRoof
|
|
roofLines.forEach((r, index) => {
|
|
if (r.id === currentRoof.id) {
|
|
currentRoof = r
|
|
prevRoof = roofLines[index === 0 ? roofLines.length - 1 : index - 1]
|
|
nextRoof = roofLines[index === roofLines.length - 1 ? 0 : index + 1]
|
|
}
|
|
})
|
|
|
|
const wall = canvas?.getObjects().find((object) => object.name === 'wall' && object.attributes.roofId === roofId)
|
|
let wallLine = wall.lines.filter((w) => w.id === currentRoof.attributes.wallLine)
|
|
let hipLines = canvas?.getObjects().filter((object) => object.name === LINE_TYPE.SUBLINE.HIP && object.attributes.roofId === roofId)
|
|
let ridgeLines = canvas?.getObjects().filter((object) => object.name === LINE_TYPE.SUBLINE.RIDGE && object.attributes.roofId === roofId)
|
|
|
|
if (wallLine.length > 0) {
|
|
wallLine = wallLine[0]
|
|
}
|
|
|
|
ridgeLines = ridgeLines.filter(
|
|
(ridge) =>
|
|
(ridge.x1 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y1 === currentRoof.attributes.ridgeCoordinate.y1) ||
|
|
(ridge.x2 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y2 === currentRoof.attributes.ridgeCoordinate.y1),
|
|
)
|
|
hipLines = hipLines.filter(
|
|
(hip) => (hip.x1 === currentRoof.x1 && hip.y1 === currentRoof.y1) || (hip.x1 === currentRoof.x2 && hip.y1 === currentRoof.y2),
|
|
)
|
|
|
|
const wallMidX = Big(wallLine.x1).plus(Big(wallLine.x2)).div(2)
|
|
const wallMidY = Big(wallLine.y1).plus(Big(wallLine.y2)).div(2)
|
|
const roofMidX = Big(currentRoof.x1).plus(Big(currentRoof.x2)).div(2)
|
|
const roofMidY = Big(currentRoof.y1).plus(Big(currentRoof.y2)).div(2)
|
|
|
|
const alpha = wallMidX.minus(roofMidX)
|
|
const beta = wallMidY.minus(roofMidY)
|
|
|
|
currentRoof.set({
|
|
x1: alpha.plus(currentRoof.x1).toNumber(),
|
|
y1: beta.plus(currentRoof.y1).toNumber(),
|
|
x2: alpha.plus(currentRoof.x2).toNumber(),
|
|
y2: beta.plus(currentRoof.y2).toNumber(),
|
|
})
|
|
|
|
prevRoof.set({
|
|
x1: prevRoof.x1,
|
|
y1: prevRoof.y1,
|
|
x2: alpha.plus(prevRoof.x2),
|
|
y2: beta.plus(prevRoof.y2),
|
|
})
|
|
|
|
nextRoof.set({
|
|
x1: alpha.plus(nextRoof.x1),
|
|
y1: beta.plus(nextRoof.y1),
|
|
x2: nextRoof.x2,
|
|
y2: nextRoof.y2,
|
|
})
|
|
|
|
const innerLines = canvas
|
|
?.getObjects()
|
|
.filter(
|
|
(object) =>
|
|
object.attributes?.roofId === roofId &&
|
|
object.attributes?.currentRoofId === currentRoof.id &&
|
|
object.x1 !== undefined &&
|
|
object.x2 !== undefined,
|
|
)
|
|
|
|
innerLines
|
|
.filter(
|
|
(line) =>
|
|
line.name !== LINE_TYPE.SUBLINE.RIDGE &&
|
|
line.name !== LINE_TYPE.SUBLINE.HIP &&
|
|
line.name !== LINE_TYPE.SUBLINE.VALLEY &&
|
|
line.name !== OUTER_LINE_TYPE.OUTER_LINE &&
|
|
line.name !== 'wallLine',
|
|
)
|
|
.forEach((line) => {
|
|
roof.innerLines = roof.innerLines.filter((l) => l.id !== line.id)
|
|
canvas?.remove(line)
|
|
})
|
|
|
|
const prevDegree = prevRoof.attributes.pitch > 0 ? getDegreeByChon(prevRoof.attributes.pitch) : prevRoof.attributes.degree
|
|
const nextDegree = nextRoof.attributes.pitch > 0 ? getDegreeByChon(nextRoof.attributes.pitch) : nextRoof.attributes.degree
|
|
|
|
if (currentRoof.attributes.sleeve && currentRoof.attributes.width > 0 && prevRoof.attributes.offset > 0 && nextRoof.attributes.offset > 0) {
|
|
const prevSignX = Math.sign(prevRoof.x1 - prevRoof.x2)
|
|
const prevSignY = Math.sign(prevRoof.y1 - prevRoof.y2)
|
|
const nextSignX = Math.sign(nextRoof.x1 - nextRoof.x2)
|
|
const nextSignY = Math.sign(nextRoof.y1 - nextRoof.y2)
|
|
|
|
const prevWidthX = prevSignX === 0 ? 0 : prevSignX * currentRoof.attributes.width
|
|
const prevWidthY = prevSignY === 0 ? 0 : prevSignY * currentRoof.attributes.width
|
|
const nextWidthX = nextSignX === 0 ? 0 : nextSignX * currentRoof.attributes.width
|
|
const nextWidthY = nextSignY === 0 ? 0 : nextSignY * currentRoof.attributes.width
|
|
const prevX2 = Big(prevRoof.x2).minus(prevWidthX)
|
|
const prevY2 = Big(prevRoof.y2).minus(prevWidthY)
|
|
const nextX1 = Big(nextRoof.x1).plus(nextWidthX)
|
|
const nextY1 = Big(nextRoof.y1).plus(nextWidthY)
|
|
|
|
currentRoof.set({
|
|
x1: wallLine.x1,
|
|
y1: wallLine.y1,
|
|
x2: wallLine.x2,
|
|
y2: wallLine.y2,
|
|
})
|
|
|
|
prevRoof.set({
|
|
x1: prevRoof.x1,
|
|
y1: prevRoof.y1,
|
|
x2: prevX2.toNumber(),
|
|
y2: prevY2.toNumber(),
|
|
})
|
|
|
|
nextRoof.set({
|
|
x1: nextX1.toNumber(),
|
|
y1: nextY1.toNumber(),
|
|
x2: nextRoof.x2,
|
|
y2: nextRoof.y2,
|
|
})
|
|
|
|
const addPrevWallLine1 = new QLine(
|
|
[prevX2.toNumber(), prevY2.toNumber(), Big(wallLine.x1).minus(prevWidthX).toNumber(), Big(wallLine.y1).minus(prevWidthY).toNumber()],
|
|
{
|
|
parentId: roof.id,
|
|
fontSize: roof.fontSize,
|
|
stroke: '#1083E3',
|
|
strokeWidth: 2,
|
|
name: 'roofLine',
|
|
textMode: textMode,
|
|
attributes: {
|
|
roofId: roofId,
|
|
type: LINE_TYPE.WALLLINE.ETC,
|
|
planeSize: calcLinePlaneSize({
|
|
x1: prevX2.toNumber(),
|
|
y1: prevY2.toNumber(),
|
|
x2: Big(wallLine.x1).minus(prevWidthX).toNumber(),
|
|
y2: Big(wallLine.y1).minus(prevWidthY).toNumber(),
|
|
}),
|
|
actualSize: calcLinePlaneSize({
|
|
x1: prevX2.toNumber(),
|
|
y1: prevY2.toNumber(),
|
|
x2: Big(wallLine.x1).minus(prevWidthX).toNumber(),
|
|
y2: Big(wallLine.y1).minus(prevWidthY).toNumber(),
|
|
}),
|
|
},
|
|
},
|
|
)
|
|
|
|
const addPrevWallLine2 = new QLine(
|
|
[
|
|
addPrevWallLine1.x2,
|
|
addPrevWallLine1.y2,
|
|
Big(addPrevWallLine1.x2).plus(prevWidthX).toNumber(),
|
|
Big(addPrevWallLine1.y2).plus(prevWidthY).toNumber(),
|
|
],
|
|
{
|
|
parentId: roof.id,
|
|
fontSize: roof.fontSize,
|
|
stroke: '#1083E3',
|
|
strokeWidth: 2,
|
|
name: 'roofLine',
|
|
textMode: textMode,
|
|
attributes: {
|
|
roofId: roofId,
|
|
type: LINE_TYPE.WALLLINE.ETC,
|
|
planeSize: calcLinePlaneSize({
|
|
x1: addPrevWallLine1.x2,
|
|
y1: addPrevWallLine1.y2,
|
|
x2: Big(addPrevWallLine1.x2).plus(prevWidthX).toNumber(),
|
|
y2: Big(addPrevWallLine1.y2).plus(prevWidthY).toNumber(),
|
|
}),
|
|
actualSize: calcLinePlaneSize({
|
|
x1: addPrevWallLine1.x2,
|
|
y1: addPrevWallLine1.y2,
|
|
x2: Big(addPrevWallLine1.x2).plus(prevWidthX).toNumber(),
|
|
y2: Big(addPrevWallLine1.y2).plus(prevWidthY).toNumber(),
|
|
}),
|
|
},
|
|
},
|
|
)
|
|
|
|
const addNextWallLine1 = new QLine(
|
|
[wallLine.x2, wallLine.y2, Big(wallLine.x2).plus(nextWidthX).toNumber(), Big(wallLine.y2).plus(nextWidthY).toNumber()],
|
|
{
|
|
parentId: roof.id,
|
|
fontSize: roof.fontSize,
|
|
stroke: '#1083E3',
|
|
strokeWidth: 2,
|
|
name: 'roofLine',
|
|
textMode: textMode,
|
|
attributes: {
|
|
roofId: roofId,
|
|
type: LINE_TYPE.WALLLINE.ETC,
|
|
planeSize: calcLinePlaneSize({
|
|
x1: wallLine.x2,
|
|
y1: wallLine.y2,
|
|
x2: Big(wallLine.x2).plus(nextWidthX).toNumber(),
|
|
y2: Big(wallLine.y2).plus(nextWidthY).toNumber(),
|
|
}),
|
|
actualSize: calcLinePlaneSize({
|
|
x1: wallLine.x2,
|
|
y1: wallLine.y2,
|
|
}),
|
|
},
|
|
},
|
|
)
|
|
|
|
const addNextWallLine2 = new QLine([addNextWallLine1.x2, addNextWallLine1.y2, nextX1.toNumber(), nextY1.toNumber()], {
|
|
parentId: roof.id,
|
|
fontSize: roof.fontSize,
|
|
stroke: '#1083E3',
|
|
strokeWidth: 2,
|
|
name: 'roofLine',
|
|
attributes: {
|
|
roofId: roofId,
|
|
type: LINE_TYPE.WALLLINE.ETC,
|
|
planeSize: calcLinePlaneSize({
|
|
x1: addNextWallLine1.x2,
|
|
y1: addNextWallLine1.y2,
|
|
x2: nextX1.toNumber(),
|
|
y2: nextY1.toNumber(),
|
|
}),
|
|
actualSize: calcLinePlaneSize({
|
|
x1: addNextWallLine1.x2,
|
|
y1: addNextWallLine1.y2,
|
|
x2: nextX1.toNumber(),
|
|
y2: nextY1.toNumber(),
|
|
}),
|
|
},
|
|
})
|
|
|
|
canvas?.renderAll()
|
|
const prevIndex = roof.lines.indexOf(prevRoof) + 1
|
|
roof.lines.splice(prevIndex, 0, addPrevWallLine1, addPrevWallLine2)
|
|
|
|
const nextIndex = roof.lines.indexOf(currentRoof) + 1
|
|
roof.lines.splice(nextIndex, 0, addNextWallLine1, addNextWallLine2)
|
|
}
|
|
|
|
reDrawPolygon(roof, canvas)
|
|
|
|
if (ridgeLines.length > 0) {
|
|
const ridge = ridgeLines[0]
|
|
if (ridge.x1 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y1 === currentRoof.attributes.ridgeCoordinate.y1) {
|
|
const diffX = Big(ridge.x1).minus(wallMidX)
|
|
const diffY = Big(ridge.y1).minus(wallMidY)
|
|
|
|
ridge.set({
|
|
x1: Big(ridge.x1).minus(diffX).toNumber(),
|
|
y1: Big(ridge.y1).minus(diffY).toNumber(),
|
|
x2: ridge.x2,
|
|
y2: ridge.y2,
|
|
})
|
|
}
|
|
if (ridge.x2 === currentRoof.attributes.ridgeCoordinate.x1 && ridge.y2 === currentRoof.attributes.ridgeCoordinate.y1) {
|
|
const diffX = Big(ridge.x2).minus(wallMidX)
|
|
const diffY = Big(ridge.y2).minus(wallMidY)
|
|
|
|
ridge.set({
|
|
x1: ridge.x1,
|
|
y1: ridge.y1,
|
|
x2: Big(ridge.x2).minus(diffX).toNumber(),
|
|
y2: Big(ridge.y2).minus(diffY).toNumber(),
|
|
})
|
|
}
|
|
// ridge.attributes.planeSize = Math.round(Math.sqrt(Math.pow(ridge.x1 - ridge.x2, 2) + Math.pow(ridge.y1 - ridge.y2, 2)) * 10)
|
|
ridge.attributes.planeSize = calcLinePlaneSize({ x1: ridge.x1, y1: ridge.y1, x2: ridge.x2, y2: ridge.y2 })
|
|
ridge.attributes.actualSize = calcLinePlaneSize({ x1: ridge.x1, y1: ridge.y1, x2: ridge.x2, y2: ridge.y2 })
|
|
|
|
let hip1 = new QLine([currentRoof.x1, currentRoof.y1, wallMidX.toNumber(), wallMidY.toNumber()], {
|
|
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: currentRoof.x1,
|
|
y1: currentRoof.y1,
|
|
x2: wallMidX.toNumber(),
|
|
y2: wallMidY.toNumber(),
|
|
}),
|
|
actualSize: calcLineActualSize({ x1: currentRoof.x1, y1: currentRoof.y1, x2: wallMidX.toNumber(), y2: wallMidY.toNumber() }, prevDegree),
|
|
},
|
|
})
|
|
// const hip1Base = ((Math.abs(hip1.x1 - hip1.x2) + Math.abs(hip1.y1 - hip1.y2)) / 2) * 10
|
|
// const hip1Height = Math.round(hip1Base / Math.tan(((90 - prevDegree) * Math.PI) / 180))
|
|
// hip1.attributes.planeSize = Math.round(Math.sqrt(Math.pow(hip1.x1 - hip1.x2, 2) + Math.pow(hip1.y1 - hip1.y2, 2))) * 10
|
|
// hip1.attributes.actualSize = Math.round(Math.sqrt(Math.pow(hip1.attributes.planeSize, 2) + Math.pow(hip1Height, 2)))
|
|
|
|
let hip2 = new QLine([currentRoof.x2, currentRoof.y2, wallMidX.toNumber(), wallMidY.toNumber()], {
|
|
parentId: roof.id,
|
|
fontSize: roof.fontSize,
|
|
stroke: '#1083E3',
|
|
strokeWidth: 2,
|
|
name: LINE_TYPE.SUBLINE.HIP,
|
|
attributes: {
|
|
roofId: roof.id,
|
|
currentRoofId: currentRoof.id,
|
|
planeSize: calcLinePlaneSize({
|
|
x1: currentRoof.x2,
|
|
y1: currentRoof.y2,
|
|
x2: wallMidX.toNumber(),
|
|
y2: wallMidY.toNumber(),
|
|
}),
|
|
actualSize: calcLineActualSize({ x1: currentRoof.x2, y1: currentRoof.y2, x2: wallMidX.toNumber(), y2: wallMidY.toNumber() }, nextDegree),
|
|
},
|
|
})
|
|
// const hip2Base = ((Math.abs(hip2.x1 - hip2.x2) + Math.abs(hip2.y1 - hip2.y2)) / 2) * 10
|
|
// const hip2Height = Math.round(hip2Base / Math.tan(((90 - nextDegree) * Math.PI) / 180))
|
|
// hip2.attributes.planeSize = Math.round(Math.sqrt(Math.pow(hip2.x1 - hip2.x2, 2) + Math.pow(hip2.y1 - hip2.y2, 2))) * 10
|
|
// hip2.attributes.actualSize = Math.round(Math.sqrt(Math.pow(hip2.attributes.planeSize, 2) + Math.pow(hip2Height, 2)))
|
|
canvas?.add(hip1)
|
|
canvas?.add(hip2)
|
|
roof.innerLines.push(hip1)
|
|
roof.innerLines.push(hip2)
|
|
}
|
|
if (hipLines.length > 0) {
|
|
hipLines.forEach((hip) => {
|
|
roof.innerLines = roof.innerLines.filter((h) => h.id !== hip.id)
|
|
canvas?.remove(hip)
|
|
})
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 지붕을 변경한다.
|
|
* @param currentRoof
|
|
* @param canvas
|
|
*/
|
|
export const changeCurrentRoof = (currentRoof, canvas) => {
|
|
const roofId = currentRoof.attributes.roofId
|
|
const originRoof = canvas?.getObjects().find((object) => object.name === 'roof' && object.id === roofId)
|
|
const wall = canvas?.getObjects().find((object) => object.name === 'wall' && object.attributes.roofId === roofId)
|
|
const wallLine = wall.lines.filter((w) => w.id === currentRoof.attributes.wallLine)[0]
|
|
const innerLines = canvas
|
|
?.getObjects()
|
|
.filter((object) => object.attributes !== undefined && object.attributes.roofId === roofId && object.x1 !== undefined && object.x2 !== undefined)
|
|
|
|
wallLine.attributes.type = currentRoof.attributes.type
|
|
wallLine.attributes.offset = currentRoof.attributes.offset
|
|
wallLine.attributes.width = currentRoof.attributes.width
|
|
wallLine.attributes.pitch = currentRoof.attributes.pitch
|
|
wallLine.attributes.sleeve = currentRoof.attributes.sleeve
|
|
|
|
canvas?.remove(originRoof)
|
|
|
|
innerLines.filter((line) => line.name !== OUTER_LINE_TYPE.OUTER_LINE).forEach((line) => canvas?.remove(line))
|
|
|
|
const polygon = createPolygon(wall.points)
|
|
const originPolygon = new QPolygon(wall.points, { fontSize: 0 })
|
|
originPolygon.setViewLengthText(false)
|
|
let offsetPolygon
|
|
|
|
let result = createRoofMarginPolygon(polygon, wall.lines).vertices
|
|
const allPointsOutside = result.every((point) => !originPolygon.inPolygon(point))
|
|
|
|
if (allPointsOutside) {
|
|
offsetPolygon = createRoofMarginPolygon(polygon, wall.lines).vertices
|
|
} else {
|
|
offsetPolygon = createRoofPaddingPolygon(polygon, wall.lines).vertices
|
|
}
|
|
|
|
const newRoof = new QPolygon(offsetPolygon, {
|
|
fill: originRoof.fill,
|
|
stroke: originRoof.stroke,
|
|
strokeWidth: originRoof.strokeWidth,
|
|
selectable: originRoof.selectable,
|
|
fontSize: originRoof.fontSize,
|
|
})
|
|
|
|
newRoof.name = POLYGON_TYPE.ROOF
|
|
newRoof.setWall(wall)
|
|
|
|
newRoof.lines.forEach((line, index) => {
|
|
const lineLength = Math.sqrt(
|
|
Math.pow(Math.round(Math.abs(line.x1 - line.x2) * 10), 2) + Math.pow(Math.round(Math.abs(line.y1 - line.y2) * 10), 2),
|
|
)
|
|
line.attributes = {
|
|
roofId: newRoof.id,
|
|
planeSize: lineLength,
|
|
actualSize: lineLength,
|
|
wallLine: wall.lines[index].id,
|
|
type: wall.lines[index].attributes.type,
|
|
offset: wall.lines[index].attributes.offset,
|
|
width: wall.lines[index].attributes.width,
|
|
pitch: wall.lines[index].attributes.pitch,
|
|
sleeve: wall.lines[index].attributes.sleeve || false,
|
|
}
|
|
})
|
|
wall.attributes = {
|
|
roofId: newRoof.id,
|
|
}
|
|
|
|
wall.lines.forEach((line, index) => {
|
|
line.attributes.roofId = newRoof.id
|
|
line.attributes.currentRoofId = newRoof.lines[index].id
|
|
})
|
|
canvas?.add(newRoof)
|
|
canvas?.renderAll()
|
|
|
|
newRoof.drawHelpLine()
|
|
}
|
|
|
|
/**
|
|
* 지붕을 변경한다.
|
|
* @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,
|
|
})
|
|
|
|
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 = Math.sqrt(
|
|
// Math.pow(Math.round(Math.abs(line.x1 - line.x2) * 10), 2) + Math.pow(Math.round(Math.abs(line.y1 - line.y2) * 10), 2),
|
|
// )
|
|
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 = currentRoof.attributes.pitch > 0 ? getDegreeByChon(currentRoof.attributes.pitch) : currentRoof.attributes.degree
|
|
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 createRoofMarginPolygon(polygon, lines, arcSegments = 0) {
|
|
const offsetEdges = []
|
|
|
|
polygon.edges.forEach((edge, i) => {
|
|
const offset =
|
|
lines[i % lines.length].attributes.offset === undefined || lines[i % lines.length].attributes.offset === 0
|
|
? 0.1
|
|
: lines[i % lines.length].attributes.offset
|
|
const dx = edge.outwardNormal.x * offset
|
|
const dy = edge.outwardNormal.y * offset
|
|
offsetEdges.push(createOffsetEdge(edge, dx, dy))
|
|
})
|
|
|
|
const vertices = []
|
|
|
|
offsetEdges.forEach((thisEdge, 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,
|
|
})
|
|
}
|
|
})
|
|
|
|
const marginPolygon = createPolygon(vertices)
|
|
marginPolygon.offsetEdges = offsetEdges
|
|
return marginPolygon
|
|
}
|
|
|
|
function createRoofPaddingPolygon(polygon, lines, arcSegments = 0) {
|
|
const offsetEdges = []
|
|
|
|
polygon.edges.forEach((edge, i) => {
|
|
const offset =
|
|
lines[i % lines.length].attributes.offset === undefined || lines[i % lines.length].attributes.offset === 0
|
|
? 0.1
|
|
: lines[i % lines.length].attributes.offset
|
|
const dx = edge.inwardNormal.x * offset
|
|
const dy = edge.inwardNormal.y * offset
|
|
offsetEdges.push(createOffsetEdge(edge, dx, dy))
|
|
})
|
|
|
|
const vertices = []
|
|
|
|
offsetEdges.forEach((thisEdge, 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,
|
|
})
|
|
}
|
|
})
|
|
|
|
const paddingPolygon = createPolygon(vertices)
|
|
paddingPolygon.offsetEdges = offsetEdges
|
|
return paddingPolygon
|
|
}
|
|
|
|
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 { x1, y1, x2, y2 } = points
|
|
const planeSize = calcLinePlaneSize(points)
|
|
let height = Big(Math.tan(Big(degree).times(Math.PI / 180))).times(planeSize)
|
|
/**
|
|
* 대각선일 경우 높이 계산 변경
|
|
*/
|
|
if (x1 !== x2 && y1 !== y2) {
|
|
height = Big(Math.tan(Big(degree).times(Math.PI / 180))).times(Big(x1).minus(x2).times(10).round())
|
|
}
|
|
return Big(planeSize).pow(2).plus(height.pow(2)).sqrt().abs().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
|
|
}
|