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) 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) bottomModuleLine(setupSurface) topModuleLine(setupSurface) leftModuleLine(setupSurface) // rightModuleLine(setupSurface) //지붕면 선택 금지 roof.set({ selectable: false, }) //모듈설치면 클릭이벤트 addTargetMouseEventListener('mousedown', setupSurface, function () { toggleSelection(setupSurface) }) }) } //설치 범위 지정 클릭 이벤트 const toggleSelection = (setupSurface) => { console.log('setupSurface', 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 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') 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]) } if (i === 0) { difference = turf.difference(turf.featureCollection([turfModuleSetupSurface, convertBatchObject])) //한 면에 도머가 1개일때 } else { if (difference) { difference = turf.difference(turf.featureCollection([difference, convertBatchObject])) //한면에 도머가 여러개일때 계속 제외시킴 } } } } const bbox = turf.bbox(difference) let width = maxLengthLine.flowDirection === 'right' || maxLengthLine.flowDirection === 'left' ? 172.2 : 113.4 let height = maxLengthLine.flowDirection === 'right' || maxLengthLine.flowDirection === 'left' ? 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 cols = Math.floor((bbox[2] - bbox[0]) / width) let rows = Math.floor((bbox[3] - bbox[1]) / height) // cols = cols * 2 for (let col = 0; col <= cols; col++) { for (let row = 0; row <= rows; row++) { let x = 0, y = 0, square = [], margin = 0 if (moduleSetupSurface.flowDirection !== undefined) { //배치면 처림 방향이 정해져있는 경우 if (moduleSetupSurface.flowDirection === 'south' || moduleSetupSurface.flowDirection === 'north') { //남,북 margin = (bbox[2] - bbox[0] - cols * width) / 2 //박스 끝에서 박스 시작값을 빼고 width와 계산된 cols를 곱한값을 뺀뒤 나누기 2 하면 가운데 배치됨 if (moduleSetupSurface.flowDirection === 'south') { //남쪽 x = col === 0 ? moduleSetupSurface.left + margin : bbox[0] + col * width + margin //상하 위치 기준이면 좌우 가운데 정렬한다 y = bbox[3] - row * height } else { //북쪽 x = col === 0 ? moduleSetupSurface.left + margin : bbox[0] + col * width + margin y = bbox[1] + row * height } } else if (moduleSetupSurface.flowDirection === 'east' || moduleSetupSurface.flowDirection === 'west') { //동쪽 margin = (bbox[3] - bbox[1] - rows * height) / 2 if (moduleSetupSurface.flowDirection === 'east') { x = bbox[2] - col * width y = rows === 0 ? moduleSetupSurface.top + margin : bbox[1] + row * height + margin //좌우 위치 기준이면 상하 가운데 정렬한다 } else { x = bbox[0] + col * width y = rows === 0 ? moduleSetupSurface.top + margin : bbox[1] + row * height + margin } } } else { //방향이 없는 경우 ex) 템플릿 x = bbox[0] + col * width y = bbox[1] + row * height } if (isChidori === 'true') { if (row % 2 !== 0) { square = [ [x, y], [x + width, y], [x + width, y + height], [x, y + height], [x, y], ] } else { square = [ [x - width / 2, y], [x - width / 2 + width, y], [x - width / 2 + width, y + height], [x - width / 2, y + height], [x - width / 2, y], ] } } else { square = [ [x, y], [x + width, y], [x + width, y + height], [x, y + height], [x, y], ] } // square = [ // [x - width / 2, y], // [x - width / 2 + width, y], // [x - width / 2 + width, y + height], // [x - width / 2, y + height], // [x - width / 2, y], // ] 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', selectable: true, // 선택 가능하게 설정 lockMovementX: false, // X 축 이동 잠금 lockMovementY: false, // Y 축 이동 잠금 lockRotation: false, // 회전 잠금 lockScalingX: false, // X 축 크기 조정 잠금 lockScalingY: false, // Y 축 크기 조정 잠금 opacity: 0.8, parentId: moduleSetupSurface.parentId, lineCol: col, lineRow: row, 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, lineCol: col, lineRow: row, name: 'module', }) // canvas?.add(tempModule) moduleSetupArray.push(tempModule) } } } } 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 bottomModuleLine = (nowSurface) => { let selectedLine = null const sortedLines = sortLinesByTopLeft(nowSurface.lines) const moduleWidthLength = 173.3 + 5 //임시 약간 여유를 줌 // if (nowSurface.flowDirection === 'east') { // const leftFlow = nowSurface.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 }, // 초기값: 무한대와 유효하지 않은 인덱스 // ) // selectedLine = leftFlow // } else if (nowSurface.flowDirection === 'west') { // const rightFlow = nowSurface.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 }, // 초기값: 무한대와 유효하지 않은 인덱스 // ) // selectedLine = rightFlow // } else if (nowSurface.flowDirection === 'north') { // const topFlow = nowSurface.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 }, // 초기값: 무한대와 유효하지 않은 인덱스 // ) // selectedLine = topFlow // } else { const bottomFlow = nowSurface.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 }, // 초기값: 무한대와 유효하지 않은 인덱스 ) selectedLine = bottomFlow // } let prevLines = nowSurface.lines[(selectedLine.index - 1 + nowSurface.lines.length) % nowSurface.lines.length] let nextLines = nowSurface.lines[selectedLine.index] const overlapCoords = { x: nextLines.x1, y: nextLines.y1 } //겹치는 꼭지점 const m1 = (prevLines.y2 - prevLines.y1) / (prevLines.x2 - prevLines.x1) const m2 = (nextLines.y2 - nextLines.y1) / (nextLines.x2 - nextLines.x1) const c1 = prevLines.y1 - m1 * prevLines.x1 const c2 = nextLines.y1 - m2 * nextLines.x1 // Step 2: Calculate intersection point let xIntersectPrev = 0 let yIntersectPrev = 0 let xIntersectNext = 0 let yIntersectNext = 0 let endPoint = prevLines.y1 > nextLines.y2 ? prevLines.y1 : nextLines.y2 let biggerEndPoint = prevLines.y1 > nextLines.y2 ? 'left' : 'right' //bottom일 경우 xIntersectPrev = (endPoint - c1) / m1 yIntersectPrev = m1 * xIntersectPrev + c1 xIntersectNext = (endPoint - c2) / m2 yIntersectNext = m2 * xIntersectNext + c2 let lineCoords let polygonCoords let ratio = 1 if (biggerEndPoint === 'left') { //왼쪽이 더 밑이면 우측 라인에 절편으로 계산 lineCoords = [prevLines.x1, yIntersectNext, xIntersectNext, yIntersectNext] polygonCoords = [ { x: prevLines.x1, y: yIntersectNext }, { x: xIntersectNext, y: yIntersectNext }, { x: overlapCoords.x, y: overlapCoords.y }, ] ratio = moduleWidthLength / Math.abs(prevLines.x1 - xIntersectNext) } else { lineCoords = [xIntersectPrev, yIntersectPrev, nextLines.x2, yIntersectPrev] polygonCoords = [ { x: xIntersectPrev, y: yIntersectPrev }, { x: nextLines.x2, y: yIntersectPrev }, { x: overlapCoords.x, y: overlapCoords.y }, ] ratio = moduleWidthLength / Math.abs(nextLines.x2 - xIntersectPrev) } const tempTriangle = new QPolygon(polygonCoords, { fill: 'transparent', stroke: 'green', strokeWidth: 2, originY: 'bottom', strokeDashArray: [5, 5], // fontSize: 15, }) // canvas.add(tempTriangle) let cloneCoords = [] tempTriangle.clone((clone) => { clone.scale(ratio) cloneCoords = clone.getCurrentPoints() }) //아래쪽에선 잴 작은 const vertexPoints = cloneCoords.reduce((acc, point, index) => (acc['y'] > point['y'] ? acc : point)) const differenceDistance = overlapCoords.x - vertexPoints.x const newTriangleCoords = cloneCoords.map((point) => { return { x: point.x + differenceDistance, y: point.y } }) const deleteBottomPoint = newTriangleCoords.reduce((acc, point) => (acc['y'] > point['y'] ? acc : point)) const deleteIndex = newTriangleCoords.indexOf(deleteBottomPoint) if (deleteIndex !== -1) newTriangleCoords.splice(deleteIndex, 1) const newLine = new QLine([newTriangleCoords[0].x, newTriangleCoords[0].y, newTriangleCoords[1].x, newTriangleCoords[1].y], { fill: 'transparent', stroke: 'red', strokeWidth: 2, selectable: true, fontSize: 14, }) canvas.add(newLine) return newLine } const topModuleLine = (nowSurface) => { let selectedLine = null const moduleWidthLength = 173.3 + 5 //임시 약간 여유를 줌 const topFlow = nowSurface.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 }, // 초기값: 무한대와 유효하지 않은 인덱스 ) selectedLine = topFlow let prevLines = nowSurface.lines[(selectedLine.index - 1 + nowSurface.lines.length) % nowSurface.lines.length] let nextLines = nowSurface.lines[selectedLine.index] const overlapCoords = { x: nextLines.x1, y: nextLines.y1 } //겹치는 꼭지점 const m1 = (prevLines.y2 - prevLines.y1) / (prevLines.x2 - prevLines.x1) const m2 = (nextLines.y2 - nextLines.y1) / (nextLines.x2 - nextLines.x1) const c1 = prevLines.y1 - m1 * prevLines.x1 const c2 = nextLines.y1 - m2 * nextLines.x1 // Step 2: Calculate intersection point let xIntersectPrev = 0 let yIntersectPrev = 0 let xIntersectNext = 0 let yIntersectNext = 0 let endPoint = prevLines.y1 > nextLines.y2 ? nextLines.y2 : prevLines.y1 let biggerEndPoint = prevLines.y1 < nextLines.y2 ? 'left' : 'right' //bottom일 경우 xIntersectPrev = (endPoint - c1) / m1 yIntersectPrev = m1 * xIntersectPrev + c1 xIntersectNext = (endPoint - c2) / m2 yIntersectNext = m2 * xIntersectNext + c2 let lineCoords let polygonCoords let ratio = 1 if (biggerEndPoint === 'left') { //왼쪽이 더 밑이면 우측 라인에 절편으로 계산 lineCoords = [prevLines.x1, yIntersectNext, xIntersectNext, yIntersectNext] polygonCoords = [ { x: prevLines.x1, y: yIntersectNext }, { x: xIntersectNext, y: yIntersectNext }, { x: overlapCoords.x, y: overlapCoords.y }, ] ratio = moduleWidthLength / Math.abs(prevLines.x1 - xIntersectNext) } else { lineCoords = [xIntersectPrev, yIntersectPrev, nextLines.x2, yIntersectPrev] polygonCoords = [ { x: xIntersectPrev, y: yIntersectPrev }, { x: nextLines.x2, y: yIntersectPrev }, { x: overlapCoords.x, y: overlapCoords.y }, ] ratio = moduleWidthLength / Math.abs(nextLines.x2 - xIntersectPrev) } const tempTriangle = new QPolygon(polygonCoords, { fill: 'transparent', stroke: 'green', strokeWidth: 2, originY: 'top', strokeDashArray: [5, 5], // fontSize: 15, }) // canvas.add(tempTriangle) let cloneCoords = [] tempTriangle.clone((clone) => { clone.scale(ratio) cloneCoords = clone.getCurrentPoints() }) //아래쪽에선 잴 작은 const vertexPoints = cloneCoords.reduce((acc, point, index) => (acc['y'] < point['y'] ? acc : point)) const differenceDistance = overlapCoords.x - vertexPoints.x const newTriangleCoords = cloneCoords.map((point) => { return { x: point.x + differenceDistance, y: point.y } }) // const newTriangle1 = new QPolygon(newTriangleCoords, { // fill: 'transparent', // stroke: 'red', // strokeWidth: 1, // selectable: true, // fontSize: 14, // }) // canvas.add(newTriangle1) const deleteBottomPoint = newTriangleCoords.reduce((acc, point) => (acc['y'] < point['y'] ? acc : point)) const deleteIndex = newTriangleCoords.indexOf(deleteBottomPoint) if (deleteIndex !== -1) newTriangleCoords.splice(deleteIndex, 1) const newLine = new QLine([newTriangleCoords[0].x, newTriangleCoords[0].y, newTriangleCoords[1].x, newTriangleCoords[1].y], { fill: 'transparent', stroke: 'red', strokeWidth: 2, selectable: true, fontSize: 14, }) canvas.add(newLine) return newLine } const leftModuleLine = (nowSurface) => { let selectedLine = null sortLinesByTopLeft(nowSurface) console.log('nowSurface', nowSurface) const moduleWidthLength = 173.3 + 5 //임시 약간 여유를 줌 const leftFlow = nowSurface.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 }, // 초기값: 무한대와 유효하지 않은 인덱스 ) selectedLine = leftFlow let prevLines = nowSurface.lines[(selectedLine.index - 1 + nowSurface.lines.length) % nowSurface.lines.length] let nextLines = nowSurface.lines[selectedLine.index] const overlapCoords = { x: nextLines.x1, y: nextLines.y1 } //겹치는 꼭지점 const m1 = (prevLines.y2 - prevLines.y1) / (prevLines.x2 - prevLines.x1) const m2 = (nextLines.y2 - nextLines.y1) / (nextLines.x2 - nextLines.x1) const c1 = prevLines.y1 - m1 * prevLines.x1 const c2 = nextLines.y1 - m2 * nextLines.x1 // Step 2: Calculate intersection point let xIntersectPrev = 0 let yIntersectPrev = 0 let xIntersectNext = 0 let yIntersectNext = 0 let biggerEndPoint = prevLines.x1 > nextLines.x2 ? 'top' : 'bottom' console.log('prevLines.x1', prevLines.x1) console.log('nextLines.x2', nextLines.x2) console.log('biggerEndPoint', biggerEndPoint) //bottom일 경우 xIntersectPrev = prevLines.x1 yIntersectPrev = m1 * xIntersectPrev + c1 xIntersectNext = prevLines.x1 yIntersectNext = m2 * xIntersectNext + c2 let lineCoords let polygonCoords let ratio = 1 if (biggerEndPoint === 'top') { //윗쪽이이 더 밑이면 아래 라인에 절편으로 계산 lineCoords = [prevLines.x1, yIntersectNext, xIntersectNext, yIntersectNext] polygonCoords = [ { x: prevLines.x1, y: yIntersectNext }, { x: xIntersectNext, y: yIntersectNext }, { x: overlapCoords.x, y: overlapCoords.y }, ] ratio = moduleWidthLength / Math.abs(prevLines.x1 - xIntersectNext) } else { lineCoords = [xIntersectPrev, prevLines.y1, xIntersectPrev, yIntersectPrev] polygonCoords = [ { x: xIntersectNext, y: prevLines.y1 }, { x: xIntersectNext, y: yIntersectNext }, { x: overlapCoords.x, y: overlapCoords.y }, ] ratio = moduleWidthLength / Math.abs(prevLines.y1 - yIntersectNext) } const tempTriangle = new QPolygon(polygonCoords, { fill: 'transparent', stroke: 'green', strokeWidth: 2, originX: 'left', strokeDashArray: [5, 5], // fontSize: 15, selectable: true, }) // canvas.add(tempTriangle) let cloneCoords = [] tempTriangle.clone((clone) => { clone.scale(ratio) cloneCoords = clone.getCurrentPoints() // canvas.add(clone) }) canvas.remove(tempTriangle) //left에선 가장 왼쪽 const vertexPoints = cloneCoords.reduce((acc, point, index) => (acc['x'] < point['x'] ? acc : point)) const differenceDistance = overlapCoords.y - vertexPoints.y const newTriangleCoords = cloneCoords.map((point) => { return { x: point.x, y: point.y + differenceDistance } }) // const newTriangle1 = new QPolygon(newTriangleCoords, { // fill: 'transparent', // stroke: 'red', // strokeWidth: 1, // selectable: true, // fontSize: 14, // }) // canvas.add(newTriangle1) const deleteLeftPoint = newTriangleCoords.reduce((acc, point) => (acc['x'] < point['x'] ? acc : point)) const deleteIndex = newTriangleCoords.indexOf(deleteLeftPoint) if (deleteIndex !== -1) newTriangleCoords.splice(deleteIndex, 1) const newLine = new QLine([newTriangleCoords[0].x, newTriangleCoords[0].y, newTriangleCoords[1].x, newTriangleCoords[1].y], { fill: 'transparent', stroke: 'red', strokeWidth: 2, viewLengthText: false, // selectable: true, fontSize: 14, }) canvas.add(newLine) return newLine } function sortLinesByTopLeft(surface) { // 좌측 상단 기준으로 정렬 const sortedLines = surface.lines.sort((a, b) => { // x1, y1 값을 기준으로 정렬 if (a.x1 !== b.x1) { return a.x1 - b.x1 // x1 기준 정렬 } else { return a.y1 - b.y1 // x1이 같으면 y1 기준 정렬 } }) // 정렬된 결과를 기반으로 좌표 재정렬 sortedLines.forEach((line) => { // 좌측 상단이 (0,0) 기준이 되도록 좌표 이동 const minX = Math.min(line.x1, line.x2) const minY = Math.min(line.y1, line.y2) line.set({ x1: line.x1 - minX, y1: line.y1 - minY, x2: line.x2 - minX, y2: line.y2 - minY, }) }) surface.set({ sortedLines: sortedLines, }) return sortedLines } return { makeModuleInstArea, manualModuleSetup, autoModuleSetup, } }