auto-git:
[change] index.html
This commit is contained in:
299
index.html
299
index.html
@@ -32,10 +32,15 @@
|
|||||||
import { DRACOLoader } from 'DRACOLoader';
|
import { DRACOLoader } from 'DRACOLoader';
|
||||||
import { EffectComposer } from 'https://cdn.jsdelivr.net/npm/three@0.155.0/examples/jsm/postprocessing/EffectComposer.js';
|
import { EffectComposer } from 'https://cdn.jsdelivr.net/npm/three@0.155.0/examples/jsm/postprocessing/EffectComposer.js';
|
||||||
import { RenderPass } from 'https://cdn.jsdelivr.net/npm/three@0.155.0/examples/jsm/postprocessing/RenderPass.js';
|
import { RenderPass } from 'https://cdn.jsdelivr.net/npm/three@0.155.0/examples/jsm/postprocessing/RenderPass.js';
|
||||||
import { ShaderPass } from 'https://cdn.jsdelivr.net/npm/three@0.155.0/examples/jsm/postprocessing/ShaderPass.js';
|
import { ShaderPass } from 'https://cdn.jsdelivr.net/npm/three@0.155.0/examples/jsm/postprocessing/ShaderPass.js'; // damit kann man auch easy webgl shader hier reinschreiben
|
||||||
import { UnrealBloomPass }from 'https://cdn.jsdelivr.net/npm/three@0.155.0/examples/jsm/postprocessing/UnrealBloomPass.js';
|
import { UnrealBloomPass } from 'https://cdn.jsdelivr.net/npm/three@0.155.0/examples/jsm/postprocessing/UnrealBloomPass.js'; // EPICCC ..
|
||||||
|
import { BrightnessContrastShader } from 'https://cdn.jsdelivr.net/npm/three@0.155.0/examples/jsm/shaders/BrightnessContrastShader.js';
|
||||||
import { GammaCorrectionShader } from 'https://cdn.jsdelivr.net/npm/three@0.155.0/examples/jsm/shaders/GammaCorrectionShader.js';
|
import { GammaCorrectionShader } from 'https://cdn.jsdelivr.net/npm/three@0.155.0/examples/jsm/shaders/GammaCorrectionShader.js';
|
||||||
|
import { SSAOPass } from 'https://cdn.jsdelivr.net/npm/three@0.155.0/examples/jsm/postprocessing/SSAOPass.js';
|
||||||
|
|
||||||
|
import { FilmPass } from "https://cdn.jsdelivr.net/npm/three@0.155.0/examples/jsm/postprocessing/FilmPass.js";
|
||||||
import { VignetteShader } from "https://cdn.jsdelivr.net/npm/three@0.155.0/examples/jsm/shaders/VignetteShader.js";
|
import { VignetteShader } from "https://cdn.jsdelivr.net/npm/three@0.155.0/examples/jsm/shaders/VignetteShader.js";
|
||||||
|
import { RGBShiftShader } from "https://cdn.jsdelivr.net/npm/three@0.155.0/examples/jsm/shaders/RGBShiftShader.js";
|
||||||
|
|
||||||
// --- Szene / Kamera / Renderer ---
|
// --- Szene / Kamera / Renderer ---
|
||||||
const scene = new THREE.Scene();
|
const scene = new THREE.Scene();
|
||||||
@@ -48,6 +53,7 @@
|
|||||||
const container = document.getElementById('viewer');
|
const container = document.getElementById('viewer');
|
||||||
const renderer = new THREE.WebGLRenderer({ antialias:true });
|
const renderer = new THREE.WebGLRenderer({ antialias:true });
|
||||||
renderer.outputColorSpace = THREE.SRGBColorSpace;
|
renderer.outputColorSpace = THREE.SRGBColorSpace;
|
||||||
|
|
||||||
renderer.shadowMap.enabled = true;
|
renderer.shadowMap.enabled = true;
|
||||||
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
||||||
container.appendChild(renderer.domElement);
|
container.appendChild(renderer.domElement);
|
||||||
@@ -55,11 +61,12 @@
|
|||||||
//Foliage-Shader
|
//Foliage-Shader
|
||||||
const foliageTexture = new THREE.TextureLoader().load('assets/sprites/foliage.png');
|
const foliageTexture = new THREE.TextureLoader().load('assets/sprites/foliage.png');
|
||||||
foliageTexture.colorSpace = THREE.SRGBColorSpace;
|
foliageTexture.colorSpace = THREE.SRGBColorSpace;
|
||||||
|
|
||||||
const FoliageOverlayShader = {
|
const FoliageOverlayShader = {
|
||||||
uniforms: {
|
uniforms: {
|
||||||
'tDiffuse': { value: null },
|
'tDiffuse': { value: null },
|
||||||
'tFoliage': { value: foliageTexture },
|
'tFoliage': { value: foliageTexture },
|
||||||
'opacity': { value: 1.0 }
|
'opacity': { value: 1.0 } // 0 = aus, 1 = voll
|
||||||
},
|
},
|
||||||
vertexShader: `
|
vertexShader: `
|
||||||
varying vec2 vUv;
|
varying vec2 vUv;
|
||||||
@@ -76,27 +83,81 @@
|
|||||||
void main() {
|
void main() {
|
||||||
vec4 base = texture2D(tDiffuse, vUv);
|
vec4 base = texture2D(tDiffuse, vUv);
|
||||||
vec4 foliage = texture2D(tFoliage, vUv);
|
vec4 foliage = texture2D(tFoliage, vUv);
|
||||||
|
// Normales Overlay: Foliage-Alpha mischt drüber
|
||||||
gl_FragColor = mix(base, vec4(foliage.rgb, base.a), foliage.a * opacity);
|
gl_FragColor = mix(base, vec4(foliage.rgb, base.a), foliage.a * opacity);
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
};
|
};
|
||||||
const foliageOverlayPass = new ShaderPass(FoliageOverlayShader);
|
|
||||||
|
|
||||||
// Postprocessing
|
const foliageOverlayPass = new ShaderPass(FoliageOverlayShader);
|
||||||
|
foliageOverlayPass.uniforms['tFoliage'].value = foliageTexture;
|
||||||
|
foliageOverlayPass.uniforms['opacity'].value = 1.0; // ggf. anpassen
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// post processing stack
|
||||||
const composer = new EffectComposer(renderer);
|
const composer = new EffectComposer(renderer);
|
||||||
|
|
||||||
|
//SSAO
|
||||||
|
const ssaoPass = new SSAOPass(scene, camera, container.clientWidth, container.clientHeight);
|
||||||
|
ssaoPass.kernelRadius = 16; // Radius des AO-Effekts (6-16 experimentieren!)
|
||||||
|
ssaoPass.minDistance = 0.05; // Wie nah am Geometriepunkt AO startet
|
||||||
|
ssaoPass.maxDistance = 0.2; // Maximalweite (0.1–0.3 sieht am realistischsten aus)
|
||||||
|
ssaoPass.output = SSAOPass.OUTPUT.Default; // Normal/Default
|
||||||
|
|
||||||
|
ssaoPass.bias = 0.05; // "Füllung" (0.01–0.1)
|
||||||
|
ssaoPass.aoClamp = 0.5; // Kontrast/Abdunklung (0.1–1.0)
|
||||||
|
ssaoPass.lumInfluence = 1; // Wie stark das Umgebungslicht AO beeinflusst
|
||||||
|
ssaoPass.intensity = 1.7; // Hauptstärke des Effekts (1.0–3.0!)
|
||||||
|
|
||||||
|
//BLOOM
|
||||||
|
const bloomPass = new UnrealBloomPass(
|
||||||
|
new THREE.Vector2(container.clientWidth, container.clientHeight),
|
||||||
|
0.8, // strength (1.0 ist gut, 0.3-2.0 experimentieren!)
|
||||||
|
0.2, // radius (0.5-1.0)
|
||||||
|
0.4 // threshold (alles, was heller als 0.0 ist, kann blühen)
|
||||||
|
);
|
||||||
|
|
||||||
|
//BRIGHTNESS-CONTRAST
|
||||||
|
const brightnessContrastPass = new ShaderPass(BrightnessContrastShader);
|
||||||
|
brightnessContrastPass.uniforms['brightness'].value = 0.25; // -1 bis +1
|
||||||
|
brightnessContrastPass.uniforms['contrast'].value = 0.51; // -1 bis +1 (0.2-0.7 sieht oft gut aus!)
|
||||||
|
|
||||||
|
//VIGNETTE-PASS
|
||||||
|
const vignettePass = new ShaderPass(VignetteShader);
|
||||||
|
vignettePass.uniforms['offset'].value = 0.3; // 1.0-2.0, experimentieren!
|
||||||
|
vignettePass.uniforms['darkness'].value = 1.35; // 1.0-2.5, je nach Mood
|
||||||
|
|
||||||
|
//RBG SHIFT (Chromatic Aberration)
|
||||||
|
const rgbShiftPass = new ShaderPass(RGBShiftShader);
|
||||||
|
rgbShiftPass.uniforms['amount'].value = 0.001; // Sehr subtil! (0.001–0.003)
|
||||||
|
|
||||||
|
//FILMPASS (Scanlines)
|
||||||
|
const filmPass = new FilmPass(
|
||||||
|
1.15, // noise intensity
|
||||||
|
0.125, // scanline intensity
|
||||||
|
600, // scanline count
|
||||||
|
false // grayscale
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
const gammaPass = new ShaderPass(GammaCorrectionShader);
|
||||||
|
|
||||||
|
|
||||||
composer.addPass(new RenderPass(scene, camera));
|
composer.addPass(new RenderPass(scene, camera));
|
||||||
composer.addPass(foliageOverlayPass);
|
composer.addPass(foliageOverlayPass);
|
||||||
composer.addPass(new UnrealBloomPass(
|
//composer.addPass(ssaoPass);
|
||||||
new THREE.Vector2(container.clientWidth, container.clientHeight),
|
composer.addPass(bloomPass);
|
||||||
0.8, 0.2, 0.4
|
//composer.addPass(brightnessContrastPass);
|
||||||
));
|
|
||||||
const vignettePass = new ShaderPass(VignetteShader);
|
|
||||||
vignettePass.uniforms['offset'].value = 0.3;
|
|
||||||
vignettePass.uniforms['darkness'].value = 1.35;
|
|
||||||
composer.addPass(vignettePass);
|
composer.addPass(vignettePass);
|
||||||
composer.addPass(new ShaderPass(GammaCorrectionShader));
|
composer.addPass(rgbShiftPass);
|
||||||
|
//composer.addPass(filmPass);
|
||||||
|
composer.addPass(gammaPass);
|
||||||
|
|
||||||
|
|
||||||
|
// end of postprocessing
|
||||||
|
|
||||||
|
|
||||||
// Resize-Handler
|
|
||||||
function onResize(){
|
function onResize(){
|
||||||
const W=container.clientWidth, H=container.clientHeight, winA=W/H;
|
const W=container.clientWidth, H=container.clientHeight, winA=W/H;
|
||||||
let vw,vh,vx,vy;
|
let vw,vh,vx,vy;
|
||||||
@@ -110,9 +171,10 @@
|
|||||||
renderer.setScissor(vx,vy,vw,vh);
|
renderer.setScissor(vx,vy,vw,vh);
|
||||||
renderer.setScissorTest(true);
|
renderer.setScissorTest(true);
|
||||||
renderer.toneMapping = THREE.ACESFilmicToneMapping;
|
renderer.toneMapping = THREE.ACESFilmicToneMapping;
|
||||||
renderer.toneMappingExposure = 1.2;
|
renderer.toneMappingExposure = 1.2; // Belichtung erhöhen/niedriger testen!
|
||||||
renderer.outputColorSpace = THREE.SRGBColorSpace;
|
renderer.outputColorSpace = THREE.SRGBColorSpace;
|
||||||
composer.setSize(W, H);
|
composer.setSize(W, H);
|
||||||
|
if (ssaoPass) ssaoPass.setSize(W, H);
|
||||||
const dpr = window.devicePixelRatio || 1;
|
const dpr = window.devicePixelRatio || 1;
|
||||||
renderer.setPixelRatio(dpr);
|
renderer.setPixelRatio(dpr);
|
||||||
composer.setPixelRatio(dpr);
|
composer.setPixelRatio(dpr);
|
||||||
@@ -124,7 +186,7 @@
|
|||||||
// --- HDRI Environment ---
|
// --- HDRI Environment ---
|
||||||
const texLoader = new THREE.TextureLoader();
|
const texLoader = new THREE.TextureLoader();
|
||||||
const pmremGen = new THREE.PMREMGenerator(renderer);
|
const pmremGen = new THREE.PMREMGenerator(renderer);
|
||||||
texLoader.load('assets/hdri/environment.jpg', tex => {
|
texLoader.load('assets/hdri/environment.png', tex => {
|
||||||
const envRT = pmremGen.fromEquirectangular(tex).texture;
|
const envRT = pmremGen.fromEquirectangular(tex).texture;
|
||||||
scene.environment = envRT;
|
scene.environment = envRT;
|
||||||
scene.background = envRT;
|
scene.background = envRT;
|
||||||
@@ -133,8 +195,11 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// --- Schatten-gebendes Licht ---
|
// --- Schatten-gebendes Licht ---
|
||||||
|
//scene.add(new THREE.AmbientLight(0xFFA230, 0.25));
|
||||||
const sun = new THREE.DirectionalLight(0xFFA230, 2);
|
const sun = new THREE.DirectionalLight(0xFFA230, 2);
|
||||||
sun.position.set(21, -25, 30);
|
sun.position.set(21, -25, 30);
|
||||||
|
//sun.position.set(10, -10, 20);
|
||||||
|
|
||||||
sun.castShadow = true;
|
sun.castShadow = true;
|
||||||
sun.shadow.mapSize.width = 2048;
|
sun.shadow.mapSize.width = 2048;
|
||||||
sun.shadow.mapSize.height = 2048;
|
sun.shadow.mapSize.height = 2048;
|
||||||
@@ -164,7 +229,7 @@
|
|||||||
.map(name=>`assets/models/spirits/${name}`);
|
.map(name=>`assets/models/spirits/${name}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- GLBs laden ---
|
// --- GLBs laden (kein Axis Swap) ---
|
||||||
async function loadGLB(path, pos, rotDeg, {receiveShadow=false, castShadow=false, emissive=null, visible=true, shadowOnly=false} = {}) {
|
async function loadGLB(path, pos, rotDeg, {receiveShadow=false, castShadow=false, emissive=null, visible=true, shadowOnly=false} = {}) {
|
||||||
const { scene: obj } = await gltfLoader.loadAsync(path);
|
const { scene: obj } = await gltfLoader.loadAsync(path);
|
||||||
obj.position.set(pos[0], pos[1], pos[2]);
|
obj.position.set(pos[0], pos[1], pos[2]);
|
||||||
@@ -174,7 +239,7 @@
|
|||||||
THREE.MathUtils.degToRad(rotDeg[2])
|
THREE.MathUtils.degToRad(rotDeg[2])
|
||||||
);
|
);
|
||||||
obj.traverse(c => {
|
obj.traverse(c => {
|
||||||
c.visible = visible;
|
c.visible = visible; // MUSS true sein, sonst kein Schatten!
|
||||||
if (c.isMesh) {
|
if (c.isMesh) {
|
||||||
c.castShadow = castShadow;
|
c.castShadow = castShadow;
|
||||||
c.receiveShadow = receiveShadow;
|
c.receiveShadow = receiveShadow;
|
||||||
@@ -189,16 +254,8 @@
|
|||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Shadow-Only-Material ---
|
|
||||||
const shadowOnlyMaterial = new THREE.MeshBasicMaterial({
|
|
||||||
color: 0x000000,
|
|
||||||
opacity: 0.01,
|
|
||||||
transparent: true,
|
|
||||||
depthWrite: false
|
|
||||||
});
|
|
||||||
|
|
||||||
// --- Variablen für Animation ---
|
// --- Variablen für Animation ---
|
||||||
let spinnerRed, spinnerBlue, torigate, landscape, shadowTree;
|
let spinnerRed, spinnerBlue, torigate, environment, shadowTree;
|
||||||
const clock = new THREE.Clock();
|
const clock = new THREE.Clock();
|
||||||
let lastSpawn = 0;
|
let lastSpawn = 0;
|
||||||
const SPAWN_INT = 5;
|
const SPAWN_INT = 5;
|
||||||
@@ -209,8 +266,9 @@
|
|||||||
const rotatingLights = [];
|
const rotatingLights = [];
|
||||||
const counterRotatingLights = [];
|
const counterRotatingLights = [];
|
||||||
const LIGHT_RADIUS = 1;
|
const LIGHT_RADIUS = 1;
|
||||||
|
|
||||||
for(let i=0;i<3;i++){
|
for(let i=0;i<3;i++){
|
||||||
const L = new THREE.PointLight(0xFFA230, 5, 30);
|
const L = new THREE.PointLight(0xFFA230, 5, 30); // Intensität runter, Reichweite hoch
|
||||||
L.castShadow = true;
|
L.castShadow = true;
|
||||||
rotatingLights.push(L);
|
rotatingLights.push(L);
|
||||||
scene.add(L);
|
scene.add(L);
|
||||||
@@ -221,17 +279,67 @@
|
|||||||
scene.add(L2);
|
scene.add(L2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Spirit-Klasse ohne Partikel & Sound ---
|
// Teleport-Flash-Material
|
||||||
|
const flashMaterial = new THREE.MeshBasicMaterial({
|
||||||
|
color: 0xffffcc,
|
||||||
|
transparent: true,
|
||||||
|
opacity: 0.95,
|
||||||
|
blending: THREE.AdditiveBlending,
|
||||||
|
depthWrite: false
|
||||||
|
});
|
||||||
|
|
||||||
|
// Shadow-Only-Material
|
||||||
|
const shadowOnlyMaterial = new THREE.MeshBasicMaterial({
|
||||||
|
color: 0x000000,
|
||||||
|
opacity: 0.01,
|
||||||
|
transparent: true,
|
||||||
|
depthWrite: false
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// === Partikel-Textur vorbereiten (einfacher weißer Kreis) ===
|
||||||
|
const particleTexture = new THREE.TextureLoader().load('assets/sprites/particle.png');
|
||||||
|
particleTexture.colorSpace = THREE.SRGBColorSpace;
|
||||||
|
// === Audio vorbereiten ===
|
||||||
|
/*
|
||||||
|
const listener = new THREE.AudioListener();
|
||||||
|
camera.add(listener);
|
||||||
|
const soundBufferPromise = fetch('assets/sound/spawn.ogg')
|
||||||
|
.then(r=>r.arrayBuffer())
|
||||||
|
.then(buf=>new Promise(res=>{
|
||||||
|
const audio = new AudioContext();
|
||||||
|
audio.decodeAudioData(buf, res);
|
||||||
|
}));
|
||||||
|
*/
|
||||||
|
|
||||||
|
// === Neue Spirit-Klasse ===
|
||||||
class Spirit {
|
class Spirit {
|
||||||
constructor(position) {
|
constructor(position) {
|
||||||
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; // Für den Material-Überblend
|
||||||
|
// Positionieren (auf Boden, dann +1m für Partikeleffekt)
|
||||||
this.grp.position.copy(position);
|
this.grp.position.copy(position);
|
||||||
this.grp.position.z -= 0.6;
|
this.grp.position.z -= 0.6;
|
||||||
scene.add(this.grp);
|
scene.add(this.grp);
|
||||||
|
|
||||||
|
// Partikel-Spawn
|
||||||
|
this.particles = this.makeParticles();
|
||||||
|
this.particles.position.y = 1.0; // Mitte Spirit
|
||||||
|
this.grp.add(this.particles);
|
||||||
|
|
||||||
|
// Sound abspielen (async, aber egal)
|
||||||
|
/*
|
||||||
|
soundBufferPromise.then(buffer => {
|
||||||
|
const sfx = new THREE.Audio(listener);
|
||||||
|
sfx.setBuffer(buffer);
|
||||||
|
sfx.setVolume(0.7);
|
||||||
|
sfx.play();
|
||||||
|
// Sound ist "virtuell" an Spirit-Gruppe gebunden, keine Entfernungsmischung
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
// Laden Spirit-GLB
|
// Laden Spirit-GLB
|
||||||
const path = spiritModels[Math.floor(Math.random()*spiritModels.length)];
|
const path = spiritModels[Math.floor(Math.random()*spiritModels.length)];
|
||||||
gltfLoader.load(path, ({ scene: s }) => {
|
gltfLoader.load(path, ({ scene: s }) => {
|
||||||
@@ -239,11 +347,14 @@
|
|||||||
if(mesh.isMesh){
|
if(mesh.isMesh){
|
||||||
mesh.castShadow = true;
|
mesh.castShadow = true;
|
||||||
mesh.receiveShadow = true;
|
mesh.receiveShadow = true;
|
||||||
|
// Originalmaterial speichern
|
||||||
mesh.userData.originalMaterial = mesh.material.clone();
|
mesh.userData.originalMaterial = mesh.material.clone();
|
||||||
mesh.material = mesh.material.clone();
|
mesh.material = mesh.material.clone();
|
||||||
|
// Direkt auf Flash-Farbe setzen, transparent!
|
||||||
mesh.material.color.set(0xffffcc);
|
mesh.material.color.set(0xffffcc);
|
||||||
|
//mesh.material.transparent = true;
|
||||||
mesh.material.opacity = 0.0;
|
mesh.material.opacity = 0.0;
|
||||||
mesh.material.emissive?.set(0xffffcc);
|
mesh.material.emissive?.set(0xffffcc); // Falls vorhanden
|
||||||
mesh.material.emissiveIntensity = 2.0;
|
mesh.material.emissiveIntensity = 2.0;
|
||||||
this.spiritMeshes.push(mesh);
|
this.spiritMeshes.push(mesh);
|
||||||
}
|
}
|
||||||
@@ -253,8 +364,67 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// === Partikel-Effekt erstellen ===
|
||||||
|
makeParticles() {
|
||||||
|
const N = 36; // Partikelanzahl
|
||||||
|
const geom = new THREE.BufferGeometry();
|
||||||
|
const positions = [];
|
||||||
|
const velocities = [];
|
||||||
|
for(let i=0; i<N; i++){
|
||||||
|
// Punkte zufällig in Halb-Kugel, random Richtung nach oben
|
||||||
|
const theta = Math.random()*2*Math.PI;
|
||||||
|
const phi = Math.random()*Math.PI/1.7;
|
||||||
|
const r = Math.random()*0.2+0.5;
|
||||||
|
const x = Math.sin(phi)*Math.cos(theta)*r;
|
||||||
|
const y = Math.abs(Math.cos(phi))*r; // <-- Y ist nach oben
|
||||||
|
const z = Math.sin(phi)*Math.sin(theta)*r;
|
||||||
|
positions.push(x, y, z);
|
||||||
|
velocities.push(x*2, y*2.5+0.7, z*2);
|
||||||
|
}
|
||||||
|
geom.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
|
||||||
|
geom.setAttribute('velocity', new THREE.Float32BufferAttribute(velocities, 3));
|
||||||
|
const mat = new THREE.PointsMaterial({
|
||||||
|
size: 1.3,
|
||||||
|
color: 0xffffff,
|
||||||
|
map: particleTexture,
|
||||||
|
blending: THREE.AdditiveBlending,
|
||||||
|
alphaTest: 0.15,
|
||||||
|
depthWrite: false,
|
||||||
|
transparent: true,
|
||||||
|
opacity: 1.0
|
||||||
|
});
|
||||||
|
const pts = new THREE.Points(geom, mat);
|
||||||
|
pts.userData.lifetime = 0.7 + Math.random()*0.13;
|
||||||
|
pts.userData.age = 0;
|
||||||
|
return pts;
|
||||||
|
}
|
||||||
|
|
||||||
update(dt) {
|
update(dt) {
|
||||||
const t = this.clock.getElapsedTime();
|
const t = this.clock.getElapsedTime();
|
||||||
|
|
||||||
|
// === Partikel animieren und ggf. entfernen ===
|
||||||
|
if(this.particles){
|
||||||
|
this.particles.userData.age += dt;
|
||||||
|
const pos = this.particles.geometry.attributes.position;
|
||||||
|
const vel = this.particles.geometry.attributes.velocity;
|
||||||
|
for(let i=0; i<pos.count; i++){
|
||||||
|
// Linear nach außen
|
||||||
|
pos.array[3*i+0] += vel.array[3*i+0]*dt;
|
||||||
|
pos.array[3*i+1] += vel.array[3*i+1]*dt;
|
||||||
|
pos.array[3*i+2] += vel.array[3*i+2]*dt;
|
||||||
|
}
|
||||||
|
pos.needsUpdate = true;
|
||||||
|
// Transparenz schnell ausfaden
|
||||||
|
this.particles.material.opacity = 0.7 * (1.0 - this.particles.userData.age/this.particles.userData.lifetime);
|
||||||
|
if(this.particles.userData.age > this.particles.userData.lifetime){
|
||||||
|
this.grp.remove(this.particles);
|
||||||
|
this.particles.geometry.dispose();
|
||||||
|
this.particles.material.dispose();
|
||||||
|
this.particles = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Spirit-Meshes aus dem Licht in Textur/Originalfarbe blenden ===
|
||||||
if(this.spiritMeshes && this.isFading){
|
if(this.spiritMeshes && this.isFading){
|
||||||
for(const mesh of this.spiritMeshes){
|
for(const mesh of this.spiritMeshes){
|
||||||
if(t < 0.5){
|
if(t < 0.5){
|
||||||
@@ -269,10 +439,12 @@
|
|||||||
if(mesh.material.emissive)
|
if(mesh.material.emissive)
|
||||||
mesh.material.emissive.copy(mesh.userData.originalMaterial.emissive || new THREE.Color(0x000000));
|
mesh.material.emissive.copy(mesh.userData.originalMaterial.emissive || new THREE.Color(0x000000));
|
||||||
mesh.material.emissiveIntensity = mesh.userData.originalMaterial.emissiveIntensity ?? 1.0;
|
mesh.material.emissiveIntensity = mesh.userData.originalMaterial.emissiveIntensity ?? 1.0;
|
||||||
this.isFading = false;
|
this.isFading = false; // fertig
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// === Bewegung nach unten ===
|
||||||
this.grp.position.y -= MOVE_SPEED * dt;
|
this.grp.position.y -= MOVE_SPEED * dt;
|
||||||
if (t > 15) {
|
if (t > 15) {
|
||||||
scene.remove(this.grp);
|
scene.remove(this.grp);
|
||||||
@@ -282,6 +454,31 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function spawnPair(){
|
||||||
|
// Spirit
|
||||||
|
const sp = new Spirit(spinnerRed.position.clone().add(new THREE.Vector3(0,-1.5,0)));
|
||||||
|
spawned.push(sp);
|
||||||
|
// Light
|
||||||
|
/*
|
||||||
|
const pl = new THREE.PointLight(0xffe0b3,0,0);
|
||||||
|
pl.position.copy(spinnerRed.position).add(new THREE.Vector3(0,-2,0.5));
|
||||||
|
pl.clock = new THREE.Clock();
|
||||||
|
pl.update = function(dt){
|
||||||
|
this.position.y -= MOVE_SPEED*dt;
|
||||||
|
if(this.clock.getElapsedTime()>20){
|
||||||
|
scene.remove(this);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
scene.add(pl);
|
||||||
|
spawned.push(pl);
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
// --- Spinner (mit Transparenz) ---
|
// --- Spinner (mit Transparenz) ---
|
||||||
async function loadSpinner(path, pos, rotDeg, color, opacity) {
|
async function loadSpinner(path, pos, rotDeg, color, opacity) {
|
||||||
const { scene: obj } = await gltfLoader.loadAsync(path);
|
const { scene: obj } = await gltfLoader.loadAsync(path);
|
||||||
@@ -296,8 +493,10 @@
|
|||||||
if (c.isMesh && c.material && c.material.isMeshStandardMaterial) {
|
if (c.isMesh && c.material && c.material.isMeshStandardMaterial) {
|
||||||
c.material.transparent = true;
|
c.material.transparent = true;
|
||||||
c.material.opacity = opacity;
|
c.material.opacity = opacity;
|
||||||
|
// <- Das hier ist NEU!
|
||||||
c.material.emissive = new THREE.Color(color);
|
c.material.emissive = new THREE.Color(color);
|
||||||
c.material.emissiveIntensity = 3.0;
|
c.material.emissiveIntensity = 3.0; // experimentier ruhig mit höheren Werten!
|
||||||
|
// <- Optional: VertexColors aktivieren, falls du sie willst
|
||||||
c.material.vertexColors = true;
|
c.material.vertexColors = true;
|
||||||
c.castShadow = true;
|
c.castShadow = true;
|
||||||
}
|
}
|
||||||
@@ -309,42 +508,53 @@
|
|||||||
// --- Haupt-Flow ---
|
// --- Haupt-Flow ---
|
||||||
(async()=>{
|
(async()=>{
|
||||||
await fetchSpirits();
|
await fetchSpirits();
|
||||||
landscape = await loadGLB(
|
|
||||||
'assets/models/landscape.glb',
|
// Environment (empfängt Schatten)
|
||||||
|
environment = await loadGLB(
|
||||||
|
'assets/models/environment.glb',
|
||||||
[0,0,0], [90,0,0],
|
[0,0,0], [90,0,0],
|
||||||
{receiveShadow:true, castShadow:true}
|
{receiveShadow:true, castShadow:true}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Tori Gate
|
||||||
torigate = await loadGLB(
|
torigate = await loadGLB(
|
||||||
'assets/models/tori.glb',
|
'assets/models/torigate.glb',
|
||||||
[0,6.59,0.375], [90,0,0],
|
[0,6.59,0.375], [90,0,0],
|
||||||
{receiveShadow:true, castShadow:true}
|
{receiveShadow:true, castShadow:true}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Spinner rot (z.B. models/spinner_red.glb) & Spinner blau (z.B. spinner_blue.glb)
|
||||||
spinnerRed = await loadSpinner(
|
spinnerRed = await loadSpinner(
|
||||||
'assets/models/spinner_red.glb', [0,16.55,0.88], [90,0,0], "#ff3333", 0.2
|
'assets/models/spinner_red.glb', [0,16.55,0.88], [90,0,0], "#ff3333", 0.2
|
||||||
);
|
);
|
||||||
spinnerBlue = await loadSpinner(
|
spinnerBlue = await loadSpinner(
|
||||||
'assets/models/spinner_blue.glb', [0,16.55,0.88], [90,0,0], "#3380ff", 0.2
|
'assets/models/spinner_blue.glb', [0,16.55,0.88], [90,0,0], "#3380ff", 0.2
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// --- Low Poly Bäume als unsichtbare Schattenobjekte einbauen:
|
||||||
shadowTree = await loadGLB(
|
shadowTree = await loadGLB(
|
||||||
'assets/models/tree_low.glb',
|
'assets/models/tree_low.glb',
|
||||||
[0.0,0.0,0.0], [90,0,0],
|
[0.0,0.0,0.0], [90,0,0],
|
||||||
{receiveShadow:false, castShadow:true, shadowOnly: true}
|
{receiveShadow:false, castShadow:true, shadowOnly: true}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// --- Animation / Render-Loop ---
|
||||||
function animate(){
|
function animate(){
|
||||||
const dt = clock.getDelta(), t = clock.getElapsedTime();
|
const dt = clock.getDelta(), t = clock.getElapsedTime();
|
||||||
|
|
||||||
// Spinner Animation
|
// Spinner Animation: Rotation & Schweben
|
||||||
const bob = Math.sin(t*1.2)*0.5;
|
const bob = Math.sin(t*1.2)*0.5; // sanftes Schweben
|
||||||
const baseY = 16.55 + bob;
|
const baseY = 16.55 + bob;
|
||||||
spinnerRed.position.y = baseY + 0.8;
|
spinnerRed.position.y = baseY + 0.8;
|
||||||
spinnerBlue.position.y = baseY;
|
spinnerBlue.position.y = baseY;
|
||||||
spinnerRed.rotation.y -= 1.2 * dt;
|
|
||||||
spinnerBlue.rotation.y += 1.2 * dt;
|
spinnerRed.rotation.y -= 1.2 * dt; // Uhrzeigersinn
|
||||||
|
spinnerBlue.rotation.y += 1.2 * dt; // Gegen-Uhrzeigersinn
|
||||||
|
|
||||||
// Rotierende Lichter
|
// Rotierende Lichter
|
||||||
const center = new THREE.Vector3(0, 16.55, 1.5);
|
const center = new THREE.Vector3(0, 16.55, 1.5);
|
||||||
const lightZ = center.z;
|
const lightZ = center.z; // Fix auf Z wie Spinner
|
||||||
|
// Normale Richtung (wie bisher)
|
||||||
for(let i=0; i<rotatingLights.length; i++){
|
for(let i=0; i<rotatingLights.length; i++){
|
||||||
const ang = t * 0.8 + i * 2 * Math.PI / 3;
|
const ang = t * 0.8 + i * 2 * Math.PI / 3;
|
||||||
rotatingLights[i].position.set(
|
rotatingLights[i].position.set(
|
||||||
@@ -353,6 +563,8 @@
|
|||||||
lightZ
|
lightZ
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Gegendrehende Lichter:
|
||||||
for(let i=0; i<counterRotatingLights.length; i++){
|
for(let i=0; i<counterRotatingLights.length; i++){
|
||||||
const ang = -t * 0.8 + i * 2 * Math.PI / 3;
|
const ang = -t * 0.8 + i * 2 * Math.PI / 3;
|
||||||
counterRotatingLights[i].position.set(
|
counterRotatingLights[i].position.set(
|
||||||
@@ -361,11 +573,10 @@
|
|||||||
lightZ
|
lightZ
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// Spawn Spirits/Lichter
|
||||||
// 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))));
|
spawnPair();
|
||||||
}
|
}
|
||||||
|
|
||||||
// update & cleanup
|
// update & cleanup
|
||||||
|
|||||||
Reference in New Issue
Block a user