Touch/collision
This commit is contained in:
parent
bf8e65b59a
commit
3392de10cd
4
TODO
4
TODO
|
@ -1,4 +1,4 @@
|
|||
portals to other scenes
|
||||
collision events
|
||||
touch events/interactions
|
||||
✓ collision events
|
||||
✓ touch events/interactions
|
||||
motion with xr without a controller
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
version 1 .
|
||||
|
||||
; Message
|
||||
Touch = <touch @subject string @object string> .
|
|
@ -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> .
|
||||
|
||||
|
|
|
@ -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>>>>>>>
|
||||
|
||||
[]
|
||||
|
|
|
@ -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() };
|
||||
|
|
58
src/index.ts
58
src/index.ts
|
@ -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);
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue