diff --git a/packages/frontend-misskey-world-engine/src/EngineBase.ts b/packages/frontend-misskey-world-engine/src/EngineBase.ts index 772ecdf557..173d16f68f 100644 --- a/packages/frontend-misskey-world-engine/src/EngineBase.ts +++ b/packages/frontend-misskey-world-engine/src/EngineBase.ts @@ -5,9 +5,6 @@ import * as BABYLON from '@babylonjs/core/pure.js'; import EventEmitter from 'eventemitter3'; -import { PlayerContainer, type PlayerProfile, type PlayerState } from './PlayerContainer.js'; - -// TODO: multiplayer関連は全ての子クラスで必要とは限らない(preview用など)ため、このクラスを継承する別のabstract classに分離 const IN_WEB_WORKER = typeof window === 'undefined'; @@ -29,11 +26,6 @@ export abstract class EngineBase extends EventEmit protected fps: number | null = null; protected disposed = false; - protected playerProfiles: Record = {}; - protected playerContainers: PlayerContainer[] = []; - protected showUsernameOnAvatar: boolean; - protected show2dAvatarOnAvatar: boolean; - public inputs: EventEmitter<{ 'click': (event: { x: number; y: number; }) => void; 'keydown': (event: { code: string; shiftKey: boolean; }) => void; @@ -123,105 +115,6 @@ export abstract class EngineBase extends EventEmit public abstract resize(): void; - public updatePlayerProfiles(profiles: Record) { - this.playerProfiles = profiles; - - for (const playerContainer of this.playerContainers) { - if (this.playerProfiles[playerContainer.id] == null) { - this.sr.disableSnapshotRendering(); - playerContainer.destroy(); - this.sr.enableSnapshotRendering(); - } - } - this.playerContainers = this.playerContainers.filter(p => this.playerProfiles[p.id] != null); - } - - public updatePlayerStates(states: Record) { - for (const [k, v] of Object.entries(this.playerProfiles)) { - const playerContainer = this.playerContainers.find(p => p.id === k); - if (playerContainer == null) { - const p = new PlayerContainer({ - id: k, - profile: v, - state: states[k], - scene: this.scene, - sr: this.sr, - showUsername: this.showUsernameOnAvatar, - show2dAvatar: this.show2dAvatarOnAvatar, - }); - // TODO: loadFurnitureのものとある程度共通化 - p.registerMeshes = (meshes) => { - for (const mesh of meshes) { - mesh.receiveShadows = false; - mesh.metadata = { isPlayer: true, playerId: k }; - - //if (mesh.material) (mesh.material as BABYLON.PBRMaterial).ambientColor = new BABYLON.Color3(0.2, 0.2, 0.2); - if (mesh.material) { - if (mesh.material instanceof BABYLON.MultiMaterial) { - for (const subMat of mesh.material.subMaterials) { - if ((subMat as BABYLON.PBRMaterial).subSurface.isRefractionEnabled) { - (subMat as BABYLON.PBRMaterial).subSurface.isRefractionEnabled = false; // 有効にするとドローコールが激増する - (subMat as BABYLON.PBRMaterial).transparencyMode = BABYLON.PBRMaterial.PBRMATERIAL_ALPHABLEND; - (subMat as BABYLON.PBRMaterial).alpha = 0.5; - (subMat as BABYLON.PBRMaterial).metallic = 1; - } - (subMat as BABYLON.PBRMaterial).reflectionTexture = this.getEnvMap(); - if ((subMat as BABYLON.PBRMaterial).metadata == null) (subMat as BABYLON.PBRMaterial).metadata = {}; - (subMat as BABYLON.PBRMaterial).metadata.useEnvMap = true; - (subMat as BABYLON.PBRMaterial).useGLTFLightFalloff = true; // Clustered Lightingではphysical falloffを持つマテリアルはアーチファクトが発生する https://doc.babylonjs.com/features/featuresDeepDive/lights/clusteredLighting/#materials-with-a-physical-falloff-may-cause-artefacts - (subMat as BABYLON.PBRMaterial).anisotropy.isEnabled = false; // なんかきれいにレンダリングされないため - } - } else { - if ((mesh.material as BABYLON.PBRMaterial).subSurface.isRefractionEnabled) { - (mesh.material as BABYLON.PBRMaterial).subSurface.isRefractionEnabled = false; // 有効にするとドローコールが激増する - (mesh.material as BABYLON.PBRMaterial).transparencyMode = BABYLON.PBRMaterial.PBRMATERIAL_ALPHABLEND; - (mesh.material as BABYLON.PBRMaterial).alpha = 0.5; - (mesh.material as BABYLON.PBRMaterial).metallic = 1; - } - (mesh.material as BABYLON.PBRMaterial).reflectionTexture = this.getEnvMap(); - if ((mesh.material as BABYLON.PBRMaterial).metadata == null) (mesh.material as BABYLON.PBRMaterial).metadata = {}; - (mesh.material as BABYLON.PBRMaterial).metadata.useEnvMap = true; - (mesh.material as BABYLON.PBRMaterial).useGLTFLightFalloff = true; // Clustered Lightingではphysical falloffを持つマテリアルはアーチファクトが発生する https://doc.babylonjs.com/features/featuresDeepDive/lights/clusteredLighting/#materials-with-a-physical-falloff-may-cause-artefacts - (mesh.material as BABYLON.PBRMaterial).anisotropy.isEnabled = false; // なんかきれいにレンダリングされないため - } - } - - if (!this.scene.meshes.includes(mesh)) this.scene.addMesh(mesh); - } - }; - p.loadAvatar().then(() => { - this.sr.disableSnapshotRendering(); - this.sr.enableSnapshotRendering(); - }); - this.playerContainers.push(p); - } else { - if (states[k] != null) { - playerContainer.applyState(states[k]); - } - } - } - } - - public clearPlayers() { - this.sr.disableSnapshotRendering(); - for (const playerContainer of this.playerContainers) { - playerContainer.destroy(); - } - this.sr.enableSnapshotRendering(); - this.playerContainers = []; - } - - public updateAvatarDisplayOptions(options: { showUsername: boolean; show2dAvatar: boolean }) { - this.showUsernameOnAvatar = options.showUsername; - this.show2dAvatarOnAvatar = options.show2dAvatar; - - this.sr.disableSnapshotRendering(); - for (const playerContainer of this.playerContainers) { - playerContainer.updateUserInfoDisplayOptions(options); - } - this.sr.enableSnapshotRendering(); - } - public destroy() { this.babylonEngine.stopRenderLoop(); if (this.currentRafId != null) { @@ -229,9 +122,6 @@ export abstract class EngineBase extends EventEmit cancelAnimationFrame(this.currentRafId); this.currentRafId = null; } - for (const playerContainer of this.playerContainers) { - playerContainer.destroy(); - } this.babylonEngine.dispose(); this.scene.dispose(); this.disposed = true; diff --git a/packages/frontend-misskey-world-engine/src/MultiplayEngineBase.ts b/packages/frontend-misskey-world-engine/src/MultiplayEngineBase.ts new file mode 100644 index 0000000000..928acdf8c4 --- /dev/null +++ b/packages/frontend-misskey-world-engine/src/MultiplayEngineBase.ts @@ -0,0 +1,143 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import * as BABYLON from '@babylonjs/core/pure.js'; +import { EngineBase } from './EngineBase.js'; +import { PlayerContainer, type PlayerProfile, type PlayerState } from './PlayerContainer.js'; + +const IN_WEB_WORKER = typeof window === 'undefined'; + +export type MultiplayEngineBaseEvents = { + 'loadingProgress': (ctx: { progress: number }) => void; + 'contextlost': (ctx: { reason: string; message: string; }) => void; +}; + +export abstract class MultiplayEngineBase extends EngineBase { + protected playerProfiles: Record = {}; + protected playerContainers: PlayerContainer[] = []; + protected showUsernameOnAvatar: boolean; + protected show2dAvatarOnAvatar: boolean; + + constructor(options: { + babylonEngine: BABYLON.WebGPUEngine; + fps: number | null; + showUsernameOnAvatar: boolean; + show2dAvatarOnAvatar: boolean; + }) { + super({ + babylonEngine: options.babylonEngine, + fps: options.fps, + }); + + this.showUsernameOnAvatar = options.showUsernameOnAvatar; + this.show2dAvatarOnAvatar = options.show2dAvatarOnAvatar; + } + + public updatePlayerProfiles(profiles: Record) { + this.playerProfiles = profiles; + + for (const playerContainer of this.playerContainers) { + if (this.playerProfiles[playerContainer.id] == null) { + this.sr.disableSnapshotRendering(); + playerContainer.destroy(); + this.sr.enableSnapshotRendering(); + } + } + this.playerContainers = this.playerContainers.filter(p => this.playerProfiles[p.id] != null); + } + + public updatePlayerStates(states: Record) { + for (const [k, v] of Object.entries(this.playerProfiles)) { + const playerContainer = this.playerContainers.find(p => p.id === k); + if (playerContainer == null) { + const p = new PlayerContainer({ + id: k, + profile: v, + state: states[k], + scene: this.scene, + sr: this.sr, + showUsername: this.showUsernameOnAvatar, + show2dAvatar: this.show2dAvatarOnAvatar, + }); + // TODO: loadFurnitureのものとある程度共通化 + p.registerMeshes = (meshes) => { + for (const mesh of meshes) { + mesh.receiveShadows = false; + mesh.metadata = { isPlayer: true, playerId: k }; + + //if (mesh.material) (mesh.material as BABYLON.PBRMaterial).ambientColor = new BABYLON.Color3(0.2, 0.2, 0.2); + if (mesh.material) { + if (mesh.material instanceof BABYLON.MultiMaterial) { + for (const subMat of mesh.material.subMaterials) { + if ((subMat as BABYLON.PBRMaterial).subSurface.isRefractionEnabled) { + (subMat as BABYLON.PBRMaterial).subSurface.isRefractionEnabled = false; // 有効にするとドローコールが激増する + (subMat as BABYLON.PBRMaterial).transparencyMode = BABYLON.PBRMaterial.PBRMATERIAL_ALPHABLEND; + (subMat as BABYLON.PBRMaterial).alpha = 0.5; + (subMat as BABYLON.PBRMaterial).metallic = 1; + } + (subMat as BABYLON.PBRMaterial).reflectionTexture = this.getEnvMap(); + if ((subMat as BABYLON.PBRMaterial).metadata == null) (subMat as BABYLON.PBRMaterial).metadata = {}; + (subMat as BABYLON.PBRMaterial).metadata.useEnvMap = true; + (subMat as BABYLON.PBRMaterial).useGLTFLightFalloff = true; // Clustered Lightingではphysical falloffを持つマテリアルはアーチファクトが発生する https://doc.babylonjs.com/features/featuresDeepDive/lights/clusteredLighting/#materials-with-a-physical-falloff-may-cause-artefacts + (subMat as BABYLON.PBRMaterial).anisotropy.isEnabled = false; // なんかきれいにレンダリングされないため + } + } else { + if ((mesh.material as BABYLON.PBRMaterial).subSurface.isRefractionEnabled) { + (mesh.material as BABYLON.PBRMaterial).subSurface.isRefractionEnabled = false; // 有効にするとドローコールが激増する + (mesh.material as BABYLON.PBRMaterial).transparencyMode = BABYLON.PBRMaterial.PBRMATERIAL_ALPHABLEND; + (mesh.material as BABYLON.PBRMaterial).alpha = 0.5; + (mesh.material as BABYLON.PBRMaterial).metallic = 1; + } + (mesh.material as BABYLON.PBRMaterial).reflectionTexture = this.getEnvMap(); + if ((mesh.material as BABYLON.PBRMaterial).metadata == null) (mesh.material as BABYLON.PBRMaterial).metadata = {}; + (mesh.material as BABYLON.PBRMaterial).metadata.useEnvMap = true; + (mesh.material as BABYLON.PBRMaterial).useGLTFLightFalloff = true; // Clustered Lightingではphysical falloffを持つマテリアルはアーチファクトが発生する https://doc.babylonjs.com/features/featuresDeepDive/lights/clusteredLighting/#materials-with-a-physical-falloff-may-cause-artefacts + (mesh.material as BABYLON.PBRMaterial).anisotropy.isEnabled = false; // なんかきれいにレンダリングされないため + } + } + + if (!this.scene.meshes.includes(mesh)) this.scene.addMesh(mesh); + } + }; + p.loadAvatar().then(() => { + this.sr.disableSnapshotRendering(); + this.sr.enableSnapshotRendering(); + }); + this.playerContainers.push(p); + } else { + if (states[k] != null) { + playerContainer.applyState(states[k]); + } + } + } + } + + public clearPlayers() { + this.sr.disableSnapshotRendering(); + for (const playerContainer of this.playerContainers) { + playerContainer.destroy(); + } + this.sr.enableSnapshotRendering(); + this.playerContainers = []; + } + + public updateAvatarDisplayOptions(options: { showUsername: boolean; show2dAvatar: boolean }) { + this.showUsernameOnAvatar = options.showUsername; + this.show2dAvatarOnAvatar = options.show2dAvatar; + + this.sr.disableSnapshotRendering(); + for (const playerContainer of this.playerContainers) { + playerContainer.updateUserInfoDisplayOptions(options); + } + this.sr.enableSnapshotRendering(); + } + + public destroy() { + for (const playerContainer of this.playerContainers) { + playerContainer.destroy(); + } + super.destroy(); + } +} diff --git a/packages/frontend-misskey-world-engine/src/engine.ts b/packages/frontend-misskey-world-engine/src/engine.ts index 6e0834de99..b052569a55 100644 --- a/packages/frontend-misskey-world-engine/src/engine.ts +++ b/packages/frontend-misskey-world-engine/src/engine.ts @@ -8,14 +8,14 @@ import { registerBuiltInLoaders } from '@babylonjs/loaders/dynamic.js'; import { cm, WORLD_SCALE } from 'misskey-world/src/utility.js'; import { FreeCameraManualInput, GRAPHICS_QUALITY, Timer } from './utility.js'; import { TIME_MAP } from './utility.js'; -import { EngineBase } from './EngineBase.js'; +import { MultiplayEngineBase } from './MultiplayEngineBase.js'; import { LobbyEnvManager } from './envs/lobby.js'; import type { PlayerContainer, PlayerProfile, PlayerState } from './PlayerContainer.js'; import type { WorldEnvManager } from './env.js'; const IN_WEB_WORKER = typeof window === 'undefined'; -export class WorldEngine extends EngineBase<{ +export class WorldEngine extends MultiplayEngineBase<{ 'changeMyPlayerState': (ctx: PlayerState) => void; 'playerPointed': (ctx: { playerId: string; }) => void; 'playSfxUrl': (ctx: { diff --git a/packages/frontend-misskey-world-engine/src/room/engine.ts b/packages/frontend-misskey-world-engine/src/room/engine.ts index 0089ce2041..8f17a28d3e 100644 --- a/packages/frontend-misskey-world-engine/src/room/engine.ts +++ b/packages/frontend-misskey-world-engine/src/room/engine.ts @@ -22,7 +22,7 @@ import * as BABYLON from '@babylonjs/core/pure.js'; import { registerBuiltInLoaders } from '@babylonjs/loaders/dynamic.js'; import { cm, WORLD_SCALE } from 'misskey-world/src/utility.js'; import { TIME_MAP, getMeshesBoundingBox, Timer, getYRotationDirection, FreeCameraManualInput, remap, GRAPHICS_QUALITY } from '../utility.js'; -import { EngineBase } from '../EngineBase.js'; +import { MultiplayEngineBase } from '../MultiplayEngineBase.js'; import { genId } from '../id.js'; import { deepClone } from '../clone.js'; import { PlayerContainer, type PlayerProfile, type PlayerState } from '../PlayerContainer.js'; @@ -63,7 +63,7 @@ function intersectBoundingBoxes(bb1: { min: BABYLON.Vector3; max: BABYLON.Vector return true; } -export class RoomEngine extends EngineBase<{ +export class RoomEngine extends MultiplayEngineBase<{ 'changeSelectedState': (ctx: { selected: { furnitureId: string; diff --git a/packages/frontend/src/components/MkVirtualJoystick.vue b/packages/frontend/src/components/MkVirtualJoystick.vue index 7309e2415e..597aad211c 100644 --- a/packages/frontend/src/components/MkVirtualJoystick.vue +++ b/packages/frontend/src/components/MkVirtualJoystick.vue @@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only