Touch/collision

This commit is contained in:
Tony Garnock-Jones 2023-01-06 15:09:46 +01:00
parent bf8e65b59a
commit 3392de10cd
7 changed files with 91 additions and 31 deletions

4
TODO
View File

@ -1,4 +1,4 @@
portals to other scenes
collision events
touch events/interactions
collision events
touch events/interactions
motion with xr without a controller

View File

@ -0,0 +1,4 @@
version 1 .
; Message
Touch = <touch @subject string @object string> .

View File

@ -2,7 +2,7 @@ version 1 .
Sprite = <sprite @name string @shape Shape> .
Shape = Mesh / Light / Scale / Move / Rotate / @many [Shape ...] / Texture / Color / Name / Floor / Nonphysical / CSG .
Shape = Mesh / Light / Scale / Move / Rotate / @many [Shape ...] / Texture / Color / Name / Floor / Nonphysical / Touchable / CSG .
Mesh = Sphere / Box / Ground / Plane .
@ -36,8 +36,8 @@ Color =
Name = <name @base string @shape Shape> .
Floor = <floor @shape Shape> .
Nonphysical = <nonphysical @shape Shape> .
Touchable = <touchable @shape Shape> .
CSG = <csg @expr CSGExpr> .

View File

@ -37,7 +37,8 @@
<scale <v 1.0 3.0 1.0>
<color 1.0 1.0 0.0
<floor
<sphere>>>>>>
<touchable
<sphere>>>>>>>
<sprite "plans"
<texture ["plans/signal-2022-12-27-125451_002.jpeg"]
@ -47,9 +48,11 @@
<box>>>>>>
<sprite "tower"
<move <v -1005.0 5000.0 3.0>
<scale <v 3.0 10000.0 3.0>
<floor
<box>>>>>
<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>>>>>>>
[]

View File

@ -1,4 +1,5 @@
import {
AbstractMesh,
DualShockPad,
Engine,
FreeCamera,
@ -61,7 +62,8 @@ if ((navigator as any).oscpu?.startsWith('Linux')) {
export type CreateScene = (canvas: HTMLCanvasElement, engine: Engine) => Promise<CreatedScene>;
export type CreatedScene = {
scene: Scene,
floorMeshes: () => Mesh[]
floorMeshes: () => Mesh[],
touchableMeshes: () => Mesh[],
};
export async function startEngine(
@ -71,7 +73,7 @@ export async function startEngine(
): Promise<Scene> {
const canvas = document.getElementById("renderCanvas") as HTMLCanvasElement;
const engine = new Engine(canvas, true);
const { scene, floorMeshes } = await createScene(canvas, engine);
const { scene, floorMeshes, touchableMeshes } = await createScene(canvas, engine);
const xr = await scene.createDefaultXRExperienceAsync({});
const xrAvailable = xr?.baseExperience !== void 0;
@ -171,6 +173,17 @@ export async function startEngine(
}
}
if (sm && latch(gp, 1)) {
const ray = xr.baseExperience.camera.getForwardRay();
const meshes = touchableMeshes();
const hit = scene.pickWithRay(ray, m => meshes.indexOf(m as any) !== -1);
if (hit !== null) {
if (meshes.indexOf(hit.pickedMesh as any) !== -1) {
camera.onCollide?.(hit.pickedMesh!);
}
}
}
if (sm) {
if (latch(gp, 4)) {
recenterBase = { rotation: xr.baseExperience.camera.rotationQuaternion.clone() };

View File

@ -4,17 +4,20 @@ 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 * as SceneProtocol from './gen/scene.js';
import { md5 } from './md5.js';
import {
AbstractMesh,
Engine,
FreeCamera,
Mesh,
Scene,
TargetCamera,
WebXRCamera,
} from '@babylonjs/core/Legacy/legacy';
import { activeFloorMeshes, ShapeTree, builder as B } from './shapes.js';
import { activeFloorMeshes, activeTouchableMeshes, ShapeTree, builder as B } from './shapes.js';
import { startEngine, CreatedScene } from './engine.js';
import { uuid } from './uuid.js';
@ -34,12 +37,12 @@ function interpretScene(myId: string, scene: Scene, sceneDs: Ref) {
}
}
function spriteMain(name: string, scene: Scene, sceneDs: Ref) {
function spriteMain(spriteName: string, scene: Scene, sceneDs: Ref) {
at sceneDs {
let currentShape = ShapeTree.empty(name, scene);
let currentShape = ShapeTree.empty(spriteName, scene);
on stop currentShape.remove();
during Shapes.Sprite({ "name": name, "shape": $shape: Shapes.Shape }) => {
currentShape = currentShape.reconcile(name, shape);
during Shapes.Sprite({ "name": spriteName, "shape": $shape: Shapes.Shape }) => {
currentShape = currentShape.reconcile(spriteName, spriteName, shape);
}
}
}
@ -52,11 +55,22 @@ function wsurl(): string {
function bootApp(ds: Ref, scene: Scene) {
spawn named 'app' {
at ds {
const id = uuid();
const url = wsurl();
const serverCap = Sturdy.asSturdyRef(new Reader<Ref>(
'<ref "syndicate" [] #[pkgN9TBmEd3Q04grVG4Zdw==]>').next());
const relayAddr = wsRelay.RelayAddress(Schemas.transportAddress.WebSocket(url));
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);
}));
during wsRelay.Resolved({
"addr": relayAddr,
"sturdyref": serverCap,
@ -70,19 +84,34 @@ function bootApp(ds: Ref, scene: Scene) {
at remoteDs {
during SceneHandle($sceneDs_e: Embedded) => {
const id = uuid();
const thisFacet = Turn.activeFacet;
const sceneDs = sceneDs_e.embeddedValue;
interpretScene(id, scene, sceneDs);
const camera = scene.cameras[0];
if (camera instanceof FreeCamera) {
camera.onCollide = (other: AbstractMesh) => {
if (other.metadata?.touchable) {
thisFacet.turn(() => {
at sceneDs {
send message SceneProtocol.Touch({
subject: id,
object: other.metadata?.spriteName,
});
}
});
}
};
}
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) {
if (camera instanceof TargetCamera) {
const newPosition = Shapes.Vector3(camera.position);
const newRotation = Shapes.Vector3(camera instanceof WebXRCamera
? camera.rotationQuaternion.toEulerAngles()
@ -97,15 +126,11 @@ function bootApp(ds: Ref, scene: Scene) {
}
}
field email: string = localStorage.getItem('userEmail') ?? id;
const emailInput = document.getElementById('emailInput') as HTMLInputElement;
emailInput.value = email.value;
emailInput.addEventListener('keyup', () => thisFacet.turn(() => {
email.value = emailInput.value;
localStorage.setItem('userEmail', emailInput.value);
}));
at sceneDs {
on message SceneProtocol.Touch({ "subject": $subject, "object": $object }) => {
console.log('touch!', subject, object);
}
assert Shapes.Sprite({
name: id,
shape: B.nonphysical(
@ -143,6 +168,7 @@ window.addEventListener('load', async () => {
async (_canvas: HTMLCanvasElement, engine: Engine): Promise<CreatedScene> => ({
scene: new Scene(engine),
floorMeshes: () => activeFloorMeshes,
touchableMeshes: () => activeTouchableMeshes,
}));
Dataspace.boot(ds => {
html.boot(ds);

View File

@ -20,6 +20,7 @@ import { KeyedDictionary, Value, is } from "@syndicate-lang/core";
import * as Shapes from './gen/shapes.js';
export const activeFloorMeshes: Array<Mesh> = [];
export const activeTouchableMeshes: Array<Mesh> = [];
export function adjust(f: (... csgs: CSG[]) => CSG, ... ms: Mesh[]): Mesh {
const csgs = ms.map(m => CSG.FromMesh(m));
@ -75,17 +76,17 @@ export class ShapeTree<N extends Node = Node> {
public node: N,
) {
this.shapePreserve = Shapes.fromShape(this.shape);
this.node.metadata = {};
}
reconcile(name: string, shape: Shapes.Shape): ShapeTree {
reconcile(spriteName: string, name: string, shape: Shapes.Shape): ShapeTree {
if (is(Shapes.fromShape(shape), this.shapePreserve)) {
return this;
} else {
this.remove();
return build(name, this.scene, shape, {
collisions: m => {
m.node.checkCollisions = true;
},
spriteName: m => m.node.metadata.spriteName = spriteName,
collisions: m => m.node.checkCollisions = true,
});
}
}
@ -286,6 +287,19 @@ export function build(name: string, scene: Scene, shape: Shapes.Shape, customize
},
});
case "Touchable":
return build(name, scene, shape.value.shape, {
... customize,
touchable: m => {
m.node.metadata.touchable = true;
activeTouchableMeshes.push(m.node);
m.cleanups.push(() => {
const i = activeTouchableMeshes.indexOf(m.node);
if (i !== -1) activeTouchableMeshes.splice(i, 1);
});
}
});
case "CSG": {
throw new Error("unimplemented");
}