This commit is contained in:
syuilo 2026-05-28 09:40:02 +09:00
commit d722b8e9d7
6 changed files with 25 additions and 19 deletions

View file

@ -11,7 +11,7 @@ import { PlayerContainer, type PlayerProfile } from './PlayerContainer.js';
import { EngineBase } from './EngineBase.js';
import type { WorldAvatar } from 'misskey-world/src/types.js';
export class AvatarPreviewEngine extends EngineBase<{
export class AvatarPreviewEngine extends EngineBase<{ // PlayerPreviewEngineに改名した方がいいかもしれない
'loadingProgress': (ctx: { progress: number }) => void;
'contextlost': (ctx: { reason: string; message: string; }) => void;
}> {
@ -24,8 +24,9 @@ export class AvatarPreviewEngine extends EngineBase<{
private roomLight: BABYLON.SpotLight;
private pipeline: BABYLON.DefaultRenderingPipeline;
private graphicsQuality: number;
private profile: PlayerProfile;
constructor(options: {
constructor(profile: PlayerProfile, options: {
engine: BABYLON.WebGPUEngine;
graphicsQuality: number;
fps: number | null;
@ -38,6 +39,7 @@ export class AvatarPreviewEngine extends EngineBase<{
registerBuiltInLoaders();
this.graphicsQuality = options.graphicsQuality;
this.profile = profile;
this.scene.autoClear = false;
this.scene.skipPointerMovePicking = true;
@ -87,6 +89,8 @@ export class AvatarPreviewEngine extends EngineBase<{
}
public async init() {
this.startRenderLoop();
await this.scene.whenReadyAsync();
this.sr.enableSnapshotRendering();
@ -103,14 +107,16 @@ export class AvatarPreviewEngine extends EngineBase<{
this.inputs.on('pointer', (ev) => {
(this.camera.inputs.attached.manual as ArcRotateCameraManualInput).setRotationVector({ x: ev.x, y: ev.y });
});
await this.load();
}
public async load(profile: PlayerProfile) {
private async load() {
this.sr.disableSnapshotRendering();
this.playerContainer = new PlayerContainer({
id: '',
profile,
profile: this.profile,
state: {
position: [0, 0, 0],
rotation: [0, 0, 0],

View file

@ -5,13 +5,14 @@
import * as BABYLON from '@babylonjs/core';
import { AvatarPreviewEngine } from './avatarPreviewEngine.js';
import type { PlayerProfile } from './PlayerContainer.js';
//BABYLON.RegisterStandardEngineExtensions();
//BABYLON.RegisterEnginesExtensionsEngineRawTexture();
//BABYLON.RegisterCollisionCoordinator();
export async function createAvatarPreviewEngine(params: {
canvas: HTMLCanvasElement; options: { graphicsQuality: number; resolution: number; fps: number | null };
canvas: HTMLCanvasElement; options: { graphicsQuality: number; resolution: number; fps: number | null }; profile: PlayerProfile;
}) {
const babylonEngine = new BABYLON.WebGPUEngine(params.canvas, { doNotHandleContextLost: true, powerPreference: 'low-power', antialias: true });
babylonEngine.compatibilityMode = false;
@ -20,7 +21,7 @@ export async function createAvatarPreviewEngine(params: {
if (params.options.resolution === 2) babylonEngine.setHardwareScalingLevel(0.5);
if (params.options.resolution === 0.5) babylonEngine.setHardwareScalingLevel(2);
const engine = new AvatarPreviewEngine({
const engine = new AvatarPreviewEngine(params.profile, {
engine: babylonEngine,
...params.options,
});

View file

@ -5,6 +5,7 @@
import * as BABYLON from '@babylonjs/core';
import { AvatarPreviewEngine } from './avatarPreviewEngine.js';
import type { PlayerProfile } from './PlayerContainer.js';
let engine: AvatarPreviewEngine | null = null;
let canvas: OffscreenCanvas | null = null;
@ -19,6 +20,7 @@ onmessage = async (event) => {
switch (event.data?.type) {
case 'init': {
const profile = event.data.profile as PlayerProfile;
canvas = event.data.canvas as OffscreenCanvas;
const babylonEngine = new BABYLON.WebGPUEngine(canvas, { doNotHandleContextLost: true, powerPreference: 'low-power', antialias: true });
babylonEngine.compatibilityMode = false;
@ -27,7 +29,7 @@ onmessage = async (event) => {
if (event.data.options.resolution === 2) babylonEngine.setHardwareScalingLevel(0.5);
if (event.data.options.resolution === 0.5) babylonEngine.setHardwareScalingLevel(2);
engine = new AvatarPreviewEngine({
engine = new AvatarPreviewEngine(profile, {
engine: babylonEngine,
...event.data.options,
});

View file

@ -22,9 +22,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.previewContainer">
<div :class="$style.preview">
<MkButton v-if="selectedObjectSchema != null && Object.keys(selectedObjectSchema.options.schema).length > 0" :class="$style.customizeButton" small rounded iconOnly @click="showObjectOptions = !showObjectOptions"><i class="ti ti-tool"></i></MkButton>
<MkButton :class="$style.customizeButton" small rounded iconOnly @click="showObjectOptions = !showObjectOptions"><i class="ti ti-tool"></i></MkButton>
<div :class="[$style.previewMain, { [$style.optionsOpened]: selectedObjectSchema != null && selectedInstanceId != null && showObjectOptions }]">
<div :class="[$style.previewMain]">
<canvas ref="canvas" :class="$style.canvas"></canvas>
</div>
@ -34,7 +34,7 @@ SPDX-License-Identifier: AGPL-3.0-only
:enterFromClass="prefer.s.animation ? $style.transition_options_enterFrom : ''"
:leaveToClass="prefer.s.animation ? $style.transition_options_leaveTo : ''"
>
<div v-if="selectedObjectSchema != null && selectedInstanceId != null && showObjectOptions" :class="$style.customize">
<div v-if="showObjectOptions" :class="$style.customize">
</div>
</Transition>
</div>
@ -111,8 +111,7 @@ const controller = markRaw(new AvatarPreviewEngineController(avatarPreviewEngine
onMounted(async () => {
try {
await controller.init(canvas.value!);
await controller.load({
await controller.init(canvas.value!, {
name: $i.name,
username: $i.username,
avatarUrl: $i.avatarUrl,

View file

@ -1235,6 +1235,8 @@ function testNotification(): void {
async function createWorldAvatar(ev: PointerEvent) {
const { dispose } = await os.popupAsyncWithDialog(import('../rooms/edit-world-avatar-dialog.vue').then(x => x.default), {
graphicsQuality: worldGraphicsQuality.value ?? 0,
avatar: null,
}, {
ok: async (res) => {
console.log(res);

View file

@ -23,18 +23,18 @@ export class AvatarPreviewEngineController extends EngineControllerBase<AvatarPr
});
}
public async init(canvas: HTMLCanvasElement) {
public async init(canvas: HTMLCanvasElement, profile: PlayerProfile) {
await this._init_(canvas, {
createWorker: (offscreen) => new Promise((resolve) => {
import('misskey-world-engine/src/avatarPreviewEngineWorker?worker').then(({ default: PreviewEngineWorker }) => {
const worker = new PreviewEngineWorker();
worker.postMessage({ type: 'init', canvas: offscreen, options: this.options }, [offscreen]);
worker.postMessage({ type: 'init', canvas: offscreen, options: this.options, profile }, [offscreen]);
resolve(worker);
});
}),
createEngine: () => new Promise((resolve) => {
import('misskey-world-engine/src/avatarPreviewEngineNonWorker.js').then(({ createAvatarPreviewEngine }) => {
const engine = createAvatarPreviewEngine({ canvas, options: this.options });
const engine = createAvatarPreviewEngine({ canvas, options: this.options, profile });
resolve(engine);
});
}),
@ -44,8 +44,4 @@ export class AvatarPreviewEngineController extends EngineControllerBase<AvatarPr
public updateAvatarOption(key: string, value: any) {
this.call('updateAvatarOption', [key, value]);
}
public load(profile: PlayerProfile) {
return this.callAndWaitReturn('load', [profile]);
}
}