house/src/index.ts

154 lines
6.8 KiB
TypeScript

import { is, Dataspace, Embedded, Reader, Ref, Schemas, Sturdy, Turn } from "@syndicate-lang/core";
import * as html from "@syndicate-lang/html";
import * as timer from "@syndicate-lang/timer";
import * as wsRelay from "@syndicate-lang/ws-relay";
import * as wakeDetector from './wake-detector.js';
import * as Shapes from './gen/shapes.js';
import { md5 } from './md5.js';
import {
Engine,
Mesh,
Scene,
TargetCamera,
WebXRCamera,
} from '@babylonjs/core/Legacy/legacy';
import { activeFloorMeshes, ShapeTree, builder as B } from './shapes.js';
import { startEngine, CreatedScene } from './engine.js';
import { uuid } from './uuid.js';
assertion type SceneHandle(ds: Embedded<Ref>);
function interpretScene(myId: string, scene: Scene, sceneDs: Ref) {
at sceneDs {
during Shapes.Sprite({ "name": $name: string }) => spawn named `sprite:${name}` {
if (name === myId) {
console.log('ignoring sprite', name);
} else {
console.log('+shape', name);
on stop console.log('-shape', name);
spriteMain(name, scene, sceneDs);
}
}
}
}
function spriteMain(name: string, scene: Scene, sceneDs: Ref) {
at sceneDs {
let currentShape = ShapeTree.empty(name, scene);
on stop currentShape.remove();
during Shapes.Sprite({ "name": name, "shape": $shape: Shapes.Shape }) => {
currentShape = currentShape.reconcile(name, shape);
}
}
}
function wsurl(): string {
const scheme = (document.location.protocol.toLowerCase() === 'https:') ? 'wss' : 'ws';
return `${scheme}://${document.location.host}/ws`;
}
function bootApp(ds: Ref, scene: Scene) {
spawn named 'app' {
at ds {
const url = wsurl();
const serverCap = Sturdy.asSturdyRef(new Reader<Ref>(
'<ref "syndicate" [] #[pkgN9TBmEd3Q04grVG4Zdw==]>').next());
const relayAddr = wsRelay.RelayAddress(Schemas.transportAddress.WebSocket(url));
during wsRelay.Resolved({
"addr": relayAddr,
"sturdyref": serverCap,
"resolved": $remoteDs_e: Embedded,
}) => {
const remoteDs = remoteDs_e.embeddedValue;
on message wakeDetector.WakeEvent() => {
send message wsRelay.ForceRelayDisconnect(relayAddr);
}
at remoteDs {
during SceneHandle($sceneDs_e: Embedded) => {
const id = uuid();
const thisFacet = Turn.activeFacet;
const sceneDs = sceneDs_e.embeddedValue;
interpretScene(id, scene, sceneDs);
field position: Shapes.Vector3 = Shapes.Vector3({ x:0, y:0, z:0 });
field rotation: Shapes.Vector3 = Shapes.Vector3({ x:0, y:0, z:0 });
at ds {
const refreshPeriod = Math.floor(1000 / 10);
on message timer.PeriodicTick(refreshPeriod) => {
const camera = scene.cameras[0];
if (camera && camera instanceof TargetCamera) {
const newPosition = Shapes.Vector3(camera.position);
const newRotation = Shapes.Vector3(camera instanceof WebXRCamera
? camera.rotationQuaternion.toEulerAngles()
: camera.rotation);
if (!is(Shapes.fromVector3(position.value), Shapes.fromVector3(newPosition))) {
position.value = newPosition;
}
if (!is(Shapes.fromVector3(rotation.value), Shapes.fromVector3(newRotation))) {
rotation.value = newRotation;
}
}
}
}
field email: string = id;
const emailInput = document.getElementById('emailInput') as HTMLInputElement;
emailInput.value = id;
emailInput.addEventListener('keyup', () => thisFacet.turn(() => {
email.value = emailInput.value;
}));
at sceneDs {
assert Shapes.Sprite({
name: id,
shape: B.nonphysical(
B.move(position.value, B.many([
B.move({ x: 0, y: -0.9, z: 0 },
B.rotate({ x: 0, y: rotation.value.y, z: 0 },
B.scale({ x: 0.4, y: 1.4, z: 0.1 },
B.box()))),
B.rotate(rotation.value,
B.scale({ x: 0.15, y: 0.23, z: 0.18 }, B.many([
B.box(),
B.move({ x: 0, y: 0, z: 0.501 },
B.texture(
Shapes.TextureSpec.uvAlpha({
path: `https://www.gravatar.com/avatar/${md5(new TextEncoder().encode(email.value.trim()))}?s=256&d=wavatar`,
scale: Shapes.Vector2({ x:1, y:1 }),
offset: Shapes.Vector2({ x:0, y:0 }),
alpha: 1
}),
B.rotate({ x: 0, y: Math.PI, z: 0 },
B.plane()))),
]))),
]))),
});
}
}
}
}
}
}
}
window.addEventListener('load', async () => {
const scene = await startEngine(
async (_canvas: HTMLCanvasElement, engine: Engine): Promise<CreatedScene> => ({
scene: new Scene(engine),
floorMeshes: () => activeFloorMeshes,
}));
Dataspace.boot(ds => {
html.boot(ds);
timer.boot(ds);
wsRelay.boot(ds, false);
wakeDetector.boot(ds);
bootApp(ds, scene);
});
});