1658 lines
55 KiB
JavaScript
1658 lines
55 KiB
JavaScript
import { ANGLE_TYPE, canvasState, currentAngleTypeSelector, pitchTextSelector } from '@/store/canvasAtom'
|
|
import { useRecoilValue } from 'recoil'
|
|
import { fabric } from 'fabric'
|
|
import { calculateIntersection, findAndRemoveClosestPoint, getDegreeByChon, getDegreeInOrientation, isPointOnLine } from '@/util/canvas-util'
|
|
import { QPolygon } from '@/components/fabric/QPolygon'
|
|
import { isSamePoint, removeDuplicatePolygons } from '@/util/qpolygon-utils'
|
|
import { flowDisplaySelector } from '@/store/settingAtom'
|
|
import { fontSelector } from '@/store/fontAtom'
|
|
import { QLine } from '@/components/fabric/QLine'
|
|
import { LINE_TYPE, POLYGON_TYPE } from '@/common/common'
|
|
import { useLine } from '@/hooks/useLine'
|
|
|
|
export const usePolygon = () => {
|
|
const canvas = useRecoilValue(canvasState)
|
|
const isFlowDisplay = useRecoilValue(flowDisplaySelector)
|
|
const flowFontOptions = useRecoilValue(fontSelector('flowText'))
|
|
const lengthTextFontOptions = useRecoilValue(fontSelector('lengthText'))
|
|
const currentAngleType = useRecoilValue(currentAngleTypeSelector)
|
|
const pitchText = useRecoilValue(pitchTextSelector)
|
|
|
|
const { getLengthByLine } = useLine()
|
|
|
|
const addPolygon = (points, options, isAddCanvas = true) => {
|
|
const polygon = new QPolygon(points, {
|
|
...options,
|
|
fontSize: lengthTextFontOptions.fontSize.value,
|
|
fill: options.fill || 'transparent',
|
|
stroke: options.stroke || '#000000',
|
|
// selectable: true,
|
|
})
|
|
|
|
if (isAddCanvas) canvas?.add(polygon)
|
|
addLengthText(polygon)
|
|
|
|
return polygon
|
|
}
|
|
|
|
const addPolygonByLines = (lines, options) => {
|
|
//lines의 idx를 정렬한다.
|
|
lines.sort((a, b) => a.idx - b.idx)
|
|
const points = createPolygonPointsFromOuterLines(lines)
|
|
|
|
return addPolygon(points, {
|
|
...options,
|
|
})
|
|
}
|
|
|
|
const addLengthText = (polygon) => {
|
|
const lengthTexts = canvas.getObjects().filter((obj) => obj.name === 'lengthText' && obj.parentId === polygon.id)
|
|
lengthTexts.forEach((text) => {
|
|
canvas.remove(text)
|
|
})
|
|
const lines = polygon.lines
|
|
polygon.texts = []
|
|
|
|
lines.forEach((line, i) => {
|
|
const length = line.getLength()
|
|
const { planeSize, actualSize } = line.attributes
|
|
const scaleX = line.scaleX
|
|
const scaleY = line.scaleY
|
|
const x1 = line.left
|
|
const y1 = line.top
|
|
const x2 = line.left + line.width * scaleX
|
|
const y2 = line.top + line.height * scaleY
|
|
|
|
let left, top
|
|
|
|
if (line.direction === 'right') {
|
|
left = (x1 + x2) / 2
|
|
top = (y1 + y2) / 2 + 10
|
|
} else if (line.direction === 'top') {
|
|
left = (x1 + x2) / 2 + 10
|
|
top = (y1 + y2) / 2
|
|
} else if (line.direction === 'left') {
|
|
left = (x1 + x2) / 2
|
|
top = (y1 + y2) / 2 - 30
|
|
} else if (line.direction === 'bottom') {
|
|
left = (x1 + x2) / 2 - 50
|
|
top = (y1 + y2) / 2
|
|
}
|
|
|
|
const minX = line.left
|
|
const maxX = line.left + line.width
|
|
const minY = line.top
|
|
const maxY = line.top + line.length
|
|
const degree = (Math.atan2(y2 - y1, x2 - x1) * 180) / Math.PI
|
|
|
|
const text = new fabric.Textbox(planeSize ? planeSize.toString() : length.toString(), {
|
|
left: left,
|
|
top: top,
|
|
fontSize: lengthTextFontOptions.fontSize.value,
|
|
minX,
|
|
maxX,
|
|
minY,
|
|
maxY,
|
|
parentDirection: line.direction,
|
|
parentDegree: degree,
|
|
parentId: polygon.id,
|
|
planeSize: planeSize ?? length,
|
|
actualSize: actualSize ?? length,
|
|
editable: false,
|
|
selectable: true,
|
|
lockRotation: true,
|
|
lockScalingX: true,
|
|
lockScalingY: true,
|
|
parent: polygon,
|
|
name: 'lengthText',
|
|
})
|
|
polygon.texts.push(text)
|
|
canvas.add(text)
|
|
})
|
|
|
|
/*const points = polygon.get('points')
|
|
points.forEach((start, i) => {
|
|
const end = points[(i + 1) % points.length]
|
|
const dx = end.x - start.x
|
|
const dy = end.y - start.y
|
|
const length = Number(Math.sqrt(dx * dx + dy * dy).toFixed(1)) * 10
|
|
|
|
const midPoint = new fabric.Point((start.x + end.x) / 2, (start.y + end.y) / 2)
|
|
|
|
const degree = (Math.atan2(dy, dx) * 180) / Math.PI
|
|
|
|
// Create new text object if it doesn't exist
|
|
const text = new fabric.Text(length.toString(), {
|
|
left: midPoint.x,
|
|
top: midPoint.y,
|
|
parentId: polygon.id,
|
|
fontSize: lengthTextFontOptions.fontSize.value,
|
|
minX: Math.min(start.x, end.x),
|
|
maxX: Math.max(start.x, end.x),
|
|
minY: Math.min(start.y, end.y),
|
|
maxY: Math.max(start.y, end.y),
|
|
parentDirection: getDirectionByPoint(start, end),
|
|
parentDegree: degree,
|
|
dirty: true,
|
|
editable: true,
|
|
selectable: true,
|
|
lockRotation: true,
|
|
lockScalingX: true,
|
|
lockScalingY: true,
|
|
idx: i,
|
|
name: 'lengthText',
|
|
parent: polygon,
|
|
})
|
|
|
|
// this.texts.push(text)
|
|
canvas.add(text)
|
|
})*/
|
|
canvas.renderAll()
|
|
}
|
|
|
|
const createPolygonPointsFromOuterLines = (outerLines) => {
|
|
if (!outerLines || outerLines.length === 0) {
|
|
return []
|
|
}
|
|
|
|
// Extract points from outerLines
|
|
|
|
return outerLines.map((line) => ({
|
|
x: line.x1,
|
|
y: line.y1,
|
|
}))
|
|
}
|
|
|
|
const removePolygon = (polygon) => {
|
|
const texts = canvas.getObjects().filter((obj) => obj.parentId === polygon.id)
|
|
texts.forEach((text) => {
|
|
canvas.remove(text)
|
|
})
|
|
|
|
canvas.remove(polygon)
|
|
canvas.renderAll()
|
|
}
|
|
|
|
/**
|
|
* poolygon의 방향에 따라 화살표를 추가한다.
|
|
* @param polygon
|
|
* @param showDirectionText
|
|
*/
|
|
const drawDirectionArrow = (polygon, showDirectionText = true) => {
|
|
if (!polygon) {
|
|
return
|
|
}
|
|
|
|
if (polygon.points.length < 3) {
|
|
return
|
|
}
|
|
// 모듈있으면 화살표 이미 그려져 있으므로 수행 안함
|
|
const hasModules = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE).length > 0
|
|
|
|
if (hasModules) {
|
|
return
|
|
}
|
|
const direction = polygon.direction
|
|
if (!direction) {
|
|
return
|
|
}
|
|
|
|
//동일 아이디가 있으면 일단 지우고 다시 그린다
|
|
const existArrow = polygon.canvas.getObjects().filter((obj) => obj.name === 'arrow' && obj.parentId === polygon.id)
|
|
if (existArrow.length > 0) {
|
|
polygon.canvas.remove(...existArrow)
|
|
}
|
|
|
|
polygon.canvas
|
|
.getObjects()
|
|
.filter((obj) => obj.name === 'flowText' && obj.parentId === polygon.arrow?.id)
|
|
.forEach((obj) => polygon.canvas.remove(obj))
|
|
|
|
let arrow = null
|
|
let points = []
|
|
|
|
if (polygon.arrow) {
|
|
polygon.canvas.remove(polygon.arrow)
|
|
}
|
|
|
|
let centerPoint = { x: polygon.left, y: polygon.top }
|
|
const { width, height } = polygon
|
|
let stickeyPoint
|
|
|
|
const polygonMaxX = Math.max(...polygon.getCurrentPoints().map((point) => point.x))
|
|
const polygonMinX = Math.min(...polygon.getCurrentPoints().map((point) => point.x))
|
|
const polygonMaxY = Math.max(...polygon.getCurrentPoints().map((point) => point.y))
|
|
const polygonMinY = Math.min(...polygon.getCurrentPoints().map((point) => point.y))
|
|
const lines = polygon.lines
|
|
let centerPoints
|
|
switch (direction) {
|
|
case 'south':
|
|
// lines중 가장 아래에 있는 라인을 찾는다.
|
|
const line = lines.reduce((acc, cur) => {
|
|
return acc.y2 > cur.y2 ? acc : cur
|
|
}, lines[0])
|
|
centerPoint = { x: (line.x2 + line.x1) / 2, y: Math.max(line.y1, line.y2) }
|
|
break
|
|
case 'north':
|
|
// lines중 가장 위에 있는 라인을 찾는다.
|
|
const line2 = lines.reduce((acc, cur) => {
|
|
return acc.y2 < cur.y2 ? acc : cur
|
|
}, lines[0])
|
|
centerPoint = { x: (line2.x2 + line2.x1) / 2, y: Math.min(line2.y1, line2.y2) }
|
|
break
|
|
case 'west':
|
|
// lines중 가장 왼쪽에 있는 라인을 찾는다.
|
|
const line3 = lines.reduce((acc, cur) => {
|
|
return acc.x2 < cur.x2 ? acc : cur
|
|
}, lines[0])
|
|
centerPoint = { x: Math.min(line3.x1, line3.x2), y: (line3.y1 + line3.y2) / 2 }
|
|
break
|
|
case 'east':
|
|
// lines중 가장 오른쪽에 있는 라인을 찾는다.
|
|
const line4 = lines.reduce((acc, cur) => {
|
|
return acc.x2 > cur.x2 ? acc : cur
|
|
}, lines[0])
|
|
centerPoint = { x: Math.max(line4.x1, line4.x2), y: (line4.y1 + line4.y2) / 2 }
|
|
break
|
|
}
|
|
|
|
switch (direction) {
|
|
case 'north':
|
|
points = [
|
|
{ x: centerPoint.x, y: polygonMinY - 50 },
|
|
{ x: centerPoint.x + 20, y: polygonMinY - 50 },
|
|
{ x: centerPoint.x + 20, y: polygonMinY - 80 },
|
|
{ x: centerPoint.x + 50, y: polygonMinY - 80 },
|
|
{ x: centerPoint.x, y: polygonMinY - 110 },
|
|
{ x: centerPoint.x - 50, y: polygonMinY - 80 },
|
|
{ x: centerPoint.x - 20, y: polygonMinY - 80 },
|
|
{ x: centerPoint.x - 20, y: polygonMinY - 50 },
|
|
]
|
|
|
|
stickeyPoint = { x: centerPoint.x, y: polygonMinY - 110 }
|
|
break
|
|
case 'south':
|
|
points = [
|
|
{ x: centerPoint.x, y: polygonMaxY + 50 },
|
|
{ x: centerPoint.x + 20, y: polygonMaxY + 50 },
|
|
{ x: centerPoint.x + 20, y: polygonMaxY + 80 },
|
|
{ x: centerPoint.x + 50, y: polygonMaxY + 80 },
|
|
{ x: centerPoint.x, y: polygonMaxY + 110 },
|
|
{ x: centerPoint.x - 50, y: polygonMaxY + 80 },
|
|
{ x: centerPoint.x - 20, y: polygonMaxY + 80 },
|
|
{ x: centerPoint.x - 20, y: polygonMaxY + 50 },
|
|
]
|
|
stickeyPoint = { x: centerPoint.x, y: polygonMaxY + 110 }
|
|
break
|
|
case 'west':
|
|
points = [
|
|
{ x: polygonMinX - 50, y: centerPoint.y },
|
|
{ x: polygonMinX - 50, y: centerPoint.y + 20 },
|
|
{ x: polygonMinX - 80, y: centerPoint.y + 20 },
|
|
{ x: polygonMinX - 80, y: centerPoint.y + 50 },
|
|
{ x: polygonMinX - 110, y: centerPoint.y },
|
|
{ x: polygonMinX - 80, y: centerPoint.y - 50 },
|
|
{ x: polygonMinX - 80, y: centerPoint.y - 20 },
|
|
{ x: polygonMinX - 50, y: centerPoint.y - 20 },
|
|
]
|
|
|
|
stickeyPoint = { x: polygonMinX - 110, y: centerPoint.y }
|
|
break
|
|
case 'east':
|
|
points = [
|
|
{ x: polygonMaxX + 50, y: centerPoint.y },
|
|
{ x: polygonMaxX + 50, y: centerPoint.y + 20 },
|
|
{ x: polygonMaxX + 80, y: centerPoint.y + 20 },
|
|
{ x: polygonMaxX + 80, y: centerPoint.y + 50 },
|
|
{ x: polygonMaxX + 110, y: centerPoint.y },
|
|
{ x: polygonMaxX + 80, y: centerPoint.y - 50 },
|
|
{ x: polygonMaxX + 80, y: centerPoint.y - 20 },
|
|
{ x: polygonMaxX + 50, y: centerPoint.y - 20 },
|
|
]
|
|
|
|
stickeyPoint = { x: polygonMaxX + 110, y: centerPoint.y }
|
|
break
|
|
}
|
|
|
|
arrow = new QPolygon(points, {
|
|
selectable: false,
|
|
name: 'arrow',
|
|
fill: 'transparent',
|
|
stroke: 'black',
|
|
direction: direction,
|
|
parent: polygon,
|
|
stickeyPoint: stickeyPoint,
|
|
surfaceCompass: polygon.surfaceCompass,
|
|
moduleCompass: polygon.moduleCompass,
|
|
visible: isFlowDisplay,
|
|
pitch: polygon.roofMaterial?.pitch ?? 4,
|
|
parentId: polygon.id,
|
|
})
|
|
arrow.setViewLengthText(false)
|
|
polygon.arrow = arrow
|
|
polygon.canvas.add(arrow)
|
|
polygon.canvas.renderAll()
|
|
drawDirectionStringToArrow2(polygon, showDirectionText)
|
|
// drawDirectionStringToArrow()
|
|
}
|
|
|
|
//arrow의 compass 값으로 방향 글자 설정 필요
|
|
const drawDirectionStringToArrow2 = (polygon, showDirectionText) => {
|
|
let { direction, surfaceCompass, moduleCompass, arrow } = polygon
|
|
if (moduleCompass === null || moduleCompass === undefined) {
|
|
const textObj = new fabric.Text(`${currentAngleType === ANGLE_TYPE.SLOPE ? arrow.pitch : getDegreeByChon(arrow.pitch)}${pitchText}`, {
|
|
fontFamily: flowFontOptions.fontFamily.value,
|
|
fontWeight: flowFontOptions.fontWeight.value.toLowerCase().includes('bold') ? 'bold' : 'normal',
|
|
fontStyle: flowFontOptions.fontWeight.value.toLowerCase().includes('italic') ? 'italic' : 'normal',
|
|
fontSize: flowFontOptions.fontSize.value,
|
|
fill: flowFontOptions.fontColor.value,
|
|
originX: 'center',
|
|
originY: 'center',
|
|
pitch: arrow.pitch,
|
|
name: 'flowText',
|
|
selectable: false,
|
|
left: arrow.stickeyPoint.x,
|
|
top: arrow.stickeyPoint.y,
|
|
parent: arrow,
|
|
parentId: arrow.id,
|
|
visible: isFlowDisplay,
|
|
})
|
|
|
|
polygon.canvas.add(textObj)
|
|
return
|
|
}
|
|
|
|
let text = ''
|
|
|
|
let compassType = (375 + getDegreeInOrientation(moduleCompass)) / 15
|
|
|
|
moduleCompass = -1 * moduleCompass
|
|
|
|
if (moduleCompass === 0 || (moduleCompass < 0 && moduleCompass >= -6)) {
|
|
compassType = 1
|
|
} else if (moduleCompass < 0 && moduleCompass >= -21) {
|
|
compassType = 2
|
|
} else if (moduleCompass < 0 && moduleCompass >= -36) {
|
|
compassType = 3
|
|
} else if (moduleCompass < 0 && moduleCompass >= -51) {
|
|
compassType = 4
|
|
} else if (moduleCompass < 0 && moduleCompass >= -66) {
|
|
compassType = 5
|
|
} else if (moduleCompass < 0 && moduleCompass >= -81) {
|
|
compassType = 6
|
|
} else if (moduleCompass < 0 && moduleCompass >= -96) {
|
|
compassType = 7
|
|
} else if (moduleCompass < 0 && moduleCompass >= -111) {
|
|
compassType = 8
|
|
} else if (moduleCompass < 0 && moduleCompass >= -126) {
|
|
compassType = 9
|
|
} else if (moduleCompass < 0 && moduleCompass >= -141) {
|
|
compassType = 10
|
|
} else if (moduleCompass < 0 && moduleCompass >= -156) {
|
|
compassType = 11
|
|
} else if (moduleCompass < 0 && moduleCompass >= -171) {
|
|
compassType = 12
|
|
} else if (Math.abs(moduleCompass) === 180) {
|
|
compassType = 13
|
|
}
|
|
|
|
if ([1, 25].includes(compassType)) {
|
|
direction === 'north' ? (text = '北') : direction === 'south' ? (text = '南') : direction === 'west' ? (text = '西') : (text = '東')
|
|
} else if ([2, 3].includes(compassType)) {
|
|
direction === 'north'
|
|
? (text = '北北東')
|
|
: direction === 'south'
|
|
? (text = '南南西')
|
|
: direction === 'west'
|
|
? (text = '西北西')
|
|
: (text = '東南東')
|
|
} else if ([4].includes(compassType)) {
|
|
direction === 'north' ? (text = '北東') : direction === 'south' ? (text = '南西') : direction === 'west' ? (text = '北西') : (text = '南東')
|
|
} else if ([5, 6].includes(compassType)) {
|
|
direction === 'north'
|
|
? (text = '東北東')
|
|
: direction === 'south'
|
|
? (text = '西南西')
|
|
: direction === 'west'
|
|
? (text = '北北西')
|
|
: (text = '南南東')
|
|
} else if ([7].includes(compassType)) {
|
|
direction === 'north' ? (text = '東') : direction === 'south' ? (text = '西') : direction === 'west' ? (text = '北') : (text = '南')
|
|
} else if ([8, 9].includes(compassType)) {
|
|
direction === 'north'
|
|
? (text = '東南東')
|
|
: direction === 'south'
|
|
? (text = '西北西')
|
|
: direction === 'west'
|
|
? (text = '北北東')
|
|
: (text = '南南西')
|
|
} else if ([10].includes(compassType)) {
|
|
direction === 'north' ? (text = '南東') : direction === 'south' ? (text = '北西') : direction === 'west' ? (text = '北東') : (text = '南西')
|
|
} else if ([11, 12].includes(compassType)) {
|
|
direction === 'north'
|
|
? (text = '南南東')
|
|
: direction === 'south'
|
|
? (text = '北北西')
|
|
: direction === 'west'
|
|
? (text = '東北東')
|
|
: (text = '西南西')
|
|
} else if ([13].includes(compassType)) {
|
|
direction === 'north' ? (text = '南') : direction === 'south' ? (text = '北') : direction === 'west' ? (text = '東') : (text = '西')
|
|
} else if ([14, 15].includes(compassType)) {
|
|
direction === 'north'
|
|
? (text = '南南西')
|
|
: direction === 'south'
|
|
? (text = '北北東')
|
|
: direction === 'west'
|
|
? (text = '東南東')
|
|
: (text = '西北西')
|
|
} else if ([16].includes(compassType)) {
|
|
direction === 'north' ? (text = '南西') : direction === 'south' ? (text = '北東') : direction === 'west' ? (text = '南東') : (text = '北西')
|
|
} else if ([17, 18].includes(compassType)) {
|
|
direction === 'north'
|
|
? (text = '西南西')
|
|
: direction === 'south'
|
|
? (text = '東北東')
|
|
: direction === 'west'
|
|
? (text = '南南東')
|
|
: (text = '北北西')
|
|
} else if ([19].includes(compassType)) {
|
|
direction === 'north' ? (text = '西') : direction === 'south' ? (text = '東') : direction === 'west' ? (text = '南') : (text = '北')
|
|
} else if ([20, 21].includes(compassType)) {
|
|
direction === 'north'
|
|
? (text = '西北西')
|
|
: direction === 'south'
|
|
? (text = '東南東')
|
|
: direction === 'west'
|
|
? (text = '南南西')
|
|
: (text = '北北東')
|
|
} else if ([22].includes(compassType)) {
|
|
direction === 'north' ? (text = '北西') : direction === 'south' ? (text = '南東') : direction === 'west' ? (text = '南西') : (text = '北東')
|
|
} else if ([23, 24].includes(compassType)) {
|
|
direction === 'north'
|
|
? (text = '北北西')
|
|
: direction === 'south'
|
|
? (text = '南南東')
|
|
: direction === 'west'
|
|
? (text = '西南西')
|
|
: (text = '東北東')
|
|
}
|
|
|
|
// 東,西,南,北
|
|
if ([0].includes(surfaceCompass)) {
|
|
text = '南'
|
|
} else if ([15, 30].includes(surfaceCompass)) {
|
|
text = '南南東'
|
|
} else if ([45].includes(surfaceCompass)) {
|
|
text = '南東'
|
|
} else if ([60, 75].includes(surfaceCompass)) {
|
|
text = '東南東'
|
|
} else if ([90].includes(surfaceCompass)) {
|
|
text = '東'
|
|
} else if ([105, 120].includes(surfaceCompass)) {
|
|
text = '東北東'
|
|
} else if ([135].includes(surfaceCompass)) {
|
|
text = '北東'
|
|
} else if ([150, 165].includes(surfaceCompass)) {
|
|
text = '北北東'
|
|
} else if ([180].includes(surfaceCompass)) {
|
|
text = '北'
|
|
} else if ([-165, -150].includes(surfaceCompass)) {
|
|
text = '北北西'
|
|
} else if ([-135].includes(surfaceCompass)) {
|
|
text = '北西'
|
|
} else if ([-120, -105].includes(surfaceCompass)) {
|
|
text = '西北西'
|
|
} else if ([-90].includes(surfaceCompass)) {
|
|
text = '西'
|
|
} else if ([-75, -60].includes(surfaceCompass)) {
|
|
text = '西南西'
|
|
} else if ([-45].includes(surfaceCompass)) {
|
|
text = '西南'
|
|
} else if ([-30, -15].includes(surfaceCompass)) {
|
|
text = '西西南'
|
|
}
|
|
|
|
const sameDirectionCnt = canvas.getObjects().filter((obj) => {
|
|
const onlyStrDirection = obj.directionText?.replace(/[0-9]/g, '')
|
|
return obj.name === POLYGON_TYPE.ROOF && obj !== polygon && onlyStrDirection === text
|
|
})
|
|
|
|
text = text + (sameDirectionCnt.length + 1)
|
|
polygon.set('directionText', text)
|
|
const textObj = new fabric.Text(
|
|
`${showDirectionText && text} (${currentAngleType === ANGLE_TYPE.SLOPE ? arrow.pitch : getDegreeByChon(arrow.pitch)}${pitchText})`,
|
|
{
|
|
fontFamily: flowFontOptions.fontFamily.value,
|
|
fontWeight: flowFontOptions.fontWeight.value.toLowerCase().includes('bold') ? 'bold' : 'normal',
|
|
fontStyle: flowFontOptions.fontWeight.value.toLowerCase().includes('italic') ? 'italic' : 'normal',
|
|
fontSize: flowFontOptions.fontSize.value,
|
|
fill: flowFontOptions.fontColor.value,
|
|
pitch: arrow.pitch,
|
|
originX: 'center',
|
|
originY: 'center',
|
|
name: 'flowText',
|
|
originText: text,
|
|
selectable: false,
|
|
left: arrow.stickeyPoint.x,
|
|
top: arrow.stickeyPoint.y,
|
|
parent: arrow,
|
|
parentId: arrow.id,
|
|
visible: isFlowDisplay,
|
|
},
|
|
)
|
|
|
|
polygon.canvas.add(textObj)
|
|
}
|
|
|
|
/**
|
|
* 방향을 나타낸 화살표에 각도에 따라 글씨 추가
|
|
* @param canvas
|
|
* @param compass
|
|
*/
|
|
const drawDirectionStringToArrow = (canvas, compass = 0) => {
|
|
const arrows = canvas?.getObjects().filter((obj) => obj.name === 'arrow')
|
|
|
|
if (arrows.length === 0) {
|
|
return
|
|
}
|
|
|
|
const eastArrows = arrows.filter((arrow) => arrow.direction === 'east')
|
|
const westArrows = arrows.filter((arrow) => arrow.direction === 'west')
|
|
const northArrows = arrows.filter((arrow) => arrow.direction === 'north')
|
|
const southArrows = arrows.filter((arrow) => arrow.direction === 'south')
|
|
|
|
let southText = '南'
|
|
let eastText = '東'
|
|
let westText = '西'
|
|
let northText = '北'
|
|
|
|
if (compass === 0 || compass === 360) {
|
|
// 남,동,서 가능
|
|
// 그대로
|
|
} else if (compass < 45) {
|
|
//남(남남동),동(동북동),서(서남서) 가능
|
|
//북(북북서)
|
|
southText = '南南東'
|
|
eastText = '東北東'
|
|
westText = '西南西'
|
|
northText = '北北西'
|
|
} else if (compass === 45) {
|
|
// 남, 서 가능
|
|
// 남(남동)
|
|
// 서(남서)
|
|
// 북(북서)
|
|
// 동(북동)
|
|
southText = '南東'
|
|
westText = '南西'
|
|
northText = '北西'
|
|
eastText = '北東'
|
|
} else if (compass < 90) {
|
|
// 북(서북서)
|
|
// 동 (북북동)
|
|
// 남(동남동)
|
|
// 서(남남서)
|
|
northText = '北西北'
|
|
eastText = '北北東'
|
|
southText = '東南東'
|
|
westText = '南南西'
|
|
} else if (compass === 90) {
|
|
// 동(북)
|
|
// 서(남)
|
|
// 남(동)
|
|
// 북(서)
|
|
eastText = '北'
|
|
westText = '南'
|
|
southText = '東'
|
|
northText = '西'
|
|
} else if (compass < 135) {
|
|
// 남,서,북 가능
|
|
// 동(북북서)
|
|
// 서(남남동)
|
|
// 남(동북동)
|
|
// 북(서남서)
|
|
eastText = '北北西'
|
|
westText = '南南東'
|
|
southText = '東北東'
|
|
northText = '西南西'
|
|
} else if (compass === 135) {
|
|
// 서,북 가능
|
|
|
|
// 서(남동)
|
|
// 북(남서)
|
|
// 남(북동)
|
|
// 동(북서)
|
|
|
|
westText = '南東'
|
|
northText = '南西'
|
|
southText = '北東'
|
|
eastText = '北西'
|
|
} else if (compass < 180) {
|
|
// 북,동,서 가능
|
|
// 북(남남서)
|
|
// 동(서북서)
|
|
// 남(북북동)
|
|
// 서(동남동)
|
|
|
|
northText = '南南西'
|
|
eastText = '西北西'
|
|
southText = '北北東'
|
|
westText = '東南東'
|
|
} else if (compass === 180) {
|
|
// 북,동,서 가능
|
|
// 북(남)
|
|
// 동(서)
|
|
// 남(북)
|
|
// 서(동)
|
|
northText = '南'
|
|
eastText = '西'
|
|
southText = '北'
|
|
westText = '東'
|
|
} else if (compass < 225) {
|
|
// 서,북,동 가능
|
|
// 북(남남동)
|
|
// 동(서남서)
|
|
// 남(북북서)
|
|
// 서(동남동)
|
|
northText = '南南東'
|
|
eastText = '西南西'
|
|
southText = '北北西'
|
|
westText = '東南東'
|
|
} else if (compass === 225) {
|
|
// 북,동 가능
|
|
// 북(남동)
|
|
// 동(남서)
|
|
// 남(북서)
|
|
// 서(북동)
|
|
northText = '南東'
|
|
eastText = '南西'
|
|
southText = '北西'
|
|
westText = '北東'
|
|
} else if (compass < 270) {
|
|
// 북동남 가능
|
|
// 북(동남동)
|
|
// 동(남남서)
|
|
// 남(서북서)
|
|
// 서(북북동)
|
|
northText = '東南東'
|
|
eastText = '南南西'
|
|
southText = '西北西'
|
|
westText = '北北東'
|
|
} else if (compass === 270) {
|
|
// 북동남 가능
|
|
// 북(동)
|
|
// 동(남)
|
|
// 남(서)
|
|
// 서(북)
|
|
northText = '東'
|
|
eastText = '南'
|
|
southText = '西'
|
|
westText = '北'
|
|
} else if (compass < 315) {
|
|
// 북,동,남 가능
|
|
// 북(동북동)
|
|
// 동(남남동)
|
|
// 남(서남서)
|
|
// 서(북북서)
|
|
northText = '東北東'
|
|
eastText = '南南東'
|
|
southText = '西南西'
|
|
westText = '北北西'
|
|
} else if (compass === 315) {
|
|
// 동,남 가능
|
|
// 북(북동)
|
|
// 동(남동)
|
|
// 남(남서)
|
|
// 서(북서)
|
|
northText = '北東'
|
|
eastText = '南東'
|
|
southText = '南西'
|
|
westText = '北西'
|
|
} else if (compass < 360) {
|
|
// 남,동,서 가능
|
|
// 북(북북동)
|
|
// 동(동남동)
|
|
// 남(남남서)
|
|
// 서(서북서)
|
|
northText = '北北東'
|
|
eastText = '東南東'
|
|
southText = '南南西'
|
|
westText = '西北西'
|
|
}
|
|
|
|
clearFlowText(canvas)
|
|
|
|
addTextByArrows(eastArrows, eastText, canvas)
|
|
addTextByArrows(westArrows, westText, canvas)
|
|
addTextByArrows(northArrows, northText, canvas)
|
|
addTextByArrows(southArrows, southText, canvas)
|
|
}
|
|
|
|
const clearFlowText = (canvas) => {
|
|
const texts = canvas.getObjects().filter((obj) => obj.name === 'flowText')
|
|
texts.forEach((text) => {
|
|
canvas.remove(text)
|
|
})
|
|
}
|
|
|
|
const addTextByArrows = (arrows, txt, canvas) => {
|
|
arrows.forEach((arrow, index) => {
|
|
// const textStr = `${txt}${index + 1} (${currentAngleType === ANGLE_TYPE.SLOPE ? arrow.pitch : getDegreeByChon(arrow.pitch)}${pitchText})`
|
|
const textStr = `${txt} (${currentAngleType === ANGLE_TYPE.SLOPE ? arrow.pitch : getDegreeByChon(arrow.pitch)}${pitchText})`
|
|
|
|
const text = new fabric.Text(`${textStr}`, {
|
|
fontFamily: flowFontOptions.fontFamily.value,
|
|
fontWeight: flowFontOptions.fontWeight.value.toLowerCase().includes('bold') ? 'bold' : 'normal',
|
|
fontStyle: flowFontOptions.fontWeight.value.toLowerCase().includes('italic') ? 'italic' : 'normal',
|
|
fontSize: flowFontOptions.fontSize.value,
|
|
fill: flowFontOptions.fontColor.value,
|
|
pitch: arrow.pitch,
|
|
originX: 'center',
|
|
originY: 'center',
|
|
name: 'flowText',
|
|
originText: `${txt}${index + 1}`,
|
|
selectable: false,
|
|
left: arrow.stickeyPoint.x,
|
|
top: arrow.stickeyPoint.y,
|
|
parent: arrow,
|
|
parentId: arrow.id,
|
|
visible: isFlowDisplay,
|
|
})
|
|
canvas.add(text)
|
|
})
|
|
}
|
|
|
|
const splitPolygonWithLines = (polygon) => {
|
|
polygon.set({ visible: false })
|
|
|
|
const auxiliaryLines = canvas.getObjects().filter((obj) => obj.name === 'auxiliaryLine')
|
|
|
|
let innerLines = [...polygon.innerLines].filter((line) => line.visible)
|
|
|
|
/*// innerLine이 세팅이 안되어있는경우 찾아서 세팅한다.
|
|
if (!innerLines || innerLines.length === 0) {
|
|
let innerLineTypes = Object.keys(LINE_TYPE.SUBLINE).map((key, value) => LINE_TYPE.SUBLINE[key])
|
|
polygon.innerLines = canvas
|
|
.getObjects()
|
|
.filter(
|
|
(obj) =>
|
|
obj.type === 'QLine' &&
|
|
obj.attributes?.type !== 'pitchSizeLine' &&
|
|
obj.attributes?.roofId === polygon.id &&
|
|
innerLineTypes.includes(obj.name),
|
|
)
|
|
|
|
innerLines = [...polygon.innerLines]
|
|
}*/
|
|
canvas.renderAll()
|
|
let polygonLines = [...polygon.lines]
|
|
|
|
// polygonLines와 innerLines의 겹침을 확인하고 type을 변경하는 함수
|
|
const checkLineOverlap = (line1, line2) => {
|
|
// 두 선분이 같은 직선 위에 있는지 확인
|
|
const isOnSameLine = (l1, l2) => {
|
|
// 수직선인 경우 (x1 == x2)
|
|
if (Math.abs(l1.x1 - l1.x2) < 1 && Math.abs(l2.x1 - l2.x2) < 1) {
|
|
return Math.abs(l1.x1 - l2.x1) < 1
|
|
}
|
|
// 수평선인 경우 (y1 == y2)
|
|
if (Math.abs(l1.y1 - l1.y2) < 1 && Math.abs(l2.y1 - l2.y2) < 1) {
|
|
return Math.abs(l1.y1 - l2.y1) < 1
|
|
}
|
|
// 대각선인 경우는 기울기가 같은지 확인
|
|
const slope1 = (l1.y2 - l1.y1) / (l1.x2 - l1.x1)
|
|
const slope2 = (l2.y2 - l2.y1) / (l2.x2 - l2.x1)
|
|
const intercept1 = l1.y1 - slope1 * l1.x1
|
|
const intercept2 = l2.y1 - slope2 * l2.x1
|
|
return Math.abs(slope1 - slope2) < 0.01 && Math.abs(intercept1 - intercept2) < 1
|
|
}
|
|
|
|
if (!isOnSameLine(line1, line2)) {
|
|
return false
|
|
}
|
|
|
|
// 선분들이 같은 직선 위에 있다면 겹치는 부분이 있는지 확인
|
|
const getLineRange = (line) => {
|
|
if (Math.abs(line.x1 - line.x2) < 1) {
|
|
// 수직선: y 범위 확인
|
|
return {
|
|
min: Math.min(line.y1, line.y2),
|
|
max: Math.max(line.y1, line.y2),
|
|
}
|
|
} else {
|
|
// 수평선 또는 대각선: x 범위 확인
|
|
return {
|
|
min: Math.min(line.x1, line.x2),
|
|
max: Math.max(line.x1, line.x2),
|
|
}
|
|
}
|
|
}
|
|
|
|
const range1 = getLineRange(line1)
|
|
const range2 = getLineRange(line2)
|
|
|
|
// 겹치는 부분이 있는지 확인
|
|
return !(range1.max < range2.min || range2.max < range1.min)
|
|
}
|
|
|
|
polygonLines.forEach((line) => {
|
|
line.need = true
|
|
})
|
|
|
|
// innerLines와 polygonLines의 겹침을 확인하고 type 변경
|
|
innerLines.forEach((innerLine) => {
|
|
polygonLines.forEach((polygonLine) => {
|
|
if (checkLineOverlap(innerLine, polygonLine)) {
|
|
// innerLine의 type을 polygonLine의 type으로 변경
|
|
if (polygonLine.attributes?.type && innerLine.attributes) {
|
|
polygonLine.need = false
|
|
innerLine.attributes.type = polygonLine.attributes.type
|
|
innerLine.attributes.isStart = true
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
const roofs = []
|
|
polygonLines = polygonLines.filter((line) => line.need)
|
|
|
|
//polygonLines를 순회하며 innerLines와 교차하는 점을 line의 속성에 배열로 저장한다.
|
|
polygonLines.forEach((line) => {
|
|
let startPoint // 시작점
|
|
let endPoint // 끝점
|
|
if (line.x1 < line.x2) {
|
|
startPoint = { x: line.x1, y: line.y1 }
|
|
endPoint = { x: line.x2, y: line.y2 }
|
|
} else if (line.x1 > line.x2) {
|
|
startPoint = { x: line.x2, y: line.y2 }
|
|
endPoint = { x: line.x1, y: line.y1 }
|
|
} else {
|
|
if (line.y1 < line.y2) {
|
|
startPoint = { x: line.x1, y: line.y1 }
|
|
endPoint = { x: line.x2, y: line.y2 }
|
|
} else {
|
|
startPoint = { x: line.x2, y: line.y2 }
|
|
endPoint = { x: line.x1, y: line.y1 }
|
|
}
|
|
}
|
|
|
|
line.startPoint = startPoint
|
|
line.endPoint = endPoint
|
|
})
|
|
innerLines.forEach((line) => {
|
|
let startPoint // 시작점
|
|
let endPoint // 끝점
|
|
if (line.x1 < line.x2) {
|
|
startPoint = { x: line.x1, y: line.y1 }
|
|
endPoint = { x: line.x2, y: line.y2 }
|
|
} else if (line.x1 > line.x2) {
|
|
startPoint = { x: line.x2, y: line.y2 }
|
|
endPoint = { x: line.x1, y: line.y1 }
|
|
} else {
|
|
if (line.y1 < line.y2) {
|
|
startPoint = { x: line.x1, y: line.y1 }
|
|
endPoint = { x: line.x2, y: line.y2 }
|
|
} else {
|
|
startPoint = { x: line.x2, y: line.y2 }
|
|
endPoint = { x: line.x1, y: line.y1 }
|
|
}
|
|
}
|
|
|
|
line.startPoint = startPoint
|
|
line.endPoint = endPoint
|
|
})
|
|
|
|
/*polygonLines.forEach((line) => {
|
|
line.set({ strokeWidth: 10 })
|
|
canvas.add(line)
|
|
})
|
|
canvas.renderAll()*/
|
|
|
|
polygonLines.forEach((line) => {
|
|
/*const originStroke = line.stroke
|
|
line.set({ stroke: 'red' })
|
|
canvas.renderAll()*/
|
|
const intersections = []
|
|
innerLines.forEach((innerLine) => {
|
|
/*const originInnerStroke = innerLine.stroke
|
|
innerLine.set({ stroke: 'red' })
|
|
canvas.renderAll()*/
|
|
if (isPointOnLine(line, innerLine.startPoint)) {
|
|
canvas.renderAll()
|
|
if (isSamePoint(line.startPoint, innerLine.startPoint) || isSamePoint(line.endPoint, innerLine.startPoint)) {
|
|
return
|
|
}
|
|
intersections.push(innerLine.startPoint)
|
|
}
|
|
if (isPointOnLine(line, innerLine.endPoint)) {
|
|
canvas.renderAll()
|
|
if (isSamePoint(line.startPoint, innerLine.endPoint) || isSamePoint(line.endPoint, innerLine.endPoint)) {
|
|
return
|
|
}
|
|
intersections.push(innerLine.endPoint)
|
|
}
|
|
/*innerLine.set({ stroke: originInnerStroke })
|
|
canvas.renderAll()*/
|
|
})
|
|
line.set({ intersections })
|
|
|
|
/*line.set({ stroke: originStroke })
|
|
canvas.renderAll()*/
|
|
})
|
|
|
|
const divideLines = polygonLines.filter((line) => line.intersections?.length > 0)
|
|
let newLines = []
|
|
polygonLines = polygonLines.filter((line) => !line.intersections || line.intersections.length === 0)
|
|
for (let i = divideLines.length - 1; i >= 0; i--) {
|
|
const line = divideLines[i]
|
|
const { intersections, startPoint, endPoint } = line
|
|
|
|
if (intersections.length === 1) {
|
|
const newLinePoint1 = [line.x1, line.y1, intersections[0].x, intersections[0].y]
|
|
const newLinePoint2 = [intersections[0].x, intersections[0].y, line.x2, line.y2]
|
|
|
|
const newLine1 = new QLine(newLinePoint1, {
|
|
stroke: 'blue',
|
|
strokeWidth: 3,
|
|
fontSize: polygon.fontSize,
|
|
attributes: line.attributes,
|
|
name: 'newLine',
|
|
})
|
|
const newLine2 = new QLine(newLinePoint2, {
|
|
stroke: 'blue',
|
|
strokeWidth: 3,
|
|
fontSize: polygon.fontSize,
|
|
attributes: line.attributes,
|
|
name: 'newLine',
|
|
})
|
|
|
|
newLine1.attributes = {
|
|
...line.attributes,
|
|
planeSize: Math.round(Math.hypot(newLine1.x1 - newLine1.x2, newLine1.y1 - newLine1.y2)) * 10,
|
|
actualSize: Math.round(Math.hypot(newLine1.x1 - newLine1.x2, newLine1.y1 - newLine1.y2)) * 10,
|
|
}
|
|
newLine2.attributes = {
|
|
...line.attributes,
|
|
planeSize: Math.round(Math.hypot(newLine2.x1 - newLine2.x2, newLine2.y1 - newLine2.y2)) * 10,
|
|
actualSize: Math.round(Math.hypot(newLine2.x1 - newLine2.x2, newLine2.y1 - newLine2.y2)) * 10,
|
|
}
|
|
|
|
newLines.push(newLine1, newLine2)
|
|
divideLines.splice(i, 1) // 기존 line 제거
|
|
} else {
|
|
let currentPoint = startPoint
|
|
|
|
while (intersections.length !== 0) {
|
|
const minDistancePoint = findAndRemoveClosestPoint(currentPoint, intersections)
|
|
const newLinePoint = [currentPoint.x, currentPoint.y, minDistancePoint.x, minDistancePoint.y]
|
|
|
|
const newLine = new QLine(newLinePoint, {
|
|
stroke: 'blue',
|
|
strokeWidth: 3,
|
|
fontSize: polygon.fontSize,
|
|
attributes: line.attributes,
|
|
name: 'newLine',
|
|
})
|
|
|
|
newLine.attributes = {
|
|
...line.attributes,
|
|
planeSize: Math.round(Math.hypot(newLine.x1 - newLine.x2, newLine.y1 - newLine.y2)) * 10,
|
|
actualSize: Math.round(Math.hypot(newLine.x1 - newLine.x2, newLine.y1 - newLine.y2)) * 10,
|
|
}
|
|
|
|
newLines.push(newLine)
|
|
currentPoint = minDistancePoint
|
|
}
|
|
|
|
const newLinePoint = [currentPoint.x, currentPoint.y, endPoint.x, endPoint.y]
|
|
const newLine = new QLine(newLinePoint, {
|
|
stroke: 'blue',
|
|
strokeWidth: 3,
|
|
fontSize: polygon.fontSize,
|
|
attributes: line.attributes,
|
|
name: 'newLine',
|
|
})
|
|
newLine.attributes = {
|
|
...line.attributes,
|
|
planeSize: Math.round(Math.hypot(newLine.x1 - newLine.x2, newLine.y1 - newLine.y2)) * 10,
|
|
actualSize: Math.round(Math.hypot(newLine.x1 - newLine.x2, newLine.y1 - newLine.y2)) * 10,
|
|
}
|
|
|
|
newLines.push(newLine)
|
|
divideLines.splice(i, 1) // 기존 line 제거
|
|
}
|
|
}
|
|
|
|
//polygonLines에서 divideLines를 제거하고 newLines를 추가한다.
|
|
newLines = newLines.filter((line) => !(Math.abs(line.startPoint.x - line.endPoint.x) < 1 && Math.abs(line.startPoint.y - line.endPoint.y) < 1))
|
|
|
|
polygonLines = [...polygonLines, ...newLines]
|
|
|
|
polygonLines.forEach((polygonLine) => {
|
|
polygonLine.attributes = { ...polygonLine.attributes, isStart: true }
|
|
})
|
|
|
|
let allLines = [...polygonLines, ...innerLines]
|
|
|
|
// allLines를 전부 돌면서 교차점이 있는 경우 그 line을 잘라서 allLines에 추가
|
|
const processIntersections = () => {
|
|
const linesToProcess = canvas.getObjects().filter((obj) => obj.type === 'QLine' && obj.name === 'auxiliaryLine' && obj.visible)
|
|
const newDividedLines = []
|
|
const processedLines = new Set()
|
|
|
|
for (let i = 0; i < linesToProcess.length; i++) {
|
|
for (let j = i + 1; j < linesToProcess.length; j++) {
|
|
const line1 = linesToProcess[i]
|
|
const line2 = linesToProcess[j]
|
|
|
|
// 이미 처리된 line들은 건너뛰기
|
|
if (processedLines.has(line1) || processedLines.has(line2)) continue
|
|
|
|
// 같은 line이거나 이미 연결된 line은 건너뛰기
|
|
if (line1 === line2) continue
|
|
if (
|
|
isSamePoint({ x: line1.x1, y: line1.y1 }, { x: line2.x1, y: line2.y1 }) ||
|
|
isSamePoint({ x: line1.x1, y: line1.y1 }, { x: line2.x2, y: line2.y2 }) ||
|
|
isSamePoint({ x: line1.x2, y: line1.y2 }, { x: line2.x1, y: line2.y1 }) ||
|
|
isSamePoint({ x: line1.x2, y: line1.y2 }, { x: line2.x2, y: line2.y2 })
|
|
) {
|
|
continue
|
|
}
|
|
|
|
const intersectionPoint = calculateIntersection(line1, line2)
|
|
if (intersectionPoint) {
|
|
// line1에 교차점 추가
|
|
if (!line1.intersectionPoints) line1.intersectionPoints = []
|
|
line1.intersectionPoints.push(intersectionPoint)
|
|
|
|
// line2에 교차점 추가
|
|
if (!line2.intersectionPoints) line2.intersectionPoints = []
|
|
line2.intersectionPoints.push(intersectionPoint)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 교차점이 있는 line들을 분할
|
|
linesToProcess.forEach((line) => {
|
|
if (line.intersectionPoints && line.intersectionPoints.length > 0) {
|
|
// 교차점들을 line의 시작점에서부터의 거리순으로 정렬
|
|
const sortedPoints = line.intersectionPoints.sort((a, b) => {
|
|
const distA = Math.hypot(a.x - line.x1, a.y - line.y1)
|
|
const distB = Math.hypot(b.x - line.x1, b.y - line.y1)
|
|
return distA - distB
|
|
})
|
|
|
|
let currentPoint = { x: line.x1, y: line.y1 }
|
|
|
|
// 각 교차점까지의 line segment 생성
|
|
sortedPoints.forEach((intersectionPoint) => {
|
|
if (!isSamePoint(currentPoint, intersectionPoint)) {
|
|
const newLine = new QLine([currentPoint.x, currentPoint.y, intersectionPoint.x, intersectionPoint.y], {
|
|
stroke: line.stroke,
|
|
strokeWidth: line.strokeWidth,
|
|
fontSize: line.fontSize,
|
|
attributes: { ...line.attributes },
|
|
name: line.name,
|
|
visible: line.visible,
|
|
})
|
|
// startPoint와 endPoint 설정
|
|
if (newLine.x1 < newLine.x2) {
|
|
newLine.startPoint = { x: newLine.x1, y: newLine.y1 }
|
|
newLine.endPoint = { x: newLine.x2, y: newLine.y2 }
|
|
} else if (newLine.x1 > newLine.x2) {
|
|
newLine.startPoint = { x: newLine.x2, y: newLine.y2 }
|
|
newLine.endPoint = { x: newLine.x1, y: newLine.y1 }
|
|
} else {
|
|
if (newLine.y1 < newLine.y2) {
|
|
newLine.startPoint = { x: newLine.x1, y: newLine.y1 }
|
|
newLine.endPoint = { x: newLine.x2, y: newLine.y2 }
|
|
} else {
|
|
newLine.startPoint = { x: newLine.x2, y: newLine.y2 }
|
|
newLine.endPoint = { x: newLine.x1, y: newLine.y1 }
|
|
}
|
|
}
|
|
newDividedLines.push(newLine)
|
|
}
|
|
currentPoint = intersectionPoint
|
|
})
|
|
|
|
// 마지막 교차점에서 line 끝점까지의 segment
|
|
const endPoint = { x: line.x2, y: line.y2 }
|
|
if (!isSamePoint(currentPoint, endPoint)) {
|
|
const newLine = new QLine([currentPoint.x, currentPoint.y, endPoint.x, endPoint.y], {
|
|
stroke: line.stroke,
|
|
strokeWidth: line.strokeWidth,
|
|
fontSize: line.fontSize,
|
|
attributes: { ...line.attributes },
|
|
name: line.name,
|
|
visible: line.visible,
|
|
})
|
|
// startPoint와 endPoint 설정
|
|
if (newLine.x1 < newLine.x2) {
|
|
newLine.startPoint = { x: newLine.x1, y: newLine.y1 }
|
|
newLine.endPoint = { x: newLine.x2, y: newLine.y2 }
|
|
} else if (newLine.x1 > newLine.x2) {
|
|
newLine.startPoint = { x: newLine.x2, y: newLine.y2 }
|
|
newLine.endPoint = { x: newLine.x1, y: newLine.y1 }
|
|
} else {
|
|
if (newLine.y1 < newLine.y2) {
|
|
newLine.startPoint = { x: newLine.x1, y: newLine.y1 }
|
|
newLine.endPoint = { x: newLine.x2, y: newLine.y2 }
|
|
} else {
|
|
newLine.startPoint = { x: newLine.x2, y: newLine.y2 }
|
|
newLine.endPoint = { x: newLine.x1, y: newLine.y1 }
|
|
}
|
|
}
|
|
newDividedLines.push(newLine)
|
|
}
|
|
|
|
processedLines.add(line)
|
|
}
|
|
})
|
|
|
|
// allLines 업데이트: 분할된 line들 제거하고 새 line들 추가
|
|
allLines = allLines.filter((line) => !processedLines.has(line))
|
|
allLines = [...allLines, ...newDividedLines]
|
|
}
|
|
|
|
// 교차점 처리 실행
|
|
processIntersections()
|
|
|
|
/*allLines.forEach((line) => {
|
|
const originColor = line.stroke
|
|
|
|
line.set('stroke', 'red')
|
|
canvas.renderAll()
|
|
|
|
line.set('stroke', originColor)
|
|
canvas.renderAll()
|
|
})*/
|
|
|
|
const allPoints = []
|
|
|
|
// test용 좌표
|
|
const polygonLinesPoints = polygonLines.map((line) => {
|
|
return { startPoint: line.startPoint, endPoint: line.endPoint }
|
|
})
|
|
|
|
const innerLinesPoints = innerLines.map((line) => {
|
|
return { startPoint: line.startPoint, endPoint: line.endPoint }
|
|
})
|
|
|
|
polygonLinesPoints.forEach(({ startPoint, endPoint }) => {
|
|
allPoints.push(startPoint)
|
|
allPoints.push(endPoint)
|
|
})
|
|
|
|
innerLinesPoints.forEach(({ startPoint, endPoint }) => {
|
|
allPoints.push(startPoint)
|
|
allPoints.push(endPoint)
|
|
})
|
|
|
|
// 2025-02-19 대각선은 케라바, 직선은 용마루로 세팅
|
|
innerLines.forEach((innerLine) => {
|
|
const startPoint = innerLine.startPoint
|
|
const endPoint = innerLine.endPoint
|
|
|
|
// startPoint와 endPoint의 각도가 0,90,180,270이면 직선으로 판단
|
|
if (Math.abs(startPoint.x - endPoint.x) < 2 || Math.abs(startPoint.y - endPoint.y) < 2) {
|
|
if (!innerLine.attributes || !innerLine.attributes.type || innerLine.attributes.type === 'default') {
|
|
innerLine.attributes = {
|
|
...innerLine.attributes,
|
|
type: LINE_TYPE.SUBLINE.RIDGE,
|
|
}
|
|
}
|
|
} else {
|
|
if (!innerLine.attributes || !innerLine.attributes.type || innerLine.attributes.type === 'default') {
|
|
innerLine.attributes = {
|
|
...innerLine.attributes,
|
|
type: LINE_TYPE.SUBLINE.GABLE,
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
/*innerLines.forEach((line) => {
|
|
const startPoint = line.startPoint
|
|
const endPoint = line.endPoint
|
|
/!*canvas.add(new fabric.Circle({ left: startPoint.x, top: startPoint.y + 10, radius: 5, fill: 'red' }))
|
|
canvas.add(new fabric.Circle({ left: endPoint.x, top: endPoint.y - 10, radius: 5, fill: 'blue' }))*!/
|
|
})*/
|
|
|
|
/**
|
|
* 왼쪽 상단을 startPoint로 전부 변경
|
|
*/
|
|
allLines.forEach((line) => {
|
|
let startPoint // 시작점
|
|
let endPoint // 끝점
|
|
if (line.x1 < line.x2) {
|
|
startPoint = { x: line.x1, y: line.y1 }
|
|
endPoint = { x: line.x2, y: line.y2 }
|
|
} else if (line.x1 > line.x2) {
|
|
startPoint = { x: line.x2, y: line.y2 }
|
|
endPoint = { x: line.x1, y: line.y1 }
|
|
} else {
|
|
if (line.y1 < line.y2) {
|
|
startPoint = { x: line.x1, y: line.y1 }
|
|
endPoint = { x: line.x2, y: line.y2 }
|
|
} else {
|
|
startPoint = { x: line.x2, y: line.y2 }
|
|
endPoint = { x: line.x1, y: line.y1 }
|
|
}
|
|
}
|
|
|
|
line.startPoint = startPoint
|
|
line.endPoint = endPoint
|
|
})
|
|
|
|
//allLines에서 중복을 제거한다.
|
|
allLines = allLines.filter((line, index, self) => {
|
|
return (
|
|
index ===
|
|
self.findIndex((l) => {
|
|
return isSamePoint(l.startPoint, line.startPoint) && isSamePoint(l.endPoint, line.endPoint)
|
|
})
|
|
)
|
|
})
|
|
|
|
allLines = allLines.filter((line) => {
|
|
return Math.abs(line.startPoint.x - line.endPoint.x) > 2 || Math.abs(line.startPoint.y - line.endPoint.y) > 2
|
|
})
|
|
|
|
// 나눠서 중복 제거된 roof return
|
|
let newRoofs = getSplitRoofsPoints(allLines)
|
|
|
|
newRoofs = newRoofs.filter((roof) => roof.length !== 0)
|
|
|
|
newRoofs.forEach((roofPoint, index) => {
|
|
let defense, pitch
|
|
|
|
let representLines = []
|
|
let representLine
|
|
|
|
// 지붕을 그리면서 기존 polygon의 line중 연결된 line을 찾는다.
|
|
;[...polygonLines, ...innerLines].forEach((line) => {
|
|
let startFlag = false
|
|
let endFlag = false
|
|
const startPoint = line.startPoint
|
|
const endPoint = line.endPoint
|
|
roofPoint.forEach((point, index) => {
|
|
if (isSamePoint(point, startPoint)) {
|
|
startFlag = true
|
|
}
|
|
if (isSamePoint(point, endPoint)) {
|
|
endFlag = true
|
|
}
|
|
})
|
|
|
|
if (startFlag && endFlag) {
|
|
if (!representLines.includes(line) && line.attributes.type === LINE_TYPE.WALLLINE.EAVES) {
|
|
representLines.push(line)
|
|
} else if (!representLines.includes(line) && line.attributes.type === LINE_TYPE.WALLLINE.HIPANDGABLE) {
|
|
representLines.push(line)
|
|
}
|
|
}
|
|
})
|
|
|
|
// representLines중 가장 긴 line을 찾는다.
|
|
representLines.forEach((line) => {
|
|
if (!representLine) {
|
|
representLine = line
|
|
} else {
|
|
if (getLengthByLine(representLine) < getLengthByLine(line)) {
|
|
representLine = line
|
|
}
|
|
}
|
|
})
|
|
|
|
if (!representLine) {
|
|
representLines.forEach((line) => {
|
|
if (!representLine) {
|
|
representLine = line
|
|
} else {
|
|
if (representLine.length < line.length) {
|
|
representLine = line
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
const direction = polygon.direction ?? representLine?.direction ?? ''
|
|
const polygonDirection = polygon.direction
|
|
|
|
switch (direction) {
|
|
case 'top':
|
|
defense = 'east'
|
|
break
|
|
case 'right':
|
|
defense = 'south'
|
|
break
|
|
case 'bottom':
|
|
defense = 'west'
|
|
break
|
|
case 'left':
|
|
defense = 'north'
|
|
break
|
|
default:
|
|
defense = 'south'
|
|
break
|
|
}
|
|
pitch = polygon.lines[index]?.attributes?.pitch ?? representLine?.attributes?.pitch ?? 0
|
|
|
|
const roof = new QPolygon(roofPoint, {
|
|
fontSize: polygon.fontSize,
|
|
stroke: 'black',
|
|
fill: 'transparent',
|
|
strokeWidth: 3,
|
|
name: POLYGON_TYPE.ROOF,
|
|
originX: 'center',
|
|
originY: 'center',
|
|
selectable: true,
|
|
defense: defense,
|
|
from: 'roofCover',
|
|
direction: polygonDirection ?? defense,
|
|
pitch: pitch,
|
|
})
|
|
|
|
//allLines중 생성된 roof와 관련있는 line을 찾는다.
|
|
|
|
const roofLines = [...polygonLines, ...polygon.innerLines].filter((line) => {
|
|
let startFlag = false
|
|
let endFlag = false
|
|
const startPoint = line.startPoint
|
|
const endPoint = line.endPoint
|
|
roofPoint.forEach((point, index) => {
|
|
if (isSamePoint(point, startPoint)) {
|
|
startFlag = true
|
|
}
|
|
if (isSamePoint(point, endPoint)) {
|
|
endFlag = true
|
|
}
|
|
})
|
|
|
|
return startFlag && endFlag
|
|
})
|
|
|
|
roofLines.forEach((line) => {
|
|
roof.lines.forEach((roofLine) => {
|
|
if (
|
|
(isSamePoint(line.startPoint, roofLine.startPoint) && isSamePoint(line.endPoint, roofLine.endPoint)) ||
|
|
(isSamePoint(line.startPoint, roofLine.endPoint) && isSamePoint(line.endPoint, roofLine.startPoint))
|
|
) {
|
|
roofLine.attributes = { ...line.attributes }
|
|
}
|
|
})
|
|
})
|
|
|
|
canvas.add(roof)
|
|
addLengthText(roof)
|
|
canvas.remove(polygon)
|
|
canvas.renderAll()
|
|
})
|
|
|
|
//지붕 완료 후 보조선을 전부 제거한다.
|
|
|
|
auxiliaryLines.forEach((line) => {
|
|
canvas.remove(line)
|
|
})
|
|
canvas.renderAll()
|
|
canvas.discardActiveObject()
|
|
}
|
|
|
|
const getSplitRoofsPoints = (allLines) => {
|
|
// 모든 좌표점들을 수집
|
|
const allPoints = []
|
|
allLines.forEach((line, lineIndex) => {
|
|
allPoints.push({ point: line.startPoint, lineIndex, pointType: 'start' })
|
|
allPoints.push({ point: line.endPoint, lineIndex, pointType: 'end' })
|
|
})
|
|
|
|
// X 좌표 통일
|
|
for (let i = 0; i < allPoints.length; i++) {
|
|
for (let j = i + 1; j < allPoints.length; j++) {
|
|
const point1 = allPoints[i].point
|
|
const point2 = allPoints[j].point
|
|
|
|
if (Math.abs(point1.x - point2.x) < 1) {
|
|
const maxX = Math.max(point1.x, point2.x)
|
|
point1.x = maxX
|
|
point2.x = maxX
|
|
}
|
|
}
|
|
}
|
|
|
|
// Y 좌표 통일
|
|
for (let i = 0; i < allPoints.length; i++) {
|
|
for (let j = i + 1; j < allPoints.length; j++) {
|
|
const point1 = allPoints[i].point
|
|
const point2 = allPoints[j].point
|
|
|
|
if (Math.abs(point1.y - point2.y) < 1) {
|
|
const maxY = Math.max(point1.y, point2.y)
|
|
point1.y = maxY
|
|
point2.y = maxY
|
|
}
|
|
}
|
|
}
|
|
|
|
// ==== Utility functions ====
|
|
|
|
function isSamePoint(p1, p2, epsilon = 1) {
|
|
return Math.abs(p1.x - p2.x) <= epsilon && Math.abs(p1.y - p2.y) <= epsilon
|
|
}
|
|
|
|
function normalizePoint(p, epsilon = 1) {
|
|
return {
|
|
x: Math.round(p.x / epsilon) * epsilon,
|
|
y: Math.round(p.y / epsilon) * epsilon,
|
|
}
|
|
}
|
|
|
|
function pointToKey(p, epsilon = 1) {
|
|
const norm = normalizePoint(p, epsilon)
|
|
return `${norm.x},${norm.y}`
|
|
}
|
|
|
|
// 거리 계산
|
|
function calcDistance(p1, p2) {
|
|
return Math.hypot(p2.x - p1.x, p2.y - p1.y)
|
|
}
|
|
|
|
// ==== Direct edge check ====
|
|
|
|
function isDirectlyConnected(start, end, graph, epsilon = 1) {
|
|
const startKey = pointToKey(start, epsilon)
|
|
return (graph[startKey] || []).some((neighbor) => isSamePoint(neighbor.point, end, epsilon))
|
|
}
|
|
|
|
// ==== Dijkstra pathfinding ====
|
|
|
|
function findShortestPath(start, end, graph, epsilon = 1) {
|
|
const startKey = pointToKey(start, epsilon)
|
|
const endKey = pointToKey(end, epsilon)
|
|
|
|
const distances = {}
|
|
const previous = {}
|
|
const visited = new Set()
|
|
const queue = [{ key: startKey, dist: 0 }]
|
|
|
|
for (const key in graph) distances[key] = Infinity
|
|
distances[startKey] = 0
|
|
|
|
while (queue.length > 0) {
|
|
queue.sort((a, b) => a.dist - b.dist)
|
|
const { key } = queue.shift()
|
|
if (visited.has(key)) continue
|
|
visited.add(key)
|
|
|
|
for (const neighbor of graph[key] || []) {
|
|
const neighborKey = pointToKey(neighbor.point, epsilon)
|
|
const alt = distances[key] + neighbor.distance
|
|
if (alt < distances[neighborKey]) {
|
|
distances[neighborKey] = alt
|
|
previous[neighborKey] = key
|
|
queue.push({ key: neighborKey, dist: alt })
|
|
}
|
|
}
|
|
}
|
|
|
|
const path = []
|
|
let currentKey = endKey
|
|
|
|
if (!previous[currentKey]) return null
|
|
|
|
while (currentKey !== startKey) {
|
|
const [x, y] = currentKey.split(',').map(Number)
|
|
path.unshift({ x, y })
|
|
currentKey = previous[currentKey]
|
|
}
|
|
|
|
const [sx, sy] = startKey.split(',').map(Number)
|
|
path.unshift({ x: sx, y: sy })
|
|
|
|
return path
|
|
}
|
|
|
|
// 최종 함수
|
|
function getPath(start, end, graph, epsilon = 1) {
|
|
// startPoint와 arrivalPoint가 될 수 있는 점은 line.attributes.type이 'default' 혹은 null이 아닌 line인 경우에만 가능
|
|
const isValidPoint = (point) => {
|
|
return allLines.some((line) => {
|
|
const isOnLine = isSamePoint(line.startPoint, point, epsilon) || isSamePoint(line.endPoint, point, epsilon)
|
|
const hasValidType = line.attributes?.type && line.attributes.type !== 'default'
|
|
return isOnLine && hasValidType
|
|
})
|
|
}
|
|
|
|
if (!isValidPoint(start) || !isValidPoint(end)) {
|
|
console.log('시작점 또는 도착점이 유효하지 않음. 무시.')
|
|
return []
|
|
}
|
|
|
|
if (isDirectlyConnected(start, end, graph, epsilon)) {
|
|
console.log('직선 연결 있음. 무시.')
|
|
return []
|
|
}
|
|
|
|
const path = findShortestPath(start, end, graph, epsilon)
|
|
if (!path || path.length < 3) {
|
|
console.log('경로 존재하나 3개 미만 좌표. 무시.')
|
|
return []
|
|
}
|
|
|
|
// 사용된 노드들을 graph에서 제거
|
|
if (path.length > 0) {
|
|
path.forEach((point) => {
|
|
const pointKey = pointToKey(point, epsilon)
|
|
delete graph[pointKey]
|
|
// 다른 노드들의 연결에서도 이 노드를 제거
|
|
Object.keys(graph).forEach((key) => {
|
|
graph[key] = graph[key].filter((neighbor) => !isSamePoint(neighbor.point, point, epsilon))
|
|
})
|
|
})
|
|
}
|
|
|
|
return path
|
|
}
|
|
|
|
const roofs = []
|
|
const remainingLines = [...allLines] // 사용 가능한 line들의 복사본
|
|
|
|
// isStart가 true인 line들만 시작점으로 사용
|
|
const startLines = remainingLines.filter(line => line.attributes?.isStart === true)
|
|
|
|
startLines.forEach((startLine) => {
|
|
// 현재 남아있는 line들로 그래프 생성
|
|
const graph = {}
|
|
|
|
for (const line of remainingLines.filter((line2) => line2 !== startLine)) {
|
|
const p1 = line.startPoint
|
|
const p2 = line.endPoint
|
|
const key1 = pointToKey(p1)
|
|
const key2 = pointToKey(p2)
|
|
const distance = calcDistance(p1, p2)
|
|
const isStartLine = line.attributes?.isStart === true
|
|
|
|
if (!graph[key1]) graph[key1] = []
|
|
if (!graph[key2]) graph[key2] = []
|
|
|
|
// isStart가 아닌 line을 우선하도록 distance 조정
|
|
const adjustedDistance = isStartLine ? distance + 1000 : distance
|
|
|
|
graph[key1].push({ point: p2, distance: adjustedDistance, line })
|
|
graph[key2].push({ point: p1, distance: adjustedDistance, line })
|
|
}
|
|
|
|
const startPoint = { ...startLine.startPoint } // 시작점
|
|
let arrivalPoint = { ...startLine.endPoint } // 도착점
|
|
|
|
const roof = getPath(startPoint, arrivalPoint, graph)
|
|
if (roof.length > 0) {
|
|
roofs.push(roof)
|
|
|
|
// 사용된 startLine을 remainingLines에서 제거
|
|
const startLineIndex = remainingLines.findIndex(line => line === startLine)
|
|
if (startLineIndex !== -1) {
|
|
remainingLines.splice(startLineIndex, 1)
|
|
}
|
|
}
|
|
})
|
|
|
|
return removeDuplicatePolygons(
|
|
roofs.filter((roof) => roof.length < 100),
|
|
allLines.some((line) => line.name === 'auxiliaryLine'),
|
|
)
|
|
}
|
|
|
|
const splitPolygonWithSeparate = (separates) => {
|
|
separates.forEach((separate) => {
|
|
const points = separate.lines.map((line) => {
|
|
return { x: line.x1, y: line.y1 }
|
|
})
|
|
let defense = ''
|
|
switch (separate.attributes.direction) {
|
|
case 'top':
|
|
defense = 'east'
|
|
break
|
|
case 'right':
|
|
defense = 'south'
|
|
break
|
|
case 'bottom':
|
|
defense = 'west'
|
|
break
|
|
case 'left':
|
|
defense = 'north'
|
|
break
|
|
}
|
|
|
|
const roof = new QPolygon(points, {
|
|
fontSize: separate.fontSize,
|
|
stroke: 'black',
|
|
fill: 'transparent',
|
|
strokeWidth: 3,
|
|
name: POLYGON_TYPE.ROOF,
|
|
originX: 'center',
|
|
originY: 'center',
|
|
selectable: true,
|
|
defense: defense,
|
|
pitch: separate.attributes.pitch,
|
|
direction: defense,
|
|
})
|
|
|
|
canvas.add(roof)
|
|
})
|
|
|
|
canvas.renderAll()
|
|
}
|
|
|
|
return {
|
|
addPolygon,
|
|
addPolygonByLines,
|
|
removePolygon,
|
|
drawDirectionArrow,
|
|
addLengthText,
|
|
splitPolygonWithLines,
|
|
splitPolygonWithSeparate,
|
|
}
|
|
}
|