diff --git a/packages/frontend/src/pages/room.vue b/packages/frontend/src/pages/room.vue index ac5eb4bceb..c7dfa480dc 100644 --- a/packages/frontend/src/pages/room.vue +++ b/packages/frontend/src/pages/room.vue @@ -37,6 +37,7 @@ onMounted(() => { type: 'cardboard-box2', position: [120, 31, 50], rotation: [0, 0.1, 0], + sticky: 'a', }, { id: '1', type: 'cardboard-box', diff --git a/packages/frontend/src/utility/room/engine.ts b/packages/frontend/src/utility/room/engine.ts index 024f0aeb84..85afbfbe6e 100644 --- a/packages/frontend/src/utility/room/engine.ts +++ b/packages/frontend/src/utility/room/engine.ts @@ -240,7 +240,7 @@ export class RoomEngine { private camera: BABYLON.UniversalCamera; private camera2: BABYLON.ArcRotateCamera; private intervalIds: number[] = []; - private objects: Map = new Map(); + private objectMeshs: Map = new Map(); private grabbing: BABYLON.AbstractMesh | null = null; private grabbingStartDistance: number | null = null; private grabbingGhost: BABYLON.AbstractMesh | null = null; @@ -389,17 +389,24 @@ export class RoomEngine { const mesh = this.scene.pick(this.scene.pointerX, this.scene.pointerY)?.pickedMesh; if (mesh != null) { const oid = mesh.metadata.objectId; - if (oid != null && this.objects.has(oid)) { - const o = this.objects.get(oid)!; + if (oid != null && this.objectMeshs.has(oid)) { + const o = this.objectMeshs.get(oid)!; // focus camera this.camera.setTarget(o.position); } } }); + this.canvas.addEventListener('keypress', (ev) => { + if (ev.code === 'KeyE') { + ev.preventDefault(); + ev.stopPropagation(); + this.grab(); + } + }); + if (_DEV_) { - const axes = new AxesViewer(this.scene, 5); - axes.scaleLines = 30; + const axes = new AxesViewer(this.scene, 30); axes.xAxis.position = new BABYLON.Vector3(0, 30, 0); axes.yAxis.position = new BABYLON.Vector3(0, 30, 0); axes.zAxis.position = new BABYLON.Vector3(0, 30, 0); @@ -432,17 +439,17 @@ export class RoomEngine { private handleSeeking() { this.highlightedObjectId = null; const ray = new BABYLON.Ray(this.camera.position, this.camera.getDirection(BABYLON.Axis.Z), 1000/*cm*/); - for (const [id, o] of this.objects.entries()) { + for (const [id, o] of this.objectMeshs.entries()) { for (const om of o.getChildMeshes()) { om.renderOutline = false; } } const hit = this.scene.pickWithRay(ray)!; if (hit.pickedMesh != null) { - const oid = hit.pickedMesh.metadata.objectId; - if (oid != null && this.objects.has(oid)) { + const oid = hit.pickedMesh.metadata?.objectId; + if (oid != null && this.objectMeshs.has(oid)) { this.highlightedObjectId = oid; - const o = this.objects.get(oid)!; + const o = this.objectMeshs.get(oid)!; for (const om of o.getChildMeshes()) { om.renderOutline = true; } @@ -454,7 +461,10 @@ export class RoomEngine { const dir = this.camera.getDirection(BABYLON.Axis.Z); this.grabbingGhost.position = this.camera.position.add(dir.scale(this.grabbingStartDistance)); + const stickyObjectIds = Array.from(this.def.objects.filter(o => o.sticky === this.grabbing!.metadata.objectId)).map(o => o.id); + let y = 0; + let sticky = null; for (const rcmb of this.roomCollisionMeshes.filter(m => m.name.startsWith('_COLLISION_FLOOR_'))) { const rcb = rcmb.getBoundingInfo().boundingBox; @@ -469,7 +479,25 @@ export class RoomEngine { } } - for (const [id, o] of this.objects.entries().filter(([_id, o]) => o !== this.grabbing)) { + const isStickyChild = (parent: BABYLON.AbstractMesh, target: BABYLON.AbstractMesh): boolean => { + const stickyObjectIds = Array.from(this.def.objects.filter(o => o.sticky === parent.metadata.objectId)).map(o => o.id); + for (const soid of stickyObjectIds) { + if (soid === target.metadata.objectId) return true; + const soMesh = this.objectMeshs.get(soid)!; + if (isStickyChild(soMesh, target)) return true; + } + return false; + }; + + const checkObjectEntries = this.objectMeshs.entries() + .filter(([_id, o]) => { + if (o === this.grabbing) return false; + if (stickyObjectIds.includes(_id)) return false; + if (isStickyChild(this.grabbing!, o)) return false; + return true; + }); + + for (const [id, o] of checkObjectEntries) { for (const om of o.getChildMeshes()) { const omb = om.getBoundingInfo().boundingBox; for (const tm of this.grabbing.getChildMeshes()) { @@ -478,12 +506,19 @@ export class RoomEngine { const topY = omb.maximumWorld.y; if (y === 0 || topY > y) { y = topY; + sticky = id; } } } } } + if (sticky != null) { + this.def.objects.find(o => o.id === this.grabbing!.metadata.objectId)!.sticky = sticky; + } else { + this.def.objects.find(o => o.id === this.grabbing!.metadata.objectId)!.sticky = null; + } + this.grabbing.position = this.grabbingGhost.position.clone(); //this.grabbing.position.x = Math.min(Math.max(this.grabbing.position.x, -(this.ROOM_SIZE / 2)), (this.ROOM_SIZE / 2)); //this.grabbing.position.z = Math.min(Math.max(this.grabbing.position.z, -(this.ROOM_SIZE / 2)), (this.ROOM_SIZE / 2)); @@ -507,6 +542,12 @@ export class RoomEngine { //); //this.grabbing.moveWithCollisions(displacementVector); //this.grabbing.position.y = y; + + for (const soid of stickyObjectIds) { + //const soMesh = this.objectMeshs.get(soid)!; + //const offset = this.grabbing!.position.subtract(soMeshStartPosition); + //soMesh.position = this.grabbing!.position.subtract(offset); + } } private async loadEnvModel() { @@ -566,7 +607,7 @@ export class RoomEngine { mesh.outlineColor = new BABYLON.Color3(1, 0, 0); } - this.objects.set(id, obj.meshes[0]); + this.objectMeshs.set(id, obj.meshes[0]); const objDef = OBJECTS[type]; if (objDef != null && objDef.onInit != null) { @@ -576,6 +617,17 @@ export class RoomEngine { public grab() { if (this.grabbing != null) { + // 親から先に外していく + const removeStickyParentRecursively = (mesh: BABYLON.AbstractMesh) => { + const stickyObjectIds = Array.from(this.def.objects.filter(o => o.sticky === mesh.metadata.objectId)).map(o => o.id); + for (const soid of stickyObjectIds) { + const soMesh = this.objectMeshs.get(soid)!; + soMesh.parent = null; + soMesh.position = soMesh.position.add(mesh.position); + removeStickyParentRecursively(soMesh); + } + }; + removeStickyParentRecursively(this.grabbing); this.grabbing = null; this.grabbingStartDistance = null; if (this.grabbingGhost != null) { @@ -585,7 +637,7 @@ export class RoomEngine { return; } if (this.highlightedObjectId == null) return; - const highlightedObject = this.objects.get(this.highlightedObjectId)!; + const highlightedObject = this.objectMeshs.get(this.highlightedObjectId)!; this.grabbing = highlightedObject; this.grabbingStartDistance = BABYLON.Vector3.Distance(this.camera.position, highlightedObject.position); this.grabbingGhost = highlightedObject.clone('ghost', null, false); @@ -598,6 +650,18 @@ export class RoomEngine { m.material = mat; } } + + // 子から先に適用していく + const setStickyParentRecursively = (mesh: BABYLON.AbstractMesh) => { + const stickyObjectIds = Array.from(this.def.objects.filter(o => o.sticky === mesh.metadata.objectId)).map(o => o.id); + for (const soid of stickyObjectIds) { + const soMesh = this.objectMeshs.get(soid)!; + setStickyParentRecursively(soMesh); + soMesh.parent = mesh; + soMesh.position = soMesh.position.subtract(mesh.position); + } + }; + setStickyParentRecursively(this.grabbing); } public destroy() {