Transparent multiplayer!?!

This commit is contained in:
Tony Garnock-Jones 2023-01-05 15:47:27 +01:00
parent bdafb77667
commit 3d6de0b9c8
13 changed files with 326 additions and 54 deletions

1
.gitignore vendored
View File

@ -7,3 +7,4 @@
/node_modules
/src.ts/
/src/gen/
/tsconfig.tsbuildinfo

View File

@ -10,6 +10,7 @@
; Create a dataspace entity, and register it with the gatekeeper with name `"syndicate"` and an
; empty secret key:
let ?ds = dataspace
<MainDataspace $ds>
<bind "syndicate" #x"" $ds>
? <nginx-command ?command> [

9
config/scene.pr Normal file
View File

@ -0,0 +1,9 @@
let ?sceneDs = dataspace
<require-service <config-watcher "scene" { config: $sceneDs }>>
? <MainDataspace ?ds> [
$ds [
<SceneHandle $sceneDs>
]
]

View File

@ -22,11 +22,11 @@
"devDependencies": {
"@preserves/core": "*",
"@preserves/schema": "*",
"@rollup/plugin-node-resolve": "15.0",
"@syndicate-lang/ts-plugin": "*",
"@syndicate-lang/tsc": "*",
"@rollup/plugin-node-resolve": "15.0",
"rollup-plugin-sourcemaps": "0.6",
"rollup": "3.8",
"rollup-plugin-sourcemaps": "0.6",
"tslib": "2.4",
"typescript": "4.9",
"typescript-language-server": "3.0"

View File

@ -1 +1,32 @@
version 1 .
Sprite = <sprite @name string @shape Shape> .
Shape = Sphere / Box / Light / Ground / Scale / Move / Rotate / @many [Shape ...] / Texture / Color / Name / Floor .
Sphere = <sphere> .
Box = <box> .
Light = <hemispheric-light @v Vector3> .
Ground = <ground @size Vector2> .
Vector2 = <v @x double @y double> .
Vector3 = <v @x double @y double @z double> .
Quaternion = <q @a double @b double @c double @d double> .
Scale = <scale @v Vector3 @shape Shape> .
Move = <move @v Vector3 @shape Shape> .
Rotate = @euler <rotate @v Vector3 @shape Shape> / @quaternion <rotate @q Quaternion @shape Shape> .
Texture =
/ @simple <texture @path string @shape Shape>
/ @uv <texture @path string @scale Vector2 @offset Vector2 @shape Shape>
.
Color =
/ @opaque <color @r double @g double @b double @shape Shape>
/ @transparent <color @r double @g double @b double @alpha double @shape Shape>
.
Name = <name @base string @shape Shape> .
Floor = <floor @shape Shape> .

1
scene/README.md Normal file
View File

@ -0,0 +1 @@
For scene files.

19
scene/example.pr Normal file
View File

@ -0,0 +1,19 @@
<sprite "light" <hemispheric-light <v 0.0 1.0 0.0>>>
<sprite "ground"
<texture
"textures/grass-256x256.jpg"
<v 0.1 0.1>
<v 0.0 0.0>
<floor <ground <v 30.0 30.0>>>>>
<sprite "box"
<move <v 1.0 1.5 3.0>
<texture "textures/grass-256x256.jpg" <floor <box>>>>>
<sprite "box2"
<move <v -1.0 0.5 3.0>
<floor
<color 1.0 0.0 0.0 0.5 <box>>>>>
[]

View File

@ -1,11 +1,11 @@
import {
AbstractMesh,
DualShockPad,
Engine,
FreeCamera,
FreeCameraGamepadInput,
Gamepad as b_Gamepad,
Mesh,
Quaternion,
Scene,
Vector3,
} from '@babylonjs/core/Legacy/legacy';
@ -58,16 +58,17 @@ if ((navigator as any).oscpu?.startsWith('Linux')) {
};
}
export type CreateScene = (canvas: HTMLCanvasElement, engine: Engine) => Promise<{
export type CreateScene = (canvas: HTMLCanvasElement, engine: Engine) => Promise<CreatedScene>;
export type CreatedScene = {
scene: Scene,
floorMeshes: Mesh[]
}>;
floorMeshes: () => Mesh[]
};
export async function startEngine(
createScene: CreateScene,
initialPos = new Vector3(0, 1.6, 0),
initialRotation = new Vector3(0, 0, 0).scaleInPlace(2 * Math.PI),
): Promise<void> {
): Promise<Scene> {
const canvas = document.getElementById("renderCanvas") as HTMLCanvasElement;
const engine = new Engine(canvas, true);
const { scene, floorMeshes } = await createScene(canvas, engine);
@ -99,12 +100,6 @@ export async function startEngine(
camera.applyGravity = true;
camera.ellipsoid = new Vector3(0.25, 0.8, 0.25);
scene.getNodes().forEach(n => {
if (n instanceof AbstractMesh) {
n.checkCollisions = true;
}
});
const enableVR = () => {
if (xrAvailable) {
xr.baseExperience.enterXRAsync('immersive-vr', 'local').then(() => {
@ -117,6 +112,7 @@ export async function startEngine(
document.body.onclick = enableVR;
let leanBase: { position: Vector3 } | null = null;
let recenterBase: { rotation: Quaternion } | null = null;
engine.runRenderLoop(() => {
@ -142,9 +138,10 @@ export async function startEngine(
if (sm && latch(gp, 0)) {
const ray = xr.baseExperience.camera.getForwardRay();
const hit = scene.pickWithRay(ray, m => floorMeshes.indexOf(m as any) !== -1);
const meshes = floorMeshes();
const hit = scene.pickWithRay(ray, m => meshes.indexOf(m as any) !== -1);
if (hit !== null) {
if (floorMeshes.indexOf(hit.pickedMesh as any) !== -1) {
if (meshes.indexOf(hit.pickedMesh as any) !== -1) {
if (hit.pickedPoint !== null) {
xr.baseExperience.camera.position =
hit.pickedPoint.add(new Vector3(0, 1.6, 0));
@ -172,8 +169,14 @@ export async function startEngine(
xr.baseExperience.camera.rotationQuaternion.copyFrom(r.toQuaternion());
}
}
if (sm && latch(gp, 4)) {
xr.baseExperience.camera.rotationQuaternion.copyFrom(initialRotation.toQuaternion());
if (sm) {
if (latch(gp, 4)) {
recenterBase = { rotation: xr.baseExperience.camera.rotationQuaternion.clone() };
}
if (buttonDown[4] && recenterBase) {
xr.baseExperience.camera.rotationQuaternion.copyFrom(recenterBase.rotation);
}
}
}
}
@ -181,4 +184,6 @@ export async function startEngine(
scene.render();
});
window.addEventListener("resize", () => engine.resize());
return scene;
}

View File

@ -2,38 +2,38 @@ import { Dataspace, Embedded, Reader, Ref, Schemas, Sturdy } from "@syndicate-la
import * as html from "@syndicate-lang/html";
import * as wsRelay from "@syndicate-lang/ws-relay";
import * as wakeDetector from './wake-detector.js';
import * as Shapes from './gen/shapes.js';
import {
Engine,
HemisphericLight,
Mesh,
MeshBuilder,
Scene,
Vector3,
} from '@babylonjs/core/Legacy/legacy';
import { box } from './shapes.js';
import { attachTexture } from './interiors.js';
import { startEngine } from './engine.js';
import { uuid } from './uuid.js';
import { activeFloorMeshes, ShapeTree } from './shapes.js';
import { startEngine, CreatedScene } from './engine.js';
async function createScene(_canvas: HTMLCanvasElement, engine: Engine): Promise<{
scene: Scene,
floorMeshes: Mesh[]
}> {
const scene = new Scene(engine);
assertion type SceneHandle(ds: Embedded<Ref>);
new HemisphericLight("light", new Vector3(0, 1, 0), scene);
function interpretScene(scene: Scene, sceneDs: Ref) {
at sceneDs {
during Shapes.Sprite({ "name": $name: string }) => spawn named `sprite:${name}` {
console.log('+shape', name);
on stop console.log('-shape', name);
spriteMain(name, scene, sceneDs);
}
}
}
const b = attachTexture(box('b', 1, 1, 1, 0, 2, 2), scene, 'papenweg-textures/individual/floor1.jpg');
return {
scene,
floorMeshes: [
b,
MeshBuilder.CreateGround("ground", {width:30, height:30}),
],
};
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 }) => {
console.log('=shape', name, shape);
currentShape = currentShape.reconcile(name, shape);
}
}
}
function wsurl(): string {
@ -41,13 +41,12 @@ function wsurl(): string {
return `${scheme}://${document.location.host}/ws`;
}
function bootApp(ds: Ref) {
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 this_instance = uuid();
const relayAddr = wsRelay.RelayAddress(Schemas.transportAddress.WebSocket(url));
during wsRelay.Resolved({
@ -60,17 +59,28 @@ function bootApp(ds: Ref) {
on message wakeDetector.WakeEvent() => {
send message wsRelay.ForceRelayDisconnect(relayAddr);
}
at remoteDs {
during SceneHandle($sceneDs_e: Embedded) => {
const sceneDs = sceneDs_e.embeddedValue;
interpretScene(scene, sceneDs);
}
}
}
}
}
}
window.addEventListener('load', async () => {
await startEngine(createScene);
const scene = await startEngine(
async (_canvas: HTMLCanvasElement, engine: Engine): Promise<CreatedScene> => ({
scene: new Scene(engine),
floorMeshes: () => activeFloorMeshes,
}));
Dataspace.boot(ds => {
html.boot(ds);
wsRelay.boot(ds, true);
wakeDetector.boot(ds);
bootApp(ds);
bootApp(ds, scene);
});
});

View File

@ -1,10 +1,25 @@
import {
AbstractMesh,
Color3,
CSG,
HemisphericLight,
Mesh,
MeshBuilder,
Node,
PhotoDome,
Quaternion,
Scene,
StandardMaterial,
Texture,
TransformNode,
Vector2,
Vector3,
} from '@babylonjs/core/Legacy/legacy';
import { Value, is } from "@syndicate-lang/core";
import * as Shapes from './gen/shapes.js';
export const activeFloorMeshes: Array<Mesh> = [];
export function adjust(f: (... csgs: CSG[]) => CSG, ... ms: Mesh[]): Mesh {
const csgs = ms.map(m => CSG.FromMesh(m));
@ -47,3 +62,182 @@ export function box(name: string, width: number, height: number, depth: number,
b.position = new Vector3(x ?? 0, y ?? b.scaling.y/2, z ?? 0);
return b;
}
//---------------------------------------------------------------------------
export class ShapeTree<N extends Node = Node> {
shapePreserve: Value;
cleanups: Array<() => void> = [];
constructor (
public scene: Scene,
public shape: Shapes.Shape,
public node: N,
) {
this.shapePreserve = Shapes.fromShape(this.shape);
if (this.node instanceof AbstractMesh) {
this.node.checkCollisions = true;
}
}
reconcile(name: string, shape: Shapes.Shape): ShapeTree {
if (is(Shapes.fromShape(shape), this.shapePreserve)) {
return this;
} else {
this.remove();
return build(name, this.scene, shape, {});
}
}
remove() {
this.node?.dispose(false, true);
}
static empty(name: string, scene: Scene): ShapeTree {
return ShapeTree.transform(name, scene, Shapes.Shape.many([]));
}
static transform(name: string, scene: Scene, shape: Shapes.Shape): ShapeTree<TransformNode> {
return new ShapeTree(scene, shape, new TransformNode(name, scene));
}
}
export function v2(v: Shapes.Vector2): Vector2 {
return new Vector2(v.x, v.y);
}
export function v3(v: Shapes.Vector3): Vector3 {
return new Vector3(v.x, v.y, v.z);
}
export function q(q: Shapes.Quaternion): Quaternion {
return new Quaternion(q.a, q.b, q.c, q.d);
}
export type MeshCustomizer = { [key: string]: ((m: ShapeTree<Mesh>) => void) };
function applyCustomizer(m: ShapeTree<Mesh>, c: MeshCustomizer) {
Object.values(c).forEach(f => f(m));
}
export function build(name: string, scene: Scene, shape: Shapes.Shape, customize: MeshCustomizer): ShapeTree {
switch (shape._variant) {
case "Sphere": {
const t = new ShapeTree<Mesh>(scene, shape, MeshBuilder.CreateSphere(name, {}, scene));
applyCustomizer(t, customize);
return t;
}
case "Box": {
const t = new ShapeTree<Mesh>(scene, shape, MeshBuilder.CreateBox(name, {}, scene));
applyCustomizer(t, customize);
return t;
}
case "Light":
return new ShapeTree(
scene,
shape,
new HemisphericLight(name, v3(shape.value.v), scene));
case "Ground": {
const v = v2(shape.value.size);
const t = new ShapeTree(
scene,
shape,
MeshBuilder.CreateGround(name, { width: v.x, height: v.y }, scene));
applyCustomizer(t, customize);
return t;
}
case "Scale": {
const t = ShapeTree.transform(name, scene, shape);
t.node.scaling = v3(shape.value.v);
build(name + '.inner', scene, shape.value.shape, customize).node.parent = t.node;
return t;
}
case "Move": {
const t = ShapeTree.transform(name, scene, shape);
t.node.position = v3(shape.value.v);
build(name + '.inner', scene, shape.value.shape, customize).node.parent = t.node;
return t;
}
case "Rotate": {
const t = ShapeTree.transform(name, scene, shape);
switch (shape.value._variant) {
case "euler":
t.node.rotation = v3(shape.value.v);
break;
case "quaternion":
t.node.rotationQuaternion = q(shape.value.q);
break;
}
build(name + '.inner', scene, shape.value.shape, customize).node.parent = t.node;
return t;
}
case "many": {
const t = ShapeTree.transform(name, scene, shape);
shape.value.forEach((s, i) => {
build(name + '[' + i + ']', scene, s, customize).node.parent = t.node;
});
return t;
}
case "Texture": {
const mat = new StandardMaterial(name + '.texture', scene);
const tex = new Texture(shape.value.path, scene);
mat.diffuseTexture = tex;
switch (shape.value._variant) {
case "simple":
break;
case "uv": {
const scale = v2(shape.value.scale);
const offset = v2(shape.value.offset);
tex.uScale = 1 / scale.x;
tex.vScale = 1 / scale.y;
tex.uOffset = offset.x;
tex.vOffset = offset.y;
break;
}
}
return build(name + '.inner', scene, shape.value.shape, {
... customize,
material: m => m.node.material = mat,
});
}
case "Color": {
const mat = new StandardMaterial(name + '.texture', scene);
mat.diffuseColor = new Color3(shape.value.r, shape.value.g, shape.value.b);
if (shape.value._variant === "transparent") mat.alpha = shape.value.alpha;
return build(name + '.inner', scene, shape.value.shape, {
... customize,
material: m => m.node.material = mat,
});
}
case "Name":
return build(name + '.' + shape.value.base, scene, shape.value.shape, customize);
case "Floor":
return build(name, scene, shape.value.shape, {
... customize,
floor: m => {
activeFloorMeshes.push(m.node);
m.cleanups.push(() => {
const i = activeFloorMeshes.indexOf(m.node);
if (i !== -1) activeFloorMeshes.splice(i, 1);
});
},
});
default:
((_shape: never) => {
console.error('Unsupported shape variant', shape);
throw new Error("Unsupported shape variant");
})(shape);
}
}

BIN
textures/grass-256x256.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

View File

@ -12,6 +12,7 @@
"module": "es6",
"sourceMap": true,
"strict": true,
"incremental": true,
"plugins": [
{ "name": "@syndicate-lang/ts-plugin" }
]

View File

@ -3,21 +3,21 @@
"@babylonjs/core@5":
version "5.39.0"
resolved "https://registry.yarnpkg.com/@babylonjs/core/-/core-5.39.0.tgz#8343fdc2ef105005bc564fc98bd36008e4301d08"
integrity sha512-bGDsxFbIq9GeiwpojdnQB58s1EvDxJSzUK5C8hL3Wj8UqK6tCELNTEHonmJ8KruilLGQRpm2mrrPZWEzUvAKkA==
version "5.41.0"
resolved "https://registry.yarnpkg.com/@babylonjs/core/-/core-5.41.0.tgz#ebc98f9d338d5dcbb4e81fcd3b6d515419532bca"
integrity sha512-PrY12n9IOql+9P/bFhEI7WTUqneTI0W9+ROKkwallqtTYku3XV7O5E7BXpdLJwrB/VufKApu6ErNoUb9Zhj9Cg==
"@preserves/core@*", "@preserves/core@>=0.20.2", "@preserves/core@^0.20.4":
version "0.20.4"
resolved "https://registry.yarnpkg.com/@preserves/core/-/core-0.20.4.tgz#b964d31c291a489c2682b5cf35f29b6677885000"
integrity sha512-4XSQwcbn66LQWl8ympnhumWboZiMGW3bIQmvSx4Bt04v3m1vtiJz/Pw/mltTgPahWWs/f6ADAZoNywD4w6aY0Q==
"@preserves/core@*", "@preserves/core@>=0.20.2", "@preserves/core@^0.20.5":
version "0.20.5"
resolved "https://registry.yarnpkg.com/@preserves/core/-/core-0.20.5.tgz#3b34693b1f5aff659639690725a703a95689f822"
integrity sha512-hnywtmY30swKSuHix3MbEoheyCh6plCERzmhnCQllrkhdT0hFm66iSr2PVjNsXAUNIwd3Rh3Vu2btgdjMK+Q9Q==
"@preserves/schema@*", "@preserves/schema@>=0.21.2":
version "0.21.6"
resolved "https://registry.yarnpkg.com/@preserves/schema/-/schema-0.21.6.tgz#572a2731712a6e503ad218639ef58099a61f2956"
integrity sha512-0lxqEN5qJDu/Ez84gpZxD3/XHF8LzrBcx2WMfRIqOGl4L4067l4N+xM01YNRTHjiY53weT3xyoWAizlqow5fBg==
version "0.21.7"
resolved "https://registry.yarnpkg.com/@preserves/schema/-/schema-0.21.7.tgz#09e388b8c582c3dc95018ebdfe990381dddabfa8"
integrity sha512-q2gncEOOY3qqs+i+Op5yZx89cckqN601vbEcBDfnQge5PB9HrOrfMk68Wq4w/rpdQpRyVqWhZNxLlKsANjgoOw==
dependencies:
"@preserves/core" "^0.20.4"
"@preserves/core" "^0.20.5"
"@types/glob" "^7.1"
"@types/minimatch" "^3.0"
chalk "^4.1"