diff --git a/packages/frontend-misskey-world-engine/src/EngineBase.ts b/packages/frontend-misskey-world-engine/src/EngineBase.ts index f8734dbf5e..77b8cd78ea 100644 --- a/packages/frontend-misskey-world-engine/src/EngineBase.ts +++ b/packages/frontend-misskey-world-engine/src/EngineBase.ts @@ -20,6 +20,7 @@ export abstract class EngineBase extends EventEmit protected engine: BABYLON.WebGPUEngine; public scene: BABYLON.Scene; + abstract sr: BABYLON.SnapshotRenderingHelper; protected fps: number | null = null; protected disposed = false; diff --git a/packages/frontend-misskey-world-engine/src/Firework.ts b/packages/frontend-misskey-world-engine/src/Firework.ts new file mode 100644 index 0000000000..4f19614c35 --- /dev/null +++ b/packages/frontend-misskey-world-engine/src/Firework.ts @@ -0,0 +1,121 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import * as BABYLON from '@babylonjs/core/pure.js'; +import { cm, WORLD_SCALE } from 'misskey-world/src/utility.js'; +import { randomRange, Timer } from './utility.js'; +import type { EngineBase } from './EngineBase.js'; + +export class Firework { + private engine: EngineBase; + private timer: Timer = new Timer(); + + constructor(engine: EngineBase) { + this.engine = engine; + } + + public launch(options: { + position: [number, number, number]; + }) { + this.engine.sr.disableSnapshotRendering(); + + const texture = new BABYLON.Texture(`/client-assets/world/other/firework/flare-${Math.round(randomRange(1, 8))}.png`, this.engine.scene); + + //const emitter = new BABYLON.TransformNode('emitter', this.engine.scene); + const emitter = BABYLON.MeshBuilder.CreateBox('emitter', { size: cm(10) }, this.engine.scene); + emitter.position = new BABYLON.Vector3(options.position[0], options.position[1], options.position[2]); + const ps = new BABYLON.ParticleSystem('', 128, this.engine.scene); + ps.particleTexture = texture; + ps.emitter = emitter; + ps.minEmitPower = cm(100); + ps.maxEmitPower = cm(500); + ps.minLifeTime = 0.5; + ps.maxLifeTime = 1; + ps.minSize = cm(3); + ps.maxSize = cm(30); + //ps.direction1 = new BABYLON.Vector3(0, 1, 0); + //ps.direction2 = new BABYLON.Vector3(0, 1, 0); + ps.emitRate = 20; + ps.blendMode = BABYLON.ParticleSystem.BLENDMODE_ADD; + //ps.color1 = new BABYLON.Color4(1, 1, 1, 0.3); + //ps.color2 = new BABYLON.Color4(1, 1, 1, 0.2); + ps.colorDead = new BABYLON.Color4(1, 1, 1, 0); + ps.start(); + + this.engine.sr.fixParticleSystem(ps); + + const launchAnim = new BABYLON.Animation( + '', + 'position', + 60, + BABYLON.Animation.ANIMATIONTYPE_VECTOR3, + BABYLON.Animation.ANIMATIONLOOPMODE_CONSTANT, + ); + launchAnim.setKeys([ + { frame: 0, value: new BABYLON.Vector3(options.position[0], options.position[1], options.position[2]) }, + { frame: 60, value: new BABYLON.Vector3(options.position[0], options.position[1] + cm(randomRange(1000, 3000)), options.position[2]) }, + ]); + + emitter.animations.push(launchAnim); + const animating = Promise.withResolvers(); + const animationObserver = this.engine.scene.onAfterAnimationsObservable.add(() => { + }); + this.engine.scene.beginAnimation(emitter, 0, 60, false, 1, () => { animating.resolve(); }); + animating.promise.then(() => { + this.engine.scene.onAfterAnimationsObservable.remove(animationObserver); + + ps.stop(); + + this.timer.setTimeout(() => { + ps.dispose(); + emitter.dispose(); + }, 1000); + + this.explode({ + position: [emitter.position.x, emitter.position.y, emitter.position.z], + texture, + }); + }); + + //this.engine.sr.enableSnapshotRendering(); + } + + public explode(options: { + position: [number, number, number]; + texture: BABYLON.Texture; + }) { + const ps = new BABYLON.ParticleSystem('', 128, this.engine.scene); + ps.particleTexture = options.texture; + ps.emitter = new BABYLON.Vector3(options.position[0], options.position[1], options.position[2]); + ps.minEmitPower = cm(2000); + ps.maxEmitPower = cm(4000); + ps.minLifeTime = 0.7; + ps.maxLifeTime = 1; + ps.addDragGradient(0, 0.1); + ps.addDragGradient(1, 0.8); + ps.gravity = new BABYLON.Vector3(0, -30, 0).scale(WORLD_SCALE); + ps.minSize = cm(20); + ps.maxSize = cm(40); + const sphereEmitter = ps.createSphereEmitter(cm(1)); + //ps.direction1 = new BABYLON.Vector3(0, 1, 0); + //ps.direction2 = new BABYLON.Vector3(0, 1, 0); + ps.manualEmitCount = 100; + ps.blendMode = BABYLON.ParticleSystem.BLENDMODE_ADD; + ps.color1 = new BABYLON.Color4(1, 1, 1, 1); + ps.color2 = new BABYLON.Color4(1, 1, 1, 1); + ps.colorDead = new BABYLON.Color4(1, 1, 1, 0); + ps.start(); + + this.engine.sr.fixParticleSystem(ps); + + this.timer.setTimeout(() => { + ps.dispose(); + }, 2000); + } + + public dispose() { + this.timer.dispose(); + } +} diff --git a/packages/frontend-misskey-world-engine/src/envs/lobby.ts b/packages/frontend-misskey-world-engine/src/envs/lobby.ts index a6c8c40602..b6a453208c 100644 --- a/packages/frontend-misskey-world-engine/src/envs/lobby.ts +++ b/packages/frontend-misskey-world-engine/src/envs/lobby.ts @@ -10,6 +10,7 @@ import Hls from 'hls.js'; import { findMaterial, GRAPHICS_QUALITY, Timer } from '../utility.js'; import { WorldEnvManager } from '../env.js'; import { RecyvlingTextGrid, createPlaneUvMapper, randomRange } from '../utility.js'; +import { Firework } from '../Firework.js'; import type { WorldEngine } from '../engine.js'; export class LobbyEnvManager extends WorldEnvManager { @@ -447,31 +448,37 @@ export class LobbyEnvManager extends WorldEnvManager { this.engine.sr.updateMesh([...hourHands, ...minuteHands], false); }, 1000); - const emitter = new BABYLON.TransformNode('emitter', this.engine.scene); - emitter.position = new BABYLON.Vector3(0, cm(-1000), 0); - const ps = new BABYLON.ParticleSystem('', 128, this.engine.scene); - ps.particleTexture = new BABYLON.Texture('/client-assets/world/envs/lobby/bubble.png'); - ps.emitter = emitter; - ps.isLocal = true; - ps.minEmitBox = new BABYLON.Vector3(cm(-1000), 0, cm(-1000)); - ps.maxEmitBox = new BABYLON.Vector3(cm(1000), 0, cm(1000)); - ps.minEmitPower = cm(100); - ps.maxEmitPower = cm(500); - ps.minLifeTime = 30; - ps.maxLifeTime = 30; - ps.minSize = cm(30); - ps.maxSize = cm(300); - ps.direction1 = new BABYLON.Vector3(0, 1, 0); - ps.direction2 = new BABYLON.Vector3(0, 1, 0); - ps.emitRate = 1.5; - ps.blendMode = BABYLON.ParticleSystem.BLENDMODE_ADD; - ps.color1 = new BABYLON.Color4(1, 1, 1, 0.3); - ps.color2 = new BABYLON.Color4(1, 1, 1, 0.2); - ps.colorDead = new BABYLON.Color4(1, 1, 1, 0); - ps.preWarmCycles = Math.random() * 1000; - ps.start(); + //const emitter = new BABYLON.TransformNode('emitter', this.engine.scene); + //emitter.position = new BABYLON.Vector3(0, cm(-1000), 0); + //const ps = new BABYLON.ParticleSystem('', 128, this.engine.scene); + //ps.particleTexture = new BABYLON.Texture('/client-assets/world/envs/lobby/bubble.png'); + //ps.emitter = emitter; + //ps.isLocal = true; + //ps.minEmitBox = new BABYLON.Vector3(cm(-1000), 0, cm(-1000)); + //ps.maxEmitBox = new BABYLON.Vector3(cm(1000), 0, cm(1000)); + //ps.minEmitPower = cm(100); + //ps.maxEmitPower = cm(500); + //ps.minLifeTime = 30; + //ps.maxLifeTime = 30; + //ps.minSize = cm(30); + //ps.maxSize = cm(300); + //ps.direction1 = new BABYLON.Vector3(0, 1, 0); + //ps.direction2 = new BABYLON.Vector3(0, 1, 0); + //ps.emitRate = 1.5; + //ps.blendMode = BABYLON.ParticleSystem.BLENDMODE_ADD; + //ps.color1 = new BABYLON.Color4(1, 1, 1, 0.3); + //ps.color2 = new BABYLON.Color4(1, 1, 1, 0.2); + //ps.colorDead = new BABYLON.Color4(1, 1, 1, 0); + //ps.preWarmCycles = Math.random() * 1000; + //ps.start(); + //this.engine.sr.fixParticleSystem(ps); - this.engine.sr.fixParticleSystem(ps); + this.timer.setInterval(() => { + const firework = new Firework(this.engine); + firework.launch({ + position: [randomRange(cm(-2000), cm(2000)), cm(randomRange(1000, 3000)), randomRange(cm(-2000), cm(2000))], + }); + }, 1000); this.registerMeshes(this.meshes); } diff --git a/packages/frontend/assets/world/other/firework/flare-1.png b/packages/frontend/assets/world/other/firework/flare-1.png new file mode 100644 index 0000000000..cb77aa9ce1 Binary files /dev/null and b/packages/frontend/assets/world/other/firework/flare-1.png differ diff --git a/packages/frontend/assets/world/other/firework/flare-2.png b/packages/frontend/assets/world/other/firework/flare-2.png new file mode 100644 index 0000000000..a1357badb2 Binary files /dev/null and b/packages/frontend/assets/world/other/firework/flare-2.png differ diff --git a/packages/frontend/assets/world/other/firework/flare-3.png b/packages/frontend/assets/world/other/firework/flare-3.png new file mode 100644 index 0000000000..b4c2eaaa35 Binary files /dev/null and b/packages/frontend/assets/world/other/firework/flare-3.png differ diff --git a/packages/frontend/assets/world/other/firework/flare-4.png b/packages/frontend/assets/world/other/firework/flare-4.png new file mode 100644 index 0000000000..8c073ac0e1 Binary files /dev/null and b/packages/frontend/assets/world/other/firework/flare-4.png differ diff --git a/packages/frontend/assets/world/other/firework/flare-5.png b/packages/frontend/assets/world/other/firework/flare-5.png new file mode 100644 index 0000000000..01a89aea55 Binary files /dev/null and b/packages/frontend/assets/world/other/firework/flare-5.png differ diff --git a/packages/frontend/assets/world/other/firework/flare-6.png b/packages/frontend/assets/world/other/firework/flare-6.png new file mode 100644 index 0000000000..cfe997ef8a Binary files /dev/null and b/packages/frontend/assets/world/other/firework/flare-6.png differ diff --git a/packages/frontend/assets/world/other/firework/flare-7.png b/packages/frontend/assets/world/other/firework/flare-7.png new file mode 100644 index 0000000000..a715ff28f0 Binary files /dev/null and b/packages/frontend/assets/world/other/firework/flare-7.png differ diff --git a/packages/frontend/assets/world/other/firework/flare-8.png b/packages/frontend/assets/world/other/firework/flare-8.png new file mode 100644 index 0000000000..004dd01d5b Binary files /dev/null and b/packages/frontend/assets/world/other/firework/flare-8.png differ