This commit is contained in:
Tony Garnock-Jones 2023-01-09 14:40:05 +01:00
parent 4d59654eef
commit 16ed2a012e
9 changed files with 264 additions and 191 deletions

2
.gitignore vendored
View File

@ -1,8 +1,8 @@
*.js.map
/dummykey.crt
/dummykey.key
/dummykey.pem
/index.js
/index.js.map
/lib
/node_modules
/src.ts/

View File

@ -6,5 +6,7 @@ PortalDestination =
/ @remote <remote <ws @url string> @sturdyref sturdy.SturdyRef>
.
Gravity = <gravity @direction shapes.Vector3> .
; Message
Touch = <touch @subject string @object string> .

7
scene/00-rooms.pr Normal file
View File

@ -0,0 +1,7 @@
<Scene "lobby" $config>
<Room "other">
? <Room ?name> [
let ?scene = dataspace
<Scene $name $scene>
]

9
scene/05-common-scene.pr Normal file
View File

@ -0,0 +1,9 @@
? <Scene ?name ?scene> [
$scene [
? <Exit ?spriteName ?roomName> [
$config ? <Scene $roomName ?ds> [
$scene <portal $spriteName $ds>
]
]
]
]

65
scene/10-lobby.pr Normal file
View File

@ -0,0 +1,65 @@
? <Scene "lobby" ?scene> $scene [
<sprite "light" <hemispheric-light <v 0.1 1.0 0.0>>>
<gravity <v 0.0 -9.81 0.0>>
<sprite "ground"
<texture ["textures/grass-256x256.jpg"
<v 0.01 0.01>
<v 0.0 0.0>]
<floor <ground <v 300.0 300.0>>>>>
<sprite "box"
<move <v -6.0 1.0 8.0>
<scale <v 10.0 0.1 10.0>
<texture ["textures/oak-herringbone-5e80fb40b00c9-1200.jpg"
<v 0.1 0.1>
<v 0.0 0.0>
0.75]
<floor <box>>>>>>
<sprite "box2"
<move <v -500.0 0.5 3.0>
<floor
<color 0.0 0.0 1.0 1.0
<scale <v 1000.0 1.0 1.0> <box>>>>>>
<sprite "steps"
<color 0.0 0.5 0.0
<move <v 0.0 0.0 3.5>
<scale <v 1.0 1.0 3.0>
<texture ["textures/oak-herringbone-5e80fb40b00c9-1200.jpg"]
[
<move <v 0.0 0.25 0.0> <box>>
<move <v 1.0 0.0 0.0> <box>>
<move <v 2.0 -0.25 0.0> <box>>
]>>>>>
<Exit "ball" "other">
<sprite "ball"
<move <v 0.0 1.5 2.0>
<scale <v 1.0 1.0 1.0>
<color 0.0 1.0 0.0
<floor
<touchable
<sphere>>>>>>>
<sprite "plans"
<texture ["plans/signal-2022-12-27-125451_002.jpeg"]
<move <v 0.0 1.0 -10.0>
<rotate <v 0.5 0.0 0.0>
<scale <v 2.0 2.0 0.1>
<box>>>>>>
<sprite "tower"
<rotate <v 0.6 0.0 0.0>
<move <v -10.0 50.0 13.0>
<scale <v 3.0 100.0 3.0>
<floor
<color 0.5 0.5 0.0
<box>>>>>>>
<sprite "house"
<scale <v 10.0 4.0 15.0>
<floor
<move <v 1.5 0.5 -0.5> <box>>>>>
]

12
scene/10-other.pr Normal file
View File

@ -0,0 +1,12 @@
? <Scene "other" ?scene> $scene [
<sprite "light" <hemispheric-light <v 0.1 1.0 0.0>>>
<sprite "ground2"
<texture ["textures/oak-herringbone-5e80fb40b00c9-1200.jpg"
<v 0.01 0.01>
<v 0.0 0.0>]
<floor <ground <v 300.0 300.0>>>>>
<Exit "home" "lobby">
<sprite "home" <move <v 0.0 1.5 -5.0> <color 0.0 1.0 0.0 <touchable <sphere>>>>>
]

View File

@ -1,66 +0,0 @@
<sprite "light" <hemispheric-light <v 0.1 1.0 0.0>>>
<sprite "ground"
<texture ["textures/grass-256x256.jpg"
<v 0.01 0.01>
<v 0.0 0.0>]
<floor <ground <v 300.0 300.0>>>>>
<sprite "box"
<move <v -6.0 1.0 8.0>
<scale <v 10.0 0.1 10.0>
<texture ["textures/oak-herringbone-5e80fb40b00c9-1200.jpg"
<v 0.1 0.1>
<v 0.0 0.0>
0.75]
<floor <box>>>>>>
<sprite "box2"
<move <v -500.0 0.5 3.0>
<floor
<color 0.0 0.0 1.0 1.0
<scale <v 1000.0 1.0 1.0> <box>>>>>>
<sprite "steps"
<color 0.0 0.5 0.0
<move <v 0.0 0.0 3.5>
<scale <v 1.0 1.0 3.0>
<texture ["textures/oak-herringbone-5e80fb40b00c9-1200.jpg"]
[
<move <v 0.0 0.25 0.0> <box>>
<move <v 1.0 0.0 0.0> <box>>
<move <v 2.0 -0.25 0.0> <box>>
]>>>>>
let ?ballDs = dataspace
<portal "ball" $ballDs>
<sprite "ball"
<move <v 0.0 1.5 2.0>
<scale <v 1.0 1.0 1.0>
<color 0.0 1.0 0.0
<floor
<touchable
<sphere>>>>>>>
<sprite "plans"
<texture ["plans/signal-2022-12-27-125451_002.jpeg"]
<move <v 0.0 1.0 -10.0>
<rotate <v 0.5 0.0 0.0>
<scale <v 2.0 2.0 0.1>
<box>>>>>>
<sprite "tower"
<rotate <v 0.6 0.0 0.0>
<move <v -10.0 50.0 13.0>
<scale <v 3.0 100.0 3.0>
<floor
<color 0.5 0.5 0.0
<box>>>>>>>
<sprite "house"
<scale <v 10.0 4.0 15.0>
<floor
<move <v 1.5 0.5 -0.5> <box>>>>>
[]

View File

@ -45,9 +45,7 @@ if ((navigator as any).oscpu?.startsWith('Linux')) {
};
}
export type CreateScene = (engine: Engine) => Promise<CreatedScene>;
export type CreatedScene = {
scene: Scene,
export type Interactivity = {
floorMeshes: () => Mesh[],
touchableMeshes: () => Mesh[],
};
@ -87,9 +85,9 @@ export class GamepadState {
}
export class RunningEngine {
camera: FreeCamera;
xrSessionManager: WebXRSessionManager | null;
gamepadInput: FreeCameraGamepadInput;
camera!: FreeCamera;
xrSessionManager: WebXRSessionManager | null = null;
gamepadInput!: FreeCameraGamepadInput;
leanBase: { position: Vector3 } | null = null;
recenterBase: { rotation: Quaternion } | null = null;
@ -97,7 +95,7 @@ export class RunningEngine {
padStates: Map<Gamepad, GamepadState> = new Map();
static async start(
createScene: CreateScene,
interactivity: Interactivity,
options0: Partial<EngineOptions> = {},
): Promise<RunningEngine> {
const options = Object.assign({
@ -106,15 +104,16 @@ export class RunningEngine {
canvas: document.getElementById("renderCanvas") as HTMLCanvasElement,
}, options0);
const engine = new Engine(options.canvas, true);
const createdScene = await createScene(engine);
const xr = await createdScene.scene.createDefaultXRExperienceAsync({});
return new RunningEngine(options, engine, createdScene, xr);
const scene = new Scene(engine);
const xr = await scene.createDefaultXRExperienceAsync({});
return new RunningEngine(options, engine, interactivity, scene, xr);
}
private constructor (
public options: EngineOptions,
public engine: Engine,
public createdScene: CreatedScene,
public interactivity: Interactivity,
public scene: Scene,
public xr: WebXRDefaultExperience,
) {
this.xrSessionManager = this.xr.baseExperience?.sessionManager ?? null;
@ -124,7 +123,7 @@ export class RunningEngine {
} else {
this.camera = new FreeCamera("camera",
this.options.initialPos,
this.createdScene.scene);
this.scene);
this.camera.rotation = this.options.initialRotation;
this.camera.minZ = 0.1;
this.camera.inertia = 0.75;
@ -138,12 +137,13 @@ export class RunningEngine {
this.camera.inputs.add(this.gamepadInput);
this.gamepadInput.attachControl();
this.createdScene.scene.gravity = new Vector3(0, -9.81 / 90, 0);
this.createdScene.scene.collisionsEnabled = true;
this.scene.gravity = new Vector3(0, -9.81 / 90, 0);
this.scene.collisionsEnabled = true;
this.camera.checkCollisions = true;
this.camera.applyGravity = true;
this.camera.ellipsoid = new Vector3(0.25, 0.8, 0.25);
this.camera.applyGravity = false;
(this.camera as any)._needMoveForGravity = true;
this.camera.ellipsoid = new Vector3(0.25, this.options.initialPos.y / 2, 0.25);
if (this.options.canvas) {
const canvas = this.options.canvas;
@ -158,8 +158,9 @@ export class RunningEngine {
Array.from(navigator.getGamepads()).forEach(gp => {
if (gp !== null) this.checkGamepadInput(gp);
});
this.createdScene.scene.render();
this.scene.render();
});
window.addEventListener("resize", () => this.engine.resize());
}
@ -226,8 +227,8 @@ export class RunningEngine {
xrTouch() {
const ray = this.xr.baseExperience.camera.getForwardRay();
const meshes = this.createdScene.touchableMeshes();
const hit = this.createdScene.scene.pickWithRay(ray, m => meshes.indexOf(m as any) !== -1);
const meshes = this.interactivity.touchableMeshes();
const hit = this.scene.pickWithRay(ray, m => meshes.indexOf(m as any) !== -1);
if (hit === null) return;
if (meshes.indexOf(hit.pickedMesh as any) === -1) return;
@ -239,8 +240,8 @@ export class RunningEngine {
if (!this.xrSessionManager) return;
const ray = this.xr.baseExperience.camera.getForwardRay();
const meshes = this.createdScene.floorMeshes();
const hit = this.createdScene.scene.pickWithRay(ray, m => meshes.indexOf(m as any) !== -1);
const meshes = this.interactivity.floorMeshes();
const hit = this.scene.pickWithRay(ray, m => meshes.indexOf(m as any) !== -1);
if (hit === null) return;
if (meshes.indexOf(hit.pickedMesh as any) === -1) return;

View File

@ -1,4 +1,4 @@
import { is, Dataspace, Embedded, Reader, Ref, Schemas, Sturdy, Turn } from "@syndicate-lang/core";
import { is, embed, stringify, Dataflow, Dataspace, Embedded, Reader, Ref, Schemas, Sturdy, Turn, Value } 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";
@ -14,16 +14,17 @@ import {
Mesh,
Scene,
TargetCamera,
Vector3,
WebXRCamera,
} from '@babylonjs/core/Legacy/legacy';
import { activeFloorMeshes, activeTouchableMeshes, ShapeTree, builder as B } from './shapes.js';
import { RunningEngine, CreatedScene } from './engine.js';
import { RunningEngine } from './engine.js';
import { uuid } from './uuid.js';
assertion type SceneHandle(ds: Embedded<Ref>);
function interpretScene(myId: string, scene: Scene, sceneDs: Ref) {
function interpretScene(myId: string, scene: Scene, rootMesh: Mesh, sceneDs: Ref) {
at sceneDs {
during Shapes.Sprite({ "name": $name: string }) => spawn named `sprite:${name}` {
if (name === myId) {
@ -31,22 +32,153 @@ function interpretScene(myId: string, scene: Scene, sceneDs: Ref) {
} else {
console.log('+shape', name);
on stop console.log('-shape', name);
spriteMain(name, scene, sceneDs);
spriteMain(name, scene, rootMesh, sceneDs);
}
}
}
}
function spriteMain(spriteName: string, scene: Scene, sceneDs: Ref) {
function spriteMain(spriteName: string, scene: Scene, rootMesh: Mesh, sceneDs: Ref) {
at sceneDs {
let currentShape = ShapeTree.empty(spriteName, scene);
currentShape.node.parent = rootMesh;
on stop currentShape.remove();
during Shapes.Sprite({ "name": spriteName, "shape": $shape: Shapes.Shape }) => {
currentShape = currentShape.reconcile(spriteName, spriteName, shape);
currentShape.node.parent = rootMesh;
}
}
}
async function enterScene(
id: string,
runningEngine: RunningEngine,
ds: Ref,
sceneDs: Ref,
email: Dataflow.Field<string>,
) {
const currentSceneFacet = Turn.activeFacet;
console.log('enterScene', sceneDs);
const rootMesh = new Mesh('--root-' + (+new Date()), runningEngine.scene);
interpretScene(id, runningEngine.scene, rootMesh, sceneDs);
const camera = runningEngine.camera;
camera.applyGravity = false;
camera.position = runningEngine.options.initialPos;
let lastTouchTime = 0;
let lastTouchSpriteName = "";
camera.onCollide = (other: AbstractMesh) => {
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;
}
}
};
const currentPosition = () => Shapes.Vector3(camera.position);
const currentRotation = () => Shapes.Vector3(
camera instanceof WebXRCamera
? camera.rotationQuaternion.toEulerAngles()
: camera.rotation);
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 {
during SceneProtocol.Gravity($direction: Shapes.Vector3) => {
camera.applyGravity = true;
on stop camera.applyGravity = false;
const frameRate = 60;
runningEngine.scene.gravity = new Vector3(direction.x / frameRate,
direction.y / frameRate,
direction.z / frameRate);
}
on message SceneProtocol.Touch({ "subject": id, "object": $o }) => {
console.log('touched', o);
react {
let needStop = true;
on asserted SceneProtocol.Portal({
"name": o,
"destination": $dest: SceneProtocol.PortalDestination
}) => {
needStop = false;
switch (dest._variant) {
case "local":
runningEngine.scene.removeMesh(rootMesh, true);
Turn.active.stop(currentSceneFacet, () => {
react {
enterScene(id, runningEngine, ds, dest.value, email);
}
});
break;
default:
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 },
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()))),
]))),
]))),
});
}
}
function wsurl(): string {
const scheme = (document.location.protocol.toLowerCase() === 'https:') ? 'wss' : 'ws';
return `${scheme}://${document.location.host}/ws`;
@ -82,99 +214,12 @@ function bootApp(ds: Ref, runningEngine: RunningEngine) {
send message wsRelay.ForceRelayDisconnect(relayAddr);
}
at remoteDs {
during SceneHandle($sceneDs_e: Embedded) => {
const thisFacet = Turn.activeFacet;
const sceneDs = sceneDs_e.embeddedValue;
interpretScene(id, runningEngine.createdScene.scene, sceneDs);
const camera = runningEngine.camera;
camera.onCollide = (other: AbstractMesh) => {
if (other.metadata?.touchable) {
thisFacet.turn(() => {
at sceneDs {
send message SceneProtocol.Touch({
subject: id,
object: other.metadata?.spriteName,
});
}
});
react {
at remoteDs {
stop on asserted SceneHandle($sceneDs_e: Embedded) => {
react {
enterScene(id, runningEngine, ds, sceneDs_e.embeddedValue, email);
}
};
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) => {
if (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;
}
}
}
}
at sceneDs {
on message SceneProtocol.Touch({ "subject": id, "object": $o }) => {
console.log('touch!', o);
react {
on stop console.log('portal check ending', o);
stop on asserted SceneProtocol.Portal({
"name": o,
"destination": $dest: SceneProtocol.PortalDestination
}) => react {
console.log('portal!', dest);
switch (dest._variant) {
case "local":
at dest.value {
assert 909909909;
}
break;
default:
break;
}
}
const checkFacet = Turn.activeFacet;
Turn.active.sync(sceneDs).then(() => checkFacet.turn(() => {
console.log('synced');
stop {}
}));
}
}
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()))),
]))),
]))),
});
}
}
}
@ -184,12 +229,10 @@ function bootApp(ds: Ref, runningEngine: RunningEngine) {
}
window.addEventListener('load', async () => {
const runningEngine = await RunningEngine.start(
async (engine: Engine): Promise<CreatedScene> => ({
scene: new Scene(engine),
floorMeshes: () => activeFloorMeshes,
touchableMeshes: () => activeTouchableMeshes,
}));
const runningEngine = await RunningEngine.start({
floorMeshes: () => activeFloorMeshes,
touchableMeshes: () => activeTouchableMeshes,
});
Dataspace.boot(ds => {
html.boot(ds);
timer.boot(ds);