This commit is contained in:
syuilo 2026-05-23 15:54:07 +09:00
commit 1154bb5370
19 changed files with 153 additions and 307 deletions

View file

@ -20,22 +20,22 @@ import { ModerationLogService } from '@/core/ModerationLogService.js';
import { QueryService } from '@/core/QueryService.js';
const driveFileReferencingOptions = {
clippedPicture: ['customPicture'],
tapestry: ['customPicture'],
poster: ['customPicture'],
pictureFrame: ['customPicture'],
tabletopPictureFrame: ['customPicture'],
tabletopGlassPictureFrame: ['customPicture'],
wallCanvas: ['customPicture'],
wallGlassPictureFrame: ['customPicture'],
tabletopFlag: ['customPicture'],
tabletopLcdButtonsController: ['customPicture'],
djPlayer: ['customPicture'],
monitor: ['customPicture'],
allInOnePc: ['customPicture'],
laptopPc: ['customPicture'],
handheldGameConsole: ['customPicture'],
largeMousepad: ['customPicture'],
clippedPicture: ['image'],
tapestry: ['image'],
poster: ['image'],
pictureFrame: ['image'],
tabletopPictureFrame: ['image'],
tabletopGlassPictureFrame: ['image'],
wallCanvas: ['image'],
wallGlassPictureFrame: ['image'],
tabletopFlag: ['image'],
tabletopLcdButtonsController: ['image'],
djPlayer: ['image'],
monitor: ['image'],
allInOnePc: ['image'],
laptopPc: ['image'],
handheldGameConsole: ['image'],
largeMousepad: ['image'],
} as Record<string, string[]>;
@Injectable()
@ -124,8 +124,8 @@ export class WorldRoomService {
if (def == null) continue;
for (const key of def) {
const optionValue = o.options[key];
if (optionValue != null && optionValue !== '') {
fileIds.add(optionValue);
if (optionValue != null && optionValue.driveFileId != null && optionValue.driveFileId !== '') {
fileIds.add(optionValue.driveFileId);
}
}
}

View file

@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSwitch :modelValue="options[k]" @update:modelValue="v => emit('update', k, v)"></MkSwitch>
</div>
<div v-else-if="s.type === 'enum'">
<MkSelect :items="s.enum.map(e => ({ label: e, value: e }))" :modelValue="options[k]" @update:modelValue="v => emit('update', k, v)"></MkSelect>
<MkSelect :items="s.enum.map(e => ({ label: e.label, value: e.value }))" :modelValue="options[k]" @update:modelValue="v => emit('update', k, v)"></MkSelect>
</div>
<div v-else-if="s.type === 'string'">
<MkInput type="text" :modelValue="options[k]" @update:modelValue="v => emit('update', k, v)"></MkInput>
@ -25,8 +25,14 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkRange :continuousUpdate="true" :min="s.min" :max="s.max" :step="s.step" :modelValue="options[k]" @update:modelValue="v => emit('update', k, v)"></MkRange>
</div>
<div v-else-if="s.type === 'image'">
<MkButton primary inline @click="changeImage(k)">Change</MkButton>
<MkButton v-if="options[k] != null" danger inline iconOnly @click="clearImage(k)"><i class="ti ti-x"></i></MkButton>
<MkSelect :items="[{ label: i18n.ts.none, value: null }, { label: i18n.ts.custom, value: '_custom_' }, ...(s.presets.length > 0 ? [{ type: 'divider' } as const] : []), ...s.presets.map(e => ({ label: e.label, value: e.value }))]" :modelValue="options[k].type" @update:modelValue="v => changeImageType(k, v)"></MkSelect>
<div v-if="options[k].type === '_custom_'">
<MkButton primary inline @click="changeImage(k)">Change</MkButton>
<MkButton v-if="options[k].driveFileId != null" danger inline iconOnly @click="clearImage(k)"><i class="ti ti-x"></i></MkButton>
</div>
<MkRadios :options="[{ label: 'cover', value: 'cover' }, { label: 'contain', value: 'contain' }, { label: 'stretch', value: 'stretch' }]" :modelValue="options[k].fit ?? 'cover'" @update:modelValue="v => changeImageFit(k, v)"></MkRadios>
</div>
<div v-else-if="s.type === 'seed'">
<MkRange :continuousUpdate="true" :min="0" :max="1000" :step="1" :modelValue="options[k]" @update:modelValue="v => emit('update', k, v)"></MkRange>
@ -37,7 +43,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { computed, defineAsyncComponent, nextTick, onMounted, onUnmounted, ref, shallowRef, useTemplateRef, watch } from 'vue';
import { } from 'vue';
import * as Misskey from 'misskey-js';
import type { ObjectDef } from '@/world/room/object.js';
import { i18n } from '@/i18n.js';
@ -49,6 +55,7 @@ import MkSwitch from '@/components/MkSwitch.vue';
import MkRange from '@/components/MkRange.vue';
import { getHex, getRgb } from '@/world/utility.js';
import { chooseDriveFile } from '@/utility/drive.js';
import MkRadios from '@/components/MkRadios.vue';
const props = defineProps<{
schema: ObjectDef['options']['schema'];
@ -60,6 +67,10 @@ const emit = defineEmits<{
(ev: 'update', k: string, v: any): void;
}>();
function changeImageType(k: string, type: string) {
emit('update', k, { ...props.options[k], type, driveFileId: type === '_custom_' ? props.options[k].driveFileId : null });
}
async function changeImage(k: string) {
chooseDriveFile({ multiple: false }).then((fileResponse) => {
if (fileResponse.length === 0) return;
@ -79,12 +90,19 @@ async function changeImage(k: string) {
return;
}
props.addFileAttachment(file);
emit('update', k, file.id);
emit('update', k, {
...props.options[k],
driveFileId: file.id,
});
});
}
function clearImage(k: string) {
emit('update', k, null);
emit('update', k, { ...props.options[k], driveFileId: null });
}
function changeImageFit(k: string, fit: string) {
emit('update', k, { ...props.options[k], fit });
}
</script>

View file

@ -4,8 +4,9 @@
*/
import * as BABYLON from '@babylonjs/core';
import type { ModelManager, RoomAttachments } from './utility.js';
import { createPlaneUvMapper } from '../utility.js';
import type { Timer } from '../utility.js';
import type { ModelManager, RoomAttachments } from './utility.js';
// babylonのドメイン知識は持たない
export type RoomStateObject = {
@ -76,6 +77,10 @@ type RangeOptionSchema = {
type ImageOptionSchema = {
type: 'image';
label: string;
presets: {
label: string;
value: string | number;
}[];
};
type SeedOptionSchema = {
@ -101,7 +106,7 @@ type GetRawOptionsSchemaValues<T extends OptionsSchema> = {
T[K] extends ColorOptionSchema ? [number, number, number] :
T[K] extends EnumOptionSchema ? T[K]['enum'][number]['value'] :
T[K] extends RangeOptionSchema ? number :
T[K] extends ImageOptionSchema ? string | null :
T[K] extends ImageOptionSchema ? { type: T[K]['presets'][number]['value'] | null | '_custom_'; driveFileId?: string | null; fit?: 'cover' | 'contain' | 'stretch'; } :
T[K] extends SeedOptionSchema ? number :
never;
};
@ -113,7 +118,7 @@ type GetConvertedOptionsSchemaValues<T extends OptionsSchema> = {
T[K] extends ColorOptionSchema ? [number, number, number] :
T[K] extends EnumOptionSchema ? T[K]['enum'][number]['value'] :
T[K] extends RangeOptionSchema ? number :
T[K] extends ImageOptionSchema ? { url: string; } | null :
T[K] extends ImageOptionSchema ? { type: T[K]['presets'][number]['value'] | null | '_custom_'; custom?: { url: string; } | null; fit?: 'cover' | 'contain' | 'stretch'; } :
T[K] extends SeedOptionSchema ? number :
never;
};
@ -174,24 +179,69 @@ export function convertRawOptions<OpSc extends OptionsSchema>(schema: OpSc, raw:
for (const record of Object.entries(schema)) {
const k = record[0];
const v = raw[k];
if (record[1].type === 'image' && v != null) {
if (v === '') {
converted[k] = null;
continue;
}
const file = attachments.files.find(f => f.id === v);
if (file != null) {
if (file.url.startsWith('http://syu-win.local:3000/')) { // debug
converted[k] = { url: file.url.replace('http://syu-win.local:3000/', 'https://local-mi.syuilo.dev/') };
} else {
converted[k] = { url: file.url };
}
} else {
converted[k] = null;
if (record[1].type === 'image') {
const file = v.type === '_custom_' ? attachments.files.find(f => f.id === v.driveFileId) : null;
if (file != null && file.url.startsWith('http://syu-win.local:3000/')) { // debug
file.url = file.url.replace('http://syu-win.local:3000/', 'https://local-mi.syuilo.dev/');
}
converted[k] = { type: v.type, custom: file != null ? { url: file.url } : null, fit: v.fit };
} else {
converted[k] = v;
}
}
return converted;
}
export const createTextureManager = (targetMesh: BABYLON.Mesh, targetAspect: number, scene: BABYLON.Scene) => {
let currentUrl: string | null = null;
let currentTexture: BABYLON.Texture | null = null;
const updateUv = createPlaneUvMapper(targetMesh);
const applyFit = (method?: 'cover' | 'contain' | 'stretch') => {
if (currentTexture == null) return;
const srcAspect = currentTexture.getSize().width / currentTexture.getSize().height;
updateUv(srcAspect, targetAspect, method ?? 'cover');
};
const change = (url: string | null, fit?: 'cover' | 'contain' | 'stretch') => new Promise<BABYLON.Texture | null>((resolve) => {
if (currentUrl === url) {
applyFit(fit);
resolve(currentTexture);
return;
}
if (currentTexture != null) {
currentTexture.dispose();
}
currentUrl = url;
if (url == null) {
currentTexture = null;
resolve(null);
return;
}
currentTexture = new BABYLON.Texture(url, scene, false, false, undefined, () => {
currentTexture!.level = 0.5;
applyFit(fit);
resolve(currentTexture);
}, (message, exception) => {
console.warn('Failed to load texture:', message, exception);
currentTexture!.dispose();
currentTexture = null;
resolve(null);
});
});
return {
change,
dispose: () => {
if (currentTexture != null) {
currentTexture.dispose();
}
},
};
};

View file

@ -4,7 +4,7 @@
*/
import * as BABYLON from '@babylonjs/core';
import { defineObject } from '../object.js';
import { createTextureManager, defineObject } from '../object.js';
import { cm, WORLD_SCALE, createPlaneUvMapper } from '../../utility.js';
import { getLightRangeFactorByGraphicsQuality } from '../utility.js';
@ -28,22 +28,22 @@ export const allInOnePc = defineObject({
max: 1,
step: 0.01,
},
customPicture: {
image: {
type: 'image',
label: 'Custom picture',
},
fit: {
type: 'enum',
label: 'Custom picture fit',
enum: ['cover', 'contain', 'stretch'],
label: 'Screen image',
presets: [{
label: 'Desktop',
value: 'desktop',
}],
},
},
default: {
bodyColor: [1, 1, 1],
bezelColor: [0, 0, 0],
screenBrightness: 0.5,
customPicture: null,
fit: 'cover',
image: {
type: null,
},
},
},
placement: 'top',
@ -69,22 +69,9 @@ export const allInOnePc = defineObject({
screenMaterial.ambientColor = new BABYLON.Color3(0, 0, 0);
screenMaterial.albedoColor = new BABYLON.Color3(0, 0, 0);
screenMaterial.emissiveColor = new BABYLON.Color3(1, 1, 1);
const updateUv = createPlaneUvMapper(screenMesh);
const applyFit = () => {
const tex = screenMaterial.emissiveTexture;
if (tex == null) return;
const srcAspect = tex.getSize().width / tex.getSize().height;
const targetAspect = 50 / 27.5;
updateUv(srcAspect, targetAspect, options.fit);
model.updated();
};
applyFit();
const textureManager = createTextureManager(screenMesh, 50 / 27.5, scene);
const applyScreenBrightness = () => {
const b = options.screenBrightness;
@ -94,29 +81,20 @@ export const allInOnePc = defineObject({
applyScreenBrightness();
const applyCustomPicture = () => new Promise<void>((resolve) => {
if (options.customPicture != null) {
screenMaterial.unfreeze();
const tex = new BABYLON.Texture(options.customPicture.url, scene, false, false, undefined, () => {
screenMaterial.emissiveColor = new BABYLON.Color3(1, 1, 1);
screenMaterial.emissiveTexture = tex;
applyFit();
applyScreenBrightness();
resolve();
}, (message, exception) => {
console.warn('Failed to load texture:', message, exception);
screenMaterial.emissiveColor = new BABYLON.Color3(0, 1, 0);
screenMaterial.emissiveTexture = null;
resolve();
});
tex.level = 0.5;
} else {
screenMaterial.emissiveTexture = null;
resolve();
const applyImage = () => {
screenMaterial.unfreeze();
let url: string | null = null;
if (options.image.type === '_custom_') {
url = options.image.custom?.url ?? null;
} else if (options.image.type === 'desktop') {
url = '/assets/objects/all-in-one-pc/desktop.png';
}
});
return textureManager.change(url, options.image.fit).then((tex) => {
screenMaterial.emissiveTexture = tex;
});
};
await applyCustomPicture();
await applyImage();
const applyBodyColor = () => {
const [r, g, b] = options.bodyColor;
@ -137,8 +115,7 @@ export const allInOnePc = defineObject({
case 'bodyColor': applyBodyColor(); break;
case 'bezelColor': applyBezelColor(); break;
case 'screenBrightness': applyScreenBrightness(); break;
case 'customPicture': applyCustomPicture(); break;
case 'fit': applyFit(); break;
case 'image': applyImage(); break;
}
},
interactions: {},
@ -146,6 +123,8 @@ export const allInOnePc = defineObject({
light.dispose();
if (lc != null) lc.removeLight(light);
scene.removeLight(light); // lc使用時はsceneには追加してないはずだが、これがないとクラッシュする babylonのバグ
textureManager.dispose();
},
};
},

View file

@ -30,17 +30,11 @@ export const clippedPicture = defineObject({
type: 'image',
label: 'Custom picture',
},
fit: {
type: 'enum',
label: 'Custom picture fit',
enum: ['cover', 'contain', 'stretch'],
},
},
default: {
width: 0.1,
height: 0.1,
customPicture: null,
fit: 'cover',
},
},
placement: 'side',
@ -121,7 +115,6 @@ export const clippedPicture = defineObject({
case 'width': applySize(); break;
case 'height': applySize(); break;
case 'customPicture': applyCustomPicture(); break;
case 'fit': applyFit(); break;
}
},
interactions: {},

View file

@ -23,16 +23,10 @@ export const djPlayer = defineObject({
type: 'image',
label: 'Custom picture',
},
fit: {
type: 'enum',
label: 'Custom picture fit',
enum: ['cover', 'contain', 'stretch'],
},
},
default: {
screenBrightness: 0.5,
customPicture: null,
fit: 'cover',
},
},
placement: 'top',
@ -98,7 +92,6 @@ export const djPlayer = defineObject({
switch (k) {
case 'screenBrightness': applyScreenBrightness(); break;
case 'customPicture': applyCustomPicture(); break;
case 'fit': applyFit(); break;
}
},
interactions: {},

View file

@ -28,17 +28,11 @@ export const handheldGameConsole = defineObject({
type: 'image',
label: 'Custom picture',
},
fit: {
type: 'enum',
label: 'Custom picture fit',
enum: ['cover', 'contain', 'stretch'],
},
},
default: {
bodyColor: [1, 1, 1],
screenBrightness: 0.5,
customPicture: null,
fit: 'cover',
},
},
placement: 'top',
@ -113,7 +107,6 @@ export const handheldGameConsole = defineObject({
case 'bodyColor': applyBodyColor(); break;
case 'screenBrightness': applyScreenBrightness(); break;
case 'customPicture': applyCustomPicture(); break;
case 'fit': applyFit(); break;
}
},
interactions: {},

View file

@ -32,11 +32,6 @@ export const laptopPc = defineObject({
type: 'image',
label: 'Custom picture',
},
fit: {
type: 'enum',
label: 'Custom picture fit',
enum: ['cover', 'contain', 'stretch'],
},
openAngle: {
type: 'range',
label: 'Open',
@ -50,7 +45,6 @@ export const laptopPc = defineObject({
bezelColor: [0, 0, 0],
screenBrightness: 0.5,
customPicture: null,
fit: 'cover',
openAngle: 0,
},
},
@ -162,7 +156,6 @@ export const laptopPc = defineObject({
case 'bezelColor': applyBezelColor(); break;
case 'screenBrightness': applyScreenBrightness(); break;
case 'customPicture': applyCustomPicture(); break;
case 'fit': applyFit(); break;
case 'openAngle': applyOpenAngle(); break;
}
},

View file

@ -16,15 +16,9 @@ export const largeMousepad = defineObject({
type: 'image',
label: 'Custom picture',
},
fit: {
type: 'enum',
label: 'Custom picture fit',
enum: ['cover', 'contain', 'stretch'],
},
},
default: {
customPicture: null,
fit: 'cover',
},
},
placement: 'top',
@ -79,7 +73,6 @@ export const largeMousepad = defineObject({
onOptionsUpdated: ([k, v]) => {
switch (k) {
case 'customPicture': applyCustomPicture(); break;
case 'fit': applyFit(); break;
}
},
interactions: {},

View file

@ -28,17 +28,11 @@ export const monitor = defineObject({
type: 'image',
label: 'Custom picture',
},
fit: {
type: 'enum',
label: 'Custom picture fit',
enum: ['cover', 'contain', 'stretch'],
},
},
default: {
bodyColor: [0.1, 0.1, 0.1],
screenBrightness: 0.5,
customPicture: null,
fit: 'cover',
},
},
placement: 'top',
@ -126,7 +120,6 @@ export const monitor = defineObject({
case 'bodyColor': applyBodyColor(); break;
case 'screenBrightness': applyScreenBrightness(); break;
case 'customPicture': applyCustomPicture(); break;
case 'fit': applyFit(); break;
}
},
interactions: {},

View file

@ -68,11 +68,6 @@ export const pictureFrame = defineObject({
type: 'image',
label: 'Custom picture',
},
fit: {
type: 'enum',
label: 'Custom picture fit',
enum: ['cover', 'contain', 'stretch'],
},
},
default: {
frameColor: [0.71, 0.58, 0.39],
@ -84,7 +79,6 @@ export const pictureFrame = defineObject({
matVThickness: 0.35,
withCover: true,
customPicture: null,
fit: 'cover',
},
},
placement: 'side',
@ -221,48 +215,9 @@ export const pictureFrame = defineObject({
case 'depth': applyDepth(); break;
case 'withCover': applyWithCover(); break;
case 'customPicture':
case 'fit': applyCustomPicture(); break;
}
},
interactions: {},
};
},
});
/*
const applyDirection = () => {
if (options.direction === 'vertical') {
frameMesh.rotation.z = 0;
matMesh.rotation.z = 0;
coverMesh.rotation.z = 0;
pictureMesh.rotation.z = 0;
uvs[6] = ax;
uvs[7] = ay;
uvs[2] = bx;
uvs[3] = by;
uvs[4] = cx;
uvs[5] = cy;
uvs[0] = dx;
uvs[1] = dy;
} else if (options.direction === 'horizontal') {
frameMesh.rotation.z = -Math.PI / 2;
matMesh.rotation.z = -Math.PI / 2;
coverMesh.rotation.z = -Math.PI / 2;
pictureMesh.rotation.z = -Math.PI / 2;
uvs[6] = cy;
uvs[7] = cx;
uvs[2] = dy;
uvs[3] = dx;
uvs[4] = ay;
uvs[5] = ax;
uvs[0] = by;
uvs[1] = bx;
}
pictureMesh.updateVerticesData(BABYLON.VertexBuffer.UVKind, uvs);
};
*/

View file

@ -34,17 +34,11 @@ export const poster = defineObject({
type: 'image',
label: 'Custom picture',
},
fit: {
type: 'enum',
label: 'Custom picture fit',
enum: ['cover', 'contain', 'stretch'],
},
},
default: {
width: 0.15,
height: 0.15,
customPicture: null,
fit: 'cover',
},
},
placement: 'side',
@ -122,14 +116,10 @@ export const poster = defineObject({
},
onOptionsUpdated: ([k, v]) => {
if (k === 'width' || k === 'height') {
applySize();
}
if (k === 'customPicture') {
applyCustomPicture();
}
if (k === 'fit') {
applyFit();
switch (k) {
case 'width':
case 'height': applySize(); break;
case 'customPicture': applyCustomPicture(); break;
}
},
interactions: {},

View file

@ -16,15 +16,9 @@ export const tabletopFlag = defineObject({
type: 'image',
label: 'Custom picture',
},
fit: {
type: 'enum',
label: 'Custom picture fit',
enum: ['cover', 'contain', 'stretch'],
},
},
default: {
customPicture: null,
fit: 'cover',
},
},
placement: 'top',
@ -80,7 +74,6 @@ export const tabletopFlag = defineObject({
onOptionsUpdated: ([k, v]) => {
switch (k) {
case 'customPicture': applyCustomPicture(); break;
case 'fit': applyFit(); break;
}
},
interactions: {},

View file

@ -34,17 +34,11 @@ export const tabletopGlassPictureFrame = defineObject({
type: 'image',
label: 'Custom picture',
},
fit: {
type: 'enum',
label: 'Custom picture fit',
enum: ['cover', 'contain', 'stretch'],
},
},
default: {
width: 0.1,
height: 0.1,
customPicture: null,
fit: 'cover',
},
},
placement: 'top',
@ -130,9 +124,6 @@ export const tabletopGlassPictureFrame = defineObject({
case 'customPicture':
applyCustomPicture();
break;
case 'fit':
applyFit();
break;
}
},
interactions: {},

View file

@ -27,17 +27,11 @@ export const tabletopLcdButtonsController = defineObject({
type: 'image',
label: 'Custom picture',
},
fit: {
type: 'enum',
label: 'Custom picture fit',
enum: ['cover', 'contain', 'stretch'],
},
},
default: {
bodyColor: [0.05, 0.05, 0.05],
screenBrightness: 0.5,
customPicture: null,
fit: 'cover',
},
},
placement: 'top',
@ -111,7 +105,6 @@ export const tabletopLcdButtonsController = defineObject({
case 'bodyColor': applyBodyColor(); break;
case 'screenBrightness': applyScreenBrightness(); break;
case 'customPicture': applyCustomPicture(); break;
case 'fit': applyFit(); break;
}
},
interactions: {},

View file

@ -64,11 +64,6 @@ export const tabletopPictureFrame = defineObject({
type: 'image',
label: 'Custom picture',
},
fit: {
type: 'enum',
label: 'Custom picture fit',
enum: ['cover', 'contain', 'stretch'],
},
},
default: {
frameColor: [0.71, 0.58, 0.39],
@ -79,7 +74,6 @@ export const tabletopPictureFrame = defineObject({
matHThickness: 0,
matVThickness: 0,
customPicture: null,
fit: 'cover',
},
},
placement: 'top',
@ -204,67 +198,18 @@ export const tabletopPictureFrame = defineObject({
},
onOptionsUpdated: ([k, v]) => {
if (k === 'frameColor') {
applyFrameColor();
}
if (k === 'width' || k === 'height') {
applySize();
}
if (k === 'frameThickness') {
applyFrameThickness();
}
if (k === 'depth') {
applyDepth();
}
if (k === 'matHThickness' || k === 'matVThickness') {
applyMatThickness();
}
if (k === 'customPicture') {
applyCustomPicture();
}
if (k === 'fit') {
applyFit();
switch (k) {
case 'frameColor': applyFrameColor(); break;
case 'width':
case 'height': applySize(); break;
case 'frameThickness': applyFrameThickness(); break;
case 'depth': applyDepth(); break;
case 'matHThickness':
case 'matVThickness': applyMatThickness(); break;
case 'customPicture': applyCustomPicture(); break;
}
},
interactions: {},
};
},
});
/*
const applyDirection = () => {
if (options.direction === 'vertical') {
frameMesh.rotation.z = 0;
matMesh.rotation.z = 0;
coverMesh.rotation.z = 0;
pictureMesh.rotation.z = 0;
uvs[6] = ax;
uvs[7] = ay;
uvs[2] = bx;
uvs[3] = by;
uvs[4] = cx;
uvs[5] = cy;
uvs[0] = dx;
uvs[1] = dy;
} else if (options.direction === 'horizontal') {
frameMesh.rotation.z = -Math.PI / 2;
matMesh.rotation.z = -Math.PI / 2;
coverMesh.rotation.z = -Math.PI / 2;
pictureMesh.rotation.z = -Math.PI / 2;
uvs[6] = cy;
uvs[7] = cx;
uvs[2] = dy;
uvs[3] = dx;
uvs[4] = ay;
uvs[5] = ax;
uvs[0] = by;
uvs[1] = bx;
}
pictureMesh.updateVerticesData(BABYLON.VertexBuffer.UVKind, uvs);
};
*/

View file

@ -34,17 +34,11 @@ export const tapestry = defineObject({
type: 'image',
label: 'Custom picture',
},
fit: {
type: 'enum',
label: 'Custom picture fit',
enum: ['cover', 'contain', 'stretch'],
},
},
default: {
width: 0.15,
height: 0.15,
customPicture: null,
fit: 'cover',
},
},
placement: 'side',
@ -126,14 +120,10 @@ export const tapestry = defineObject({
},
onOptionsUpdated: ([k, v]) => {
if (k === 'width' || k === 'height') {
applySize();
}
if (k === 'customPicture') {
applyCustomPicture();
}
if (k === 'fit') {
applyFit();
switch (k) {
case 'width':
case 'height': applySize(); break;
case 'customPicture': applyCustomPicture(); break;
}
},
interactions: {},

View file

@ -30,17 +30,11 @@ export const wallCanvas = defineObject({
type: 'image',
label: 'Custom picture',
},
fit: {
type: 'enum',
label: 'Custom picture fit',
enum: ['cover', 'contain', 'stretch'],
},
},
default: {
width: 0.15,
height: 0.15,
customPicture: null,
fit: 'cover',
},
},
placement: 'side',
@ -118,7 +112,6 @@ export const wallCanvas = defineObject({
applySize();
break;
case 'customPicture':
case 'fit':
applyCustomPicture();
break;
}

View file

@ -34,17 +34,11 @@ export const wallGlassPictureFrame = defineObject({
type: 'image',
label: 'Custom picture',
},
fit: {
type: 'enum',
label: 'Custom picture fit',
enum: ['cover', 'contain', 'stretch'],
},
},
default: {
width: 0.1,
height: 0.1,
customPicture: null,
fit: 'cover',
},
},
placement: 'wall',
@ -130,9 +124,6 @@ export const wallGlassPictureFrame = defineObject({
case 'customPicture':
applyCustomPicture();
break;
case 'fit':
applyFit();
break;
}
},
interactions: {},