Refactor spirit loading and animation logic
This commit is contained in:
129
index.html
129
index.html
@@ -152,43 +152,6 @@
|
|||||||
const gltfLoader = new GLTFLoader();
|
const gltfLoader = new GLTFLoader();
|
||||||
gltfLoader.setDRACOLoader(draco);
|
gltfLoader.setDRACOLoader(draco);
|
||||||
|
|
||||||
// --- Spirits automatisch einsammeln ---
|
|
||||||
let spiritModels = [];
|
|
||||||
async function fetchSpirits(){
|
|
||||||
const res = await fetch('assets/models/spirits/');
|
|
||||||
const html = await res.text();
|
|
||||||
const doc = new DOMParser().parseFromString(html,'text/html');
|
|
||||||
spiritModels = [...doc.querySelectorAll('a')]
|
|
||||||
.map(a => a.getAttribute('href'))
|
|
||||||
.filter(h=>h.endsWith('.glb'))
|
|
||||||
.map(name=>`assets/models/spirits/${name}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- GLBs laden ---
|
|
||||||
async function loadGLB(path, pos, rotDeg, {receiveShadow=false, castShadow=false, emissive=null, visible=true, shadowOnly=false} = {}) {
|
|
||||||
const { scene: obj } = await gltfLoader.loadAsync(path);
|
|
||||||
obj.position.set(pos[0], pos[1], pos[2]);
|
|
||||||
obj.rotation.set(
|
|
||||||
THREE.MathUtils.degToRad(rotDeg[0]),
|
|
||||||
THREE.MathUtils.degToRad(rotDeg[1]),
|
|
||||||
THREE.MathUtils.degToRad(rotDeg[2])
|
|
||||||
);
|
|
||||||
obj.traverse(c => {
|
|
||||||
c.visible = visible;
|
|
||||||
if (c.isMesh) {
|
|
||||||
c.castShadow = castShadow;
|
|
||||||
c.receiveShadow = receiveShadow;
|
|
||||||
if (shadowOnly) c.material = shadowOnlyMaterial;
|
|
||||||
if (emissive && c.material && c.material.isMeshStandardMaterial) {
|
|
||||||
c.material.emissive = new THREE.Color(emissive);
|
|
||||||
c.material.emissiveIntensity = 1.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
scene.add(obj);
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Shadow-Only-Material ---
|
// --- Shadow-Only-Material ---
|
||||||
const shadowOnlyMaterial = new THREE.MeshBasicMaterial({
|
const shadowOnlyMaterial = new THREE.MeshBasicMaterial({
|
||||||
color: 0x000000,
|
color: 0x000000,
|
||||||
@@ -221,36 +184,68 @@
|
|||||||
scene.add(L2);
|
scene.add(L2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Spirit-Klasse ohne Partikel & Sound ---
|
// --- Lazy-Loading/Preload Spirit Mechanik ---
|
||||||
|
let spiritModelsList = [];
|
||||||
|
let nextSpiritUrl = null;
|
||||||
|
let nextSpiritGltf = null;
|
||||||
|
let spiritLoadingPromise = null;
|
||||||
|
|
||||||
|
async function fetchSpiritList(){
|
||||||
|
const res = await fetch('assets/models/spirits/');
|
||||||
|
const html = await res.text();
|
||||||
|
const doc = new DOMParser().parseFromString(html,'text/html');
|
||||||
|
spiritModelsList = [...doc.querySelectorAll('a')]
|
||||||
|
.map(a => a.getAttribute('href'))
|
||||||
|
.filter(h=>h.endsWith('.glb'))
|
||||||
|
.map(name=>`assets/models/spirits/${name}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function prepareNextSpirit() {
|
||||||
|
nextSpiritUrl = spiritModelsList[Math.floor(Math.random()*spiritModelsList.length)];
|
||||||
|
nextSpiritGltf = null;
|
||||||
|
spiritLoadingPromise = gltfLoader.loadAsync(nextSpiritUrl).then(gltf => {
|
||||||
|
nextSpiritGltf = gltf;
|
||||||
|
}).catch(err => {
|
||||||
|
console.warn('Spirit konnte nicht geladen werden:', nextSpiritUrl, err);
|
||||||
|
prepareNextSpirit();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getAndPrepareSpiritInstance(position) {
|
||||||
|
// Warte ggf. aufs Laden!
|
||||||
|
if (!nextSpiritGltf) await spiritLoadingPromise;
|
||||||
|
const s = nextSpiritGltf.scene.clone(true); // mit true: tiefer Clone
|
||||||
|
s.traverse(mesh => {
|
||||||
|
if(mesh.isMesh){
|
||||||
|
mesh.castShadow = true;
|
||||||
|
mesh.receiveShadow = true;
|
||||||
|
mesh.userData.originalMaterial = mesh.material.clone();
|
||||||
|
mesh.material = mesh.material.clone();
|
||||||
|
mesh.material.color.set(0xffffcc);
|
||||||
|
mesh.material.opacity = 0.0;
|
||||||
|
mesh.material.emissive?.set(0xffffcc);
|
||||||
|
mesh.material.emissiveIntensity = 2.0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
s.rotation.x = -Math.PI;
|
||||||
|
s.position.copy(position).add(new THREE.Vector3(0, 0, -0.6));
|
||||||
|
// Nach dem Spawn gleich das nächste Modell vorbereiten:
|
||||||
|
prepareNextSpirit();
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Spirit-Klasse nur noch für Animation/State ---
|
||||||
class Spirit {
|
class Spirit {
|
||||||
constructor(position) {
|
constructor(obj3d) {
|
||||||
this.clock = new THREE.Clock();
|
this.clock = new THREE.Clock();
|
||||||
this.grp = new THREE.Group();
|
this.grp = new THREE.Group();
|
||||||
this.spiritMeshes = [];
|
this.spiritMeshes = [];
|
||||||
this.isFading = true;
|
this.isFading = true;
|
||||||
this.grp.position.copy(position);
|
this.grp.add(obj3d);
|
||||||
this.grp.position.z -= 0.6;
|
obj3d.traverse(mesh=>{
|
||||||
scene.add(this.grp);
|
if(mesh.isMesh) this.spiritMeshes.push(mesh);
|
||||||
|
|
||||||
// Laden Spirit-GLB
|
|
||||||
const path = spiritModels[Math.floor(Math.random()*spiritModels.length)];
|
|
||||||
gltfLoader.load(path, ({ scene: s }) => {
|
|
||||||
s.traverse(mesh => {
|
|
||||||
if(mesh.isMesh){
|
|
||||||
mesh.castShadow = true;
|
|
||||||
mesh.receiveShadow = true;
|
|
||||||
mesh.userData.originalMaterial = mesh.material.clone();
|
|
||||||
mesh.material = mesh.material.clone();
|
|
||||||
mesh.material.color.set(0xffffcc);
|
|
||||||
mesh.material.opacity = 0.0;
|
|
||||||
mesh.material.emissive?.set(0xffffcc);
|
|
||||||
mesh.material.emissiveIntensity = 2.0;
|
|
||||||
this.spiritMeshes.push(mesh);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
s.rotation.x = -Math.PI;
|
|
||||||
this.grp.add(s);
|
|
||||||
});
|
});
|
||||||
|
scene.add(this.grp);
|
||||||
}
|
}
|
||||||
|
|
||||||
update(dt) {
|
update(dt) {
|
||||||
@@ -308,7 +303,8 @@
|
|||||||
|
|
||||||
// --- Haupt-Flow ---
|
// --- Haupt-Flow ---
|
||||||
(async()=>{
|
(async()=>{
|
||||||
await fetchSpirits();
|
await fetchSpiritList();
|
||||||
|
prepareNextSpirit();
|
||||||
landscape = await loadGLB(
|
landscape = await loadGLB(
|
||||||
'assets/models/landscape.glb',
|
'assets/models/landscape.glb',
|
||||||
[0,0,0], [90,0,0],
|
[0,0,0], [90,0,0],
|
||||||
@@ -331,6 +327,13 @@
|
|||||||
{receiveShadow:false, castShadow:true, shadowOnly: true}
|
{receiveShadow:false, castShadow:true, shadowOnly: true}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
function spawnSpirit(){
|
||||||
|
// Spawn exakt EIN Spirit, Modell ist garantiert geladen:
|
||||||
|
getAndPrepareSpiritInstance(spinnerRed.position.clone().add(new THREE.Vector3(0,-1.5,0))).then(obj=>{
|
||||||
|
spawned.push(new Spirit(obj));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function animate(){
|
function animate(){
|
||||||
const dt = clock.getDelta(), t = clock.getElapsedTime();
|
const dt = clock.getDelta(), t = clock.getElapsedTime();
|
||||||
|
|
||||||
@@ -365,7 +368,7 @@
|
|||||||
// Spawn Spirits
|
// Spawn Spirits
|
||||||
if (t - lastSpawn > SPAWN_INT) {
|
if (t - lastSpawn > SPAWN_INT) {
|
||||||
lastSpawn = t;
|
lastSpawn = t;
|
||||||
spawned.push(new Spirit(spinnerRed.position.clone().add(new THREE.Vector3(0,-1.5,0))));
|
spawnSpirit();
|
||||||
}
|
}
|
||||||
|
|
||||||
// update & cleanup
|
// update & cleanup
|
||||||
|
|||||||
Reference in New Issue
Block a user