import React, { useRef, useEffect, useState, useMemo } from 'react'; import * as THREE from 'three'; import { useGPS } from './useGPS'; import type { WorldBounds } from './useGPS'; // ========================================== // 1. Premium 2D HUD GPS Overlay Component // ========================================== export interface GPSMinimapHUDProps { bwMaskUrl: string; colorMapUrl: string; gridWidth: number; gridHeight: number; worldBounds: WorldBounds; playerPos: { x: number; z: number }; destPos?: { x: number; z: number }; size?: number; // Size of HUD in pixels } /** * A beautiful, glassmorphic 2D HUD overlay that renders the GPS Minimap * in the corner of the screen. */ export const GPSMinimapHUD: React.FC = ({ bwMaskUrl, colorMapUrl, gridWidth, gridHeight, worldBounds, playerPos, destPos, size = 200, }) => { const canvasRef = useRef(null); const gpsOptions = useMemo(() => ({ bwMaskUrl, colorMapUrl, gridWidth, gridHeight, worldBounds, }), [bwMaskUrl, colorMapUrl, gridWidth, gridHeight, worldBounds]); const { calculateWorldPath, renderGPSToCanvas, loading, error } = useGPS(gpsOptions); useEffect(() => { if (loading || error || !canvasRef.current) return; // Calculate A* path in world coordinates const path = destPos ? calculateWorldPath(playerPos, destPos) : []; // Render path onto HUD canvas renderGPSToCanvas(canvasRef.current, path, playerPos, destPos, { pathColor: '#3b82f6', // Premium vibrant blue pathWidth: 5, playerColor: '#ef4444', // Hot red for player playerSize: 6, destColor: '#10b981', // Emerald green for destination destSize: 6, }); }, [playerPos, destPos, loading, error, calculateWorldPath, renderGPSToCanvas]); return (
{loading &&
Initializing GPS...
} {error &&
GPS Error: {error}
} {!loading && !error && ( )}
); }; // ========================================== // 2. 3D Handlebar Screen Mesh Component (R3F) // ========================================== export interface GPSBikeScreenProps { bwMaskUrl: string; colorMapUrl: string; gridWidth: number; gridHeight: number; worldBounds: WorldBounds; playerPos: { x: number; z: number }; destPos?: { x: number; z: number }; width?: number; // 3D Plane Width height?: number; // 3D Plane Height } /** * A Three.js 3D plane mesh that renders the GPS dynamically as a CanvasTexture. * This can be directly attached to the bike's handlebars in your 3D world. */ export const GPSBikeScreen: React.FC = ({ bwMaskUrl, colorMapUrl, gridWidth, gridHeight, worldBounds, playerPos, destPos, width = 0.4, height = 0.4, }) => { // Offscreen canvas to render the GPS texture onto const [offscreenCanvas] = useState(() => { const canvas = document.createElement('canvas'); canvas.width = 512; canvas.height = 512; return canvas; }); const textureRef = useRef(null); const gpsOptions = useMemo(() => ({ bwMaskUrl, colorMapUrl, gridWidth, gridHeight, worldBounds, }), [bwMaskUrl, colorMapUrl, gridWidth, gridHeight, worldBounds]); const { calculateWorldPath, renderGPSToCanvas, loading } = useGPS(gpsOptions); useEffect(() => { if (loading) return; // Calculate A* path const path = destPos ? calculateWorldPath(playerPos, destPos) : []; // Render path onto our offscreen canvas renderGPSToCanvas(offscreenCanvas, path, playerPos, destPos, { pathColor: '#60a5fa', // Bright neon blue pathWidth: 8, playerColor: '#ff0055', // Neon pink-red for bike playerSize: 10, destColor: '#00ffcc', // Vibrant cyan for target destSize: 10, }); // Notify Three.js that the texture needs an update if (textureRef.current) { textureRef.current.needsUpdate = true; } }, [playerPos, destPos, loading, calculateWorldPath, renderGPSToCanvas, offscreenCanvas]); return ( ); }; // ========================================== // Styles for HUD (Premium Glassmorphism) // ========================================== const hudStyles = { container: (size: number): React.CSSProperties => ({ position: 'absolute', bottom: '24px', right: '24px', width: `${size}px`, height: `${size}px`, borderRadius: '24px', overflow: 'hidden', border: '1px solid rgba(255, 255, 255, 0.15)', boxShadow: '0 8px 32px 0 rgba(0, 0, 0, 0.37), 0 0 15px rgba(59, 130, 246, 0.2)', backdropFilter: 'blur(8px)', WebkitBackdropFilter: 'blur(8px)', background: 'rgba(15, 23, 42, 0.6)', // Sleek dark slate display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 1000, pointerEvents: 'none', transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)', }), canvas: (size: number): React.CSSProperties => ({ width: `${size}px`, height: `${size}px`, display: 'block', }), statusText: { color: '#94a3b8', fontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif', fontSize: '12px', fontWeight: 500, letterSpacing: '0.05em', } as React.CSSProperties, };