5779 lines
203 KiB
JavaScript
5779 lines
203 KiB
JavaScript
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
import {
|
|
calculateIntersection,
|
|
distanceBetweenPoints,
|
|
findClosestPoint,
|
|
getCenterPoint,
|
|
getClosestHorizontalLine,
|
|
getClosestVerticalLine,
|
|
getDirection,
|
|
getStartIndex,
|
|
rearrangeArray,
|
|
} from '@/util/canvas-util'
|
|
import { useRecoilState, useRecoilValue } from 'recoil'
|
|
|
|
import {
|
|
canvasSizeState,
|
|
canvasState,
|
|
compassState,
|
|
drewRoofCellsState,
|
|
fontSizeState,
|
|
guideLineState,
|
|
horiGuideLinesState,
|
|
modeState,
|
|
objectPlacementModeState,
|
|
roofPolygonArrayState,
|
|
roofPolygonPatternArrayState,
|
|
roofState,
|
|
sortedPolygonArray,
|
|
templateTypeState,
|
|
vertGuideLinesState,
|
|
wallState,
|
|
} from '@/store/canvasAtom'
|
|
import { QLine } from '@/components/fabric/QLine'
|
|
import { fabric } from 'fabric'
|
|
import { QPolygon } from '@/components/fabric/QPolygon'
|
|
import offsetPolygon, { calculateAngle } from '@/util/qpolygon-utils'
|
|
import { isObjectNotEmpty } from '@/util/common-utils'
|
|
import * as turf from '@turf/turf'
|
|
import { INPUT_TYPE, LINE_TYPE, Mode, POLYGON_TYPE } from '@/common/common'
|
|
import Big from 'big.js'
|
|
import { settingModalFirstOptionsState } from '@/store/settingAtom'
|
|
|
|
export function useMode() {
|
|
const [mode, setMode] = useRecoilState(modeState)
|
|
const points = useRef([])
|
|
const historyPoints = useRef([])
|
|
const historyLines = useRef([])
|
|
const startPoint = useRef()
|
|
const [canvas, setCanvas] = useRecoilState(canvasState)
|
|
const [zoom, setZoom] = useState(100)
|
|
const [fontSize] = useRecoilState(fontSizeState)
|
|
const [sortedArray, setSortedArray] = useRecoilState(sortedPolygonArray)
|
|
const [roof, setRoof] = useRecoilState(roofState)
|
|
const [wall, setWall] = useRecoilState(wallState)
|
|
|
|
const [endPoint, setEndPoint] = useState(null)
|
|
|
|
const pointCount = useRef(0)
|
|
|
|
const [roofPolygonPattern, setRoofPolygonPattern] = useRecoilState(roofPolygonPatternArrayState)
|
|
const [roofPolygonArray, setRoofPolygonArray] = useRecoilState(roofPolygonArrayState)
|
|
const [templateType, setTemplateType] = useRecoilState(templateTypeState)
|
|
|
|
const [canvasSize] = useRecoilState(canvasSizeState)
|
|
|
|
const [selectedCellRoofArray, setSelectedCellRoofArray] = useState([])
|
|
const [drewRoofCells, setDrewRoofCells] = useRecoilState(drewRoofCellsState)
|
|
const [roofStyle, setRoofStyle] = useState(1) //기본 지붕 패턴
|
|
const [templateCenterLine, setTemplateCenterLine] = useState([])
|
|
const compass = useRecoilValue(compassState)
|
|
const [isCellCenter, setIsCellCenter] = useState(false)
|
|
|
|
const [guideLineInfo, setGuideLineInfo] = useRecoilState(guideLineState)
|
|
|
|
const [guideLineMode, setGuideLineMode] = useState(false)
|
|
const [guideDotMode, setGuideDotMode] = useState(false)
|
|
|
|
const [horiGuideLines, setHoriGuideLines] = useRecoilState(horiGuideLinesState)
|
|
const [vertGuideLines, setVertGuideLines] = useRecoilState(vertGuideLinesState)
|
|
|
|
const [objectPlacementMode, setObjectPlacementModeState] = useRecoilState(objectPlacementModeState)
|
|
|
|
const settingModalFirstOptions = useRecoilValue(settingModalFirstOptionsState)
|
|
|
|
useEffect(() => {
|
|
// if (!canvas) {
|
|
// canvas?.setZoom(0.8)
|
|
// return
|
|
// }
|
|
if (!canvas) return
|
|
canvas?.off('mouse:out', removeMouseLines)
|
|
canvas?.on('mouse:out', removeMouseLines)
|
|
canvas?.off('mouse:move')
|
|
canvas?.on('mouse:move', drawMouseLines)
|
|
}, [canvas, zoom]) // 빈 배열을 전달하여 컴포넌트가 마운트될 때만 실행되도록 함
|
|
|
|
useEffect(() => {
|
|
if (canvas?.getObjects().find((obj) => obj.name === 'connectLine')) {
|
|
canvas?.remove(canvas?.getObjects().find((obj) => obj.name === 'connectLine'))
|
|
}
|
|
canvas?.off('mouse:move', (e) => addLineEndPointToMousePoint(e, endPoint))
|
|
canvas?.off('mouse:move')
|
|
canvas?.on('mouse:move', drawMouseLines)
|
|
canvas?.off('mouse:out', removeMouseLines)
|
|
canvas?.on('mouse:out', removeMouseLines)
|
|
if (!endPoint) {
|
|
return
|
|
}
|
|
|
|
canvas?.on('mouse:move', (e) => addLineEndPointToMousePoint(e, endPoint))
|
|
}, [endPoint])
|
|
|
|
useEffect(() => {
|
|
changeMode(canvas, mode)
|
|
}, [mode, horiGuideLines, vertGuideLines])
|
|
|
|
useEffect(() => {
|
|
setGuideLineMode(false)
|
|
setGuideDotMode(false)
|
|
if (isObjectNotEmpty(guideLineInfo)) {
|
|
const guideLineState = guideLineInfo.filter((item) => item.guideMode === 'guideLine')
|
|
const guideDotState = guideLineInfo.filter((item) => item.guideMode === 'guideDot')
|
|
|
|
setGuideLineMode(guideLineState.length > 0)
|
|
setGuideDotMode(guideDotState.length > 0)
|
|
}
|
|
}, [guideLineInfo])
|
|
|
|
// 마우스 보조선 가로선, 세로선 그리기
|
|
const drawMouseLines = (e) => {
|
|
let isGuideLineMode = false,
|
|
isGuideDotMode = false
|
|
let guideDotLength, guideLineLengthHori, guideLineLengthVert, horizontalLineArray, verticalLineArray
|
|
|
|
if (isObjectNotEmpty(guideLineInfo)) {
|
|
const guideLineState = guideLineInfo.filter((item) => item.guideMode === 'guideLine')
|
|
const guideDotState = guideLineInfo.filter((item) => item.guideMode === 'guideDot')
|
|
setGuideLineMode(guideLineState.length > 0)
|
|
setGuideDotMode(guideDotState.length > 0)
|
|
isGuideLineMode = guideLineState.length > 0
|
|
isGuideDotMode = guideDotState.length > 0
|
|
|
|
if (isGuideDotMode) {
|
|
guideLineLengthHori = Number(guideDotState[0].moduleHoriLength)
|
|
guideLineLengthVert = Number(guideDotState[0].moduleVertLength)
|
|
}
|
|
|
|
if (isGuideLineMode) {
|
|
horizontalLineArray = [...horiGuideLines]
|
|
verticalLineArray = [...vertGuideLines]
|
|
guideLineLengthHori = Number(guideLineState[0].moduleHoriLength)
|
|
guideLineLengthVert = Number(guideLineState[0].moduleVertLength)
|
|
}
|
|
}
|
|
|
|
// 현재 마우스 포인터의 위치를 가져옵니다.
|
|
const pointer = canvas?.getPointer(e.e)
|
|
|
|
// 기존에 그려진 가이드라인을 제거합니다.
|
|
removeMouseLines()
|
|
|
|
let newX = pointer.x
|
|
let newY = pointer.y
|
|
|
|
//흡착점 있는지 확인
|
|
const adsorptionPointList = canvas?._objects.filter((obj) => obj.name === 'adsorptionPoint')
|
|
|
|
if (mode === Mode.EDIT || mode === Mode.ADSORPTION_POINT) {
|
|
let adsorptionPoint = adsorptionPointList.length > 0 ? findClosestPoint(pointer, adsorptionPointList) : null
|
|
if ((horiGuideLines.length > 0 || vertGuideLines.length > 0) && guideDotMode) {
|
|
const closestHorizontalLine = getClosestHorizontalLine(pointer, horiGuideLines)
|
|
const closetVerticalLine = getClosestVerticalLine(pointer, vertGuideLines)
|
|
let intersection = null
|
|
let intersectionDistance = Infinity
|
|
|
|
if (closestHorizontalLine && closetVerticalLine) {
|
|
intersection = calculateIntersection(closestHorizontalLine, closetVerticalLine)
|
|
if (intersection) {
|
|
intersectionDistance = distanceBetweenPoints(pointer, intersection)
|
|
}
|
|
}
|
|
|
|
let xDiff, yDiff
|
|
|
|
if (closetVerticalLine) {
|
|
xDiff = Math.abs(pointer.x - closetVerticalLine.x1)
|
|
}
|
|
if (closestHorizontalLine) {
|
|
yDiff = Math.abs(pointer.y - closestHorizontalLine.y1)
|
|
}
|
|
|
|
const x = pointer.x - guideLineLengthHori * Math.floor(pointer.x / guideLineLengthHori)
|
|
const y = pointer.y - guideLineLengthVert * Math.floor(pointer.y / guideLineLengthVert)
|
|
|
|
const xRate = x / guideLineLengthHori
|
|
const yRate = y / guideLineLengthVert
|
|
const isAttachX = xRate >= 0.4 && xRate <= 0.7
|
|
const isAttachY = yRate >= 0.4 && yRate <= 0.7
|
|
if (isAttachX && isAttachY) {
|
|
newX = Math.floor(pointer.x / guideLineLengthHori) * guideLineLengthHori + guideLineLengthHori / 2
|
|
newY = Math.floor(pointer.y / guideLineLengthVert) * guideLineLengthVert + guideLineLengthVert / 2
|
|
} else {
|
|
if (intersection && intersectionDistance < 20) {
|
|
newX = intersection.x
|
|
newY = intersection.y
|
|
} else {
|
|
if (Math.min(xDiff, yDiff) <= 20) {
|
|
if (xDiff < yDiff) {
|
|
newX = closetVerticalLine.x1
|
|
newY = pointer.y
|
|
} else {
|
|
newX = pointer.x
|
|
newY = closestHorizontalLine.y1
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else if (guideDotMode) {
|
|
const x = pointer.x - guideLineLengthHori * Math.floor(pointer.x / guideLineLengthHori)
|
|
const y = pointer.y - guideLineLengthVert * Math.floor(pointer.y / guideLineLengthVert)
|
|
|
|
const xRate = x / guideLineLengthHori
|
|
const yRate = y / guideLineLengthVert
|
|
const isAttachX = xRate >= 0.4 && xRate <= 0.7
|
|
const isAttachY = yRate >= 0.4 && yRate <= 0.7
|
|
|
|
if (isAttachX && isAttachY) {
|
|
newX = Math.floor(pointer.x / guideLineLengthHori) * guideLineLengthHori + guideLineLengthHori / 2
|
|
newY = Math.floor(pointer.y / guideLineLengthVert) * guideLineLengthVert + guideLineLengthVert / 2
|
|
}
|
|
} else if (horiGuideLines.length > 0 || vertGuideLines.length > 0) {
|
|
const closestHorizontalLine = getClosestHorizontalLine(pointer, horiGuideLines)
|
|
const closetVerticalLine = getClosestVerticalLine(pointer, vertGuideLines)
|
|
let intersection = null
|
|
let intersectionDistance = Infinity
|
|
|
|
if (closestHorizontalLine && closetVerticalLine) {
|
|
intersection = calculateIntersection(closestHorizontalLine, closetVerticalLine)
|
|
if (intersection) {
|
|
intersectionDistance = distanceBetweenPoints(pointer, intersection)
|
|
}
|
|
}
|
|
|
|
let xDiff, yDiff
|
|
|
|
if (closetVerticalLine) {
|
|
xDiff = Math.abs(pointer.x - closetVerticalLine.x1)
|
|
}
|
|
if (closestHorizontalLine) {
|
|
yDiff = Math.abs(pointer.y - closestHorizontalLine.y1)
|
|
}
|
|
|
|
const x = pointer.x - guideLineLengthHori * Math.floor(pointer.x / guideLineLengthHori)
|
|
const y = pointer.y - guideLineLengthVert * Math.floor(pointer.y / guideLineLengthVert)
|
|
|
|
const xRate = x / guideLineLengthHori
|
|
const yRate = y / guideLineLengthVert
|
|
const isAttachX = xRate >= 0.4 && xRate <= 0.7
|
|
const isAttachY = yRate >= 0.4 && yRate <= 0.7
|
|
if (isAttachX && isAttachY) {
|
|
newX = Math.floor(pointer.x / guideLineLengthHori) * guideLineLengthHori + guideLineLengthHori / 2
|
|
newY = Math.floor(pointer.y / guideLineLengthVert) * guideLineLengthVert + guideLineLengthVert / 2
|
|
} else {
|
|
if (intersection && intersectionDistance < 20) {
|
|
newX = intersection.x
|
|
newY = intersection.y
|
|
} else {
|
|
if (Math.min(xDiff, yDiff) <= 20) {
|
|
if (xDiff < yDiff) {
|
|
newX = closetVerticalLine.x1
|
|
newY = pointer.y
|
|
} else {
|
|
newX = pointer.x
|
|
newY = closestHorizontalLine.y1
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (adsorptionPoint && distanceBetweenPoints(pointer, adsorptionPoint) < 20) {
|
|
newX = adsorptionPoint.left
|
|
newY = adsorptionPoint.top
|
|
}
|
|
}
|
|
|
|
// 가로선을 그립니다.
|
|
const horizontalLine = new fabric.Line([0, newY, 2 * canvas.width, newY], {
|
|
stroke: 'red',
|
|
strokeWidth: 1,
|
|
selectable: false,
|
|
name: 'mouseLine',
|
|
})
|
|
|
|
// 세로선을 그립니다.
|
|
const verticalLine = new fabric.Line([newX, 0, newX, 2 * canvas.height], {
|
|
stroke: 'red',
|
|
strokeWidth: 1,
|
|
selectable: false,
|
|
name: 'mouseLine',
|
|
})
|
|
|
|
// 선들을 캔버스에 추가합니다.
|
|
canvas?.add(horizontalLine, verticalLine)
|
|
|
|
// 캔버스를 다시 그립니다.
|
|
canvas?.renderAll()
|
|
}
|
|
|
|
useEffect(() => {
|
|
if (pointCount.current <= 2) {
|
|
removeGuideLines()
|
|
return
|
|
}
|
|
drawGuideLines()
|
|
}, [pointCount.current])
|
|
|
|
const removeGuideLines = () => {
|
|
const guideLines = canvas?._objects.filter((obj) => obj.name === 'helpGuideLine')
|
|
guideLines?.forEach((item) => canvas?.remove(item))
|
|
}
|
|
|
|
const drawGuideLines = () => {
|
|
// 이름이 guideLine인 가이드라인을 제거합니다.
|
|
removeGuideLines()
|
|
|
|
const arrivalX = startPoint.current?.left
|
|
const arrivalY = startPoint.current?.top
|
|
|
|
const lastX = endPoint?.left
|
|
const lastY = endPoint?.top
|
|
|
|
if (lastX === arrivalX || lastY === arrivalY) {
|
|
// 둘중 하나라도 같으면 guideLine은 한개만 생성
|
|
const guideLine = new QLine([lastX, lastY, arrivalX, arrivalY], {
|
|
fontSize: fontSize,
|
|
stroke: 'black',
|
|
strokeWidth: 1,
|
|
strokeDashArray: [1, 1, 1],
|
|
})
|
|
guideLine.name = 'helpGuideLine'
|
|
canvas?.add(guideLine)
|
|
} else {
|
|
const guideLine1 = new QLine([lastX, lastY, lastX, arrivalY], {
|
|
fontSize: fontSize,
|
|
stroke: 'black',
|
|
strokeWidth: 1,
|
|
strokeDashArray: [1, 1, 1],
|
|
})
|
|
|
|
const guideLine2 = new QLine([guideLine1.x2, guideLine1.y2, arrivalX, arrivalY], {
|
|
fontSize: fontSize,
|
|
stroke: 'black',
|
|
strokeWidth: 1,
|
|
strokeDashArray: [1, 1, 1],
|
|
})
|
|
|
|
guideLine1.name = 'helpGuideLine'
|
|
guideLine2.name = 'helpGuideLine'
|
|
|
|
canvas?.add(guideLine1)
|
|
canvas?.add(guideLine2)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 마우스 포인터의 가이드라인을 제거합니다.
|
|
*/
|
|
const removeMouseLines = () => {
|
|
if (canvas?._objects.length > 0) {
|
|
const mouseLines = canvas?._objects.filter((obj) => obj.name === 'mouseLine')
|
|
mouseLines.forEach((item) => canvas?.remove(item))
|
|
}
|
|
canvas?.renderAll()
|
|
}
|
|
|
|
const addLineEndPointToMousePoint = (e, endPoint) => {
|
|
if (canvas?.getObjects().find((obj) => obj.name === 'connectLine')) {
|
|
canvas?.remove(canvas?.getObjects().find((obj) => obj.name === 'connectLine'))
|
|
}
|
|
|
|
if (!endPoint) {
|
|
return
|
|
}
|
|
|
|
const pointer = canvas?.getPointer(e.e)
|
|
|
|
let newX, newY
|
|
|
|
newX = pointer.x
|
|
newY = pointer.y
|
|
|
|
// 마우스 포인터 위치랑 endPoint를 연결하는 line 생성
|
|
const line = new fabric.Line([endPoint.left, endPoint.top, newX, newY], {
|
|
stroke: 'black',
|
|
strokeWidth: 1,
|
|
selectable: false,
|
|
})
|
|
|
|
line.set({ name: 'connectLine' })
|
|
|
|
canvas?.add(line)
|
|
canvas?.renderAll()
|
|
}
|
|
|
|
// 모드에 따른 마우스 이벤트 변경
|
|
const changeMouseEvent = (mode) => {
|
|
switch (mode) {
|
|
case 'drawLine':
|
|
canvas?.on('mouse:down', mouseEvent.drawLineModeLeftClick)
|
|
document.addEventListener('contextmenu', mouseEvent.drawLineModeRightClick)
|
|
break
|
|
case 'edit':
|
|
canvas?.on('mouse:down', mouseEvent.editMode)
|
|
break
|
|
case 'textbox':
|
|
canvas?.on('mouse:down', mouseEvent.textboxMode)
|
|
break
|
|
case 'drawRect':
|
|
canvas?.on('mouse:down', mouseEvent.drawRectMode)
|
|
break
|
|
case 'drawHelpLine':
|
|
canvas?.off('selection:created', addSelectCreatedEvent)
|
|
canvas?.off('selection:cleared', addSelectClearedEvent)
|
|
canvas?.on('selection:created', addSelectCreatedEvent)
|
|
canvas?.on('selection:cleared', addSelectClearedEvent)
|
|
break
|
|
case 'adsorptionPoint':
|
|
canvas?.on('mouse:down', mouseEvent.adsorptionPoint)
|
|
break
|
|
case 'shadow':
|
|
canvas?.on('mouse:down', mouseEvent.shadowMode.down)
|
|
canvas?.on('mouse:move', mouseEvent.shadowMode.move)
|
|
canvas?.on('mouse:up', mouseEvent.shadowMode.up)
|
|
break
|
|
case 'opening':
|
|
canvas?.on('mouse:down', mouseEvent.openingMode.down)
|
|
canvas?.on('mouse:move', mouseEvent.openingMode.move)
|
|
canvas?.on('mouse:up', mouseEvent.openingMode.up)
|
|
|
|
break
|
|
case 'default':
|
|
canvas?.off('mouse:down')
|
|
break
|
|
}
|
|
}
|
|
|
|
const keyValid = () => {
|
|
if (points.current.length === 0) {
|
|
alert('시작점을 선택해주세요')
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
const drawCircleAndLine = (verticalLength, horizontalLength) => {
|
|
const circle = new fabric.Circle({
|
|
radius: 5,
|
|
fill: 'transparent', // 원 안을 비웁니다.
|
|
stroke: 'black', // 원 테두리 색상을 검은색으로 설정합니다.
|
|
left: points.current[points.current.length - 1].left + horizontalLength - 5,
|
|
top: points.current[points.current.length - 1].top + verticalLength - 5,
|
|
originX: 'center',
|
|
originY: 'center',
|
|
selectable: false,
|
|
})
|
|
|
|
historyPoints.current.push(circle)
|
|
points.current.push(circle)
|
|
canvas?.add(circle)
|
|
canvas?.renderAll()
|
|
|
|
// length 값이 숫자가 아닌 경우
|
|
if (isNaN(length) || Math.max(Math.abs(verticalLength), Math.abs(horizontalLength)) === 0) {
|
|
//마지막 추가 된 points 제거합니다.
|
|
|
|
const lastPoint = historyPoints.current[historyPoints.current.length - 1]
|
|
|
|
canvas?.remove(lastPoint)
|
|
|
|
historyPoints.current.pop()
|
|
points.current.pop()
|
|
return
|
|
}
|
|
|
|
const line = new QLine(
|
|
[points.current[0].left, points.current[0].top, points.current[0].left + horizontalLength, points.current[0].top + verticalLength],
|
|
{
|
|
stroke: 'black',
|
|
strokeWidth: 2,
|
|
selectable: false,
|
|
viewLengthText: true,
|
|
direction: getDirection(points.current[0], points.current[1]),
|
|
fontSize: fontSize,
|
|
},
|
|
)
|
|
|
|
pushHistoryLine(line)
|
|
|
|
// 라인의 끝에 점을 추가합니다.
|
|
const endPointCircle = new fabric.Circle({
|
|
radius: 1,
|
|
fill: 'transparent', // 원 안을 비웁니다.
|
|
stroke: 'black', // 원 테두리 색상을 검은색으로 설정합니다.
|
|
left: points.current[0].left + horizontalLength,
|
|
top: points.current[0].top + verticalLength,
|
|
originX: 'center',
|
|
originY: 'center',
|
|
selectable: false,
|
|
})
|
|
|
|
canvas?.add(endPointCircle)
|
|
|
|
historyPoints.current.push(endPointCircle)
|
|
|
|
points.current.forEach((point) => {
|
|
canvas?.remove(point)
|
|
})
|
|
|
|
setEndPoint(endPointCircle)
|
|
pointCount.current = pointCount.current + 1
|
|
|
|
points.current = [endPointCircle]
|
|
|
|
canvas?.renderAll()
|
|
}
|
|
|
|
const addSelectCreatedEvent = (e) => {
|
|
const target = e.selected[0]
|
|
|
|
if (target.name === 'helpPoint') {
|
|
canvas?.on('mouse:move', helpPointEvent.mouseMove)
|
|
}
|
|
}
|
|
|
|
const helpPointEvent = {
|
|
mouseMove: (e) => {
|
|
const target = canvas?.getActiveObject()
|
|
const pointer = canvas?.getPointer(e.e)
|
|
const point = { x: target.left + target.radius, y: target.top + target.radius }
|
|
const angle = Math.atan2(pointer.y - point.y, pointer.x - point.x)
|
|
const degree = fabric.util.radiansToDegrees(angle)
|
|
|
|
const min = [0, 45, 90, -0, -90, -45, 135, -135, 180, -180].reduce((prev, curr) => {
|
|
return Math.abs(curr - degree) < Math.abs(prev - degree) ? curr : prev
|
|
})
|
|
|
|
// Calculate the center point of the target object
|
|
const centerX = target.left + target.width / 2
|
|
const centerY = target.top + target.height / 2
|
|
|
|
const length = distanceBetweenPoints(point, { x: pointer.x, y: pointer.y })
|
|
|
|
// min의 각도와 pointer의 위치를 이용하여 새로운 점을 구한다.
|
|
const newPoint = {
|
|
x: centerX + length * Math.cos(fabric.util.degreesToRadians(min)),
|
|
y: centerY + length * Math.sin(fabric.util.degreesToRadians(min)),
|
|
}
|
|
|
|
const line = new fabric.Line([point.x, point.y, newPoint.x, newPoint.y], {
|
|
stroke: 'black',
|
|
strokeWidth: 1,
|
|
selectable: false,
|
|
name: 'beforeHelpLine',
|
|
helpPoint: target,
|
|
})
|
|
|
|
const helpLines = canvas?._objects.filter((obj) => obj.name === 'beforeHelpLine')
|
|
helpLines.forEach((item) => canvas?.remove(item))
|
|
|
|
canvas?.add(line)
|
|
},
|
|
}
|
|
|
|
const addSelectClearedEvent = (e) => {
|
|
const target = e.deselected[0]
|
|
|
|
if (target.name === 'helpPoint') {
|
|
const beforeHelpLines = canvas?._objects.filter((obj) => obj.name === 'beforeHelpLine' && obj.helpPoint === target)
|
|
|
|
const helpLines = canvas?._objects.filter((obj) => obj.name === 'helpLine' && obj.helpPoint === target)
|
|
|
|
beforeHelpLines.forEach((item) => canvas?.remove(item))
|
|
helpLines.forEach((item) => canvas?.remove(item))
|
|
|
|
const newPoint = { x: beforeHelpLines[0].x2, y: beforeHelpLines[0].y2 }
|
|
|
|
const helpLine = new fabric.Line([target.left + target.radius, target.top + target.radius, newPoint.x, newPoint.y], {
|
|
stroke: 'red',
|
|
strokeWidth: 1,
|
|
selectable: false,
|
|
name: 'helpLine',
|
|
helpPoint: target,
|
|
})
|
|
|
|
canvas?.add(helpLine)
|
|
canvas?.renderAll()
|
|
|
|
canvas?.off('mouse:move', helpPointEvent.mouseMove)
|
|
}
|
|
}
|
|
|
|
const mouseAndkeyboardEventClear = () => {
|
|
canvas?.off('mouse:down')
|
|
canvas?.off('mouse:move')
|
|
canvas?.off('mouse:up')
|
|
canvas?.off('mouse:out')
|
|
|
|
Object.keys(keyboardEvent).forEach((key) => {
|
|
document.removeEventListener('keydown', keyboardEvent[key])
|
|
})
|
|
}
|
|
|
|
const keyboardEvent = {
|
|
// rerendering을 막기 위해 useCallback 사용
|
|
editMode: useCallback(
|
|
(e) => {
|
|
e.preventDefault()
|
|
switch (e.key) {
|
|
case 'ArrowDown': {
|
|
if (!keyValid()) {
|
|
return
|
|
}
|
|
const verticalLength = Number(prompt('길이를 입력하세요:'))
|
|
const horizontalLength = 0
|
|
|
|
drawCircleAndLine(verticalLength, horizontalLength)
|
|
|
|
break
|
|
}
|
|
case 'ArrowUp': {
|
|
if (!keyValid()) {
|
|
return
|
|
}
|
|
const verticalLength = -Number(prompt('길이를 입력하세요:'))
|
|
const horizontalLength = 0
|
|
|
|
drawCircleAndLine(verticalLength, horizontalLength)
|
|
|
|
break
|
|
}
|
|
case 'ArrowLeft': {
|
|
if (!keyValid()) {
|
|
return
|
|
}
|
|
const verticalLength = 0
|
|
const horizontalLength = -Number(prompt('길이를 입력하세요:'))
|
|
|
|
drawCircleAndLine(verticalLength, horizontalLength)
|
|
|
|
break
|
|
}
|
|
case 'ArrowRight': {
|
|
if (!keyValid()) {
|
|
return
|
|
}
|
|
|
|
const verticalLength = 0
|
|
const horizontalLength = Number(prompt('길이를 입력하세요:'))
|
|
|
|
drawCircleAndLine(verticalLength, horizontalLength)
|
|
|
|
break
|
|
}
|
|
|
|
case 'Enter': {
|
|
const result = prompt('입력하세요 (a(A패턴), b(B패턴), t(지붕), e(변별))')
|
|
|
|
switch (result) {
|
|
case 'a':
|
|
applyTemplateA()
|
|
break
|
|
case 'b':
|
|
applyTemplateB()
|
|
break
|
|
case 't':
|
|
templateMode()
|
|
break
|
|
case 'e':
|
|
templateSideMode()
|
|
}
|
|
}
|
|
}
|
|
},
|
|
[canvas],
|
|
),
|
|
}
|
|
|
|
const changeMode = (canvas, mode) => {
|
|
mouseAndkeyboardEventClear()
|
|
addCommonMouseEvent()
|
|
setMode(mode)
|
|
|
|
// mode별 이벤트 변경
|
|
|
|
changeMouseEvent(mode)
|
|
changeKeyboardEvent(mode)
|
|
|
|
switch (mode) {
|
|
case 'template':
|
|
templateMode()
|
|
break
|
|
case 'patterna':
|
|
applyTemplateA()
|
|
break
|
|
case 'patternb':
|
|
applyTemplateB()
|
|
break
|
|
case 'roofPattern':
|
|
makeRoofPatternPolygon()
|
|
break
|
|
case 'roofTrestle':
|
|
makeRoofTrestle()
|
|
break
|
|
case 'fillCells':
|
|
makeRoofFillCells()
|
|
break
|
|
case 'cellPowercon':
|
|
makeCellPowercon()
|
|
break
|
|
case 'drawHelpLine':
|
|
drawHelpLineMode()
|
|
break
|
|
case 'default':
|
|
clearEditMode()
|
|
break
|
|
}
|
|
}
|
|
|
|
// 모든 모드에서 사용되는 공통 이벤트 추가
|
|
const addCommonMouseEvent = () => {
|
|
canvas?.on('mouse:move', drawMouseLines)
|
|
canvas?.on('mouse:out', removeMouseLines)
|
|
}
|
|
|
|
const changeKeyboardEvent = (mode) => {
|
|
if (mode === Mode.EDIT) {
|
|
switch (mode) {
|
|
case 'edit':
|
|
document.addEventListener('keydown', keyboardEvent.editMode)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
const mouseEvent = {
|
|
drawLineModeLeftClick: (options) => {
|
|
if (mode !== Mode.DRAW_LINE) {
|
|
return
|
|
}
|
|
const pointer = canvas?.getPointer(options.e)
|
|
|
|
const line = new QLine(
|
|
[pointer.x, 0, pointer.x, canvasSize.vertical], // y축에 1자 선을 그립니다.
|
|
{
|
|
stroke: 'gray',
|
|
strokeWidth: 1,
|
|
selectable: true,
|
|
lockMovementX: true,
|
|
lockMovementY: true,
|
|
lockRotation: true,
|
|
lockScalingX: true,
|
|
lockScalingY: true,
|
|
name: 'guideLine',
|
|
direction: 'vertical',
|
|
},
|
|
)
|
|
|
|
canvas?.add(line)
|
|
canvas?.renderAll()
|
|
|
|
const newVerticalLineArray = [...vertGuideLines]
|
|
newVerticalLineArray.push(line)
|
|
|
|
setVertGuideLines(newVerticalLineArray)
|
|
},
|
|
drawLineModeRightClick: useCallback(
|
|
(options) => {
|
|
document.removeEventListener('contextmenu', mouseEvent.drawLineModeRightClick)
|
|
if (mode !== Mode.DRAW_LINE) {
|
|
return
|
|
}
|
|
const line = new fabric.Line(
|
|
[0, options.offsetY, canvasSize.horizontal, options.offsetY], // y축에 1자 선을 그립니다.
|
|
{
|
|
stroke: 'gray',
|
|
strokeWidth: 1,
|
|
selectable: true,
|
|
lockMovementX: true,
|
|
lockMovementY: true,
|
|
lockRotation: true,
|
|
lockScalingX: true,
|
|
lockScalingY: true,
|
|
name: 'guideLine',
|
|
direction: 'horizontal',
|
|
},
|
|
)
|
|
|
|
canvas?.add(line)
|
|
canvas?.renderAll()
|
|
|
|
const newHorizontalLineArray = [...horiGuideLines]
|
|
newHorizontalLineArray.push(line)
|
|
setHoriGuideLines(newHorizontalLineArray)
|
|
},
|
|
[canvas, mode, horiGuideLines],
|
|
),
|
|
editMode: (options) => {
|
|
if (mode !== Mode.EDIT) {
|
|
return
|
|
}
|
|
let pointer = canvas?.getPointer(options.e)
|
|
|
|
if (getInterSectPointByMouseLine()) {
|
|
pointer = getInterSectPointByMouseLine()
|
|
}
|
|
|
|
const circle = new fabric.Circle({
|
|
radius: 5,
|
|
fill: 'transparent', // 원 안을 비웁니다.
|
|
stroke: 'red', // 원 테두리 색상을 검은색으로 설정합니다.
|
|
left: pointer.x,
|
|
top: pointer.y,
|
|
x: pointer.x,
|
|
y: pointer.y,
|
|
originX: 'center',
|
|
originY: 'center',
|
|
selectable: false,
|
|
})
|
|
if (!startPoint.current) {
|
|
startPoint.current = circle
|
|
pointCount.current = pointCount.current + 1
|
|
}
|
|
let prevEndPoint
|
|
|
|
setEndPoint((prev) => {
|
|
prevEndPoint = prev
|
|
return circle
|
|
})
|
|
|
|
historyPoints.current.push(circle)
|
|
points.current.push(circle)
|
|
canvas?.add(circle)
|
|
|
|
if (points.current.length === 2) {
|
|
if (guideLineMode || guideDotMode) {
|
|
const vector = {
|
|
x: points.current[1].left - points.current[0].left,
|
|
y: points.current[1].top - points.current[0].top,
|
|
}
|
|
const slope = Math.abs(vector.y / vector.x) // 기울기 계산
|
|
|
|
let scaledVector
|
|
|
|
if (slope >= 1) {
|
|
// 기울기가 1 이상이면 x축 방향으로 그림
|
|
scaledVector = {
|
|
x: 0,
|
|
y: pointer.y - prevEndPoint?.top,
|
|
}
|
|
} else {
|
|
// 기울기가 1 미만이면 y축 방향으로 그림
|
|
scaledVector = {
|
|
x: pointer.x - prevEndPoint?.left,
|
|
y: 0,
|
|
}
|
|
}
|
|
|
|
const verticalLength = scaledVector.y
|
|
const horizontalLength = scaledVector.x
|
|
|
|
drawCircleAndLine(verticalLength, horizontalLength)
|
|
canvas?.renderAll()
|
|
return
|
|
}
|
|
const length = Number(prompt('길이를 입력하세요:'))
|
|
|
|
// length 값이 숫자가 아닌 경우
|
|
if (isNaN(length) || length === 0) {
|
|
//마지막 추가 된 points 제거합니다.
|
|
|
|
const lastPoint = historyPoints.current[historyPoints.current.length - 1]
|
|
|
|
canvas?.remove(lastPoint)
|
|
setEndPoint(prevEndPoint)
|
|
historyPoints.current.pop()
|
|
points.current.pop()
|
|
return
|
|
}
|
|
|
|
if (length) {
|
|
const vector = {
|
|
x: points.current[1].left - points.current[0].left,
|
|
y: points.current[1].top - points.current[0].top,
|
|
}
|
|
const slope = Math.abs(vector.y / vector.x) // 기울기 계산
|
|
|
|
let scaledVector
|
|
if (slope >= 1) {
|
|
// 기울기가 1 이상이면 x축 방향으로 그림
|
|
scaledVector = {
|
|
x: 0,
|
|
y: vector.y >= 0 ? Number(length) : -Number(length),
|
|
}
|
|
} else {
|
|
// 기울기가 1 미만이면 y축 방향으로 그림
|
|
scaledVector = {
|
|
x: vector.x >= 0 ? Number(length) : -Number(length),
|
|
y: 0,
|
|
}
|
|
}
|
|
|
|
const verticalLength = scaledVector.y
|
|
const horizontalLength = scaledVector.x
|
|
|
|
drawCircleAndLine(verticalLength, horizontalLength)
|
|
}
|
|
}
|
|
|
|
canvas?.renderAll()
|
|
},
|
|
|
|
textboxMode: (options) => {
|
|
if (mode !== Mode.TEXTBOX) return
|
|
if (canvas?.getActiveObject()?.type === 'textbox') return
|
|
const pointer = canvas?.getPointer(options.e)
|
|
|
|
const textbox = new fabric.Textbox('텍스트를 입력하세요', {
|
|
left: pointer.x,
|
|
top: pointer.y,
|
|
width: 150, // 텍스트박스의 너비를 설정합니다.
|
|
fontSize: fontSize, // 텍스트의 크기를 설정합니다.
|
|
})
|
|
|
|
canvas?.add(textbox)
|
|
canvas?.setActiveObject(textbox) // 생성된 텍스트박스를 활성 객체로 설정합니다.
|
|
canvas?.renderAll()
|
|
// textbox가 active가 풀린 경우 editing mode로 변경
|
|
textbox?.on('editing:exited', function () {
|
|
changeMode(canvas, Mode.EDIT)
|
|
})
|
|
},
|
|
drawRectMode: (o) => {
|
|
if (mode !== Mode.DRAW_RECT) return
|
|
let rect, isDown, origX, origY
|
|
isDown = true
|
|
const pointer = canvas.getPointer(o.e)
|
|
origX = pointer.x
|
|
origY = pointer.y
|
|
rect = new fabric.Rect({
|
|
left: origX,
|
|
top: origY,
|
|
originX: 'left',
|
|
originY: 'top',
|
|
width: pointer.x - origX,
|
|
height: pointer.y - origY,
|
|
angle: 0,
|
|
fill: 'white',
|
|
stroke: 'black',
|
|
transparentCorners: false,
|
|
name: 'dormer',
|
|
})
|
|
canvas.add(rect)
|
|
|
|
canvas.on('mouse:move', function (e) {
|
|
if (!isDown) return
|
|
const pointer = canvas.getPointer(e.e)
|
|
if (origX > pointer.x) {
|
|
rect.set({ left: Math.abs(pointer.x) })
|
|
}
|
|
if (origY > pointer.y) {
|
|
rect.set({ top: Math.abs(pointer.y) })
|
|
}
|
|
|
|
rect.set({ width: Math.abs(origX - pointer.x) })
|
|
rect.set({ height: Math.abs(origY - pointer.y) })
|
|
})
|
|
|
|
canvas.on('mouse:up', function (o) {
|
|
isDown = false
|
|
canvas.off('mouse:move')
|
|
canvas.off('mouse:up')
|
|
setMode(Mode.DEFAULT)
|
|
})
|
|
},
|
|
// 흡착점 추가
|
|
adsorptionPoint: (o) => {
|
|
if (mode !== Mode.ADSORPTION_POINT) return
|
|
const pointer = canvas.getPointer(o.e)
|
|
let newX = pointer.x
|
|
let newY = pointer.y
|
|
|
|
if (getInterSectPointByMouseLine()) {
|
|
const interSectPoint = getInterSectPointByMouseLine()
|
|
newX = interSectPoint.x
|
|
newY = interSectPoint.y
|
|
}
|
|
|
|
const circle = new fabric.Circle({
|
|
radius: 5,
|
|
fill: 'transparent', // 원 안을 비웁니다.
|
|
stroke: 'black', // 원 테두리 색상을 검은색으로 설정합니다.
|
|
left: newX,
|
|
top: newY,
|
|
originX: 'center',
|
|
originY: 'center',
|
|
x: newX - 5,
|
|
y: newY - 5,
|
|
selectable: false,
|
|
name: 'adsorptionPoint',
|
|
})
|
|
|
|
canvas.add(circle)
|
|
canvas.renderAll()
|
|
},
|
|
//면 형상 배치 모드
|
|
surfaceShapeMode: (o) => {},
|
|
// 그림자 모드
|
|
shadowMode: {
|
|
// rect: null,
|
|
isDown: false,
|
|
origX: 0,
|
|
origY: 0,
|
|
down: (o) => {
|
|
if (mode !== Mode.SHADOW) return
|
|
mouseEvent.shadowMode.isDown = true
|
|
const pointer = canvas.getPointer(o.e)
|
|
mouseEvent.shadowMode.origX = pointer.x
|
|
mouseEvent.shadowMode.origY = pointer.y
|
|
mouseEvent.shadowMode.rect = new fabric.Rect({
|
|
fill: 'grey',
|
|
left: mouseEvent.shadowMode.origX,
|
|
top: mouseEvent.shadowMode.origY,
|
|
originX: 'left',
|
|
originY: 'top',
|
|
opacity: 0.3,
|
|
width: 0,
|
|
height: 0,
|
|
angle: 0,
|
|
transparentCorners: false,
|
|
})
|
|
canvas.add(mouseEvent.shadowMode.rect)
|
|
},
|
|
move: (e) => {
|
|
if (!mouseEvent.shadowMode.isDown) return
|
|
const pointer = canvas.getPointer(e.e)
|
|
if (mouseEvent.shadowMode.origX > pointer.x) {
|
|
mouseEvent.shadowMode.rect.set({ left: Math.abs(pointer.x) })
|
|
}
|
|
if (mouseEvent.shadowMode.origY > pointer.y) {
|
|
mouseEvent.shadowMode.rect.set({ top: Math.abs(pointer.y) })
|
|
}
|
|
|
|
mouseEvent.shadowMode.rect.set({ width: Math.abs(mouseEvent.shadowMode.origX - pointer.x) })
|
|
mouseEvent.shadowMode.rect.set({ height: Math.abs(mouseEvent.shadowMode.origY - pointer.y) })
|
|
},
|
|
up: (o) => {
|
|
mouseEvent.shadowMode.isDown = false
|
|
setMode(Mode.DEFAULT)
|
|
},
|
|
},
|
|
openingMode: {
|
|
rect: null,
|
|
isDown: false,
|
|
origX: 0,
|
|
origY: 0,
|
|
down: (o) => {
|
|
if (mode !== Mode.OPENING) return
|
|
const roofs = canvas?._objects.filter((obj) => obj.name === 'roof')
|
|
if (roofs.length === 0) {
|
|
alert('지붕을 먼저 그려주세요')
|
|
setMode(Mode.DEFAULT)
|
|
return
|
|
}
|
|
const pointer = canvas.getPointer(o.e)
|
|
let selectRoof = null
|
|
roofs.forEach((roof) => {
|
|
if (roof.inPolygon({ x: pointer.x, y: pointer.y })) {
|
|
selectRoof = roof
|
|
}
|
|
})
|
|
if (!selectRoof) {
|
|
alert('지붕 내부에만 생성 가능합니다.')
|
|
return
|
|
}
|
|
mouseEvent.openingMode.origX = pointer.x
|
|
mouseEvent.openingMode.origY = pointer.y
|
|
if (objectPlacementMode.inputType === INPUT_TYPE.FREE) {
|
|
mouseEvent.openingMode.isDown = true
|
|
|
|
mouseEvent.openingMode.rect = new fabric.Rect({
|
|
fill: 'white',
|
|
stroke: 'black',
|
|
strokeWidth: 1,
|
|
left: mouseEvent.openingMode.origX,
|
|
top: mouseEvent.openingMode.origY,
|
|
originX: 'left',
|
|
originY: 'top',
|
|
width: pointer.x - mouseEvent.openingMode.origX,
|
|
height: pointer.y - mouseEvent.openingMode.origY,
|
|
})
|
|
canvas.add(mouseEvent.openingMode.rect)
|
|
} else if (objectPlacementMode.inputType === INPUT_TYPE.DIMENSION) {
|
|
mouseEvent.openingMode.rect = new fabric.Rect({
|
|
fill: 'white',
|
|
stroke: 'black',
|
|
strokeWidth: 1,
|
|
left: mouseEvent.openingMode.origX,
|
|
top: mouseEvent.openingMode.origY,
|
|
originX: 'left',
|
|
originY: 'top',
|
|
width: Number(objectPlacementMode.width),
|
|
height: Number(objectPlacementMode.height),
|
|
})
|
|
canvas.add(mouseEvent.openingMode.rect)
|
|
canvas.off('mouse:move')
|
|
}
|
|
},
|
|
move: (e) => {
|
|
if (!mouseEvent.openingMode.isDown) return
|
|
const pointer = canvas.getPointer(e.e)
|
|
if (mouseEvent.openingMode.origX > pointer.x) {
|
|
mouseEvent.openingMode.rect.set({ left: Math.abs(pointer.x) })
|
|
}
|
|
if (mouseEvent.openingMode.origY > pointer.y) {
|
|
mouseEvent.openingMode.rect.set({ top: Math.abs(pointer.y) })
|
|
}
|
|
|
|
mouseEvent.openingMode.rect.set({ width: Math.abs(mouseEvent.openingMode.origX - pointer.x) })
|
|
mouseEvent.openingMode.rect.set({ height: Math.abs(mouseEvent.openingMode.origY - pointer.y) })
|
|
},
|
|
up: (o) => {
|
|
mouseEvent.openingMode.isDown = false
|
|
|
|
const { areaBoundary } = objectPlacementMode
|
|
|
|
//roof의 내부에 있는지 확인
|
|
if (!checkInsideRoof(mouseEvent.openingMode.rect)) {
|
|
setMode(Mode.DEFAULT)
|
|
}
|
|
|
|
// 영역 교차인지 확인
|
|
if (!areaBoundary) {
|
|
const isCross = checkCrossAreaBoundary(mouseEvent.openingMode.rect)
|
|
if (isCross) {
|
|
alert('영역이 교차되었습니다.')
|
|
canvas.remove(mouseEvent.openingMode.rect)
|
|
}
|
|
}
|
|
|
|
mouseEvent.openingMode.rect.set({ name: 'opening' })
|
|
setMode(Mode.DEFAULT)
|
|
},
|
|
},
|
|
}
|
|
|
|
const checkCrossAreaBoundary = (rect) => {
|
|
const openings = canvas?._objects.filter((obj) => obj.name === 'opening')
|
|
if (openings.length === 0) {
|
|
return false
|
|
}
|
|
|
|
const rectPoints = [
|
|
{ x: rect.left, y: rect.top },
|
|
{ x: rect.left, y: rect.top + rect.height },
|
|
{ x: rect.left + rect.width, y: rect.top + rect.height },
|
|
{ x: rect.left + rect.width, y: rect.top },
|
|
]
|
|
|
|
const rect1Corners = {
|
|
minX: Math.min(...rectPoints.map((point) => point.x)),
|
|
maxX: Math.max(...rectPoints.map((point) => point.x)),
|
|
minY: Math.min(...rectPoints.map((point) => point.y)),
|
|
maxY: Math.max(...rectPoints.map((point) => point.y)),
|
|
}
|
|
let isCross = true
|
|
for (let i = 0; i < openings.length; i++) {
|
|
if (i !== 0 && isCross) {
|
|
break
|
|
}
|
|
const rect2 = openings[i]
|
|
const rect2Points = [
|
|
{ x: rect2.left, y: rect2.top },
|
|
{ x: rect2.left, y: rect2.top + rect2.height },
|
|
{ x: rect2.left + rect2.width, y: rect2.top + rect2.height },
|
|
{ x: rect2.left + rect2.width, y: rect2.top },
|
|
]
|
|
|
|
const rect2Corners = {
|
|
minX: Math.min(...rect2Points.map((point) => point.x)),
|
|
maxX: Math.max(...rect2Points.map((point) => point.x)),
|
|
minY: Math.min(...rect2Points.map((point) => point.y)),
|
|
maxY: Math.max(...rect2Points.map((point) => point.y)),
|
|
}
|
|
|
|
// Check if one rectangle is to the left of the other
|
|
if (
|
|
rect1Corners.maxX < rect2Corners.minX ||
|
|
rect2Corners.maxX < rect1Corners.minX ||
|
|
rect1Corners.maxY < rect2Corners.minY ||
|
|
rect2Corners.maxY < rect1Corners.minY
|
|
) {
|
|
isCross = false
|
|
continue
|
|
} else {
|
|
isCross = true
|
|
break
|
|
}
|
|
}
|
|
|
|
return isCross
|
|
}
|
|
|
|
const checkInsideRoof = (rect) => {
|
|
let result = true
|
|
const roofs = canvas?._objects.filter((obj) => obj.name === 'roof')
|
|
if (roofs.length === 0) {
|
|
alert('지붕을 먼저 그려주세요')
|
|
canvas?.remove(rect)
|
|
return false
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
const getInterSectPointByMouseLine = () => {
|
|
const mouseLines = canvas?._objects.filter((obj) => obj.name === 'mouseLine')
|
|
if (mouseLines.length !== 2) {
|
|
return null
|
|
}
|
|
return calculateIntersection(mouseLines[0], mouseLines[1])
|
|
}
|
|
|
|
const pushHistoryLine = (line) => {
|
|
if (historyLines.current.length > 0 && historyLines.current[historyLines.current.length - 1].direction === line.direction) {
|
|
// 같은 방향의 선이 두 번 연속으로 그려지면 이전 선을 제거하고, 새로운 선과 merge한다.
|
|
|
|
const lastLine = historyLines.current.pop()
|
|
canvas?.remove(lastLine)
|
|
|
|
const mergedLine = new QLine([lastLine.x1, lastLine.y1, line.x2, line.y2], {
|
|
stroke: 'black',
|
|
strokeWidth: 2,
|
|
selectable: false,
|
|
viewLengthText: true,
|
|
direction: lastLine.direction,
|
|
fontSize: fontSize,
|
|
})
|
|
historyLines.current.push(mergedLine)
|
|
canvas?.add(mergedLine)
|
|
} else {
|
|
historyLines.current.push(line)
|
|
canvas?.add(line)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 마우스로 그린 점 기준으로 외벽선을 완성시켜준다.
|
|
* makePolygon 함수에 포함되어있던 내용을 다른 템플릿 적용에서도 사용할수 있도록 함수로 대체
|
|
*/
|
|
const drawWallPolygon = (sort = true) => {
|
|
const firstPoint = historyPoints.current[0]
|
|
const lastPoint = historyPoints.current[historyPoints.current.length - 1]
|
|
historyPoints.current.forEach((point) => {
|
|
canvas?.remove(point)
|
|
})
|
|
drawLineWithLength(lastPoint, firstPoint)
|
|
points.current = []
|
|
historyPoints.current = []
|
|
|
|
const wall = makePolygon(null, sort)
|
|
wall.name = 'wall'
|
|
|
|
return wall
|
|
}
|
|
|
|
const templateMode = () => {
|
|
changeMode(canvas, Mode.EDIT)
|
|
|
|
if (historyPoints.current.length >= 4) {
|
|
const wall = drawWallPolygon()
|
|
setWall(wall)
|
|
handleOuterlinesTest2(wall)
|
|
setTemplateType(1)
|
|
}
|
|
}
|
|
|
|
const templateSideMode = () => {
|
|
if (historyPoints.current.length >= 4) {
|
|
const firstPoint = historyPoints.current[0]
|
|
const lastPoint = historyPoints.current[historyPoints.current.length - 1]
|
|
historyPoints.current.forEach((point) => {
|
|
canvas?.remove(point)
|
|
})
|
|
drawLineWithLength(lastPoint, firstPoint)
|
|
|
|
// 캔버스에서 모든 라인 객체를 찾습니다.
|
|
const lines = historyLines.current
|
|
historyLines.current = []
|
|
|
|
// 각 라인의 시작점과 끝점을 사용하여 다각형의 점 배열을 생성합니다.
|
|
const points = lines.map((line) => ({ x: line.x1, y: line.y1 }))
|
|
|
|
// 모든 라인 객체를 캔버스에서 제거합니다.
|
|
lines.forEach((line) => {
|
|
canvas?.remove(line)
|
|
})
|
|
|
|
// 점 배열을 사용하여 새로운 다각형 객체를 생성합니다.
|
|
const polygon = new QPolygon(
|
|
points,
|
|
{
|
|
stroke: 'black',
|
|
fill: 'transparent',
|
|
viewLengthText: true,
|
|
fontSize: 15,
|
|
selectable: true,
|
|
},
|
|
canvas,
|
|
)
|
|
|
|
// 새로운 다각형 객체를 캔버스에 추가합니다.
|
|
canvas.add(polygon)
|
|
|
|
console.log('polygon', polygon)
|
|
|
|
changeMode(canvas, Mode.DEFAULT)
|
|
return polygon
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 두 점을 연결하는 선과 길이를 그립니다.
|
|
* a : 시작점, b : 끝점
|
|
*/
|
|
const drawLineWithLength = (a, b) => {
|
|
if (!a || !b) {
|
|
return
|
|
}
|
|
const line = new QLine([a.left, a.top, b.left, b.top], {
|
|
stroke: 'black',
|
|
strokeWidth: 2,
|
|
selectable: false,
|
|
viewLengthText: true,
|
|
direction: getDirection(a, b),
|
|
fontSize: fontSize,
|
|
})
|
|
pushHistoryLine(line)
|
|
|
|
canvas?.renderAll()
|
|
}
|
|
|
|
const makePolygon = (otherLines, sort = true) => {
|
|
// 캔버스에서 모든 라인 객체를 찾습니다.
|
|
const lines = otherLines || historyLines.current
|
|
historyLines.current = []
|
|
|
|
// 각 라인의 시작점과 끝점을 사용하여 다각형의 점 배열을 생성합니다.
|
|
const points = lines.map((line) => ({ x: line.x1, y: line.y1 }))
|
|
|
|
// 모든 라인 객체를 캔버스에서 제거합니다.
|
|
lines.forEach((line) => {
|
|
canvas?.remove(line)
|
|
})
|
|
|
|
// 점 배열을 사용하여 새로운 다각형 객체를 생성합니다.
|
|
const polygon = new QPolygon(
|
|
points,
|
|
{
|
|
stroke: '#1083E3',
|
|
strokeWidth: 2,
|
|
fill: 'transparent',
|
|
viewLengthText: true,
|
|
fontSize: fontSize,
|
|
sort: sort,
|
|
selectable: false,
|
|
originX: 'center',
|
|
originY: 'center',
|
|
},
|
|
canvas,
|
|
)
|
|
|
|
// 새로운 다각형 객체를 캔버스에 추가합니다.
|
|
canvas.add(polygon)
|
|
|
|
// 캔버스를 다시 그립니다.
|
|
// polygon.fillCell()
|
|
canvas?.renderAll()
|
|
// polygon.setViewLengthText(false)
|
|
setMode(Mode.DEFAULT)
|
|
|
|
return polygon
|
|
}
|
|
|
|
/**
|
|
* 해당 캔버스를 비운다.
|
|
*/
|
|
const handleClear = () => {
|
|
canvas?.clear()
|
|
startPoint.current = null
|
|
setEndPoint(null)
|
|
pointCount.current = 0
|
|
setTemplateType(0)
|
|
points.current = []
|
|
historyPoints.current = []
|
|
historyLines.current = []
|
|
setRoof(null)
|
|
setWall(null)
|
|
|
|
setSelectedCellRoofArray([]) //셀 그린거 삭제
|
|
}
|
|
|
|
const clearEditMode = () => {
|
|
startPoint.current = null
|
|
setEndPoint(null)
|
|
pointCount.current = 0
|
|
points.current = []
|
|
historyPoints.current = []
|
|
historyLines.current = []
|
|
}
|
|
|
|
const zoomIn = () => {
|
|
if (canvas.getZoom() + 0.1 > 1.6) {
|
|
return
|
|
}
|
|
canvas?.setZoom(canvas.getZoom() + 0.1)
|
|
setZoom(Math.round(zoom + 10))
|
|
}
|
|
|
|
const zoomOut = () => {
|
|
if (canvas.getZoom() - 0.1 < 0.5) {
|
|
return
|
|
}
|
|
canvas?.setZoom(canvas.getZoom() - 0.1)
|
|
setZoom(Math.ceil(zoom - 10))
|
|
}
|
|
|
|
/**
|
|
*벽 지붕 외곽선 생성
|
|
*/
|
|
const handleOuterlinesTest = (polygon, offsetInputX, offsetInputY = 0) => {
|
|
let offsetPoints = []
|
|
const originalMax = 71
|
|
const transformedMax = 100
|
|
|
|
offsetInputY = offsetInputY !== 0 ? offsetInputY : offsetInputX
|
|
|
|
const offsetX = (offsetInputX / transformedMax) * originalMax * 2
|
|
const offsetY = (offsetInputY / transformedMax) * originalMax * 2
|
|
|
|
const sortedIndex = getStartIndex(polygon.lines)
|
|
let tmpArraySorted = rearrangeArray(polygon.lines, sortedIndex)
|
|
|
|
if (tmpArraySorted[0].direction === 'right') {
|
|
//시계방향
|
|
tmpArraySorted = tmpArraySorted.reverse() //그럼 배열을 거꾸로 만들어서 무조건 반시계방향으로 배열 보정
|
|
}
|
|
|
|
setSortedArray(tmpArraySorted) //recoil에 넣음
|
|
|
|
const points = tmpArraySorted.map((line) => ({
|
|
x: line.x1,
|
|
y: line.y1,
|
|
}))
|
|
|
|
for (var i = 0; i < points.length; i++) {
|
|
var prev = points[(i - 1 + points.length) % points.length]
|
|
var current = points[i]
|
|
var next = points[(i + 1) % points.length]
|
|
|
|
// 두 벡터 계산 (prev -> current, current -> next)
|
|
var vector1 = { x: current.x - prev.x, y: current.y - prev.y }
|
|
var vector2 = { x: next.x - current.x, y: next.y - current.y }
|
|
|
|
// 벡터의 길이 계산
|
|
var length1 = Math.sqrt(vector1.x * vector1.x + vector1.y * vector1.y)
|
|
var length2 = Math.sqrt(vector2.x * vector2.x + vector2.y * vector2.y)
|
|
|
|
// 벡터를 단위 벡터로 정규화
|
|
var unitVector1 = { x: vector1.x / length1, y: vector1.y / length1 }
|
|
var unitVector2 = { x: vector2.x / length2, y: vector2.y / length2 }
|
|
|
|
// 법선 벡터 계산 (왼쪽 방향)
|
|
var normal1 = { x: -unitVector1.y, y: unitVector1.x }
|
|
var normal2 = { x: -unitVector2.y, y: unitVector2.x }
|
|
|
|
// 법선 벡터 평균 계산
|
|
var averageNormal = {
|
|
x: (normal1.x + normal2.x) / 2,
|
|
y: (normal1.y + normal2.y) / 2,
|
|
}
|
|
|
|
// 평균 법선 벡터를 단위 벡터로 정규화
|
|
var lengthNormal = Math.sqrt(averageNormal.x * averageNormal.x + averageNormal.y * averageNormal.y)
|
|
var unitNormal = {
|
|
x: averageNormal.x / lengthNormal,
|
|
y: averageNormal.y / lengthNormal,
|
|
}
|
|
|
|
// 오프셋 적용
|
|
var offsetPoint = {
|
|
x1: current.x + unitNormal.x * offsetX,
|
|
y1: current.y + unitNormal.y * offsetY,
|
|
}
|
|
|
|
offsetPoints.push(offsetPoint)
|
|
}
|
|
|
|
return makePolygon(offsetPoints, false)
|
|
}
|
|
|
|
/**
|
|
*벽 지붕 외곽선 생성 polygon을 입력받아 만들기
|
|
*/
|
|
const handleOuterlinesTest2 = (polygon, offset = 50) => {
|
|
// TODO [ljyoung] : offset 입력 처리 후 제거 해야함.
|
|
polygon.lines.forEach((line, index) => {
|
|
line.attributes = {
|
|
type: LINE_TYPE.WALLLINE.EAVES,
|
|
offset: 50,
|
|
width: 50,
|
|
pitch: 4,
|
|
sleeve: true,
|
|
}
|
|
/*if (index % 2 !== 0) {
|
|
line.attributes = {
|
|
type: LINE_TYPE.WALLLINE.GABLE,
|
|
offset: 30,
|
|
width: 50,
|
|
pitch: 4,
|
|
sleeve: true,
|
|
}
|
|
} else {
|
|
line.attributes = {
|
|
type: LINE_TYPE.WALLLINE.EAVES,
|
|
offset: 50,
|
|
width: 50,
|
|
pitch: 4,
|
|
sleeve: true,
|
|
}
|
|
}*/
|
|
/*if (index === polygon.lines.length - 1) {
|
|
line.attributes = {
|
|
type: LINE_TYPE.WALLLINE.GABLE,
|
|
offset: 30,
|
|
width: 50,
|
|
pitch: 4,
|
|
sleeve: true,
|
|
}
|
|
} else {
|
|
line.attributes = {
|
|
type: LINE_TYPE.WALLLINE.EAVES,
|
|
offset: 50,
|
|
width: 50,
|
|
pitch: 4,
|
|
sleeve: true,
|
|
}
|
|
}*/
|
|
})
|
|
|
|
const roof = drawRoofPolygon(polygon) //지붕 그리기
|
|
roof.drawHelpLine(settingModalFirstOptions)
|
|
// roof.divideLine()
|
|
}
|
|
|
|
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 createRoofPolygon(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,
|
|
}
|
|
}
|
|
|
|
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 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 // lines are parallel or coincident
|
|
}
|
|
|
|
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
|
|
|
|
// Edges are not intersecting but the lines defined by them are
|
|
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,
|
|
}
|
|
}
|
|
|
|
/**
|
|
* polygon 을 기준으로 margin 된 polygon 을 작성한다.
|
|
* @param polygon
|
|
* @param lines
|
|
* @param arcSegments
|
|
* @returns {{vertices, edges: *[], minX: *, minY: *, maxX: *, maxY: *}}
|
|
*/
|
|
function createMarginPolygon(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 offset = 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 = createRoofPolygon(vertices)
|
|
marginPolygon.offsetEdges = offsetEdges
|
|
return marginPolygon
|
|
}
|
|
|
|
/**
|
|
* polygon 을 기준으로 padding 된 polygon 을 작성한다.
|
|
* @param polygon
|
|
* @param lines
|
|
* @param arcSegments
|
|
* @returns {{vertices, edges: *[], minX: *, minY: *, maxX: *, maxY: *}}
|
|
*/
|
|
function createPaddingPolygon(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 offset = 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 = createRoofPolygon(vertices)
|
|
paddingPolygon.offsetEdges = offsetEdges
|
|
return paddingPolygon
|
|
}
|
|
|
|
/**
|
|
* 외벽선을 기준으로 지붕을 그린다.
|
|
* @param wall
|
|
* @returns {*}
|
|
*/
|
|
const drawRoofPolygon = (wall) => {
|
|
//외벽선의 순서를 최좌측 선을 기준으로 반시계방향으로 정리한다. 지붕선과 순서를 맞추기 위함.
|
|
const startLine = wall.lines
|
|
.filter((line) => line.x1 === Math.min(...wall.lines.map((line) => line.x1)))
|
|
.reduce((prev, current) => {
|
|
return prev.y1 < current.y1 ? prev : current
|
|
})
|
|
|
|
const beforeLine = [],
|
|
afterLine = []
|
|
let startIndex = wall.lines.findIndex((line) => line === startLine)
|
|
wall.lines.forEach((line, index) => {
|
|
if (index < startIndex) {
|
|
beforeLine.push(line)
|
|
} else {
|
|
afterLine.push(line)
|
|
}
|
|
})
|
|
wall.lines = afterLine.concat(beforeLine)
|
|
|
|
//외벽선을 기준으로 polygon을 생성한다. 지붕선의 기준이 됨.
|
|
const divWallLines = []
|
|
wall.lines.forEach((currentWall, index) => {
|
|
const nextWall = wall.lines[(index + 1) % wall.lines.length]
|
|
const currentAngle = calculateAngle(currentWall.startPoint, currentWall.endPoint)
|
|
const nextAngle = calculateAngle(nextWall.startPoint, nextWall.endPoint)
|
|
if (currentAngle === nextAngle) {
|
|
divWallLines.push({ currentWall: currentWall, nextWall: nextWall, index: index })
|
|
}
|
|
})
|
|
|
|
const polygon = createRoofPolygon(wall.points)
|
|
const originPolygon = new QPolygon(wall.points, { fontSize: 0 })
|
|
originPolygon.setViewLengthText(false)
|
|
let offsetPolygon
|
|
|
|
let result = createMarginPolygon(polygon, wall.lines).vertices
|
|
|
|
//margin polygon 의 point가 기준 polygon의 밖에 있는지 판단한다.
|
|
const allPointsOutside = result.every((point) => !originPolygon.inPolygon(point))
|
|
|
|
if (allPointsOutside) {
|
|
offsetPolygon = createMarginPolygon(polygon, wall.lines).vertices
|
|
} else {
|
|
offsetPolygon = createPaddingPolygon(polygon, wall.lines).vertices
|
|
}
|
|
|
|
if (divWallLines.length > 0) {
|
|
/**
|
|
* 외벽선을 분기한 횟수를 저장한다. 외벽선은 offset이 같지 않을때 분기한다.
|
|
*/
|
|
let addPoint = 0
|
|
|
|
divWallLines.forEach((line) => {
|
|
const currentWall = line.currentWall
|
|
const nextWall = line.nextWall
|
|
const index = line.index + addPoint
|
|
const direction = currentWall.direction
|
|
const xDiff = Big(currentWall.x1).minus(Big(nextWall.x1))
|
|
const yDiff = Big(currentWall.y1).minus(Big(nextWall.y1))
|
|
const offsetCurrentPoint = offsetPolygon[index]
|
|
let offsetNextPoint = offsetPolygon[(index + 1) % offsetPolygon.length]
|
|
line.index = index
|
|
|
|
if (currentWall.attributes.offset !== nextWall.attributes.offset) {
|
|
const offsetPoint1 = {
|
|
x: xDiff.eq(0) ? offsetCurrentPoint.x : nextWall.x1,
|
|
y: yDiff.eq(0) ? offsetCurrentPoint.y : nextWall.y1,
|
|
}
|
|
|
|
let diffOffset = ['top', 'right'].includes(direction)
|
|
? Big(nextWall.attributes.offset).minus(Big(currentWall.attributes.offset))
|
|
: Big(currentWall.attributes.offset).minus(Big(nextWall.attributes.offset))
|
|
|
|
const offsetPoint2 = {
|
|
x: yDiff.eq(0) ? offsetPoint1.x : Big(offsetPoint1.x).plus(diffOffset).toNumber(),
|
|
y: xDiff.eq(0) ? offsetPoint1.y : Big(offsetPoint1.y).plus(diffOffset).toNumber(),
|
|
}
|
|
const offsetPoint3 = {
|
|
x: yDiff.eq(0) ? offsetNextPoint.x : Big(offsetNextPoint.x).plus(diffOffset).toNumber(),
|
|
y: xDiff.eq(0) ? offsetNextPoint.y : Big(offsetNextPoint.y).plus(diffOffset).toNumber(),
|
|
}
|
|
offsetPolygon.splice(index + 1, 0, offsetPoint1, offsetPoint2)
|
|
offsetNextPoint = offsetPoint3
|
|
addPoint++
|
|
} else {
|
|
addPoint--
|
|
}
|
|
})
|
|
}
|
|
|
|
const roof = makePolygon(
|
|
offsetPolygon.map((point) => {
|
|
return { x1: point.x, y1: point.y }
|
|
}),
|
|
)
|
|
|
|
if (wall.direction) {
|
|
roof.direction = wall.direction
|
|
}
|
|
if (wall.attributes?.roofId) {
|
|
canvas
|
|
.getObjects()
|
|
.filter((obj) => obj.parentId === roof.id)
|
|
.forEach((obj) => obj.set('parentId', wall.attributes.roofId))
|
|
roof.id = wall.attributes.roofId
|
|
}
|
|
roof.name = POLYGON_TYPE.ROOF
|
|
roof.setWall(wall)
|
|
|
|
let roofWallIndex = 0
|
|
|
|
roof.lines.forEach((line, index) => {
|
|
const x1 = Big(line.x1)
|
|
const x2 = Big(line.x2)
|
|
const y1 = Big(line.y1)
|
|
const y2 = Big(line.y2)
|
|
const lineLength = x1.minus(x2).abs().pow(2).plus(y1.minus(y2).abs().pow(2)).sqrt().times(10).round().toNumber()
|
|
line.attributes = {
|
|
roofId: roof.id,
|
|
planeSize: lineLength,
|
|
actualSize: lineLength,
|
|
wallLine: wall.lines[roofWallIndex].id,
|
|
type: wall.lines[roofWallIndex].attributes.type,
|
|
offset: wall.lines[roofWallIndex].attributes.offset,
|
|
width: wall.lines[roofWallIndex].attributes.width,
|
|
pitch: wall.lines[roofWallIndex].attributes.pitch,
|
|
sleeve: wall.lines[roofWallIndex].attributes.sleeve || false,
|
|
}
|
|
|
|
const isDivLine = divWallLines.some((divLine) => divLine.index === index)
|
|
if (!isDivLine) {
|
|
roofWallIndex++
|
|
}
|
|
})
|
|
|
|
wall.set({
|
|
attributes: {
|
|
roofId: roof.id,
|
|
},
|
|
})
|
|
|
|
/** 초기화*/
|
|
roof.wall.baseLines.forEach((line, index) => {
|
|
canvas.remove(line)
|
|
})
|
|
roof.wall.baseLines = []
|
|
wall.lines.forEach((line, index) => {
|
|
const x1 = Big(line.x1)
|
|
const x2 = Big(line.x2)
|
|
const y1 = Big(line.y1)
|
|
const y2 = Big(line.y2)
|
|
const lineLength = x1.minus(x2).abs().pow(2).plus(y1.minus(y2).abs().pow(2)).sqrt().times(10).round().toNumber()
|
|
line.attributes.roofId = roof.id
|
|
line.attributes.wallId = wall.id
|
|
// line.attributes.currentRoofId = roof.lines[index].id
|
|
line.attributes.planeSize = lineLength
|
|
line.attributes.actualSize = lineLength
|
|
|
|
const baseLine = new fabric.Line([line.x1, line.y1, line.x2, line.y2], {
|
|
visible: false,
|
|
attributes: line.attributes,
|
|
startPoint: line.startPoint,
|
|
endPoint: line.endPoint,
|
|
parentId: roof.id,
|
|
name: 'baseLine',
|
|
})
|
|
baseLine.attributes.originPoint = { x1: line.x1, y1: line.y1, x2: line.x2, y2: line.y2 }
|
|
roof.wall.baseLines.push(baseLine)
|
|
canvas.add(baseLine)
|
|
})
|
|
|
|
setRoof(roof)
|
|
setWall(wall)
|
|
return roof
|
|
}
|
|
|
|
/**
|
|
* 라인 사이가 지붕골 인지 확인.
|
|
* @param polygon
|
|
* @param line1
|
|
* @param line2
|
|
* @returns {boolean}
|
|
*/
|
|
const checkValley = (polygon, line1, line2) => {
|
|
let points = [
|
|
{ x: line1.x1, y: line1.y1 },
|
|
{ x: line1.x2, y: line1.y2 },
|
|
{ x: line2.x1, y: line2.y1 },
|
|
{ x: line2.x2, y: line2.y2 },
|
|
]
|
|
points = Array.from(new Set(points.map((point) => JSON.stringify(point)))).map((point) => JSON.parse(point))
|
|
const centroidX = points.reduce((acc, point) => acc + point.x, 0) / points.length
|
|
const centroidY = points.reduce((acc, point) => acc + point.y, 0) / points.length
|
|
|
|
let isValley = true
|
|
const pPoints = polygon.points
|
|
pPoints.forEach((point, index) => {
|
|
if (index < pPoints.length - 1) {
|
|
let j = index + 1
|
|
let xi = pPoints[index].x + polygon.left,
|
|
yi = pPoints[index].y + polygon.top
|
|
let xj = pPoints[j].x + polygon.left,
|
|
yj = pPoints[j].y + polygon.top
|
|
|
|
let intersect = yi > centroidY !== yj > centroidY && centroidX < ((xj - xi) * (centroidY - yi)) / (yj - yi) + xi
|
|
if (intersect) isValley = !isValley
|
|
}
|
|
})
|
|
return isValley
|
|
}
|
|
|
|
/**
|
|
* 구하려는 라인의 x1,y1좌표가 기준
|
|
* @param line1 이전 라인
|
|
* @param line2 현재 라인
|
|
* @param line3 다음 라인
|
|
* @returns {number}
|
|
*/
|
|
const getDegreeBetweenTwoLines = (line1, line2, line3) => {
|
|
console.log('getDegreeBetweenTwoLines 확인 ==========')
|
|
console.log(line1, line2, line3)
|
|
let x1 = line1.x,
|
|
x2 = line2.x,
|
|
x3 = line3.x
|
|
let y1 = line1.y,
|
|
y2 = line2.y,
|
|
y3 = line3.y
|
|
|
|
// 각 점 사이의 벡터 계산
|
|
const vector1 = { x: x1 - x2, y: y1 - y2 }
|
|
const vector2 = { x: x3 - x2, y: y3 - y2 }
|
|
|
|
// 벡터의 길이 계산
|
|
const magnitude1 = Math.sqrt(vector1.x * vector1.x + vector1.y * vector1.y)
|
|
const magnitude2 = Math.sqrt(vector2.x * vector2.x + vector2.y * vector2.y)
|
|
|
|
// 내적 계산
|
|
const dotProduct = vector1.x * vector2.x + vector1.y * vector2.y
|
|
|
|
// 각도 계산 (라디안에서도 0에서 PI 라디안 사이의 각도로 변환)
|
|
let angle = Math.acos(dotProduct / (magnitude1 * magnitude2))
|
|
|
|
// 라디안에서 도 단위로 변환
|
|
angle = angle * (180 / Math.PI)
|
|
|
|
console.log('angel : ', angle)
|
|
return angle
|
|
}
|
|
|
|
const calculateParallelPoint = (x1, y1, x2, y2, distance) => {
|
|
// 원래 선의 dx, dy 계산
|
|
const dx = x2 - x1
|
|
const dy = y2 - y1
|
|
|
|
// 법선 벡터 정규화
|
|
const norm = Math.sqrt(dx * dx + dy * dy)
|
|
const unitVectorX = dy / norm
|
|
const unitVectorY = -dx / norm
|
|
|
|
// 원하는 거리만큼 평행 이동
|
|
const offsetX = distance * unitVectorX
|
|
const offsetY = distance * unitVectorY
|
|
|
|
// 새로운 평행선의 두 점 계산
|
|
const newX1 = x1 + offsetX
|
|
const newY1 = y1 + offsetY
|
|
const newX2 = x2 + offsetX
|
|
const newY2 = y2 + offsetY
|
|
|
|
return { newPoint1: { x: newX1, y: newY1 }, newPoint2: { x: newX2, y: newY2 } }
|
|
}
|
|
|
|
const togglePolygonLine = (obj) => {
|
|
const rtnLines = []
|
|
if (obj.type === 'QPolygon') {
|
|
const points = obj.getCurrentPoints()
|
|
points.forEach((point, index) => {
|
|
const nextPoint = points[(index + 1) % points.length] // 마지막 점이면 첫 번째 점으로 연결
|
|
const line = new QLine([point.x, point.y, nextPoint.x, nextPoint.y], {
|
|
stroke: 'black',
|
|
strokeWidth: 2,
|
|
selectable: false,
|
|
fontSize: fontSize, // fontSize는 필요에 따라 조정
|
|
parent: obj,
|
|
})
|
|
obj.visible = false
|
|
canvas.add(line)
|
|
rtnLines.push(line)
|
|
})
|
|
|
|
canvas?.renderAll()
|
|
}
|
|
if (obj.type === 'QLine') {
|
|
const parent = obj.parent
|
|
canvas
|
|
?.getObjects()
|
|
.filter((obj) => obj.parent === parent)
|
|
.forEach((obj) => {
|
|
rtnLines.push(obj)
|
|
canvas.remove(obj)
|
|
})
|
|
|
|
parent.visible = true
|
|
canvas?.renderAll()
|
|
}
|
|
return rtnLines
|
|
}
|
|
|
|
const applyTemplateA = () => {
|
|
if (historyPoints.current.length === 0) {
|
|
changeMode(canvas, Mode.EDIT)
|
|
return
|
|
}
|
|
|
|
changeMode(canvas, Mode.EDIT)
|
|
const polygon = drawWallPolygon(false)
|
|
// handleClear()
|
|
|
|
if (polygon.lines.length === 4) {
|
|
//4각형
|
|
handleOuterLineTemplateA4Points(polygon)
|
|
} else if (polygon.lines.length === 6) {
|
|
//6각형
|
|
handleOuterLineTemplateA6Points(polygon)
|
|
} else if (polygon.lines.length === 8) {
|
|
handleOuterLineTemplateA8Points(polygon)
|
|
}
|
|
setTemplateType(2)
|
|
}
|
|
|
|
const handleOuterLineTemplateA4Points = (polygon, offsetInputX = 20, offsetInputY = 50) => {
|
|
const edge = offsetInputX
|
|
const eaves = offsetInputY
|
|
|
|
// 폴리곤의 각 변을 선으로 생성
|
|
const createLine = (start, end, stroke, property) =>
|
|
new QLine([start.x, start.y, end.x, end.y], {
|
|
stroke,
|
|
strokeWidth: 1,
|
|
property,
|
|
fontSize: 14,
|
|
})
|
|
|
|
const lines = polygon.points.map((start, i) => {
|
|
const end = polygon.points[(i + 1) % polygon.points.length]
|
|
const line = createLine(start, end, '#A0D468', 'normal')
|
|
canvas.add(line)
|
|
return line
|
|
})
|
|
|
|
let edgeIndexArray = []
|
|
let normalIndexArray = []
|
|
|
|
lines.forEach((line, i) => {
|
|
if (i % 2 === 0) {
|
|
line.set('stroke', 'skyblue').set('property', 'edge')
|
|
edgeIndexArray.push(i)
|
|
} else {
|
|
normalIndexArray.push(i)
|
|
}
|
|
canvas.add(line)
|
|
})
|
|
|
|
const centerPointX = (lines[1].x1 + lines[1].x2) / 2
|
|
const centerPointY = (lines[0].y1 + lines[0].y2) / 2
|
|
const horiCenterHalfLine = (lines[1].x2 - lines[1].x1) / 2
|
|
|
|
const createCenterLine = (x1, y1, x2, y2, stroke, strokeWidth, property, dashArray = []) =>
|
|
new QLine([x1, y1, x2, y2], {
|
|
stroke,
|
|
strokeWidth,
|
|
property,
|
|
fontSize: 14,
|
|
strokeDashArray: dashArray,
|
|
})
|
|
|
|
const vertCenterLine = createCenterLine(centerPointX, lines[0].y1 - edge, centerPointX, lines[0].y2 + edge, 'blue', 1, 'center')
|
|
canvas.add(vertCenterLine)
|
|
|
|
const horiCenterLineLeft = createCenterLine(
|
|
lines[1].x1 - eaves,
|
|
centerPointY,
|
|
lines[1].x1 + horiCenterHalfLine,
|
|
centerPointY,
|
|
'black',
|
|
2,
|
|
'center',
|
|
[5, 5],
|
|
)
|
|
canvas.add(horiCenterLineLeft)
|
|
|
|
const horiCenterLineRight = createCenterLine(
|
|
horiCenterLineLeft.x2,
|
|
centerPointY,
|
|
horiCenterLineLeft.x2 + horiCenterHalfLine + eaves,
|
|
centerPointY,
|
|
'black',
|
|
2,
|
|
'center',
|
|
[5, 5],
|
|
)
|
|
canvas.add(horiCenterLineRight)
|
|
|
|
const drawArray = lines
|
|
.map((line) => {
|
|
if (line.x1 === line.x2 && line.y1 < line.y2) {
|
|
return [{ x1: line.x1 - eaves, y1: line.y1 - edge, x2: line.x1 - eaves, y2: line.y2 + edge }]
|
|
} else if (line.x1 === line.x2 && line.y1 > line.y2) {
|
|
return [{ x1: line.x1 + eaves, y1: line.y1 + edge, x2: line.x1 + eaves, y2: line.y2 - edge }]
|
|
} else if (line.x1 < line.x2 && line.y1 === line.y2) {
|
|
return [
|
|
{ x1: line.x1 - eaves, y1: line.y1 + edge, x2: line.x1 + horiCenterHalfLine, y2: line.y2 + edge },
|
|
{
|
|
x1: line.x1 + horiCenterHalfLine,
|
|
y1: line.y1 + edge,
|
|
x2: line.x1 + horiCenterHalfLine + horiCenterHalfLine + eaves,
|
|
y2: line.y2 + edge,
|
|
},
|
|
]
|
|
} else if (line.x1 > line.x2 && line.y1 === line.y2) {
|
|
return [
|
|
{ x1: line.x2 - eaves, y1: line.y1 - edge, x2: line.x2 + horiCenterHalfLine, y2: line.y2 - edge },
|
|
{
|
|
x1: line.x2 + horiCenterHalfLine,
|
|
y1: line.y1 - edge,
|
|
x2: line.x2 + horiCenterHalfLine + horiCenterHalfLine + eaves,
|
|
y2: line.y2 - edge,
|
|
},
|
|
]
|
|
}
|
|
return []
|
|
})
|
|
.flat()
|
|
|
|
drawArray.forEach((line) => {
|
|
const outLine = createLine({ x: line.x1, y: line.y1 }, { x: line.x2, y: line.y2 }, 'blue', 'normal')
|
|
canvas.add(outLine)
|
|
})
|
|
|
|
const roofPatternPolygonArray = []
|
|
|
|
const leftLine = drawArray[0]
|
|
const rightLine = drawArray[3]
|
|
|
|
//사각형 왼쪽 지붕 패턴 생성 배열
|
|
const leftPolygon = [
|
|
{ x: leftLine.x1, y: leftLine.y1 },
|
|
{ x: leftLine.x2, y: leftLine.y2 },
|
|
{ x: vertCenterLine.x1, y: vertCenterLine.y1 },
|
|
{ x: vertCenterLine.x2, y: vertCenterLine.y2 },
|
|
]
|
|
roofPatternPolygonArray.push(leftPolygon)
|
|
|
|
//사각형 오른쪽 지붕 패턴 생성 배열
|
|
const rightPolygon = [
|
|
{ x: vertCenterLine.x1, y: vertCenterLine.y1 },
|
|
{ x: vertCenterLine.x2, y: vertCenterLine.y2 },
|
|
{ x: rightLine.x1, y: rightLine.y1 },
|
|
{ x: rightLine.x2, y: rightLine.y2 },
|
|
]
|
|
roofPatternPolygonArray.push(rightPolygon)
|
|
|
|
setRoofPolygonPattern({ roofPatternPolygonArray, lines }) //모든 행을 저장
|
|
}
|
|
|
|
//탬플릿A 적용
|
|
const handleOuterLineTemplateA6Points = (polygon) => {
|
|
let lines = []
|
|
let outLines = []
|
|
|
|
let halfLength = 0
|
|
|
|
const dashedCenterLineOpt = {
|
|
stroke: 'black',
|
|
strokeWidth: 1,
|
|
property: 'centerLine',
|
|
strokeDashArray: [10, 5],
|
|
fontSize: 14,
|
|
}
|
|
|
|
const centerLineOpt = {
|
|
stroke: 'blue',
|
|
strokeWidth: 1,
|
|
property: 'bigHoriCenter',
|
|
fontSize: 14,
|
|
}
|
|
|
|
// 폴리곤의 각 변을 선으로 생성
|
|
for (let i = 0; i < polygon.points.length; i++) {
|
|
const start = polygon.points[i]
|
|
const end = polygon.points[(i + 1) % polygon.points.length] // 다음 점, 마지막 점의 경우 첫 점으로
|
|
|
|
const line = new QLine([start.x, start.y, end.x, end.y], {
|
|
stroke: '#A0D468',
|
|
strokeWidth: 2,
|
|
property: 'normal',
|
|
fontSize: 14,
|
|
})
|
|
|
|
// 선을 배열에 추가
|
|
lines.push(line)
|
|
canvas.add(line)
|
|
}
|
|
canvas?.remove(polygon) //폴리곤
|
|
|
|
let highLineLength = 0
|
|
let lowLineLength = 0
|
|
|
|
let prevHighIndex = 0
|
|
let prevHighLength = 0
|
|
|
|
let prevLowIndex = 0
|
|
let prevLowLength = 0
|
|
|
|
let edgeIndexArray = []
|
|
let normalIndexArray = []
|
|
|
|
for (let i = 0; i < lines.length; i++) {
|
|
let line = lines[i]
|
|
|
|
if (!(i % 2) == 0) {
|
|
//홀수일떄
|
|
line.line.set('stroke', 'skyblue').set('property', 'egde')
|
|
let length = Math.abs(line.get('x1') - line.get('x2')) + Math.abs(line.get('y1') - line.get('y2'))
|
|
|
|
if (length > prevHighLength) {
|
|
//잴긴거 찾음
|
|
prevHighIndex = i
|
|
prevHighLength = length
|
|
highLineLength = length
|
|
}
|
|
|
|
if (prevLowLength === 0 || length <= prevLowLength) {
|
|
//최초에는 없어서 한번 넣음
|
|
prevLowIndex = i
|
|
prevLowLength = length
|
|
lowLineLength = length
|
|
}
|
|
|
|
edgeIndexArray.push(i)
|
|
} else {
|
|
normalIndexArray.push(i)
|
|
}
|
|
|
|
// 캔버스에 선 추가
|
|
canvas.add(line)
|
|
}
|
|
|
|
let prevDirecArray //긴선 앞배열
|
|
let nextDirecArray //긴선 뒷배열
|
|
let horizontalDirection //뽈록이 좌우 방향
|
|
|
|
if (prevHighIndex === 1) {
|
|
//카라바 기준 1과 5밖에 없음
|
|
prevDirecArray = lines[prevHighIndex - 1]
|
|
nextDirecArray = lines[prevHighIndex + 1]
|
|
|
|
//밑에쪽이 긴 방향
|
|
horizontalDirection = prevDirecArray.height > nextDirecArray.height ? 'left' : 'right'
|
|
} else {
|
|
prevDirecArray = lines[prevHighIndex - 1]
|
|
nextDirecArray = lines[0]
|
|
|
|
//위에가 긴 방향
|
|
horizontalDirection = prevDirecArray.height > nextDirecArray.height ? 'right' : 'left'
|
|
}
|
|
|
|
const edge = 20 //케라바
|
|
const eaves = 50 //처마
|
|
let firstLine = lines[1]
|
|
let secondLine = lines[3]
|
|
let lastLine = lines[5]
|
|
let vertCenterLine
|
|
let secondVertCenterLine
|
|
let templatePolygonObj = {}
|
|
let roofPatternPolygonArray = []
|
|
|
|
let bigRoofPolygon = []
|
|
let middleRoofPolygon = []
|
|
let smallRoofPolygon = []
|
|
let templateCenterLine = [] //추후 셀 입력시 상하좌우를 센터선 기준으로 판단하기 위함
|
|
if (prevHighIndex === 1) {
|
|
if (horizontalDirection === 'left') {
|
|
//배열 순서대로 뒤에꺼를 찾아서 계산한다
|
|
const firstSubLine = lines[2]
|
|
const middleSubLine = lines[4]
|
|
|
|
//ㄴ자 일경우
|
|
//긴면 세로선 그리기
|
|
let vertCenterPoint = (firstLine.x1 + firstLine.x2) / 2 //가장 긴선 중앙선 가운데 점
|
|
vertCenterLine = new QLine(
|
|
[
|
|
vertCenterPoint,
|
|
firstSubLine.y1 + edge, //다음 선의 높이만큼 가져와서 edge길이를 합함
|
|
vertCenterPoint,
|
|
firstSubLine.y2 - edge, //다음 선의 높이만큼 가져옴 edge길이를 합함
|
|
],
|
|
centerLineOpt,
|
|
)
|
|
canvas.add(vertCenterLine)
|
|
outLines.push(vertCenterLine)
|
|
|
|
//긴면 가로선 그리기
|
|
let horiCenterPoint = (firstSubLine.y1 + firstSubLine.y2) / 2
|
|
let horiCenterLine1 = new QLine(
|
|
[firstLine.x1 - eaves, horiCenterPoint, firstLine.x1 - eaves + firstLine.length / 2 + eaves, horiCenterPoint],
|
|
dashedCenterLineOpt,
|
|
)
|
|
|
|
canvas.add(horiCenterLine1)
|
|
//나중에 기울기 길이 적용할때 쓸라고 대충 냅둠
|
|
// canvas?.renderAll()
|
|
// console.log('horiCenterLine1', horiCenterLine1)
|
|
// horiCenterLine1.text.set('text', getRoofHeight(horiCenterLine1.length, getDegreeByChon(4)).toString())
|
|
|
|
let horiCenterLine2 = new QLine(
|
|
[
|
|
firstLine.x1 - eaves + firstLine.length / 2 + eaves,
|
|
horiCenterPoint,
|
|
firstLine.x1 - eaves + firstLine.length / 2 + eaves + firstLine.length / 2 + eaves,
|
|
horiCenterPoint,
|
|
],
|
|
dashedCenterLineOpt,
|
|
)
|
|
canvas.add(horiCenterLine2)
|
|
|
|
//작은 지붕쪽 높이 길이를 구하는 로직
|
|
let secondVertCenterPoint = (lastLine.x1 + lastLine.x2) / 2
|
|
secondVertCenterLine = new QLine([secondVertCenterPoint, middleSubLine.y1, secondVertCenterPoint, middleSubLine.y2 - edge], centerLineOpt)
|
|
canvas.add(secondVertCenterLine)
|
|
outLines.push(secondVertCenterLine)
|
|
templateCenterLine.push(vertCenterLine)
|
|
templateCenterLine.push(secondVertCenterLine)
|
|
|
|
//작은 지붕쪽 너비 길이를 구한는 로직
|
|
let secondHoriCenterLength = (Math.abs(lastLine.get('x1') - lastLine.get('x2')) + Math.abs(lastLine.get('y1') - lastLine.get('y2'))) / 2
|
|
let secondHoriCenterPoint = (secondVertCenterLine.y1 + secondVertCenterLine.y2) / 2
|
|
|
|
let secondHoriCenterLine = new QLine(
|
|
[secondVertCenterLine.x1, secondHoriCenterPoint, secondVertCenterLine.x1 + secondHoriCenterLength + eaves, secondHoriCenterPoint],
|
|
dashedCenterLineOpt,
|
|
)
|
|
canvas.add(secondHoriCenterLine)
|
|
|
|
//일반라인 외각선 그리기
|
|
normalIndexArray.forEach((index) => {
|
|
const line = lines[index]
|
|
let points = []
|
|
if (index === 0) {
|
|
points.push(line.x1 - eaves, line.y1 - edge, line.x2 - eaves, line.y2 + edge)
|
|
} else {
|
|
let tmpEdge = index === 2 ? edge : 0
|
|
points.push(line.x1 + eaves, line.y1 + tmpEdge, line.x2 + eaves, line.y2 - edge)
|
|
}
|
|
let drawline = new QLine(points, centerLineOpt)
|
|
canvas.add(drawline)
|
|
outLines.push(drawline)
|
|
})
|
|
|
|
//케라바 라인 외각선 그리기
|
|
const firstOuterLine = lines[1]
|
|
const middleOuterLine = lines[3]
|
|
const lastOuterLine = lines[5]
|
|
|
|
//첫번째 외곽선 1번
|
|
halfLength = firstOuterLine.length / 2
|
|
let drawFirstLine1 = new QLine(
|
|
[firstOuterLine.x1 - eaves, firstOuterLine.y1 + edge, firstOuterLine.x1 + halfLength, firstOuterLine.y2 + edge],
|
|
centerLineOpt,
|
|
)
|
|
canvas.add(drawFirstLine1)
|
|
outLines.push(drawFirstLine1)
|
|
|
|
//첫번째 외곽선 2번
|
|
let drawFirstLine2 = new QLine(
|
|
[drawFirstLine1.x2, firstOuterLine.y1 + edge, firstOuterLine.x2 + eaves, firstOuterLine.y2 + edge],
|
|
centerLineOpt,
|
|
)
|
|
canvas.add(drawFirstLine2)
|
|
outLines.push(drawFirstLine2)
|
|
|
|
//중간라인 외각선
|
|
let drawMiddleLine = new QLine([drawFirstLine2.x2, middleOuterLine.y1 - edge, drawFirstLine2.x1, middleOuterLine.y2 - edge], centerLineOpt)
|
|
canvas.add(drawMiddleLine)
|
|
outLines.push(drawMiddleLine)
|
|
|
|
//마지막 외각선
|
|
halfLength = lastLine.length / 2
|
|
let drawLastLine1 = new QLine(
|
|
[lastOuterLine.x2 + halfLength, lastOuterLine.y1 - edge, lastOuterLine.x2 - eaves, lastOuterLine.y2 - edge],
|
|
centerLineOpt,
|
|
)
|
|
canvas.add(drawLastLine1)
|
|
outLines.push(drawLastLine1)
|
|
|
|
let drawLastLine2 = new QLine(
|
|
[drawLastLine1.x1, lastOuterLine.y1 - edge, lastOuterLine.x1 + length + eaves, lastOuterLine.y2 - edge],
|
|
centerLineOpt,
|
|
)
|
|
canvas.add(drawLastLine2)
|
|
outLines.push(drawLastLine2)
|
|
|
|
let drawLastInLine1 = new QLine(
|
|
[secondVertCenterLine.x1, secondVertCenterLine.y1, secondVertCenterLine.x1 + halfLength + eaves, secondVertCenterLine.y1],
|
|
centerLineOpt,
|
|
)
|
|
canvas.add(drawLastInLine1)
|
|
|
|
let drawLastInLine2 = new QLine([secondVertCenterLine.x1, vertCenterLine.y2, vertCenterLine.x2, vertCenterLine.y2], centerLineOpt)
|
|
canvas.add(drawLastInLine2)
|
|
|
|
bigRoofPolygon = [
|
|
{ x: outLines[2].x1, y: outLines[2].y1 },
|
|
{ x: outLines[2].x2, y: outLines[2].y2 },
|
|
{ x: outLines[0].x1, y: outLines[0].y1 },
|
|
{ x: outLines[0].x2, y: outLines[0].y2 },
|
|
{ x: outLines[1].x2, y: outLines[0].y2 },
|
|
{ x: outLines[1].x2, y: outLines[1].y2 },
|
|
]
|
|
|
|
middleRoofPolygon = [
|
|
{ x: outLines[0].x2, y: outLines[0].y2 },
|
|
{ x: outLines[0].x1, y: outLines[0].y1 },
|
|
{ x: outLines[3].x2, y: outLines[3].y2 },
|
|
{ x: outLines[3].x1, y: outLines[3].y1 },
|
|
]
|
|
|
|
smallRoofPolygon = [
|
|
{ x: outLines[1].x2, y: outLines[1].y2 },
|
|
{ x: outLines[1].x1, y: outLines[1].y1 },
|
|
{ x: outLines[4].x2, y: outLines[4].y2 },
|
|
{ x: outLines[4].x1, y: outLines[4].y1 },
|
|
]
|
|
} else {
|
|
//아래쪽 길게 오른쪽 방향
|
|
//배열 순서대로 뒤에꺼를 찾아서 계산한다
|
|
|
|
firstLine = lines[1]
|
|
secondLine = lines[5]
|
|
lastLine = lines[3]
|
|
|
|
const firstSubLine = lines[0]
|
|
const middleSubLine = lines[4]
|
|
|
|
//ㄴ자 일경우
|
|
//긴면 세로선 그리기
|
|
let vertCenterPoint = (firstLine.x1 + firstLine.x2) / 2 //가장 긴선 중앙선 가운데 점
|
|
vertCenterLine = new QLine(
|
|
[
|
|
vertCenterPoint,
|
|
firstSubLine.y1 - edge, //다음 선의 높이만큼 가져와서 edge길이를 합함
|
|
vertCenterPoint,
|
|
firstSubLine.y2 + edge, //다음 선의 높이만큼 가져옴 edge길이를 합함
|
|
],
|
|
centerLineOpt,
|
|
)
|
|
canvas.add(vertCenterLine)
|
|
outLines.push(vertCenterLine)
|
|
|
|
//긴면 가로선 그리기
|
|
let horiCenterPoint = (firstSubLine.y1 + firstSubLine.y2) / 2
|
|
let horiCenterLine1 = new QLine(
|
|
[firstLine.x1 - eaves, horiCenterPoint, firstLine.x1 - eaves + firstLine.length / 2 + eaves, horiCenterPoint],
|
|
dashedCenterLineOpt,
|
|
)
|
|
canvas.add(horiCenterLine1)
|
|
|
|
let horiCenterLine2 = new QLine(
|
|
[
|
|
firstLine.x1 - eaves + firstLine.length / 2 + eaves,
|
|
horiCenterPoint,
|
|
firstLine.x1 - eaves + firstLine.length / 2 + eaves + firstLine.length / 2 + eaves,
|
|
horiCenterPoint,
|
|
],
|
|
dashedCenterLineOpt,
|
|
)
|
|
canvas.add(horiCenterLine2)
|
|
|
|
//작은 지붕쪽 높이 길이를 구하는 로직
|
|
let secondVertCenterPoint = (lastLine.x1 + lastLine.x2) / 2
|
|
secondVertCenterLine = new QLine([secondVertCenterPoint, middleSubLine.y1 - edge, secondVertCenterPoint, middleSubLine.y2], centerLineOpt)
|
|
canvas.add(secondVertCenterLine)
|
|
outLines.push(secondVertCenterLine)
|
|
|
|
//작은 지붕쪽 너비 길이를 구한는 로직
|
|
let secondHoriCenterLength = (Math.abs(lastLine.get('x1') - lastLine.get('x2')) + Math.abs(lastLine.get('y1') - lastLine.get('y2'))) / 2
|
|
let secondHoriCenterPoint = (secondVertCenterLine.y1 + secondVertCenterLine.y2) / 2
|
|
|
|
let secondHoriCenterLine = new QLine(
|
|
[secondVertCenterLine.x1 - secondHoriCenterLength - eaves, secondHoriCenterPoint, secondVertCenterLine.x1, secondHoriCenterPoint],
|
|
dashedCenterLineOpt,
|
|
)
|
|
canvas.add(secondHoriCenterLine)
|
|
|
|
templateCenterLine.push(vertCenterLine)
|
|
templateCenterLine.push(secondVertCenterLine)
|
|
|
|
//일반라인 외각선 그리기
|
|
normalIndexArray.forEach((index) => {
|
|
const line = lines[index]
|
|
let points = []
|
|
if (index === 0 || index === 4) {
|
|
let tmpEdge = index === 4 ? 0 : edge
|
|
points = [line.x1 - eaves, line.y1 - edge, line.x2 - eaves, line.y2 + tmpEdge]
|
|
} else {
|
|
points = [line.x1 + eaves, line.y1 + edge, line.x2 + eaves, line.y2 - edge]
|
|
}
|
|
let drawline = new QLine(points, centerLineOpt)
|
|
canvas.add(drawline)
|
|
outLines.push(drawline)
|
|
})
|
|
|
|
//케라바 라인 외각선 그리기
|
|
const firstOuterLine = lines[1]
|
|
const middleOuterLine = lines[5]
|
|
const lastOuterLine = lines[3]
|
|
|
|
//첫번째 외곽선 1번
|
|
halfLength = firstOuterLine.length / 2
|
|
let drawFirstLine1 = new QLine(
|
|
[firstOuterLine.x1 - eaves, firstOuterLine.y1 + edge, firstOuterLine.x1 + halfLength, firstOuterLine.y2 + edge],
|
|
centerLineOpt,
|
|
)
|
|
canvas.add(drawFirstLine1)
|
|
outLines.push(drawFirstLine1)
|
|
|
|
//첫번째 외곽선 2번
|
|
let drawFirstLine2 = new QLine(
|
|
[drawFirstLine1.x2, firstOuterLine.y1 + edge, firstOuterLine.x2 + eaves, firstOuterLine.y2 + edge],
|
|
centerLineOpt,
|
|
)
|
|
canvas.add(drawFirstLine2)
|
|
outLines.push(drawFirstLine2)
|
|
|
|
//중간라인 외각선
|
|
let drawMiddleLine = new QLine([drawFirstLine1.x1, middleOuterLine.y1 - edge, drawFirstLine1.x2, middleOuterLine.y2 - edge], centerLineOpt)
|
|
canvas.add(drawMiddleLine)
|
|
outLines.push(drawMiddleLine)
|
|
|
|
//마지막 외각선
|
|
halfLength = lastLine.length / 2
|
|
|
|
let drawLastLine1 = new QLine(
|
|
[lastOuterLine.x2 - eaves, lastOuterLine.y1 - edge, lastOuterLine.x1 - halfLength, lastOuterLine.y2 - edge],
|
|
centerLineOpt,
|
|
)
|
|
canvas.add(drawLastLine1)
|
|
|
|
let drawLastLine2 = new QLine(
|
|
[drawLastLine1.x1, lastOuterLine.y1 - edge, lastOuterLine.x1 + length + eaves, lastOuterLine.y2 - edge],
|
|
centerLineOpt,
|
|
)
|
|
canvas.add(drawLastLine2)
|
|
|
|
let drawLastInLine1 = new QLine(
|
|
[secondVertCenterLine.x1, secondVertCenterLine.y2, secondVertCenterLine.x1 - halfLength - eaves, secondVertCenterLine.y2],
|
|
centerLineOpt,
|
|
)
|
|
canvas.add(drawLastInLine1)
|
|
|
|
let drawLastInLine2 = new QLine([secondVertCenterLine.x2, vertCenterLine.y1, vertCenterLine.x1, vertCenterLine.y1], centerLineOpt)
|
|
canvas.add(drawLastInLine2)
|
|
|
|
bigRoofPolygon = [
|
|
{ x: outLines[0].x1, y: outLines[0].y1 },
|
|
{ x: outLines[0].x2, y: outLines[0].y2 },
|
|
{ x: outLines[3].x1, y: outLines[3].y1 },
|
|
{ x: outLines[3].x2, y: outLines[3].y2 },
|
|
{ x: outLines[1].x1, y: outLines[1].y1 },
|
|
{ x: outLines[1].x2, y: outLines[0].y1 },
|
|
]
|
|
|
|
middleRoofPolygon = [
|
|
{ x: outLines[2].x1, y: outLines[2].y1 },
|
|
{ x: outLines[2].x2, y: outLines[2].y2 },
|
|
{ x: outLines[0].x2, y: outLines[0].y2 },
|
|
{ x: outLines[0].x1, y: outLines[0].y1 },
|
|
]
|
|
|
|
smallRoofPolygon = [
|
|
{ x: outLines[4].x1, y: outLines[4].y1 },
|
|
{ x: outLines[4].x2, y: outLines[4].y2 },
|
|
{ x: outLines[1].x2, y: outLines[1].y2 },
|
|
{ x: outLines[1].x1, y: outLines[1].y1 },
|
|
]
|
|
}
|
|
} else {
|
|
if (horizontalDirection === 'left') {
|
|
//아래쪽 길게 오른쪽 방향
|
|
//배열 순서대로 뒤에꺼를 찾아서 계산한다
|
|
firstLine = lines[5]
|
|
secondLine = lines[3]
|
|
lastLine = lines[1]
|
|
|
|
const firstSubLine = lines[4]
|
|
const middleSubLine = lines[2]
|
|
|
|
//ㄴ자 일경우
|
|
//긴면 세로선 그리기
|
|
let vertCenterPoint = (firstLine.x1 + firstLine.x2) / 2 //가장 긴선 중앙선 가운데 점
|
|
vertCenterLine = new QLine(
|
|
[
|
|
vertCenterPoint,
|
|
firstSubLine.y2 - edge, //다음 선의 높이만큼 가져와서 edge길이를 합함
|
|
vertCenterPoint,
|
|
firstSubLine.y1 + edge, //다음 선의 높이만큼 가져옴 edge길이를 합함
|
|
],
|
|
centerLineOpt,
|
|
)
|
|
canvas.add(vertCenterLine)
|
|
outLines.push(vertCenterLine)
|
|
|
|
//긴면 가로선 그리기
|
|
let horiCenterPoint = (firstSubLine.y1 + firstSubLine.y2) / 2
|
|
let horiCenterLine1 = new QLine(
|
|
[firstLine.x2 - eaves, horiCenterPoint, firstLine.x2 - eaves + firstLine.length / 2 + eaves, horiCenterPoint],
|
|
dashedCenterLineOpt,
|
|
)
|
|
canvas.add(horiCenterLine1)
|
|
|
|
let horiCenterLine2 = new QLine(
|
|
[
|
|
firstLine.x2 - eaves + firstLine.length / 2 + eaves,
|
|
horiCenterPoint,
|
|
firstLine.x2 - eaves + firstLine.length / 2 + eaves + firstLine.length / 2 + eaves,
|
|
horiCenterPoint,
|
|
],
|
|
dashedCenterLineOpt,
|
|
)
|
|
canvas.add(horiCenterLine2)
|
|
|
|
//작은 지붕쪽 높이 길이를 구하는 로직
|
|
let secondVertCenterPoint = (lastLine.x1 + lastLine.x2) / 2
|
|
secondVertCenterLine = new QLine([secondVertCenterPoint, middleSubLine.y1 + edge, secondVertCenterPoint, middleSubLine.y2], centerLineOpt)
|
|
canvas.add(secondVertCenterLine)
|
|
outLines.push(secondVertCenterLine)
|
|
|
|
templateCenterLine.push(vertCenterLine)
|
|
templateCenterLine.push(secondVertCenterLine)
|
|
|
|
//작은 지붕쪽 너비 길이를 구한는 로직
|
|
let secondHoriCenterLength = (Math.abs(lastLine.get('x1') - lastLine.get('x2')) + Math.abs(lastLine.get('y1') - lastLine.get('y2'))) / 2
|
|
let secondHoriCenterPoint = (secondVertCenterLine.y1 + secondVertCenterLine.y2) / 2
|
|
let secondHoriCenterLine = new QLine(
|
|
[secondVertCenterLine.x1 + secondHoriCenterLength + eaves, secondHoriCenterPoint, secondVertCenterLine.x1, secondHoriCenterPoint],
|
|
dashedCenterLineOpt,
|
|
)
|
|
canvas.add(secondHoriCenterLine)
|
|
|
|
//일반라인 외각선 그리기
|
|
normalIndexArray.forEach((index) => {
|
|
const line = lines[index]
|
|
let points = []
|
|
if (index === 0) {
|
|
points = [line.x1 - eaves, line.y1 - edge, line.x2 - eaves, line.y2 + edge]
|
|
} else {
|
|
let tmpEdge = index === 2 ? 0 : edge
|
|
points = [line.x1 + eaves, line.y1 + edge, line.x2 + eaves, line.y2 - tmpEdge]
|
|
}
|
|
|
|
let drawline = new QLine(points, centerLineOpt)
|
|
canvas.add(drawline)
|
|
outLines.push(drawline)
|
|
})
|
|
|
|
//케라바 라인 외각선 그리기
|
|
const firstOuterLine = lines[5]
|
|
const middleOuterLine = lines[3]
|
|
const lastOuterLine = lines[1]
|
|
|
|
//첫번째 외곽선 1번
|
|
halfLength = firstOuterLine.length / 2
|
|
let drawFirstLine1 = new QLine(
|
|
[firstOuterLine.x2 - eaves, firstOuterLine.y1 - edge, firstOuterLine.x2 + halfLength, firstOuterLine.y2 - edge],
|
|
centerLineOpt,
|
|
)
|
|
canvas.add(drawFirstLine1)
|
|
|
|
//첫번째 외곽선 2번
|
|
let drawFirstLine2 = new QLine(
|
|
[drawFirstLine1.x2, drawFirstLine1.y1, drawFirstLine1.x2 + halfLength + eaves, drawFirstLine1.y2],
|
|
centerLineOpt,
|
|
)
|
|
canvas.add(drawFirstLine2)
|
|
|
|
//중간라인 외각선
|
|
let drawMiddleLine = new QLine([drawFirstLine2.x1, middleOuterLine.y1 + edge, drawFirstLine2.x2, middleOuterLine.y2 + edge], centerLineOpt)
|
|
canvas.add(drawMiddleLine)
|
|
|
|
//마지막 외각선
|
|
halfLength = lastLine.length / 2
|
|
let drawLastLine1 = new QLine(
|
|
[lastOuterLine.x1 - eaves, lastOuterLine.y1 + edge, lastOuterLine.x1 + halfLength, lastOuterLine.y2 + edge],
|
|
centerLineOpt,
|
|
)
|
|
canvas.add(drawLastLine1)
|
|
|
|
let drawLastLine2 = new QLine([drawLastLine1.x2, drawLastLine1.y1, drawLastLine1.x2 + halfLength + eaves, drawLastLine1.y1], centerLineOpt)
|
|
canvas.add(drawLastLine2)
|
|
|
|
let drawLastInLine1 = new QLine(
|
|
[secondVertCenterLine.x1, secondVertCenterLine.y2, secondVertCenterLine.x1 + halfLength + eaves, secondVertCenterLine.y2],
|
|
centerLineOpt,
|
|
)
|
|
canvas.add(drawLastInLine1)
|
|
|
|
let drawLastInLine2 = new QLine([vertCenterLine.x1, vertCenterLine.y2, secondVertCenterLine.x2, vertCenterLine.y2], centerLineOpt)
|
|
canvas.add(drawLastInLine2)
|
|
|
|
bigRoofPolygon = [
|
|
{ x: outLines[2].x1, y: outLines[2].y1 },
|
|
{ x: outLines[2].x2, y: outLines[2].y2 },
|
|
{ x: outLines[1].x1, y: outLines[1].y1 },
|
|
{ x: outLines[1].x2, y: outLines[0].y2 },
|
|
{ x: outLines[0].x1, y: outLines[0].y2 },
|
|
{ x: outLines[0].x1, y: outLines[0].y1 },
|
|
]
|
|
|
|
middleRoofPolygon = [
|
|
{ x: outLines[0].x1, y: outLines[0].y1 },
|
|
{ x: outLines[0].x2, y: outLines[0].y2 },
|
|
{ x: outLines[4].x2, y: outLines[4].y2 },
|
|
{ x: outLines[4].x1, y: outLines[4].y1 },
|
|
]
|
|
|
|
smallRoofPolygon = [
|
|
{ x: outLines[1].x2, y: outLines[1].y2 },
|
|
{ x: outLines[1].x1, y: outLines[1].y1 },
|
|
{ x: outLines[3].x2, y: outLines[3].y2 },
|
|
{ x: outLines[3].x1, y: outLines[3].y1 },
|
|
]
|
|
} else {
|
|
//윗쪽 길게 오른쪽 방향
|
|
//배열 순서대로 뒤에꺼를 찾아서 계산한다
|
|
firstLine = lines[5]
|
|
secondLine = lines[1]
|
|
lastLine = lines[3]
|
|
|
|
const firstSubLine = lines[0]
|
|
const middleSubLine = lines[2]
|
|
|
|
//ㄴ자 일경우
|
|
//긴면 세로선 그리기
|
|
let vertCenterPoint = (firstLine.x1 + firstLine.x2) / 2 //가장 긴선 중앙선 가운데 점
|
|
vertCenterLine = new QLine(
|
|
[
|
|
vertCenterPoint,
|
|
firstSubLine.y1 - edge, //다음 선의 높이만큼 가져와서 edge길이를 합함
|
|
vertCenterPoint,
|
|
firstSubLine.y2 + edge, //다음 선의 높이만큼 가져옴 edge길이를 합함
|
|
],
|
|
centerLineOpt,
|
|
)
|
|
canvas.add(vertCenterLine)
|
|
outLines.push(vertCenterLine)
|
|
|
|
//긴면 가로선 그리기
|
|
let horiCenterPoint = (firstSubLine.y1 + firstSubLine.y2) / 2
|
|
let horiCenterLine1 = new QLine(
|
|
[firstLine.x2 - eaves, horiCenterPoint, firstLine.x2 - eaves + firstLine.length / 2 + eaves, horiCenterPoint],
|
|
dashedCenterLineOpt,
|
|
)
|
|
canvas.add(horiCenterLine1)
|
|
|
|
let horiCenterLine2 = new QLine(
|
|
[
|
|
firstLine.x2 - eaves + firstLine.length / 2 + eaves,
|
|
horiCenterPoint,
|
|
firstLine.x2 - eaves + firstLine.length / 2 + eaves + firstLine.length / 2 + eaves,
|
|
horiCenterPoint,
|
|
],
|
|
dashedCenterLineOpt,
|
|
)
|
|
canvas.add(horiCenterLine2)
|
|
|
|
//작은 지붕쪽 높이 길이를 구하는 로직
|
|
let secondVertCenterPoint = (lastLine.x1 + lastLine.x2) / 2
|
|
secondVertCenterLine = new QLine([secondVertCenterPoint, middleSubLine.y1, secondVertCenterPoint, middleSubLine.y2 + edge], centerLineOpt)
|
|
|
|
canvas.add(secondVertCenterLine)
|
|
outLines.push(secondVertCenterLine)
|
|
|
|
templateCenterLine.push(vertCenterLine)
|
|
templateCenterLine.push(secondVertCenterLine)
|
|
|
|
//작은 지붕쪽 너비 길이를 구한는 로직
|
|
let secondHoriCenterLength = (Math.abs(lastLine.get('x1') - lastLine.get('x2')) + Math.abs(lastLine.get('y1') - lastLine.get('y2'))) / 2
|
|
let secondHoriCenterPoint = (secondVertCenterLine.y1 + secondVertCenterLine.y2) / 2
|
|
|
|
let secondHoriCenterLine = new QLine(
|
|
[secondVertCenterLine.x1, secondHoriCenterPoint, secondVertCenterLine.x1 - secondHoriCenterLength - eaves, secondHoriCenterPoint],
|
|
dashedCenterLineOpt,
|
|
)
|
|
canvas.add(secondHoriCenterLine)
|
|
|
|
//일반라인 외각선 그리기
|
|
normalIndexArray.forEach((index) => {
|
|
const line = lines[index]
|
|
let drawline
|
|
if (index === 0 || index === 2) {
|
|
let tmpEdge = index === 2 ? 0 : edge
|
|
drawline = new QLine([line.x1 - eaves, line.y1 - tmpEdge, line.x2 - eaves, line.y2 + edge], centerLineOpt)
|
|
canvas.add(drawline)
|
|
} else {
|
|
drawline = new QLine([line.x1 + eaves, line.y1 + edge, line.x2 + eaves, line.y2 - edge], centerLineOpt)
|
|
canvas.add(drawline)
|
|
}
|
|
outLines.push(drawline)
|
|
})
|
|
|
|
//케라바 라인 외각선 그리기
|
|
const firstOuterLine = lines[5]
|
|
const middleOuterLine = lines[1]
|
|
const lastOuterLine = lines[3]
|
|
|
|
//첫번째 외곽선 1번
|
|
halfLength = firstOuterLine.length / 2
|
|
let drawFirstLine1 = new QLine(
|
|
[firstOuterLine.x2 - eaves, firstOuterLine.y1 - edge, firstOuterLine.x2 + halfLength, firstOuterLine.y2 - edge],
|
|
centerLineOpt,
|
|
)
|
|
canvas.add(drawFirstLine1)
|
|
|
|
//첫번째 외곽선 2번
|
|
let drawFirstLine2 = new QLine(
|
|
[drawFirstLine1.x2, drawFirstLine1.y1, drawFirstLine1.x2 + halfLength + eaves, drawFirstLine1.y2],
|
|
centerLineOpt,
|
|
)
|
|
canvas.add(drawFirstLine2)
|
|
|
|
//중간라인 외각선
|
|
let drawMiddleLine = new QLine([drawFirstLine1.x1, middleOuterLine.y1 + edge, drawFirstLine1.x2, middleOuterLine.y2 + edge], centerLineOpt)
|
|
canvas.add(drawMiddleLine)
|
|
|
|
//마지막 외각선
|
|
halfLength = lastLine.length / 2
|
|
let drawLastLine1 = new QLine(
|
|
[lastOuterLine.x1 - eaves, lastOuterLine.y1 + edge, lastOuterLine.x1 + halfLength, lastOuterLine.y2 + edge],
|
|
centerLineOpt,
|
|
)
|
|
canvas.add(drawLastLine1)
|
|
|
|
let drawLastLine2 = new QLine([drawLastLine1.x2, drawLastLine1.y1, drawLastLine1.x2 + halfLength + eaves, drawLastLine1.y1], centerLineOpt)
|
|
canvas.add(drawLastLine2)
|
|
|
|
let drawLastInLine1 = new QLine([vertCenterLine.x2, vertCenterLine.y2, secondVertCenterLine.x1, vertCenterLine.y2], centerLineOpt)
|
|
canvas.add(drawLastInLine1)
|
|
|
|
let drawLastInLine2 = new QLine([secondLine.x2 - eaves, secondLine.y1, drawLastLine1.x2, secondLine.y1], centerLineOpt)
|
|
canvas.add(drawLastInLine2)
|
|
|
|
bigRoofPolygon = [
|
|
{ x: outLines[0].x1, y: outLines[0].y1 },
|
|
{ x: outLines[0].x2, y: outLines[0].y2 },
|
|
{ x: outLines[1].x1, y: outLines[0].y2 },
|
|
{ x: outLines[1].x2, y: outLines[1].y2 },
|
|
{ x: outLines[4].x1, y: outLines[4].y1 },
|
|
{ x: outLines[4].x2, y: outLines[4].y2 },
|
|
]
|
|
|
|
middleRoofPolygon = [
|
|
{ x: outLines[2].x1, y: outLines[2].y1 },
|
|
{ x: outLines[2].x2, y: outLines[2].y2 },
|
|
{ x: outLines[0].x2, y: outLines[0].y2 },
|
|
{ x: outLines[0].x1, y: outLines[0].y1 },
|
|
]
|
|
|
|
smallRoofPolygon = [
|
|
{ x: outLines[3].x1, y: outLines[3].y1 },
|
|
{ x: outLines[3].x2, y: outLines[3].y2 },
|
|
{ x: outLines[1].x2, y: outLines[1].y2 },
|
|
{ x: outLines[1].x1, y: outLines[1].y1 },
|
|
]
|
|
}
|
|
}
|
|
|
|
roofPatternPolygonArray.push(bigRoofPolygon) //지붕폴리곤
|
|
roofPatternPolygonArray.push(middleRoofPolygon) //중간라인 폴리곤
|
|
roofPatternPolygonArray.push(smallRoofPolygon) //작은지붕폴리곤
|
|
setRoofPolygonPattern({ roofPatternPolygonArray, lines }) //모든 행을 저장
|
|
setTemplateCenterLine(templateCenterLine) //A,B 템플릿의 센터 라인을 저장
|
|
canvas?.renderAll()
|
|
}
|
|
|
|
const handleOuterLineTemplateA8Points = (polygon, offsetInputX = 20, offsetInputY = 50) => {
|
|
let offsetPoints = []
|
|
|
|
const originalMax = 71
|
|
const transformedMax = 100
|
|
|
|
let lines = [] //내각라인
|
|
let outLines = [] //아웃라인
|
|
let halfLength = 0 //선길이
|
|
let offsetX
|
|
let offsetY
|
|
|
|
const dashedCenterLineOpt = {
|
|
stroke: 'black',
|
|
strokeWidth: 1,
|
|
property: 'centerLine',
|
|
strokeDashArray: [8, 4],
|
|
fontSize: 14,
|
|
}
|
|
|
|
const centerLineOpt = {
|
|
stroke: 'blue',
|
|
strokeWidth: 2,
|
|
property: 'bigHoriCenter',
|
|
fontSize: 14,
|
|
}
|
|
|
|
// 폴리곤의 각 변을 선으로 생성
|
|
for (let i = 0; i < polygon.points.length; i++) {
|
|
const start = polygon.points[i]
|
|
const end = polygon.points[(i + 1) % polygon.points.length] // 다음 점, 마지막 점의 경우 첫 점으로
|
|
|
|
const line = new QLine([start.x, start.y, end.x, end.y], {
|
|
stroke: '#A0D468',
|
|
strokeWidth: 2,
|
|
property: 'normal',
|
|
fontSize: 14,
|
|
})
|
|
|
|
// 선을 배열에 추가
|
|
lines.push(line)
|
|
canvas.add(line)
|
|
}
|
|
|
|
offsetInputY = offsetInputY !== 0 ? offsetInputY : offsetInputX
|
|
|
|
const sortedIndex = getStartIndex(polygon.lines)
|
|
let tmpArraySorted = rearrangeArray(polygon.lines, sortedIndex)
|
|
setSortedArray(tmpArraySorted) //recoil에 넣음
|
|
|
|
const points = tmpArraySorted.map((line) => ({
|
|
x: line.x1,
|
|
y: line.y1,
|
|
}))
|
|
|
|
//좌표 재정렬
|
|
function reSortQlineArray(array) {
|
|
let tmpArray = []
|
|
let minX, minY, maxX, maxY
|
|
let tmp
|
|
array.forEach((arr, index) => {
|
|
tmp = arr
|
|
if (arr.x2 < arr.x1 || arr.y2 < arr.y1) {
|
|
minX = arr.x2
|
|
minY = arr.y2
|
|
maxX = arr.x1
|
|
maxY = arr.y1
|
|
tmp['x1'] = minX
|
|
tmp['y1'] = minY
|
|
tmp['x2'] = maxX
|
|
tmp['y2'] = maxY
|
|
tmp.line['x1'] = minX
|
|
tmp.line['y1'] = minY
|
|
tmp.line['x2'] = maxX
|
|
tmp.line['y2'] = maxY
|
|
}
|
|
tmpArray.push(tmp)
|
|
})
|
|
|
|
return tmpArray
|
|
}
|
|
|
|
// 오목한 부분 인덱스 찾기
|
|
const concaveIndicesObj = findConcavePointIndices(points) //오목한 부분을 제외한 인덱스
|
|
let concavePointIndices = concaveIndicesObj.concavePointIndices
|
|
|
|
const concaveLine = {
|
|
index: concavePointIndices[0],
|
|
line: lines[concavePointIndices[0]],
|
|
}
|
|
|
|
for (let i = 0; i < points.length; i++) {
|
|
let prev = points[(i - 1 + points.length) % points.length]
|
|
let current = points[i]
|
|
let next = points[(i + 1) % points.length]
|
|
|
|
// 두 벡터 계산 (prev -> current, current -> next)
|
|
let vector1 = { x: current.x - prev.x, y: current.y - prev.y }
|
|
let vector2 = { x: next.x - current.x, y: next.y - current.y }
|
|
|
|
// 벡터의 길이 계산
|
|
let length1 = Math.sqrt(vector1.x * vector1.x + vector1.y * vector1.y)
|
|
let length2 = Math.sqrt(vector2.x * vector2.x + vector2.y * vector2.y)
|
|
|
|
// 벡터를 단위 벡터로 정규화
|
|
let unitVector1 = { x: vector1.x / length1, y: vector1.y / length1 }
|
|
let unitVector2 = { x: vector2.x / length2, y: vector2.y / length2 }
|
|
|
|
// 법선 벡터 계산 (왼쪽 방향)
|
|
let normal1 = { x: -unitVector1.y, y: unitVector1.x }
|
|
let normal2 = { x: -unitVector2.y, y: unitVector2.x }
|
|
|
|
// 법선 벡터 평균 계산
|
|
let averageNormal = {
|
|
x: (normal1.x + normal2.x) / 2,
|
|
y: (normal1.y + normal2.y) / 2,
|
|
}
|
|
|
|
// 평균 법선 벡터를 단위 벡터로 정규화
|
|
let lengthNormal = Math.sqrt(averageNormal.x * averageNormal.x + averageNormal.y * averageNormal.y)
|
|
let unitNormal = {
|
|
x: averageNormal.x / lengthNormal,
|
|
y: averageNormal.y / lengthNormal,
|
|
}
|
|
|
|
offsetX = (offsetInputX / transformedMax) * originalMax * 2
|
|
offsetY = (offsetInputY / transformedMax) * originalMax * 2
|
|
|
|
// 오프셋 적용
|
|
let offsetPoint = {
|
|
x1: current.x + unitNormal.x * offsetX,
|
|
y1: current.y + unitNormal.y * offsetY,
|
|
}
|
|
|
|
offsetPoints.push(offsetPoint)
|
|
}
|
|
|
|
const outlinePolygon = makePolygon(offsetPoints, false)
|
|
outlinePolygon.setViewLengthText(false)
|
|
|
|
// 아웃라인 폴리곤의 각 변을 선으로 생성
|
|
for (let i = 0; i < outlinePolygon.points.length; i++) {
|
|
const start = outlinePolygon.points[i]
|
|
const end = outlinePolygon.points[(i + 1) % outlinePolygon.points.length] // 다음 점, 마지막 점의 경우 첫 점으로
|
|
|
|
const line = new QLine([start.x, start.y, end.x, end.y], {
|
|
stroke: 'blue',
|
|
strokeWidth: 2,
|
|
property: 'normal',
|
|
fontSize: 14,
|
|
idx: i,
|
|
})
|
|
|
|
// 선을 배열에 추가
|
|
outLines.push(line)
|
|
canvas.add(line)
|
|
}
|
|
|
|
canvas?.remove(outlinePolygon) //임시 폴리곤을 삭제
|
|
|
|
let parallelLinesIdx = concavePointIndices[0] + 4 //들어간선에 무조건 평행하는 선 찾기
|
|
if (parallelLinesIdx >= outLines.length) {
|
|
parallelLinesIdx = parallelLinesIdx - outLines.length
|
|
}
|
|
|
|
let vertCenterLine = []
|
|
let halfHoriCenterLinePoint = [] //카라바선의 2분할의 1번 배열
|
|
let horiCenterLine = []
|
|
let shorVertCenterLine = []
|
|
|
|
let edgeIndexArray = []
|
|
|
|
if (concavePointIndices[0] % 2 === 0) {
|
|
//concave가 짝수면 좌우로 그려진 ㄷ자
|
|
//케라바 색을 바꾼다
|
|
lines.forEach((line, index) => {
|
|
if (index % 2 === 0) {
|
|
line.line.set('stroke', 'skyblue')
|
|
if (concavePointIndices[0] !== index) {
|
|
edgeIndexArray.push(index)
|
|
}
|
|
}
|
|
})
|
|
|
|
outLines = reSortQlineArray(outLines)
|
|
edgeIndexArray.forEach((idx, index) => {
|
|
//가로라인이 케라바 라인임
|
|
if (concavePointIndices[0] !== idx) {
|
|
//오목이가 아니면 반으로 갈라서 계산
|
|
|
|
//카라바 선의 2분할 치수를 그림
|
|
let halfLength = outLines[idx].length / 2
|
|
let centerLine1 = new QLine([outLines[idx].x1, outLines[idx].y1, outLines[idx].x1, outLines[idx].y1 + halfLength], centerLineOpt)
|
|
canvas.add(centerLine1)
|
|
|
|
let centerLine2 = new QLine([outLines[idx].x1, centerLine1.y2, outLines[idx].x2, centerLine1.y2 + halfLength], centerLineOpt)
|
|
canvas.add(centerLine2)
|
|
canvas.remove(outLines[idx]) //기존 라인 삭제
|
|
|
|
halfHoriCenterLinePoint.push({
|
|
index: idx,
|
|
x1: centerLine1.x1,
|
|
y1: centerLine1.y1,
|
|
x2: centerLine1.x2,
|
|
y2: centerLine1.y2,
|
|
}) //각 카라바 라인의 1번이 마지막점을 잡아서 센터선으로 설정
|
|
}
|
|
})
|
|
|
|
// //각 센터 라인을 그림
|
|
halfHoriCenterLinePoint.forEach((centerPoint) => {
|
|
let tmpX2 = parallelLinesIdx !== centerPoint.index ? concaveLine.line.x2 : outLines[concavePointIndices[0]].x1 //평행선에서 내려오는 선은 아웃라인에 닿아야한다
|
|
|
|
let line = new QLine([centerPoint.x2, centerPoint.y2, tmpX2, centerPoint.y2], centerLineOpt)
|
|
canvas.add(line)
|
|
|
|
line['arrayIndex'] = centerPoint.index //커스텀으로 기존 index를 넣어줌
|
|
vertCenterLine.push(line)
|
|
})
|
|
|
|
vertCenterLine = reSortQlineArray(vertCenterLine)
|
|
lines = reSortQlineArray(lines)
|
|
setTemplateCenterLine(vertCenterLine)
|
|
|
|
//해당라인에서 만나는점을 계산
|
|
vertCenterLine.forEach((vertLine) => {
|
|
if (parallelLinesIdx !== vertLine.arrayIndex) {
|
|
//평행선을 제외한 애들만 네모를 연결
|
|
let nearLine
|
|
let nearOutline
|
|
if (vertLine.arrayIndex > concaveLine.index) {
|
|
//센터에 인덱스가 오목점 보다 크면 다음 작으면 앞에꺼
|
|
nearLine = lines[concaveLine.index + 1]
|
|
nearOutline = outLines[concaveLine.index + 1]
|
|
} else {
|
|
nearLine = lines[concaveLine.index - 1]
|
|
nearOutline = outLines[concaveLine.index - 1]
|
|
}
|
|
|
|
let nearLineY = nearLine.y1
|
|
if (parallelLinesIdx < concaveLine.index) {
|
|
//오목점 위치가 평행선보다 크면 위쪽으로 오목
|
|
nearLineY = nearLine.y2
|
|
}
|
|
|
|
//기존에 있는 라인에서 연장해서 새로 그림
|
|
let centerExtendHoriLine = new QLine([vertLine.x1, nearOutline.line.y1, vertLine.x2, nearOutline.line.y2], centerLineOpt)
|
|
canvas.add(centerExtendHoriLine)
|
|
canvas.remove(nearOutline)
|
|
outLines.splice(nearOutline.idx, 1, centerExtendHoriLine) //아웃라인에 데이터를 다시 넣는다
|
|
|
|
//가로형에선 기본으로 ㄷ자 형태로 한다
|
|
let centerExtendLine = new QLine([vertLine.line.x1, vertLine.line.y1, centerExtendHoriLine.x1, centerExtendHoriLine.y1], centerLineOpt)
|
|
|
|
//오목이가 배열에 반보다 작으면 역 ㄷ자 여서 변경
|
|
if (concavePointIndices[0] < outLines.length / 2) {
|
|
centerExtendLine = new QLine([vertLine.line.x2, vertLine.line.y2, centerExtendHoriLine.x2, centerExtendHoriLine.y2], centerLineOpt)
|
|
}
|
|
|
|
canvas.add(centerExtendLine) //새로그리고
|
|
|
|
let betweenCenterLine = (vertLine.line.x1 + vertLine.line.x2) / 2
|
|
let centerDashLine = new QLine([betweenCenterLine, centerExtendLine.y1, betweenCenterLine, centerExtendLine.y2], dashedCenterLineOpt)
|
|
|
|
canvas.add(centerDashLine)
|
|
horiCenterLine.push(centerDashLine)
|
|
shorVertCenterLine.push(vertLine) //마지막에 가운데 선을 긋기 위해 담음
|
|
} else {
|
|
let longDashLine = halfHoriCenterLinePoint.find((obj) => obj.index === parallelLinesIdx) //평행선
|
|
|
|
let dashCenterExtendLineLength = longDashLine.y2 - longDashLine.y1 //y반개 길이
|
|
let betweenCenterLine = (vertLine.line.x1 + vertLine.line.x2) / 2 //y의 길이
|
|
let totalLength = ((longDashLine.y2 - longDashLine.y1) * 2) / dashCenterExtendLineLength //2개로 나눔
|
|
|
|
//반 쪼개서 그린다
|
|
for (let i = 0; i < totalLength; i++) {
|
|
//2번에 나눠서
|
|
let startY = i === 0 ? longDashLine.y1 : longDashLine.y1 + dashCenterExtendLineLength //시작 하는 y의 좌표
|
|
//x값은 고정이임 //TODO: 지붕 각도 계산법에 의해 재계산해야함
|
|
let centerDashLine = new QLine([betweenCenterLine, startY, betweenCenterLine, startY + dashCenterExtendLineLength], dashedCenterLineOpt)
|
|
canvas.add(centerDashLine)
|
|
horiCenterLine.push(centerDashLine)
|
|
}
|
|
}
|
|
})
|
|
|
|
//마지막에 오목한 외곽선을 연장한다
|
|
const tmpLastOutLine = outLines[concavePointIndices[0]]
|
|
const lastOutLine = new QLine([tmpLastOutLine.x1, shorVertCenterLine[0].y1, tmpLastOutLine.x1, shorVertCenterLine[1].y1], centerLineOpt)
|
|
canvas.add(lastOutLine)
|
|
canvas.remove(tmpLastOutLine)
|
|
|
|
//폴리곤 패턴을 그리기 위해 작성
|
|
let tmpVertCenterLine = outLines.filter((x, index) => index % 2 !== 0) //세로만 찾음
|
|
tmpVertCenterLine = tmpVertCenterLine.concat(vertCenterLine)
|
|
tmpVertCenterLine.sort((a, b) => a.y1 - b.y1)
|
|
tmpVertCenterLine.push(lastOutLine)
|
|
|
|
let roofPatternPolygonArray = []
|
|
let tmpArray = []
|
|
let tmpBigArray = []
|
|
|
|
const lastCenterLine = tmpVertCenterLine[tmpVertCenterLine.length - 1] //마지막 센터라인을 정의
|
|
|
|
for (let i = 0; i < tmpVertCenterLine.length - 1; i++) {
|
|
//-1인건 마지막은 오목한 선이라 돌 필요 없음
|
|
//라인 하나에 두점씩 나온다
|
|
let firstPointObj = {}
|
|
let secondPointObj = {}
|
|
|
|
let x1 = tmpVertCenterLine[i].x1
|
|
let y1 = tmpVertCenterLine[i].y1
|
|
let x2 = tmpVertCenterLine[i].x2
|
|
let y2 = tmpVertCenterLine[i].y2
|
|
|
|
if (i === 2 || i === 4) {
|
|
//작은 네모들
|
|
tmpArray = []
|
|
const prevLine = tmpVertCenterLine[i - 1] //뒤에서 앞라인을 찾는다
|
|
const nextLine = tmpVertCenterLine[i + 1]
|
|
|
|
//내 앞뒤 라인
|
|
const tmpX1 = i === 2 ? prevLine.x1 : nextLine.x1
|
|
const tmpY1 = i === 2 ? prevLine.y1 : nextLine.y1
|
|
const tmpX2 = i === 2 ? prevLine.x2 : nextLine.x2
|
|
const tmpY2 = i === 2 ? prevLine.y2 : nextLine.y2
|
|
|
|
firstPointObj = { x: tmpX1, y: tmpY1 }
|
|
secondPointObj = { x: tmpX2, y: tmpY2 }
|
|
tmpArray.push(firstPointObj)
|
|
tmpArray.push(secondPointObj)
|
|
|
|
//현재 내 선
|
|
firstPointObj = { x: x1, y: y1 }
|
|
secondPointObj = { x: x2, y: y2 }
|
|
tmpArray.push(firstPointObj)
|
|
tmpArray.push(secondPointObj)
|
|
roofPatternPolygonArray.push(tmpArray)
|
|
} else {
|
|
//큰 육각
|
|
if (i === 1 || i === 5) {
|
|
// 큰 폴리곤은 가운데 선으로 되야됨
|
|
if (outLines.length / 2 > concavePointIndices[0]) {
|
|
x2 = i === 1 ? lastCenterLine.x1 : lastCenterLine.x2
|
|
y2 = i === 1 ? lastCenterLine.y1 : lastCenterLine.y2
|
|
} else {
|
|
//오목이가 배열 전체보다 크면 오른쪽 오목이
|
|
x1 = i === 1 ? lastCenterLine.x1 : lastCenterLine.x2
|
|
y1 = i === 1 ? lastCenterLine.y2 : lastCenterLine.y1
|
|
}
|
|
}
|
|
|
|
if (i === 5) {
|
|
//5번일때는 앞에 3번에 선이 필요하다
|
|
let prevX1 = tmpVertCenterLine[i - 2].x1
|
|
let prevY1 = tmpVertCenterLine[i - 2].y1
|
|
let prevX2 = tmpVertCenterLine[i - 2].x2
|
|
let prevY2 = tmpVertCenterLine[i - 2].y2
|
|
firstPointObj = { x: prevX1, y: prevY1 }
|
|
secondPointObj = { x: prevX2, y: prevY2 }
|
|
tmpBigArray.push(firstPointObj)
|
|
tmpBigArray.push(secondPointObj)
|
|
}
|
|
|
|
firstPointObj = { x: x1, y: y1 }
|
|
secondPointObj = { x: x2, y: y2 }
|
|
tmpBigArray.push(firstPointObj)
|
|
tmpBigArray.push(secondPointObj)
|
|
|
|
if (i === 3 || i === 6) {
|
|
roofPatternPolygonArray.push(tmpBigArray)
|
|
tmpBigArray = []
|
|
}
|
|
}
|
|
}
|
|
|
|
setRoofPolygonPattern({ roofPatternPolygonArray, lines })
|
|
} else {
|
|
// 오목한 부분이 세로선일때 아래ㄷ, 위ㄷ
|
|
//라인들을 좌측에서 -> 우측으로 그리는거처럼 데이터 보정
|
|
lines.forEach((line, index) => {
|
|
if (!(index % 2 === 0)) {
|
|
line.line.set('stroke', 'skyblue')
|
|
}
|
|
})
|
|
outLines = reSortQlineArray(outLines)
|
|
outLines.forEach((outline, index) => {
|
|
if (!(index % 2 === 0)) {
|
|
//세로라인이 케라바 라인임
|
|
|
|
if (concavePointIndices[0] !== index) {
|
|
//오목이가 아니면 반으로 갈라서 계산
|
|
|
|
//카라바 선의 2분할 치수를 그림
|
|
let halfLength = outline.length / 2
|
|
let centerLine1 = new QLine([outline.x1, outline.y1, outline.x1 + halfLength, outline.y1], centerLineOpt)
|
|
canvas.add(centerLine1)
|
|
|
|
let centerLine2 = new QLine([centerLine1.x2, outline.y1, centerLine1.x2 + halfLength, outline.y1], centerLineOpt)
|
|
canvas.add(centerLine2)
|
|
canvas.remove(outline) //기존 라인 삭제
|
|
|
|
halfHoriCenterLinePoint.push({
|
|
index: index,
|
|
x1: centerLine1.x1,
|
|
y1: centerLine1.y1,
|
|
x2: centerLine1.x2,
|
|
y2: centerLine1.y2,
|
|
}) //각 카라바 라인의 1번이 마지막점을 잡아서 센터선으로 설정
|
|
}
|
|
}
|
|
})
|
|
|
|
//각 센터 라인을 그림
|
|
halfHoriCenterLinePoint.forEach((centerPoint) => {
|
|
let tmpY2 = parallelLinesIdx !== centerPoint.index ? concaveLine.line.y1 : outLines[concavePointIndices[0]].y2 //평행선에서 내려오는 선은 아웃라인에 닿아야한다
|
|
|
|
let line = new QLine([centerPoint.x2, centerPoint.y1, centerPoint.x2, tmpY2], centerLineOpt)
|
|
canvas.add(line)
|
|
|
|
line['arrayIndex'] = centerPoint.index //커스텀으로 기존 index를 넣어줌
|
|
vertCenterLine.push(line)
|
|
})
|
|
|
|
vertCenterLine = reSortQlineArray(vertCenterLine)
|
|
lines = reSortQlineArray(lines)
|
|
setTemplateCenterLine(vertCenterLine)
|
|
|
|
//해당라인에서 만나는점을 계산
|
|
vertCenterLine.forEach((vertLine) => {
|
|
if (parallelLinesIdx !== vertLine.arrayIndex) {
|
|
//평행선을 제외한 애들만 네모를 연결
|
|
let nearLine
|
|
let nearOutline
|
|
if (vertLine.arrayIndex > concaveLine.index) {
|
|
//센터에 인덱스가 오목점 보다 크면 다음 작으면 앞에꺼
|
|
nearLine = lines[concaveLine.index + 1]
|
|
nearOutline = outLines[concaveLine.index + 1]
|
|
} else {
|
|
nearLine = lines[concaveLine.index - 1]
|
|
nearOutline = outLines[concaveLine.index - 1]
|
|
}
|
|
|
|
let nearLineY = nearLine.y1
|
|
if (parallelLinesIdx < concaveLine.index) {
|
|
//오목점 위치가 평행선보다 크면 위쪽으로 오목
|
|
nearLineY = nearLine.y2
|
|
}
|
|
|
|
let centerExtendLine = new QLine([vertLine.line.x1, nearLineY, nearOutline.x1, nearLineY], centerLineOpt)
|
|
canvas.add(centerExtendLine) //새로그리고
|
|
|
|
//기존에 있는 라인에서 연장해서 새로 그림
|
|
let centerExtendHoriLine = new QLine([nearOutline.line.x1, vertLine.y1, nearOutline.line.x2, vertLine.line.y2], centerLineOpt)
|
|
canvas.add(centerExtendHoriLine)
|
|
canvas.remove(nearOutline)
|
|
outLines.splice(nearOutline.idx, 1, centerExtendHoriLine) //아웃라인에 데이터를 다시 넣는다
|
|
|
|
let betweenCenterLine = (vertLine.line.y1 + vertLine.line.y2) / 2
|
|
let centerDashLine = new QLine([vertLine.line.x1, betweenCenterLine, nearOutline.x1, betweenCenterLine], dashedCenterLineOpt)
|
|
|
|
canvas.add(centerDashLine)
|
|
horiCenterLine.push(centerDashLine)
|
|
shorVertCenterLine.push(vertLine) //마지막에 가운데 선을 긋기 위해 담음
|
|
} else {
|
|
let longDashLine = halfHoriCenterLinePoint.find((obj) => obj.index === parallelLinesIdx)
|
|
|
|
let dashCenterExtendLineLength = longDashLine.x2 - longDashLine.x1
|
|
let betweenCenterLine = (vertLine.line.y1 + vertLine.line.y2) / 2
|
|
let totalLength = ((longDashLine.x2 - longDashLine.x1) * 2) / dashCenterExtendLineLength
|
|
|
|
//반 쪼개서 그린다
|
|
for (let i = 0; i < totalLength; i++) {
|
|
let startX = i === 0 ? longDashLine.x1 : longDashLine.x1 + dashCenterExtendLineLength
|
|
let centerDashLine = new QLine([startX, betweenCenterLine, startX + dashCenterExtendLineLength, betweenCenterLine], dashedCenterLineOpt)
|
|
canvas.add(centerDashLine)
|
|
horiCenterLine.push(centerDashLine)
|
|
}
|
|
}
|
|
})
|
|
|
|
//마지막에 오목한 외곽선을 연장한다
|
|
const tmpLastOutLine = outLines[concavePointIndices[0]]
|
|
const lastOutLine = new QLine([shorVertCenterLine[0].x1, tmpLastOutLine.y1, shorVertCenterLine[1].x1, tmpLastOutLine.y2], centerLineOpt)
|
|
canvas.add(lastOutLine)
|
|
canvas.remove(tmpLastOutLine)
|
|
|
|
let tmpVertCenterLine = outLines.filter((x, index) => index % 2 === 0) //세로만 찾음
|
|
tmpVertCenterLine = tmpVertCenterLine.concat(vertCenterLine)
|
|
tmpVertCenterLine.sort((a, b) => a.x1 - b.x1)
|
|
tmpVertCenterLine.push(lastOutLine)
|
|
|
|
let roofPatternPolygonArray = []
|
|
let tmpArray = []
|
|
let tmpBigArray = []
|
|
|
|
const lastCenterLine = tmpVertCenterLine[tmpVertCenterLine.length - 1] //마지막 센터라인을 정의
|
|
|
|
for (let i = 0; i < tmpVertCenterLine.length - 1; i++) {
|
|
//-1인건 마지막은 오목한 선이라 돌 필요 없음
|
|
//라인 하나에 두점씩 나온다
|
|
let firstPointObj = {}
|
|
let secondPointObj = {}
|
|
|
|
let x1 = tmpVertCenterLine[i].x1
|
|
let y1 = tmpVertCenterLine[i].y1
|
|
let x2 = tmpVertCenterLine[i].x2
|
|
let y2 = tmpVertCenterLine[i].y2
|
|
|
|
if (i === 2 || i === 4) {
|
|
tmpArray = []
|
|
const prevLine = tmpVertCenterLine[i - 1] //뒤에서 앞라인을 찾는다
|
|
const nextLine = tmpVertCenterLine[i + 1]
|
|
|
|
//내 앞뒤 라인
|
|
const tmpX1 = i === 2 ? prevLine.x1 : nextLine.x1
|
|
const tmpY1 = i === 2 ? prevLine.y1 : nextLine.y1
|
|
const tmpX2 = i === 2 ? prevLine.x2 : nextLine.x2
|
|
const tmpY2 = i === 2 ? prevLine.y2 : nextLine.y2
|
|
|
|
firstPointObj = { x: tmpX1, y: tmpY1 }
|
|
secondPointObj = { x: tmpX2, y: tmpY2 }
|
|
tmpArray.push(firstPointObj)
|
|
tmpArray.push(secondPointObj)
|
|
|
|
//현재 내 선
|
|
firstPointObj = { x: x1, y: y1 }
|
|
secondPointObj = { x: x2, y: y2 }
|
|
tmpArray.push(firstPointObj)
|
|
tmpArray.push(secondPointObj)
|
|
roofPatternPolygonArray.push(tmpArray)
|
|
} else {
|
|
if (i === 1 || i === 5) {
|
|
// 큰 폴리곤은 가운데 선으로 되야됨
|
|
if (outLines.length / 2 < concavePointIndices[0]) {
|
|
//오목이가 배열 전체보다 크면 위쪽 방향
|
|
x2 = i === 1 ? lastCenterLine.x2 : lastCenterLine.x1
|
|
y2 = i === 1 ? lastCenterLine.y2 : lastCenterLine.y1
|
|
} else {
|
|
x1 = i === 1 ? lastCenterLine.x1 : lastCenterLine.x2
|
|
y1 = i === 1 ? lastCenterLine.y1 : lastCenterLine.y2
|
|
}
|
|
}
|
|
|
|
if (i === 5) {
|
|
//5번일때는 앞에 3번에 선이 필요하다
|
|
let prevX1 = tmpVertCenterLine[i - 2].x1
|
|
let prevY1 = tmpVertCenterLine[i - 2].y1
|
|
let prevX2 = tmpVertCenterLine[i - 2].x2
|
|
let prevY2 = tmpVertCenterLine[i - 2].y2
|
|
firstPointObj = { x: prevX1, y: prevY1 }
|
|
secondPointObj = { x: prevX2, y: prevY2 }
|
|
tmpBigArray.push(firstPointObj)
|
|
tmpBigArray.push(secondPointObj)
|
|
}
|
|
|
|
firstPointObj = { x: x1, y: y1 }
|
|
secondPointObj = { x: x2, y: y2 }
|
|
tmpBigArray.push(firstPointObj)
|
|
tmpBigArray.push(secondPointObj)
|
|
|
|
if (i === 3 || i === 6) {
|
|
roofPatternPolygonArray.push(tmpBigArray)
|
|
tmpBigArray = []
|
|
}
|
|
}
|
|
}
|
|
setRoofPolygonPattern({ roofPatternPolygonArray, lines })
|
|
}
|
|
canvas?.renderAll()
|
|
}
|
|
|
|
/**
|
|
* 템플릿 B 적용
|
|
*/
|
|
const applyTemplateB = () => {
|
|
if (historyPoints.current.length === 0) {
|
|
changeMode(canvas, Mode.EDIT)
|
|
return
|
|
}
|
|
|
|
const polygon = drawWallPolygon(false)
|
|
const params = {
|
|
eaves: 50,
|
|
edge: 20,
|
|
polygon,
|
|
}
|
|
handleInnerLineColor(polygon)
|
|
// handleOuterLineTemplateB(params)
|
|
console.log(polygon.lines.length)
|
|
if (polygon.lines.length === 4) {
|
|
console.log('4각형')
|
|
handleTemplateBRect(params)
|
|
} else if (polygon.lines.length === 6) {
|
|
console.log('6각형')
|
|
handleTemplateB(params)
|
|
} else if (polygon.lines.length === 8) {
|
|
handleOuterLineTemplateB8Points(polygon)
|
|
}
|
|
|
|
setTemplateType(3)
|
|
}
|
|
|
|
/**
|
|
* 4각형일때 계산 로직
|
|
* @param {obj} params
|
|
*/
|
|
const handleTemplateBRect = (params) => {
|
|
const { eaves, edge, polygon } = params
|
|
const centerLinePoint = {}
|
|
const centerDashLinePoint = {}
|
|
|
|
const qlineOpt = {
|
|
stroke: 'blue',
|
|
strokeWidth: 2,
|
|
selectable: false,
|
|
fontSize: fontSize,
|
|
}
|
|
const qlineOptDash = {
|
|
stroke: 'black',
|
|
strokeWidth: 2,
|
|
strokeDashArray: [5, 5],
|
|
selectable: false,
|
|
fontSize: fontSize,
|
|
}
|
|
const qlineOptDashWithoutLength = {
|
|
...qlineOptDash,
|
|
isActiveLengthText: false,
|
|
}
|
|
|
|
const bigRoofPolygon = []
|
|
const middleRoofPolygon = []
|
|
|
|
polygon.lines.forEach((line, index) => {
|
|
let outline
|
|
if (index === 0) {
|
|
outline = new QLine([line.x1 - edge, line.y1 - eaves, line.x2 - edge, line.y2 + eaves], qlineOpt)
|
|
const centeredPoint = getCenterPoint(line.y1, line.y2)
|
|
centerLinePoint.x1 = line.x1 - edge
|
|
centerLinePoint.y1 = centeredPoint
|
|
centerLinePoint.y2 = centeredPoint
|
|
centerDashLinePoint.y1 = line.y1 - eaves
|
|
centerDashLinePoint.y2 = line.y2 + eaves
|
|
bigRoofPolygon[0] = { x: line.x1 - edge, y: line.y1 - eaves }
|
|
middleRoofPolygon[0] = { x: line.x1 - edge, y: centeredPoint }
|
|
middleRoofPolygon[1] = { x: line.x2 - edge, y: line.y2 + eaves }
|
|
} else if (index === 1) {
|
|
outline = new QLine([line.x1 - edge, line.y1 + eaves, line.x2 + edge, line.y2 + eaves], qlineOpt)
|
|
const centeredPoint = getCenterPoint(line.x1, line.x2)
|
|
centerLinePoint.x2 = line.x2 + edge
|
|
centerDashLinePoint.x1 = centeredPoint
|
|
centerDashLinePoint.x2 = centeredPoint
|
|
} else if (index === 2) {
|
|
outline = new QLine([line.x1 + edge, line.y1 + eaves, line.x2 + edge, line.y2 - eaves], qlineOpt)
|
|
bigRoofPolygon[3] = { x: line.x2 + edge, y: line.y2 - eaves }
|
|
middleRoofPolygon[2] = { x: line.x1 + edge, y: line.y1 + eaves }
|
|
} else if (index === 3) {
|
|
outline = new QLine([line.x1 + edge, line.y1 - eaves, line.x2 - edge, line.y2 - eaves], qlineOpt)
|
|
}
|
|
canvas.add(outline)
|
|
})
|
|
const centerLine = new QLine([centerLinePoint.x1, centerLinePoint.y1, centerLinePoint.x2, centerLinePoint.y2], qlineOpt)
|
|
canvas.add(centerLine)
|
|
const centerDashLine1 = new QLine(
|
|
[centerDashLinePoint.x1, centerDashLinePoint.y1, centerDashLinePoint.x2, getCenterPoint(centerDashLinePoint.y1, centerDashLinePoint.y2)],
|
|
qlineOptDashWithoutLength,
|
|
)
|
|
canvas.add(centerDashLine1)
|
|
const centerDashLine2 = new QLine(
|
|
[centerDashLinePoint.x1, getCenterPoint(centerDashLinePoint.y1, centerDashLinePoint.y2), centerDashLinePoint.x2, centerDashLinePoint.y2],
|
|
qlineOptDashWithoutLength,
|
|
)
|
|
canvas.add(centerDashLine2)
|
|
|
|
bigRoofPolygon[1] = { x: centerLine.x1, y: centerLine.y1 }
|
|
bigRoofPolygon[2] = { x: centerLine.x2, y: centerLine.y2 }
|
|
|
|
middleRoofPolygon[3] = { x: centerLinePoint.x2, y: centerLinePoint.y2 }
|
|
|
|
const roofPatternPolygonArray = []
|
|
roofPatternPolygonArray.push(bigRoofPolygon)
|
|
roofPatternPolygonArray.push(middleRoofPolygon)
|
|
if (roofPatternPolygonArray.length > 0) {
|
|
setRoofPolygonPattern({ roofPatternPolygonArray, lines: polygon.lines })
|
|
}
|
|
|
|
canvas?.renderAll()
|
|
}
|
|
|
|
/**
|
|
* 6각형일때 계산 로직
|
|
* @param {obj} params
|
|
*/
|
|
const handleTemplateB = (params) => {
|
|
const { eaves, edge, polygon } = params
|
|
// 가장 긴 라인이 첫번째일때
|
|
let shapeType = 0
|
|
console.log(polygon)
|
|
const odd = polygon.lines.filter((line, index) => index % 2 === 0)
|
|
const even = polygon.lines.filter((line, index) => index % 2 !== 0)
|
|
const rerangeOdd = chgLineDirectionVertical(odd)
|
|
const rerangeEven = chgLineDirectionHorizontal(even)
|
|
// 가장 긴 라인이 첫번째인지 판단
|
|
chkLengthIndex({ arr: odd, type: 'L' }) !== 0 ? (shapeType = 1) : null
|
|
// 가장 짧은 라인의 인덱스 반환
|
|
const shortIndex = chkLengthIndex({ arr: odd, type: 'S' })
|
|
|
|
const centralLinePoint = {
|
|
x1: 0,
|
|
y1: 0,
|
|
x2: 0,
|
|
y2: 0,
|
|
}
|
|
const centralSubLinePoint = {
|
|
x1: 0,
|
|
y1: 0,
|
|
x2: 0,
|
|
y2: 0,
|
|
}
|
|
const centralDashLinePoint = {
|
|
x1: 0,
|
|
y1: 0,
|
|
x2: 0,
|
|
y2: 0,
|
|
}
|
|
const centralSubDashLinePoint = {
|
|
x1: 0,
|
|
y1: 0,
|
|
x2: 0,
|
|
y2: 0,
|
|
}
|
|
const qlineOpt = {
|
|
stroke: 'blue',
|
|
strokeWidth: 2,
|
|
selectable: false,
|
|
fontSize: fontSize,
|
|
}
|
|
const qlineOptDash = {
|
|
stroke: 'black',
|
|
strokeWidth: 2,
|
|
strokeDashArray: [5, 5],
|
|
selectable: false,
|
|
fontSize: fontSize,
|
|
}
|
|
|
|
/**
|
|
* 지붕 패턴을 위한 폴리곤 좌표 생성
|
|
*/
|
|
const bigRoofPolygon = []
|
|
const middleRoofPolygon = []
|
|
const smallRoofPolygon = []
|
|
|
|
const templateCenterLine = []
|
|
|
|
rerangeOdd.forEach((line, index) => {
|
|
const centeredPoint = getCenterPoint(line.y1, line.y2)
|
|
let points1 = []
|
|
let points2 = []
|
|
if (polygon.shape === 2 || polygon.shape === 3) {
|
|
if (index === 0) {
|
|
points1 = [line.x1 - edge, line.y1 - eaves, line.x1 - edge, centeredPoint]
|
|
points2 = [line.x2 - edge, centeredPoint, line.x2 - edge, line.y2 + eaves]
|
|
centralLinePoint.x1 = line.x1 - edge
|
|
centralLinePoint.y1 = centeredPoint
|
|
centralLinePoint.y2 = centeredPoint
|
|
centralDashLinePoint.y1 = line.y1 - eaves
|
|
centralDashLinePoint.y2 = line.y2 + eaves
|
|
if (polygon.shape === 2) {
|
|
middleRoofPolygon[0] = { x: line.x1 - edge, y: line.y1 - eaves }
|
|
middleRoofPolygon[1] = { x: line.x1 - edge, y: centeredPoint }
|
|
} else {
|
|
bigRoofPolygon[1] = { x: line.x1 - edge, y: centeredPoint }
|
|
bigRoofPolygon[0] = { x: line.x1 - edge, y: line.y1 - eaves }
|
|
middleRoofPolygon[1] = { x: line.x2 - edge, y: line.y2 + eaves }
|
|
}
|
|
} else if (index === 1) {
|
|
if (polygon.shape === 2) {
|
|
points1 = [line.x1 + edge, line.y1 - eaves, line.x1 + edge, centeredPoint]
|
|
points2 = [line.x1 + edge, centeredPoint, line.x2 + edge, line.y2 + eaves]
|
|
centralSubLinePoint.x2 = line.x1 + edge
|
|
centralSubLinePoint.y2 = centeredPoint
|
|
bigRoofPolygon[2] = { x: line.x2 + edge, y: line.y2 + eaves }
|
|
bigRoofPolygon[3] = { x: line.x2 + edge, y: centeredPoint }
|
|
smallRoofPolygon[0] = { x: line.x1 + edge, y: centeredPoint }
|
|
smallRoofPolygon[1] = { x: line.x1 + edge, y: line.y1 - eaves }
|
|
} else {
|
|
points1 = [line.x1 + edge, getCenterPoint(rerangeOdd[2].y1, rerangeOdd[2].y2), line.x2 + edge, line.y2 + eaves]
|
|
points2 = [line.x1, getCenterPoint(rerangeOdd[2].y1, rerangeOdd[2].y2), line.x1, line.y1 + eaves]
|
|
centralLinePoint.x2 = line.x1 + edge
|
|
centralSubLinePoint.y1 = getCenterPoint(rerangeOdd[2].y1, rerangeOdd[2].y2)
|
|
centralSubLinePoint.y2 = getCenterPoint(rerangeOdd[2].y1, rerangeOdd[2].y2)
|
|
bigRoofPolygon[3] = { x: line.x1 + edge, y: centralSubLinePoint.y1 }
|
|
middleRoofPolygon[2] = { x: line.x2 + edge, y: line.y2 + eaves }
|
|
smallRoofPolygon[0] = { x: line.x1, y: centralSubLinePoint.y1 }
|
|
smallRoofPolygon[1] = { x: line.x1, y: line.y1 + eaves }
|
|
}
|
|
} else if (index === 2) {
|
|
if (polygon.shape === 2) {
|
|
points1 = [line.x1 + edge, line.y1 - eaves, line.x2 + edge, centralSubLinePoint.y2]
|
|
points2 = [line.x2, line.y2 - eaves, line.x2, centralSubLinePoint.y2]
|
|
bigRoofPolygon[4] = { x: line.x2 + edge, y: centralSubLinePoint.y2 }
|
|
middleRoofPolygon[3] = { x: line.x1 + edge, y: line.y1 - eaves }
|
|
smallRoofPolygon[2] = { x: line.x2, y: line.y2 - eaves }
|
|
smallRoofPolygon[3] = { x: line.x2, y: centralSubLinePoint.y2 }
|
|
} else {
|
|
points1 = [line.x1 + edge, line.y1 - eaves, line.x2 + edge, centeredPoint]
|
|
points2 = [line.x2 + edge, centeredPoint, line.x2 + edge, line.y2 + eaves]
|
|
bigRoofPolygon[4] = { x: line.x1 + edge, y: centralSubLinePoint.y2 }
|
|
bigRoofPolygon[5] = { x: line.x1 + edge, y: line.y1 - eaves }
|
|
smallRoofPolygon[2] = { x: line.x2 + edge, y: line.y2 + eaves }
|
|
smallRoofPolygon[3] = { x: line.x2 + edge, y: centeredPoint }
|
|
}
|
|
}
|
|
} else {
|
|
if (index === 0) {
|
|
points1 = [line.x1 - edge, line.y1 - eaves, line.x2 - edge, line.y2 + eaves]
|
|
centralSubLinePoint.x1 = line.x1 - edge
|
|
centralSubLinePoint.y1 = getCenterPoint(line.y1, line.y2)
|
|
centralSubLinePoint.y2 = getCenterPoint(line.y1, line.y2)
|
|
if (polygon.shape === 1) {
|
|
bigRoofPolygon[0] = { x: line.x1 - edge, y: line.y1 - eaves }
|
|
bigRoofPolygon[1] = { x: line.x1 - edge, y: getCenterPoint(line.y1, line.y2) }
|
|
smallRoofPolygon[0] = { x: line.x1 - edge, y: getCenterPoint(line.y1, line.y2) }
|
|
smallRoofPolygon[1] = { x: line.x2 - edge, y: line.y2 + eaves }
|
|
} else {
|
|
bigRoofPolygon[0] = { x: line.x1 - edge, y: centeredPoint }
|
|
bigRoofPolygon[1] = { x: line.x1 - edge, y: line.y2 + eaves }
|
|
smallRoofPolygon[0] = { x: line.x1 - edge, y: line.y1 - eaves }
|
|
smallRoofPolygon[1] = { x: centralSubLinePoint.x1, y: centralSubLinePoint.y1 }
|
|
}
|
|
} else if (index === 1) {
|
|
if (polygon.shape === 1) {
|
|
points1 = [line.x1 - edge, centralSubLinePoint.y1, line.x2 - edge, line.y2 + eaves]
|
|
points2 = [line.x1, centralSubLinePoint.y1, line.x1, line.y1 + eaves]
|
|
centralLinePoint.x1 = line.x1 - edge
|
|
centralSubLinePoint.x2 = line.x2
|
|
bigRoofPolygon[2] = { x: line.x1 - edge, y: centralSubLinePoint.y2 }
|
|
middleRoofPolygon[1] = { x: line.x2 - edge, y: line.y2 + eaves }
|
|
smallRoofPolygon[2] = { x: line.x1, y: line.y1 + eaves }
|
|
} else {
|
|
points1 = [line.x1 + edge, line.y1 - eaves, line.x2 + edge, centeredPoint]
|
|
points2 = [line.x2 + edge, centeredPoint, line.x2 + edge, line.y2 + eaves]
|
|
centralLinePoint.x2 = line.x1 + edge
|
|
centralLinePoint.y1 = centeredPoint
|
|
centralLinePoint.y2 = centeredPoint
|
|
centralDashLinePoint.y1 = line.y1 - eaves
|
|
centralDashLinePoint.y2 = line.y2 + eaves
|
|
bigRoofPolygon[2] = { x: line.x2 + edge, y: line.y2 + eaves }
|
|
bigRoofPolygon[3] = { x: line.x2 + edge, y: centralLinePoint.y2 }
|
|
middleRoofPolygon[3] = { x: line.x1 + edge, y: line.y1 - eaves }
|
|
}
|
|
} else {
|
|
if (polygon.shape === 1) {
|
|
points1 = [line.x1 + edge, line.y1 - eaves, line.x1 + edge, centeredPoint]
|
|
points2 = [line.x2 + edge, centeredPoint, line.x2 + edge, line.y2 + eaves]
|
|
centralLinePoint.x2 = line.x1 + edge
|
|
centralLinePoint.y1 = centeredPoint
|
|
centralLinePoint.y2 = centeredPoint
|
|
centralDashLinePoint.y1 = line.y1 - eaves
|
|
centralDashLinePoint.y2 = line.y2 + eaves
|
|
bigRoofPolygon[5] = { x: line.x1 + edge, y: line.y1 - eaves }
|
|
middleRoofPolygon[2] = { x: line.x2 + edge, y: line.y2 + eaves }
|
|
} else {
|
|
points1 = [line.x1 - edge, line.y1 - eaves, line.x2 - edge, centralSubLinePoint.y1]
|
|
points2 = [line.x2, line.y2 - eaves, line.x2, centralSubLinePoint.y2]
|
|
centralLinePoint.x1 = line.x1 - edge
|
|
centralSubLinePoint.x2 = line.x2
|
|
bigRoofPolygon[4] = { x: line.x2 - edge, y: centralLinePoint.y1 }
|
|
bigRoofPolygon[5] = { x: line.x2 - edge, y: centralSubLinePoint.y2 }
|
|
middleRoofPolygon[0] = { x: line.x1 - edge, y: line.y1 - eaves }
|
|
smallRoofPolygon[2] = { x: line.x2, y: centralSubLinePoint.y2 }
|
|
smallRoofPolygon[3] = { x: line.x2, y: line.y2 - eaves }
|
|
}
|
|
}
|
|
}
|
|
|
|
if (points1.length > 0) {
|
|
const subLine1 = new QLine(points1, qlineOpt)
|
|
canvas.add(subLine1)
|
|
}
|
|
if (points2.length > 0) {
|
|
const subLine2 = new QLine(points2, qlineOpt)
|
|
canvas.add(subLine2)
|
|
}
|
|
})
|
|
|
|
rerangeEven.forEach((line, index) => {
|
|
let points = []
|
|
if (polygon.shape === 2 || polygon.shape === 3) {
|
|
if (index === 0) {
|
|
points = [line.x1 - edge, line.y1 + eaves, line.x2 + edge, line.y2 + eaves]
|
|
if (polygon.shape === 3) {
|
|
centralDashLinePoint.x1 = getCenterPoint(line.x1, line.x2)
|
|
centralDashLinePoint.x2 = getCenterPoint(line.x1, line.x2)
|
|
}
|
|
} else if (index === 2) {
|
|
points = [line.x1 - edge, line.y1 - eaves, line.x2 + edge, line.y2 - eaves]
|
|
if (polygon.shape === 2) {
|
|
centralDashLinePoint.x1 = getCenterPoint(line.x1, line.x2)
|
|
centralDashLinePoint.x2 = getCenterPoint(line.x1, line.x2)
|
|
centralLinePoint.x2 = line.x2 + edge
|
|
}
|
|
} else {
|
|
if (polygon.shape === 2) {
|
|
const subLines = [
|
|
[line.x1, line.y1 - eaves, line.x2 + edge, line.y2 - eaves],
|
|
[line.x1, centralSubLinePoint.y2, centralSubLinePoint.x2, centralSubLinePoint.y2],
|
|
]
|
|
subLines.forEach((sLine, index) => {
|
|
const subLine = new QLine(sLine, qlineOpt)
|
|
if (index === 1) {
|
|
templateCenterLine.push(subLine)
|
|
}
|
|
canvas.add(subLine)
|
|
})
|
|
centralSubDashLinePoint.x1 = getCenterPoint(line.x1, line.x2 + edge)
|
|
centralSubDashLinePoint.x2 = getCenterPoint(line.x1, line.x2 + edge)
|
|
centralSubDashLinePoint.y1 = line.y1 - eaves
|
|
centralSubDashLinePoint.y2 = centralSubLinePoint.y2
|
|
} else {
|
|
const subLines = [
|
|
[line.x1, line.y1 + eaves, line.x2 + edge, line.y2 + eaves],
|
|
[line.x1, centralSubLinePoint.y1, line.x2 + edge, centralSubLinePoint.y2],
|
|
]
|
|
subLines.forEach((sLine, index) => {
|
|
const subLine = new QLine(sLine, qlineOpt)
|
|
if (index === 1) {
|
|
templateCenterLine.push(subLine)
|
|
}
|
|
canvas.add(subLine)
|
|
})
|
|
centralSubDashLinePoint.x1 = getCenterPoint(line.x1, line.x2 + edge)
|
|
centralSubDashLinePoint.x2 = getCenterPoint(line.x1, line.x2 + edge)
|
|
centralSubDashLinePoint.y1 = centralSubLinePoint.y1
|
|
centralSubDashLinePoint.y2 = line.y2 + eaves
|
|
}
|
|
}
|
|
} else {
|
|
if (index === 0) {
|
|
if (polygon.shape === 1) {
|
|
const subLines = [
|
|
[centralSubLinePoint.x1, centralSubLinePoint.y1, centralSubLinePoint.x2, centralSubLinePoint.y2],
|
|
[line.x1 - edge, line.y1 + eaves, line.x2, line.y1 + eaves],
|
|
]
|
|
subLines.forEach((sLine, index) => {
|
|
const subLine = new QLine(sLine, qlineOpt)
|
|
if (index === 0) {
|
|
templateCenterLine.push(subLine)
|
|
}
|
|
canvas.add(subLine)
|
|
})
|
|
centralSubDashLinePoint.x1 = getCenterPoint(line.x1 - edge, line.x2)
|
|
centralSubDashLinePoint.x2 = getCenterPoint(line.x1 - edge, line.x2)
|
|
centralSubDashLinePoint.y1 = centralSubLinePoint.y1
|
|
centralSubDashLinePoint.y2 = line.y2 + eaves
|
|
} else {
|
|
points = [line.x1 - edge, line.y1 + eaves, line.x2 + edge, line.y2 + eaves]
|
|
}
|
|
} else if (index === 1) {
|
|
if (polygon.shape === 1) {
|
|
points = [line.x1 - edge, line.y1 + eaves, line.x2 + edge, line.y2 + eaves]
|
|
centralDashLinePoint.x1 = getCenterPoint(line.x1, line.x2)
|
|
centralDashLinePoint.x2 = getCenterPoint(line.x1, line.x2)
|
|
} else {
|
|
points = [line.x1 - edge, line.y1 - eaves, line.x2 + edge, line.y2 - eaves]
|
|
centralDashLinePoint.x1 = getCenterPoint(line.x1, line.x2)
|
|
centralDashLinePoint.x2 = getCenterPoint(line.x1, line.x2)
|
|
}
|
|
} else {
|
|
if (polygon.shape === 1) {
|
|
points = [line.x1 - edge, line.y1 - eaves, line.x2 + edge, line.y2 - eaves]
|
|
} else {
|
|
const subLines = [
|
|
[centralSubLinePoint.x1, centralSubLinePoint.y1, centralSubLinePoint.x2, centralSubLinePoint.y2],
|
|
[line.x1 - edge, line.y1 - eaves, line.x2, line.y1 - eaves],
|
|
]
|
|
subLines.forEach((sLine, index) => {
|
|
const subLine = new QLine(sLine, qlineOpt)
|
|
if (index === 0) {
|
|
templateCenterLine.push(subLine)
|
|
}
|
|
canvas.add(subLine)
|
|
})
|
|
|
|
centralSubDashLinePoint.x1 = getCenterPoint(line.x1 - edge, line.x2)
|
|
centralSubDashLinePoint.x2 = getCenterPoint(line.x1 - edge, line.x2)
|
|
centralSubDashLinePoint.y1 = line.y1 - eaves
|
|
centralSubDashLinePoint.y2 = centralSubLinePoint.y2
|
|
}
|
|
}
|
|
}
|
|
|
|
if (points.length > 0) {
|
|
const subLine = new QLine(points, qlineOpt)
|
|
canvas.add(subLine)
|
|
}
|
|
})
|
|
|
|
const centralLine = new QLine([centralLinePoint.x1, centralLinePoint.y1, centralLinePoint.x2, centralLinePoint.y2], qlineOpt)
|
|
canvas.add(centralLine)
|
|
const centralDashLine1 = new QLine([centralDashLinePoint.x1, centralDashLinePoint.y1, centralDashLinePoint.x1, centralLinePoint.y2], qlineOptDash)
|
|
canvas.add(centralDashLine1)
|
|
const centralDashLine2 = new QLine([centralDashLine1.x2, centralDashLine1.y2, centralDashLinePoint.x2, centralDashLinePoint.y2], qlineOptDash)
|
|
canvas.add(centralDashLine2)
|
|
const centralSubDashLine = new QLine(
|
|
[centralSubDashLinePoint.x1, centralSubDashLinePoint.y1, centralSubDashLinePoint.x2, centralSubDashLinePoint.y2],
|
|
qlineOptDash,
|
|
)
|
|
canvas.add(centralSubDashLine)
|
|
templateCenterLine.push(centralLine)
|
|
|
|
if (polygon.shape === 1) {
|
|
bigRoofPolygon[3] = { x: centralLine.x1, y: centralLine.y1 }
|
|
bigRoofPolygon[4] = { x: centralLine.x2, y: centralLine.y2 }
|
|
middleRoofPolygon[0] = { x: centralLine.x1, y: centralLine.y1 }
|
|
middleRoofPolygon[3] = { x: centralLine.x2, y: centralLine.y2 }
|
|
smallRoofPolygon[3] = { x: centralSubLinePoint.x2, y: centralSubLinePoint.y2 }
|
|
} else if (polygon.shape === 2) {
|
|
bigRoofPolygon[0] = { x: centralLinePoint.x1, y: centralLinePoint.y1 }
|
|
bigRoofPolygon[1] = { x: centralLinePoint.x1, y: centralDashLinePoint.y2 }
|
|
bigRoofPolygon[5] = { x: centralLinePoint.x2, y: centralLinePoint.y2 }
|
|
middleRoofPolygon[2] = { x: centralLine.x2, y: centralLine.y2 }
|
|
} else if (polygon.shape === 3) {
|
|
bigRoofPolygon[2] = { x: centralLine.x2, y: centralLine.y2 }
|
|
middleRoofPolygon[0] = { x: centralLine.x1, y: centralLine.y1 }
|
|
middleRoofPolygon[3] = { x: centralLine.x2, y: centralLine.y2 }
|
|
} else if (polygon.shape === 4) {
|
|
middleRoofPolygon[1] = { x: centralLine.x1, y: centralLine.y1 }
|
|
middleRoofPolygon[2] = { x: centralLine.x2, y: centralLine.y2 }
|
|
}
|
|
|
|
const roofPatternPolygonArray = []
|
|
// if (bigRoofPolygon.length > 0 && middleRoofPolygon.length > 0 && smallRoofPolygon.length > 0) {
|
|
roofPatternPolygonArray.push(bigRoofPolygon)
|
|
roofPatternPolygonArray.push(middleRoofPolygon)
|
|
roofPatternPolygonArray.push(smallRoofPolygon)
|
|
setRoofPolygonPattern({ roofPatternPolygonArray, lines: polygon.lines })
|
|
setTemplateCenterLine(templateCenterLine)
|
|
// }
|
|
|
|
canvas?.renderAll()
|
|
}
|
|
|
|
const handleOuterLineTemplateB8Points = (polygon, offsetInputX = 20, offsetInputY = 50) => {
|
|
let offsetPoints = []
|
|
|
|
const originalMax = 71
|
|
const transformedMax = 100
|
|
|
|
let lines = [] //내각라인
|
|
let outLines = [] //아웃라인
|
|
let halfLength = 0 //선길이
|
|
let offsetX
|
|
let offsetY
|
|
|
|
const dashedCenterLineOpt = {
|
|
stroke: 'black',
|
|
strokeWidth: 1,
|
|
property: 'centerLine',
|
|
strokeDashArray: [8, 4],
|
|
fontSize: 14,
|
|
}
|
|
|
|
const centerLineOpt = {
|
|
stroke: 'blue',
|
|
strokeWidth: 2,
|
|
property: 'bigHoriCenter',
|
|
fontSize: 14,
|
|
}
|
|
|
|
// 폴리곤의 각 변을 선으로 생성
|
|
for (let i = 0; i < polygon.points.length; i++) {
|
|
const start = polygon.points[i]
|
|
const end = polygon.points[(i + 1) % polygon.points.length] // 다음 점, 마지막 점의 경우 첫 점으로
|
|
|
|
const line = new QLine([start.x, start.y, end.x, end.y], {
|
|
stroke: '#A0D468',
|
|
strokeWidth: 2,
|
|
property: 'normal',
|
|
fontSize: 14,
|
|
})
|
|
|
|
// 선을 배열에 추가
|
|
lines.push(line)
|
|
canvas.add(line)
|
|
}
|
|
|
|
offsetInputY = offsetInputY !== 0 ? offsetInputY : offsetInputX
|
|
|
|
const sortedIndex = getStartIndex(polygon.lines)
|
|
let tmpArraySorted = rearrangeArray(polygon.lines, sortedIndex)
|
|
setSortedArray(tmpArraySorted) //recoil에 넣음
|
|
|
|
const points = tmpArraySorted.map((line) => ({
|
|
x: line.x1,
|
|
y: line.y1,
|
|
}))
|
|
|
|
//좌표 재정렬
|
|
function reSortQlineArray(array) {
|
|
let tmpArray = []
|
|
let minX, minY, maxX, maxY
|
|
let tmp
|
|
array.forEach((arr, index) => {
|
|
tmp = arr
|
|
if (arr.x2 < arr.x1 || arr.y2 < arr.y1) {
|
|
minX = arr.x2
|
|
minY = arr.y2
|
|
maxX = arr.x1
|
|
maxY = arr.y1
|
|
tmp['x1'] = minX
|
|
tmp['y1'] = minY
|
|
tmp['x2'] = maxX
|
|
tmp['y2'] = maxY
|
|
tmp.line['x1'] = minX
|
|
tmp.line['y1'] = minY
|
|
tmp.line['x2'] = maxX
|
|
tmp.line['y2'] = maxY
|
|
}
|
|
tmpArray.push(tmp)
|
|
})
|
|
|
|
return tmpArray
|
|
}
|
|
|
|
// 오목한 부분 인덱스 찾기
|
|
const concaveIndicesObj = findConcavePointIndices(points) //오목한 부분을 제외한 인덱스
|
|
let concavePointIndices = concaveIndicesObj.concavePointIndices
|
|
|
|
const concaveLine = {
|
|
index: concavePointIndices[0],
|
|
line: lines[concavePointIndices[0]],
|
|
}
|
|
|
|
for (let i = 0; i < points.length; i++) {
|
|
let prev = points[(i - 1 + points.length) % points.length]
|
|
let current = points[i]
|
|
let next = points[(i + 1) % points.length]
|
|
|
|
// 두 벡터 계산 (prev -> current, current -> next)
|
|
let vector1 = { x: current.x - prev.x, y: current.y - prev.y }
|
|
let vector2 = { x: next.x - current.x, y: next.y - current.y }
|
|
|
|
// 벡터의 길이 계산
|
|
let length1 = Math.sqrt(vector1.x * vector1.x + vector1.y * vector1.y)
|
|
let length2 = Math.sqrt(vector2.x * vector2.x + vector2.y * vector2.y)
|
|
|
|
// 벡터를 단위 벡터로 정규화
|
|
let unitVector1 = { x: vector1.x / length1, y: vector1.y / length1 }
|
|
let unitVector2 = { x: vector2.x / length2, y: vector2.y / length2 }
|
|
|
|
// 법선 벡터 계산 (왼쪽 방향)
|
|
let normal1 = { x: -unitVector1.y, y: unitVector1.x }
|
|
let normal2 = { x: -unitVector2.y, y: unitVector2.x }
|
|
|
|
// 법선 벡터 평균 계산
|
|
let averageNormal = {
|
|
x: (normal1.x + normal2.x) / 2,
|
|
y: (normal1.y + normal2.y) / 2,
|
|
}
|
|
|
|
// 평균 법선 벡터를 단위 벡터로 정규화
|
|
let lengthNormal = Math.sqrt(averageNormal.x * averageNormal.x + averageNormal.y * averageNormal.y)
|
|
let unitNormal = {
|
|
x: averageNormal.x / lengthNormal,
|
|
y: averageNormal.y / lengthNormal,
|
|
}
|
|
|
|
offsetX = (offsetInputX / transformedMax) * originalMax * 2
|
|
offsetY = (offsetInputY / transformedMax) * originalMax * 2
|
|
|
|
// 오프셋 적용
|
|
let offsetPoint = {
|
|
x1: current.x + unitNormal.x * offsetX,
|
|
y1: current.y + unitNormal.y * offsetY,
|
|
}
|
|
|
|
offsetPoints.push(offsetPoint)
|
|
}
|
|
|
|
const outlinePolygon = makePolygon(offsetPoints, false)
|
|
outlinePolygon.setViewLengthText(false)
|
|
|
|
// 아웃라인 폴리곤의 각 변을 선으로 생성
|
|
for (let i = 0; i < outlinePolygon.points.length; i++) {
|
|
const start = outlinePolygon.points[i]
|
|
const end = outlinePolygon.points[(i + 1) % outlinePolygon.points.length] // 다음 점, 마지막 점의 경우 첫 점으로
|
|
|
|
const line = new QLine([start.x, start.y, end.x, end.y], {
|
|
stroke: 'blue',
|
|
strokeWidth: 2,
|
|
property: 'normal',
|
|
fontSize: 14,
|
|
idx: i,
|
|
})
|
|
|
|
// 선을 배열에 추가
|
|
outLines.push(line)
|
|
canvas.add(line)
|
|
}
|
|
|
|
canvas?.remove(outlinePolygon) //임시 폴리곤을 삭제
|
|
|
|
let parallelLinesIdx = concavePointIndices[0] + 4 //들어간선에 무조건 평행하는 선 찾기
|
|
if (parallelLinesIdx >= outLines.length) {
|
|
parallelLinesIdx = parallelLinesIdx - outLines.length
|
|
}
|
|
|
|
let vertCenterLine = []
|
|
let halfHoriCenterLinePoint = [] //카라바선의 2분할의 1번 배열
|
|
let horiCenterLine = []
|
|
let shorVertCenterLine = []
|
|
|
|
let edgeIndexArray = []
|
|
|
|
if (concavePointIndices[0] % 2 === 0) {
|
|
//concave가 짝수면 좌우로 그려진 ㄷ자
|
|
//케라바 색을 바꾼다
|
|
lines.forEach((line, index) => {
|
|
if (index % 2 === 0) {
|
|
line.line.set('stroke', 'skyblue')
|
|
if (concavePointIndices[0] !== index) {
|
|
edgeIndexArray.push(index)
|
|
}
|
|
}
|
|
})
|
|
|
|
outLines = reSortQlineArray(outLines)
|
|
edgeIndexArray.forEach((idx, index) => {
|
|
//가로라인이 케라바 라인임
|
|
if (concavePointIndices[0] !== idx) {
|
|
//오목이가 아니면 반으로 갈라서 계산
|
|
|
|
//카라바 선의 2분할 치수를 그림
|
|
let halfLength = outLines[idx].length / 2
|
|
let centerLine1 = new QLine([outLines[idx].x1, outLines[idx].y1, outLines[idx].x1, outLines[idx].y1 + halfLength], centerLineOpt)
|
|
canvas.add(centerLine1)
|
|
|
|
let centerLine2 = new QLine([outLines[idx].x1, centerLine1.y2, outLines[idx].x2, centerLine1.y2 + halfLength], centerLineOpt)
|
|
canvas.add(centerLine2)
|
|
canvas.remove(outLines[idx]) //기존 라인 삭제
|
|
|
|
halfHoriCenterLinePoint.push({
|
|
index: idx,
|
|
x1: centerLine1.x1,
|
|
y1: centerLine1.y1,
|
|
x2: centerLine1.x2,
|
|
y2: centerLine1.y2,
|
|
}) //각 카라바 라인의 1번이 마지막점을 잡아서 센터선으로 설정
|
|
}
|
|
})
|
|
|
|
// //각 센터 라인을 그림
|
|
halfHoriCenterLinePoint.forEach((centerPoint) => {
|
|
let tmpX2 = parallelLinesIdx !== centerPoint.index ? concaveLine.line.x2 : outLines[concavePointIndices[0]].x1 //평행선에서 내려오는 선은 아웃라인에 닿아야한다
|
|
|
|
let line = new QLine([centerPoint.x2, centerPoint.y2, tmpX2, centerPoint.y2], centerLineOpt)
|
|
canvas.add(line)
|
|
|
|
line['arrayIndex'] = centerPoint.index //커스텀으로 기존 index를 넣어줌
|
|
vertCenterLine.push(line)
|
|
})
|
|
|
|
vertCenterLine = reSortQlineArray(vertCenterLine)
|
|
lines = reSortQlineArray(lines)
|
|
setTemplateCenterLine(vertCenterLine)
|
|
|
|
//해당라인에서 만나는점을 계산
|
|
vertCenterLine.forEach((vertLine) => {
|
|
if (parallelLinesIdx !== vertLine.arrayIndex) {
|
|
//평행선을 제외한 애들만 네모를 연결
|
|
let nearLine
|
|
let nearOutline
|
|
if (vertLine.arrayIndex > concaveLine.index) {
|
|
//센터에 인덱스가 오목점 보다 크면 다음 작으면 앞에꺼
|
|
nearLine = lines[concaveLine.index + 1]
|
|
nearOutline = outLines[concaveLine.index + 1]
|
|
} else {
|
|
nearLine = lines[concaveLine.index - 1]
|
|
nearOutline = outLines[concaveLine.index - 1]
|
|
}
|
|
|
|
let nearLineY = nearLine.y1
|
|
if (parallelLinesIdx < concaveLine.index) {
|
|
//오목점 위치가 평행선보다 크면 위쪽으로 오목
|
|
nearLineY = nearLine.y2
|
|
}
|
|
|
|
//기존에 있는 라인에서 연장해서 새로 그림
|
|
let centerExtendHoriLine = new QLine([vertLine.x1, nearOutline.line.y1, vertLine.x2, nearOutline.line.y2], centerLineOpt)
|
|
canvas.add(centerExtendHoriLine)
|
|
canvas.remove(nearOutline)
|
|
outLines.splice(nearOutline.idx, 1, centerExtendHoriLine) //아웃라인에 데이터를 다시 넣는다
|
|
|
|
//가로형에선 기본으로 ㄷ자 형태로 한다
|
|
let centerExtendLine = new QLine([vertLine.line.x1, vertLine.line.y1, centerExtendHoriLine.x1, centerExtendHoriLine.y1], centerLineOpt)
|
|
|
|
//오목이가 배열에 반보다 작으면 역 ㄷ자 여서 변경
|
|
if (concavePointIndices[0] < outLines.length / 2) {
|
|
centerExtendLine = new QLine([vertLine.line.x2, vertLine.line.y2, centerExtendHoriLine.x2, centerExtendHoriLine.y2], centerLineOpt)
|
|
}
|
|
|
|
canvas.add(centerExtendLine) //새로그리고
|
|
|
|
let betweenCenterLine = (vertLine.line.x1 + vertLine.line.x2) / 2
|
|
let centerDashLine = new QLine([betweenCenterLine, centerExtendLine.y1, betweenCenterLine, centerExtendLine.y2], dashedCenterLineOpt)
|
|
|
|
canvas.add(centerDashLine)
|
|
horiCenterLine.push(centerDashLine)
|
|
shorVertCenterLine.push(vertLine) //마지막에 가운데 선을 긋기 위해 담음
|
|
} else {
|
|
let longDashLine = halfHoriCenterLinePoint.find((obj) => obj.index === parallelLinesIdx) //평행선
|
|
|
|
let dashCenterExtendLineLength = longDashLine.y2 - longDashLine.y1 //y반개 길이
|
|
let betweenCenterLine = (vertLine.line.x1 + vertLine.line.x2) / 2 //y의 길이
|
|
let totalLength = ((longDashLine.y2 - longDashLine.y1) * 2) / dashCenterExtendLineLength //2개로 나눔
|
|
|
|
//반 쪼개서 그린다
|
|
for (let i = 0; i < totalLength; i++) {
|
|
//2번에 나눠서
|
|
let startY = i === 0 ? longDashLine.y1 : longDashLine.y1 + dashCenterExtendLineLength //시작 하는 y의 좌표
|
|
//x값은 고정이임 //TODO: 지붕 각도 계산법에 의해 재계산해야함
|
|
let centerDashLine = new QLine([betweenCenterLine, startY, betweenCenterLine, startY + dashCenterExtendLineLength], dashedCenterLineOpt)
|
|
canvas.add(centerDashLine)
|
|
horiCenterLine.push(centerDashLine)
|
|
}
|
|
}
|
|
})
|
|
|
|
//마지막에 오목한 외곽선을 연장한다
|
|
const tmpLastOutLine = outLines[concavePointIndices[0]]
|
|
const lastOutLine = new QLine([tmpLastOutLine.x1, shorVertCenterLine[0].y1, tmpLastOutLine.x1, shorVertCenterLine[1].y1], centerLineOpt)
|
|
canvas.add(lastOutLine)
|
|
canvas.remove(tmpLastOutLine)
|
|
|
|
//폴리곤 패턴을 그리기 위해 작성
|
|
let tmpVertCenterLine = outLines.filter((x, index) => index % 2 !== 0) //세로만 찾음
|
|
tmpVertCenterLine = tmpVertCenterLine.concat(vertCenterLine)
|
|
tmpVertCenterLine.sort((a, b) => a.y1 - b.y1)
|
|
tmpVertCenterLine.push(lastOutLine)
|
|
|
|
let roofPatternPolygonArray = []
|
|
let tmpArray = []
|
|
let tmpBigArray = []
|
|
|
|
const lastCenterLine = tmpVertCenterLine[tmpVertCenterLine.length - 1] //마지막 센터라인을 정의
|
|
|
|
for (let i = 0; i < tmpVertCenterLine.length - 1; i++) {
|
|
//-1인건 마지막은 오목한 선이라 돌 필요 없음
|
|
//라인 하나에 두점씩 나온다
|
|
let firstPointObj = {}
|
|
let secondPointObj = {}
|
|
|
|
let x1 = tmpVertCenterLine[i].x1
|
|
let y1 = tmpVertCenterLine[i].y1
|
|
let x2 = tmpVertCenterLine[i].x2
|
|
let y2 = tmpVertCenterLine[i].y2
|
|
|
|
if (i === 2 || i === 4) {
|
|
//작은 네모들
|
|
tmpArray = []
|
|
const prevLine = tmpVertCenterLine[i - 1] //뒤에서 앞라인을 찾는다
|
|
const nextLine = tmpVertCenterLine[i + 1]
|
|
|
|
//내 앞뒤 라인
|
|
const tmpX1 = i === 2 ? prevLine.x1 : nextLine.x1
|
|
const tmpY1 = i === 2 ? prevLine.y1 : nextLine.y1
|
|
const tmpX2 = i === 2 ? prevLine.x2 : nextLine.x2
|
|
const tmpY2 = i === 2 ? prevLine.y2 : nextLine.y2
|
|
|
|
firstPointObj = { x: tmpX1, y: tmpY1 }
|
|
secondPointObj = { x: tmpX2, y: tmpY2 }
|
|
tmpArray.push(firstPointObj)
|
|
tmpArray.push(secondPointObj)
|
|
|
|
//현재 내 선
|
|
firstPointObj = { x: x1, y: y1 }
|
|
secondPointObj = { x: x2, y: y2 }
|
|
tmpArray.push(firstPointObj)
|
|
tmpArray.push(secondPointObj)
|
|
roofPatternPolygonArray.push(tmpArray)
|
|
} else {
|
|
//큰 육각
|
|
if (i === 1 || i === 5) {
|
|
// 큰 폴리곤은 가운데 선으로 되야됨
|
|
if (outLines.length / 2 > concavePointIndices[0]) {
|
|
x2 = i === 1 ? lastCenterLine.x1 : lastCenterLine.x2
|
|
y2 = i === 1 ? lastCenterLine.y1 : lastCenterLine.y2
|
|
} else {
|
|
//오목이가 배열 전체보다 크면 오른쪽 오목이
|
|
x1 = i === 1 ? lastCenterLine.x1 : lastCenterLine.x2
|
|
y1 = i === 1 ? lastCenterLine.y2 : lastCenterLine.y1
|
|
}
|
|
}
|
|
|
|
if (i === 5) {
|
|
//5번일때는 앞에 3번에 선이 필요하다
|
|
let prevX1 = tmpVertCenterLine[i - 2].x1
|
|
let prevY1 = tmpVertCenterLine[i - 2].y1
|
|
let prevX2 = tmpVertCenterLine[i - 2].x2
|
|
let prevY2 = tmpVertCenterLine[i - 2].y2
|
|
firstPointObj = { x: prevX1, y: prevY1 }
|
|
secondPointObj = { x: prevX2, y: prevY2 }
|
|
tmpBigArray.push(firstPointObj)
|
|
tmpBigArray.push(secondPointObj)
|
|
}
|
|
|
|
firstPointObj = { x: x1, y: y1 }
|
|
secondPointObj = { x: x2, y: y2 }
|
|
tmpBigArray.push(firstPointObj)
|
|
tmpBigArray.push(secondPointObj)
|
|
|
|
if (i === 3 || i === 6) {
|
|
roofPatternPolygonArray.push(tmpBigArray)
|
|
tmpBigArray = []
|
|
}
|
|
}
|
|
}
|
|
|
|
setRoofPolygonPattern({ roofPatternPolygonArray, lines })
|
|
} else {
|
|
// 오목한 부분이 세로선일때 아래ㄷ, 위ㄷ
|
|
//라인들을 좌측에서 -> 우측으로 그리는거처럼 데이터 보정
|
|
lines.forEach((line, index) => {
|
|
if (!(index % 2 === 0)) {
|
|
line.line.set('stroke', 'skyblue')
|
|
}
|
|
})
|
|
outLines = reSortQlineArray(outLines)
|
|
outLines.forEach((outline, index) => {
|
|
if (!(index % 2 === 0)) {
|
|
//세로라인이 케라바 라인임
|
|
|
|
if (concavePointIndices[0] !== index) {
|
|
//오목이가 아니면 반으로 갈라서 계산
|
|
|
|
//카라바 선의 2분할 치수를 그림
|
|
let halfLength = outline.length / 2
|
|
let centerLine1 = new QLine([outline.x1, outline.y1, outline.x1 + halfLength, outline.y1], centerLineOpt)
|
|
canvas.add(centerLine1)
|
|
|
|
let centerLine2 = new QLine([centerLine1.x2, outline.y1, centerLine1.x2 + halfLength, outline.y1], centerLineOpt)
|
|
canvas.add(centerLine2)
|
|
canvas.remove(outline) //기존 라인 삭제
|
|
|
|
halfHoriCenterLinePoint.push({
|
|
index: index,
|
|
x1: centerLine1.x1,
|
|
y1: centerLine1.y1,
|
|
x2: centerLine1.x2,
|
|
y2: centerLine1.y2,
|
|
}) //각 카라바 라인의 1번이 마지막점을 잡아서 센터선으로 설정
|
|
}
|
|
}
|
|
})
|
|
|
|
//각 센터 라인을 그림
|
|
halfHoriCenterLinePoint.forEach((centerPoint) => {
|
|
let tmpY2 = parallelLinesIdx !== centerPoint.index ? concaveLine.line.y1 : outLines[concavePointIndices[0]].y2 //평행선에서 내려오는 선은 아웃라인에 닿아야한다
|
|
|
|
let line = new QLine([centerPoint.x2, centerPoint.y1, centerPoint.x2, tmpY2], centerLineOpt)
|
|
canvas.add(line)
|
|
|
|
line['arrayIndex'] = centerPoint.index //커스텀으로 기존 index를 넣어줌
|
|
vertCenterLine.push(line)
|
|
})
|
|
|
|
vertCenterLine = reSortQlineArray(vertCenterLine)
|
|
lines = reSortQlineArray(lines)
|
|
setTemplateCenterLine(vertCenterLine)
|
|
|
|
//해당라인에서 만나는점을 계산
|
|
vertCenterLine.forEach((vertLine) => {
|
|
if (parallelLinesIdx !== vertLine.arrayIndex) {
|
|
//평행선을 제외한 애들만 네모를 연결
|
|
let nearLine
|
|
let nearOutline
|
|
if (vertLine.arrayIndex > concaveLine.index) {
|
|
//센터에 인덱스가 오목점 보다 크면 다음 작으면 앞에꺼
|
|
nearLine = lines[concaveLine.index + 1]
|
|
nearOutline = outLines[concaveLine.index + 1]
|
|
} else {
|
|
nearLine = lines[concaveLine.index - 1]
|
|
nearOutline = outLines[concaveLine.index - 1]
|
|
}
|
|
|
|
let nearLineY = nearLine.y1
|
|
if (parallelLinesIdx < concaveLine.index) {
|
|
//오목점 위치가 평행선보다 크면 위쪽으로 오목
|
|
nearLineY = nearLine.y2
|
|
}
|
|
|
|
let centerExtendLine = new QLine([vertLine.line.x1, nearLineY, nearOutline.x1, nearLineY], centerLineOpt)
|
|
canvas.add(centerExtendLine) //새로그리고
|
|
|
|
//기존에 있는 라인에서 연장해서 새로 그림
|
|
let centerExtendHoriLine = new QLine([nearOutline.line.x1, vertLine.y1, nearOutline.line.x2, vertLine.line.y2], centerLineOpt)
|
|
canvas.add(centerExtendHoriLine)
|
|
canvas.remove(nearOutline)
|
|
outLines.splice(nearOutline.idx, 1, centerExtendHoriLine) //아웃라인에 데이터를 다시 넣는다
|
|
|
|
let betweenCenterLine = (vertLine.line.y1 + vertLine.line.y2) / 2
|
|
let centerDashLine = new QLine([vertLine.line.x1, betweenCenterLine, nearOutline.x1, betweenCenterLine], dashedCenterLineOpt)
|
|
|
|
canvas.add(centerDashLine)
|
|
horiCenterLine.push(centerDashLine)
|
|
shorVertCenterLine.push(vertLine) //마지막에 가운데 선을 긋기 위해 담음
|
|
} else {
|
|
let longDashLine = halfHoriCenterLinePoint.find((obj) => obj.index === parallelLinesIdx)
|
|
|
|
let dashCenterExtendLineLength = longDashLine.x2 - longDashLine.x1
|
|
let betweenCenterLine = (vertLine.line.y1 + vertLine.line.y2) / 2
|
|
let totalLength = ((longDashLine.x2 - longDashLine.x1) * 2) / dashCenterExtendLineLength
|
|
|
|
//반 쪼개서 그린다
|
|
for (let i = 0; i < totalLength; i++) {
|
|
let startX = i === 0 ? longDashLine.x1 : longDashLine.x1 + dashCenterExtendLineLength
|
|
let centerDashLine = new QLine([startX, betweenCenterLine, startX + dashCenterExtendLineLength, betweenCenterLine], dashedCenterLineOpt)
|
|
canvas.add(centerDashLine)
|
|
horiCenterLine.push(centerDashLine)
|
|
}
|
|
}
|
|
})
|
|
|
|
//마지막에 오목한 외곽선을 연장한다
|
|
const tmpLastOutLine = outLines[concavePointIndices[0]]
|
|
const lastOutLine = new QLine([shorVertCenterLine[0].x1, tmpLastOutLine.y1, shorVertCenterLine[1].x1, tmpLastOutLine.y2], centerLineOpt)
|
|
canvas.add(lastOutLine)
|
|
canvas.remove(tmpLastOutLine)
|
|
|
|
let tmpVertCenterLine = outLines.filter((x, index) => index % 2 === 0) //세로만 찾음
|
|
tmpVertCenterLine = tmpVertCenterLine.concat(vertCenterLine)
|
|
tmpVertCenterLine.sort((a, b) => a.x1 - b.x1)
|
|
tmpVertCenterLine.push(lastOutLine)
|
|
|
|
let roofPatternPolygonArray = []
|
|
let tmpArray = []
|
|
let tmpBigArray = []
|
|
|
|
const lastCenterLine = tmpVertCenterLine[tmpVertCenterLine.length - 1] //마지막 센터라인을 정의
|
|
|
|
for (let i = 0; i < tmpVertCenterLine.length - 1; i++) {
|
|
//-1인건 마지막은 오목한 선이라 돌 필요 없음
|
|
//라인 하나에 두점씩 나온다
|
|
let firstPointObj = {}
|
|
let secondPointObj = {}
|
|
|
|
let x1 = tmpVertCenterLine[i].x1
|
|
let y1 = tmpVertCenterLine[i].y1
|
|
let x2 = tmpVertCenterLine[i].x2
|
|
let y2 = tmpVertCenterLine[i].y2
|
|
|
|
if (i === 2 || i === 4) {
|
|
tmpArray = []
|
|
const prevLine = tmpVertCenterLine[i - 1] //뒤에서 앞라인을 찾는다
|
|
const nextLine = tmpVertCenterLine[i + 1]
|
|
|
|
//내 앞뒤 라인
|
|
const tmpX1 = i === 2 ? prevLine.x1 : nextLine.x1
|
|
const tmpY1 = i === 2 ? prevLine.y1 : nextLine.y1
|
|
const tmpX2 = i === 2 ? prevLine.x2 : nextLine.x2
|
|
const tmpY2 = i === 2 ? prevLine.y2 : nextLine.y2
|
|
|
|
firstPointObj = { x: tmpX1, y: tmpY1 }
|
|
secondPointObj = { x: tmpX2, y: tmpY2 }
|
|
tmpArray.push(firstPointObj)
|
|
tmpArray.push(secondPointObj)
|
|
|
|
//현재 내 선
|
|
firstPointObj = { x: x1, y: y1 }
|
|
secondPointObj = { x: x2, y: y2 }
|
|
tmpArray.push(firstPointObj)
|
|
tmpArray.push(secondPointObj)
|
|
roofPatternPolygonArray.push(tmpArray)
|
|
} else {
|
|
if (i === 1 || i === 5) {
|
|
// 큰 폴리곤은 가운데 선으로 되야됨
|
|
if (outLines.length / 2 < concavePointIndices[0]) {
|
|
//오목이가 배열 전체보다 크면 위쪽 방향
|
|
x2 = i === 1 ? lastCenterLine.x2 : lastCenterLine.x1
|
|
y2 = i === 1 ? lastCenterLine.y2 : lastCenterLine.y1
|
|
} else {
|
|
x1 = i === 1 ? lastCenterLine.x1 : lastCenterLine.x2
|
|
y1 = i === 1 ? lastCenterLine.y1 : lastCenterLine.y2
|
|
}
|
|
}
|
|
|
|
if (i === 5) {
|
|
//5번일때는 앞에 3번에 선이 필요하다
|
|
let prevX1 = tmpVertCenterLine[i - 2].x1
|
|
let prevY1 = tmpVertCenterLine[i - 2].y1
|
|
let prevX2 = tmpVertCenterLine[i - 2].x2
|
|
let prevY2 = tmpVertCenterLine[i - 2].y2
|
|
firstPointObj = { x: prevX1, y: prevY1 }
|
|
secondPointObj = { x: prevX2, y: prevY2 }
|
|
tmpBigArray.push(firstPointObj)
|
|
tmpBigArray.push(secondPointObj)
|
|
}
|
|
|
|
firstPointObj = { x: x1, y: y1 }
|
|
secondPointObj = { x: x2, y: y2 }
|
|
tmpBigArray.push(firstPointObj)
|
|
tmpBigArray.push(secondPointObj)
|
|
|
|
if (i === 3 || i === 6) {
|
|
roofPatternPolygonArray.push(tmpBigArray)
|
|
tmpBigArray = []
|
|
}
|
|
}
|
|
}
|
|
setRoofPolygonPattern({ roofPatternPolygonArray, lines })
|
|
}
|
|
canvas?.renderAll()
|
|
}
|
|
|
|
/**
|
|
* 세로 방샹 라인의 좌표 순서를 위에서 아래로 변경
|
|
* @param {array} arr
|
|
* @returns
|
|
*/
|
|
const chgLineDirectionVertical = (arr) => {
|
|
const newArr = arr.map((line, index) => {
|
|
if (line.direction !== 'bottom') {
|
|
const newPoint = { x1: line.x2, y1: line.y2, x2: line.x1, y2: line.y1 }
|
|
return newPoint
|
|
} else {
|
|
return line
|
|
}
|
|
})
|
|
return newArr
|
|
}
|
|
|
|
/**
|
|
* 가로 방향 라인의 좌표 순서를 왼쪽에서 오른쪽으로 변경
|
|
* @param {array} arr
|
|
* @returns
|
|
*/
|
|
const chgLineDirectionHorizontal = (arr) => {
|
|
const newArr = arr.map((line, index) => {
|
|
if (line.direction !== 'right') {
|
|
const newPoint = { x1: line.x2, y1: line.y2, x2: line.x1, y2: line.y1 }
|
|
return newPoint
|
|
} else {
|
|
return line
|
|
}
|
|
})
|
|
return newArr
|
|
}
|
|
|
|
/**
|
|
* 라인 배열 중 가장 긴 라인의 인덱스를 반환합니다.
|
|
*/
|
|
const chkLengthIndex = (params) => {
|
|
const { arr, type } = params
|
|
let maxIndex = 0
|
|
for (let i = 1; i < arr.length; i++) {
|
|
if (type === 'L') {
|
|
if (arr[maxIndex].length < arr[i].length) {
|
|
maxIndex = i
|
|
}
|
|
} else {
|
|
if (arr[maxIndex].length > arr[i].length) {
|
|
maxIndex = i
|
|
}
|
|
}
|
|
}
|
|
return maxIndex
|
|
}
|
|
|
|
/**
|
|
* 외벽선 색깔을 변경
|
|
* @param {array} polygon
|
|
*/
|
|
const handleInnerLineColor = (polygon) => {
|
|
polygon.lines.forEach((line, index) => {
|
|
if (index % 2 === 0) {
|
|
line.set('stroke', 'rgb(0, 220, 221)')
|
|
} else {
|
|
line.set('stroke', 'rgb(0, 224, 61)')
|
|
}
|
|
|
|
const overLine = new QLine([line.x1, line.y1, line.x2, line.y2], {
|
|
stroke: line.stroke,
|
|
strokeWidth: 2,
|
|
selectable: false,
|
|
fontSize: fontSize,
|
|
isActiveLengthText: false,
|
|
})
|
|
canvas.add(overLine)
|
|
})
|
|
canvas?.renderAll()
|
|
}
|
|
|
|
const getRoofPattern = (roofStyle, mode = 'normal') => {
|
|
const ratio = window.devicePixelRatio || 1
|
|
|
|
const inputPatternSize = { width: 30, height: 20 } //임시 사이즈
|
|
const patternSize = { ...inputPatternSize } // 입력된 값을 뒤집기 위해
|
|
|
|
if (templateType === 2) {
|
|
//세로형이면 width height를 바꿈
|
|
;[patternSize.width, patternSize.height] = [inputPatternSize.height, patternSize.width]
|
|
}
|
|
|
|
// 패턴 소스를 위한 임시 캔버스 생성
|
|
const patternSourceCanvas = document.createElement('canvas')
|
|
patternSourceCanvas.width = roofStyle === 2 ? patternSize.width * 2 * ratio : patternSize.width * ratio
|
|
patternSourceCanvas.height = roofStyle === 2 ? patternSize.height * 2 * ratio : patternSize.height * ratio
|
|
const ctx = patternSourceCanvas.getContext('2d')
|
|
|
|
// 벽돌 패턴 그리기
|
|
ctx.scale(ratio, ratio)
|
|
|
|
if (mode === 'cell') {
|
|
ctx.fillStyle = 'rgba(0, 0, 0, 0.1)'
|
|
ctx.fillRect(0, 0, patternSize.width * 2, patternSize.height * 2)
|
|
}
|
|
|
|
ctx.strokeStyle = 'green'
|
|
ctx.lineWidth = 0.4
|
|
|
|
// 첫 번째 행 벽돌
|
|
if (roofStyle === 2) {
|
|
patternSize.width = patternSize.width * 2
|
|
patternSize.height = patternSize.height * 2
|
|
|
|
//지그재그
|
|
// // 두 번째 행 벽돌
|
|
if (templateType === 2) {
|
|
ctx.strokeRect(0, 0, patternSize.width / 2, patternSize.height)
|
|
ctx.strokeRect(patternSize.width / 2, patternSize.height / 2, patternSize.width / 2, patternSize.height)
|
|
} else if (templateType === 3) {
|
|
ctx.strokeRect(0, 0, patternSize.width, patternSize.height / 2)
|
|
ctx.strokeRect(patternSize.width / 2, patternSize.height / 2, patternSize.width, patternSize.height / 2)
|
|
}
|
|
} else {
|
|
ctx.strokeRect(0, 0, patternSize.width, patternSize.height) // 원패턴일때랑 지그재그일때랑은 다르게 들어가야함
|
|
}
|
|
|
|
// 패턴 생성
|
|
const pattern = new fabric.Pattern({
|
|
source: patternSourceCanvas,
|
|
repeat: 'repeat',
|
|
})
|
|
|
|
return pattern
|
|
}
|
|
|
|
/**
|
|
* 지붕 패턴 생성 로직
|
|
* @param roofStyle
|
|
*/
|
|
const makeRoofPatternPolygon = (roofStyle) => {
|
|
if (Object.keys(roofPolygonPattern).length === 0 && roofPolygonPattern.constructor === Object) {
|
|
alert('객체가 비어있습니다.')
|
|
return
|
|
}
|
|
|
|
setRoofStyle(roofStyle) //클릭한 지붕패턴을 저장
|
|
|
|
//내부 선 점선으로 변경 추후에 다시 되돌리는 로직 필요
|
|
roofPolygonPattern.lines.forEach((line, index) => {
|
|
line.line.set('strokeDashArray', [10, 5, 2, 5])
|
|
line.line.set('stroke', 'blue')
|
|
line.line.set('strokeWidth', 1)
|
|
})
|
|
|
|
const pattern = getRoofPattern(roofStyle)
|
|
|
|
const commonOption = {
|
|
fill: pattern,
|
|
selectable: false,
|
|
fontSize: 15, // fontSize는 필요에 따라 조정
|
|
sort: false,
|
|
lockMovementX: true,
|
|
lockMovementY: true,
|
|
lockRotation: true,
|
|
lockScalingY: true,
|
|
lockScalingX: true,
|
|
}
|
|
|
|
let polygonArray = []
|
|
|
|
//패턴 폴리곤을 생성 후 배열에 담음
|
|
roofPolygonPattern.roofPatternPolygonArray.forEach((patternPolygon, index) => {
|
|
const drawPolygon = new QPolygon(patternPolygon, commonOption)
|
|
canvas.add(drawPolygon)
|
|
drawPolygon.setViewLengthText(false)
|
|
drawPolygon.set('customIndex', index)
|
|
polygonArray.push(drawPolygon)
|
|
})
|
|
canvas?.renderAll()
|
|
|
|
//지붕 폴리곤 recoil에 담음
|
|
setRoofPolygonArray(polygonArray)
|
|
}
|
|
|
|
/**
|
|
* 가대 생성 로직
|
|
*/
|
|
const makeRoofTrestle = () => {
|
|
if (compass === undefined) {
|
|
alert('방위를 먼저 선택 해주세요.')
|
|
return
|
|
}
|
|
|
|
if (Object.keys(roofPolygonPattern).length === 0 && roofPolygonPattern.constructor === Object) {
|
|
alert('객체가 비어있습니다.')
|
|
return
|
|
}
|
|
|
|
const polygons = roofPolygonArray //리코일에 있는 패턴그린 폴리곤가져옴
|
|
|
|
/**
|
|
* 지붕가대 생성 후 가대 선택 이벤트를 추가하는 로직
|
|
* @param polygon
|
|
*/
|
|
const pattern = getRoofPattern(roofStyle, 'cell') //셀모드 배경색을 칠한다
|
|
|
|
polygons.sort((a, b) => b.points.length - a.points.length) //무조건 잴 긴거 정렬
|
|
|
|
// 외각선을 안쪽으로 그려 가대선을 그린다.
|
|
polygons.forEach((polygon, index) => {
|
|
const trestlePolygon = handleOuterlinesTest(polygon, -12)
|
|
let referenceDirection = 'none' //상단 기준 값
|
|
let parallelPoint = -1
|
|
trestlePolygon.setViewLengthText(false) //얘는 set으로 안먹는다...
|
|
trestlePolygon.bringForward()
|
|
|
|
trestlePolygon.set({
|
|
stroke: 'red',
|
|
strokeDashArray: [9, 5],
|
|
strokeWidth: 0.3,
|
|
lockMovementX: true,
|
|
lockMovementY: true,
|
|
lockRotation: true,
|
|
lockScalingX: true,
|
|
lockScalingY: true,
|
|
idx: polygon.customIndex, //가대 폴리곤의 임시 인덱스를 넣어줌
|
|
name: 'trestlePolygon',
|
|
})
|
|
|
|
if (polygon.points.length > 4) {
|
|
//6, 8각
|
|
//4각 이상일때만 한다
|
|
const concave = findConcavePointIndices(polygon.points) //오목한 부분기준으로 시작점을 찾으려 계산
|
|
parallelPoint = parseInt(concave.concavePointIndices[0] + 3) % polygon.points.length //시작점을 찾기 위해 적용
|
|
|
|
if (templateType === 2) {
|
|
//셀이 그려져야할 기준 방향
|
|
//A타입
|
|
referenceDirection = parallelPoint === 0 || parallelPoint === 5 ? 'top' : 'bottom'
|
|
} else if (templateType === 3) {
|
|
//B타입
|
|
referenceDirection = parallelPoint === 0 || parallelPoint === 1 ? 'left' : 'right'
|
|
}
|
|
}
|
|
|
|
if (templateCenterLine.length > 0) {
|
|
//셀이 생성될 지붕의 흐름방향을 정함
|
|
templateCenterLine.some((centerLine) => {
|
|
if (templateType === 2) {
|
|
trestlePolygon.set({
|
|
referenceDirection: referenceDirection,
|
|
startIndex: parallelPoint,
|
|
}) //기준면 방향
|
|
trestlePolygon.points.forEach((trestleLine, index) => {
|
|
if (trestleLine.x === centerLine.x1 - 12) {
|
|
trestlePolygon.set({ wallDirection: 'left' })
|
|
return true
|
|
} else if (trestleLine.x === centerLine.x1 + 12) {
|
|
trestlePolygon.set({ wallDirection: 'right' })
|
|
return true
|
|
}
|
|
})
|
|
} else if (templateType === 3) {
|
|
trestlePolygon.set({
|
|
referenceDirection: referenceDirection,
|
|
startIndex: parallelPoint,
|
|
}) //기준면 방향
|
|
trestlePolygon.points.forEach((trestleLine, index) => {
|
|
if (trestleLine.y === centerLine.y1 - 12) {
|
|
trestlePolygon.set({ wallDirection: 'top' })
|
|
return true
|
|
} else if (trestleLine.y === centerLine.y1 + 12) {
|
|
trestlePolygon.set({ wallDirection: 'bottom' })
|
|
return true
|
|
}
|
|
})
|
|
}
|
|
})
|
|
} else {
|
|
if (templateType === 2) {
|
|
index === 0 ? trestlePolygon.set({ wallDirection: 'left' }) : trestlePolygon.set({ wallDirection: 'right' })
|
|
} else if (templateType === 3) {
|
|
index === 0 ? trestlePolygon.set({ wallDirection: 'top' }) : trestlePolygon.set({ wallDirection: 'bottom' })
|
|
}
|
|
trestlePolygon.set({ referenceDirection: 'none' })
|
|
}
|
|
|
|
/**
|
|
* 가대 선택 이벤트
|
|
*/
|
|
trestlePolygon.on('mousedown', function () {
|
|
toggleSelection(trestlePolygon)
|
|
})
|
|
polygon.set({ fill: pattern })
|
|
trestlePolygon.bringForward()
|
|
})
|
|
|
|
canvas?.renderAll()
|
|
setMode(Mode.DEFAULT) //default 모드로 변경
|
|
}
|
|
|
|
/**
|
|
* 가대 영역 선택 시 이벤트
|
|
* @param {} polygon
|
|
*/
|
|
const toggleSelection = (polygon) => {
|
|
const selectedAreaArray = selectedCellRoofArray
|
|
|
|
const defualtStrokeStyle = {
|
|
stroke: 'red',
|
|
strokeDashArray: [9, 5],
|
|
strokeWidth: 0.3,
|
|
}
|
|
|
|
const wallDirection = polygon.wallDirection
|
|
|
|
const invalidDirections = {
|
|
//반대편으로 작성
|
|
2: {
|
|
90: 'right',
|
|
270: 'left',
|
|
},
|
|
3: {
|
|
0: 'top',
|
|
180: 'bottom',
|
|
},
|
|
}
|
|
|
|
if (invalidDirections[templateType] && invalidDirections[templateType][compass] === wallDirection) {
|
|
alert('선택할 수 없는 방향입니다.')
|
|
canvas.discardActiveObject() // 객체의 활성 상태 해제
|
|
return
|
|
}
|
|
|
|
const selectedStrokeStyle = {
|
|
stroke: 'red',
|
|
strokeWidth: 3,
|
|
}
|
|
if (polygon.strokeWidth === defualtStrokeStyle.strokeWidth) {
|
|
//기본 선택이랑 스트로크 굵기가 같으면 선택 안됨으로 봄
|
|
polygon.set({
|
|
stroke: selectedStrokeStyle.stroke,
|
|
strokeWidth: selectedStrokeStyle.strokeWidth,
|
|
strokeDashArray: [0],
|
|
})
|
|
canvas.discardActiveObject() // 객체의 활성 상태 해제
|
|
|
|
//중복으로 들어가는걸 방지하기 위한 코드
|
|
const isExist = selectedAreaArray.some((x) => x.idx === polygon.idx)
|
|
if (!isExist) {
|
|
selectedAreaArray.push(polygon)
|
|
}
|
|
} else {
|
|
//선택후 재선택하면 선택안됨으로 변경
|
|
polygon.set({
|
|
stroke: defualtStrokeStyle.stroke,
|
|
strokeWidth: defualtStrokeStyle.strokeWidth,
|
|
strokeDashArray: defualtStrokeStyle.strokeDashArray,
|
|
})
|
|
canvas.discardActiveObject() // 객체의 활성 상태 해제
|
|
|
|
//폴리곤에 커스텀 인덱스를 가지고 해당 배열 인덱스를 찾아 삭제함
|
|
const removeIndex = polygon.idx
|
|
const removeArrayIndex = selectedAreaArray.findIndex((x) => x.idx === removeIndex)
|
|
selectedAreaArray.splice(removeArrayIndex, 1)
|
|
}
|
|
|
|
setSelectedCellRoofArray(selectedAreaArray)
|
|
canvas?.renderAll()
|
|
}
|
|
|
|
/**
|
|
* 가대 선택 후 셀 채우기
|
|
*/
|
|
const makeRoofFillCells = () => {
|
|
const drawCellsArray = []
|
|
const selectedCellRoofs = [...selectedCellRoofArray]
|
|
|
|
if (selectedCellRoofs.length === 0) {
|
|
//배열에 선택된 가대 셀이 없으면 리턴
|
|
alert('선택된 영역이 없습니다.')
|
|
setMode(Mode.DEFAULT) //default 모드로 변경
|
|
return
|
|
}
|
|
|
|
if (drewRoofCells.length > 0) {
|
|
if (confirm('패널이 초기화 됩니다.')) {
|
|
drewRoofCells.forEach((cells, index) => {
|
|
cells.drawCells.forEach((cell) => {
|
|
canvas?.remove(cell)
|
|
})
|
|
})
|
|
setDrewRoofCells([])
|
|
canvas?.renderAll()
|
|
}
|
|
}
|
|
|
|
const inputCellSize = { width: 172, height: 70 } //추후 입력받는 값으로 변경
|
|
const cellSize = { ...inputCellSize } //기본으로 가로형으로 넣고
|
|
|
|
if (templateType === 2) {
|
|
;[cellSize.width, cellSize.height] = [cellSize.height, cellSize.width]
|
|
}
|
|
|
|
selectedCellRoofs.forEach((polygon, index) => {
|
|
const drawCells = polygon.fillCellABType({
|
|
width: cellSize.width,
|
|
height: cellSize.height,
|
|
padding: 10,
|
|
wallDirection: polygon.wallDirection,
|
|
referenceDirection: polygon.referenceDirection,
|
|
startIndex: polygon.startIndex,
|
|
isCellCenter: isCellCenter,
|
|
})
|
|
drawCellsArray.push({ roofIndex: polygon.idx, drawCells: drawCells })
|
|
})
|
|
|
|
setDrewRoofCells(drawCellsArray)
|
|
setMode(Mode.DEFAULT) //default 모드로 변경
|
|
}
|
|
|
|
const createRoofRack = () => {
|
|
removeMouseLines()
|
|
canvas?.off('mouse:move')
|
|
canvas?.off('mouse:out')
|
|
const roofs = canvas?.getObjects().filter((obj) => obj.name === 'roof')
|
|
const dormers = canvas?.getObjects().filter((obj) => obj.name === 'dormer')
|
|
|
|
console.log('roofs', roofs)
|
|
|
|
roofs.forEach((roof, index) => {
|
|
// const offsetPolygonPoint = offsetPolygon(roof.points(), -20) //이동되서 찍을라고 바꿈
|
|
const offsetPolygonPoint = offsetPolygon(roof.getCurrentPoints(), -20)
|
|
|
|
const trestlePoly = new QPolygon(offsetPolygonPoint, {
|
|
fill: 'transparent',
|
|
stroke: 'red',
|
|
strokeDashArray: [5, 5],
|
|
strokeWidth: 1,
|
|
selectable: false,
|
|
fontSize: fontSize,
|
|
name: 'trestle',
|
|
defense: roof.defense,
|
|
lockMovementX: true, // X 축 이동 잠금
|
|
lockMovementY: true, // Y 축 이동 잠금
|
|
lockRotation: true, // 회전 잠금
|
|
lockScalingX: true, // X 축 크기 조정 잠금
|
|
lockScalingY: true, // Y 축 크기 조정 잠금
|
|
idx: index,
|
|
parentId: roof.id,
|
|
direction: roof.direction,
|
|
})
|
|
|
|
canvas?.add(trestlePoly)
|
|
trestlePoly.setViewLengthText(false)
|
|
})
|
|
|
|
//도머가 있으면 외곽으로 선을 낸다
|
|
dormers.forEach((dormer) => {
|
|
const offsetDormerPoint = offsetPolygon(rectToPolygon(dormer), 20)
|
|
const dormerPoly = new QPolygon(offsetDormerPoint, {
|
|
fill: 'transparent',
|
|
stroke: 'blue',
|
|
strokeDashArray: [5, 5],
|
|
strokeWidth: 1,
|
|
fontSize: fontSize,
|
|
name: 'dormerTrestle',
|
|
lockMovementX: true, // X 축 이동 잠금
|
|
lockMovementY: true, // Y축 이동 잠금
|
|
lockRotation: true, // 회전 잠금
|
|
lockScalingX: true, // X축 크기 조정 잠금
|
|
lockScalingY: true, // Y축 크기 조정 잠금
|
|
})
|
|
canvas?.add(dormerPoly)
|
|
dormerPoly.setViewLengthText(false)
|
|
})
|
|
|
|
removeHelpPointAndHelpLine()
|
|
}
|
|
|
|
const rectToPolygon = (rect) => {
|
|
const points = []
|
|
const left = rect.left
|
|
const top = rect.top
|
|
const width = rect.width * rect.scaleX // 스케일 적용
|
|
const height = rect.height * rect.scaleY // 스케일 적용
|
|
|
|
// 네 개의 꼭짓점 좌표 계산
|
|
points.push({ x: left, y: top }) // 좌상단
|
|
points.push({ x: left + width, y: top }) // 우상단
|
|
points.push({ x: left + width, y: top + height }) // 우하단
|
|
points.push({ x: left, y: top + height }) // 좌하단
|
|
|
|
return points
|
|
}
|
|
|
|
const polygonToTurfPolygon = (polygon) => {
|
|
const coordinates = polygon.points.map((point) => [point.x, point.y])
|
|
coordinates.push(coordinates[0])
|
|
return turf.polygon(
|
|
[coordinates],
|
|
{},
|
|
{
|
|
parentId: polygon.parentId,
|
|
},
|
|
)
|
|
}
|
|
|
|
const coordToTurfPolygon = (points) => {
|
|
const coordinates = points.map((point) => [point.x, point.y])
|
|
coordinates.push(coordinates[0])
|
|
return turf.polygon([coordinates])
|
|
}
|
|
|
|
/**
|
|
* trestle에서 영역을 가져와 mouse:move 이벤트로 해당 영역에 진입했을때 booleanPointInPolygon 로 진입여부를 확인
|
|
* 확인 후 셀을 이동시킴
|
|
*/
|
|
const drawCellManualInTrestle = () => {
|
|
const trestlePolygons = canvas?.getObjects().filter((obj) => obj.name === 'trestle') //가대를 가져옴
|
|
|
|
const dormerTrestlePolygons = canvas?.getObjects().filter((obj) => obj.name === 'dormerTrestle') //도머 객체
|
|
|
|
if (trestlePolygons.length !== 0) {
|
|
let fabricPolygon = null
|
|
let inside = false
|
|
let turfPolygon
|
|
let manualDrawCells = drewRoofCells // 앞에서 자동으로 했을때 추가됨
|
|
let direction
|
|
let trestlePolygon
|
|
canvas.on('mouse:move', (e) => {
|
|
//마우스 이벤트 삭제 후 재추가
|
|
const mousePoint = canvas.getPointer(e.e)
|
|
|
|
for (let i = 0; i < trestlePolygons.length; i++) {
|
|
turfPolygon = polygonToTurfPolygon(trestlePolygons[i])
|
|
trestlePolygon = trestlePolygons[i]
|
|
direction = trestlePolygons[i].direction //도형의 방향
|
|
let width = direction === 'south' || direction === 'north' ? 172 : 113
|
|
let height = direction === 'south' || direction === 'north' ? 113 : 172
|
|
|
|
const points = [
|
|
{ x: mousePoint.x - width / 2, y: mousePoint.y - height / 2 },
|
|
{ x: mousePoint.x + width / 2, y: mousePoint.y - height / 2 },
|
|
{ x: mousePoint.x + width / 2, y: mousePoint.y + height / 2 },
|
|
{ x: mousePoint.x - width / 2, y: mousePoint.y + height / 2 },
|
|
]
|
|
|
|
const turfPoints = coordToTurfPolygon(points)
|
|
|
|
if (turf.booleanWithin(turfPoints, turfPolygon)) {
|
|
let isDrawing = false
|
|
|
|
if (isDrawing) return
|
|
canvas?.remove(...canvas?.getObjects().filter((obj) => obj.name === 'tmpCell')) //움직일때 일단 지워가면서 움직임
|
|
|
|
fabricPolygon = new fabric.Rect({
|
|
fill: 'white',
|
|
stroke: 'black',
|
|
strokeWidth: 1,
|
|
width: width,
|
|
height: height,
|
|
left: mousePoint.x - width / 2,
|
|
top: mousePoint.y - height / 2,
|
|
selectable: false,
|
|
lockMovementX: true,
|
|
lockMovementY: true,
|
|
lockRotation: true,
|
|
lockScalingX: true,
|
|
lockScalingY: true,
|
|
opacity: 0.8,
|
|
name: 'tmpCell',
|
|
parentId: trestlePolygons[i].parentId,
|
|
})
|
|
|
|
canvas?.add(fabricPolygon) //움직여가면서 추가됨
|
|
|
|
/**
|
|
* 스냅기능
|
|
*/
|
|
let snapDistance = 10
|
|
let cellSnapDistance = 20
|
|
|
|
const trestleLeft = trestlePolygons[i].left
|
|
const trestleTop = trestlePolygons[i].top
|
|
const trestleRight = trestleLeft + trestlePolygons[i].width * trestlePolygons[i].scaleX
|
|
const trestleBottom = trestleTop + trestlePolygons[i].height * trestlePolygons[i].scaleY
|
|
const bigCenterY = (trestleTop + trestleTop + trestlePolygons[i].height) / 2
|
|
|
|
// 작은 폴리곤의 경계 좌표 계산
|
|
const smallLeft = fabricPolygon.left
|
|
const smallTop = fabricPolygon.top
|
|
const smallRight = smallLeft + fabricPolygon.width * fabricPolygon.scaleX
|
|
const smallBottom = smallTop + fabricPolygon.height * fabricPolygon.scaleY
|
|
const smallCenterX = smallLeft + (fabricPolygon.width * fabricPolygon.scaleX) / 2
|
|
const smallCenterY = smallTop + (fabricPolygon.height * fabricPolygon.scaleX) / 2
|
|
|
|
/**
|
|
* 미리 깔아놓은 셀이 있을때 셀에 흡착됨
|
|
*/
|
|
if (manualDrawCells) {
|
|
manualDrawCells.forEach((cell) => {
|
|
const holdCellLeft = cell.left
|
|
const holdCellTop = cell.top
|
|
const holdCellRight = holdCellLeft + cell.width * cell.scaleX
|
|
const holdCellBottom = holdCellTop + cell.height * cell.scaleY
|
|
const holdCellCenterX = holdCellLeft + (cell.width * cell.scaleX) / 2
|
|
const holdCellCenterY = holdCellTop + (cell.height * cell.scaleY) / 2
|
|
|
|
//설치된 셀에 좌측에 스냅
|
|
if (Math.abs(smallRight - holdCellLeft) < snapDistance) {
|
|
fabricPolygon.left = holdCellLeft - width - 0.5
|
|
}
|
|
|
|
//설치된 셀에 우측에 스냅
|
|
if (Math.abs(smallLeft - holdCellRight) < snapDistance) {
|
|
fabricPolygon.left = holdCellRight + 0.5
|
|
}
|
|
|
|
//설치된 셀에 위쪽에 스냅
|
|
if (Math.abs(smallBottom - holdCellTop) < snapDistance) {
|
|
fabricPolygon.top = holdCellTop - height - 0.5
|
|
}
|
|
|
|
//설치된 셀에 밑쪽에 스냅
|
|
if (Math.abs(smallTop - holdCellBottom) < snapDistance) {
|
|
fabricPolygon.top = holdCellBottom + 0.5
|
|
}
|
|
//가운데 -> 가운데
|
|
if (Math.abs(smallCenterX - holdCellCenterX) < cellSnapDistance) {
|
|
fabricPolygon.left = holdCellCenterX - width / 2
|
|
}
|
|
//왼쪽 -> 가운데
|
|
if (Math.abs(smallLeft - holdCellCenterX) < cellSnapDistance) {
|
|
fabricPolygon.left = holdCellCenterX
|
|
}
|
|
// 오른쪽 -> 가운데
|
|
if (Math.abs(smallRight - holdCellCenterX) < cellSnapDistance) {
|
|
fabricPolygon.left = holdCellCenterX - width
|
|
}
|
|
//세로 가운데 -> 가운데
|
|
if (Math.abs(smallCenterY - holdCellCenterY) < cellSnapDistance) {
|
|
fabricPolygon.top = holdCellCenterY - height / 2
|
|
}
|
|
//위쪽 -> 가운데
|
|
if (Math.abs(smallTop - holdCellCenterY) < cellSnapDistance) {
|
|
fabricPolygon.top = holdCellCenterY
|
|
}
|
|
//아랫쪽 -> 가운데
|
|
if (Math.abs(smallBottom - holdCellCenterY) < cellSnapDistance) {
|
|
fabricPolygon.top = holdCellCenterY - height
|
|
}
|
|
})
|
|
}
|
|
|
|
// 위쪽 변에 스냅
|
|
if (Math.abs(smallTop - trestleTop) < snapDistance) {
|
|
fabricPolygon.top = trestleTop
|
|
}
|
|
|
|
// 아래쪽 변에 스냅
|
|
if (Math.abs(smallTop + fabricPolygon.height * fabricPolygon.scaleY - (trestleTop + trestlePolygons[i].height)) < snapDistance) {
|
|
fabricPolygon.top = trestleTop + trestlePolygons[i].height - fabricPolygon.height * fabricPolygon.scaleY
|
|
}
|
|
|
|
// 왼쪽변에 스냅
|
|
if (Math.abs(smallLeft - trestleLeft) < snapDistance) {
|
|
fabricPolygon.left = trestleLeft
|
|
}
|
|
//오른쪽 변에 스냅
|
|
if (Math.abs(smallRight - trestleRight) < snapDistance) {
|
|
fabricPolygon.left = trestleRight - fabricPolygon.width * fabricPolygon.scaleX
|
|
}
|
|
|
|
if (direction === 'south' || direction === 'north') {
|
|
// 모듈왼쪽이 세로중앙선에 붙게 스냅
|
|
if (Math.abs(smallLeft - (trestleLeft + trestlePolygons[i].width / 2)) < snapDistance) {
|
|
fabricPolygon.left = trestleLeft + trestlePolygons[i].width / 2
|
|
}
|
|
|
|
// 모듈이 가운데가 세로중앙선에 붙게 스냅
|
|
if (Math.abs(smallCenterX - (trestleLeft + trestlePolygons[i].width / 2)) < snapDistance) {
|
|
fabricPolygon.left = trestleLeft + trestlePolygons[i].width / 2 - (fabricPolygon.width * fabricPolygon.scaleX) / 2
|
|
}
|
|
|
|
// 모듈오른쪽이 세로중앙선에 붙게 스냅
|
|
if (Math.abs(smallRight - (trestleLeft + trestlePolygons[i].width / 2)) < snapDistance) {
|
|
fabricPolygon.left = trestleLeft + trestlePolygons[i].width / 2 - fabricPolygon.width * fabricPolygon.scaleX
|
|
}
|
|
} else {
|
|
// 모듈이 가로중앙선에 스냅
|
|
if (Math.abs(smallTop + fabricPolygon.height / 2 - bigCenterY) < snapDistance) {
|
|
fabricPolygon.top = bigCenterY - fabricPolygon.height / 2
|
|
}
|
|
|
|
if (Math.abs(smallTop - (trestleTop + trestlePolygons[i].height / 2)) < snapDistance) {
|
|
fabricPolygon.top = trestleTop + trestlePolygons[i].height / 2
|
|
}
|
|
// 모듈 밑면이 가로중앙선에 스냅
|
|
if (Math.abs(smallBottom - (trestleTop + trestlePolygons[i].height / 2)) < snapDistance) {
|
|
fabricPolygon.top = trestleTop + trestlePolygons[i].height / 2 - fabricPolygon.height * fabricPolygon.scaleY
|
|
}
|
|
}
|
|
|
|
fabricPolygon.setCoords()
|
|
canvas?.renderAll()
|
|
inside = true
|
|
break
|
|
} else {
|
|
inside = false
|
|
}
|
|
}
|
|
|
|
if (!inside) {
|
|
canvas?.remove(...canvas?.getObjects().filter((obj) => obj.name === 'tmpCell'))
|
|
canvas?.renderAll()
|
|
}
|
|
})
|
|
|
|
canvas?.on('mouse:up', (e) => {
|
|
let isIntersection = true
|
|
if (!inside) return
|
|
if (fabricPolygon) {
|
|
const rectPoints = [
|
|
{ x: fabricPolygon.left + 0.5, y: fabricPolygon.top + 0.5 },
|
|
{ x: fabricPolygon.left + 0.5 + fabricPolygon.width * fabricPolygon.scaleX, y: fabricPolygon.top + 0.5 },
|
|
{
|
|
x: fabricPolygon.left + fabricPolygon.width * fabricPolygon.scaleX + 0.5,
|
|
y: fabricPolygon.top + fabricPolygon.height * fabricPolygon.scaleY + 0.5,
|
|
},
|
|
{ x: fabricPolygon.left + 0.5, y: fabricPolygon.top + fabricPolygon.height * fabricPolygon.scaleY + 0.5 },
|
|
]
|
|
|
|
fabricPolygon.set({ points: rectPoints })
|
|
const tempTurfModule = polygonToTurfPolygon(fabricPolygon)
|
|
|
|
//도머 객체를 가져옴
|
|
if (dormerTrestlePolygons) {
|
|
dormerTrestlePolygons.forEach((dormerTrestle) => {
|
|
const dormerTurfPolygon = polygonToTurfPolygon(dormerTrestle) //turf객체로 변환
|
|
const intersection = turf.intersect(turf.featureCollection([dormerTurfPolygon, tempTurfModule])) //겹치는지 확인
|
|
//겹치면 안됨
|
|
if (intersection) {
|
|
alert('도머위에 모듈을 올릴 수 없습니다.')
|
|
isIntersection = false
|
|
}
|
|
})
|
|
}
|
|
|
|
if (!isIntersection) return
|
|
|
|
fabricPolygon.setCoords() //좌표 재정렬
|
|
|
|
if (turf.booleanWithin(tempTurfModule, turfPolygon)) {
|
|
//마우스 클릭시 set으로 해당 위치에 셀을 넣음
|
|
const isOverlap = manualDrawCells.some((cell) => turf.booleanOverlap(tempTurfModule, polygonToTurfPolygon(cell))) //겹치는지 확인
|
|
if (!isOverlap) {
|
|
//안겹치면 넣는다
|
|
fabricPolygon.setCoords()
|
|
fabricPolygon.set({ name: 'cell', fill: '#BFFD9F' })
|
|
manualDrawCells.push(fabricPolygon) //모듈배열에 추가
|
|
//해당 모듈에 프로퍼티로 넣는다
|
|
trestlePolygon.set({
|
|
modules: manualDrawCells,
|
|
})
|
|
} else {
|
|
alert('셀끼리 겹치면 안되죠?')
|
|
}
|
|
} else {
|
|
alert('나갔죠?!!')
|
|
}
|
|
setDrewRoofCells(manualDrawCells)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
//배터리 셀 넣기
|
|
const drawCellInTrestle = () => {
|
|
const trestlePolygons = canvas?.getObjects().filter((obj) => obj.name === 'trestle' && obj.selected)
|
|
const notSelectedTrestlePolygons = canvas?.getObjects().filter((obj) => obj.name === 'trestle' && !obj.selected)
|
|
|
|
const dormerTrestlePolygons = canvas?.getObjects().filter((obj) => obj.name === 'dormerTrestle') //도머 객체
|
|
|
|
if (trestlePolygons.length === 0) {
|
|
alert('가대가 없습니다.')
|
|
return
|
|
}
|
|
|
|
if (drewRoofCells.length > 0) {
|
|
alert('기존 셀은 제거됩니다.')
|
|
}
|
|
|
|
notSelectedTrestlePolygons.forEach((trestle) => {
|
|
trestle.cells.forEach((cell) => {
|
|
canvas?.remove(cell)
|
|
})
|
|
trestle.cells = []
|
|
})
|
|
|
|
const drawCellsArray = []
|
|
trestlePolygons.forEach((trestle, index) => {
|
|
trestle.fire('mousedown')
|
|
let maxLengthLine = trestle.lines.reduce((acc, cur) => {
|
|
return acc.length > cur.length ? acc : cur
|
|
})
|
|
|
|
const turfTrestlePolygon = polygonToTurfPolygon(trestle) //폴리곤을 turf 객체로 변환
|
|
|
|
const containsDormerTrestlePolygons = dormerTrestlePolygons.filter((dormerTrestle) => {
|
|
// 폴리곤 안에 도머 폴리곤이 포함되어있는지 확인해서 반환하는 로직
|
|
return (
|
|
turf.booleanContains(turfTrestlePolygon, polygonToTurfPolygon(dormerTrestle)) ||
|
|
turf.booleanWithin(polygonToTurfPolygon(dormerTrestle), turfTrestlePolygon)
|
|
)
|
|
})
|
|
|
|
let difference = turfTrestlePolygon //기본 객체(면형상)
|
|
|
|
if (containsDormerTrestlePolygons.length > 0) {
|
|
//turf로 도머를 제외시키는 로직
|
|
for (let i = 0; i < containsDormerTrestlePolygons.length; i++) {
|
|
if (i === 0) {
|
|
difference = turf.difference(turf.featureCollection([turfTrestlePolygon, polygonToTurfPolygon(containsDormerTrestlePolygons[i])])) //한 면에 도머가 1개일때
|
|
} else {
|
|
if (difference) {
|
|
difference = turf.difference(turf.featureCollection([difference, polygonToTurfPolygon(containsDormerTrestlePolygons[i])])) //한면에 도머가 여러개일때 계속 제외시킴
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const bbox = turf.bbox(difference)
|
|
let width = maxLengthLine.direction === 'right' || maxLengthLine.direction === 'left' ? 172.2 : 113.4
|
|
let height = maxLengthLine.direction === 'right' || maxLengthLine.direction === 'left' ? 113.4 : 172.2
|
|
|
|
//배치면때는 방향쪽으로 패널이 넓게 누워져야함
|
|
if (trestle.direction !== undefined) {
|
|
width = trestle.direction === 'south' || trestle.direction === 'north' ? 172.2 : 113.4
|
|
height = trestle.direction === 'south' || trestle.direction === 'north' ? 113.4 : 172.2
|
|
}
|
|
const cols = Math.floor((bbox[2] - bbox[0]) / width)
|
|
const rows = Math.floor((bbox[3] - bbox[1]) / height)
|
|
|
|
for (let col = 0; col <= cols; col++) {
|
|
for (let row = 0; row <= rows; row++) {
|
|
let x = 0,
|
|
y = 0,
|
|
square = [],
|
|
margin = 0
|
|
|
|
if (trestle.direction !== undefined) {
|
|
//배치면 처림 방향이 정해져있는 경우
|
|
|
|
if (trestle.direction === 'south' || trestle.direction === 'north') {
|
|
//남,북
|
|
margin = (bbox[2] - bbox[0] - cols * width) / 2 //박스 끝에서 박스 시작값을 빼고 width와 계산된 cols를 곱한값을 뺀뒤 나누기 2 하면 가운데 배치됨
|
|
if (trestle.direction === 'south') {
|
|
//남쪽
|
|
x = col === 0 ? trestle.left + margin : bbox[0] + col * width + margin //상하 위치 기준이면 좌우 가운데 정렬한다
|
|
y = bbox[3] - row * height
|
|
} else {
|
|
//북쪽
|
|
x = col === 0 ? trestle.left + margin : bbox[0] + col * width + margin
|
|
y = bbox[1] + row * height
|
|
}
|
|
} else if (trestle.direction === 'east' || trestle.direction === 'west') {
|
|
//동쪽
|
|
margin = (bbox[3] - bbox[1] - rows * height) / 2
|
|
if (trestle.direction === 'east') {
|
|
x = bbox[2] - col * width
|
|
y = rows === 0 ? trestle.top + margin : bbox[1] + row * height + margin //좌우 위치 기준이면 상하 가운데 정렬한다
|
|
} else {
|
|
x = bbox[0] + col * width
|
|
y = rows === 0 ? trestle.top + margin : bbox[1] + row * height + margin
|
|
}
|
|
}
|
|
} else {
|
|
//방향이 없는 경우 ex) 템플릿
|
|
x = bbox[0] + col * width
|
|
y = bbox[1] + row * height
|
|
}
|
|
|
|
square = [
|
|
[x, y],
|
|
[x + width, y],
|
|
[x + width, y + height],
|
|
[x, y + height],
|
|
[x, y],
|
|
]
|
|
|
|
const squarePolygon = turf.polygon([square])
|
|
|
|
const disjointFromTrestle = turf.booleanContains(turfTrestlePolygon, squarePolygon) || turf.booleanWithin(squarePolygon, turfTrestlePolygon)
|
|
|
|
if (disjointFromTrestle) {
|
|
let turfCoordnates = squarePolygon.geometry.coordinates[0].slice(0, -1)
|
|
const points = turfCoordnates.map((coord) => ({ x: coord[0], y: coord[1] }))
|
|
|
|
if (containsDormerTrestlePolygons.length > 0) {
|
|
//도머가 있으면 적용되는 로직
|
|
const isDisjoint = containsDormerTrestlePolygons.some((dormerTrestle) => {
|
|
return turf.booleanDisjoint(squarePolygon, polygonToTurfPolygon(dormerTrestle)) //도머가 여러개일수있으므로 겹치는게 있다면...
|
|
})
|
|
if (isDisjoint) {
|
|
const fabricPolygon = new QPolygon(points, {
|
|
fill: '#BFFD9F',
|
|
stroke: 'black',
|
|
selectable: true, // 선택 가능하게 설정
|
|
lockMovementX: false, // X 축 이동 잠금
|
|
lockMovementY: false, // Y 축 이동 잠금
|
|
lockRotation: false, // 회전 잠금
|
|
lockScalingX: false, // X 축 크기 조정 잠금
|
|
lockScalingY: false, // Y 축 크기 조정 잠금
|
|
opacity: 0.8,
|
|
parentId: trestle.parentId,
|
|
})
|
|
canvas?.add(fabricPolygon)
|
|
drawCellsArray.push(fabricPolygon)
|
|
}
|
|
} else {
|
|
//도머가 없을땐 그냥 그림
|
|
const fabricPolygon = new QPolygon(points, {
|
|
fill: '#BFFD9F',
|
|
stroke: 'black',
|
|
selectable: true, // 선택 가능하게 설정
|
|
lockMovementX: true, // X 축 이동 잠금
|
|
lockMovementY: true, // Y 축 이동 잠금
|
|
lockRotation: true, // 회전 잠금
|
|
lockScalingX: true, // X 축 크기 조정 잠금
|
|
lockScalingY: true, // Y 축 크기 조정 잠금
|
|
opacity: 0.8,
|
|
parentId: trestle.parentId,
|
|
lineCol: col,
|
|
lineRow: row,
|
|
})
|
|
canvas?.add(fabricPolygon)
|
|
drawCellsArray.push(fabricPolygon)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// let drawRoofCells
|
|
// if (maxLengthLine.direction === 'right' || maxLengthLine.direction === 'left') {
|
|
// drawRoofCells = trestle.fillCell({ width: 113.4, height: 172.2, padding: 0 })
|
|
// trestle.direction = 'south'
|
|
// } else {
|
|
// drawRoofCells = trestle.fillCell({ width: 172.2, height: 113.4, padding: 0 })
|
|
// trestle.direction = 'east'
|
|
// }
|
|
|
|
// drawRoofCells.forEach((cell) => {
|
|
// drawCellsArray.push(cell)
|
|
// })
|
|
|
|
/**
|
|
* 추후에 가대까지 완료하면 그룹시켜버림
|
|
*/
|
|
// const groupCellObj = canvas
|
|
// ?.getObjects()
|
|
// .filter(
|
|
// (obj) =>
|
|
// obj?.parentId === trestle.parentId ||
|
|
// obj?.id === trestle.parentId ||
|
|
// (obj?.name === 'arrow' && obj?.parent.id === trestle.parentId) ||
|
|
// (obj?.name === 'directionText' && obj?.parent.parent.id === trestle.parentId),
|
|
// )
|
|
|
|
// console.log('groupCellObj', groupCellObj)
|
|
|
|
// canvas?.add(
|
|
// new fabric.Group(groupCellObj, {
|
|
// name: 'cellGroup',
|
|
// originX: 'center',
|
|
// originY: 'center',
|
|
// }),
|
|
// )
|
|
})
|
|
|
|
setDrewRoofCells(drawCellsArray)
|
|
}
|
|
|
|
// 가대 방위 설정
|
|
const setDirectionTrestles = () => {
|
|
console.log('roof', roof)
|
|
}
|
|
|
|
const makeCellPowercon = () => {
|
|
setMode(Mode.DEFAULT)
|
|
let cellsGroupObj = []
|
|
|
|
drewRoofCells.forEach((obj) => {
|
|
cellsGroupObj = cellsGroupObj.concat(obj.drawCells)
|
|
})
|
|
|
|
const chunkSize = 1000 / 200 // 파워콘와트 나누기 셀와트
|
|
const cellPowerconArray = []
|
|
|
|
//파워콘과 셀의 파워를 나눠서 나온 갯수만큼 배열을 재생성
|
|
for (let i = 0; i < cellsGroupObj.length; i += chunkSize) {
|
|
cellPowerconArray.push(cellsGroupObj.slice(i, i + chunkSize))
|
|
}
|
|
|
|
for (let i = 0; i < cellPowerconArray.length; i++) {
|
|
const cellPowerconGroups = cellPowerconArray[i]
|
|
cellPowerconGroups.forEach((cellPowerconGroup, index) => {
|
|
const cellRectObj = cellPowerconGroup._objects[0]
|
|
const cellTextObj = cellPowerconGroup._objects[1]
|
|
|
|
cellTextObj.set({
|
|
text: `(${i + 1})`,
|
|
})
|
|
cellPowerconGroup.addWithUpdate()
|
|
|
|
//폰트 사이즈가 커진 후에 계산을 해야함
|
|
cellTextObj.set({
|
|
left: cellRectObj.left + cellRectObj.width / 2 - cellTextObj.width / 2,
|
|
top: cellRectObj.top + cellRectObj.height / 2 - cellTextObj.height / 2,
|
|
})
|
|
cellPowerconGroup.addWithUpdate()
|
|
})
|
|
}
|
|
canvas.renderAll()
|
|
}
|
|
|
|
// 외적을 계산하는 함수
|
|
const crossProduct = (p1, p2, p3) => {
|
|
const dx1 = p2.x - p1.x
|
|
const dy1 = p2.y - p1.y
|
|
const dx2 = p3.x - p2.x
|
|
const dy2 = p3.y - p2.y
|
|
return dx1 * dy2 - dy1 * dx2
|
|
}
|
|
// 오목한 부분 찾기
|
|
const findConcavePointIndices = (points) => {
|
|
let concaveIndices = []
|
|
let concavePointIndices = []
|
|
for (let i = 0; i < points.length; i++) {
|
|
const p1 = points[i]
|
|
const p2 = points[(i + 1) % points.length]
|
|
const p3 = points[(i + 2) % points.length]
|
|
const cross = crossProduct(p1, p2, p3)
|
|
if (cross < 0) {
|
|
concaveIndices.push((i + 1) % points.length)
|
|
} else {
|
|
concavePointIndices.push((i + 1) % points.length)
|
|
}
|
|
}
|
|
return { concaveIndices: concaveIndices, concavePointIndices: concavePointIndices }
|
|
}
|
|
|
|
const drawHelpLineMode = () => {
|
|
if (!isObjectNotEmpty(roof)) {
|
|
alert('지붕을 먼저 그려주세요.')
|
|
setMode(Mode.DEFAULT)
|
|
return
|
|
}
|
|
|
|
const roofPoints = roof.points
|
|
const wallPoints = wall.points
|
|
|
|
roofPoints.forEach((roofPoint, index) => {
|
|
const circle = new fabric.Circle({
|
|
radius: 5,
|
|
fill: 'red',
|
|
left: roofPoint.x - 5,
|
|
top: roofPoint.y - 5,
|
|
selectable: true, // 선택 가능하게 설정
|
|
lockMovementX: true, // X 축 이동 잠금
|
|
lockMovementY: true, // Y 축 이동 잠금
|
|
lockRotation: true, // 회전 잠금
|
|
lockScalingX: true, // X 축 크기 조정 잠금
|
|
lockScalingY: true, // Y 축 크기 조정 잠금
|
|
name: 'helpPoint',
|
|
})
|
|
|
|
canvas?.add(circle)
|
|
canvas?.renderAll()
|
|
})
|
|
}
|
|
|
|
const cutHelpLines = () => {
|
|
// 먼저 hip을 자른다.
|
|
canvas
|
|
?.getObjects()
|
|
.filter((obj) => obj.name === 'helpLine')
|
|
.forEach((line, index1) => {
|
|
canvas
|
|
?.getObjects()
|
|
.filter((obj) => obj.name === 'helpLine')
|
|
.forEach((line2, index2) => {
|
|
if (line === line2) {
|
|
return
|
|
}
|
|
|
|
const intersectionPoint = calculateIntersection(line, line2)
|
|
if (!intersectionPoint) {
|
|
return
|
|
}
|
|
|
|
canvas?.remove(line)
|
|
canvas?.remove(line2)
|
|
|
|
const hip1 = new QLine([line.x1, line.y1, intersectionPoint.x, intersectionPoint.y], {
|
|
stroke: 'black',
|
|
strokeWidth: 1,
|
|
selectable: false,
|
|
name: 'hip',
|
|
})
|
|
|
|
const hip2 = new QLine([line2.x1, line2.y1, intersectionPoint.x, intersectionPoint.y], {
|
|
stroke: 'black',
|
|
strokeWidth: 1,
|
|
selectable: false,
|
|
name: 'hip',
|
|
})
|
|
|
|
const interSectionCircle = new fabric.Circle({
|
|
radius: 5,
|
|
fill: 'red',
|
|
left: intersectionPoint.x - 5,
|
|
top: intersectionPoint.y - 5,
|
|
selectable: true, // 선택 가능하게 설정
|
|
lockMovementX: true, // X 축 이동 잠금
|
|
lockMovementY: true, // Y 축 이동 잠금
|
|
lockRotation: true, // 회전 잠금
|
|
lockScalingX: true, // X 축 크기 조정 잠금
|
|
lockScalingY: true, // Y 축 크기 조정 잠금
|
|
name: 'helpPoint',
|
|
})
|
|
|
|
canvas?.add(hip1)
|
|
canvas?.add(hip2)
|
|
roof.innerLines.push(hip1)
|
|
roof.innerLines.push(hip2)
|
|
canvas?.add(interSectionCircle)
|
|
canvas?.renderAll()
|
|
})
|
|
})
|
|
|
|
canvas
|
|
?.getObjects()
|
|
.filter((obj) => obj.name === 'helpLine')
|
|
.forEach((line) => {
|
|
const helpPoints = canvas?.getObjects().filter((obj) => obj.name === 'helpPoint')
|
|
let cnt = 0
|
|
let intersectionPoints = []
|
|
helpPoints.forEach((point) => {
|
|
if (cnt === 2) {
|
|
return
|
|
}
|
|
if (
|
|
turf.booleanPointOnLine(
|
|
turf.point([point.left + point.radius, point.top + point.radius]),
|
|
turf.lineString([
|
|
[line.x1, line.y1],
|
|
[line.x2, line.y2],
|
|
]),
|
|
)
|
|
) {
|
|
intersectionPoints.push(point)
|
|
cnt++
|
|
}
|
|
})
|
|
|
|
if (intersectionPoints.length === 2) {
|
|
const ridge = new QLine(
|
|
[
|
|
intersectionPoints[0].left + intersectionPoints[0].radius,
|
|
intersectionPoints[0].top + intersectionPoints[0].radius,
|
|
intersectionPoints[1].left + intersectionPoints[1].radius,
|
|
intersectionPoints[1].top + intersectionPoints[1].radius,
|
|
],
|
|
{
|
|
stroke: 'black',
|
|
strokeWidth: 1,
|
|
selectable: false,
|
|
name: 'ridge',
|
|
},
|
|
)
|
|
roof.innerLines.push(ridge)
|
|
canvas?.add(ridge)
|
|
canvas?.remove(line)
|
|
canvas?.renderAll()
|
|
}
|
|
})
|
|
}
|
|
|
|
const removeHelpPointAndHelpLine = () => {
|
|
const helpPoints = canvas?.getObjects().filter((obj) => obj.name === 'helpPoint')
|
|
helpPoints.forEach((point) => {
|
|
canvas?.remove(point)
|
|
})
|
|
|
|
const helpLines = canvas?.getObjects().filter((obj) => obj.name === 'helpLine')
|
|
helpLines.forEach((line) => {
|
|
canvas?.remove(line)
|
|
})
|
|
canvas?.renderAll()
|
|
}
|
|
|
|
return {
|
|
mode,
|
|
setMode,
|
|
changeMode,
|
|
handleClear,
|
|
zoomIn,
|
|
zoomOut,
|
|
zoom,
|
|
togglePolygonLine,
|
|
handleOuterlinesTest,
|
|
handleOuterlinesTest2,
|
|
makeRoofPatternPolygon,
|
|
makeRoofTrestle,
|
|
createRoofRack,
|
|
drawRoofPolygon,
|
|
drawCellInTrestle,
|
|
drawCellManualInTrestle,
|
|
setDirectionTrestles,
|
|
cutHelpLines,
|
|
createRoofPolygon,
|
|
createMarginPolygon,
|
|
createPaddingPolygon,
|
|
}
|
|
}
|