2023-01-31 15:44:35 +00:00
|
|
|
import { is, fromJS, Dataflow, Dataspace, Embedded, Reader, Ref, Schemas, Sturdy, Turn } from "@syndicate-lang/core";
|
2023-01-05 10:26:06 +00:00
|
|
|
import * as html from "@syndicate-lang/html";
|
2023-01-05 15:45:19 +00:00
|
|
|
import * as timer from "@syndicate-lang/timer";
|
2023-01-05 10:26:06 +00:00
|
|
|
import * as wsRelay from "@syndicate-lang/ws-relay";
|
|
|
|
import * as wakeDetector from './wake-detector.js';
|
2023-01-05 14:47:27 +00:00
|
|
|
import * as Shapes from './gen/shapes.js';
|
2023-01-06 14:09:46 +00:00
|
|
|
import * as SceneProtocol from './gen/scene.js';
|
2023-01-06 13:01:47 +00:00
|
|
|
import { md5 } from './md5.js';
|
2023-01-10 14:09:19 +00:00
|
|
|
import { setupLog, log } from './log.js';
|
2023-01-05 10:26:06 +00:00
|
|
|
|
2023-01-03 13:18:34 +00:00
|
|
|
import {
|
2023-01-06 14:09:46 +00:00
|
|
|
AbstractMesh,
|
2023-01-03 13:18:34 +00:00
|
|
|
Mesh,
|
2023-01-09 13:40:05 +00:00
|
|
|
Vector3,
|
2023-01-03 13:18:34 +00:00
|
|
|
} from '@babylonjs/core/Legacy/legacy';
|
|
|
|
|
2023-01-12 18:44:23 +00:00
|
|
|
import { activeFloorMeshes, activeTouchableMeshes, ShapeTree, v3, scale3, buildSound, builder as B } from './shapes.js';
|
2023-01-09 13:40:05 +00:00
|
|
|
import { RunningEngine } from './engine.js';
|
2023-01-05 15:45:19 +00:00
|
|
|
import { uuid } from './uuid.js';
|
2023-01-03 13:18:34 +00:00
|
|
|
|
2023-01-05 14:47:27 +00:00
|
|
|
assertion type SceneHandle(ds: Embedded<Ref>);
|
2023-01-03 13:18:34 +00:00
|
|
|
|
2023-01-09 14:36:58 +00:00
|
|
|
function interpretScene(myId: string, runningEngine: RunningEngine, rootMesh: Mesh, sceneDs: Ref) {
|
2023-01-05 14:47:27 +00:00
|
|
|
at sceneDs {
|
|
|
|
during Shapes.Sprite({ "name": $name: string }) => spawn named `sprite:${name}` {
|
2023-01-05 15:45:19 +00:00
|
|
|
if (name === myId) {
|
|
|
|
console.log('ignoring sprite', name);
|
|
|
|
} else {
|
|
|
|
console.log('+shape', name);
|
|
|
|
on stop console.log('-shape', name);
|
2023-01-09 14:36:58 +00:00
|
|
|
spriteMain(name, runningEngine, rootMesh, sceneDs);
|
2023-01-05 15:45:19 +00:00
|
|
|
}
|
2023-01-05 14:47:27 +00:00
|
|
|
}
|
2023-01-12 15:34:54 +00:00
|
|
|
|
|
|
|
during SceneProtocol.AmbientSound({
|
|
|
|
"name": $name: string,
|
2023-02-02 20:58:53 +00:00
|
|
|
"spec": $spec: Shapes.SoundSpec
|
2023-01-12 15:34:54 +00:00
|
|
|
}) => spawn named `sound:${name}` {
|
|
|
|
const sound = buildSound(name, runningEngine.scene, spec, false);
|
|
|
|
on stop sound.dispose();
|
|
|
|
}
|
2023-01-05 14:47:27 +00:00
|
|
|
}
|
|
|
|
}
|
2023-01-03 13:18:34 +00:00
|
|
|
|
2023-01-09 14:36:58 +00:00
|
|
|
function spriteMain(spriteName: string, runningEngine: RunningEngine, rootMesh: Mesh, sceneDs: Ref) {
|
2023-01-05 14:47:27 +00:00
|
|
|
at sceneDs {
|
2023-01-09 14:36:58 +00:00
|
|
|
let currentShape = ShapeTree.empty(spriteName, runningEngine.scene);
|
2023-01-12 14:22:03 +00:00
|
|
|
currentShape.rootnode.parent = rootMesh;
|
2023-01-05 14:47:27 +00:00
|
|
|
on stop currentShape.remove();
|
2023-01-06 14:09:46 +00:00
|
|
|
during Shapes.Sprite({ "name": spriteName, "shape": $shape: Shapes.Shape }) => {
|
|
|
|
currentShape = currentShape.reconcile(spriteName, spriteName, shape);
|
2023-01-12 14:22:03 +00:00
|
|
|
currentShape.rootnode.parent = rootMesh;
|
2023-01-05 14:47:27 +00:00
|
|
|
}
|
2023-01-10 15:36:38 +00:00
|
|
|
|
|
|
|
during SceneProtocol.Gravity($direction: Shapes.Vector3) => {
|
2023-01-12 18:44:23 +00:00
|
|
|
runningEngine.applyGravity = true;
|
|
|
|
on stop runningEngine.applyGravity = false;
|
|
|
|
runningEngine.gravity = v3(direction);
|
2023-01-10 15:36:38 +00:00
|
|
|
}
|
2023-01-05 14:47:27 +00:00
|
|
|
}
|
2023-01-03 13:18:34 +00:00
|
|
|
}
|
|
|
|
|
2023-01-09 13:40:05 +00:00
|
|
|
async function enterScene(
|
2023-01-31 16:23:42 +00:00
|
|
|
routeField: Dataflow.Field<wsRelay.Noise.Route<Ref>>,
|
2023-01-09 13:40:05 +00:00
|
|
|
id: string,
|
|
|
|
runningEngine: RunningEngine,
|
|
|
|
ds: Ref,
|
|
|
|
sceneDs: Ref,
|
2023-01-09 15:10:22 +00:00
|
|
|
initialPosition: Vector3,
|
2023-01-09 13:40:05 +00:00
|
|
|
email: Dataflow.Field<string>,
|
|
|
|
) {
|
|
|
|
const currentSceneFacet = Turn.activeFacet;
|
|
|
|
|
|
|
|
console.log('enterScene', sceneDs);
|
|
|
|
|
|
|
|
const rootMesh = new Mesh('--root-' + (+new Date()), runningEngine.scene);
|
|
|
|
|
2023-01-12 18:44:23 +00:00
|
|
|
runningEngine.applyGravity = false;
|
2023-01-11 13:28:34 +00:00
|
|
|
runningEngine.camera.position = initialPosition.clone();
|
2023-01-09 13:40:05 +00:00
|
|
|
|
2023-01-09 14:36:58 +00:00
|
|
|
interpretScene(id, runningEngine, rootMesh, sceneDs);
|
|
|
|
|
2023-01-09 13:40:05 +00:00
|
|
|
let lastTouchTime = 0;
|
|
|
|
let lastTouchSpriteName = "";
|
2023-01-11 13:28:34 +00:00
|
|
|
runningEngine.onCollide = (other: AbstractMesh) => {
|
2023-01-09 13:40:05 +00:00
|
|
|
if (other.metadata?.touchable) {
|
|
|
|
const now = +new Date();
|
|
|
|
const touched = other.metadata?.spriteName ?? "";
|
|
|
|
if ((now - lastTouchTime > 500) || (lastTouchSpriteName !== touched)) {
|
|
|
|
currentSceneFacet.turn(() => {
|
|
|
|
at sceneDs {
|
|
|
|
send message SceneProtocol.Touch({
|
|
|
|
subject: id,
|
|
|
|
object: touched,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
lastTouchTime = now;
|
|
|
|
lastTouchSpriteName = touched;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2023-01-11 13:28:34 +00:00
|
|
|
const currentPosition = () => Shapes.Vector3(runningEngine.position);
|
|
|
|
const currentRotation = () => Shapes.Vector3(runningEngine.rotation);
|
2023-01-09 13:40:05 +00:00
|
|
|
|
|
|
|
field position: Shapes.Vector3 = currentPosition();
|
|
|
|
field rotation: Shapes.Vector3 = currentRotation();
|
|
|
|
|
|
|
|
const refreshPeriod = Math.floor(1000 / 10);
|
|
|
|
at ds {
|
|
|
|
on message timer.PeriodicTick(refreshPeriod) => {
|
|
|
|
const newPosition = currentPosition();
|
|
|
|
const newRotation = currentRotation();
|
|
|
|
if (!is(Shapes.fromVector3(position.value), Shapes.fromVector3(newPosition))) {
|
|
|
|
position.value = newPosition;
|
|
|
|
}
|
|
|
|
if (!is(Shapes.fromVector3(rotation.value), Shapes.fromVector3(newRotation))) {
|
|
|
|
rotation.value = newRotation;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
at sceneDs {
|
|
|
|
on message SceneProtocol.Touch({ "subject": id, "object": $o }) => {
|
|
|
|
react {
|
|
|
|
let needStop = true;
|
|
|
|
on asserted SceneProtocol.Portal({
|
|
|
|
"name": o,
|
2023-01-09 15:10:22 +00:00
|
|
|
"destination": $dest: SceneProtocol.PortalDestination,
|
|
|
|
"position": $targetPosition: Shapes.Vector3,
|
2023-01-09 13:40:05 +00:00
|
|
|
}) => {
|
2023-01-09 15:53:14 +00:00
|
|
|
const newPos = new Vector3(targetPosition.x,
|
|
|
|
targetPosition.y,
|
2023-01-10 15:36:38 +00:00
|
|
|
targetPosition.z)
|
|
|
|
.add(runningEngine.options.initialPos);
|
2023-01-09 13:40:05 +00:00
|
|
|
needStop = false;
|
|
|
|
switch (dest._variant) {
|
|
|
|
case "local":
|
2023-01-09 15:53:14 +00:00
|
|
|
if (dest.value === sceneDs) {
|
2023-01-11 13:28:34 +00:00
|
|
|
runningEngine.camera.position = newPos;
|
2023-01-09 15:53:14 +00:00
|
|
|
} else {
|
2023-01-11 10:57:00 +00:00
|
|
|
runningEngine.scene.removeMesh(rootMesh, true);
|
2023-01-09 15:53:14 +00:00
|
|
|
Turn.active.stop(currentSceneFacet, () => {
|
|
|
|
react {
|
2023-01-31 16:23:42 +00:00
|
|
|
enterScene(routeField,
|
|
|
|
id,
|
2023-01-09 15:53:14 +00:00
|
|
|
runningEngine,
|
|
|
|
ds,
|
|
|
|
dest.value,
|
|
|
|
newPos,
|
|
|
|
email);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2023-01-09 13:40:05 +00:00
|
|
|
break;
|
|
|
|
default:
|
2023-01-31 16:23:42 +00:00
|
|
|
console.log('jumping to remote portal', dest);
|
|
|
|
routeField.value = dest.value;
|
2023-01-09 13:40:05 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const checkFacet = Turn.activeFacet;
|
|
|
|
Turn.active.sync(sceneDs).then(() => checkFacet.turn(() => {
|
|
|
|
if (needStop) {
|
|
|
|
stop {}
|
|
|
|
}
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
assert Shapes.Sprite({
|
|
|
|
name: id,
|
|
|
|
shape: B.nonphysical(
|
|
|
|
B.move(position.value, B.many([
|
|
|
|
B.move({ x: 0, y: -0.9, z: 0 },
|
2023-01-09 15:53:14 +00:00
|
|
|
B.rotate(scale3({ x: 0, y: rotation.value.y, z: 0 }, 1 / (2 * Math.PI)),
|
2023-01-09 13:40:05 +00:00
|
|
|
B.scale({ x: 0.4, y: 1.4, z: 0.1 }, B.box()))),
|
2023-01-09 15:53:14 +00:00
|
|
|
B.rotate(scale3(rotation.value, 1 / (2 * Math.PI)),
|
2023-01-09 13:40:05 +00:00
|
|
|
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
|
|
|
|
}),
|
2023-01-09 15:53:14 +00:00
|
|
|
B.rotate({ x: 0, y: 0.5, z: 0 },
|
2023-01-09 13:40:05 +00:00
|
|
|
B.plane()))),
|
|
|
|
]))),
|
|
|
|
]))),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-05 10:26:06 +00:00
|
|
|
function wsurl(): string {
|
|
|
|
const scheme = (document.location.protocol.toLowerCase() === 'https:') ? 'wss' : 'ws';
|
|
|
|
return `${scheme}://${document.location.host}/ws`;
|
|
|
|
}
|
|
|
|
|
2023-01-09 10:27:55 +00:00
|
|
|
function bootApp(ds: Ref, runningEngine: RunningEngine) {
|
2023-01-05 10:26:06 +00:00
|
|
|
spawn named 'app' {
|
|
|
|
at ds {
|
2023-01-06 14:09:46 +00:00
|
|
|
const id = uuid();
|
|
|
|
|
2023-01-05 10:26:06 +00:00
|
|
|
const url = wsurl();
|
|
|
|
const relayAddr = wsRelay.RelayAddress(Schemas.transportAddress.WebSocket(url));
|
|
|
|
|
2023-01-06 14:09:46 +00:00
|
|
|
field email: string = localStorage.getItem('userEmail') ?? id;
|
|
|
|
const outerFacet = Turn.activeFacet;
|
|
|
|
const emailInput = document.getElementById('emailInput') as HTMLInputElement;
|
|
|
|
emailInput.value = email.value;
|
|
|
|
emailInput.addEventListener('keyup', () => outerFacet.turn(() => {
|
|
|
|
email.value = emailInput.value;
|
|
|
|
localStorage.setItem('userEmail', emailInput.value);
|
|
|
|
}));
|
|
|
|
|
2023-01-31 16:23:42 +00:00
|
|
|
field route: wsRelay.Noise.Route<Ref> = wsRelay.Noise.Route<Ref>({
|
2023-01-31 15:44:35 +00:00
|
|
|
"transports": [fromJS(relayAddr)],
|
2023-01-31 16:56:27 +00:00
|
|
|
"steps": [],
|
2023-01-31 15:44:35 +00:00
|
|
|
});
|
|
|
|
|
2023-01-31 16:23:42 +00:00
|
|
|
during wsRelay.Resolved({
|
|
|
|
"route": route.value,
|
2023-01-31 16:56:27 +00:00
|
|
|
"addr": $addr: wsRelay.RelayAddress,
|
2023-01-31 16:23:42 +00:00
|
|
|
"resolved": $remoteDs_e: Embedded,
|
|
|
|
}) => {
|
2023-01-05 10:26:06 +00:00
|
|
|
const remoteDs = remoteDs_e.embeddedValue;
|
|
|
|
|
2023-01-10 14:09:19 +00:00
|
|
|
setupLog(remoteDs, id, Symbol.for('vr-demo'));
|
|
|
|
log('connected');
|
|
|
|
|
2023-01-05 10:26:06 +00:00
|
|
|
on message wakeDetector.WakeEvent() => {
|
2023-01-31 16:56:27 +00:00
|
|
|
send message wsRelay.ForceRelayDisconnect(addr);
|
2023-01-05 10:26:06 +00:00
|
|
|
}
|
2023-01-05 14:47:27 +00:00
|
|
|
|
2023-01-09 13:40:05 +00:00
|
|
|
react {
|
|
|
|
at remoteDs {
|
|
|
|
stop on asserted SceneHandle($sceneDs_e: Embedded) => {
|
|
|
|
react {
|
2023-01-31 16:23:42 +00:00
|
|
|
enterScene(route,
|
|
|
|
id,
|
2023-01-09 15:10:22 +00:00
|
|
|
runningEngine,
|
|
|
|
ds,
|
|
|
|
sceneDs_e.embeddedValue,
|
|
|
|
runningEngine.options.initialPos,
|
|
|
|
email);
|
2023-01-06 14:09:46 +00:00
|
|
|
}
|
2023-01-05 15:45:19 +00:00
|
|
|
}
|
2023-01-05 14:47:27 +00:00
|
|
|
}
|
|
|
|
}
|
2023-01-05 10:26:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-03 13:18:34 +00:00
|
|
|
window.addEventListener('load', async () => {
|
2023-01-09 13:40:05 +00:00
|
|
|
const runningEngine = await RunningEngine.start({
|
|
|
|
floorMeshes: () => activeFloorMeshes,
|
|
|
|
touchableMeshes: () => activeTouchableMeshes,
|
|
|
|
});
|
2023-02-03 20:43:05 +00:00
|
|
|
(window as any).E = runningEngine;
|
2023-01-05 10:26:06 +00:00
|
|
|
Dataspace.boot(ds => {
|
|
|
|
html.boot(ds);
|
2023-01-05 15:45:19 +00:00
|
|
|
timer.boot(ds);
|
|
|
|
wsRelay.boot(ds, false);
|
2023-01-05 10:26:06 +00:00
|
|
|
wakeDetector.boot(ds);
|
2023-01-09 10:27:55 +00:00
|
|
|
bootApp(ds, runningEngine);
|
2023-01-05 10:26:06 +00:00
|
|
|
});
|
2023-01-03 13:18:34 +00:00
|
|
|
});
|