diff --git a/client/src/components/Map3D.jsx b/client/src/components/Map3D.jsx
index 0cd3964..675cc46 100644
--- a/client/src/components/Map3D.jsx
+++ b/client/src/components/Map3D.jsx
@@ -1,9 +1,129 @@
-import React, { useRef, useMemo, useEffect } from 'react';
-import { Canvas, useFrame } from '@react-three/fiber';
+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();
@@ -165,77 +285,84 @@ function getBlockAppearance(blockName) {
return { color: '#666666', pattern: 'solid' };
}
-// WorldBlocks component to render discovered blocks
+// WorldBlocks component to render discovered blocks with Minecraft textures
function WorldBlocks({ blocks }) {
- const [hoveredBlock, setHoveredBlock] = React.useState(null);
+ const [hoveredBlock, setHoveredBlock] = useState(null);
+ const { textures, loading } = useMinecraftTextures(blocks);
- // Group blocks by appearance for better performance
+ // Group blocks by block name for better performance
const blockGroups = useMemo(() => {
const groups = new Map();
blocks.forEach(block => {
- const appearance = getBlockAppearance(block.name);
- const key = `${appearance.color}-${appearance.pattern}`;
-
- if (!groups.has(key)) {
- groups.set(key, { appearance, blocks: [] });
+ if (!groups.has(block.name)) {
+ groups.set(block.name, []);
}
- groups.get(key).blocks.push(block);
+ groups.get(block.name).push(block);
});
- return Array.from(groups.values());
+ return Array.from(groups.entries());
}, [blocks]);
+ if (loading) {
+ return Loading textures...;
+ }
+
return (
- {blockGroups.map((group, groupIdx) => (
-
- {group.blocks.map((block) => {
- const appearance = group.appearance;
- 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 && (
- {
+ 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.name.replace('minecraft:', '').replace(/_/g, ' ').toUpperCase()}
-
- )}
-
- );
- })}
-
- ))}
+
+
+
+
+ {/* Block label on hover */}
+ {isHovered && (
+
+ {block.name.replace('minecraft:', '').replace(/_/g, ' ').toUpperCase()}
+
+ )}
+
+ );
+ })}
+
+ );
+ })}
);
}