From ac841dc1c5479912f01e0f35dee583d7ddc8cafa Mon Sep 17 00:00:00 2001 From: MayaTheShy Date: Mon, 16 Feb 2026 01:54:53 -0500 Subject: [PATCH] feat: Implement Minecraft texture loading and enhance WorldBlocks rendering with textures --- client/src/components/Map3D.jsx | 245 ++++++++++++++++++++++++-------- 1 file changed, 186 insertions(+), 59 deletions(-) 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()} + + )} + + ); + })} + + ); + })} ); }