Files
La-Fabrik/src/pathfinding/GPSMinimap.tsx
T
Tom Boullay 89044a18ec
📊 Quality / 🔒 Security Audit (pull_request) Has been cancelled
🔍 Lint / 🪄 Check lint (pull_request) Has been cancelled
🔍 Lint / 🎨 Check format (pull_request) Has been cancelled
🔍 Lint / 🔎 Typecheck (pull_request) Has been cancelled
📊 Quality / 📋 Dependency Freshness (pull_request) Has been cancelled
📊 Quality / 📦 Bundle Size (pull_request) Has been cancelled
🔍 Lint / 🏗 Build (pull_request) Has been cancelled
merge develop into feat/map-environment
2026-05-29 01:45:08 +02:00

235 lines
6.0 KiB
TypeScript

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<GPSMinimapHUDProps> = ({
bwMaskUrl,
colorMapUrl,
gridWidth,
gridHeight,
worldBounds,
playerPos,
destPos,
size = 200,
}) => {
const canvasRef = useRef<HTMLCanvasElement | null>(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 (
<div style={hudStyles.container(size)}>
{loading && <div style={hudStyles.statusText}>Initializing GPS...</div>}
{error && (
<div style={{ ...hudStyles.statusText, color: "#ef4444" }}>
GPS Error: {error}
</div>
)}
{!loading && !error && (
<canvas
ref={canvasRef}
width={size * 2} // Double size for retina/high-DPI screens
height={size * 2}
style={hudStyles.canvas(size)}
/>
)}
</div>
);
};
// ==========================================
// 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<GPSBikeScreenProps> = ({
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<THREE.CanvasTexture | null>(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 (
<mesh castShadow receiveShadow>
<planeGeometry args={[width, height]} />
<meshBasicMaterial toneMapped={false}>
<canvasTexture
ref={textureRef}
attach="map"
image={offscreenCanvas}
minFilter={THREE.LinearFilter}
magFilter={THREE.LinearFilter}
/>
</meshBasicMaterial>
</mesh>
);
};
// ==========================================
// 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,
};