diff --git a/packages/frontend/assets/room/objects/all-in-one-pc/all-in-one-pc.blend b/packages/frontend/assets/room/objects/all-in-one-pc/all-in-one-pc.blend
index f30c5cddab..7852b784f4 100644
Binary files a/packages/frontend/assets/room/objects/all-in-one-pc/all-in-one-pc.blend and b/packages/frontend/assets/room/objects/all-in-one-pc/all-in-one-pc.blend differ
diff --git a/packages/frontend/assets/room/objects/all-in-one-pc/all-in-one-pc.glb b/packages/frontend/assets/room/objects/all-in-one-pc/all-in-one-pc.glb
index ed8af83c95..34d4257bcc 100644
Binary files a/packages/frontend/assets/room/objects/all-in-one-pc/all-in-one-pc.glb and b/packages/frontend/assets/room/objects/all-in-one-pc/all-in-one-pc.glb differ
diff --git a/packages/frontend/assets/room/objects/empty-bento/empty-bento.blend b/packages/frontend/assets/room/objects/empty-bento/empty-bento.blend
new file mode 100644
index 0000000000..f5ac218d53
Binary files /dev/null and b/packages/frontend/assets/room/objects/empty-bento/empty-bento.blend differ
diff --git a/packages/frontend/assets/room/objects/empty-bento/empty-bento.glb b/packages/frontend/assets/room/objects/empty-bento/empty-bento.glb
new file mode 100644
index 0000000000..078788f4d7
Binary files /dev/null and b/packages/frontend/assets/room/objects/empty-bento/empty-bento.glb differ
diff --git a/packages/frontend/assets/room/sfx/remove.mp3 b/packages/frontend/assets/room/sfx/remove.mp3
new file mode 100644
index 0000000000..a4deb28f51
Binary files /dev/null and b/packages/frontend/assets/room/sfx/remove.mp3 differ
diff --git a/packages/frontend/src/pages/room.vue b/packages/frontend/src/pages/room.vue
index 96f1e6c2b7..86ea118e9b 100644
--- a/packages/frontend/src/pages/room.vue
+++ b/packages/frontend/src/pages/room.vue
@@ -18,6 +18,8 @@ SPDX-License-Identifier: AGPL-3.0-only
(R)
Grid Snap: {{ engine.enableGridSnapping.value ? 'on' : 'off' }}
+
+ (X)
降りる (Q)
@@ -559,6 +561,11 @@ function addObject(ev: PointerEvent) {
})), ev.currentTarget ?? ev.target);
}
+function removeSelectedObject() {
+ engine.value?.removeSelectedObject();
+ canvas.value!.focus();
+}
+
function getHex(c: [number, number, number]) {
return `#${c.map(x => Math.round(x * 255).toString(16).padStart(2, '0')).join('')}`;
}
diff --git a/packages/frontend/src/utility/room/engine.ts b/packages/frontend/src/utility/room/engine.ts
index 53c8ba7346..5f54a2d2fb 100644
--- a/packages/frontend/src/utility/room/engine.ts
+++ b/packages/frontend/src/utility/room/engine.ts
@@ -789,7 +789,13 @@ export class RoomEngine {
}) {
const def = getObjectDef(args.type);
- const camelToKebab = (str: string) => str.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
+ // ex) hangingTShirt -> hanging-t-shirt
+ const camelToKebab = (s: string) => {
+ return s
+ .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
+ .replace(/([A-Z])([A-Z][a-z])/g, '$1-$2')
+ .toLowerCase();
+ };
const root = new BABYLON.Mesh(`object_${args.id}_${args.type}`, this.scene);
@@ -1208,6 +1214,23 @@ export class RoomEngine {
});
}
+ public removeSelectedObject() {
+ if (this.selected.value == null) return;
+
+ const objectId = this.selected.value.objectId;
+
+ this.objectMeshs.get(objectId)?.dispose();
+ this.objectMeshs.delete(objectId);
+ this.objectInstances.delete(objectId);
+ this.roomState.installedObjects = this.roomState.installedObjects.filter(o => o.id !== objectId);
+ this.selected.value = null;
+
+ sound.playUrl('/client-assets/room/sfx/remove.mp3', {
+ volume: 1,
+ playbackRate: 1,
+ });
+ }
+
public changeGrabbingDistance(delta: number) {
if (this.grabbingCtx == null) return;
this.grabbingCtx.distance -= delta;
diff --git a/packages/frontend/src/utility/room/object-defs.ts b/packages/frontend/src/utility/room/object-defs.ts
index 5c4acc395e..6f7c2b91fa 100644
--- a/packages/frontend/src/utility/room/object-defs.ts
+++ b/packages/frontend/src/utility/room/object-defs.ts
@@ -21,6 +21,7 @@ import { colorBox } from './objects/colorBox.js';
import { cupNoodle } from './objects/cupNoodle.js';
import { desk } from './objects/desk.js';
import { ductTape } from './objects/ductTape.js';
+import { emptyBento } from './objects/emptyBento.js';
import { energyDrink } from './objects/energyDrink.js';
import { facialTissue } from './objects/facialTissue.js';
import { keyboard } from './objects/keyboard.js';
@@ -71,6 +72,7 @@ export const OBJECT_DEFS = [
cupNoodle,
desk,
ductTape,
+ emptyBento,
energyDrink,
facialTissue,
keyboard,
diff --git a/packages/frontend/src/utility/room/objects/emptyBento.ts b/packages/frontend/src/utility/room/objects/emptyBento.ts
new file mode 100644
index 0000000000..c7540658de
--- /dev/null
+++ b/packages/frontend/src/utility/room/objects/emptyBento.ts
@@ -0,0 +1,21 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { defineObject } from '../engine.js';
+
+export const emptyBento = defineObject({
+ id: 'emptyBento',
+ name: 'Empty Bento',
+ options: {
+ schema: {},
+ default: {},
+ },
+ placement: 'top',
+ createInstance: () => {
+ return {
+ interactions: {},
+ };
+ },
+});