import { useContext, useEffect, useState } from 'react' import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil' import { canvasState } from '@/store/canvasAtom' import { rectToPolygon, setSurfaceShapePattern } from '@/util/canvas-util' import { roofDisplaySelector } from '@/store/settingAtom' import offsetPolygon from '@/util/qpolygon-utils' import { QPolygon } from '@/components/fabric/QPolygon' import { QLine } from '@/components/fabric/QLine' import { moduleSetupSurfaceState, moduleIsSetupState } from '@/store/canvasAtom' import { useEvent } from '@/hooks/useEvent' import { POLYGON_TYPE, BATCH_TYPE } from '@/common/common' import * as turf from '@turf/turf' import { EventContext } from '@/app/floor-plan/EventProvider' export function useModuleBasicSetting() { const canvas = useRecoilValue(canvasState) const roofDisplay = useRecoilValue(roofDisplaySelector) const [moduleSetupSurface, setModuleSetupSurface] = useRecoilState(moduleSetupSurfaceState) const [moduleIsSetup, setModuleIsSetup] = useRecoilState(moduleIsSetupState) const { addTargetMouseEventListener, addCanvasMouseEventListener, initEvent } = useEvent() // const { addTargetMouseEventListener, addCanvasMouseEventListener, initEvent } = useContext(EventContext) const [flowModuleLine, setFlowModuleLine] = useState({}) let selectedModuleInstSurfaceArray = [] const makeModuleInstArea = () => { //지붕 객체 반환 const roofs = canvas.getObjects().filter((obj) => obj.name === 'roof') if (!roofs) { return } roofs.forEach((roof) => { setSurfaceShapePattern(roof, roofDisplay.column, true) //패턴 변경 const offsetPoints = offsetPolygon(roof.points, -20) //안쪽 offset //모듈설치영역?? 생성 let setupSurface = new QPolygon(offsetPoints, { stroke: 'red', fill: 'transparent', strokeDashArray: [10, 4], strokeWidth: 1, lockMovementX: true, lockMovementY: true, lockRotation: true, lockScalingX: true, lockScalingY: true, selectable: true, parentId: roof.id, //가대 폴리곤의 임시 인덱스를 넣어줌 name: POLYGON_TYPE.MODULE_SETUP_SURFACE, flowDirection: roof.direction, flipX: roof.flipX, flipY: roof.flipY, }) setupSurface.setViewLengthText(false) canvas.add(setupSurface) if (setupSurface.flowDirection === 'south' || setupSurface.flowDirection === 'north') { setFlowModuleLine(bottomTopFlowLine(setupSurface)) } else { setFlowModuleLine(leftRightFlowLine(setupSurface)) } //지붕면 선택 금지 roof.set({ selectable: false, }) //모듈설치면 클릭이벤트 addTargetMouseEventListener('mousedown', setupSurface, function () { toggleSelection(setupSurface) }) }) } //설치 범위 지정 클릭 이벤트 const toggleSelection = (setupSurface) => { const isExist = selectedModuleInstSurfaceArray.some((obj) => obj.parentId === setupSurface.parentId) //최초 선택일때 if (!isExist) { //기본 선택이랑 스트로크 굵기가 같으면 선택 안됨으로 봄 setupSurface.set({ ...setupSurface, strokeWidth: 3, strokeDashArray: [0], fill: 'transparent', }) canvas.discardActiveObject() // 객체의 활성 상태 해제 //중복으로 들어가는걸 방지하기 위한 코드 canvas?.renderAll() selectedModuleInstSurfaceArray.push(setupSurface) } else { //선택후 재선택하면 선택안됨으로 변경 setupSurface.set({ ...setupSurface, fill: 'transparent', strokeDashArray: [10, 4], strokeWidth: 1, }) canvas.discardActiveObject() // 객체의 활성 상태 해제 //폴리곤에 커스텀 인덱스를 가지고 해당 배열 인덱스를 찾아 삭제함 const removeIndex = setupSurface.parentId const removeArrayIndex = selectedModuleInstSurfaceArray.findIndex((obj) => obj.parentId === removeIndex) selectedModuleInstSurfaceArray.splice(removeArrayIndex, 1) } canvas?.renderAll() setModuleSetupSurface([...selectedModuleInstSurfaceArray]) } /** * trestle에서 영역을 가져와 mouse:move 이벤트로 해당 영역에 진입했을때 booleanPointInPolygon 로 진입여부를 확인 * 확인 후 셀을 이동시킴 */ const manualModuleSetup = () => { const moduleSetupSurfaces = canvas?.getObjects().filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE) //모듈설치면를 가져옴 const batchObjects = canvas ?.getObjects() .filter( (obj) => obj.name === BATCH_TYPE.OPENING || obj.name === BATCH_TYPE.TRIANGLE_DORMER || obj.name === BATCH_TYPE.PENTAGON_DORMER || obj.name === BATCH_TYPE.SHADOW, ) //도머s 객체 if (moduleSetupSurfaces.length !== 0) { let tempModule let manualDrawModules = moduleIsSetup // 앞에서 자동으로 했을때 추가됨 let inside = false let turfPolygon let flowDirection let trestlePolygon addCanvasMouseEventListener('mouse:move', (e) => { //마우스 이벤트 삭제 후 재추가 const mousePoint = canvas.getPointer(e.e) for (let i = 0; i < moduleSetupSurfaces.length; i++) { turfPolygon = polygonToTurfPolygon(moduleSetupSurfaces[i]) trestlePolygon = moduleSetupSurfaces[i] flowDirection = moduleSetupSurfaces[i].flowDirection //도형의 방향 let width = flowDirection === 'south' || flowDirection === 'north' ? 172 : 113 let height = flowDirection === 'south' || flowDirection === '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 === 'tempModule')) //움직일때 일단 지워가면서 움직임 tempModule = 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: 'tempModule', parentId: moduleSetupSurfaces[i].parentId, }) canvas?.add(tempModule) //움직여가면서 추가됨 /** * 스냅기능 */ let snapDistance = 10 let cellSnapDistance = 20 const trestleLeft = moduleSetupSurfaces[i].left const trestleTop = moduleSetupSurfaces[i].top const trestleRight = trestleLeft + moduleSetupSurfaces[i].width * moduleSetupSurfaces[i].scaleX const trestleBottom = trestleTop + moduleSetupSurfaces[i].height * moduleSetupSurfaces[i].scaleY const bigCenterY = (trestleTop + trestleTop + moduleSetupSurfaces[i].height) / 2 // 작은 폴리곤의 경계 좌표 계산 const smallLeft = tempModule.left const smallTop = tempModule.top const smallRight = smallLeft + tempModule.width * tempModule.scaleX const smallBottom = smallTop + tempModule.height * tempModule.scaleY const smallCenterX = smallLeft + (tempModule.width * tempModule.scaleX) / 2 const smallCenterY = smallTop + (tempModule.height * tempModule.scaleX) / 2 /** * 미리 깔아놓은 셀이 있을때 셀에 흡착됨 */ if (manualDrawModules) { manualDrawModules.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) { tempModule.left = holdCellLeft - width - 0.5 } //설치된 셀에 우측에 스냅 if (Math.abs(smallLeft - holdCellRight) < snapDistance) { tempModule.left = holdCellRight + 0.5 } //설치된 셀에 위쪽에 스냅 if (Math.abs(smallBottom - holdCellTop) < snapDistance) { tempModule.top = holdCellTop - height - 0.5 } //설치된 셀에 밑쪽에 스냅 if (Math.abs(smallTop - holdCellBottom) < snapDistance) { tempModule.top = holdCellBottom + 0.5 } //가운데 -> 가운데 if (Math.abs(smallCenterX - holdCellCenterX) < cellSnapDistance) { tempModule.left = holdCellCenterX - width / 2 } //왼쪽 -> 가운데 if (Math.abs(smallLeft - holdCellCenterX) < cellSnapDistance) { tempModule.left = holdCellCenterX } // 오른쪽 -> 가운데 if (Math.abs(smallRight - holdCellCenterX) < cellSnapDistance) { tempModule.left = holdCellCenterX - width } //세로 가운데 -> 가운데 if (Math.abs(smallCenterY - holdCellCenterY) < cellSnapDistance) { tempModule.top = holdCellCenterY - height / 2 } //위쪽 -> 가운데 if (Math.abs(smallTop - holdCellCenterY) < cellSnapDistance) { tempModule.top = holdCellCenterY } //아랫쪽 -> 가운데 if (Math.abs(smallBottom - holdCellCenterY) < cellSnapDistance) { tempModule.top = holdCellCenterY - height } }) } // 위쪽 변에 스냅 if (Math.abs(smallTop - trestleTop) < snapDistance) { tempModule.top = trestleTop } // 아래쪽 변에 스냅 if (Math.abs(smallTop + tempModule.height * tempModule.scaleY - (trestleTop + moduleSetupSurfaces[i].height)) < snapDistance) { tempModule.top = trestleTop + moduleSetupSurfaces[i].height - tempModule.height * tempModule.scaleY } // 왼쪽변에 스냅 if (Math.abs(smallLeft - trestleLeft) < snapDistance) { tempModule.left = trestleLeft } //오른쪽 변에 스냅 if (Math.abs(smallRight - trestleRight) < snapDistance) { tempModule.left = trestleRight - tempModule.width * tempModule.scaleX } if (flowDirection === 'south' || flowDirection === 'north') { // 모듈왼쪽이 세로중앙선에 붙게 스냅 if (Math.abs(smallLeft - (trestleLeft + moduleSetupSurfaces[i].width / 2)) < snapDistance) { tempModule.left = trestleLeft + moduleSetupSurfaces[i].width / 2 } // 모듈이 가운데가 세로중앙선에 붙게 스냅 if (Math.abs(smallCenterX - (trestleLeft + moduleSetupSurfaces[i].width / 2)) < snapDistance) { tempModule.left = trestleLeft + moduleSetupSurfaces[i].width / 2 - (tempModule.width * tempModule.scaleX) / 2 } // 모듈오른쪽이 세로중앙선에 붙게 스냅 if (Math.abs(smallRight - (trestleLeft + moduleSetupSurfaces[i].width / 2)) < snapDistance) { tempModule.left = trestleLeft + moduleSetupSurfaces[i].width / 2 - tempModule.width * tempModule.scaleX } } else { // 모듈이 가로중앙선에 스냅 if (Math.abs(smallTop + tempModule.height / 2 - bigCenterY) < snapDistance) { tempModule.top = bigCenterY - tempModule.height / 2 } if (Math.abs(smallTop - (trestleTop + moduleSetupSurfaces[i].height / 2)) < snapDistance) { tempModule.top = trestleTop + moduleSetupSurfaces[i].height / 2 } // 모듈 밑면이 가로중앙선에 스냅 if (Math.abs(smallBottom - (trestleTop + moduleSetupSurfaces[i].height / 2)) < snapDistance) { tempModule.top = trestleTop + moduleSetupSurfaces[i].height / 2 - tempModule.height * tempModule.scaleY } } tempModule.setCoords() canvas?.renderAll() inside = true break } else { inside = false } } if (!inside) { canvas?.remove(...canvas?.getObjects().filter((obj) => obj.name === 'tempModule')) canvas?.renderAll() } }) addCanvasMouseEventListener('mouse:up', (e) => { let isIntersection = true if (!inside) return if (tempModule) { const rectPoints = [ { x: tempModule.left + 0.5, y: tempModule.top + 0.5 }, { x: tempModule.left + 0.5 + tempModule.width * tempModule.scaleX, y: tempModule.top + 0.5 }, { x: tempModule.left + tempModule.width * tempModule.scaleX + 0.5, y: tempModule.top + tempModule.height * tempModule.scaleY + 0.5, }, { x: tempModule.left + 0.5, y: tempModule.top + tempModule.height * tempModule.scaleY + 0.5 }, ] tempModule.set({ points: rectPoints }) const tempTurfModule = polygonToTurfPolygon(tempModule) //도머 객체를 가져옴 if (batchObjects) { batchObjects.forEach((object) => { let dormerTurfPolygon if (object.type === 'group') { //도머는 그룹형태임 dormerTurfPolygon = batchObjectGroupToTurfPolygon(object) } else { //개구, 그림자 dormerTurfPolygon = polygonToTurfPolygon(rectToPolygon(object)) } const intersection = turf.intersect(turf.featureCollection([dormerTurfPolygon, tempTurfModule])) //겹치는지 확인 //겹치면 안됨 if (intersection) { alert('도머위에 모듈을 올릴 수 없습니다.') isIntersection = false } }) } if (!isIntersection) return tempModule.setCoords() //좌표 재정렬 if (turf.booleanWithin(tempTurfModule, turfPolygon)) { //마우스 클릭시 set으로 해당 위치에 셀을 넣음 const isOverlap = manualDrawModules.some((module) => turf.booleanOverlap(tempTurfModule, polygonToTurfPolygon(module))) //겹치는지 확인 if (!isOverlap) { //안겹치면 넣는다 tempModule.setCoords() tempModule.set({ name: 'module', fill: '#BFFD9F' }) manualDrawModules.push(tempModule) //모듈배열에 추가 //해당 모듈에 프로퍼티로 넣는다 trestlePolygon.set({ modules: manualDrawModules, }) } else { alert('셀끼리 겹치면 안되죠?') } } else { alert('나갔죠?!!') } } }) } } //자동 모듈 설치(그리드 방식) const autoModuleSetup = (placementRef) => { const isChidori = placementRef.isChidori.current const setupLocation = placementRef.setupLocation.current const isMaxSetup = placementRef.isMaxSetup.current initEvent() const moduleSetupSurfaces = moduleSetupSurface //선택 설치면 const notSelectedTrestlePolygons = canvas ?.getObjects() .filter((obj) => obj.name === POLYGON_TYPE.MODULE_SETUP_SURFACE && !moduleSetupSurfaces.includes(obj)) //설치면이 아닌것 const batchObjects = canvas ?.getObjects() .filter( (obj) => obj.name === BATCH_TYPE.OPENING || obj.name === BATCH_TYPE.TRIANGLE_DORMER || obj.name === BATCH_TYPE.PENTAGON_DORMER || obj.name === BATCH_TYPE.SHADOW, ) //도머s 객체 if (moduleSetupSurfaces.length === 0) { alert('선택된 모듈 설치면이 없습니다.') return } if (moduleIsSetup.length > 0) { alert('기존 모듈은 제거됩니다.') moduleIsSetup.forEach((module) => { canvas?.remove(module) }) } notSelectedTrestlePolygons.forEach((obj) => { if (obj.modules) { obj.modules.forEach((module) => { canvas?.remove(module) }) obj.modules = [] } }) const moduleSetupArray = [] moduleSetupSurfaces.forEach((moduleSetupSurface, index) => { moduleSetupSurface.fire('mousedown') const surfaceMaxLines = findSetupSurfaceMaxLines(moduleSetupSurface) let maxLengthLine = moduleSetupSurface.lines.reduce((acc, cur) => { return acc.length > cur.length ? acc : cur }) const turfModuleSetupSurface = polygonToTurfPolygon(moduleSetupSurface) //폴리곤을 turf 객체로 변환 const containsBatchObjects = batchObjects.filter((batchObject) => { let convertBatchObject if (batchObject.type === 'group') { //도머는 그룹형태임 convertBatchObject = batchObjectGroupToTurfPolygon(batchObject) } else { //개구, 그림자 batchObject.set({ points: rectToPolygon(batchObject), }) canvas?.renderAll() // set된걸 바로 적용하기 위해 convertBatchObject = polygonToTurfPolygon(batchObject) //rect를 폴리곤으로 변환 -> turf 폴리곤으로 변환 } // 폴리곤 안에 도머 폴리곤이 포함되어있는지 확인해서 반환하는 로직 return turf.booleanContains(turfModuleSetupSurface, convertBatchObject) || turf.booleanWithin(convertBatchObject, turfModuleSetupSurface) }) let difference = turfModuleSetupSurface //기본 객체(면형상) if (containsBatchObjects.length > 0) { //turf로 도머를 제외시키는 로직 for (let i = 0; i < containsBatchObjects.length; i++) { let convertBatchObject if (containsBatchObjects[i].type === 'group') { convertBatchObject = batchObjectGroupToTurfPolygon(containsBatchObjects[i]) } else { convertBatchObject = polygonToTurfPolygon(containsBatchObjects[i]) } } } let width = maxLengthLine.flowDirection === 'east' || maxLengthLine.flowDirection === 'west' ? 172.2 : 113.4 let height = maxLengthLine.flowDirection === 'east' || maxLengthLine.flowDirection === 'west' ? 113.4 : 172.2 //배치면때는 방향쪽으로 패널이 넓게 누워져야함 if (moduleSetupSurface.flowDirection !== undefined) { width = moduleSetupSurface.flowDirection === 'south' || moduleSetupSurface.flowDirection === 'north' ? 172.2 : 113.4 height = moduleSetupSurface.flowDirection === 'south' || moduleSetupSurface.flowDirection === 'north' ? 113.4 : 172.2 } let square let startPoint, endPoint if (setupLocation === 'eaves') { if (moduleSetupSurface.flowDirection === 'south') { startPoint = flowModuleLine.find((obj) => obj.target === 'bottom') endPoint = flowModuleLine.find((obj) => obj.target === 'top') const totalHeight = endPoint.y1 - startPoint.y1 const diffHeight = Math.abs(totalHeight / height) let leftMargin = 0 let bottomMargin = 0 for (let i = 0; i < diffHeight; i++) { leftMargin = i === 0 ? 1 : 0 bottomMargin = i === 0 ? 0 : 1 square = [ [startPoint.x1 + leftMargin, startPoint.y1 - height - bottomMargin], [startPoint.x1 + leftMargin, startPoint.y1 - bottomMargin], [startPoint.x1 + leftMargin + width, startPoint.y1 - bottomMargin], [startPoint.x1 + leftMargin + width, startPoint.y1 - height - bottomMargin], [startPoint.x1 + leftMargin, startPoint.y1 - height - bottomMargin], ] const squarePolygon = turf.polygon([square]) //설치면 안에 있는지 확인 const disjointFromTrestle = turf.booleanContains(turfModuleSetupSurface, squarePolygon) || turf.booleanWithin(squarePolygon, turfModuleSetupSurface) if (disjointFromTrestle) { let turfCoordnates = squarePolygon.geometry.coordinates[0].slice(0, -1) const points = turfCoordnates.map((coord) => ({ x: coord[0], y: coord[1] })) if (containsBatchObjects.length > 0) { let convertBatchObject //도머가 있으면 적용되는 로직 const isDisjoint = containsBatchObjects.every((batchObject) => { if (batchObject.type === 'group') { convertBatchObject = batchObjectGroupToTurfPolygon(batchObject) } else { convertBatchObject = polygonToTurfPolygon(batchObject) } return turf.booleanDisjoint(squarePolygon, convertBatchObject) //도머가 여러개일수있으므로 겹치는게 있다면... }) if (isDisjoint) { const tempModule = new QPolygon(points, { fill: '#BFFD9F', stroke: 'black', strokeWidth: 0.1, selectable: true, // 선택 가능하게 설정 lockMovementX: false, // X 축 이동 잠금 lockMovementY: false, // Y 축 이동 잠금 lockRotation: false, // 회전 잠금 lockScalingX: false, // X 축 크기 조정 잠금 lockScalingY: false, // Y 축 크기 조정 잠금 opacity: 0.8, parentId: moduleSetupSurface.parentId, name: 'module', }) tempModule.setViewLengthText(false) canvas?.add(tempModule) moduleSetupArray.push(tempModule) } } else { //도머가 없을땐 그냥 그림 const tempModule = 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: moduleSetupSurface.parentId, name: 'module', }) canvas?.add(tempModule) moduleSetupArray.push(tempModule) } startPoint = { x1: points[0].x, y1: points[0].y, x2: points[3].x, y2: points[3].y } } } } } else if (setupLocation === 'ridge') { } else { } moduleSetupSurface.set({ modules: moduleSetupArray }) }) setModuleIsSetup(moduleSetupArray) console.log(calculateForApi(moduleSetupArray)) } const calculateForApi = (moduleSetupArray) => { const centerPoints = [] moduleSetupArray.forEach((module, index) => { module.tempIndex = index const { x, y } = module.getCenterPoint() const { width, height } = module centerPoints.push({ x, y, width, height, index }) const circle = new fabric.Circle({ radius: 5, fill: 'red', name: 'redCircle', left: x - 5, top: y - 5, index: index, selectable: false, }) canvas.add(circle) }) //완전 노출 하면 let exposedBottom = 0 // 반 노출 하면 let exposedHalfBottom = 0 // 완전 노출 상면 let exposedTop = 0 //반 노출 상면 let exposedHalfTop = 0 // 완전 접면 let touchDimension = 0 //반접면 let halfTouchDimension = 0 // 노출하면 체크 centerPoints.forEach((centerPoint, index) => { const { x, y, width, height } = centerPoint // centerPoints중에 현재 centerPoint와 x값이 같고, y값이 y-height값과 같은 centerPoint가 있는지 확인 const bottomCell = centerPoints.filter((centerPoint) => centerPoint.x === x && Math.abs(centerPoint.y - (y + height)) < 2) if (bottomCell.length === 1) { touchDimension++ return } const bottomLeftPoint = { x: x - width / 2, y: y + height } const bottomRightPoint = { x: x + width / 2, y: y + height } // 바로 아래에 셀이 없는 경우 물떼세 배치가 왼쪽 되어있는 셀을 찾는다. const leftBottomCnt = centerPoints.filter( (centerPoint) => Math.abs(centerPoint.x - bottomLeftPoint.x) < 2 && Math.abs(centerPoint.y - bottomLeftPoint.y) < 2, ).length const rightBottomCnt = centerPoints.filter( (centerPoint) => Math.abs(centerPoint.x - bottomRightPoint.x) < 2 && Math.abs(centerPoint.y - bottomRightPoint.y) < 2, ).length if (leftBottomCnt + rightBottomCnt === 2) { touchDimension++ return } if (leftBottomCnt + rightBottomCnt === 1) { halfTouchDimension++ exposedHalfBottom++ return } if (leftBottomCnt + rightBottomCnt === 0) { exposedBottom++ return } }) // 노출상면 체크 centerPoints.forEach((centerPoint, index) => { const { x, y, width, height } = centerPoint const topCell = centerPoints.filter((centerPoint) => centerPoint.x === x && Math.abs(centerPoint.y - (y - height)) < 2) if (topCell.length === 1) { return } const topLeftPoint = { x: x - width / 2, y: y - height } const topRightPoint = { x: x + width / 2, y: y - height } const leftTopCnt = centerPoints.filter( (centerPoint) => Math.abs(centerPoint.x - topLeftPoint.x) < 2 && Math.abs(centerPoint.y - topRightPoint.y) < 2, ).length const rightTopCnt = centerPoints.filter( (centerPoint) => Math.abs(centerPoint.x - topRightPoint.x) < 2 && Math.abs(centerPoint.y - topRightPoint.y) < 2, ).length if (leftTopCnt + rightTopCnt === 1) { exposedHalfTop++ return } if (leftTopCnt + rightTopCnt === 0) { exposedTop++ return } }) return { exposedBottom, exposedHalfBottom, exposedTop, exposedHalfTop, touchDimension, halfTouchDimension, } } const coordToTurfPolygon = (points) => { const coordinates = points.map((point) => [point.x, point.y]) coordinates.push(coordinates[0]) return turf.polygon([coordinates]) } const polygonToTurfPolygon = (object) => { let coordinates coordinates = object.points.map((point) => [point.x, point.y]) coordinates.push(coordinates[0]) return turf.polygon( [coordinates], {}, { parentId: object.parentId, }, ) } const batchObjectGroupToTurfPolygon = (group) => { const polygons = group.getObjects().filter((obj) => obj.type === 'QPolygon') let allPoints = [] polygons.forEach((obj) => allPoints.push(...obj.get('points'))) const points = turf.featureCollection(allPoints.map((point) => turf.point([point.x, point.y]))) const hull = turf.concave(points, { tolerance: 0.1 }) return hull } const bottomTopFlowLine = (surface) => { const flowArray = [] const bottomFlow = surface.lines.reduce( (acc, line, index) => { if (line.y1 > acc.y1 || (line.y1 === acc.y1 && line.x1 > acc.x1)) { return { x1: line.x1, y1: line.y1, index: index } } return acc }, { x1: 0, y1: 0, index: -1 }, // 초기값: 무한대와 유효하지 않은 인덱스 ) flowArray.push(bottomFlow) const topFlow = surface.lines.reduce( (acc, line, index) => { if (line.y1 < acc.y1 || (line.y1 === acc.y1 && line.x1 < acc.x1)) { return { x1: line.x1, y1: line.y1, index: index } } return acc }, { x1: Infinity, y1: Infinity, index: -1 }, // 초기값: 무한대와 유효하지 않은 인덱스 ) flowArray.push(topFlow) let idx = 0 let rtnObjArray = [] flowArray.forEach((center) => { const linesArray = new Array() surface.lines.filter((line) => { if ((center.x1 === line.x1 && center.y1 === line.y1) || (center.x1 === line.x2 && center.y1 === line.y2)) { linesArray.push(line) } }) let coords = [] if (center.index === 0) { coords = [ { x: linesArray[0].x2, y: linesArray[0].y2 }, { x: center.x1, y: center.y1 }, { x: linesArray[1].x1, y: linesArray[1].y1 }, ] } else { coords = [ { x: linesArray[0].x1, y: linesArray[0].y1 }, { x: center.x1, y: center.y1 }, { x: linesArray[1].x2, y: linesArray[1].y2 }, ] } const adjust1 = coords[0].x - coords[1].x const height1 = coords[1].y - coords[0].y const angle1 = Math.abs(Math.round(Math.atan(height1 / adjust1) * (180 / Math.PI) * 1000) / 1000) const adjust2 = coords[2].x - coords[1].x const height2 = coords[2].y - coords[1].y const angle2 = Math.abs(Math.round(Math.atan(height2 / adjust2) * (180 / Math.PI) * 1000) / 1000) const angle3 = 180 - (angle1 + angle2) const charlie = 173.3 + 3 // 평행선길이 약간 여유를 줌 const alpha = (charlie * Math.sin((angle1 * Math.PI) / 180)) / Math.sin((angle3 * Math.PI) / 180) const beta = Math.sqrt(alpha ** 2 + charlie ** 2 - 2 * alpha * charlie * Math.cos((angle2 * Math.PI) / 180)) const h = beta * Math.sin((angle1 * Math.PI) / 180) // 높이 const sign = Math.sign(coords[0].y - coords[1].y) // 진행방향 const top = coords[1].y + sign * h // 변경되는 높이 좌표 값 // const line3 = new QLine([coords[1].x, coords[1].y, coords[1].x, top], { // stroke: 'blue', // strokeWidth: 1, // selectable: true, // }) // // canvas?.add(line3) const pointX1 = coords[0].x + ((coords[0].y - top) / (coords[0].y - coords[1].y)) * (coords[1].x - coords[0].x) const pointY1 = top const pointX2 = coords[2].x + ((coords[2].y - top) / (coords[2].y - coords[1].y)) * (coords[1].x - coords[2].x) const pointY2 = top const finalLine = new QLine([pointX1, pointY1, pointX2, pointY2], { stroke: 'red', strokeWidth: 1, selectable: true, }) canvas?.add(finalLine) canvas?.renderAll() const rtnObj = { target: idx === 0 ? 'bottom' : 'top', x1: pointX1, y1: pointY1, x2: pointX2, y2: pointY2 } rtnObjArray.push(rtnObj) ++idx }) return rtnObjArray } const leftRightFlowLine = (surface) => { const flowArray = [] const leftFlow = surface.lines.reduce( (acc, line, index) => { if (line.x1 < acc.x1 || (line.x1 === acc.x1 && line.y1 < acc.y1)) { return { x1: line.x1, y1: line.y1, index: index } } return acc }, { x1: Infinity, y1: Infinity, index: -1 }, // 초기값: 무한대와 유효하지 않은 인덱스 ) flowArray.push(leftFlow) const rightFlow = surface.lines.reduce( (acc, line, index) => { if (line.x1 > acc.x1 || (line.x1 === acc.x1 && line.y1 > acc.y1)) { return { x1: line.x1, y1: line.y1, index: index } } return acc }, { x1: 0, y1: 0, index: -1 }, // 초기값: 무한대와 유효하지 않은 인덱스 ) flowArray.push(rightFlow) let idx = 0 let rtnObjArray = [] flowArray.forEach((center) => { const linesArray = surface.lines.filter((line) => { if ((center.x1 === line.x1 && center.y1 === line.y1) || (center.x1 === line.x2 && center.y1 === line.y2)) { return line } }) let coords = [] if (center.index === 0) { coords = [ { x: linesArray[1].x1, y: linesArray[1].y1 }, { x: center.x1, y: center.y1 }, { x: linesArray[0].x2, y: linesArray[0].y2 }, ] } else { coords = [ { x: linesArray[0].x1, y: linesArray[0].y1 }, { x: center.x1, y: center.y1 }, { x: linesArray[1].x2, y: linesArray[1].y2 }, ] } const adjust1 = coords[0].x - coords[1].x const height1 = coords[1].y - coords[0].y const angle1 = Math.abs(Math.round(Math.atan(adjust1 / height1) * (180 / Math.PI) * 1000) / 1000) const adjust2 = coords[2].x - coords[1].x const height2 = coords[2].y - coords[1].y const angle2 = Math.abs(Math.round(Math.atan(adjust2 / height2) * (180 / Math.PI) * 1000) / 1000) const angle3 = 180 - (angle1 + angle2) const charlie = 173.3 + 3 // 평행선길이 약간 여유를줌 const alpha = (charlie * Math.sin((angle1 * Math.PI) / 180)) / Math.sin((angle3 * Math.PI) / 180) const beta = Math.sqrt(alpha ** 2 + charlie ** 2 - 2 * alpha * charlie * Math.cos((angle2 * Math.PI) / 180)) const h = beta * Math.sin((angle1 * Math.PI) / 180) // 높이 const sign = Math.sign(coords[0].x - coords[1].x) // 진행방향 const top = coords[1].x + sign * h // 변경되는 높이 좌표 값 // const line3 = new QLine([coords[1].x, coords[1].y, top, coords[1].y], { // stroke: 'blue', // strokeWidth: 1, // selectable: true, // }) // canvas?.add(line3) const pointX1 = top const pointY1 = coords[0].y + ((coords[0].x - top) / (coords[0].x - coords[1].x)) * (coords[1].y - coords[0].y) const pointX2 = top const pointY2 = coords[2].y + ((coords[2].x - top) / (coords[2].x - coords[1].x)) * (coords[1].y - coords[2].y) const finalLine = new QLine([pointX1, pointY1, pointX2, pointY2], { stroke: 'red', strokeWidth: 1, selectable: true, }) canvas?.add(finalLine) canvas?.renderAll() const rtnObj = { target: idx === 0 ? 'left' : 'right', x1: pointX1, y1: pointY1, x2: pointX2, y2: pointY2 } rtnObjArray.push(rtnObj) ++idx }) } const findSetupSurfaceMaxLines = (surface) => { const leftFlow = surface.lines.reduce( (acc, line, index) => { if (line.x1 < acc.x1 || (line.x1 === acc.x1 && line.y1 < acc.y1)) { return { x1: line.x1, y1: line.y1 } } return acc }, { x1: Infinity, y1: Infinity, index: -1 }, // 초기값: 무한대와 유효하지 않은 인덱스 ) const rightFlow = surface.lines.reduce( (acc, line, index) => { if (line.x1 > acc.x1 || (line.x1 === acc.x1 && line.y1 > acc.y1)) { return { x1: line.x1, y1: line.y1 } } return acc }, { x1: 0, y1: 0, index: -1 }, // 초기값: 무한대와 유효하지 않은 인덱스 ) const topFlow = surface.lines.reduce( (acc, line, index) => { if (line.y1 < acc.y1 || (line.y1 === acc.y1 && line.x1 < acc.x1)) { return { x1: line.x1, y1: line.y1 } } return acc }, { x1: Infinity, y1: Infinity, index: -1 }, // 초기값: 무한대와 유효하지 않은 인덱스 ) const bottomFlow = surface.lines.reduce( (acc, line, index) => { if (line.y1 > acc.y1 || (line.y1 === acc.y1 && line.x1 > acc.x1)) { return { x1: line.x1, y1: line.y1 } } return acc }, { x1: 0, y1: 0, index: -1 }, // 초기값: 무한대와 유효하지 않은 인덱스 ) const obj = { left: leftFlow, right: rightFlow, top: topFlow, bottom: bottomFlow, } return obj } return { makeModuleInstArea, manualModuleSetup, autoModuleSetup, } }