import React, { useRef, useMemo, useEffect, useState } from 'react'; import { Canvas, useFrame, useLoader } from '@react-three/fiber'; import { OrbitControls, Grid, Text, Line } from '@react-three/drei'; import * as THREE from 'three'; import { useTurtleStore } from '../store/turtleStore'; // Texture mapping for Minecraft blocks const TEXTURE_MAP = { // Basic blocks 'minecraft:stone': 'stone', 'minecraft:cobblestone': 'cobblestone', 'minecraft:dirt': 'dirt', 'minecraft:grass_block': 'grass_block_side', 'minecraft:sand': 'sand', 'minecraft:red_sand': 'red_sand', 'minecraft:gravel': 'gravel', 'minecraft:bedrock': 'bedrock', 'minecraft:oak_log': 'oak_log', 'minecraft:oak_planks': 'oak_planks', 'minecraft:glass': 'glass', // Stone variants 'minecraft:granite': 'granite', 'minecraft:diorite': 'diorite', 'minecraft:andesite': 'andesite', 'minecraft:deepslate': 'deepslate', 'minecraft:tuff': 'tuff', 'minecraft:calcite': 'calcite', 'minecraft:dripstone_block': 'dripstone_block', // Ores (overworld) 'minecraft:coal_ore': 'coal_ore', 'minecraft:iron_ore': 'iron_ore', 'minecraft:gold_ore': 'gold_ore', 'minecraft:diamond_ore': 'diamond_ore', 'minecraft:emerald_ore': 'emerald_ore', 'minecraft:redstone_ore': 'redstone_ore', 'minecraft:lapis_ore': 'lapis_ore', 'minecraft:copper_ore': 'copper_ore', // Deepslate ores 'minecraft:deepslate_coal_ore': 'deepslate_coal_ore', 'minecraft:deepslate_iron_ore': 'deepslate_iron_ore', 'minecraft:deepslate_gold_ore': 'deepslate_gold_ore', 'minecraft:deepslate_diamond_ore': 'deepslate_diamond_ore', 'minecraft:deepslate_emerald_ore': 'deepslate_emerald_ore', 'minecraft:deepslate_redstone_ore': 'deepslate_redstone_ore', 'minecraft:deepslate_lapis_ore': 'deepslate_lapis_ore', 'minecraft:deepslate_copper_ore': 'deepslate_copper_ore', // Special blocks 'minecraft:obsidian': 'obsidian', 'minecraft:netherrack': 'netherrack', 'minecraft:soul_sand': 'soul_sand', 'minecraft:glowstone': 'glowstone', 'minecraft:end_stone': 'end_stone', 'minecraft:water': 'water_still', 'minecraft:lava': 'lava_still', // Building blocks 'minecraft:bricks': 'bricks', 'minecraft:stone_bricks': 'stone_bricks', 'minecraft:mossy_cobblestone': 'mossy_cobblestone', 'minecraft:mossy_stone_bricks': 'mossy_stone_bricks', 'minecraft:prismarine': 'prismarine', 'minecraft:dark_prismarine': 'dark_prismarine', }; // Function to get texture URL from a Minecraft texture service function getTextureURL(blockName) { const textureName = TEXTURE_MAP[blockName] || 'stone'; // Using a public Minecraft texture API return `https://raw.githubusercontent.com/InventivetalentDev/minecraft-assets/1.20.4/assets/minecraft/textures/block/${textureName}.png`; } // Custom hook to load multiple textures function useMinecraftTextures(blocks) { const [textures, setTextures] = useState({}); const [loading, setLoading] = useState(true); useEffect(() => { const textureLoader = new THREE.TextureLoader(); const uniqueBlocks = [...new Set(blocks.map(b => b.name))]; const loadedTextures = {}; let loadedCount = 0; if (uniqueBlocks.length === 0) { setLoading(false); return; } uniqueBlocks.forEach(blockName => { const url = getTextureURL(blockName); textureLoader.load( url, (texture) => { texture.magFilter = THREE.NearestFilter; texture.minFilter = THREE.NearestFilter; texture.wrapS = THREE.RepeatWrapping; texture.wrapT = THREE.RepeatWrapping; loadedTextures[blockName] = texture; loadedCount++; if (loadedCount === uniqueBlocks.length) { setTextures(loadedTextures); setLoading(false); } }, undefined, (error) => { console.warn(`Failed to load texture for ${blockName}, using color fallback`); loadedCount++; if (loadedCount === uniqueBlocks.length) { setTextures(loadedTextures); setLoading(false); } } ); }); }, [blocks]); return { textures, loading }; } // Minecraft-style turtle model function TurtleModel({ turtle, isSelected, onClick }) { const groupRef = useRef(); const { position, facing, mode } = turtle; useFrame((state) => { if (groupRef.current && isSelected) { groupRef.current.children[0].position.y = Math.sin(state.clock.elapsedTime * 2) * 0.05; } }); if (!position) return null; // Calculate rotation based on facing (0=North(-Z), 1=East(+X), 2=South(+Z), 3=West(-X)) // Add 180 degrees (Math.PI) to face the head forward initially, then apply facing rotation const rotation = facing !== undefined ? [0, (facing * Math.PI / 2) + Math.PI, 0] : [0, Math.PI, 0]; const color = mode === 'mining' ? '#4ade80' : mode === 'exploring' ? '#60a5fa' : mode === 'returning' ? '#f59e0b' : '#9ca3af'; return ( {/* Shell (main body) */} {/* Head */} {/* Eyes */} {/* Legs */} {/* Tail */} {/* Selection indicator */} {isSelected && ( <> {/* Selection ring */} )} {/* Turtle ID label */} 🐢 {turtle.turtleID} ); } // Path trail component function PathTrail({ turtle }) { const { position, homePosition } = turtle; if (!position || !homePosition) return null; const points = [ new THREE.Vector3(position.x, position.y, position.z), new THREE.Vector3(homePosition.x, homePosition.y, homePosition.z) ]; return ( ); } // Get color and texture info for block type function getBlockAppearance(blockName) { if (!blockName) return { color: '#888', pattern: 'solid' }; const name = blockName.toLowerCase(); // Ores - bright colors with sparkle if (name.includes('diamond')) return { color: '#00ffff', pattern: 'ore', emissive: '#00ffff', intensity: 0.3 }; if (name.includes('emerald')) return { color: '#00ff00', pattern: 'ore', emissive: '#00ff00', intensity: 0.3 }; if (name.includes('gold')) return { color: '#ffd700', pattern: 'ore', emissive: '#ffd700', intensity: 0.2 }; if (name.includes('iron')) return { color: '#d4d4d4', pattern: 'ore' }; if (name.includes('coal')) return { color: '#1a1a1a', pattern: 'ore' }; if (name.includes('redstone')) return { color: '#ff0000', pattern: 'ore', emissive: '#ff0000', intensity: 0.2 }; if (name.includes('lapis')) return { color: '#1e40af', pattern: 'ore' }; if (name.includes('copper')) return { color: '#ff8c00', pattern: 'ore' }; // Common blocks - textured if (name.includes('stone') && !name.includes('cobble')) return { color: '#7f7f7f', pattern: 'stone' }; if (name.includes('cobblestone')) return { color: '#808080', pattern: 'cobble' }; if (name.includes('dirt')) return { color: '#8b4513', pattern: 'dirt' }; if (name.includes('grass')) return { color: '#228b22', pattern: 'grass' }; if (name.includes('sand')) return { color: '#f4a460', pattern: 'sand' }; if (name.includes('gravel')) return { color: '#808080', pattern: 'gravel' }; if (name.includes('bedrock')) return { color: '#222', pattern: 'solid' }; if (name.includes('obsidian')) return { color: '#1a0033', pattern: 'solid' }; if (name.includes('netherrack')) return { color: '#8b0000', pattern: 'netherrack' }; return { color: '#666666', pattern: 'solid' }; } // WorldBlocks component to render discovered blocks with Minecraft textures function WorldBlocks({ blocks }) { const [hoveredBlock, setHoveredBlock] = useState(null); const { textures, loading } = useMinecraftTextures(blocks); // Group blocks by block name for better performance const blockGroups = useMemo(() => { const groups = new Map(); blocks.forEach(block => { if (!groups.has(block.name)) { groups.set(block.name, []); } groups.get(block.name).push(block); }); return Array.from(groups.entries()); }, [blocks]); if (loading) { return Loading textures...; } return ( {blockGroups.map(([blockName, blockList]) => { const appearance = getBlockAppearance(blockName); const texture = textures[blockName]; return ( {blockList.map((block) => { const blockKey = `${block.x},${block.y},${block.z}`; const isHovered = hoveredBlock === blockKey; return ( { e.stopPropagation(); setHoveredBlock(blockKey); }} onPointerOut={() => setHoveredBlock(null)} > {/* Block label on hover */} {isHovered && ( {block.name.replace('minecraft:', '').replace(/_/g, ' ').toUpperCase()} )} ); })} ); })} ); } // Path trail component (old version removed above) function OldPathTrail({ turtle }) { const { position, homePosition } = turtle; if (!position || !homePosition) return null; const points = [ new THREE.Vector3(position.x, position.y, position.z), new THREE.Vector3(homePosition.x, homePosition.y, homePosition.z) ]; return ( ); } // Home marker component function HomeMarker({ position }) { if (!position) return null; return ( HOME ); } // Main scene component function Scene() { const turtles = useTurtleStore((state) => state.getTurtleArray()); const selectedTurtleId = useTurtleStore((state) => state.selectedTurtleId); const selectTurtle = useTurtleStore((state) => state.selectTurtle); const worldBlocks = useTurtleStore((state) => state.worldBlocks || []); // Calculate center point for camera focus const centerPoint = useMemo(() => { if (turtles.length === 0) return [0, 0, 0]; let sumX = 0, sumY = 0, sumZ = 0; let count = 0; turtles.forEach(turtle => { if (turtle.position) { sumX += turtle.position.x; sumY += turtle.position.y; sumZ += turtle.position.z; count++; } }); if (count === 0) return [0, 0, 0]; return [sumX / count, sumY / count, sumZ / count]; }, [turtles]); // Get home position from first turtle const homePosition = turtles.find(t => t.homePosition)?.homePosition; return ( <> {/* Grid */} {/* Render discovered blocks */} {/* Home marker */} {homePosition && } {/* Turtles and paths */} {turtles.map((turtle) => ( selectTurtle(turtle.turtleID)} /> ))} {/* Camera controls */} ); } // Main Map3D component export default function Map3D() { return (
); }