import { useRecoilValue } from 'recoil' import { canvasState, currentObjectState } from '@/store/canvasAtom' import { usePopup } from '@/hooks/usePopup' import { useMessage } from '@/hooks/useMessage' import { useEffect, useRef, useState } from 'react' import { useEvent } from '@/hooks/useEvent' import { LINE_TYPE, POLYGON_TYPE } from '@/common/common' import { useSwal } from '@/hooks/useSwal' import { QPolygon } from '@/components/fabric/QPolygon' import { getDegreeByChon } from '@/util/canvas-util' //동선이동 형 올림 내림 export function useMovementSetting(id) { const TYPE = { FLOW_LINE: 'flowLine', // 동선이동 UP_DOWN: 'updown', //형 올림내림 } const canvas = useRecoilValue(canvasState) const { initEvent, addCanvasMouseEventListener } = useEvent() // const { initEvent, addCanvasMouseEventListener } = useContext(EventContext) const { closePopup } = usePopup() const { getMessage } = useMessage() const currentObject = useRecoilValue(currentObjectState) const selectedObject = useRef(null) const buttonType = [ { id: 1, name: getMessage('modal.movement.flow.line.move'), type: TYPE.FLOW_LINE }, { id: 2, name: getMessage('modal.movement.flow.line.updown'), type: TYPE.UP_DOWN }, ] const [type, setType] = useState(TYPE.FLOW_LINE) const typeRef = useRef(type) const { swalFire } = useSwal() const FLOW_LINE_REF = { FILLED_INPUT_REF: useRef(null), DOWN_LEFT_RADIO_REF: useRef(null), UP_RIGHT_RADIO_REF: useRef(null), } const UP_DOWN_REF = { FILLED_INPUT_REF: useRef(null), UP_RADIO_REF: useRef(null), DOWN_RADIO_REF: useRef(null), } useEffect(() => { typeRef.current = type canvas.getObjects().forEach((obj) => { obj.set({ selectable: false }) }) const roofs = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF) // 기존 wallLine의 visible false roofs.forEach((roof) => { roof.set({ selectable: false }) roof.set({ strokeWidth: 1 }) roof.set({ stroke: '#000000' }) roof.innerLines.forEach((line) => { line.bringToFront() line.set({ selectable: false }) line.set({ strokeWidth: 1 }) line.set({ stroke: '#000000' }) }) roof.separatePolygon?.forEach((polygon) => { polygon.bringToFront() polygon.set({ selectable: false }) polygon.set({ strokeWidth: 1 }) polygon.set({ stroke: '#000000' }) }) }) if (type === TYPE.FLOW_LINE) { roofs.forEach((roof) => { roof.innerLines.forEach((line) => { if (line.name === LINE_TYPE.SUBLINE.RIDGE) { line.bringToFront() line.set({ visible: true }) line.set({ selectable: true }) line.set({ strokeWidth: 4 }) line.set({ stroke: '#1083E3' }) } }) }) } else if (type === TYPE.UP_DOWN) { const wallLine = canvas.getObjects().filter((obj) => obj.name === 'wallLine') // 기존 outerLine의 selectable true wallLine.forEach((line) => { line.bringToFront() line.set({ selectable: true }) }) } canvas.renderAll() }, [type]) useEffect(() => { canvas.renderAll() addCanvasMouseEventListener('mouse:move', mouseMoveEvent) addCanvasMouseEventListener('mouse:down', mouseDownEvent) return () => { initEvent() const wallLines = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.WALL) wallLines.forEach((line) => { line.set({ visible: true }) }) const wallLine = canvas.getObjects().filter((obj) => obj.name === 'wallLine') // 기존 outerLine의 selectable true wallLine.forEach((line) => { let wallStroke, wallStrokeWidth switch (line.attributes.type) { case LINE_TYPE.WALLLINE.EAVES: wallStroke = '#45CD7D' wallStrokeWidth = 4 break case LINE_TYPE.WALLLINE.HIPANDGABLE: wallStroke = '#45CD7D' wallStrokeWidth = 4 break case LINE_TYPE.WALLLINE.GABLE: wallStroke = '#3FBAE6' wallStrokeWidth = 4 break case LINE_TYPE.WALLLINE.JERKINHEAD: wallStroke = '#3FBAE6' wallStrokeWidth = 4 break case LINE_TYPE.WALLLINE.SHED: wallStroke = '#000000' wallStrokeWidth = 4 break case LINE_TYPE.WALLLINE.WALL: wallStroke = '#000000' wallStrokeWidth = 4 break } line.set({ selectable: false, stroke: wallStroke, strokeWidth: wallStrokeWidth }) }) canvas.renderAll() } }, []) useEffect(() => { console.log('selectedObject.current : ', selectedObject.current) if (selectedObject.current != null) { let wallStroke switch (selectedObject.current.attributes.type) { case LINE_TYPE.WALLLINE.EAVES: wallStroke = '#45CD7D' break case LINE_TYPE.WALLLINE.HIPANDGABLE: wallStroke = '#45CD7D' break case LINE_TYPE.WALLLINE.GABLE: wallStroke = '#3FBAE6' break case LINE_TYPE.WALLLINE.JERKINHEAD: wallStroke = '#3FBAE6' break case LINE_TYPE.WALLLINE.SHED: wallStroke = '#000000' break case LINE_TYPE.WALLLINE.WALL: wallStroke = '#000000' break } selectedObject.current.set({ stroke: wallStroke }) selectedObject.current.bringToFront() } selectedObject.current = null if (!currentObject) { return } clearRef() selectedObject.current = currentObject if (currentObject.name === 'wallLine') { currentObject.set({ stroke: '#EA10AC' }) currentObject.bringToFront() } canvas.renderAll() }, [currentObject]) const clearRef = () => { if (type === TYPE.FLOW_LINE) { FLOW_LINE_REF.FILLED_INPUT_REF.current.value = '' FLOW_LINE_REF.DOWN_LEFT_RADIO_REF.current.checked = true FLOW_LINE_REF.UP_RIGHT_RADIO_REF.current.checked = false } if (type === TYPE.UP_DOWN) { UP_DOWN_REF.FILLED_INPUT_REF.current.value = '' UP_DOWN_REF.UP_RADIO_REF.current.checked = true UP_DOWN_REF.DOWN_RADIO_REF.current.checked = false } } const mouseDownEvent = (e) => { if (typeRef.current === TYPE.FLOW_LINE) { saveFlowLine(e) } else { saveUpDownLine(e) } } const mouseMoveEvent = (e) => { if (typeRef.current === TYPE.FLOW_LINE) { flowLineMoveEvent(e) } else { updownMoveEvent(e) } } 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, } } //동선 이동 마우스 클릭 이벤트 const saveFlowLine = (e) => { const target = selectedObject.current if (!target) { return } let newPoint = [] if (Math.sign(target.x1 - target.x2) !== 0) { const sign = FLOW_LINE_REF.UP_RIGHT_RADIO_REF.current.checked ? 1 : -1 newPoint = [ target.x1, target.y1 - sign * Number(FLOW_LINE_REF.FILLED_INPUT_REF.current.value / 10), target.x2, target.y2 - sign * Number(FLOW_LINE_REF.FILLED_INPUT_REF.current.value / 10), ] } else { const sign = FLOW_LINE_REF.UP_RIGHT_RADIO_REF.current.checked ? -1 : 1 newPoint = [ target.x1 - sign * Number(FLOW_LINE_REF.FILLED_INPUT_REF.current.value / 10), target.y1, target.x2 - sign * Number(FLOW_LINE_REF.FILLED_INPUT_REF.current.value / 10), target.y2, ] } newPoint = newPoint.map((point) => Math.round(point * 10) / 10) const cloned = new fabric.Line(newPoint, {}) let currentObject = selectedObject.current const currentX1 = currentObject.x1, currentY1 = currentObject.y1, currentX2 = currentObject.x2, currentY2 = currentObject.y2 const currentMidX = (currentX1 + currentX2) / 2 const currentMidY = (currentY1 + currentY2) / 2 const clonedMidX = (cloned.x1 + cloned.x2) / 2 const clonedMidY = (cloned.y1 + cloned.y2) / 2 const roof = canvas.getObjects().find((obj) => obj.id === currentObject.attributes.roofId) let isOutside = false roof.lines.forEach((line) => { const intersection = edgesIntersection( { vertex1: { x: currentMidX, y: currentMidY }, vertex2: { x: clonedMidX, y: clonedMidY } }, { vertex1: { x: line.x1, y: line.y1 }, vertex2: { x: line.x2, y: line.y2 } }, ) if (intersection && !intersection.isIntersectionOutside) { isOutside = true } }) if (isOutside) { swalFire({ text: getMessage('modal.movement.flow.line.move.alert'), icon: 'error' }) return } currentObject.set({ x1: cloned.x1, y1: cloned.y1, x2: cloned.x2, y2: cloned.y2 }) currentObject.setCoords() const otherRidges = roof.innerLines.filter((line) => line.name === LINE_TYPE.SUBLINE.RIDGE && line.id !== currentObject.id) const overlapRidges = otherRidges.filter((line) => { if (currentObject.x1 === currentObject.x2 && line.x1 === line.x2 && 0 < Math.abs(currentObject.x1 - line.x1) < 1) { if ( currentObject.y1 <= line.y1 <= currentObject.y2 || currentObject.y1 >= line.y1 >= currentObject.y2 || currentObject.y1 <= line.y2 <= currentObject.y2 || currentObject.y1 >= line.y2 >= currentObject.y2 ) { return true } } if (currentObject.y1 === currentObject.y2 && line.y1 === line.y2 && 0 < Math.abs(currentObject.y1 - line.y1) < 1) { if ( currentObject.x1 <= line.x1 <= currentObject.x2 || currentObject.x1 >= line.x1 >= currentObject.x2 || currentObject.x1 <= line.x2 <= currentObject.x2 || currentObject.x1 >= line.x2 >= currentObject.x2 ) { return true } } }) if (overlapRidges.length > 0) { const minX = Math.min(...overlapRidges.flatMap((line) => [line.x1, line.x2]), currentObject.x1, currentObject.x2) const maxX = Math.max(...overlapRidges.flatMap((line) => [line.x1, line.x2]), currentObject.x1, currentObject.x2) const minY = Math.min(...overlapRidges.flatMap((line) => [line.y1, line.y2]), currentObject.y1, currentObject.y2) const maxY = Math.max(...overlapRidges.flatMap((line) => [line.y1, line.y2]), currentObject.y1, currentObject.y2) const newRidge = new fabric.Line([minX, minY, maxX, maxY], { name: LINE_TYPE.SUBLINE.RIDGE, selectable: true, stroke: '#1083E3', strokeWidth: 4, attributes: { roofId: roof.id, currentRoofId: currentObject.attributes.currentRoofId }, }) overlapRidges.forEach((line) => line.attributes.currentRoofId.forEach((id) => { if (!newRidge.attributes.currentRoofId.includes(id)) { newRidge.attributes.currentRoofId.push(id) } }), ) canvas.add(newRidge) newRidge.bringToFront() roof.innerLines.push(newRidge) canvas.remove(currentObject) roof.innerLines.forEach((innerLine, index) => { if (innerLine.id === currentObject.id) { roof.innerLines.splice(index, 1) } }) overlapRidges.forEach((line) => { canvas.remove(line) roof.innerLines.forEach((innerLine, index) => { if (innerLine.id === line.id) { roof.innerLines.splice(index, 1) } }) }) } if (roof.separatePolygon.length > 0) { redrawSeparatePolygon(roof) } else { roof.innerLines .filter((line) => currentObject !== line) .filter( (line) => (line.x1 === currentX1 && line.y1 === currentY1) || (line.x2 === currentX1 && line.y2 === currentY1) || (line.x1 === currentX2 && line.y1 === currentY2) || (line.x2 === currentX2 && line.y2 === currentY2), ) .forEach((line) => { const lineDegree = 90 - Math.asin(line.attributes.planeSize / line.attributes.actualSize) * (180 / Math.PI) if (line.x1 === currentX1 && line.y1 === currentY1) { line.set({ x1: newPoint[0], y1: newPoint[1] }) } else if (line.x2 === currentX1 && line.y2 === currentY1) { line.set({ x2: newPoint[0], y2: newPoint[1] }) } else if (line.x1 === currentX2 && line.y1 === currentY2) { line.set({ x1: newPoint[2], y1: newPoint[3] }) } else if (line.x2 === currentX2 && line.y2 === currentY2) { line.set({ x2: newPoint[2], y2: newPoint[3] }) } line.setCoords() line.attributes.planeSize = Math.round(Math.sqrt(Math.pow(line.x2 - line.x1, 2) + Math.pow(line.y2 - line.y1, 2)) * 10) line.attributes.actualSize = Math.round( Math.sqrt(Math.pow(line.attributes.planeSize, 2) + Math.pow(line.attributes.planeSize * Math.tan(lineDegree * (Math.PI / 180)), 2)), ) }) } currentObject.set({ x1: cloned.x1, y1: cloned.y1, x2: cloned.x2, y2: cloned.y2 }) currentObject.setCoords() canvas.renderAll() canvas.discardActiveObject() FLOW_LINE_REF.FILLED_INPUT_REF.current.value = '' } //형 올림내림 마우스 클릭 이벤트 const saveUpDownLine = (e) => { const target = selectedObject.current if (!target) { return } const roof = canvas.getObjects().find((obj) => obj.id === target.attributes.roofId) const wallLines = roof.wall.lines const roofLines = roof.lines const currentWall = wallLines.find((line) => line.id === target.attributes.currentWall) let prevWall, nextWall let prevEave, nextEave wallLines.forEach((wallLine, index) => { if (wallLine.id === currentWall.id) { prevWall = index === 0 ? wallLines[wallLines.length - 1] : wallLines[index - 1] nextWall = index === wallLines.length - 1 ? wallLines[0] : wallLines[index + 1] } }) const eaves = roofLines.filter((line) => line.attributes?.type === LINE_TYPE.WALLLINE.EAVES) wallLines .filter((line) => line.attributes.type === LINE_TYPE.WALLLINE.EAVES) .forEach((eave, index) => { if (eave.id === currentWall.id) { prevEave = index === 0 ? eaves[eaves.length - 1] : eaves[index - 1] nextEave = index === eaves.length - 1 ? eaves[0] : eaves[index + 1] } }) const currentRoof = roofLines.find((line) => line.id === target.attributes.currentRoof) const currentRidges = roof.innerLines.filter( (line) => line.name === LINE_TYPE.SUBLINE.RIDGE && line.attributes.currentRoof.includes(currentRoof.id), ) /*const prevWallRidges = roof.innerLines.filter( (line) => line.name === LINE_TYPE.SUBLINE.RIDGE && line.attributes.currentRoof.includes(prevEave.id), ) const nextWallRidges = roof.innerLines.filter( (line) => line.name === LINE_TYPE.SUBLINE.RIDGE && line.attributes.currentRoof.includes(nextEave.id), ) //라인 확인 작업 /!* const roofLine = new fabric.Line([currentRoof.x1, currentRoof.y1, currentRoof.x2, currentRoof.y2], { stroke: 'red', strokeWidth: 5, selectable: false, }) canvas.add(roofLine) const prevEaves = new fabric.Line([prevEave.x1, prevEave.y1, prevEave.x2, prevEave.y2], { stroke: 'yellow', strokeWidth: 5, selectable: false, }) canvas.add(prevEaves) currentRidges.forEach((ridge) => ridge.set({ stroke: 'purple', strokeWidth: 5 })) prevWallRidges.forEach((ridge) => ridge.set({ stroke: 'yellow', strokeWidth: 5 })) nextWallRidges.forEach((ridge) => ridge.set({ stroke: 'green', strokeWidth: 5 })) canvas.renderAll()*!/*/ console.log( 'UP/DOWN', UP_DOWN_REF.UP_RADIO_REF.current.checked, UP_DOWN_REF.DOWN_RADIO_REF.current.checked, UP_DOWN_REF.FILLED_INPUT_REF.current.value, ) let compareLine if (Math.sign(currentRoof.x1 - currentRoof.x2) === 0) { const currentDiff = currentRidges[0].x1 - currentRoof.x1 const prevDiff = prevEave.x1 - currentRoof.x1 const nextDiff = nextEave.x1 - currentRoof.x1 console.log('currentDiff', Math.sign(currentDiff), currentDiff) if (UP_DOWN_REF.UP_RADIO_REF.current.checked) { if (Math.sign(currentDiff) === 1) { } else { if (Math.sign(prevDiff) === 1 && Math.sign(nextDiff) === 1) { } else { } } } else { if (Math.sign(currentDiff) === -1) { } else { if (Math.sign(prevDiff) === -1 && Math.sign(nextDiff) === -1) { } else { } } } } else { const currentDiff = currentRidges[0].y1 - currentRoof.y1 const prevDiff = prevEave.y1 - currentRoof.y1 const nextDiff = nextEave.y1 - currentRoof.y1 if (UP_DOWN_REF.UP_RADIO_REF.current.checked) { if (Math.sign(currentDiff) === 1) { } else { if (Math.sign(prevDiff) === 1 && Math.sign(nextDiff) === 1) { } else { } } } else { if (Math.sign(currentDiff) === -1) { } else { if (Math.sign(prevDiff) === -1 && Math.sign(nextDiff) === -1) { } else { } } } } //확인 /*const compareRoofLine = new fabric.Line([compareRoof.x1, compareRoof.y1, compareRoof.x2, compareRoof.y2], { stroke: 'red', strokeWidth: 5, selectable: false, }) canvas.add(compareRoofLine) canvas.renderAll()*/ } const redrawSeparatePolygon = (roof) => { roof.separatePolygon.forEach((polygon) => canvas.remove(polygon)) roof.separatePolygon = [] const roofLines = roof.lines const wallLines = roof.wall.lines const eaves = [] roofLines.forEach((currentRoof, index) => { if (currentRoof.attributes?.type === LINE_TYPE.WALLLINE.EAVES) { eaves.push({ index: index, roof: currentRoof, length: currentRoof.attributes.planeSize }) } }) const ridges = roof.innerLines.filter((line) => line.name === LINE_TYPE.SUBLINE.RIDGE) eaves.sort((a, b) => a.length - b.length) const ridgeCount = eaves.length - 1 console.log('ridgeCount', ridgeCount, 'ridges', ridges.length) const duplicatedEaves = [] ridges.forEach((ridge) => { const positiveLines = [] const negativeLines = [] ridge.attributes.currentRoof.forEach((id) => { console.log('id : ', id) const eave = roofLines.find((obj) => obj.id === id) console.log('roof : ', eave) if (ridge.x1 === ridge.x2) { if (eave.y1 < eave.y2) { positiveLines.push(eave) } else { negativeLines.push(eave) } } else { if (eave.x1 < eave.x2) { positiveLines.push(eave) } else { negativeLines.push(eave) } } }) if (positiveLines.length > 1) { duplicatedEaves.push(positiveLines) } if (negativeLines.length > 1) { duplicatedEaves.push(negativeLines) } }) console.log('duplicatedEaves', duplicatedEaves) duplicatedEaves.forEach((duplicatedEave) => { duplicatedEave.forEach((eave) => { const index = eaves.findIndex((item) => item.roof.id === eave.id) eaves.splice(index, 1) }) }) // if (ridgeCount === ridges.length) { eaves.forEach((eave, i) => { const index = eave.index, currentRoof = eave.roof const currentWall = wallLines[index] const currentRidges = ridges.filter((ridge) => ridge.attributes.currentRoof.includes(eave.roof.id)) let points = [] const signX = Math.sign(currentRoof.x1 - currentRoof.x2) let currentX1 = currentRoof.x1, currentY1 = currentRoof.y1, currentX2 = currentRoof.x2, currentY2 = currentRoof.y2 let changeX1 = [Math.min(currentRoof.x1, currentRoof.x2), Math.min(currentRoof.x1, currentRoof.x2)], changeY1 = [Math.min(currentRoof.y1, currentRoof.y2), Math.min(currentRoof.y1, currentRoof.y2)], changeX2 = [Math.max(currentRoof.x2, currentRoof.x1), Math.max(currentRoof.x2, currentRoof.x1)], changeY2 = [Math.max(currentRoof.y2, currentRoof.y1), Math.max(currentRoof.y2, currentRoof.y1)] if (signX === 0) { currentY1 = Math.min(currentRoof.y1, currentRoof.y2, currentWall.y1, currentWall.y2) changeY1[1] = currentY1 currentY2 = Math.max(currentRoof.y1, currentRoof.y2, currentWall.y1, currentWall.y2) changeY2[1] = currentY2 } else { currentX1 = Math.min(currentRoof.x1, currentRoof.x2, currentWall.x1, currentWall.x2) changeX1[1] = currentX1 currentX2 = Math.max(currentRoof.x1, currentRoof.x2, currentWall.x1, currentWall.x2) changeX2[1] = currentX2 } points.push({ x: currentX1, y: currentY1 }, { x: currentX2, y: currentY2 }) currentRidges.forEach((ridge) => { let ridgeX1 = ridge.x1, ridgeY1 = ridge.y1, ridgeX2 = ridge.x2, ridgeY2 = ridge.y2 if (signX === 0) { ridgeY1 = Math.min(ridge.y1, ridge.y2) ridgeY2 = Math.max(ridge.y1, ridge.y2) } else { ridgeX1 = Math.min(ridge.x1, ridge.x2) ridgeX2 = Math.max(ridge.x1, ridge.x2) } points.push({ x: ridgeX1, y: ridgeY1 }, { x: ridgeX2, y: ridgeY2 }) }) points.forEach((point) => { if (point.x === changeX1[0] && changeX1[0] !== changeX1[1]) { point.x = changeX1[1] } if (point.x === changeX2[0] && changeX2[0] !== changeX2[1]) { point.x = changeX2[1] } if (point.y === changeY1[0] && changeY1[0] !== changeY1[1]) { point.y = changeY1[1] } if (point.y === changeY2[0] && changeY2[0] !== changeY2[1]) { point.y = changeY2[1] } }) //중복된 point 제거 points = points.filter((point, index, self) => index === self.findIndex((p) => p.x === point.x && p.y === point.y)) //point 정렬 (가장 좌측, 최상단의 점을 기준으로 삼는다.) const startPoint = points .filter((point) => point.x === Math.min(...points.map((point) => point.x))) .reduce((prev, current) => { return prev.y < current.y ? prev : current }) const sortedPoints = [] sortedPoints.push(startPoint) points.forEach((p, index) => { if (index === 0) { //시작점 다음 점 찾기, y좌표가 startPoint.y 보다 큰 점 중 x좌표가 가까운 점 const underStartPoint = points.filter((point) => point.y > startPoint.y) const nextPoint = underStartPoint .filter((point) => point.x === startPoint.x) .reduce((prev, current) => { if (prev === undefined) { return current } return Math.abs(prev.y - startPoint.y) < Math.abs(current.y - startPoint.y) ? prev : current }, undefined) if (nextPoint) { sortedPoints.push(nextPoint) } else { const nextPoint = underStartPoint.reduce((prev, current) => { const prevHypos = Math.sqrt(Math.abs(Math.pow(prev.x - startPoint.x, 2)) + Math.abs(Math.pow(prev.y - startPoint.y, 2))) const currentHypos = Math.sqrt(Math.abs(Math.pow(current.x - startPoint.x, 2)) + Math.abs(Math.pow(current.y - startPoint.y, 2))) return prevHypos < currentHypos ? prev : current }, undefined) sortedPoints.push(nextPoint) } } else { const lastPoint = sortedPoints[sortedPoints.length - 1] console.log('lastPoint', lastPoint) const prevPoint = sortedPoints[sortedPoints.length - 2] const otherPoints = points.filter((point) => sortedPoints.includes(point) === false) const nextPoint = otherPoints.reduce((prev, current) => { if (prev === undefined) { const height = Math.abs(Math.sqrt(Math.abs(Math.pow(prevPoint.x - lastPoint.x, 2)) + Math.abs(Math.pow(prevPoint.y - lastPoint.y, 2)))) const adjacent = Math.abs(Math.sqrt(Math.abs(Math.pow(current.x - lastPoint.x, 2)) + Math.abs(Math.pow(current.y - lastPoint.y, 2)))) const hypotenuse = Math.abs(Math.sqrt(Math.abs(Math.pow(current.x - prevPoint.x, 2)) + Math.abs(Math.pow(current.y - prevPoint.y, 2)))) const angle = Math.round( Math.acos((Math.pow(adjacent, 2) + Math.pow(height, 2) - Math.pow(hypotenuse, 2)) / (2 * adjacent * height)) * (180 / Math.PI), ) if (angle === 90) { return current } } else { return prev } }, undefined) if (nextPoint) { sortedPoints.push(nextPoint) } else { const nextPoint = otherPoints.reduce((prev, current) => { if (prev !== undefined) { const height = Math.abs( Math.sqrt(Math.abs(Math.pow(prevPoint.x - lastPoint.x, 2)) + Math.abs(Math.pow(prevPoint.y - lastPoint.y, 2))), ) const adjacentC = Math.abs(Math.sqrt(Math.abs(Math.pow(current.x - lastPoint.x, 2)) + Math.abs(Math.pow(current.y - lastPoint.y, 2)))) const hypotenuseC = Math.abs( Math.sqrt(Math.abs(Math.pow(current.x - prevPoint.x, 2)) + Math.abs(Math.pow(current.y - prevPoint.y, 2))), ) const angleC = Math.round( Math.acos((Math.pow(adjacentC, 2) + Math.pow(height, 2) - Math.pow(hypotenuseC, 2)) / (2 * adjacentC * height)) * (180 / Math.PI), ) const adjacentP = Math.abs(Math.sqrt(Math.abs(Math.pow(prev.x - lastPoint.x, 2)) + Math.abs(Math.pow(prev.y - lastPoint.y, 2)))) const hypotenuseP = Math.abs(Math.sqrt(Math.abs(Math.pow(prev.x - prevPoint.x, 2)) + Math.abs(Math.pow(prev.y - prevPoint.y, 2)))) const angleP = Math.round( Math.acos((Math.pow(adjacentP, 2) + Math.pow(height, 2) - Math.pow(hypotenuseP, 2)) / (2 * adjacentP * height)) * (180 / Math.PI), ) if (Math.abs(90 - angleC) < Math.abs(90 - angleP)) { return current } else { return prev } } else { return current } }, undefined) if (nextPoint) { sortedPoints.push(nextPoint) } } } }) if (sortedPoints.length > 0) { const roofPolygon = new QPolygon(sortedPoints, { fill: 'transparent', stroke: '#000000', strokeWidth: 1, selectable: false, fontSize: roof.fontSize, name: 'roofPolygon', attributes: { roofId: roof.id, currentRoofId: currentRoof.id, pitch: currentRoof.attributes.pitch, degree: currentRoof.attributes.degree, direction: currentRoof.direction, }, }) const currentDegree = currentRoof.attributes.pitch > 0 ? getDegreeByChon(currentRoof.attributes.pitch) : currentRoof.attributes.degree //지붕 각도에 따른 실측치 조정 roofPolygon.lines.forEach((line) => { line.attributes.planeSize = Math.round(Math.sqrt(Math.pow(line.x2 - line.x1, 2) + Math.pow(line.y2 - line.y1, 2)) * 10) const slope = (line) => (line.x2 - line.x1 === 0 ? Infinity : (line.y2 - line.y1) / (line.x2 - line.x1)) if (currentDegree > 0 && slope(line) !== slope(currentRoof)) { const height = Math.tan(currentDegree * (Math.PI / 180)) * line.attributes.planeSize line.attributes.actualSize = Math.round(Math.sqrt(Math.pow(line.attributes.planeSize, 2) + Math.pow(height, 2))) } else { line.attributes.actualSize = line.attributes.planeSize } }) roof.separatePolygon.push(roofPolygon) canvas.add(roofPolygon) canvas.renderAll() } }) duplicatedEaves.forEach((duplicatedEave) => { const currentRoof = duplicatedEave[0] let points = [] duplicatedEave.forEach((eave) => { points.push({ x: eave.x1, y: eave.y1 }, { x: eave.x2, y: eave.y2 }) const currentRidges = ridges.filter((ridge) => ridge.attributes.currentRoof.includes(eave.id)) currentRidges.forEach((ridge) => { points.push({ x: ridge.x1, y: ridge.y1 }, { x: ridge.x2, y: ridge.y2 }) }) }) console.log('points', points) points = points.filter((point, index, self) => index === self.findIndex((p) => p.x === point.x && p.y === point.y)) //point 정렬 (가장 좌측, 최상단의 점을 기준으로 삼는다.) const startPoint = points .filter((point) => point.x === Math.min(...points.map((point) => point.x))) .reduce((prev, current) => { return prev.y < current.y ? prev : current }) const sortedPoints = [] sortedPoints.push(startPoint) points.forEach((p, index) => { if (index === 0) { //시작점 다음 점 찾기, y좌표가 startPoint.y 보다 큰 점 중 x좌표가 가까운 점 const underStartPoint = points.filter((point) => point.y > startPoint.y) const nextPoint = underStartPoint .filter((point) => point.x === startPoint.x) .reduce((prev, current) => { if (prev === undefined) { return current } return Math.abs(prev.y - startPoint.y) < Math.abs(current.y - startPoint.y) ? prev : current }, undefined) if (nextPoint) { sortedPoints.push(nextPoint) } else { const nextPoint = underStartPoint.reduce((prev, current) => { const prevHypos = Math.sqrt(Math.abs(Math.pow(prev.x - startPoint.x, 2)) + Math.abs(Math.pow(prev.y - startPoint.y, 2))) const currentHypos = Math.sqrt(Math.abs(Math.pow(current.x - startPoint.x, 2)) + Math.abs(Math.pow(current.y - startPoint.y, 2))) return prevHypos < currentHypos ? prev : current }, undefined) sortedPoints.push(nextPoint) } } else { const lastPoint = sortedPoints[sortedPoints.length - 1] console.log('lastPoint', lastPoint) const prevPoint = sortedPoints[sortedPoints.length - 2] const otherPoints = points.filter((point) => sortedPoints.includes(point) === false) const nextPoint = otherPoints.reduce((prev, current) => { if (prev === undefined) { const height = Math.abs(Math.sqrt(Math.abs(Math.pow(prevPoint.x - lastPoint.x, 2)) + Math.abs(Math.pow(prevPoint.y - lastPoint.y, 2)))) const adjacent = Math.abs(Math.sqrt(Math.abs(Math.pow(current.x - lastPoint.x, 2)) + Math.abs(Math.pow(current.y - lastPoint.y, 2)))) const hypotenuse = Math.abs(Math.sqrt(Math.abs(Math.pow(current.x - prevPoint.x, 2)) + Math.abs(Math.pow(current.y - prevPoint.y, 2)))) const angle = Math.round( Math.acos((Math.pow(adjacent, 2) + Math.pow(height, 2) - Math.pow(hypotenuse, 2)) / (2 * adjacent * height)) * (180 / Math.PI), ) if (angle === 90) { return current } } else { return prev } }, undefined) if (nextPoint) { sortedPoints.push(nextPoint) } else { const nextPoint = otherPoints.reduce((prev, current) => { if (prev !== undefined) { const height = Math.abs( Math.sqrt(Math.abs(Math.pow(prevPoint.x - lastPoint.x, 2)) + Math.abs(Math.pow(prevPoint.y - lastPoint.y, 2))), ) const adjacentC = Math.abs(Math.sqrt(Math.abs(Math.pow(current.x - lastPoint.x, 2)) + Math.abs(Math.pow(current.y - lastPoint.y, 2)))) const hypotenuseC = Math.abs( Math.sqrt(Math.abs(Math.pow(current.x - prevPoint.x, 2)) + Math.abs(Math.pow(current.y - prevPoint.y, 2))), ) const angleC = Math.round( Math.acos((Math.pow(adjacentC, 2) + Math.pow(height, 2) - Math.pow(hypotenuseC, 2)) / (2 * adjacentC * height)) * (180 / Math.PI), ) const adjacentP = Math.abs(Math.sqrt(Math.abs(Math.pow(prev.x - lastPoint.x, 2)) + Math.abs(Math.pow(prev.y - lastPoint.y, 2)))) const hypotenuseP = Math.abs(Math.sqrt(Math.abs(Math.pow(prev.x - prevPoint.x, 2)) + Math.abs(Math.pow(prev.y - prevPoint.y, 2)))) const angleP = Math.round( Math.acos((Math.pow(adjacentP, 2) + Math.pow(height, 2) - Math.pow(hypotenuseP, 2)) / (2 * adjacentP * height)) * (180 / Math.PI), ) if (Math.abs(90 - angleC) < Math.abs(90 - angleP)) { return current } else { return prev } } else { return current } }, undefined) if (nextPoint) { sortedPoints.push(nextPoint) } } } }) if (sortedPoints.length > 0) { const roofPolygon = new QPolygon(sortedPoints, { fill: 'transparent', stroke: '#000000', strokeWidth: 1, selectable: false, fontSize: roof.fontSize, name: 'roofPolygon', attributes: { roofId: roof.id, currentRoofId: currentRoof.id, pitch: currentRoof.attributes.pitch, degree: currentRoof.attributes.degree, direction: currentRoof.direction, }, }) const currentDegree = currentRoof.attributes.pitch > 0 ? getDegreeByChon(currentRoof.attributes.pitch) : currentRoof.attributes.degree //지붕 각도에 따른 실측치 조정 roofPolygon.lines.forEach((line) => { line.attributes.planeSize = Math.round(Math.sqrt(Math.pow(line.x2 - line.x1, 2) + Math.pow(line.y2 - line.y1, 2)) * 10) const slope = (line) => (line.x2 - line.x1 === 0 ? Infinity : (line.y2 - line.y1) / (line.x2 - line.x1)) if (currentDegree > 0 && slope(line) !== slope(currentRoof)) { const height = Math.tan(currentDegree * (Math.PI / 180)) * line.attributes.planeSize line.attributes.actualSize = Math.round(Math.sqrt(Math.pow(line.attributes.planeSize, 2) + Math.pow(height, 2))) } else { line.attributes.actualSize = line.attributes.planeSize } }) roof.separatePolygon.push(roofPolygon) canvas.add(roofPolygon) canvas.renderAll() } }) console.log('roof.separatePolygon : ', roof.separatePolygon) ridges.forEach((ridge) => ridge.bringToFront()) console.log('ridges : ', ridges) } const flowLineMoveEvent = (e) => { const target = canvas.getActiveObject() if (!target) { return } const { top: targetTop, left: targetLeft } = target const currentX = canvas.getPointer(e.e).x const currentY = Math.floor(canvas.getPointer(e.e).y) if (Math.sign(target.x1 - target.x2) !== 0) { if (targetTop > currentY) { FLOW_LINE_REF.UP_RIGHT_RADIO_REF.current.checked = true FLOW_LINE_REF.FILLED_INPUT_REF.current.value = Math.floor((Number(Math.abs(Math.round(targetTop - currentY))) / 10000).toFixed(5) * 100000) } else { FLOW_LINE_REF.DOWN_LEFT_RADIO_REF.current.checked = true FLOW_LINE_REF.FILLED_INPUT_REF.current.value = Math.floor((Number(Math.abs(Math.round(targetTop - currentY))) / 10000).toFixed(5) * 100000) } } else { if (targetLeft < currentX) { FLOW_LINE_REF.UP_RIGHT_RADIO_REF.current.checked = true FLOW_LINE_REF.FILLED_INPUT_REF.current.value = Math.floor((Number(Math.abs(Math.round(currentX - targetLeft))) / 10000).toFixed(5) * 100000) } else { FLOW_LINE_REF.DOWN_LEFT_RADIO_REF.current.checked = true FLOW_LINE_REF.FILLED_INPUT_REF.current.value = Math.floor((Number(Math.abs(Math.round(currentX - targetLeft))) / 10000).toFixed(5) * 100000) } } canvas?.renderAll() } const updownMoveEvent = (e) => { const target = canvas.getActiveObject() if (!target) { return } const { top: targetTop, left: targetLeft } = target const currentX = canvas.getPointer(e.e).x const currentY = Math.floor(canvas.getPointer(e.e).y) if (Math.sign(target.x1 - target.x2) !== 0) { if (targetTop > currentY) { UP_DOWN_REF.DOWN_RADIO_REF.current.checked = true UP_DOWN_REF.FILLED_INPUT_REF.current.value = Math.floor((Number(Math.abs(targetTop - currentY)) / 10000).toFixed(5) * 100000) } else { UP_DOWN_REF.UP_RADIO_REF.current.checked = true UP_DOWN_REF.FILLED_INPUT_REF.current.value = Math.floor((Number(Math.abs(targetTop - currentY)) / 10000).toFixed(5) * 100000) } } else { if (targetLeft > currentX) { UP_DOWN_REF.DOWN_RADIO_REF.current.checked = true UP_DOWN_REF.FILLED_INPUT_REF.current.value = Math.floor((Number(Math.abs(targetLeft - currentX)) / 10000).toFixed(5) * 100000) } else { UP_DOWN_REF.UP_RADIO_REF.current.checked = true UP_DOWN_REF.FILLED_INPUT_REF.current.value = Math.floor((Number(Math.abs(targetLeft - currentX)) / 10000).toFixed(5) * 100000) } } canvas?.renderAll() } const handleSave = () => { if (type === TYPE.FLOW_LINE) { saveFlowLine() } else { saveUpDownLine() } } return { TYPE, closePopup, buttonType, type, setType, FLOW_LINE_REF, UP_DOWN_REF, handleSave, } }