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 }) {