1380 lines
48 KiB
JavaScript
1380 lines
48 KiB
JavaScript
'use client'
|
|
|
|
import { useEffect } from 'react'
|
|
import { useRecoilState, useRecoilValue, useResetRecoilState } from 'recoil'
|
|
import { canvasSettingState, canvasState, currentCanvasPlanState, globalPitchState } from '@/store/canvasAtom'
|
|
import { MENU, POLYGON_TYPE, LINE_TYPE } from '@/common/common'
|
|
import { getIntersectionPoint, toFixedWithoutRounding } from '@/util/canvas-util'
|
|
import { degreesToRadians } from '@turf/turf'
|
|
import { QPolygon } from '@/components/fabric/QPolygon'
|
|
import { useSwal } from '@/hooks/useSwal'
|
|
import { useMessage } from '@/hooks/useMessage'
|
|
import { useEvent } from '@/hooks/useEvent'
|
|
import { usePopup } from '@/hooks/usePopup'
|
|
import { roofDisplaySelector } from '@/store/settingAtom'
|
|
import { usePolygon } from '@/hooks/usePolygon'
|
|
import { fontSelector } from '@/store/fontAtom'
|
|
import { slopeSelector } from '@/store/commonAtom'
|
|
import { QLine } from '@/components/fabric/QLine'
|
|
import { useRoofFn } from '@/hooks/common/useRoofFn'
|
|
import { outerLinePointsState } from '@/store/outerLineAtom'
|
|
import { placementShapeDrawingPointsState } from '@/store/placementShapeDrawingAtom'
|
|
import { getBackGroundImage } from '@/lib/imageActions'
|
|
import PlacementSurfaceLineProperty from '@/components/floor-plan/modal/placementShape/PlacementSurfaceLineProperty'
|
|
import { v4 as uuidv4 } from 'uuid'
|
|
import { useCanvasSetting } from '@/hooks/option/useCanvasSetting'
|
|
|
|
export function useSurfaceShapeBatch({ isHidden, setIsHidden }) {
|
|
const { getMessage } = useMessage()
|
|
const { drawDirectionArrow, addPolygon } = usePolygon()
|
|
const lengthTextFont = useRecoilValue(fontSelector('lengthText'))
|
|
const resetOuterLinePoints = useResetRecoilState(outerLinePointsState)
|
|
const resetPlacementShapeDrawingPoints = useResetRecoilState(placementShapeDrawingPointsState)
|
|
const canvasSetting = useRecoilValue(canvasSettingState)
|
|
const canvas = useRecoilValue(canvasState)
|
|
const globalPitch = useRecoilValue(globalPitchState)
|
|
const roofDisplay = useRecoilValue(roofDisplaySelector)
|
|
const slope = useRecoilValue(slopeSelector(globalPitch))
|
|
const { swalFire } = useSwal()
|
|
const { addCanvasMouseEventListener, initEvent } = useEvent()
|
|
// const { addCanvasMouseEventListener, initEvent } = useContext(EventContext)
|
|
const { addPopup, closePopup } = usePopup()
|
|
const { setSurfaceShapePattern } = useRoofFn()
|
|
const currentCanvasPlan = useRecoilValue(currentCanvasPlanState)
|
|
const { fetchSettings } = useCanvasSetting(false)
|
|
|
|
const applySurfaceShape = (surfaceRefs, selectedType, id) => {
|
|
let length1, length2, length3, length4, length5
|
|
const surfaceId = selectedType?.id
|
|
const azimuth = surfaceRefs.azimuth.current
|
|
|
|
if (surfaceId === 1) {
|
|
length1 = surfaceRefs.length1.current.value
|
|
length2 = surfaceRefs.length2.current.value
|
|
length3 = surfaceRefs.lengthetc.current.value //대각선
|
|
} else if ([2, 4].includes(surfaceId)) {
|
|
length1 = surfaceRefs.length1.current.value
|
|
length2 = surfaceRefs.length2.current.value
|
|
} else if ([3, 5, 6, 15, 18].includes(surfaceId)) {
|
|
length1 = surfaceRefs.length1.current.value
|
|
length2 = surfaceRefs.length2.current.value
|
|
length3 = surfaceRefs.length3.current.value
|
|
} else if ([8, 12, 13, 16, 17].includes(surfaceId)) {
|
|
length1 = surfaceRefs.length1.current.value
|
|
length2 = surfaceRefs.length2.current.value
|
|
length3 = surfaceRefs.length3.current.value
|
|
length4 = surfaceRefs.length4.current.value
|
|
} else if ([7, 9, 10, 11, 14].includes(surfaceId)) {
|
|
length1 = surfaceRefs.length1.current.value
|
|
length2 = surfaceRefs.length2.current.value
|
|
length3 = surfaceRefs.length3.current.value
|
|
length4 = surfaceRefs.length4.current.value
|
|
length5 = surfaceRefs.length5.current.value
|
|
}
|
|
|
|
console.log(' before length : ', length1, length2, length3, length4, length5)
|
|
|
|
length1 = parseFloat(length1 === undefined ? 0 : Number(length1 / 10).toFixed(1))
|
|
length2 = parseFloat(length2 === undefined ? 0 : Number(length2 / 10).toFixed(1))
|
|
length3 = parseFloat(length3 === undefined ? 0 : Number(length3 / 10).toFixed(1))
|
|
length4 = parseFloat(length4 === undefined ? 0 : Number(length4 / 10).toFixed(1))
|
|
length5 = parseFloat(length5 === undefined ? 0 : Number(length5 / 10).toFixed(1))
|
|
|
|
console.log(' after length : ', length1, length2, length3, length4, length5)
|
|
|
|
let isDrawing = true
|
|
let obj = null
|
|
let points = []
|
|
|
|
//일단 팝업을 가린다
|
|
|
|
if (checkSurfaceShape(surfaceId, { length1, length2, length3, length4, length5 })) {
|
|
addCanvasMouseEventListener('mouse:move', (e) => {
|
|
if (!isDrawing) {
|
|
return
|
|
}
|
|
const pointer = canvas?.getPointer(e.e)
|
|
canvas?.remove(...canvas?.getObjects().filter((obj) => obj.name === MENU.BATCH_CANVAS.SURFACE_SHAPE_BATCH_TEMP))
|
|
points = getSurfaceShape(surfaceId, pointer, { length1, length2, length3, length4, length5 })
|
|
|
|
const { xInversion, yInversion, rotate } = surfaceRefs
|
|
const options = {
|
|
fill: 'transparent',
|
|
stroke: 'black',
|
|
strokeWidth: 1,
|
|
strokeDasharray: [10, 4],
|
|
fontSize: 12,
|
|
selectable: true,
|
|
lockMovementX: true, // X 축 이동 잠금
|
|
lockMovementY: true, // Y 축 이동 잠금
|
|
lockRotation: true, // 회전 잠금
|
|
lockScalingX: true, // X 축 크기 조정 잠금
|
|
lockScalingY: true, // Y 축 크기 조정 잠금
|
|
name: MENU.BATCH_CANVAS.SURFACE_SHAPE_BATCH_TEMP,
|
|
// flipX: xInversion !== yInversion,
|
|
// angle: xInversion && yInversion ? Math.abs((rotate + 180) % 360) : Math.abs(rotate),
|
|
// angle: rotate,
|
|
originX: 'center',
|
|
originY: 'center',
|
|
pitch: globalPitch,
|
|
}
|
|
|
|
obj = new QPolygon(points, options)
|
|
|
|
let imageRotate = 0
|
|
if (xInversion && !yInversion) {
|
|
if (rotate % 180 === 0 || rotate < 0) {
|
|
imageRotate = Math.abs(rotate % 360)
|
|
} else {
|
|
if (rotate < 0) {
|
|
imageRotate = Math.abs((rotate - 180) % 360)
|
|
} else {
|
|
imageRotate = Math.abs((rotate + 180) % 360)
|
|
}
|
|
}
|
|
} else if (xInversion && yInversion) {
|
|
imageRotate = Math.abs((rotate + 360) % 360)
|
|
} else if (xInversion !== yInversion && rotate < 0) {
|
|
imageRotate = Math.abs(rotate)
|
|
} else if (!xInversion && yInversion) {
|
|
if (rotate % 180 === 0 || rotate < 0) {
|
|
imageRotate = Math.abs(rotate % 360)
|
|
} else {
|
|
if (rotate < 0) {
|
|
imageRotate = Math.abs((rotate - 180) % 360)
|
|
} else {
|
|
imageRotate = Math.abs((rotate + 180) % 360)
|
|
}
|
|
}
|
|
} else {
|
|
imageRotate = (rotate + 360) % 360
|
|
}
|
|
obj.set({ angle: imageRotate, flipX: xInversion !== yInversion })
|
|
obj.setCoords() //좌표 변경 적용
|
|
|
|
canvas?.add(obj)
|
|
canvas?.renderAll()
|
|
})
|
|
|
|
addCanvasMouseEventListener('mouse:down', (e) => {
|
|
isDrawing = false
|
|
|
|
const { xInversion, yInversion } = surfaceRefs
|
|
canvas?.remove(obj)
|
|
|
|
//각도 추가
|
|
let originAngle = 0 //기본 남쪽
|
|
let direction = 'south'
|
|
if (azimuth === 'left') {
|
|
//서
|
|
originAngle = 90
|
|
direction = 'west'
|
|
} else if (azimuth === 'right') {
|
|
//동
|
|
originAngle = 270
|
|
direction = 'east'
|
|
} else if (azimuth === 'up') {
|
|
//북
|
|
originAngle = 180
|
|
direction = 'north'
|
|
}
|
|
|
|
//회전, flip등이 먹은 기준으로 새로생성
|
|
// const batchSurface = addPolygon(reorderedPoints, {
|
|
const batchSurface = addPolygon(obj.getCurrentPoints(), {
|
|
fill: 'transparent',
|
|
stroke: 'red',
|
|
strokeWidth: 3,
|
|
strokeDasharray: [10, 4],
|
|
fontSize: 12,
|
|
selectable: true,
|
|
lockMovementX: true, // X 축 이동 잠금
|
|
lockMovementY: true, // Y 축 이동 잠금
|
|
lockRotation: true, // 회전 잠금
|
|
lockScalingX: true, // X 축 크기 조정 잠금
|
|
lockScalingY: true, // Y 축 크기 조정 잠금
|
|
name: POLYGON_TYPE.ROOF,
|
|
originX: 'center',
|
|
originY: 'center',
|
|
pitch: globalPitch,
|
|
surfaceId: surfaceId,
|
|
direction: direction,
|
|
isXInversion: xInversion,
|
|
isYInversion: yInversion,
|
|
})
|
|
canvas.setActiveObject(batchSurface)
|
|
setSurfaceShapePattern(batchSurface, roofDisplay.column)
|
|
drawDirectionArrow(batchSurface)
|
|
|
|
// closePopup(id)
|
|
initEvent()
|
|
// if (+canvasSetting?.roofSizeSet === 3) return
|
|
// const popupId = uuidv4()
|
|
// addPopup(popupId, 2, <PlacementSurfaceLineProperty roof={batchSurface} id={popupId} setIsHidden={setIsHidden} />)
|
|
|
|
// console.log('xInversion', xInversion) //상하반전
|
|
// console.log('yInversion', yInversion) //좌우반전
|
|
|
|
changeSurfaceLineType(batchSurface)
|
|
|
|
if (setIsHidden) setIsHidden(false)
|
|
})
|
|
} else {
|
|
if (setIsHidden) setIsHidden(false)
|
|
}
|
|
}
|
|
|
|
//면형상 입력 validate
|
|
const checkSurfaceShape = (surfaceId, lengths) => {
|
|
const { length1, length2, length3, length4, length5 } = lengths
|
|
let check = true
|
|
|
|
if (surfaceId === 1) {
|
|
if (length1 === 0) {
|
|
swalFire({ text: getMessage('common.canvas.validate.size'), icon: 'error' })
|
|
check = false
|
|
}
|
|
if (length2 === 0) {
|
|
if (length3 === 0) {
|
|
swalFire({ text: getMessage('common.canvas.validate.size'), icon: 'error' })
|
|
check = false
|
|
}
|
|
}
|
|
} else if ([2, 4].includes(surfaceId)) {
|
|
if (length1 === 0 || length2 === 0) {
|
|
swalFire({ text: getMessage('common.canvas.validate.size'), icon: 'error' })
|
|
check = false
|
|
}
|
|
} else if ([3, 5, 6, 15, 18].includes(surfaceId)) {
|
|
if (length1 === 0 || length2 === 0 || length3 === 0) {
|
|
swalFire({ text: getMessage('common.canvas.validate.size'), icon: 'error' })
|
|
check = false
|
|
}
|
|
if (surfaceId === 3 && length3 >= length1) {
|
|
swalFire({ text: getMessage('surface.shape.validate.size.1to3'), icon: 'error' })
|
|
check = false
|
|
}
|
|
|
|
if (surfaceId === 5 || surfaceId === 15) {
|
|
if (length3 >= length2) {
|
|
swalFire({ text: getMessage('surface.shape.validate.size.2to3'), icon: 'error' })
|
|
check = false
|
|
}
|
|
}
|
|
if (surfaceId === 18) {
|
|
if (length2 >= length3) {
|
|
swalFire({ text: getMessage('surface.shape.validate.size.3to2'), icon: 'error' })
|
|
check = false
|
|
}
|
|
}
|
|
|
|
if (surfaceId === 6) {
|
|
if (length2 >= length3) {
|
|
swalFire({ text: getMessage('surface.shape.validate.size.3to2'), icon: 'error' })
|
|
check = false
|
|
}
|
|
}
|
|
} else if ([8, 12, 13, 16, 17].includes(surfaceId)) {
|
|
if (length1 === 0 || length2 === 0 || length3 === 0 || length4 === 0) {
|
|
swalFire({ text: getMessage('common.canvas.validate.size'), icon: 'error' })
|
|
check = false
|
|
}
|
|
|
|
if (surfaceId === 8) {
|
|
if (length2 >= length1) {
|
|
swalFire({ text: getMessage('surface.shape.validate.size.1to2'), icon: 'error' })
|
|
check = false
|
|
}
|
|
if (length4 >= length3) {
|
|
swalFire({ text: getMessage('surface.shape.validate.size.3to4'), icon: 'error' })
|
|
check = false
|
|
}
|
|
}
|
|
|
|
if (surfaceId === 12 || surfaceId === 13 || surfaceId === 16) {
|
|
if (length2 >= length1) {
|
|
swalFire({ text: getMessage('surface.shape.validate.size.1to2'), icon: 'error' })
|
|
check = false
|
|
}
|
|
|
|
if (length4 >= length3) {
|
|
swalFire({ text: getMessage('surface.shape.validate.size.3to4'), icon: 'error' })
|
|
check = false
|
|
}
|
|
}
|
|
|
|
if (surfaceId === 17) {
|
|
if (length2 >= length1) {
|
|
swalFire({ text: getMessage('surface.shape.validate.size.1to2'), icon: 'error' })
|
|
check = false
|
|
}
|
|
|
|
if (length3 >= length4) {
|
|
swalFire({ text: getMessage('surface.shape.validate.size.4to3'), icon: 'error' })
|
|
check = false
|
|
}
|
|
}
|
|
} else if ([7, 9, 10, 11, 14].includes(surfaceId)) {
|
|
if (length1 === 0 || length2 === 0 || length3 === 0 || length4 === 0 || length5 === 0) {
|
|
swalFire({ text: getMessage('common.canvas.validate.size'), icon: 'error' })
|
|
check = false
|
|
}
|
|
if (surfaceId === 9 || surfaceId === 10) {
|
|
if (length2 + length3 >= length1) {
|
|
swalFire({ text: getMessage('surface.shape.validate.size.1to23'), icon: 'error' })
|
|
check = false
|
|
}
|
|
if (length5 >= length4) {
|
|
swalFire({ text: getMessage('surface.shape.validate.size.4to5'), icon: 'error' })
|
|
check = false
|
|
}
|
|
}
|
|
|
|
if (surfaceId === 11) {
|
|
if (length1 > length2 + length3) {
|
|
swalFire({ text: getMessage('surface.shape.validate.size.1to23low'), icon: 'error' })
|
|
check = false
|
|
}
|
|
if (length5 >= length4) {
|
|
swalFire({ text: getMessage('surface.shape.validate.size.4to5'), icon: 'error' })
|
|
check = false
|
|
}
|
|
}
|
|
|
|
if (surfaceId === 14) {
|
|
if (length2 + length3 >= length1) {
|
|
swalFire({ text: getMessage('surface.shape.validate.size.1to23'), icon: 'error' })
|
|
check = false
|
|
}
|
|
if (length5 >= length4) {
|
|
swalFire({ text: getMessage('surface.shape.validate.size.4to5'), icon: 'error' })
|
|
check = false
|
|
}
|
|
}
|
|
}
|
|
return check
|
|
}
|
|
|
|
//면형상 가져오기
|
|
const getSurfaceShape = (surfaceId, pointer, lengths) => {
|
|
let points = []
|
|
const { length1, length2, length3, length4, length5 } = lengths
|
|
|
|
switch (surfaceId) {
|
|
case 1: {
|
|
let newLength2 = length2
|
|
|
|
if (length3 !== 0) {
|
|
newLength2 = Math.sqrt(length3 ** 2 - (length1 / 2) ** 2)
|
|
}
|
|
|
|
points = [
|
|
{ x: pointer.x, y: pointer.y - parseInt(newLength2) / 2 },
|
|
{ x: pointer.x - parseInt(length1) / 2, y: pointer.y + parseInt(newLength2) / 2 },
|
|
{ x: pointer.x + parseInt(length1) / 2, y: pointer.y + parseInt(newLength2) / 2 },
|
|
]
|
|
|
|
break
|
|
}
|
|
case 2: {
|
|
points = [
|
|
{ x: pointer.x - length1 / 2, y: pointer.y + length2 / 2 },
|
|
{ x: pointer.x + length1 / 2, y: pointer.y + length2 / 2 },
|
|
{ x: pointer.x + length1 / 2, y: pointer.y - length2 / 2 },
|
|
{ x: pointer.x - length1 / 2, y: pointer.y - length2 / 2 },
|
|
]
|
|
|
|
break
|
|
}
|
|
case 3: {
|
|
points = [
|
|
{ x: pointer.x - length1 / 2, y: pointer.y + length2 / 2 },
|
|
{ x: pointer.x + length1 / 2, y: pointer.y + length2 / 2 },
|
|
{ x: pointer.x + length3 / 2, y: pointer.y - length2 / 2 },
|
|
{ x: pointer.x - length3 / 2, y: pointer.y - length2 / 2 },
|
|
]
|
|
|
|
break
|
|
}
|
|
case 4: {
|
|
points = [
|
|
{ x: pointer.x - length1 / 2, y: pointer.y + length2 / 2 },
|
|
{ x: pointer.x + length1 / 2, y: pointer.y + length2 / 2 },
|
|
{ x: pointer.x + length1 / 2, y: pointer.y - length2 / 2 },
|
|
]
|
|
|
|
break
|
|
}
|
|
case 5: {
|
|
points = [
|
|
{ x: pointer.x - length1 / 2, y: pointer.y - length3 / 2 },
|
|
{ x: pointer.x - length1 / 2, y: pointer.y + length3 / 2 },
|
|
{ x: pointer.x + length1 / 2, y: pointer.y + length3 / 2 },
|
|
{ x: pointer.x + length1 / 2, y: pointer.y + length3 / 2 - length2 },
|
|
]
|
|
|
|
break
|
|
}
|
|
case 6: {
|
|
const angleInRadians = Math.asin(length2 / length3)
|
|
points = [
|
|
{ x: pointer.x - length1 / 2, y: pointer.y + length2 / 2 },
|
|
{
|
|
x: pointer.x - length1 / 2 + length3 * Math.cos(angleInRadians),
|
|
y: pointer.y + length2 / 2 - length3 * Math.sin(angleInRadians),
|
|
},
|
|
{
|
|
x: pointer.x + length1 / 2 + length3 * Math.cos(angleInRadians),
|
|
y: pointer.y + length2 / 2 - length3 * Math.sin(angleInRadians),
|
|
},
|
|
{ x: pointer.x + length1 / 2, y: pointer.y + length2 / 2 },
|
|
]
|
|
|
|
break
|
|
}
|
|
case 7: {
|
|
points = [
|
|
{ x: pointer.x - (length1 + length2 + length3) / 2, y: pointer.y - (length4 + length5) / 2 },
|
|
{ x: pointer.x - (length1 + length2 + length3) / 2, y: pointer.y + (length4 + length5) / 2 },
|
|
{ x: pointer.x - (length1 + length2 + length3) / 2 + length1, y: pointer.y + (length4 + length5) / 2 },
|
|
{
|
|
x: pointer.x - (length1 + length2 + length3) / 2 + length1,
|
|
y: pointer.y + (length4 + length5) / 2 - length5,
|
|
},
|
|
{
|
|
x: pointer.x - (length1 + length2 + length3) / 2 + length1 + length2,
|
|
y: pointer.y + (length4 + length5) / 2 - length5,
|
|
},
|
|
{
|
|
x: pointer.x - (length1 + length2 + length3) / 2 + length1 + length2,
|
|
y: pointer.y + (length4 + length5) / 2 - length5 + length5,
|
|
},
|
|
{
|
|
x: pointer.x - (length1 + length2 + length3) / 2 + length1 + length2 + length3,
|
|
y: pointer.y + (length4 + length5) / 2 - length5 + length5,
|
|
},
|
|
{
|
|
x: pointer.x - (length1 + length2 + length3) / 2 + length1 + length2 + length3,
|
|
y: pointer.y + (length4 + length5) / 2 - length5 + length5 - (length4 + length5),
|
|
},
|
|
]
|
|
|
|
break
|
|
}
|
|
case 8: {
|
|
points = [
|
|
{ x: pointer.x - length1 / 2, y: pointer.y + length4 / 2 },
|
|
{ x: pointer.x - length1 / 2 + length1, y: pointer.y + length4 / 2 },
|
|
{ x: pointer.x - length1 / 2 + length1, y: pointer.y + length4 / 2 - length3 },
|
|
{ x: pointer.x - length1 / 2 + length1 - (length1 - length2), y: pointer.y + length4 / 2 - length3 },
|
|
{
|
|
x: pointer.x - length1 / 2 + length1 - (length1 - length2),
|
|
y: pointer.y + length4 / 2 - length3 + (length3 - length4),
|
|
},
|
|
{
|
|
x: pointer.x - length1 / 2 + length1 - (length1 - length2) - length2,
|
|
y: pointer.y + length4 / 2 - length3 + (length3 - length4),
|
|
},
|
|
]
|
|
|
|
break
|
|
}
|
|
case 9: {
|
|
points = [
|
|
{ x: pointer.x - length1 / 2, y: pointer.y + length4 / 2 },
|
|
{ x: pointer.x - length1 / 2 + length1, y: pointer.y + length4 / 2 },
|
|
{ x: pointer.x - length1 / 2 + length1, y: pointer.y + length4 / 2 - length4 },
|
|
{ x: pointer.x - length1 / 2 + length1 - length3, y: pointer.y + length4 / 2 - length4 },
|
|
{
|
|
x: pointer.x - length1 / 2 + length1 - length3,
|
|
y: pointer.y + length4 / 2 - length4 + (length4 - length5),
|
|
},
|
|
{
|
|
x: pointer.x - length1 / 2 + length1 - length3 - length2,
|
|
y: pointer.y + length4 / 2 - length4 + (length4 - length5),
|
|
},
|
|
]
|
|
break
|
|
}
|
|
case 10: {
|
|
points = [
|
|
{ x: pointer.x + length1 / 2 - length1, y: pointer.y + length4 / 2 - length5 },
|
|
{ x: pointer.x + length1 / 2 - length1, y: pointer.y + length4 / 2 },
|
|
{ x: pointer.x + length1 / 2, y: pointer.y + length4 / 2 },
|
|
{
|
|
x: pointer.x + length1 / 2 - length1 + length2 + length3,
|
|
y: pointer.y + length4 / 2 - length5 - (length4 - length5),
|
|
},
|
|
{
|
|
x: pointer.x + length1 / 2 - length1 + length2,
|
|
y: pointer.y + length4 / 2 - length5 - (length4 - length5),
|
|
},
|
|
{ x: pointer.x + length1 / 2 - length1 + length2, y: pointer.y + length4 / 2 - length5 },
|
|
]
|
|
break
|
|
}
|
|
case 11: {
|
|
points = [
|
|
{ x: pointer.x - length1 / 2, y: pointer.y + length4 / 2 },
|
|
{ x: pointer.x - length1 / 2 + length1, y: pointer.y + length4 / 2 },
|
|
{ x: pointer.x - length1 / 2 + length1, y: pointer.y + length4 / 2 - length5 },
|
|
{ x: pointer.x - length1 / 2 + length1 - length2, y: pointer.y + length4 / 2 - length5 },
|
|
{
|
|
x: pointer.x - length1 / 2 + length1 - length2,
|
|
y: pointer.y + length4 / 2 - length5 - (length4 - length5),
|
|
},
|
|
{
|
|
x: pointer.x - length1 / 2 + length1 - length2 - length3,
|
|
y: pointer.y + length4 / 2 - length5 - (length4 - length5),
|
|
},
|
|
]
|
|
break
|
|
}
|
|
case 12: {
|
|
const leftHypotenuse = Math.sqrt(((length1 - length2) / 2) ** 2 + length3 ** 2)
|
|
const rightHypotenuse = (length4 / length3) * leftHypotenuse
|
|
|
|
const leftAngle = Math.acos((length1 - length2) / 2 / leftHypotenuse)
|
|
|
|
points = [
|
|
{
|
|
x: pointer.x - length1 / 2 + leftHypotenuse * Math.cos(leftAngle),
|
|
y: pointer.y + length3 / 2 - leftHypotenuse * Math.sin(leftAngle),
|
|
},
|
|
{ x: pointer.x - length1 / 2, y: pointer.y + length3 / 2 },
|
|
{ x: pointer.x + length1 / 2, y: pointer.y + length3 / 2 },
|
|
{
|
|
x: pointer.x + length1 / 2 - rightHypotenuse * Math.cos(leftAngle),
|
|
y: pointer.y + length3 / 2 - rightHypotenuse * Math.sin(leftAngle),
|
|
},
|
|
{
|
|
x: pointer.x + length1 / 2 - rightHypotenuse * Math.cos(leftAngle) - length2,
|
|
y: pointer.y + length3 / 2 - rightHypotenuse * Math.sin(leftAngle),
|
|
},
|
|
]
|
|
|
|
break
|
|
}
|
|
case 13: {
|
|
const pointsArray = [
|
|
{ x: 0, y: 0 },
|
|
{ x: 0, y: 0 },
|
|
{ x: 0, y: 0 },
|
|
{ x: 0, y: 0 },
|
|
{ x: 0, y: 0 },
|
|
]
|
|
|
|
const tmpPolygon = new QPolygon(
|
|
[
|
|
{ x: 0, y: length3 },
|
|
{ x: length1 - length2, y: length3 },
|
|
{ x: (length1 - length2) / 2, y: length3 - length3 },
|
|
],
|
|
{
|
|
fill: 'transparent',
|
|
stroke: 'black', //black
|
|
strokeWidth: 1,
|
|
selectable: false,
|
|
fontSize: 0,
|
|
},
|
|
)
|
|
|
|
const coord = getIntersectionPoint(tmpPolygon.lines[1].startPoint, tmpPolygon.lines[2].startPoint, length3 - length4)
|
|
const scale = (length1 - length2) / coord.x
|
|
|
|
tmpPolygon.set({ scaleX: scale })
|
|
tmpPolygon.setViewLengthText(false)
|
|
|
|
pointsArray[0].x = 0
|
|
pointsArray[0].y = length3 //바닥면부터 시작하게
|
|
pointsArray[1].x = pointsArray[0].x + length1
|
|
pointsArray[1].y = pointsArray[0].y
|
|
pointsArray[2].x = pointsArray[1].x
|
|
pointsArray[2].y = pointsArray[1].y - length4
|
|
pointsArray[3].x = pointsArray[2].x - length2
|
|
pointsArray[3].y = pointsArray[2].y
|
|
pointsArray[4].x = tmpPolygon.getCurrentPoints()[2].x
|
|
pointsArray[4].y = tmpPolygon.getCurrentPoints()[2].y
|
|
|
|
points = [
|
|
{
|
|
x: pointer.x - length1 / 2,
|
|
y: pointer.y + length4 / 2,
|
|
},
|
|
{
|
|
x: pointer.x + length1 / 2,
|
|
y: pointer.y + length4 / 2,
|
|
},
|
|
{
|
|
x: pointer.x + length1 / 2,
|
|
y: pointer.y - length4 / 2,
|
|
},
|
|
{
|
|
x: pointer.x - (length2 - length1 / 2),
|
|
y: pointer.y - length4 / 2,
|
|
},
|
|
{
|
|
x: pointer.x - length1 / 2 + pointsArray[4].x,
|
|
y: pointer.y - length3 + length4 / 2,
|
|
},
|
|
]
|
|
|
|
break
|
|
}
|
|
case 14: {
|
|
points = [
|
|
{ x: pointer.x - length1 / 2, y: pointer.y + length4 / 2 - length4 },
|
|
{ x: pointer.x - length1 / 2, y: pointer.y + length4 / 2 },
|
|
{ x: pointer.x - length1 / 2 + length2, y: pointer.y + length4 / 2 },
|
|
{
|
|
x: pointer.x - length1 / 2 + length2 + (length1 - length2 - length3) / 2,
|
|
y: pointer.y + length4 / 2 - length4 + length5,
|
|
},
|
|
{ x: pointer.x - length1 / 2 + length1 - length3, y: pointer.y + length4 / 2 - length4 + length4 },
|
|
{ x: pointer.x - length1 / 2 + length1, y: pointer.y + length4 / 2 - length4 + length4 },
|
|
{ x: pointer.x - length1 / 2 + length1, y: pointer.y + length4 / 2 - length4 },
|
|
]
|
|
break
|
|
}
|
|
|
|
case 15: {
|
|
points = [
|
|
{ x: pointer.x - length1 / 2, y: pointer.y + length2 - length2 / 2 - length3 },
|
|
{ x: pointer.x - length1 / 2, y: pointer.y + length2 - length2 / 2 },
|
|
{ x: pointer.x + length1 / 2, y: pointer.y + length2 - length2 / 2 - length3 + length3 },
|
|
{ x: pointer.x + length1 / 2, y: pointer.y + length2 - length2 / 2 - length3 },
|
|
{ x: pointer.x, y: pointer.y + length2 - length2 / 2 - length3 - (length2 - length3) },
|
|
]
|
|
break
|
|
}
|
|
|
|
case 16: {
|
|
points = [
|
|
{
|
|
x: pointer.x - length1 / 2 + (length1 - length2) / 2,
|
|
y: pointer.y + length3 / 2 - (length3 - length4) - length4,
|
|
},
|
|
{
|
|
x: pointer.x - length1 / 2 + (length1 - length2) / 2,
|
|
y: pointer.y + length3 / 2 - (length3 - length4),
|
|
},
|
|
{
|
|
x: pointer.x - length1 / 2,
|
|
y: pointer.y + length3 / 2,
|
|
},
|
|
{
|
|
x: pointer.x - length1 / 2 + length1,
|
|
y: pointer.y + length3 / 2,
|
|
},
|
|
{
|
|
x: pointer.x - length1 / 2 + (length1 - length2) / 2 + length2,
|
|
y: pointer.y + length3 / 2 - (length3 - length4) - length4 + length4,
|
|
},
|
|
{
|
|
x: pointer.x - length1 / 2 + (length1 - length2) / 2 + length2,
|
|
y: pointer.y + length3 / 2 - (length3 - length4) - length4,
|
|
},
|
|
]
|
|
break
|
|
}
|
|
case 17: {
|
|
const angle = (Math.asin(length3 / length4) * 180) / Math.PI // 높이와 빗변으로 먼저 각도구하기
|
|
|
|
const topL = (length1 - length2) / 2 / Math.cos((angle * Math.PI) / 180) // 꺽이는부분 윗쪽 길이
|
|
|
|
points = [
|
|
{
|
|
x: pointer.x - length1 / 2,
|
|
y: pointer.y + length3 / 2,
|
|
},
|
|
{
|
|
x: pointer.x - length1 / 2 + length1,
|
|
y: pointer.y + length3 / 2,
|
|
},
|
|
{
|
|
x: pointer.x - length1 / 2 + length4 * Math.cos(degreesToRadians(angle)) + length2 + topL * Math.cos(degreesToRadians(angle)),
|
|
y: pointer.y + length3 / 2 - length4 * Math.sin(degreesToRadians(angle)) + topL * Math.sin(degreesToRadians(angle)),
|
|
},
|
|
{
|
|
x: pointer.x - length1 / 2 + length4 * Math.cos(degreesToRadians(angle)) + length2,
|
|
y: pointer.y + length3 / 2 - length4 * Math.sin(degreesToRadians(angle)),
|
|
},
|
|
{
|
|
x: pointer.x - length1 / 2 + length4 * Math.cos(degreesToRadians(angle)),
|
|
y: pointer.y + length3 / 2 - length4 * Math.sin(degreesToRadians(angle)),
|
|
},
|
|
]
|
|
break
|
|
}
|
|
|
|
case 18: {
|
|
const a = Math.sqrt(length3 * length3 - length2 * length2) // 입력된 밑변과 높이
|
|
|
|
const sinA = a / length3
|
|
const angleInRadians = Math.asin(sinA)
|
|
const angleInDegrees = angleInRadians * (180 / Math.PI)
|
|
const b = a - length1 / 2
|
|
|
|
const c = b / Math.tan(angleInRadians)
|
|
const d = Math.sqrt(b * b + c * c)
|
|
const newAngleInRadians = (90 - angleInDegrees) * (Math.PI / 180)
|
|
points = [
|
|
{ x: pointer.x - (length1 + b) / 2, y: pointer.y + length2 / 2 },
|
|
{ x: pointer.x + length1 / 2 - b / 2, y: pointer.y + length2 / 2 },
|
|
{
|
|
x: pointer.x + length1 / 2 - b / 2 + d * Math.cos(newAngleInRadians),
|
|
y: pointer.y + length2 / 2 - d * Math.sin(newAngleInRadians),
|
|
},
|
|
{
|
|
x: pointer.x - (length1 + b) / 2 + length3 * Math.cos(newAngleInRadians),
|
|
y: pointer.y + length2 / 2 - length3 * Math.sin(newAngleInRadians),
|
|
},
|
|
]
|
|
break
|
|
}
|
|
}
|
|
|
|
return points
|
|
}
|
|
|
|
const deleteAllSurfacesAndObjects = async () => {
|
|
const backgroundImage = await getBackGroundImage({
|
|
objectId: currentCanvasPlan.id,
|
|
planNo: currentCanvasPlan.planNo,
|
|
})
|
|
console.log('🚀 ~ deleteAllSurfacesAndObjects ~ backgroundImage:', backgroundImage)
|
|
|
|
swalFire({
|
|
text: getMessage('batch.canvas.delete.all'),
|
|
type: 'confirm',
|
|
confirmFn: () => {
|
|
canvas.clear()
|
|
|
|
if (backgroundImage) {
|
|
fabric.Image.fromURL(`${backgroundImage.path}`, function (img) {
|
|
console.log('🚀 ~ img:', img)
|
|
img.set({
|
|
left: 0,
|
|
top: 0,
|
|
width: img.width,
|
|
height: img.height,
|
|
name: 'backGroundImage',
|
|
selectable: false,
|
|
hasRotatingPoint: false, // 회전 핸들 활성화
|
|
lockMovementX: false,
|
|
lockMovementY: false,
|
|
lockRotation: false,
|
|
lockScalingX: false,
|
|
lockScalingY: false,
|
|
})
|
|
// image = img
|
|
canvas?.add(img)
|
|
canvas?.sendToBack(img)
|
|
canvas?.renderAll()
|
|
// setBackImg(img)
|
|
})
|
|
}
|
|
|
|
resetOuterLinePoints()
|
|
resetPlacementShapeDrawingPoints()
|
|
fetchSettings()
|
|
swalFire({ text: getMessage('plan.message.delete') })
|
|
},
|
|
// denyFn: () => {
|
|
// swalFire({ text: '취소되었습니다.', icon: 'error' })
|
|
// },
|
|
})
|
|
}
|
|
|
|
const findAllChildren = (parentId) => {
|
|
let allChildren = []
|
|
|
|
// 직계 자식 객체들 찾기
|
|
const directChildren = canvas.getObjects().filter((obj) => obj.parentId === parentId)
|
|
|
|
directChildren.forEach((child) => {
|
|
allChildren.push(child) // 현재 자식 추가
|
|
|
|
// 자식이 그룹인 경우
|
|
if (child.type === 'group') {
|
|
// 그룹 내부의 객체들 추가
|
|
child.getObjects().forEach((groupItem) => {
|
|
allChildren.push(groupItem)
|
|
// 그룹 내부 객체의 자식들도 찾기
|
|
const nestedChildren = findAllChildren(groupItem.id)
|
|
allChildren.push(...nestedChildren)
|
|
})
|
|
}
|
|
|
|
// 현재 자식의 하위 자식들 찾기
|
|
const childrenOfChild = findAllChildren(child.id)
|
|
allChildren.push(...childrenOfChild)
|
|
})
|
|
|
|
// 중복 제거하여 반환
|
|
return [...new Set(allChildren)]
|
|
}
|
|
|
|
const findGroupObjects = (parentId) => {
|
|
let groupObjectsArray = []
|
|
|
|
// 직계 자식 객체들 찾기
|
|
const directChildren = canvas.getObjects().filter((obj) => obj.parentId === parentId)
|
|
|
|
// 각 자식 객체에 대해 처리
|
|
directChildren.forEach((child) => {
|
|
groupObjectsArray.push(child) // 현재 자식 추가
|
|
|
|
// 자식이 그룹인 경우 그룹 내부 객체들도 처리
|
|
if (child.type === 'group') {
|
|
child.getObjects().forEach((groupItem) => {
|
|
// 그룹 내부 각 아이템의 하위 객체들 찾기
|
|
const nestedObjects = findGroupObjects(groupItem.id)
|
|
groupObjectsArray.push(...nestedObjects)
|
|
})
|
|
}
|
|
|
|
// 일반 자식의 하위 객체들 찾기
|
|
const childObjects = findGroupObjects(child.id)
|
|
groupObjectsArray.push(...childObjects)
|
|
})
|
|
|
|
return groupObjectsArray
|
|
}
|
|
|
|
const moveSurfaceShapeBatch = () => {
|
|
const roof = canvas.getActiveObject()
|
|
|
|
if (roof) {
|
|
const childrenObjects = canvas.getObjects().filter((obj) => obj.parentId === roof.id)
|
|
|
|
const selectionArray = [roof, ...childrenObjects]
|
|
|
|
const selection = new fabric.ActiveSelection(selectionArray, {
|
|
canvas: canvas,
|
|
draggable: true,
|
|
lockMovementX: false, // X축 이동 허용
|
|
lockMovementY: false, // Y축 이동 허용
|
|
originX: 'center',
|
|
originY: 'center',
|
|
})
|
|
|
|
canvas.setActiveObject(selection)
|
|
|
|
addCanvasMouseEventListener('mouse:up', (e) => {
|
|
canvas.selection = true
|
|
|
|
canvas.discardActiveObject() // 모든 선택 해제
|
|
canvas.requestRenderAll() // 화면 업데이트
|
|
|
|
selection.getObjects().forEach((obj) => {
|
|
obj.set({
|
|
lockMovementX: true,
|
|
lockMovementY: true,
|
|
})
|
|
obj.setCoords()
|
|
|
|
if (obj.type === 'group') {
|
|
reGroupObject(obj)
|
|
}
|
|
})
|
|
|
|
canvas.renderAll()
|
|
roof.fire('polygonMoved')
|
|
drawDirectionArrow(roof)
|
|
initEvent()
|
|
})
|
|
}
|
|
}
|
|
|
|
const reGroupObject = (groupObj) => {
|
|
groupObj._restoreObjectsState() //이건 실행만 되도 그룹이 변경됨
|
|
const reGroupObjects = []
|
|
|
|
groupObj._objects.forEach((obj) => {
|
|
const newObj = new QPolygon(obj.getCurrentPoints(), {
|
|
...obj,
|
|
points: obj.getCurrentPoints(),
|
|
scaleX: 1,
|
|
scaleY: 1,
|
|
})
|
|
reGroupObjects.push(newObj)
|
|
canvas.remove(obj)
|
|
|
|
if (obj.direction) {
|
|
drawDirectionArrow(obj)
|
|
}
|
|
})
|
|
|
|
const reGroup = new fabric.Group(reGroupObjects, {
|
|
subTargetCheck: true,
|
|
name: groupObj.name,
|
|
id: groupObj.id,
|
|
groupYn: true,
|
|
parentId: groupObj.parentId,
|
|
})
|
|
canvas?.add(reGroup)
|
|
canvas?.remove(groupObj)
|
|
}
|
|
|
|
const resizeSurfaceShapeBatch = (side, target, width, height) => {
|
|
const originTarget = { ...target }
|
|
|
|
const objectWidth = target.width
|
|
const objectHeight = target.height
|
|
const changeWidth = width / 10 / objectWidth
|
|
const changeHeight = height / 10 / objectHeight
|
|
let sideX = 'left'
|
|
let sideY = 'top'
|
|
|
|
//그룹 중심점 변경
|
|
if (side === 2) {
|
|
sideX = 'right'
|
|
sideY = 'top'
|
|
} else if (side === 3) {
|
|
sideX = 'left'
|
|
sideY = 'bottom'
|
|
} else if (side === 4) {
|
|
sideX = 'right'
|
|
sideY = 'bottom'
|
|
}
|
|
|
|
//변경 전 좌표
|
|
const newCoords = target.getPointByOrigin(sideX, sideY)
|
|
|
|
target.set({
|
|
originX: sideX,
|
|
originY: sideY,
|
|
left: newCoords.x,
|
|
top: newCoords.y,
|
|
})
|
|
|
|
target.scaleX = changeWidth
|
|
target.scaleY = changeHeight
|
|
|
|
const currentPoints = target.getCurrentPoints()
|
|
|
|
target.set({
|
|
scaleX: 1,
|
|
scaleY: 1,
|
|
width: toFixedWithoutRounding(width / 10, 1),
|
|
height: toFixedWithoutRounding(height / 10, 1),
|
|
})
|
|
//크기 변경후 좌표를 재 적용
|
|
const changedCoords = target.getPointByOrigin(originTarget.originX, originTarget.originY)
|
|
|
|
target.set({
|
|
originX: originTarget.originX,
|
|
originY: originTarget.originY,
|
|
left: changedCoords.x,
|
|
top: changedCoords.y,
|
|
})
|
|
canvas.renderAll()
|
|
|
|
//면형상 리사이즈시에만
|
|
target.fire('polygonMoved')
|
|
target.points = currentPoints
|
|
target.fire('modified')
|
|
|
|
setSurfaceShapePattern(target, roofDisplay.column, false, target.roofMaterial, true)
|
|
|
|
if (target.direction) {
|
|
drawDirectionArrow(target)
|
|
}
|
|
target.setCoords()
|
|
canvas.renderAll()
|
|
}
|
|
|
|
const changeSurfaceLinePropertyEvent = () => {
|
|
let tmpLines = []
|
|
const roof = canvas.getActiveObject()
|
|
|
|
if (roof) {
|
|
roof.set({
|
|
selectable: false,
|
|
})
|
|
|
|
roof.lines.forEach((obj, index) => {
|
|
const tmpLine = new QLine([obj.x1, obj.y1, obj.x2, obj.y2], {
|
|
...obj,
|
|
stroke: 'rgb(3, 255, 0)',
|
|
strokeWidth: 8,
|
|
selectable: true,
|
|
name: 'lineProperty',
|
|
lineIndex: index,
|
|
})
|
|
|
|
tmpLines.push(tmpLine)
|
|
canvas.add(tmpLine)
|
|
})
|
|
|
|
addCanvasMouseEventListener('mouse:down', (e) => {
|
|
const selectedLine = e.target
|
|
if (selectedLine && selectedLine.name !== 'roof') {
|
|
tmpLines.forEach((line) => {
|
|
line.set({
|
|
stroke: 'rgb(3, 255, 0)',
|
|
name: 'lineProperty',
|
|
})
|
|
})
|
|
selectedLine.set({
|
|
stroke: 'red',
|
|
name: 'selectedLineProperty',
|
|
})
|
|
} else {
|
|
tmpLines.forEach((line) => {
|
|
line.set({
|
|
stroke: 'rgb(3, 255, 0)',
|
|
name: 'lineProperty',
|
|
})
|
|
})
|
|
}
|
|
})
|
|
|
|
canvas.renderAll()
|
|
}
|
|
canvas.discardActiveObject()
|
|
}
|
|
|
|
const changeSurfaceLineProperty = (property, roof) => {
|
|
if (!property) {
|
|
swalFire({ text: getMessage('modal.line.property.change'), icon: 'error' })
|
|
return
|
|
}
|
|
|
|
const selectedLine = canvas.getActiveObjects()[0] //배열로 떨어짐 한개만 선택가능
|
|
if (selectedLine && selectedLine.name === 'selectedLineProperty') {
|
|
swalFire({
|
|
text: getMessage('modal.line.property.change.confirm'),
|
|
type: 'confirm',
|
|
confirmFn: () => {
|
|
const lineIndex = selectedLine.lineIndex
|
|
roof.lines[lineIndex].attributes = {
|
|
...roof.lines[lineIndex].attributes,
|
|
type: property.value,
|
|
}
|
|
canvas.renderAll()
|
|
},
|
|
})
|
|
} else {
|
|
swalFire({ text: getMessage('modal.line.property.change.unselect'), icon: 'error' })
|
|
}
|
|
}
|
|
|
|
const changeSurfaceLinePropertyReset = (roof) => {
|
|
const lines = canvas.getObjects().filter((obj) => obj.name === 'lineProperty' || obj.name === 'selectedLineProperty')
|
|
|
|
lines.forEach((line) => {
|
|
canvas.remove(line)
|
|
})
|
|
|
|
if (roof) {
|
|
roof.set({
|
|
selectable: true,
|
|
})
|
|
}
|
|
canvas?.renderAll()
|
|
}
|
|
|
|
/**
|
|
* 면형상 작도시 라인 속성 넣는 로직
|
|
* 폴리곤으로 보면 직선방향에 따라 아래쪽인지 윗쪽인지 판단이 가능하다고 생각하여
|
|
* south -> 밑면은 무조건 right direction이라 가정하고 작업함 좌우반전시 반대로 그려지는 경우도 생기지만 그럴땐 흐름방향에 따라 최대값(최소값)을 찾아
|
|
* 해당 하는 흐름에 맞게 변경함
|
|
* @param { } polygon
|
|
*/
|
|
|
|
//폴리곤, 상하반전, 좌우반전
|
|
const changeSurfaceLineType = (polygon) => {
|
|
const { isXInversion, isYInversion } = polygon //상하반전, 좌우반전
|
|
|
|
polygon.lines.forEach((line) => {
|
|
line.attributes.type = LINE_TYPE.WALLLINE.GABLE
|
|
})
|
|
|
|
const directionConfig = {
|
|
south: { evaesDirection: 'right', ridgeDirection: 'left', coord1: 'y1', coord2: 'y2' },
|
|
north: { evaesDirection: 'left', ridgeDirection: 'right', coord1: 'y1', coord2: 'y2' },
|
|
east: { evaesDirection: 'top', ridgeDirection: 'bottom', coord1: 'x1', coord2: 'x2' },
|
|
west: { evaesDirection: 'bottom', ridgeDirection: 'top', coord1: 'x1', coord2: 'x2' },
|
|
}
|
|
|
|
const { evaesDirection, ridgeDirection, coord1, coord2 } = directionConfig[polygon.direction] || directionConfig.west
|
|
|
|
polygon.lines.forEach((line) => {
|
|
if (line[coord1] === line[coord2]) {
|
|
if (line.direction === evaesDirection) {
|
|
line.attributes.type = LINE_TYPE.WALLLINE.EAVES
|
|
} else if (line.direction === ridgeDirection) {
|
|
line.attributes.type = LINE_TYPE.SUBLINE.RIDGE
|
|
}
|
|
}
|
|
})
|
|
|
|
/**
|
|
* 진짜 처마 라인인지 확인하는 로직 -> 특정 모양에 따라 처마가 없는 경우가 있는데 위에 로직으로는
|
|
* 용마루도 처마로 만들어서 재보정
|
|
*/
|
|
//직선 찾는 로직
|
|
const maxLine = polygon.lines.filter((line) => line[coord1] === line[coord2])
|
|
|
|
if (maxLine.length > 0) {
|
|
const maxLineSorted = maxLine.reduce((a, b) => {
|
|
return (polygon.direction === 'south' || polygon.direction === 'east' ? b : a)[coord1] >
|
|
(polygon.direction === 'south' || polygon.direction === 'east' ? a : b)[coord1]
|
|
? b
|
|
: a
|
|
})
|
|
|
|
//정렬된 폴리곤이 아니면(대각선이 존재하는 폴리곤일때)
|
|
if (!polygon.isSortedPoints) {
|
|
//좌우 반전을 했으면 반대로 정의함
|
|
if (isYInversion || isXInversion) {
|
|
polygon.lines.forEach((line) => {
|
|
if (line.attributes.type === LINE_TYPE.WALLLINE.EAVES) {
|
|
line.attributes.type = LINE_TYPE.SUBLINE.RIDGE
|
|
} else if (line.attributes.type === LINE_TYPE.SUBLINE.RIDGE) {
|
|
line.attributes.type = LINE_TYPE.WALLLINE.EAVES
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
if (maxLine.length === 1) {
|
|
const maxLineCoord = polygon.lines.reduce((a, b) => {
|
|
return (polygon.direction === 'south' || polygon.direction === 'east' ? b : a)[coord1] >
|
|
(polygon.direction === 'south' || polygon.direction === 'east' ? a : b)[coord1]
|
|
? b
|
|
: a
|
|
})
|
|
|
|
const isRealEavesLine = polygon.lines.filter((line) => line.attributes.type === LINE_TYPE.WALLLINE.EAVES)
|
|
if (isRealEavesLine.length > 0) {
|
|
isRealEavesLine.forEach((line) => {
|
|
if (polygon.direction === 'south' || polygon.direction === 'north') {
|
|
const targetCoord =
|
|
polygon.direction === 'south' ? Math.max(maxLineCoord.y1, maxLineCoord.y2) : Math.min(maxLineCoord.y1, maxLineCoord.y2)
|
|
const realLineCoord = polygon.direction === 'south' ? Math.max(line.y1, line.y2) : Math.min(line.y1, line.y2)
|
|
|
|
if (targetCoord !== realLineCoord) {
|
|
line.attributes.type = LINE_TYPE.SUBLINE.RIDGE
|
|
}
|
|
} else if (polygon.direction === 'east' || polygon.direction === 'west') {
|
|
const targetCoord =
|
|
polygon.direction === 'east' ? Math.max(maxLineCoord.x1, maxLineCoord.x2) : Math.min(maxLineCoord.x1, maxLineCoord.x2)
|
|
const realLineCoord = polygon.direction === 'east' ? Math.max(line.x1, line.x2) : Math.min(line.x1, line.x2)
|
|
|
|
if (targetCoord !== realLineCoord) {
|
|
line.attributes.type = LINE_TYPE.SUBLINE.RIDGE
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function findCentroid(points) {
|
|
let sumX = 0,
|
|
sumY = 0
|
|
for (let i = 0; i < points.length; i++) {
|
|
sumX += points[i].x
|
|
sumY += points[i].y
|
|
}
|
|
return { x: sumX / points.length, y: sumY / points.length }
|
|
}
|
|
|
|
// 도형의 포인트를 왼쪽부터 반시계 방향으로 정렬하는 함수
|
|
/**
|
|
* 다각형의 점들을 시계 반대 방향으로 정렬하는 함수
|
|
* @param {Array} points - {x, y} 좌표 객체 배열
|
|
* @param {Object} startPoint - 시작점 (제공되지 않으면 가장 왼쪽 아래 점을 사용)
|
|
* @returns {Array} 시계 반대 방향으로 정렬된 점들의 배열
|
|
*/
|
|
function orderPointsCounterClockwise(points, startPoint = null) {
|
|
if (points.length <= 3) {
|
|
return points // 점이 3개 이하면 이미 다각형의 모든 점이므로 그대로 반환
|
|
}
|
|
|
|
// 시작점이 제공되지 않았다면 가장 왼쪽 아래 점을 찾음
|
|
let start = startPoint
|
|
if (!start) {
|
|
start = points[0]
|
|
for (let i = 1; i < points.length; i++) {
|
|
if (points[i].x < start.x || (points[i].x === start.x && points[i].y < start.y)) {
|
|
start = points[i]
|
|
}
|
|
}
|
|
}
|
|
|
|
// 다각형의 중심점 계산
|
|
let centerX = 0,
|
|
centerY = 0
|
|
for (let i = 0; i < points.length; i++) {
|
|
centerX += points[i].x
|
|
centerY += points[i].y
|
|
}
|
|
centerX /= points.length
|
|
centerY /= points.length
|
|
|
|
// 시작점에서 시계 반대 방향으로 각도 계산
|
|
let angles = []
|
|
for (let i = 0; i < points.length; i++) {
|
|
// 시작점은 제외
|
|
if (points[i] === start) continue
|
|
|
|
// 시작점을 기준으로 각 점의 각도 계산
|
|
let angle = Math.atan2(points[i].y - start.y, points[i].x - start.x)
|
|
|
|
// 각도가 음수면 2π를 더해 0~2π 범위로 변환
|
|
if (angle < 0) angle += 2 * Math.PI
|
|
|
|
angles.push({
|
|
point: points[i],
|
|
angle: angle,
|
|
})
|
|
}
|
|
|
|
// 각도에 따라 정렬 (시계 반대 방향)
|
|
angles.sort((a, b) => a.angle - b.angle)
|
|
|
|
// 정렬된 배열 생성 (시작점을 첫 번째로)
|
|
let orderedPoints = [start]
|
|
for (let i = 0; i < angles.length; i++) {
|
|
orderedPoints.push(angles[i].point)
|
|
}
|
|
|
|
return orderedPoints
|
|
}
|
|
|
|
/**
|
|
* 특정 점에서 시작하여 시계 반대 방향으로 다음 점을 찾는 함수
|
|
* @param {Object} currentPoint - 현재 점 {x, y}
|
|
* @param {Array} points - 모든 점들의 배열
|
|
* @param {Array} visited - 방문한 점들의 인덱스 배열
|
|
* @param {Object} prevVector - 이전 벡터 방향 (첫 호출에서는 null)
|
|
* @returns {Object} 다음 점의 인덱스와 객체
|
|
*/
|
|
function findNextCounterClockwisePoint(currentPoint, points, visited, prevVector = null) {
|
|
let minAngle = Infinity
|
|
let nextIndex = -1
|
|
|
|
// 이전 벡터가 없으면 (첫 점인 경우) 아래쪽을 향하는 벡터 사용
|
|
if (!prevVector) {
|
|
prevVector = { x: 0, y: -1 }
|
|
}
|
|
|
|
for (let i = 0; i < points.length; i++) {
|
|
// 이미 방문했거나 현재 점이면 건너뜀
|
|
if (visited.includes(i) || (points[i].x === currentPoint.x && points[i].y === currentPoint.y)) {
|
|
continue
|
|
}
|
|
|
|
// 현재 점에서 다음 후보 점으로의 벡터
|
|
let vector = {
|
|
x: points[i].x - currentPoint.x,
|
|
y: points[i].y - currentPoint.y,
|
|
}
|
|
|
|
// 벡터의 크기
|
|
let magnitude = Math.sqrt(vector.x * vector.x + vector.y * vector.y)
|
|
|
|
// 단위 벡터로 정규화
|
|
vector.x /= magnitude
|
|
vector.y /= magnitude
|
|
|
|
// 이전 벡터와 현재 벡터 사이의 각도 계산 (내적 사용)
|
|
let dotProduct = prevVector.x * vector.x + prevVector.y * vector.y
|
|
let crossProduct = prevVector.x * vector.y - prevVector.y * vector.x
|
|
|
|
// 각도 계산 (atan2 사용)
|
|
let angle = Math.atan2(crossProduct, dotProduct)
|
|
|
|
// 시계 반대 방향으로 가장 작은 각도를 가진 점 찾기
|
|
// 각도가 음수면 2π를 더해 0~2π 범위로 변환
|
|
if (angle < 0) angle += 2 * Math.PI
|
|
|
|
if (angle < minAngle) {
|
|
minAngle = angle
|
|
nextIndex = i
|
|
}
|
|
}
|
|
|
|
return nextIndex !== -1 ? { index: nextIndex, point: points[nextIndex] } : null
|
|
}
|
|
|
|
/**
|
|
* 다각형의 점들을 시계 반대 방향으로 추적하는 함수
|
|
* @param {Array} points - {x, y} 좌표 객체 배열
|
|
* @param {Object} startPoint - 시작점 (제공되지 않으면 가장 왼쪽 아래 점을 사용)
|
|
* @returns {Array} 시계 반대 방향으로 정렬된 점들의 배열
|
|
*/
|
|
function tracePolygonCounterClockwise(points, startPoint = null) {
|
|
if (points.length <= 3) {
|
|
return orderPointsCounterClockwise(points, startPoint)
|
|
}
|
|
|
|
// 시작점이 제공되지 않았다면 가장 왼쪽 아래 점을 찾음
|
|
let startIndex = 0
|
|
if (!startPoint) {
|
|
for (let i = 1; i < points.length; i++) {
|
|
if (points[i].x < points[startIndex].x || (points[i].x === points[startIndex].x && points[i].y < points[startIndex].y)) {
|
|
startIndex = i
|
|
}
|
|
}
|
|
startPoint = points[startIndex]
|
|
} else {
|
|
// 시작점이 제공된 경우 해당 점의 인덱스 찾기
|
|
for (let i = 0; i < points.length; i++) {
|
|
if (points[i].x === startPoint.x && points[i].y === startPoint.y) {
|
|
startIndex = i
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// 결과 배열 초기화
|
|
let orderedPoints = [startPoint]
|
|
let visited = [startIndex]
|
|
|
|
let currentPoint = startPoint
|
|
let prevVector = null
|
|
|
|
// 모든 점을 방문할 때까지 반복
|
|
while (visited.length < points.length) {
|
|
let next = findNextCounterClockwisePoint(currentPoint, points, visited, prevVector)
|
|
|
|
if (!next) break // 더 이상 찾을 점이 없으면 종료
|
|
|
|
orderedPoints.push(next.point)
|
|
visited.push(next.index)
|
|
|
|
// 이전 벡터 업데이트 (현재 점에서 다음 점으로의 벡터)
|
|
prevVector = {
|
|
x: next.point.x - currentPoint.x,
|
|
y: next.point.y - currentPoint.y,
|
|
}
|
|
|
|
// 벡터 정규화
|
|
let magnitude = Math.sqrt(prevVector.x * prevVector.x + prevVector.y * prevVector.y)
|
|
prevVector.x /= magnitude
|
|
prevVector.y /= magnitude
|
|
|
|
currentPoint = next.point
|
|
}
|
|
|
|
return orderedPoints
|
|
}
|
|
|
|
return {
|
|
applySurfaceShape,
|
|
deleteAllSurfacesAndObjects,
|
|
moveSurfaceShapeBatch,
|
|
resizeSurfaceShapeBatch,
|
|
changeSurfaceLinePropertyEvent,
|
|
changeSurfaceLineProperty,
|
|
changeSurfaceLinePropertyReset,
|
|
changeSurfaceLineType,
|
|
}
|
|
}
|