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:oak_planks': 'oak_planks',
|
||||||
'minecraft:glass': 'glass',
|
'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
|
// Stone variants
|
||||||
'minecraft:granite': 'granite',
|
'minecraft:granite': 'granite',
|
||||||
|
'minecraft:polished_granite': 'polished_granite',
|
||||||
'minecraft:diorite': 'diorite',
|
'minecraft:diorite': 'diorite',
|
||||||
|
'minecraft:polished_diorite': 'polished_diorite',
|
||||||
'minecraft:andesite': 'andesite',
|
'minecraft:andesite': 'andesite',
|
||||||
|
'minecraft:polished_andesite': 'polished_andesite',
|
||||||
'minecraft:deepslate': 'deepslate',
|
'minecraft:deepslate': 'deepslate',
|
||||||
|
'minecraft:cobbled_deepslate': 'cobbled_deepslate',
|
||||||
'minecraft:tuff': 'tuff',
|
'minecraft:tuff': 'tuff',
|
||||||
'minecraft:calcite': 'calcite',
|
'minecraft:calcite': 'calcite',
|
||||||
'minecraft:dripstone_block': 'dripstone_block',
|
'minecraft:dripstone_block': 'dripstone_block',
|
||||||
|
'minecraft:smooth_stone': 'smooth_stone',
|
||||||
|
'minecraft:sandstone': 'sandstone',
|
||||||
|
'minecraft:red_sandstone': 'red_sandstone',
|
||||||
|
|
||||||
// Ores (overworld)
|
// Ores (overworld)
|
||||||
'minecraft:coal_ore': 'coal_ore',
|
'minecraft:coal_ore': 'coal_ore',
|
||||||
@@ -48,22 +71,81 @@ const TEXTURE_MAP = {
|
|||||||
'minecraft:deepslate_lapis_ore': 'deepslate_lapis_ore',
|
'minecraft:deepslate_lapis_ore': 'deepslate_lapis_ore',
|
||||||
'minecraft:deepslate_copper_ore': 'deepslate_copper_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
|
// Special blocks
|
||||||
'minecraft:obsidian': 'obsidian',
|
'minecraft:obsidian': 'obsidian',
|
||||||
|
'minecraft:crying_obsidian': 'crying_obsidian',
|
||||||
'minecraft:netherrack': 'netherrack',
|
'minecraft:netherrack': 'netherrack',
|
||||||
'minecraft:soul_sand': 'soul_sand',
|
'minecraft:soul_sand': 'soul_sand',
|
||||||
|
'minecraft:soul_soil': 'soul_soil',
|
||||||
'minecraft:glowstone': 'glowstone',
|
'minecraft:glowstone': 'glowstone',
|
||||||
'minecraft:end_stone': 'end_stone',
|
'minecraft:end_stone': 'end_stone',
|
||||||
'minecraft:water': 'water_still',
|
'minecraft:water': 'water_still',
|
||||||
'minecraft:lava': 'lava_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
|
// Building blocks
|
||||||
'minecraft:bricks': 'bricks',
|
'minecraft:bricks': 'bricks',
|
||||||
'minecraft:stone_bricks': 'stone_bricks',
|
'minecraft:stone_bricks': 'stone_bricks',
|
||||||
|
'minecraft:cracked_stone_bricks': 'cracked_stone_bricks',
|
||||||
'minecraft:mossy_cobblestone': 'mossy_cobblestone',
|
'minecraft:mossy_cobblestone': 'mossy_cobblestone',
|
||||||
'minecraft:mossy_stone_bricks': 'mossy_stone_bricks',
|
'minecraft:mossy_stone_bricks': 'mossy_stone_bricks',
|
||||||
'minecraft:prismarine': 'prismarine',
|
'minecraft:prismarine': 'prismarine',
|
||||||
'minecraft:dark_prismarine': 'dark_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
|
// 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`;
|
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) {
|
function useMinecraftTextures(blocks) {
|
||||||
const [textures, setTextures] = useState({});
|
const [textures, setTextures] = useState({});
|
||||||
|
const [multiFaceTextures, setMultiFaceTextures] = useState({});
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const textureLoader = new THREE.TextureLoader();
|
const textureLoader = new THREE.TextureLoader();
|
||||||
const uniqueBlocks = [...new Set(blocks.map(b => b.name))];
|
const uniqueBlocks = [...new Set(blocks.map(b => b.name))];
|
||||||
const loadedTextures = {};
|
const loadedTextures = {};
|
||||||
|
const loadedMultiFace = {};
|
||||||
|
let totalToLoad = 0;
|
||||||
let loadedCount = 0;
|
let loadedCount = 0;
|
||||||
|
|
||||||
if (uniqueBlocks.length === 0) {
|
if (uniqueBlocks.length === 0) {
|
||||||
@@ -89,8 +174,60 @@ function useMinecraftTextures(blocks) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Collect all unique texture names to load
|
||||||
|
const textureNames = new Set();
|
||||||
uniqueBlocks.forEach(blockName => {
|
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(
|
textureLoader.load(
|
||||||
url,
|
url,
|
||||||
@@ -99,35 +236,26 @@ function useMinecraftTextures(blocks) {
|
|||||||
texture.minFilter = THREE.NearestFilter;
|
texture.minFilter = THREE.NearestFilter;
|
||||||
texture.wrapS = THREE.RepeatWrapping;
|
texture.wrapS = THREE.RepeatWrapping;
|
||||||
texture.wrapT = THREE.RepeatWrapping;
|
texture.wrapT = THREE.RepeatWrapping;
|
||||||
loadedTextures[blockName] = texture;
|
texture.colorSpace = THREE.SRGBColorSpace;
|
||||||
loadedCount++;
|
loadedTexturesByName[textureName] = texture;
|
||||||
|
checkDone();
|
||||||
if (loadedCount === uniqueBlocks.length) {
|
|
||||||
setTextures(loadedTextures);
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
undefined,
|
undefined,
|
||||||
(error) => {
|
() => {
|
||||||
console.warn(`Failed to load texture for ${blockName}, using color fallback`);
|
checkDone();
|
||||||
loadedCount++;
|
|
||||||
|
|
||||||
if (loadedCount === uniqueBlocks.length) {
|
|
||||||
setTextures(loadedTextures);
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}, [blocks]);
|
}, [blocks]);
|
||||||
|
|
||||||
return { textures, loading };
|
return { textures, multiFaceTextures, loading };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Minecraft-style turtle model
|
// Minecraft-style turtle model
|
||||||
function TurtleModel({ turtle, isSelected, onClick }) {
|
function TurtleModel({ turtle, isSelected, onClick }) {
|
||||||
const groupRef = useRef();
|
const groupRef = useRef();
|
||||||
const { position, facing, mode } = turtle;
|
const { position, facing, mode, state } = turtle;
|
||||||
|
const activeMode = state || mode || 'idle';
|
||||||
|
|
||||||
useFrame((state) => {
|
useFrame((state) => {
|
||||||
if (groupRef.current && isSelected) {
|
if (groupRef.current && isSelected) {
|
||||||
@@ -138,17 +266,16 @@ function TurtleModel({ turtle, isSelected, onClick }) {
|
|||||||
if (!position) return null;
|
if (!position) return null;
|
||||||
|
|
||||||
// Calculate rotation based on facing (0=North(-Z), 1=East(+X), 2=South(+Z), 3=West(-X))
|
// 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 facingRotations = [Math.PI, -Math.PI/2, 0, Math.PI/2];
|
||||||
const rotation = facing !== undefined ? [0, facingRotations[facing], 0] : [0, 0, 0];
|
const rotation = facing !== undefined ? [0, facingRotations[facing], 0] : [0, 0, 0];
|
||||||
|
|
||||||
const color = mode === 'mining' ? '#4ade80' :
|
const color = activeMode === 'mining' ? '#4ade80' :
|
||||||
mode === 'exploring' ? '#60a5fa' :
|
activeMode === 'exploring' ? '#60a5fa' :
|
||||||
mode === 'returning' ? '#f59e0b' :
|
activeMode === 'returning' || activeMode === 'goHome' ? '#f59e0b' :
|
||||||
|
activeMode === 'refueling' ? '#ef4444' :
|
||||||
|
activeMode === 'farming' ? '#22c55e' :
|
||||||
|
activeMode === 'dumpInventory' || activeMode === 'dumping' ? '#a855f7' :
|
||||||
|
activeMode === 'moving' ? '#06b6d4' :
|
||||||
'#9ca3af';
|
'#9ca3af';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -210,7 +337,7 @@ function TurtleModel({ turtle, isSelected, onClick }) {
|
|||||||
<boxGeometry args={[0.08, 0.08, 0.01]} />
|
<boxGeometry args={[0.08, 0.08, 0.01]} />
|
||||||
<meshStandardMaterial
|
<meshStandardMaterial
|
||||||
color="#00ff00"
|
color="#00ff00"
|
||||||
emissive={mode === 'mining' ? "#00ff00" : mode === 'returning' ? "#ffaa00" : "#00aaff"}
|
emissive={activeMode === 'mining' ? "#00ff00" : activeMode === 'returning' || activeMode === 'goHome' ? "#ffaa00" : "#00aaff"}
|
||||||
emissiveIntensity={1.5}
|
emissiveIntensity={1.5}
|
||||||
toneMapped={false}
|
toneMapped={false}
|
||||||
/>
|
/>
|
||||||
@@ -219,7 +346,7 @@ function TurtleModel({ turtle, isSelected, onClick }) {
|
|||||||
<boxGeometry args={[0.08, 0.08, 0.01]} />
|
<boxGeometry args={[0.08, 0.08, 0.01]} />
|
||||||
<meshStandardMaterial
|
<meshStandardMaterial
|
||||||
color="#00ff00"
|
color="#00ff00"
|
||||||
emissive={mode === 'mining' ? "#00ff00" : mode === 'returning' ? "#ffaa00" : "#00aaff"}
|
emissive={activeMode === 'mining' ? "#00ff00" : activeMode === 'returning' || activeMode === 'goHome' ? "#ffaa00" : "#00aaff"}
|
||||||
emissiveIntensity={1.5}
|
emissiveIntensity={1.5}
|
||||||
toneMapped={false}
|
toneMapped={false}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user