feat: Expand texture mapping for Minecraft blocks and enhance multi-face block handling

This commit is contained in:
MayaTheShy
2026-02-20 02:01:00 -05:00
parent 882dc75762
commit 08281d88fe

View File

@@ -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 }) {
<boxGeometry args={[0.08, 0.08, 0.01]} />
<meshStandardMaterial
color="#00ff00"
emissive={mode === 'mining' ? "#00ff00" : mode === 'returning' ? "#ffaa00" : "#00aaff"}
emissive={activeMode === 'mining' ? "#00ff00" : activeMode === 'returning' || activeMode === 'goHome' ? "#ffaa00" : "#00aaff"}
emissiveIntensity={1.5}
toneMapped={false}
/>
@@ -219,7 +346,7 @@ function TurtleModel({ turtle, isSelected, onClick }) {
<boxGeometry args={[0.08, 0.08, 0.01]} />
<meshStandardMaterial
color="#00ff00"
emissive={mode === 'mining' ? "#00ff00" : mode === 'returning' ? "#ffaa00" : "#00aaff"}
emissive={activeMode === 'mining' ? "#00ff00" : activeMode === 'returning' || activeMode === 'goHome' ? "#ffaa00" : "#00aaff"}
emissiveIntensity={1.5}
toneMapped={false}
/>