diff --git a/packages/frontend/assets/room/objects/dj-player/dj-player.glb b/packages/frontend/assets/room/objects/dj-player/dj-player.glb index 481581eba1..e484437a47 100644 Binary files a/packages/frontend/assets/room/objects/dj-player/dj-player.glb and b/packages/frontend/assets/room/objects/dj-player/dj-player.glb differ diff --git a/packages/frontend/src/utility/room/objects/djPlayer.ts b/packages/frontend/src/utility/room/objects/djPlayer.ts index b990d76c15..8f10a73af0 100644 --- a/packages/frontend/src/utility/room/objects/djPlayer.ts +++ b/packages/frontend/src/utility/room/objects/djPlayer.ts @@ -3,18 +3,99 @@ * SPDX-License-Identifier: AGPL-3.0-only */ +import * as BABYLON from '@babylonjs/core'; import { defineObject } from '../engine.js'; +import { createPlaneUvMapper, normalizeUvToSquare } from '../utility.js'; export const djPlayer = defineObject({ id: 'djPlayer', name: 'djPlayer', options: { - schema: {}, - default: {}, + schema: { + screenBrightness: { + type: 'range', + label: 'Screen brightness', + min: 0, + max: 1, + step: 0.01, + }, + customPicture: { + type: 'image', + label: 'Custom picture', + }, + fit: { + type: 'enum', + label: 'Custom picture fit', + enum: ['cover', 'contain', 'stretch'], + }, + }, + default: { + screenBrightness: 0.35, + customPicture: null, + fit: 'cover', + }, }, placement: 'top', - createInstance: () => { + createInstance: async ({ model, options, scene }) => { + const screenMesh = model.findMesh('__X_SCREEN__'); + const screenMaterial = model.findMaterial('__X_SCREEN__'); + const defaultScreenTexture = screenMaterial.emissiveTexture; + + normalizeUvToSquare(screenMesh); + const updateUv = createPlaneUvMapper(screenMesh); + + const applyFit = () => { + const tex = screenMaterial.emissiveTexture; + if (tex == null) return; + + const srcAspect = tex.getSize().width / tex.getSize().height; + const targetAspect = 15.6 / 8.33; + + updateUv(srcAspect, targetAspect, options.fit); + + model.updated(); + }; + + applyFit(); + + const applyCustomPicture = () => new Promise((resolve) => { + if (options.customPicture != null && options.customPicture !== '') { + const tex = new BABYLON.Texture(options.customPicture, scene, false, false); + tex.wrapU = BABYLON.Texture.MIRROR_ADDRESSMODE; + tex.wrapV = BABYLON.Texture.MIRROR_ADDRESSMODE; + tex.level = 0.5; + + screenMaterial.unfreeze(); + screenMaterial.emissiveTexture = tex; + + tex.onLoadObservable.addOnce(() => { + applyFit(); + resolve(); + }); + } else { + screenMaterial.emissiveTexture = defaultScreenTexture; + applyFit(); + resolve(); + } + }); + + await applyCustomPicture(); + + const applyScreenBrightness = () => { + const b = options.screenBrightness; + screenMaterial.emissiveColor = new BABYLON.Color3(b, b, b); + }; + + applyScreenBrightness(); + return { + onOptionsUpdated: ([k, v]) => { + switch (k) { + case 'screenBrightness': applyScreenBrightness(); break; + case 'customPicture': applyCustomPicture(); break; + case 'fit': applyFit(); break; + } + }, interactions: {}, }; }, diff --git a/packages/frontend/src/utility/room/utility.ts b/packages/frontend/src/utility/room/utility.ts index be2dd097fb..d8bc9caebd 100644 --- a/packages/frontend/src/utility/room/utility.ts +++ b/packages/frontend/src/utility/room/utility.ts @@ -376,6 +376,20 @@ export function getPlaneUvIndexes(mesh: BABYLON.Mesh) { return [aIndex, bIndex, cIndex, dIndex]; } +export function normalizeUvToSquare(mesh: BABYLON.Mesh) { + const uvs = mesh.getVerticesData(BABYLON.VertexBuffer.UVKind)!; + const uvIndexes = getPlaneUvIndexes(mesh); + uvs[uvIndexes[0]] = 0; + uvs[uvIndexes[0] + 1] = 0; + uvs[uvIndexes[1]] = 1; + uvs[uvIndexes[1] + 1] = 0; + uvs[uvIndexes[2]] = 0; + uvs[uvIndexes[2] + 1] = 1; + uvs[uvIndexes[3]] = 1; + uvs[uvIndexes[3] + 1] = 1; + mesh.updateVerticesData(BABYLON.VertexBuffer.UVKind, uvs); +} + export function createPlaneUvMapper(mesh: BABYLON.Mesh) { mesh.markVerticesDataAsUpdatable(BABYLON.VertexBuffer.UVKind, true);