diff --git a/bot/bot.rkt b/bot/bot.rkt index db10331..0c46330 100644 --- a/bot/bot.rkt +++ b/bot/bot.rkt @@ -19,25 +19,27 @@ (define (bouncy-box) (define me (symbol->string (strong-gensym 'user))) - (define start-time (current-inexact-milliseconds)) - (define-field deadline start-time) - (define-field x (* (- (random) 0.5) 100)) + + (define x (* (- (random) 0.5) 100)) (define y0 (+ 1.0 (* (random) 10))) - (define-field y y0) - (define-field z (* (- (random) 0.5) 100)) - (at ds - (on (asserted (LaterThan (deadline))) - (deadline (+ (deadline) (/ 1000 10))) - (y (+ y0 (cos (/ (- (deadline) start-time) 1000.0 (/ 1 2 pi))))))) + (define z (* (- (random) 0.5) 100)) (define r (random)) (define g (random)) (define b (random)) + + (define-field y y0) + (define start-time (current-inexact-milliseconds)) + (define-field deadline start-time) + (at ds + (on (asserted (LaterThan (deadline))) + (deadline (+ (deadline) (/ 1000 30))) + (y (+ y0 (cos (/ (- (deadline) start-time) 1000.0 (/ 1 2 pi))))))) + (at scene - (assert (Sprite me (Move (Vector3 (x) (y) (z)) - (Color-opaque r g b (Box))))) - )) + (assert (Variable me 'y (y))) + (assert (Sprite me '(y) (Move (ImmediateVector3 x 'y z) (Color-opaque r g b (Box))))))) (for [(i 100)] (bouncy-box))))) - (run-tcp-client-relay ds #:hostname "vr.demo.leastfixedpoint.com" #:port 9001 + (run-tcp-client-relay ds #:hostname "localhost" #;"vr.demo.leastfixedpoint.com" #:port 9001 #:import (lambda (v) (on-connected (embedded-value v)))))) diff --git a/bot/schemas/scene.rkt b/bot/schemas/scene.rkt index 0ef34da..711ccd2 100644 --- a/bot/schemas/scene.rkt +++ b/bot/schemas/scene.rkt @@ -59,7 +59,10 @@ ((and dest (record 'gravity - (list (app shapes:parse-Vector3 (and ?direction (not (== eof))))))) + (list + (app + shapes:parse-LiteralVector3 + (and ?direction (not (== eof))))))) (Gravity ?direction)) (_ eof))) (define parse-Gravity! (parse-success-or-error 'parse-Gravity parse-Gravity)) @@ -89,7 +92,9 @@ (list (and ?name (? string?)) (app parse-PortalDestination (and ?destination (not (== eof)))) - (app shapes:parse-Vector3 (and ?position (not (== eof))))))) + (app + shapes:parse-LiteralVector3 + (and ?position (not (== eof))))))) (Portal ?name ?destination ?position)) (_ eof))) (define parse-Portal! (parse-success-or-error 'parse-Portal parse-Portal)) diff --git a/bot/schemas/shapes.rkt b/bot/schemas/shapes.rkt index a1d87e4..d40e77f 100644 --- a/bot/schemas/shapes.rkt +++ b/bot/schemas/shapes.rkt @@ -172,21 +172,21 @@ (record 'scale (list - (app parse-Vector3 (and ?v (not (== eof)))) + (app parse-LiteralVector3 (and ?v (not (== eof)))) (app parse-CSGExpr (and ?shape (not (== eof))))))) (CSGExpr-scale ?v ?shape)) ((and dest (record 'move (list - (app parse-Vector3 (and ?v (not (== eof)))) + (app parse-LiteralVector3 (and ?v (not (== eof)))) (app parse-CSGExpr (and ?shape (not (== eof))))))) (CSGExpr-move ?v ?shape)) ((and dest (record 'rotate (list - (app parse-Vector3 (and ?v (not (== eof)))) + (app parse-LiteralVector3 (and ?v (not (== eof)))) (app parse-CSGExpr (and ?shape (not (== eof))))))) (CSGExpr-rotate ?v ?shape)) ((and dest @@ -235,9 +235,9 @@ (record 'color (list - (exact->inexact (*->preserve ?r)) - (exact->inexact (*->preserve ?g)) - (exact->inexact (*->preserve ?b)) + (*->preserve ?r) + (*->preserve ?g) + (*->preserve ?b) (*->preserve ?shape)))))))) (struct Color-transparent @@ -253,10 +253,10 @@ (record 'color (list - (exact->inexact (*->preserve ?r)) - (exact->inexact (*->preserve ?g)) - (exact->inexact (*->preserve ?b)) - (exact->inexact (*->preserve ?alpha)) + (*->preserve ?r) + (*->preserve ?g) + (*->preserve ?b) + (*->preserve ?alpha) (*->preserve ?shape)))))))) (define (parse-Color input) (match @@ -265,23 +265,53 @@ (record 'color (list - (and ?r (? flonum?)) - (and ?g (? flonum?)) - (and ?b (? flonum?)) + (app parse-DoubleValue (and ?r (not (== eof)))) + (app parse-DoubleValue (and ?g (not (== eof)))) + (app parse-DoubleValue (and ?b (not (== eof)))) (app parse-Shape (and ?shape (not (== eof))))))) (Color-opaque ?r ?g ?b ?shape)) ((and dest (record 'color (list - (and ?r (? flonum?)) - (and ?g (? flonum?)) - (and ?b (? flonum?)) - (and ?alpha (? flonum?)) + (app parse-DoubleValue (and ?r (not (== eof)))) + (app parse-DoubleValue (and ?g (not (== eof)))) + (app parse-DoubleValue (and ?b (not (== eof)))) + (app parse-DoubleValue (and ?alpha (not (== eof)))) (app parse-Shape (and ?shape (not (== eof))))))) (Color-transparent ?r ?g ?b ?alpha ?shape)) (_ eof))) (define parse-Color! (parse-success-or-error 'parse-Color parse-Color)) + (define (DoubleValue? p) + (or (DoubleValue-immediate? p) (DoubleValue-reference? p))) + (struct + DoubleValue-immediate + (value) + #:transparent + #:methods + gen:preservable + ((define/generic *->preserve ->preserve) + (define (->preserve preservable) + (match + preservable + ((DoubleValue-immediate src) (exact->inexact (*->preserve src))))))) + (struct + DoubleValue-reference + (value) + #:transparent + #:methods + gen:preservable + ((define/generic *->preserve ->preserve) + (define (->preserve preservable) + (match preservable ((DoubleValue-reference src) (*->preserve src)))))) + (define (parse-DoubleValue input) + (match + input + ((and dest (? flonum?)) (DoubleValue-immediate dest)) + ((and dest (? symbol?)) (DoubleValue-reference dest)) + (_ eof))) + (define parse-DoubleValue! + (parse-success-or-error 'parse-DoubleValue parse-DoubleValue)) (struct External (path) @@ -324,7 +354,19 @@ (define parse-Floor! (parse-success-or-error 'parse-Floor parse-Floor)) (struct Ground - (size) + () + #:transparent + #:methods + gen:preservable + ((define/generic *->preserve ->preserve) + (define (->preserve preservable) + (match preservable ((Ground) (record 'ground (list))))))) + (define (parse-Ground input) + (match input ((and dest (record 'ground (list))) (Ground)) (_ eof))) + (define parse-Ground! (parse-success-or-error 'parse-Ground parse-Ground)) + (struct + ImmediateQuaternion + (a b c d) #:transparent #:methods gen:preservable @@ -332,17 +374,84 @@ (define (->preserve preservable) (match preservable - ((Ground ?size) (record 'ground (list (*->preserve ?size)))))))) - (define (parse-Ground input) + ((ImmediateQuaternion ?a ?b ?c ?d) + (record + 'q + (list + (*->preserve ?a) + (*->preserve ?b) + (*->preserve ?c) + (*->preserve ?d)))))))) + (define (parse-ImmediateQuaternion input) (match input ((and dest (record - 'ground - (list (app parse-Vector2 (and ?size (not (== eof))))))) - (Ground ?size)) + 'q + (list + (app parse-DoubleValue (and ?a (not (== eof)))) + (app parse-DoubleValue (and ?b (not (== eof)))) + (app parse-DoubleValue (and ?c (not (== eof)))) + (app parse-DoubleValue (and ?d (not (== eof))))))) + (ImmediateQuaternion ?a ?b ?c ?d)) (_ eof))) - (define parse-Ground! (parse-success-or-error 'parse-Ground parse-Ground)) + (define parse-ImmediateQuaternion! + (parse-success-or-error + 'parse-ImmediateQuaternion + parse-ImmediateQuaternion)) + (struct + ImmediateVector2 + (x y) + #:transparent + #:methods + gen:preservable + ((define/generic *->preserve ->preserve) + (define (->preserve preservable) + (match + preservable + ((ImmediateVector2 ?x ?y) + (record 'v (list (*->preserve ?x) (*->preserve ?y)))))))) + (define (parse-ImmediateVector2 input) + (match + input + ((and dest + (record + 'v + (list + (app parse-DoubleValue (and ?x (not (== eof)))) + (app parse-DoubleValue (and ?y (not (== eof))))))) + (ImmediateVector2 ?x ?y)) + (_ eof))) + (define parse-ImmediateVector2! + (parse-success-or-error 'parse-ImmediateVector2 parse-ImmediateVector2)) + (struct + ImmediateVector3 + (x y z) + #:transparent + #:methods + gen:preservable + ((define/generic *->preserve ->preserve) + (define (->preserve preservable) + (match + preservable + ((ImmediateVector3 ?x ?y ?z) + (record + 'v + (list (*->preserve ?x) (*->preserve ?y) (*->preserve ?z)))))))) + (define (parse-ImmediateVector3 input) + (match + input + ((and dest + (record + 'v + (list + (app parse-DoubleValue (and ?x (not (== eof)))) + (app parse-DoubleValue (and ?y (not (== eof)))) + (app parse-DoubleValue (and ?z (not (== eof))))))) + (ImmediateVector3 ?x ?y ?z)) + (_ eof))) + (define parse-ImmediateVector3! + (parse-success-or-error 'parse-ImmediateVector3 parse-ImmediateVector3)) (struct Light (v) @@ -364,6 +473,37 @@ (Light ?v)) (_ eof))) (define parse-Light! (parse-success-or-error 'parse-Light parse-Light)) + (struct + LiteralVector3 + (x y z) + #:transparent + #:methods + gen:preservable + ((define/generic *->preserve ->preserve) + (define (->preserve preservable) + (match + preservable + ((LiteralVector3 ?x ?y ?z) + (record + 'v + (list + (exact->inexact (*->preserve ?x)) + (exact->inexact (*->preserve ?y)) + (exact->inexact (*->preserve ?z))))))))) + (define (parse-LiteralVector3 input) + (match + input + ((and dest + (record + 'v + (list + (and ?x (? flonum?)) + (and ?y (? flonum?)) + (and ?z (? flonum?))))) + (LiteralVector3 ?x ?y ?z)) + (_ eof))) + (define parse-LiteralVector3! + (parse-success-or-error 'parse-LiteralVector3 parse-LiteralVector3)) (define (Mesh? p) (or (Mesh-Sphere? p) (Mesh-Box? p) @@ -508,36 +648,32 @@ (define (parse-Plane input) (match input ((and dest (record 'plane (list))) (Plane)) (_ eof))) (define parse-Plane! (parse-success-or-error 'parse-Plane parse-Plane)) + (define (Quaternion? p) + (or (Quaternion-immediate? p) (Quaternion-reference? p))) (struct - Quaternion - (a b c d) + Quaternion-immediate + (value) #:transparent #:methods gen:preservable ((define/generic *->preserve ->preserve) (define (->preserve preservable) - (match - preservable - ((Quaternion ?a ?b ?c ?d) - (record - 'q - (list - (exact->inexact (*->preserve ?a)) - (exact->inexact (*->preserve ?b)) - (exact->inexact (*->preserve ?c)) - (exact->inexact (*->preserve ?d))))))))) + (match preservable ((Quaternion-immediate src) (*->preserve src)))))) + (struct + Quaternion-reference + (value) + #:transparent + #:methods + gen:preservable + ((define/generic *->preserve ->preserve) + (define (->preserve preservable) + (match preservable ((Quaternion-reference src) (*->preserve src)))))) (define (parse-Quaternion input) (match input - ((and dest - (record - 'q - (list - (and ?a (? flonum?)) - (and ?b (? flonum?)) - (and ?c (? flonum?)) - (and ?d (? flonum?))))) - (Quaternion ?a ?b ?c ?d)) + ((app parse-ImmediateQuaternion (and dest (not (== eof)))) + (Quaternion-immediate dest)) + ((and dest (? symbol?)) (Quaternion-reference dest)) (_ eof))) (define parse-Quaternion! (parse-success-or-error 'parse-Quaternion parse-Quaternion)) @@ -873,7 +1009,7 @@ (define parse-Sphere! (parse-success-or-error 'parse-Sphere parse-Sphere)) (struct Sprite - (name shape) + (name formals shape) #:transparent #:methods gen:preservable @@ -881,8 +1017,13 @@ (define (->preserve preservable) (match preservable - ((Sprite ?name ?shape) - (record 'sprite (list (*->preserve ?name) (*->preserve ?shape)))))))) + ((Sprite ?name ?formals ?shape) + (record + 'sprite + (list + (*->preserve ?name) + (for/list ((item (in-list ?formals))) (*->preserve item)) + (*->preserve ?shape)))))))) (define (parse-Sprite input) (match input @@ -891,8 +1032,9 @@ 'sprite (list (and ?name (? string?)) + (list (and ?formals (? symbol?)) ...) (app parse-Shape (and ?shape (not (== eof))))))) - (Sprite ?name ?shape)) + (Sprite ?name ?formals ?shape)) (_ eof))) (define parse-Sprite! (parse-success-or-error 'parse-Sprite parse-Sprite)) (struct @@ -962,7 +1104,7 @@ (*->preserve ?path) (*->preserve ?scale) (*->preserve ?offset) - (exact->inexact (*->preserve ?alpha)))))))) + (*->preserve ?alpha))))))) (define (parse-TextureSpec input) (match input @@ -978,7 +1120,7 @@ (and ?path (? string?)) (app parse-Vector2 (and ?scale (not (== eof)))) (app parse-Vector2 (and ?offset (not (== eof)))) - (and ?alpha (? flonum?)))) + (app parse-DoubleValue (and ?alpha (not (== eof)))))) (TextureSpec-uvAlpha ?path ?scale ?offset ?alpha)) (_ eof))) (define parse-TextureSpec! @@ -1006,8 +1148,8 @@ (define parse-Touchable! (parse-success-or-error 'parse-Touchable parse-Touchable)) (struct - Vector2 - (x y) + Variable + (spriteName variable value) #:transparent #:methods gen:preservable @@ -1015,47 +1157,79 @@ (define (->preserve preservable) (match preservable - ((Vector2 ?x ?y) + ((Variable ?spriteName ?variable ?value) (record - 'v + 'variable (list - (exact->inexact (*->preserve ?x)) - (exact->inexact (*->preserve ?y))))))))) - (define (parse-Vector2 input) - (match - input - ((and dest (record 'v (list (and ?x (? flonum?)) (and ?y (? flonum?))))) - (Vector2 ?x ?y)) - (_ eof))) - (define parse-Vector2! (parse-success-or-error 'parse-Vector2 parse-Vector2)) - (struct - Vector3 - (x y z) - #:transparent - #:methods - gen:preservable - ((define/generic *->preserve ->preserve) - (define (->preserve preservable) - (match - preservable - ((Vector3 ?x ?y ?z) - (record - 'v - (list - (exact->inexact (*->preserve ?x)) - (exact->inexact (*->preserve ?y)) - (exact->inexact (*->preserve ?z))))))))) - (define (parse-Vector3 input) + (*->preserve ?spriteName) + (*->preserve ?variable) + (*->preserve ?value)))))))) + (define (parse-Variable input) (match input ((and dest (record - 'v + 'variable (list - (and ?x (? flonum?)) - (and ?y (? flonum?)) - (and ?z (? flonum?))))) - (Vector3 ?x ?y ?z)) + (and ?spriteName (? string?)) + (and ?variable (? symbol?)) + ?value))) + (Variable ?spriteName ?variable ?value)) + (_ eof))) + (define parse-Variable! + (parse-success-or-error 'parse-Variable parse-Variable)) + (define (Vector2? p) (or (Vector2-immediate? p) (Vector2-reference? p))) + (struct + Vector2-immediate + (value) + #:transparent + #:methods + gen:preservable + ((define/generic *->preserve ->preserve) + (define (->preserve preservable) + (match preservable ((Vector2-immediate src) (*->preserve src)))))) + (struct + Vector2-reference + (value) + #:transparent + #:methods + gen:preservable + ((define/generic *->preserve ->preserve) + (define (->preserve preservable) + (match preservable ((Vector2-reference src) (*->preserve src)))))) + (define (parse-Vector2 input) + (match + input + ((app parse-ImmediateVector2 (and dest (not (== eof)))) + (Vector2-immediate dest)) + ((and dest (? symbol?)) (Vector2-reference dest)) + (_ eof))) + (define parse-Vector2! (parse-success-or-error 'parse-Vector2 parse-Vector2)) + (define (Vector3? p) (or (Vector3-immediate? p) (Vector3-reference? p))) + (struct + Vector3-immediate + (value) + #:transparent + #:methods + gen:preservable + ((define/generic *->preserve ->preserve) + (define (->preserve preservable) + (match preservable ((Vector3-immediate src) (*->preserve src)))))) + (struct + Vector3-reference + (value) + #:transparent + #:methods + gen:preservable + ((define/generic *->preserve ->preserve) + (define (->preserve preservable) + (match preservable ((Vector3-reference src) (*->preserve src)))))) + (define (parse-Vector3 input) + (match + input + ((app parse-ImmediateVector3 (and dest (not (== eof)))) + (Vector3-immediate dest)) + ((and dest (? symbol?)) (Vector3-reference dest)) (_ eof))) (define parse-Vector3! (parse-success-or-error 'parse-Vector3 parse-Vector3))) diff --git a/protocols/schemas/scene.prs b/protocols/schemas/scene.prs index 320fe1a..4c882ca 100644 --- a/protocols/schemas/scene.prs +++ b/protocols/schemas/scene.prs @@ -1,12 +1,12 @@ version 1 . -Portal = . +Portal = . PortalDestination = / @local #!any / @remote noise.Route . -Gravity = . +Gravity = . AmbientSound = . diff --git a/protocols/schemas/shapes.prs b/protocols/schemas/shapes.prs index 6de3322..9295e94 100644 --- a/protocols/schemas/shapes.prs +++ b/protocols/schemas/shapes.prs @@ -1,6 +1,7 @@ version 1 . -Sprite = . +Sprite = . +Variable = . Shape = Mesh / Light / Scale / Move / Rotate / @many [Shape ...] / Texture / Color / Sound / Name / Floor / Nonphysical / Touchable / CSG / Skybox . @@ -8,15 +9,23 @@ Mesh = Sphere / Box / Ground / Plane / External . Sphere = . Box = . -Ground = . +Ground = . Plane = . External = . Light = . -Vector2 = . -Vector3 = . -Quaternion = . +DoubleValue = @immediate double / @reference symbol . + +ImmediateVector2 = . +ImmediateVector3 = . +ImmediateQuaternion = . + +LiteralVector3 = . + +Vector2 = @immediate ImmediateVector2 / @reference symbol . +Vector3 = @immediate ImmediateVector3 / @reference symbol . +Quaternion = @immediate ImmediateQuaternion / @reference symbol . Scale = . Move = . @@ -26,12 +35,12 @@ Texture = . TextureSpec = / @simple [@path string] / @uv [@path string @scale Vector2 @offset Vector2] -/ @uvAlpha [@path string @scale Vector2 @offset Vector2 @alpha double] +/ @uvAlpha [@path string @scale Vector2 @offset Vector2 @alpha DoubleValue] . Color = -/ @opaque -/ @transparent +/ @opaque +/ @transparent . Sound = . @@ -50,9 +59,9 @@ CSG = . CSGExpr = / -/ -/ -/ +/ +/ +/ / / / diff --git a/scene/lobby.pr b/scene/lobby.pr index 5e1f648..cb1abd9 100644 --- a/scene/lobby.pr +++ b/scene/lobby.pr @@ -1,14 +1,14 @@ ->> +>> > -> +> - ] - >>>> + >>>> - >>>>> - >>>>> - >>>>>> - @@ -42,8 +42,9 @@ ]>>>>> - + + [ >> ]>> - [ @@ -61,14 +62,14 @@ >> ]>>> - >>>>> - @@ -76,7 +77,7 @@ >>>>>> -; [ ; ; > @@ -86,12 +87,12 @@ ; >>> ; ]>> -; ; ; >>> - @@ -105,7 +106,7 @@ >>>>> ; -; ; diff --git a/scene/other.pr b/scene/other.pr index a00947f..dab2cae 100644 --- a/scene/other.pr +++ b/scene/other.pr @@ -1,6 +1,6 @@ ->> +>> ;> -> +> > @@ -11,7 +11,7 @@ ; >>>> > - [ @@ -20,7 +20,7 @@ >> ]>>> - diff --git a/src/index.ts b/src/index.ts index b479d10..1725b99 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -import { is, fromJS, Dataflow, Dataspace, Embedded, Reader, Ref, Schemas, Sturdy, Turn } from "@syndicate-lang/core"; +import { is, fromJS, Dataflow, Dataspace, Embedded, Ref, Schemas, 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"; @@ -14,7 +14,7 @@ import { Vector3, } from '@babylonjs/core/Legacy/legacy'; -import { activeFloorMeshes, activeTouchableMeshes, ShapeTree, v3, scale3, buildSound, builder as B } from './shapes.js'; +import { activeFloorMeshes, activeTouchableMeshes, Environment, ShapeTree, buildSound, builder as B, u2, u3, u3v } from './shapes.js'; import { RunningEngine } from './engine.js'; import { uuid } from './uuid.js'; @@ -22,13 +22,14 @@ assertion type SceneHandle(ds: Embedded); function interpretScene(myId: string, runningEngine: RunningEngine, rootMesh: Mesh, sceneDs: Ref) { at sceneDs { - during Shapes.Sprite({ "name": $name: string }) => spawn named `sprite:${name}` { + during Shapes.Sprite({ "name": $name: string, "formals": $formals }) => 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, runningEngine, rootMesh, sceneDs); + const env = new Environment(name, formals as symbol[], sceneDs); + spriteMain(env, runningEngine, rootMesh); } } @@ -42,20 +43,20 @@ function interpretScene(myId: string, runningEngine: RunningEngine, rootMesh: Me } } -function spriteMain(spriteName: string, runningEngine: RunningEngine, rootMesh: Mesh, sceneDs: Ref) { - at sceneDs { - let currentShape = ShapeTree.empty(spriteName, runningEngine.scene); +function spriteMain(env: Environment, runningEngine: RunningEngine, rootMesh: Mesh) { + at env.sceneDs { + let currentShape = ShapeTree.empty(env.spriteName, runningEngine.scene); currentShape.rootnode.parent = rootMesh; on stop currentShape.remove(); - during Shapes.Sprite({ "name": spriteName, "shape": $shape: Shapes.Shape }) => { - currentShape = currentShape.reconcile(spriteName, spriteName, shape); + during Shapes.Sprite({ "name": env.spriteName, "shape": $shape: Shapes.Shape }) => { + currentShape = currentShape.reconcile(env, env.spriteName, shape); currentShape.rootnode.parent = rootMesh; } - during SceneProtocol.Gravity($direction: Shapes.Vector3) => { + during SceneProtocol.Gravity($direction: Shapes.LiteralVector3) => { runningEngine.applyGravity = true; on stop runningEngine.applyGravity = false; - runningEngine.gravity = v3(direction); + runningEngine.gravity = new Vector3(direction.x, direction.y, direction.z); } } } @@ -101,21 +102,23 @@ async function enterScene( } }; - const currentPosition = () => Shapes.Vector3(runningEngine.position); - const currentRotation = () => Shapes.Vector3(runningEngine.rotation); + const TAU = 2 * Math.PI; - field position: Shapes.Vector3 = currentPosition(); - field rotation: Shapes.Vector3 = currentRotation(); + const currentPosition = () => u3(runningEngine.position); + const currentRotation = () => u3(runningEngine.rotation, 1/TAU); + + field position: Shapes.ImmediateVector3 = currentPosition(); + field rotation: Shapes.ImmediateVector3 = 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))) { + if (!is(Shapes.fromImmediateVector3(position.value), Shapes.fromImmediateVector3(newPosition))) { position.value = newPosition; } - if (!is(Shapes.fromVector3(rotation.value), Shapes.fromVector3(newRotation))) { + if (!is(Shapes.fromImmediateVector3(rotation.value), Shapes.fromImmediateVector3(newRotation))) { rotation.value = newRotation; } } @@ -128,7 +131,7 @@ async function enterScene( on asserted SceneProtocol.Portal({ "name": o, "destination": $dest: SceneProtocol.PortalDestination, - "position": $targetPosition: Shapes.Vector3, + "position": $targetPosition: Shapes.LiteralVector3, }) => { const newPos = new Vector3(targetPosition.x, targetPosition.y, @@ -169,29 +172,46 @@ async function enterScene( } } - assert Shapes.Sprite({ + const _POS = Symbol.for('pos'); + const _HEAD = Symbol.for('head'); + const _BODY = Symbol.for('body'); + + assert Shapes.Variable({ spriteName: id, variable: _POS, value: Shapes.fromImmediateVector3(position.value) }); + assert Shapes.Variable({ spriteName: id, variable: _HEAD, value: Shapes.fromImmediateVector3(rotation.value) }); + assert Shapes.Variable({ spriteName: id, variable: _BODY, value: Shapes.fromImmediateVector3({ + x: Shapes.DoubleValue.immediate(0), + y: rotation.value.y, + z: Shapes.DoubleValue.immediate(0), + }) }); + + assert (() => { + const s = Shapes.Sprite({ name: id, + formals: [_POS, _HEAD, _BODY], shape: B.nonphysical( - B.move(position.value, B.many([ - B.move({ x: 0, y: -0.9, z: 0 }, - B.rotate(scale3({ x: 0, y: rotation.value.y, z: 0 }, 1 / (2 * Math.PI)), - B.scale({ x: 0.4, y: 1.4, z: 0.1 }, B.box()))), - B.rotate(scale3(rotation.value, 1 / (2 * Math.PI)), - B.scale({ x: 0.15, y: 0.23, z: 0.18 }, B.many([ + B.move(Shapes.Vector3.reference(_POS), B.many([ + B.move(u3v({x: 0, y: -0.9, z: 0}), + B.rotate(Shapes.Vector3.reference(_BODY), + B.scale(u3v({x: 0.4, y: 1.4, z: 0.1}), B.box()))), + B.rotate(Shapes.Vector3.reference(_HEAD), + B.scale(u3v({x: 0.15, y: 0.23, z: 0.18}), B.many([ B.box(), - B.move({ x: 0, y: 0, z: 0.501 }, + B.move(u3v({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 + scale: Shapes.Vector2.immediate(u2({ x:1, y:1 })), + offset: Shapes.Vector2.immediate(u2({ x:0, y:0 })), + alpha: Shapes.DoubleValue.immediate(1), }), - B.rotate({ x: 0, y: 0.5, z: 0 }, + B.rotate(u3v({ x: 0, y: 0.5, z: 0 }), B.plane()))), ]))), ]))), - }); + }); + console.log(s); + return s; + })(); } } @@ -265,7 +285,7 @@ window.addEventListener('load', async () => { Dataspace.boot(ds => { html.boot(ds); timer.boot(ds); - wsRelay.boot(ds, false); + wsRelay.boot(ds, true); wakeDetector.boot(ds); bootApp(ds, runningEngine); }); diff --git a/src/shapes.ts b/src/shapes.ts index a4fb7a1..aa9b77b 100644 --- a/src/shapes.ts +++ b/src/shapes.ts @@ -20,10 +20,46 @@ import { Vector2, Vector3, } from '@babylonjs/core/Legacy/legacy'; -import { KeyedDictionary, Value, is } from "@syndicate-lang/core"; +import { Dataflow, IdentityMap, KeyedDictionary, Ref, Value, is } from "@syndicate-lang/core"; import * as Shapes from './gen/shapes.js'; +export class Environment { + fields = new IdentityMap>>(); + + constructor( + public spriteName: string, + public formals: symbol[], + public sceneDs: Ref, + ) { + formals.forEach(f => { + field v: Value = false; + at this.sceneDs { + on asserted Shapes.Variable({ + "spriteName": this.spriteName, + "variable": f, + "value": $newValue, + }) => { + console.log('Got value for', spriteName, f, newValue); + v.value = newValue; + } + } + this.fields.set(f, v); + }); + } + + lookup(f: symbol, k: (v: Value) => void) { + dataflow { + const v = this.fields.get(f); + if (v === void 0) throw new Error(`Lookup of ${f.description} in ${this.spriteName} failed`); + const w = v.value; + if (w !== false) k(w); + } + } +} + +export type ValueK = (v: Value) => void; + export const activeFloorMeshes: Array = []; export const activeTouchableMeshes: Array = []; @@ -50,13 +86,13 @@ export class ShapeTree { return this.subnodes.then(ns => [this.rootnode, ... ns]); } - reconcile(spriteName: string, name: string, shape: Shapes.Shape): ShapeTree { + reconcile(env: Environment, name: string, shape: Shapes.Shape): ShapeTree { if (is(Shapes.fromShape(shape), this.shapePreserve)) { return this; } else { this.remove(); - return build(name, this.scene, shape, { - spriteName: async m => m.rootnode.metadata.spriteName = spriteName, + return build(env, name, this.scene, shape, { + spriteName: async m => m.rootnode.metadata.spriteName = env.spriteName, collisions: async m => (await m.allnodes).forEach(n => n.checkCollisions = true), }); } @@ -82,20 +118,64 @@ export class ShapeTree { } } -export function v2(v: Shapes.Vector2): Vector2 { - return new Vector2(v.x, v.y); +export function dv(env: Environment, v: Shapes.DoubleValue, k: (v: number) => void) { + switch (v._variant) { + case "immediate": k(v.value); break; + case "reference": env.lookup(v.value, k as ValueK); break; + } } -export function v3(v: Shapes.Vector3): Vector3 { - return new Vector3(v.x, v.y, v.z); +export function u2(v: {x: number, y: number}): Shapes.ImmediateVector2 { + return Shapes.ImmediateVector2({ + x: Shapes.DoubleValue.immediate(v.x), + y: Shapes.DoubleValue.immediate(v.y), + }); } -export function scale3(v: Shapes.Vector3, scale: number): Shapes.Vector3 { - return Shapes.Vector3({ x: v.x * scale, y: v.y * scale, z: v.z * scale }); +export function lv2(env: Environment, v: Shapes.ImmediateVector2, k: (v: Vector2) => void) { + dv(env, v.x, x => dv(env, v.y, y => k(new Vector2(x, y)))); } -export function q(q: Shapes.Quaternion): Quaternion { - return new Quaternion(q.a, q.b, q.c, q.d); +export function v2(env: Environment, v: Shapes.Vector2, k: (v: Vector2) => void) { + switch (v._variant) { + case "immediate": lv2(env, v.value, k); break; + case "reference": env.lookup(v.value, v => lv2(env, Shapes.asImmediateVector2(v), k)); break; + } +} + +export function u3(v: {x: number, y: number, z: number}, scale = 1): Shapes.ImmediateVector3 { + return Shapes.ImmediateVector3({ + x: Shapes.DoubleValue.immediate(v.x * scale), + y: Shapes.DoubleValue.immediate(v.y * scale), + z: Shapes.DoubleValue.immediate(v.z * scale), + }); +} + +export function u3v(v: {x: number, y: number, z: number}, scale = 1): Shapes.Vector3 { + return Shapes.Vector3.immediate(u3(v, scale)); +} + +export function lv3(env: Environment, v: Shapes.ImmediateVector3, k: (v: Vector3) => void) { + dv(env, v.x, x => dv(env, v.y, y => dv(env, v.z, z => k(new Vector3(x, y, z))))); +} + +export function v3(env: Environment, v: Shapes.Vector3, k: (v: Vector3) => void) { + switch (v._variant) { + case "immediate": lv3(env, v.value, k); break; + case "reference": env.lookup(v.value, v => lv3(env, Shapes.asImmediateVector3(v), k)); break; + } +} + +export function lq(env: Environment, q: Shapes.ImmediateQuaternion, k: (v: Quaternion) => void) { + dv(env, q.a, a => dv(env, q.b, b => dv(env, q.c, c => dv(env, q.d, d => + k(new Quaternion(a, b, c, d)))))); +} + +export function q(env: Environment, q: Shapes.Quaternion, k: (v: Quaternion) => void) { + switch (q._variant) { + case "immediate": lq(env, q.value, k); break; + case "reference": env.lookup(q.value, q => lq(env, Shapes.asImmediateQuaternion(q), k)); break; + } } export type MeshCustomizer = { [key: string]: ((m: ShapeTree) => void) }; @@ -111,7 +191,7 @@ type CachedTexture = { }; const textureCache = new KeyedDictionary(); -function buildTexture(name: string, scene: Scene, spec: Shapes.TextureSpec): CachedTexture { +function buildTexture(env: Environment, name: string, scene: Scene, spec: Shapes.TextureSpec): CachedTexture { const cacheKey = Shapes.fromTextureSpec(spec); const entry = textureCache.get(cacheKey); if (entry !== void 0) { @@ -125,18 +205,18 @@ function buildTexture(name: string, scene: Scene, spec: Shapes.TextureSpec): Cac case "simple": break; case "uvAlpha": - mat.alpha = spec.alpha; + mat.alpha = 0; + dv(env, spec.alpha, a => mat.alpha = a); tex.hasAlpha = true; /* FALL THROUGH */ - case "uv": { - const scale = v2(spec.scale); - const offset = v2(spec.offset); - tex.uScale = scale.x; - tex.vScale = scale.y; - tex.uOffset = offset.x; - tex.vOffset = offset.y; + case "uv": + v2(env, spec.scale, scale => v2(env, spec.offset, offset => { + tex.uScale = scale.x; + tex.vScale = scale.y; + tex.uOffset = offset.x; + tex.vOffset = offset.y; + })); break; - } } const newEntry = { key: cacheKey, @@ -167,13 +247,7 @@ export function buildMesh( switch (meshSpec._variant) { case "Sphere": return { rootnode: MeshBuilder.CreateSphere(name, {}, scene) }; case "Box": return { rootnode: MeshBuilder.CreateBox(name, {}, scene) }; - case "Ground": { - const v = v2(meshSpec.value.size); - return { - rootnode: MeshBuilder.CreateGround( - name, { width: v.x, height: v.y }, scene ?? void 0), - }; - } + case "Ground": return { rootnode: MeshBuilder.CreateGround(name, {}, scene ?? void 0) }; case "Plane": return { rootnode: MeshBuilder.CreatePlane(name, {}, scene) }; case "External": { const rootnode = new Mesh(name, scene); @@ -210,7 +284,7 @@ export function buildSound(name: string, scene: Scene, spec: Shapes.SoundSpec, s return new Sound(name, spec.url, scene, null, options); } -export function build(name: string, scene: Scene, shape: Shapes.Shape, customize: MeshCustomizer): ShapeTree { +export function build(env: Environment, name: string, scene: Scene, shape: Shapes.Shape, customize: MeshCustomizer): ShapeTree { switch (shape._variant) { case "Mesh": { const m = buildMesh(name, scene, shape.value); @@ -219,23 +293,23 @@ export function build(name: string, scene: Scene, shape: Shapes.Shape, customize return t; } - case "Light": - return new ShapeTree( - scene, - shape, - new HemisphericLight(name, v3(shape.value.v), scene)); + case "Light": { + const light = new HemisphericLight(name, new Vector3(0, 1, 0), scene); + v3(env, shape.value.v, v => light.direction = v); + return new ShapeTree(scene, shape, light); + } case "Scale": { const t = ShapeTree.transform(name, scene, shape); - t.rootnode.scaling = v3(shape.value.v); - build(name + '.inner', scene, shape.value.shape, customize).parent = t; + v3(env, shape.value.v, v => t.rootnode.scaling = v); + build(env, name + '.inner', scene, shape.value.shape, customize).parent = t; return t; } case "Move": { const t = ShapeTree.transform(name, scene, shape); - t.rootnode.position = v3(shape.value.v); - build(name + '.inner', scene, shape.value.shape, customize).parent = t; + v3(env, shape.value.v, v => t.rootnode.position = v); + build(env, name + '.inner', scene, shape.value.shape, customize).parent = t; return t; } @@ -243,28 +317,30 @@ export function build(name: string, scene: Scene, shape: Shapes.Shape, customize const t = ShapeTree.transform(name, scene, shape); switch (shape.value._variant) { case "euler": - t.rootnode.rotation = v3(shape.value.v); - t.rootnode.rotation.scaleInPlace(2 * Math.PI); + v3(env, shape.value.v, v => { + t.rootnode.rotation = v; + t.rootnode.rotation.scaleInPlace(2 * Math.PI); + }); break; case "quaternion": - t.rootnode.rotationQuaternion = q(shape.value.q); + q(env, shape.value.q, q => t.rootnode.rotationQuaternion = q); break; } - build(name + '.inner', scene, shape.value.shape, customize).parent = t; + build(env, name + '.inner', scene, shape.value.shape, customize).parent = t; return t; } case "many": { const t = ShapeTree.transform(name, scene, shape); shape.value.forEach((s, i) => { - build(name + '[' + i + ']', scene, s, customize).parent = t; + build(env, name + '[' + i + ']', scene, s, customize).parent = t; }); return t; } case "Texture": { - const entry = buildTexture(name + '.texture', scene, shape.value.spec); - const t = build(name + '.inner', scene, shape.value.shape, { + const entry = buildTexture(env, name + '.texture', scene, shape.value.spec); + const t = build(env, name + '.inner', scene, shape.value.shape, { ... customize, material: async m => (await m.allnodes).forEach(n => n.material = entry.material), }); @@ -274,9 +350,13 @@ export function build(name: string, scene: Scene, shape: Shapes.Shape, customize 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; - const t = build(name + '.inner', scene, shape.value.shape, { + dv(env, shape.value.r, r => + dv(env, shape.value.g, g => + dv(env, shape.value.b, b => mat.diffuseColor = new Color3(r, g, b)))); + if (shape.value._variant === "transparent") { + dv(env, shape.value.alpha, a => mat.alpha = a); + } + const t = build(env, name + '.inner', scene, shape.value.shape, { ... customize, material: async m => (await m.allnodes).forEach(n => n.material = mat), }); @@ -286,7 +366,7 @@ export function build(name: string, scene: Scene, shape: Shapes.Shape, customize case "Sound": { const sound = buildSound(name + ".sound", scene, shape.value.spec, true); - const t = build(name + ".inner", scene, shape.value.shape, { + const t = build(env, name + ".inner", scene, shape.value.shape, { ... customize, sound: async m => sound.attachToMesh(m.rootnode), }); @@ -295,10 +375,10 @@ export function build(name: string, scene: Scene, shape: Shapes.Shape, customize } case "Name": - return build(name + '.' + shape.value.base, scene, shape.value.shape, customize); + return build(env, name + '.' + shape.value.base, scene, shape.value.shape, customize); case "Floor": - return build(name, scene, shape.value.shape, { + return build(env, name, scene, shape.value.shape, { ... customize, floor: async m => { const nodes = await m.allnodes; @@ -311,13 +391,13 @@ export function build(name: string, scene: Scene, shape: Shapes.Shape, customize }); case "Nonphysical": - return build(name, scene, shape.value.shape, { + return build(env, name, scene, shape.value.shape, { ... customize, collisions: async m => (await m.allnodes).forEach(n => n.checkCollisions = false), }); case "Touchable": - return build(name, scene, shape.value.shape, { + return build(env, name, scene, shape.value.shape, { ... customize, touchable: async m => { const nodes = await m.allnodes; @@ -426,23 +506,30 @@ export function buildCSG(name: string, scene: Scene, expr: Shapes.CSGExpr): Buil }; } -export const builder: { [key: string]: (... args: any[]) => Shapes.Shape } = { +export const builder = { sphere: () => Shapes.Shape.Mesh(Shapes.Mesh.Sphere(Shapes.Sphere())), box: () => Shapes.Shape.Mesh(Shapes.Mesh.Box(Shapes.Box())), plane: () => Shapes.Shape.Mesh(Shapes.Mesh.Plane(Shapes.Plane())), - ground: (v: Shapes.Vector2) => Shapes.Shape.Mesh(Shapes.Mesh.Ground(Shapes.Ground(Shapes.Vector2(v)))), - light: (v: Shapes.Vector3) => Shapes.Shape.Light(Shapes.Light(Shapes.Vector3(v))), - scale: (v: Shapes.Vector3, shape: Shapes.Shape) => Shapes.Shape.Scale(Shapes.Scale({ v: Shapes.Vector3(v), shape })), - move: (v: Shapes.Vector3, shape: Shapes.Shape) => Shapes.Shape.Move(Shapes.Move({ v: Shapes.Vector3(v), shape })), - rotate: (v: Shapes.Vector3, shape: Shapes.Shape) => Shapes.Shape.Rotate(Shapes.Rotate.euler({ v: Shapes.Vector3(v), shape })), + ground: () => Shapes.Shape.Mesh(Shapes.Mesh.Ground(Shapes.Ground())), + light: (v: Shapes.Vector3) => Shapes.Shape.Light(Shapes.Light(v)), + scale: (v: Shapes.Vector3, shape: Shapes.Shape) => Shapes.Shape.Scale(Shapes.Scale({ v, shape })), + move: (v: Shapes.Vector3, shape: Shapes.Shape) => Shapes.Shape.Move(Shapes.Move({ v, shape })), + rotate: (v: Shapes.Vector3, shape: Shapes.Shape) => Shapes.Shape.Rotate(Shapes.Rotate.euler({ v, shape })), many: (shapes: Shapes.Shape[]) => Shapes.Shape.many(shapes), texture: (spec: Shapes.TextureSpec, shape: Shapes.Shape) => Shapes.Shape.Texture(Shapes.Texture({ spec, shape })), - color: (r: number, g: number, b: number, shape: Shapes.Shape, alpha = 1.0) => { - return Shapes.Shape.Color((alpha === 1.0) + color: (r0: number | symbol, g0: number | symbol, b0: number | symbol, shape: Shapes.Shape, alpha0: number | symbol = 1.0) => { + function vd(x: number | symbol): Shapes.DoubleValue { + return typeof x === 'number' ? Shapes.DoubleValue.immediate(x) : Shapes.DoubleValue.reference(x); + } + const r = vd(r0); + const g = vd(g0); + const b = vd(b0); + const alpha = vd(alpha0); + return Shapes.Shape.Color((alpha0 === 1.0) ? Shapes.Color.opaque({ r, g, b, shape }) : Shapes.Color.transparent({ r, g, b, alpha, shape })); }, name: (base: string, shape: Shapes.Shape) => Shapes.Shape.Name(Shapes.Name({ base, shape })), floor: (shape: Shapes.Shape) => Shapes.Shape.Floor(Shapes.Floor(shape)), nonphysical: (shape: Shapes.Shape) => Shapes.Shape.Nonphysical(Shapes.Nonphysical(shape)), -}; +} satisfies { [key: string]: (... args: any[]) => Shapes.Shape };