From e69ad01e700e80fa959dfee08cd556ca0d777ffd Mon Sep 17 00:00:00 2001 From: skylar Date: Sun, 1 Nov 2026 05:43:24 -0800 Subject: Yesod: self-hosted WebXR rooms Start a room in a world and hang out in VR from the headset's browser: A-Frame + Networked-A-Frame over EasyRTC, with a coturn relay that can hide everyone's IP address. Ships with the Campfire, Temple, Cabin, Balcony and Test room worlds. Co-Authored-By: Claude Opus 5.5 --- public/js/components/testroom.js | 72 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 public/js/components/testroom.js (limited to 'public/js/components/testroom.js') diff --git a/public/js/components/testroom.js b/public/js/components/testroom.js new file mode 100644 index 0000000..ba5a16a --- /dev/null +++ b/public/js/components/testroom.js @@ -0,0 +1,72 @@ +/* global AFRAME, THREE */ +// The world: a plain, brightly lit white room with a small table in the middle, +// for trying things out. The chess board on the table is its own entity +// (chess.js); its reset buttons and resetChess() are passed through here. +(function () { + const ROOM = { half: 4, height: 3 }; // 8 × 8 m, 3 m tall + const TABLE = { half: 0.4, height: 0.75, top: 0.04, leg: 0.05 }; + const MARGIN = 0.25; // how close your head gets to a wall + const LEAN = 0.08; // and to the table, so you can reach across it + + AFRAME.registerComponent('testroom', { + init() { + this.group = new THREE.Group(); + this.el.setObject3D('mesh', this.group); + this.seats = []; + this.spawn = { x: 0, z: 2.5, yaw: 0 }; + this.screen = { x: 0, y: 1.55, z: -3.85, yaw: 0, width: 3 }; // against the far wall + // Child entities start first, so the board is there already. + const chess = this.chess(); + this.touchables = chess ? chess.touchables : []; + + const white = new THREE.MeshLambertMaterial({ color: '#ffffff', side: THREE.BackSide }); + const room = new THREE.Mesh(new THREE.BoxGeometry(ROOM.half * 2, ROOM.height, ROOM.half * 2), white); + room.position.y = ROOM.height / 2; + this.group.add(room); + + const wood = new THREE.MeshLambertMaterial({ color: '#b08a60' }); + const top = new THREE.Mesh(new THREE.BoxGeometry(TABLE.half * 2, TABLE.top, TABLE.half * 2), wood); + top.position.y = TABLE.height - TABLE.top / 2; + this.group.add(top); + const legHeight = TABLE.height - TABLE.top; + const legGeometry = new THREE.BoxGeometry(TABLE.leg, legHeight, TABLE.leg); + const inset = TABLE.half - TABLE.leg; + for (const [x, z] of [[-1, -1], [1, -1], [-1, 1], [1, 1]]) { + const leg = new THREE.Mesh(legGeometry, wood); + leg.position.set(x * inset, legHeight / 2, z * inset); + this.group.add(leg); + } + }, + + chess() { + const el = this.el.querySelector('[chess]'); + return el && el.components.chess; + }, + + resetChess() { + const chess = this.chess(); + if (chess) chess.resetChess(); + }, + + remove() { + this.group.traverse((o) => { + if (o.geometry) o.geometry.dispose(); + if (o.material) o.material.dispose(); + }); + this.el.removeObject3D('mesh'); + }, + + // Keeps a head position inside the walls and out of the table. + confine(p) { + const wall = ROOM.half - MARGIN; + p.x = Math.min(wall, Math.max(-wall, p.x)); + p.z = Math.min(wall, Math.max(-wall, p.z)); + const t = TABLE.half + LEAN; + if (Math.abs(p.x) < t && Math.abs(p.z) < t) { + if (Math.abs(p.x) > Math.abs(p.z)) p.x = Math.sign(p.x) * t; + else p.z = (Math.sign(p.z) || 1) * t; + } + return p; + }, + }); +})(); -- cgit v1.2.3