Refactor SpinnerController class and improve WebSocket handling

This commit is contained in:
2025-05-28 03:36:39 +02:00
parent 853d7a8b70
commit af62625a54

View File

@@ -97,129 +97,118 @@ draco.setDecoderPath('https://www.gstatic.com/draco/versioned/decoders/1.5.6/');
const gltfLoader = new GLTFLoader(); const gltfLoader = new GLTFLoader();
gltfLoader.setDRACOLoader(draco); gltfLoader.setDRACOLoader(draco);
// ---- SpinnerController ---- // ---- Spinner Controller-Klasse ----
class SpinnerController { class SpinnerController {
constructor(scene) { constructor(scene) {
// Grundobjekte this.scene = scene;
this.scene = scene; this.spinnerRed = null;
this.spinnerRed = null; this.spinnerBlue = null;
this.spinnerBlue = null; this.lights = [];
this.rotatingLights = []; this.counterLights = [];
this.counterRotatingLights = []; this.center = new THREE.Vector3(0, 16.55, 1.5);
this.LIGHT_RADIUS = 1; this.LIGHT_RADIUS = 1;
this.baseY = 16.55; this.baseY = 16.55;
this.connected = false; this.clock = new THREE.Clock();
this.connectionLostSince = null; this.ws = null;
this.reconnectDelay = 2000;
// Create Spinner, Lights etc. this.init();
this.init();
// Setup WebSocket (mit reconnect)
this.connect();
}
async init() {
// Spinner laden
this.spinnerRed = await loadSpinner(
'assets/models/spinner_red.glb', [0, this.baseY, 0.88], [90, 0, 0], "#ff3333", 0.2
);
this.spinnerBlue = await loadSpinner(
'assets/models/spinner_blue.glb', [0, this.baseY, 0.88], [90, 0, 0], "#3380ff", 0.2
);
// 6 Lichter
for (let i = 0; i < 3; i++) {
const L = new THREE.PointLight(0xFFA230, 5, 30);
L.castShadow = true;
this.rotatingLights.push(L);
scene.add(L);
const L2 = new THREE.PointLight(0xFFA230, 5, 30);
L2.castShadow = true;
this.counterRotatingLights.push(L2);
scene.add(L2);
} }
}
connect() { async init() {
if (this.ws) this.ws.close(); // Lade beide Spinner
const ws = new WebSocket(`ws://${location.host}`); this.spinnerRed = await this.loadSpinner('assets/models/spinner_red.glb', [0, 16.55, 0.88], [90, 0, 0], "#ff3333", 0.2);
this.ws = ws; this.spinnerBlue = await this.loadSpinner('assets/models/spinner_blue.glb', [0, 16.55, 0.88], [90, 0, 0], "#3380ff", 0.2);
ws.onopen = () => {
this.connected = true;
this.connectionLostSince = null;
// Spinner wird wieder "schnell und hell"
};
ws.onclose = () => {
this.connected = false;
this.connectionLostSince = Date.now();
// Versuche nach 2 Sekunden erneut zu verbinden
setTimeout(() => this.connect(), 2000);
};
ws.onerror = () => {
ws.close();
};
ws.onmessage = async (event) => {
const msg = JSON.parse(event.data);
if (msg.type === 'spirit') {
spawnSpirit(msg.data); // neue Funktion, siehe unten!
}
};
}
update(dt, t) { // Init rotierende Lichter
// Werte je nach Connection for (let i = 0; i < 3; i++) {
let bobFactor = this.connected ? 0.5 : 0.13; const L = new THREE.PointLight(0xFFA230, 5, 30);
let spinSpeed = this.connected ? 1.2 : 0.25; L.castShadow = true;
let lightSpeed = this.connected ? 0.8 : 0.18; this.lights.push(L);
let lightInt = this.connected ? 5 : 0.7; this.scene.add(L);
let emissiveInt = this.connected ? 3.0 : 0.15;
// Spinner Bobbing const L2 = new THREE.PointLight(0xFFA230, 5, 30);
const bob = Math.sin(t * 1.2) * bobFactor; L2.castShadow = true;
const yRed = this.baseY + bob + 0.8; this.counterLights.push(L2);
const yBlue = this.baseY + bob; this.scene.add(L2);
if (this.spinnerRed && this.spinnerBlue) {
this.spinnerRed.position.y = yRed;
this.spinnerBlue.position.y = yBlue;
this.spinnerRed.rotation.y -= spinSpeed * dt;
this.spinnerBlue.rotation.y += spinSpeed * dt;
// Emissive dunkler/heller
this.spinnerRed.traverse(obj => {
if (obj.isMesh && obj.material && obj.material.isMeshStandardMaterial) {
obj.material.emissiveIntensity = emissiveInt;
} }
});
this.spinnerBlue.traverse(obj => { this.connectWebSocket();
if (obj.isMesh && obj.material && obj.material.isMeshStandardMaterial) {
obj.material.emissiveIntensity = emissiveInt;
}
});
} }
// Rotierende Lichter async loadSpinner(path, pos, rotDeg, color, opacity) {
const center = new THREE.Vector3(0, this.baseY, 1.5); const { scene: obj } = await gltfLoader.loadAsync(path);
const lightZ = center.z; obj.position.set(...pos);
for (let i = 0; i < this.rotatingLights.length; i++) { obj.rotation.set(
const ang = t * lightSpeed + i * 2 * Math.PI / 3; THREE.MathUtils.degToRad(rotDeg[0]),
const L = this.rotatingLights[i]; THREE.MathUtils.degToRad(rotDeg[1]),
L.intensity = lightInt; THREE.MathUtils.degToRad(rotDeg[2])
L.position.set( );
center.x + Math.cos(ang) * this.LIGHT_RADIUS, obj.traverse(c => {
center.y + Math.sin(ang) * this.LIGHT_RADIUS, c.visible = true;
lightZ if (c.isMesh && c.material && c.material.isMeshStandardMaterial) {
); c.material.transparent = true;
c.material.opacity = opacity;
c.material.emissive = new THREE.Color(color);
c.material.emissiveIntensity = 3.0;
c.material.vertexColors = true;
c.castShadow = true;
}
});
this.scene.add(obj);
return obj;
} }
for (let i = 0; i < this.counterRotatingLights.length; i++) {
const ang = -t * lightSpeed + i * 2 * Math.PI / 3; animate(dt, t) {
const L2 = this.counterRotatingLights[i]; // Spinner Animation
L2.intensity = lightInt; const bob = Math.sin(t * 1.2) * 0.5;
L2.position.set( const baseY = this.baseY + bob;
center.x + Math.cos(ang) * this.LIGHT_RADIUS, if (this.spinnerRed && this.spinnerBlue) {
center.y + Math.sin(ang) * this.LIGHT_RADIUS, this.spinnerRed.position.y = baseY + 0.8;
lightZ this.spinnerBlue.position.y = baseY;
); this.spinnerRed.rotation.y -= 1.2 * dt;
this.spinnerBlue.rotation.y += 1.2 * dt;
}
// Rotierende Lichter
for (let i = 0; i < this.lights.length; i++) {
const ang = t * 0.8 + i * 2 * Math.PI / 3;
this.lights[i].position.set(
this.center.x + Math.cos(ang) * this.LIGHT_RADIUS,
this.center.y + Math.sin(ang) * this.LIGHT_RADIUS,
this.center.z
);
}
for (let i = 0; i < this.counterLights.length; i++) {
const ang = -t * 0.8 + i * 2 * Math.PI / 3;
this.counterLights[i].position.set(
this.center.x + Math.cos(ang) * this.LIGHT_RADIUS,
this.center.y + Math.sin(ang) * this.LIGHT_RADIUS,
this.center.z
);
}
}
connectWebSocket() {
let self = this;
if (self.ws) self.ws.close();
self.ws = new WebSocket(`ws://${location.host}`);
self.ws.addEventListener('open', () => {
console.log("WebSocket connected!");
});
self.ws.addEventListener('message', async (event) => {
const msg = JSON.parse(event.data);
if (msg.type === 'spirit') {
spawnSpirit(msg.data);
}
});
self.ws.addEventListener('close', () => {
console.warn("WebSocket closed. Reconnecting in " + this.reconnectDelay / 1000 + "s...");
setTimeout(() => self.connectWebSocket(), self.reconnectDelay);
});
self.ws.addEventListener('error', (e) => {
console.error("WebSocket error", e);
self.ws.close();
});
} }
}
} }
// ---- Spirit-Klasse ---- // ---- Spirit-Klasse ----