({ className }: MapEditorProps)
| 33 | } |
| 34 | |
| 35 | export function MapEditor({ className }: MapEditorProps) { |
| 36 | const canvasRef = useRef<HTMLCanvasElement>(null); |
| 37 | const previewCanvasRef = useRef<HTMLCanvasElement>(null); |
| 38 | const toolPreviewCanvasRef = useRef<HTMLCanvasElement>(null); |
| 39 | const fileInputRef = useRef<HTMLInputElement>(null); |
| 40 | const yamlInputRef = useRef<HTMLInputElement>(null); |
| 41 | |
| 42 | const [isDrawing, setIsDrawing] = useState(false); |
| 43 | const [currentTool, setCurrentTool] = useState<DrawingTool>('brush'); |
| 44 | const [currentRegion, setCurrentRegion] = useState<MapRegion>('occupied'); |
| 45 | const [brushSize, setBrushSize] = useState(5); |
| 46 | const [showThreshold, setShowThreshold] = useState(false); |
| 47 | const [showInflation, setShowInflation] = useState(false); |
| 48 | const [zoom, setZoom] = useState(1); |
| 49 | const [pan, setPan] = useState({ x: 0, y: 0 }); |
| 50 | const [mousePos, setMousePos] = useState<{ x: number; y: number } | null>(null); |
| 51 | const [polygonPoints, setPolygonPoints] = useState<{ x: number; y: number }[]>([]); |
| 52 | const [mapMetadata, setMapMetadata] = useState<MapMetadata>({ |
| 53 | image: 'map.pgm', |
| 54 | resolution: 0.05, |
| 55 | origin: [-10.0, -10.0, 0.0], |
| 56 | negate: 0, |
| 57 | occupied_thresh: 0.65, |
| 58 | free_thresh: 0.196 |
| 59 | }); |
| 60 | const [robotRadius, setRobotRadius] = useState(0.3); // meters |
| 61 | |
| 62 | const [startPoint, setStartPoint] = useState<{ x: number; y: number } | null>(null); |
| 63 | const [webglContext, setWebglContext] = useState<ReturnType<typeof setupWebGL> | null>(null); |
| 64 | const [canvasVersion, setCanvasVersion] = useState(0); // Track canvas changes |
| 65 | |
| 66 | // Undo/Redo history management |
| 67 | const [history, setHistory] = useState<ImageData[]>([]); |
| 68 | const [historyIndex, setHistoryIndex] = useState(-1); |
| 69 | |
| 70 | // Get color for region based on thresholds |
| 71 | const getRegionColor = useCallback((region: MapRegion): string => { |
| 72 | const occupiedThresh = mapMetadata.occupied_thresh; |
| 73 | const freeThresh = mapMetadata.free_thresh; |
| 74 | const unknownValue = Math.round(((occupiedThresh + freeThresh) / 2) * 255); |
| 75 | |
| 76 | switch (region) { |
| 77 | case 'occupied': return '#000000'; |
| 78 | case 'free': return '#FFFFFF'; |
| 79 | case 'unknown': return `rgb(${unknownValue},${unknownValue},${unknownValue})`; |
| 80 | } |
| 81 | }, [mapMetadata]); |
| 82 | |
| 83 | // Save current canvas state to history AFTER drawing is complete |
| 84 | const saveToHistory = useCallback(() => { |
| 85 | const canvas = canvasRef.current; |
| 86 | if (!canvas) return; |
| 87 | |
| 88 | const ctx = canvas.getContext('2d', { willReadFrequently: true }); |
| 89 | if (!ctx) return; |
| 90 | |
| 91 | const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); |
| 92 |
nothing calls this directly
no test coverage detected