dev #464
3
.gitignore
vendored
3
.gitignore
vendored
@ -42,4 +42,5 @@ next-env.d.ts
|
|||||||
yarn.lock
|
yarn.lock
|
||||||
package-lock.json
|
package-lock.json
|
||||||
pnpm-lock.yaml
|
pnpm-lock.yaml
|
||||||
certificates
|
certificates
|
||||||
|
.ai
|
||||||
@ -22,7 +22,7 @@
|
|||||||
"chart.js": "^4.4.6",
|
"chart.js": "^4.4.6",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"env-cmd": "^10.1.0",
|
"env-cmd": "^10.1.0",
|
||||||
"fabric": "^5.3.0",
|
"fabric": "^5.5.2",
|
||||||
"framer-motion": "^11.2.13",
|
"framer-motion": "^11.2.13",
|
||||||
"fs": "^0.0.1-security",
|
"fs": "^0.0.1-security",
|
||||||
"iron-session": "^8.0.2",
|
"iron-session": "^8.0.2",
|
||||||
|
|||||||
@ -336,8 +336,8 @@ export const QPolygon = fabric.util.createClass(fabric.Polygon, {
|
|||||||
if (types.every((type) => type === LINE_TYPE.WALLLINE.EAVES)) {
|
if (types.every((type) => type === LINE_TYPE.WALLLINE.EAVES)) {
|
||||||
// 용마루 -- straight-skeleton
|
// 용마루 -- straight-skeleton
|
||||||
console.log('용마루 지붕')
|
console.log('용마루 지붕')
|
||||||
drawRidgeRoof(this.id, this.canvas, textMode)
|
//drawRidgeRoof(this.id, this.canvas, textMode)
|
||||||
//drawSkeletonRidgeRoof(this.id, this.canvas, textMode);
|
drawSkeletonRidgeRoof(this.id, this.canvas, textMode);
|
||||||
} else if (isGableRoof(types)) {
|
} else if (isGableRoof(types)) {
|
||||||
// A형, B형 박공 지붕
|
// A형, B형 박공 지붕
|
||||||
console.log('패턴 지붕')
|
console.log('패턴 지붕')
|
||||||
|
|||||||
@ -32,6 +32,7 @@ import { useEvent } from '@/hooks/useEvent'
|
|||||||
import { compasDegAtom } from '@/store/orientationAtom'
|
import { compasDegAtom } from '@/store/orientationAtom'
|
||||||
import { hotkeyStore } from '@/store/hotkeyAtom'
|
import { hotkeyStore } from '@/store/hotkeyAtom'
|
||||||
import { usePopup } from '@/hooks/usePopup'
|
import { usePopup } from '@/hooks/usePopup'
|
||||||
|
import { outerLinePointsState } from '@/store/outerLineAtom'
|
||||||
|
|
||||||
export default function CanvasFrame() {
|
export default function CanvasFrame() {
|
||||||
const canvasRef = useRef(null)
|
const canvasRef = useRef(null)
|
||||||
@ -45,6 +46,7 @@ export default function CanvasFrame() {
|
|||||||
const totalDisplay = useRecoilValue(totalDisplaySelector) // 집계표 표시 여부
|
const totalDisplay = useRecoilValue(totalDisplaySelector) // 집계표 표시 여부
|
||||||
const { setIsGlobalLoading } = useContext(QcastContext)
|
const { setIsGlobalLoading } = useContext(QcastContext)
|
||||||
const resetModuleStatisticsState = useResetRecoilState(moduleStatisticsState)
|
const resetModuleStatisticsState = useResetRecoilState(moduleStatisticsState)
|
||||||
|
const resetOuterLinePoints = useResetRecoilState(outerLinePointsState)
|
||||||
const resetMakersState = useResetRecoilState(makersState)
|
const resetMakersState = useResetRecoilState(makersState)
|
||||||
const resetSelectedMakerState = useResetRecoilState(selectedMakerState)
|
const resetSelectedMakerState = useResetRecoilState(selectedMakerState)
|
||||||
const resetSeriesState = useResetRecoilState(seriesState)
|
const resetSeriesState = useResetRecoilState(seriesState)
|
||||||
@ -137,6 +139,7 @@ export default function CanvasFrame() {
|
|||||||
|
|
||||||
const resetRecoilData = () => {
|
const resetRecoilData = () => {
|
||||||
// resetModuleStatisticsState()
|
// resetModuleStatisticsState()
|
||||||
|
resetOuterLinePoints()
|
||||||
resetMakersState()
|
resetMakersState()
|
||||||
resetSelectedMakerState()
|
resetSelectedMakerState()
|
||||||
resetSeriesState()
|
resetSeriesState()
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect, useRef } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import { useMessage } from '@/hooks/useMessage'
|
import { useMessage } from '@/hooks/useMessage'
|
||||||
import WithDraggable from '@/components/common/draggable/WithDraggable'
|
import WithDraggable from '@/components/common/draggable/WithDraggable'
|
||||||
import { useRecoilValue } from 'recoil'
|
import { useRecoilValue } from 'recoil'
|
||||||
@ -15,8 +15,8 @@ export default function DormerOffset(props) {
|
|||||||
const { closePopup } = usePopup()
|
const { closePopup } = usePopup()
|
||||||
const [arrow1, setArrow1] = useState(null)
|
const [arrow1, setArrow1] = useState(null)
|
||||||
const [arrow2, setArrow2] = useState(null)
|
const [arrow2, setArrow2] = useState(null)
|
||||||
const arrow1LengthRef = useRef()
|
const arrow1LengthRef = useRef(0)
|
||||||
const arrow2LengthRef = useRef()
|
const arrow2LengthRef = useRef(0)
|
||||||
const [arrow1Length, setArrow1Length] = useState(0)
|
const [arrow1Length, setArrow1Length] = useState(0)
|
||||||
const [arrow2Length, setArrow2Length] = useState(0)
|
const [arrow2Length, setArrow2Length] = useState(0)
|
||||||
|
|
||||||
@ -59,12 +59,12 @@ export default function DormerOffset(props) {
|
|||||||
name=""
|
name=""
|
||||||
label=""
|
label=""
|
||||||
className="input-origin block"
|
className="input-origin block"
|
||||||
value={arrow1LengthRef.current.value}
|
value={arrow1LengthRef.current.value ?? 0}
|
||||||
ref={arrow1LengthRef}
|
ref={arrow1LengthRef}
|
||||||
onChange={(value) => setArrow1Length(value)}
|
onChange={(value) => setArrow1Length(value)}
|
||||||
options={{
|
options={{
|
||||||
allowNegative: false,
|
allowNegative: false,
|
||||||
allowDecimal: false
|
allowDecimal: false,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -264,7 +264,6 @@ export default function Simulator() {
|
|||||||
style={{ width: '30%' }}
|
style={{ width: '30%' }}
|
||||||
className="select-light"
|
className="select-light"
|
||||||
value={pwrGnrSimType}
|
value={pwrGnrSimType}
|
||||||
defaultValue={`D`}
|
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
handleChartChangeData(e.target.value)
|
handleChartChangeData(e.target.value)
|
||||||
setPwrGnrSimType(e.target.value)
|
setPwrGnrSimType(e.target.value)
|
||||||
@ -334,33 +333,31 @@ export default function Simulator() {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{moduleInfoList.length > 0 ? (
|
{moduleInfoList.length > 0 ? (
|
||||||
moduleInfoList.map((moduleInfo) => {
|
moduleInfoList.map((moduleInfo) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<tr key={moduleInfo.itemId}>
|
||||||
<tr key={moduleInfo.itemId}>
|
{/* 지붕면 */}
|
||||||
{/* 지붕면 */}
|
<td>{moduleInfo.roofSurface}</td>
|
||||||
<td>{moduleInfo.roofSurface}</td>
|
{/* 경사각 */}
|
||||||
{/* 경사각 */}
|
<td>
|
||||||
<td>
|
{convertNumberToPriceDecimal(moduleInfo.slopeAngle)}
|
||||||
{convertNumberToPriceDecimal(moduleInfo.slopeAngle)}
|
{moduleInfo.classType == 0 ? '寸' : 'º'}
|
||||||
{moduleInfo.classType == 0 ? '寸' : 'º'}
|
</td>
|
||||||
</td>
|
{/* 방위각(도) */}
|
||||||
{/* 방위각(도) */}
|
<td>{convertNumberToPriceDecimal(moduleInfo.azimuth)}</td>
|
||||||
<td>{convertNumberToPriceDecimal(moduleInfo.azimuth)}</td>
|
{/* 태양전지모듈 */}
|
||||||
{/* 태양전지모듈 */}
|
<td>
|
||||||
<td>
|
<div className="overflow-lab">{moduleInfo.itemNo}</div>
|
||||||
<div className="overflow-lab">{moduleInfo.itemNo}</div>
|
</td>
|
||||||
</td>
|
{/* 매수 */}
|
||||||
{/* 매수 */}
|
<td>{convertNumberToPriceDecimal(moduleInfo.amount)}</td>
|
||||||
<td>{convertNumberToPriceDecimal(moduleInfo.amount)}</td>
|
</tr>
|
||||||
</tr>
|
)
|
||||||
</>
|
})
|
||||||
)
|
) : (
|
||||||
})
|
<tr>
|
||||||
) : (
|
<td colSpan={5}>{getMessage('common.message.no.data')}</td>
|
||||||
<tr>
|
|
||||||
<td colSpan={5}>{getMessage('common.message.no.data')}</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
)}
|
)}
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -385,25 +382,23 @@ export default function Simulator() {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{pcsInfoList.length > 0 ? (
|
{pcsInfoList.length > 0 ? (
|
||||||
pcsInfoList.map((pcsInfo) => {
|
pcsInfoList.map((pcsInfo) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<tr key={pcsInfo.itemId}>
|
||||||
<tr key={pcsInfo.itemId}>
|
{/* 파워컨디셔너 */}
|
||||||
{/* 파워컨디셔너 */}
|
<td className="al-l">
|
||||||
<td className="al-l">
|
<div className="overflow-lab">{pcsInfo.itemNo}</div>
|
||||||
<div className="overflow-lab">{pcsInfo.itemNo}</div>
|
</td>
|
||||||
</td>
|
{/* 대 */}
|
||||||
{/* 대 */}
|
<td>{convertNumberToPriceDecimal(pcsInfo.amount)}</td>
|
||||||
<td>{convertNumberToPriceDecimal(pcsInfo.amount)}</td>
|
</tr>
|
||||||
</tr>
|
)
|
||||||
</>
|
})
|
||||||
)
|
) : (
|
||||||
})
|
<tr>
|
||||||
) : (
|
<td colSpan={2}>{getMessage('common.message.no.data')}</td>
|
||||||
<tr>
|
</tr>
|
||||||
<td colSpan={2}>{getMessage('common.message.no.data')}</td>
|
|
||||||
</tr>
|
|
||||||
)}
|
)}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@ -648,6 +648,7 @@ export function useCommonUtils() {
|
|||||||
lockMovementY: true,
|
lockMovementY: true,
|
||||||
name: obj.name,
|
name: obj.name,
|
||||||
editable: false,
|
editable: false,
|
||||||
|
selectable: true, // 복사된 객체 선택 가능하도록 설정
|
||||||
id: uuidv4(), //복사된 객체라 새로 따준다
|
id: uuidv4(), //복사된 객체라 새로 따준다
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -656,19 +657,25 @@ export function useCommonUtils() {
|
|||||||
|
|
||||||
//배치면일 경우
|
//배치면일 경우
|
||||||
if (obj.name === 'roof') {
|
if (obj.name === 'roof') {
|
||||||
clonedObj.setCoords()
|
clonedObj.canvas = canvas // canvas 참조 설정
|
||||||
clonedObj.fire('modified')
|
|
||||||
// clonedObj.fire('polygonMoved')
|
|
||||||
clonedObj.set({
|
clonedObj.set({
|
||||||
direction: obj.direction,
|
direction: obj.direction,
|
||||||
directionText: obj.directionText,
|
directionText: obj.directionText,
|
||||||
roofMaterial: obj.roofMaterial,
|
roofMaterial: obj.roofMaterial,
|
||||||
|
stroke: 'black', // 복사된 객체는 선택 해제 상태의 색상으로 설정
|
||||||
|
selectable: true, // 선택 가능하도록 설정
|
||||||
|
evented: true, // 마우스 이벤트를 받을 수 있도록 설정
|
||||||
|
isFixed: false, // containsPoint에서 특별 처리 방지
|
||||||
})
|
})
|
||||||
|
|
||||||
obj.lines.forEach((line, index) => {
|
obj.lines.forEach((line, index) => {
|
||||||
clonedObj.lines[index].set({ attributes: line.attributes })
|
clonedObj.lines[index].set({ attributes: line.attributes })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
clonedObj.fire('polygonMoved') // 내부 좌표 재계산 (points, pathOffset)
|
||||||
|
clonedObj.fire('modified')
|
||||||
|
clonedObj.setCoords() // 모든 속성 설정 후 좌표 업데이트
|
||||||
|
canvas.setActiveObject(clonedObj)
|
||||||
canvas.renderAll()
|
canvas.renderAll()
|
||||||
addLengthText(clonedObj) //수치 추가
|
addLengthText(clonedObj) //수치 추가
|
||||||
drawDirectionArrow(clonedObj) //방향 화살표 추가
|
drawDirectionArrow(clonedObj) //방향 화살표 추가
|
||||||
|
|||||||
@ -30,6 +30,8 @@ import { QcastContext } from '@/app/QcastProvider'
|
|||||||
import { usePlan } from '@/hooks/usePlan'
|
import { usePlan } from '@/hooks/usePlan'
|
||||||
import { roofsState } from '@/store/roofAtom'
|
import { roofsState } from '@/store/roofAtom'
|
||||||
import { useText } from '@/hooks/useText'
|
import { useText } from '@/hooks/useText'
|
||||||
|
import { processEaveHelpLines } from '@/util/skeleton-utils'
|
||||||
|
import { QLine } from '@/components/fabric/QLine'
|
||||||
|
|
||||||
export function useRoofAllocationSetting(id) {
|
export function useRoofAllocationSetting(id) {
|
||||||
const canvas = useRecoilValue(canvasState)
|
const canvas = useRecoilValue(canvasState)
|
||||||
@ -372,11 +374,18 @@ export function useRoofAllocationSetting(id) {
|
|||||||
setBasicSetting((prev) => {
|
setBasicSetting((prev) => {
|
||||||
return { ...prev, selectedRoofMaterial: newRoofList.find((roof) => roof.selected) }
|
return { ...prev, selectedRoofMaterial: newRoofList.find((roof) => roof.selected) }
|
||||||
})
|
})
|
||||||
|
const selectedRoofMaterial = newRoofList.find((roof) => roof.selected)
|
||||||
|
const roofs = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.ROOF && obj.roofMaterial?.index === selectedRoofMaterial.index)
|
||||||
|
|
||||||
|
roofs.forEach((roof) => {
|
||||||
|
setSurfaceShapePattern(roof, roofDisplay.column, false, { ...selectedRoofMaterial }, true)
|
||||||
|
drawDirectionArrow(roof)
|
||||||
|
})
|
||||||
|
|
||||||
setRoofList(newRoofList)
|
setRoofList(newRoofList)
|
||||||
setRoofMaterials(newRoofList)
|
setRoofMaterials(newRoofList)
|
||||||
setRoofsStore(newRoofList)
|
setRoofsStore(newRoofList)
|
||||||
const selectedRoofMaterial = newRoofList.find((roof) => roof.selected)
|
|
||||||
setSurfaceShapePattern(currentObject, roofDisplay.column, false, selectedRoofMaterial, true)
|
setSurfaceShapePattern(currentObject, roofDisplay.column, false, selectedRoofMaterial, true)
|
||||||
drawDirectionArrow(currentObject)
|
drawDirectionArrow(currentObject)
|
||||||
modifyModuleSelectionData()
|
modifyModuleSelectionData()
|
||||||
@ -449,6 +458,22 @@ export function useRoofAllocationSetting(id) {
|
|||||||
const wallLines = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.WALL)
|
const wallLines = canvas.getObjects().filter((obj) => obj.name === POLYGON_TYPE.WALL)
|
||||||
roofBases.forEach((roofBase) => {
|
roofBases.forEach((roofBase) => {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
|
const roofEaveHelpLines = canvas.getObjects().filter((obj) => obj.lineName === 'eaveHelpLine' && obj.roofId === roofBase.id)
|
||||||
|
if (roofEaveHelpLines.length > 0) {
|
||||||
|
if (roofBase.lines) {
|
||||||
|
// Filter out any eaveHelpLines that are already in lines to avoid duplicates
|
||||||
|
const existingEaveLineIds = new Set(roofBase.lines.map((line) => line.id))
|
||||||
|
const newEaveLines = roofEaveHelpLines.filter((line) => !existingEaveLineIds.has(line.id))
|
||||||
|
roofBase.lines = [...newEaveLines]
|
||||||
|
} else {
|
||||||
|
roofBase.lines = [...roofEaveHelpLines]
|
||||||
|
}
|
||||||
|
if (!roofBase.innerLines) {
|
||||||
|
roofBase.innerLines = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (roofBase.separatePolygon.length > 0) {
|
if (roofBase.separatePolygon.length > 0) {
|
||||||
splitPolygonWithSeparate(roofBase.separatePolygon)
|
splitPolygonWithSeparate(roofBase.separatePolygon)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -845,6 +845,8 @@ export const usePolygon = () => {
|
|||||||
polygonLines.forEach((line) => {
|
polygonLines.forEach((line) => {
|
||||||
line.need = true
|
line.need = true
|
||||||
})
|
})
|
||||||
|
// 순서에 의존하지 않도록 모든 조합을 먼저 확인한 후 처리
|
||||||
|
const innerLineMapping = new Map() // innerLine -> polygonLine 매핑 저장
|
||||||
|
|
||||||
// innerLines와 polygonLines의 겹침을 확인하고 type 변경
|
// innerLines와 polygonLines의 겹침을 확인하고 type 변경
|
||||||
innerLines.forEach((innerLine) => {
|
innerLines.forEach((innerLine) => {
|
||||||
@ -854,14 +856,28 @@ export const usePolygon = () => {
|
|||||||
if (innerLine.attributes && polygonLine.attributes.type) {
|
if (innerLine.attributes && polygonLine.attributes.type) {
|
||||||
// innerLine이 polygonLine보다 긴 경우 polygonLine.need를 false로 변경
|
// innerLine이 polygonLine보다 긴 경우 polygonLine.need를 false로 변경
|
||||||
if (polygonLine.length < innerLine.length) {
|
if (polygonLine.length < innerLine.length) {
|
||||||
polygonLine.need = false
|
if(polygonLine.lineName !== 'eaveHelpLine'){
|
||||||
|
polygonLine.need = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
innerLine.attributes.planeSize = innerLine.attributes.planeSize ?? polygonLine.attributes.planeSize
|
// innerLine.attributes.planeSize = innerLine.attributes.planeSize ?? polygonLine.attributes.planeSize
|
||||||
innerLine.attributes.actualSize = innerLine.attributes.actualSize ?? polygonLine.attributes.actualSize
|
// innerLine.attributes.actualSize = innerLine.attributes.actualSize ?? polygonLine.attributes.actualSize
|
||||||
innerLine.attributes.type = polygonLine.attributes.type
|
// innerLine.attributes.type = polygonLine.attributes.type
|
||||||
innerLine.direction = polygonLine.direction
|
// innerLine.direction = polygonLine.direction
|
||||||
innerLine.attributes.isStart = true
|
// innerLine.attributes.isStart = true
|
||||||
innerLine.parentLine = polygonLine
|
// innerLine.parentLine = polygonLine
|
||||||
|
|
||||||
|
|
||||||
|
// 매핑된 innerLine의 attributes를 변경 (교차점 계산 전에 적용)
|
||||||
|
innerLineMapping.forEach((polygonLine, innerLine) => {
|
||||||
|
innerLine.attributes.planeSize = innerLine.attributes.planeSize ?? polygonLine.attributes.planeSize
|
||||||
|
innerLine.attributes.actualSize = innerLine.attributes.actualSize ?? polygonLine.attributes.actualSize
|
||||||
|
innerLine.attributes.type = polygonLine.attributes.type
|
||||||
|
innerLine.direction = polygonLine.direction
|
||||||
|
innerLine.attributes.isStart = true
|
||||||
|
innerLine.parentLine = polygonLine
|
||||||
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -1371,7 +1387,7 @@ export const usePolygon = () => {
|
|||||||
let representLine
|
let representLine
|
||||||
|
|
||||||
// 지붕을 그리면서 기존 polygon의 line중 연결된 line을 찾는다.
|
// 지붕을 그리면서 기존 polygon의 line중 연결된 line을 찾는다.
|
||||||
;[...polygonLines, ...innerLines].forEach((line) => {
|
[...polygonLines, ...innerLines].forEach((line) => {
|
||||||
let startFlag = false
|
let startFlag = false
|
||||||
let endFlag = false
|
let endFlag = false
|
||||||
const startPoint = line.startPoint
|
const startPoint = line.startPoint
|
||||||
@ -1567,52 +1583,126 @@ export const usePolygon = () => {
|
|||||||
|
|
||||||
// ==== Dijkstra pathfinding ====
|
// ==== Dijkstra pathfinding ====
|
||||||
|
|
||||||
|
// function findShortestPath(start, end, graph, epsilon = 1) {
|
||||||
|
// const startKey = pointToKey(start, epsilon)
|
||||||
|
// const endKey = pointToKey(end, epsilon)
|
||||||
|
//
|
||||||
|
// const distances = {}
|
||||||
|
// const previous = {}
|
||||||
|
// const visited = new Set()
|
||||||
|
// const queue = [{ key: startKey, dist: 0 }]
|
||||||
|
//
|
||||||
|
// for (const key in graph) distances[key] = Infinity
|
||||||
|
// distances[startKey] = 0
|
||||||
|
//
|
||||||
|
// while (queue.length > 0) {
|
||||||
|
// queue.sort((a, b) => a.dist - b.dist)
|
||||||
|
// const { key } = queue.shift()
|
||||||
|
// if (visited.has(key)) continue
|
||||||
|
// visited.add(key)
|
||||||
|
//
|
||||||
|
// for (const neighbor of graph[key] || []) {
|
||||||
|
// const neighborKey = pointToKey(neighbor.point, epsilon)
|
||||||
|
// const alt = distances[key] + neighbor.distance
|
||||||
|
// if (alt < distances[neighborKey]) {
|
||||||
|
// distances[neighborKey] = alt
|
||||||
|
// previous[neighborKey] = key
|
||||||
|
// queue.push({ key: neighborKey, dist: alt })
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// const path = []
|
||||||
|
// let currentKey = endKey
|
||||||
|
//
|
||||||
|
// if (!previous[currentKey]) return null
|
||||||
|
//
|
||||||
|
// while (currentKey !== startKey) {
|
||||||
|
// const [x, y] = currentKey.split(',').map(Number)
|
||||||
|
// path.unshift({ x, y })
|
||||||
|
// currentKey = previous[currentKey]
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// const [sx, sy] = startKey.split(',').map(Number)
|
||||||
|
// path.unshift({ x: sx, y: sy })
|
||||||
|
//
|
||||||
|
// return path
|
||||||
|
// }
|
||||||
|
|
||||||
function findShortestPath(start, end, graph, epsilon = 1) {
|
function findShortestPath(start, end, graph, epsilon = 1) {
|
||||||
const startKey = pointToKey(start, epsilon)
|
const startKey = pointToKey(start, epsilon);
|
||||||
const endKey = pointToKey(end, epsilon)
|
const endKey = pointToKey(end, epsilon);
|
||||||
|
|
||||||
const distances = {}
|
// 거리와 이전 노드 추적
|
||||||
const previous = {}
|
const distances = { [startKey]: 0 };
|
||||||
const visited = new Set()
|
const previous = {};
|
||||||
const queue = [{ key: startKey, dist: 0 }]
|
const visited = new Set();
|
||||||
|
|
||||||
for (const key in graph) distances[key] = Infinity
|
// 우선순위 큐 (거리가 짧은 순으로 정렬)
|
||||||
distances[startKey] = 0
|
const queue = [{ key: startKey, dist: 0 }];
|
||||||
|
|
||||||
while (queue.length > 0) {
|
// 모든 노드 초기화
|
||||||
queue.sort((a, b) => a.dist - b.dist)
|
for (const key in graph) {
|
||||||
const { key } = queue.shift()
|
if (key !== startKey) {
|
||||||
if (visited.has(key)) continue
|
distances[key] = Infinity;
|
||||||
visited.add(key)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (const neighbor of graph[key] || []) {
|
// 우선순위 큐에서 다음 노드 선택
|
||||||
const neighborKey = pointToKey(neighbor.point, epsilon)
|
const getNextNode = () => {
|
||||||
const alt = distances[key] + neighbor.distance
|
if (queue.length === 0) return null;
|
||||||
if (alt < distances[neighborKey]) {
|
queue.sort((a, b) => a.dist - b.dist);
|
||||||
distances[neighborKey] = alt
|
return queue.shift();
|
||||||
previous[neighborKey] = key
|
};
|
||||||
queue.push({ key: neighborKey, dist: alt })
|
|
||||||
|
let current;
|
||||||
|
while ((current = getNextNode())) {
|
||||||
|
const currentKey = current.key;
|
||||||
|
|
||||||
|
// 목적지에 도달하면 종료
|
||||||
|
if (currentKey === endKey) break;
|
||||||
|
|
||||||
|
// 이미 방문한 노드는 건너뜀
|
||||||
|
if (visited.has(currentKey)) continue;
|
||||||
|
visited.add(currentKey);
|
||||||
|
|
||||||
|
// 인접 노드 탐색
|
||||||
|
for (const neighbor of graph[currentKey] || []) {
|
||||||
|
const neighborKey = pointToKey(neighbor.point, epsilon);
|
||||||
|
if (visited.has(neighborKey)) continue;
|
||||||
|
|
||||||
|
const alt = distances[currentKey] + neighbor.distance;
|
||||||
|
|
||||||
|
// 더 짧은 경로를 찾은 경우 업데이트
|
||||||
|
if (alt < (distances[neighborKey] || Infinity)) {
|
||||||
|
distances[neighborKey] = alt;
|
||||||
|
previous[neighborKey] = currentKey;
|
||||||
|
|
||||||
|
// 우선순위 큐에 추가
|
||||||
|
queue.push({ key: neighborKey, dist: alt });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const path = []
|
// 경로 재구성
|
||||||
let currentKey = endKey
|
const path = [];
|
||||||
|
let currentKey = endKey;
|
||||||
|
|
||||||
if (!previous[currentKey]) return null
|
// 시작점에 도달할 때까지 역추적
|
||||||
|
while (previous[currentKey] !== undefined) {
|
||||||
while (currentKey !== startKey) {
|
const [x, y] = currentKey.split(',').map(Number);
|
||||||
const [x, y] = currentKey.split(',').map(Number)
|
path.unshift({ x, y });
|
||||||
path.unshift({ x, y })
|
currentKey = previous[currentKey];
|
||||||
currentKey = previous[currentKey]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const [sx, sy] = startKey.split(',').map(Number)
|
// 시작점 추가
|
||||||
path.unshift({ x: sx, y: sy })
|
if (path.length > 0) {
|
||||||
|
const [sx, sy] = startKey.split(',').map(Number);
|
||||||
|
path.unshift({ x: sx, y: sy });
|
||||||
|
}
|
||||||
|
|
||||||
return path
|
return path.length > 0 ? path : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 최종 함수
|
// 최종 함수
|
||||||
function getPath(start, end, graph, epsilon = 1) {
|
function getPath(start, end, graph, epsilon = 1) {
|
||||||
// startPoint와 arrivalPoint가 될 수 있는 점은 line.attributes.type이 'default' 혹은 null이 아닌 line인 경우에만 가능
|
// startPoint와 arrivalPoint가 될 수 있는 점은 line.attributes.type이 'default' 혹은 null이 아닌 line인 경우에만 가능
|
||||||
|
|||||||
@ -29,22 +29,39 @@ fabric.Rect.prototype.getCurrentPoints = function () {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* fabric.Group에 getCurrentPoints 메서드를 추가 (도머 그룹용)
|
* fabric.Group에 getCurrentPoints 메서드를 추가 (도머 그룹용)
|
||||||
* 그룹의 groupPoints를 다시 계산하여 반환
|
* 그룹 내 객체들의 점들을 수집하여 현재 월드 좌표를 반환
|
||||||
*/
|
*/
|
||||||
fabric.Group.prototype.getCurrentPoints = function () {
|
fabric.Group.prototype.getCurrentPoints = function () {
|
||||||
// groupPoints를 다시 계산
|
// 그룹 내 객체들로부터 실시간으로 점들을 계산
|
||||||
|
if (this._objects && this._objects.length > 0) {
|
||||||
|
let allPoints = []
|
||||||
|
|
||||||
// 그룹에 groupPoints가 있으면 해당 점들을 사용 (도머의 경우)
|
// 그룹 내 모든 객체의 점들을 수집
|
||||||
if (this.groupPoints && Array.isArray(this.groupPoints)) {
|
this._objects.forEach(function (obj) {
|
||||||
const matrix = this.calcTransformMatrix()
|
if (obj.getCurrentPoints && typeof obj.getCurrentPoints === 'function') {
|
||||||
console.log('this.groupPoints', this.groupPoints)
|
const objPoints = obj.getCurrentPoints()
|
||||||
return this.groupPoints.map(function (p) {
|
allPoints = allPoints.concat(objPoints)
|
||||||
const point = new fabric.Point(p.x, p.y)
|
} else if (obj.points && Array.isArray(obj.points)) {
|
||||||
return fabric.util.transformPoint(point, matrix)
|
const pathOffset = obj.pathOffset || { x: 0, y: 0 }
|
||||||
|
const matrix = obj.calcTransformMatrix()
|
||||||
|
const transformedPoints = obj.points
|
||||||
|
.map(function (p) {
|
||||||
|
return new fabric.Point(p.x - pathOffset.x, p.y - pathOffset.y)
|
||||||
|
})
|
||||||
|
.map(function (p) {
|
||||||
|
return fabric.util.transformPoint(p, matrix)
|
||||||
|
})
|
||||||
|
allPoints = allPoints.concat(transformedPoints)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (allPoints.length > 0) {
|
||||||
|
// Convex Hull 알고리즘을 사용하여 외곽 점들만 반환
|
||||||
|
return this.getConvexHull(allPoints)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// groupPoints가 없으면 바운딩 박스를 사용
|
// 객체가 없으면 바운딩 박스를 사용
|
||||||
const bounds = this.getBoundingRect()
|
const bounds = this.getBoundingRect()
|
||||||
const points = [
|
const points = [
|
||||||
{ x: bounds.left, y: bounds.top },
|
{ x: bounds.left, y: bounds.top },
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user