game
// ==================== REAL 3D MODEL LOADER (improved) ====================
function loadRealModels() {
function setupCharacter(gltf, name) {
const model = gltf.scene;
model.traverse(c => {
if (c.isMesh) {
c.castShadow = true;
c.receiveShadow = true;
if (c.material) {
const mats = Array.isArray(c.material) ? c.material : [c.material];
mats.forEach(m => {
if (m) {
m.roughness = Math.min(0.85, (m.roughness ?? 0.65) + 0.05);
m.metalness = Math.max(0.02, (m.metalness ?? 0) * 0.5);
m.envMapIntensity = 1.0;
}
});
}
}
});
// Both models are authored ~1 unit tall → scale to ~1.8 m
const targetHeight = 1.8;
const box = new THREE.Box3().setFromObject(model);
const size = box.getSize(new THREE.Vector3());
const scale = targetHeight / Math.max(size.y, 0.01);
model.scale.setScalar(scale);
// Put feet on the ground
const box2 = new THREE.Box3().setFromObject(model);
model.position.y = -box2.min.y;
// Clear procedural mesh and attach real model
while (playerGroup.children.length) playerGroup.remove(playerGroup.children[0]);
playerGroup.add(model);
loadedModels.character = model;
loadedModels.characterAnims = gltf.animations || [];
player.position.y = 0; // real model origin is now at feet
if (gltf.animations?.length) {
playerMixer = new THREE.AnimationMixer(model);
playerActions = {};
gltf.animations.forEach(clip => {
// Normalize names so both Xbot & Soldier work
const key = clip.name.toLowerCase().replace(/[^a-z]/g, '');
playerActions[key] = playerMixer.clipAction(clip);
playerActions[clip.name] = playerActions[key]; // keep original too
});
// Prefer idle
const idle = playerActions['idle'] || playerActions['Idle'] ||
Object.values(playerActions)[0];
if (idle) {
idle.play();
playerAnimState = idle.getClip().name;
}
console.log(name + ' animations:', Object.keys(playerActions));
}
console.log('Real human character loaded:', name);
}
// Try Xbot first, then Soldier
gltfLoader.load(MODEL_URLS.character, gltf => {
setupCharacter(gltf, 'Xbot');
}, undefined, () => {
console.warn('Xbot failed, trying Soldier…');
gltfLoader.load(MODEL_URLS.soldier, gltf => {
setupCharacter(gltf, 'Soldier');
}, undefined, err => {
console.warn('All character models failed – keeping procedural fallback', err);
});
});
// … keep the existing Ferrari + bike loaders unchanged …
}
function setPlayerAnim(logicalName) {
if (!playerMixer || !playerActions) return;
const candidates = {
Idle: ['idle', 'Idle', 'tpose'],
Walk: ['walk', 'Walk', 'walking'],
Run: ['run', 'Run', 'running'],
}[logicalName] || [logicalName.toLowerCase()];
let clip = null;
for (const c of candidates) {
if (playerActions[c]) { clip = playerActions[c]; break; }
}
// Fuzzy fallback
if (!clip) {
const key = Object.keys(playerActions).find(k =>
k.toLowerCase().includes(logicalName.toLowerCase())
);
if (key) clip = playerActions[key];
}
if (!clip) return;
const clipName = clip.getClip().name;
if (playerAnimState === clipName) return;
Object.values(playerActions).forEach(a => {
if (a.isRunning()) a.fadeOut(0.2);
});
clip.reset().fadeIn(0.2).play();
playerAnimState = clipName;
}
Comments
Post a Comment