From c35a6c01ffbd0dbe514e188c2c819bb98e6a436c Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Thu, 30 Jul 2026 20:39:15 +0900 Subject: [PATCH] wip --- .../src/EngineBase.ts | 109 +++++++++++++++++ .../src/engine.ts | 4 - .../src/room/engine.ts | 110 +----------------- packages/frontend/src/pages/world.vue | 25 ++-- packages/frontend/src/world/multiplayer.ts | 4 +- 5 files changed, 132 insertions(+), 120 deletions(-) diff --git a/packages/frontend-misskey-world-engine/src/EngineBase.ts b/packages/frontend-misskey-world-engine/src/EngineBase.ts index bc6b298986..b756f4bbf7 100644 --- a/packages/frontend-misskey-world-engine/src/EngineBase.ts +++ b/packages/frontend-misskey-world-engine/src/EngineBase.ts @@ -5,6 +5,7 @@ import * as BABYLON from '@babylonjs/core/pure.js'; import EventEmitter from 'eventemitter3'; +import { PlayerContainer, type PlayerProfile, type PlayerState } from './PlayerContainer.js'; const IN_WEB_WORKER = typeof window === 'undefined'; @@ -22,9 +23,15 @@ export abstract class EngineBase extends EventEmit public scene: BABYLON.Scene; abstract sr: BABYLON.SnapshotRenderingHelper; abstract lightContainer: BABYLON.ClusteredLightContainer; + abstract getEnvMap(): BABYLON.CubeTexture | null; 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; @@ -114,6 +121,105 @@ 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.engine.stopRenderLoop(); if (this.currentRafId != null) { @@ -121,6 +227,9 @@ export abstract class EngineBase extends EventEmit cancelAnimationFrame(this.currentRafId); this.currentRafId = null; } + for (const playerContainer of this.playerContainers) { + playerContainer.destroy(); + } this.engine.dispose(); this.scene.dispose(); this.disposed = true; diff --git a/packages/frontend-misskey-world-engine/src/engine.ts b/packages/frontend-misskey-world-engine/src/engine.ts index b32fed4713..c616c48a56 100644 --- a/packages/frontend-misskey-world-engine/src/engine.ts +++ b/packages/frontend-misskey-world-engine/src/engine.ts @@ -40,10 +40,6 @@ export class WorldEngine extends EngineBase<{ private fov: number; private useGlow: boolean; public graphicsQuality: number; - private playerProfiles: Record = {}; - private playerContainers: PlayerContainer[] = []; - private showUsernameOnAvatar: boolean; - private show2dAvatarOnAvatar: boolean; private envManager: WorldEnvManager | null = null; private inited = false; private isGodMode = false; diff --git a/packages/frontend-misskey-world-engine/src/room/engine.ts b/packages/frontend-misskey-world-engine/src/room/engine.ts index 09a2e96050..26298fb0ba 100644 --- a/packages/frontend-misskey-world-engine/src/room/engine.ts +++ b/packages/frontend-misskey-world-engine/src/room/engine.ts @@ -195,10 +195,9 @@ export class RoomEngine extends EngineBase<{ this.ev('changeSittingState', { isSitting: v }); } - private playerProfiles: Record = {}; - private playerContainers: PlayerContainer[] = []; - private showUsernameOnAvatar: boolean; - private show2dAvatarOnAvatar: boolean; + public getEnvMap(): BABYLON.CubeTexture | null { + return this.envManager?.envMapIndoor ?? null; + } private inited = false; @@ -1556,109 +1555,6 @@ export class RoomEngine extends EngineBase<{ this.ev('playSfxUrl', { url, options }); } - 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; - if (SYSTEM_MESH_NAMES.some(n => mesh.name.includes(n))) { - mesh.isVisible = false; - } else { - 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.envManager?.envMapIndoor; - 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.envManager?.envMapIndoor; - 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 resize() { // 一旦snapshot renderingを無効にしておかないとエラーが出る // ~~...が、一旦無効にしたらしたで複数のマテリアルがそれぞれ入れ替わる(?)という謎の現象が発生するためコメントアウトしとく(エラー出てもレンダリングが止まったりするわけでもないし)~~ diff --git a/packages/frontend/src/pages/world.vue b/packages/frontend/src/pages/world.vue index f3a4b6408a..b2d7046016 100644 --- a/packages/frontend/src/pages/world.vue +++ b/packages/frontend/src/pages/world.vue @@ -85,7 +85,7 @@ import { prefer } from '@/preferences.js'; import { isTouchUsing } from '@/utility/touch.js'; import { deviceKind } from '@/utility/device-kind.js'; import MkProgressBar from '@/components/MkProgressBar.vue'; -import { RoomMultiplayer } from '@/world/room/multiplayer.js'; +import { WorldMultiplayer } from '@/world/multiplayer.js'; import { $i } from '@/i.js'; import { userPage } from '@/filters/user.js'; import MkUserCardMini from '@/components/MkUserCardMini.vue'; @@ -132,7 +132,7 @@ const worldControllerOptions = computed(() => ({ })); const controller = markRaw(new WorldEngineController(worldControllerOptions.value)); -const multiplayer = markRaw(new RoomMultiplayer(0, controller)); +const multiplayer = markRaw(new WorldMultiplayer('0', controller)); const pointedPlayerInfo = ref(null); const isMenuShowing = ref(!isNarrow); @@ -200,12 +200,12 @@ onMounted(async () => { }); useInterval(() => { - //multiplayer.updateState(controller.myPlayerState.value); + multiplayer.updateState(controller.myPlayerState.value); }, 100, { immediate: false, afterMounted: true }); onDeactivated(() => { controller.destroy(); - //multiplayer.dispose(); + multiplayer.dispose(); window.removeEventListener('resize', resize); }); @@ -216,7 +216,7 @@ onActivated(() => { onUnmounted(() => { controller.destroy(); - //multiplayer.dispose(); + multiplayer.dispose(); window.removeEventListener('resize', resize); }); @@ -336,9 +336,20 @@ function showOtherMenu(ev: PointerEvent) { }], ev.currentTarget ?? ev.target); } +function leaveOnline() { + multiplayer.left(); +} + +function enterOnline() { + const closeWaiting = os.waiting(); + multiplayer.enter().finally(() => { + closeWaiting(); + }); +} + definePage(() => ({ - title: 'Room', - icon: 'ti ti-door', + title: 'World', + icon: 'ti ti-universe', needWideArea: true, })); diff --git a/packages/frontend/src/world/multiplayer.ts b/packages/frontend/src/world/multiplayer.ts index 8ca0e3e73e..9c2c9d52eb 100644 --- a/packages/frontend/src/world/multiplayer.ts +++ b/packages/frontend/src/world/multiplayer.ts @@ -16,7 +16,7 @@ import { deepEqual } from '@/utility/deep-equal.js'; export class WorldMultiplayer { public isOnline = ref(false); private controller: WorldEngineController; - private connection: Misskey.IChannelConnection | null = null; + private connection: Misskey.IChannelConnection | null = null; private dimensionId: string; public playerProfiles: Record = {}; @@ -31,7 +31,7 @@ export class WorldMultiplayer { public enter() { const p = new Promise((resolve, reject) => { - this.connection = useStream().useChannel('worldRoom', { + this.connection = useStream().useChannel('world', { roomId: this.dimensionId, }); this.connection.once('entered', ({ playerProfiles }) => {