feat: Expand texture mapping for Minecraft blocks and enhance multi-face block handling
This commit is contained in:
@@ -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}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user