From 08281d88fe5e0acbda57d705f778f64610bfdcb3 Mon Sep 17 00:00:00 2001 From: MayaTheShy Date: Fri, 20 Feb 2026 02:01:00 -0500 Subject: [PATCH] feat: Expand texture mapping for Minecraft blocks and enhance multi-face block handling --- client/src/components/Map3D.jsx | 185 +++++++++++++++++++++++++++----- 1 file changed, 156 insertions(+), 29 deletions(-) diff --git a/client/src/components/Map3D.jsx b/client/src/components/Map3D.jsx index 4679e8e..1c0c346 100644 --- a/client/src/components/Map3D.jsx +++ b/client/src/components/Map3D.jsx @@ -19,14 +19,37 @@ const TEXTURE_MAP = { 'minecraft:oak_planks': 'oak_planks', 'minecraft:glass': 'glass', + // Logs + 'minecraft:spruce_log': 'spruce_log', + 'minecraft:birch_log': 'birch_log', + 'minecraft:jungle_log': 'jungle_log', + 'minecraft:acacia_log': 'acacia_log', + 'minecraft:dark_oak_log': 'dark_oak_log', + 'minecraft:mangrove_log': 'mangrove_log', + 'minecraft:cherry_log': 'cherry_log', + + // Planks + 'minecraft:spruce_planks': 'spruce_planks', + 'minecraft:birch_planks': 'birch_planks', + 'minecraft:jungle_planks': 'jungle_planks', + 'minecraft:acacia_planks': 'acacia_planks', + 'minecraft:dark_oak_planks': 'dark_oak_planks', + // Stone variants 'minecraft:granite': 'granite', + 'minecraft:polished_granite': 'polished_granite', 'minecraft:diorite': 'diorite', + 'minecraft:polished_diorite': 'polished_diorite', 'minecraft:andesite': 'andesite', + 'minecraft:polished_andesite': 'polished_andesite', 'minecraft:deepslate': 'deepslate', + 'minecraft:cobbled_deepslate': 'cobbled_deepslate', 'minecraft:tuff': 'tuff', 'minecraft:calcite': 'calcite', 'minecraft:dripstone_block': 'dripstone_block', + 'minecraft:smooth_stone': 'smooth_stone', + 'minecraft:sandstone': 'sandstone', + 'minecraft:red_sandstone': 'red_sandstone', // Ores (overworld) 'minecraft:coal_ore': 'coal_ore', @@ -48,22 +71,81 @@ const TEXTURE_MAP = { 'minecraft:deepslate_lapis_ore': 'deepslate_lapis_ore', 'minecraft:deepslate_copper_ore': 'deepslate_copper_ore', + // Nether ores + 'minecraft:nether_gold_ore': 'nether_gold_ore', + 'minecraft:nether_quartz_ore': 'nether_quartz_ore', + 'minecraft:ancient_debris': 'ancient_debris_side', + // Special blocks 'minecraft:obsidian': 'obsidian', + 'minecraft:crying_obsidian': 'crying_obsidian', 'minecraft:netherrack': 'netherrack', 'minecraft:soul_sand': 'soul_sand', + 'minecraft:soul_soil': 'soul_soil', 'minecraft:glowstone': 'glowstone', 'minecraft:end_stone': 'end_stone', 'minecraft:water': 'water_still', 'minecraft:lava': 'lava_still', + 'minecraft:magma_block': 'magma', + 'minecraft:amethyst_block': 'amethyst_block', + 'minecraft:budding_amethyst': 'budding_amethyst', + 'minecraft:moss_block': 'moss_block', + 'minecraft:clay': 'clay', + 'minecraft:terracotta': 'terracotta', + 'minecraft:packed_ice': 'packed_ice', + 'minecraft:blue_ice': 'blue_ice', + 'minecraft:ice': 'ice', + 'minecraft:snow_block': 'snow', + 'minecraft:mycelium': 'mycelium_side', + 'minecraft:podzol': 'podzol_side', // Building blocks 'minecraft:bricks': 'bricks', 'minecraft:stone_bricks': 'stone_bricks', + 'minecraft:cracked_stone_bricks': 'cracked_stone_bricks', 'minecraft:mossy_cobblestone': 'mossy_cobblestone', 'minecraft:mossy_stone_bricks': 'mossy_stone_bricks', 'minecraft:prismarine': 'prismarine', 'minecraft:dark_prismarine': 'dark_prismarine', + 'minecraft:purpur_block': 'purpur_block', + 'minecraft:nether_bricks': 'nether_bricks', + 'minecraft:red_nether_bricks': 'red_nether_bricks', + 'minecraft:quartz_block': 'quartz_block_side', + 'minecraft:basalt': 'basalt_side', + 'minecraft:polished_basalt': 'polished_basalt_side', + 'minecraft:blackstone': 'blackstone', + 'minecraft:polished_blackstone': 'polished_blackstone', + 'minecraft:gilded_blackstone': 'gilded_blackstone', + + // Functional blocks + 'minecraft:crafting_table': 'crafting_table_front', + 'minecraft:furnace': 'furnace_front', + 'minecraft:chest': 'barrel_side', + 'minecraft:barrel': 'barrel_side', + 'minecraft:bookshelf': 'bookshelf', + 'minecraft:tnt': 'tnt_side', + 'minecraft:spawner': 'spawner', +}; + +// Blocks with different top/side/bottom textures +const MULTI_FACE_BLOCKS = { + 'minecraft:grass_block': { top: 'grass_block_top', side: 'grass_block_side', bottom: 'dirt' }, + 'minecraft:mycelium': { top: 'mycelium_top', side: 'mycelium_side', bottom: 'dirt' }, + 'minecraft:podzol': { top: 'podzol_top', side: 'podzol_side', bottom: 'dirt' }, + 'minecraft:oak_log': { top: 'oak_log_top', side: 'oak_log', bottom: 'oak_log_top' }, + 'minecraft:spruce_log': { top: 'spruce_log_top', side: 'spruce_log', bottom: 'spruce_log_top' }, + 'minecraft:birch_log': { top: 'birch_log_top', side: 'birch_log', bottom: 'birch_log_top' }, + 'minecraft:jungle_log': { top: 'jungle_log_top', side: 'jungle_log', bottom: 'jungle_log_top' }, + 'minecraft:acacia_log': { top: 'acacia_log_top', side: 'acacia_log', bottom: 'acacia_log_top' }, + 'minecraft:dark_oak_log': { top: 'dark_oak_log_top', side: 'dark_oak_log', bottom: 'dark_oak_log_top' }, + 'minecraft:sandstone': { top: 'sandstone_top', side: 'sandstone', bottom: 'sandstone_bottom' }, + 'minecraft:red_sandstone': { top: 'red_sandstone_top', side: 'red_sandstone', bottom: 'red_sandstone_bottom' }, + 'minecraft:crafting_table': { top: 'crafting_table_top', side: 'crafting_table_front', bottom: 'oak_planks' }, + 'minecraft:furnace': { top: 'furnace_top', side: 'furnace_front', bottom: 'furnace_top' }, + 'minecraft:tnt': { top: 'tnt_top', side: 'tnt_side', bottom: 'tnt_bottom' }, + 'minecraft:quartz_block': { top: 'quartz_block_top', side: 'quartz_block_side', bottom: 'quartz_block_bottom' }, + 'minecraft:basalt': { top: 'basalt_top', side: 'basalt_side', bottom: 'basalt_top' }, + 'minecraft:barrel': { top: 'barrel_top', side: 'barrel_side', bottom: 'barrel_bottom' }, }; // Function to get texture URL from a Minecraft texture service @@ -73,15 +155,18 @@ function getTextureURL(blockName) { return `https://raw.githubusercontent.com/InventivetalentDev/minecraft-assets/1.20.4/assets/minecraft/textures/block/${textureName}.png`; } -// Custom hook to load multiple textures +// Custom hook to load multiple textures (including multi-face) function useMinecraftTextures(blocks) { const [textures, setTextures] = useState({}); + const [multiFaceTextures, setMultiFaceTextures] = useState({}); const [loading, setLoading] = useState(true); useEffect(() => { const textureLoader = new THREE.TextureLoader(); const uniqueBlocks = [...new Set(blocks.map(b => b.name))]; const loadedTextures = {}; + const loadedMultiFace = {}; + let totalToLoad = 0; let loadedCount = 0; if (uniqueBlocks.length === 0) { @@ -89,8 +174,60 @@ function useMinecraftTextures(blocks) { return; } + // Collect all unique texture names to load + const textureNames = new Set(); uniqueBlocks.forEach(blockName => { - const url = getTextureURL(blockName); + const multiFace = MULTI_FACE_BLOCKS[blockName]; + if (multiFace) { + textureNames.add(multiFace.top); + textureNames.add(multiFace.side); + textureNames.add(multiFace.bottom); + } else { + const textureName = TEXTURE_MAP[blockName] || null; + if (textureName) textureNames.add(textureName); + } + }); + + totalToLoad = textureNames.size; + if (totalToLoad === 0) { + setLoading(false); + return; + } + + const loadedTexturesByName = {}; + + const checkDone = () => { + loadedCount++; + if (loadedCount >= totalToLoad) { + // Build per-block texture maps + uniqueBlocks.forEach(blockName => { + const multiFace = MULTI_FACE_BLOCKS[blockName]; + if (multiFace) { + const top = loadedTexturesByName[multiFace.top]; + const side = loadedTexturesByName[multiFace.side]; + const bottom = loadedTexturesByName[multiFace.bottom]; + if (top && side && bottom) { + loadedMultiFace[blockName] = [side, side, top, bottom, side, side]; + } else { + // Fallback to single texture + loadedTextures[blockName] = side || top || bottom || null; + } + } else { + const textureName = TEXTURE_MAP[blockName]; + if (textureName && loadedTexturesByName[textureName]) { + loadedTextures[blockName] = loadedTexturesByName[textureName]; + } + } + }); + + setTextures(loadedTextures); + setMultiFaceTextures(loadedMultiFace); + setLoading(false); + } + }; + + textureNames.forEach(textureName => { + const url = `https://raw.githubusercontent.com/InventivetalentDev/minecraft-assets/1.20.4/assets/minecraft/textures/block/${textureName}.png`; textureLoader.load( url, @@ -99,35 +236,26 @@ function useMinecraftTextures(blocks) { texture.minFilter = THREE.NearestFilter; texture.wrapS = THREE.RepeatWrapping; texture.wrapT = THREE.RepeatWrapping; - loadedTextures[blockName] = texture; - loadedCount++; - - if (loadedCount === uniqueBlocks.length) { - setTextures(loadedTextures); - setLoading(false); - } + texture.colorSpace = THREE.SRGBColorSpace; + loadedTexturesByName[textureName] = texture; + checkDone(); }, undefined, - (error) => { - console.warn(`Failed to load texture for ${blockName}, using color fallback`); - loadedCount++; - - if (loadedCount === uniqueBlocks.length) { - setTextures(loadedTextures); - setLoading(false); - } + () => { + checkDone(); } ); }); }, [blocks]); - return { textures, loading }; + return { textures, multiFaceTextures, loading }; } // Minecraft-style turtle model function TurtleModel({ turtle, isSelected, onClick }) { const groupRef = useRef(); - const { position, facing, mode } = turtle; + const { position, facing, mode, state } = turtle; + const activeMode = state || mode || 'idle'; useFrame((state) => { if (groupRef.current && isSelected) { @@ -138,17 +266,16 @@ function TurtleModel({ turtle, isSelected, onClick }) { if (!position) return null; // Calculate rotation based on facing (0=North(-Z), 1=East(+X), 2=South(+Z), 3=West(-X)) - // In Three.js, 0 rotation faces +Z, so we need to adjust: - // facing 0 (North/-Z) = rotate 180° = Math.PI - // facing 1 (East/+X) = rotate 270° = -Math.PI/2 - // facing 2 (South/+Z) = rotate 0° = 0 - // facing 3 (West/-X) = rotate 90° = Math.PI/2 const facingRotations = [Math.PI, -Math.PI/2, 0, Math.PI/2]; const rotation = facing !== undefined ? [0, facingRotations[facing], 0] : [0, 0, 0]; - const color = mode === 'mining' ? '#4ade80' : - mode === 'exploring' ? '#60a5fa' : - mode === 'returning' ? '#f59e0b' : + const color = activeMode === 'mining' ? '#4ade80' : + activeMode === 'exploring' ? '#60a5fa' : + activeMode === 'returning' || activeMode === 'goHome' ? '#f59e0b' : + activeMode === 'refueling' ? '#ef4444' : + activeMode === 'farming' ? '#22c55e' : + activeMode === 'dumpInventory' || activeMode === 'dumping' ? '#a855f7' : + activeMode === 'moving' ? '#06b6d4' : '#9ca3af'; return ( @@ -210,7 +337,7 @@ function TurtleModel({ turtle, isSelected, onClick }) { @@ -219,7 +346,7 @@ function TurtleModel({ turtle, isSelected, onClick }) {