That's better

This commit is contained in:
Tony Garnock-Jones 2023-01-12 15:22:03 +01:00
parent df1f42ea81
commit c319bb460a
4 changed files with 111 additions and 218 deletions

View File

@ -51,8 +51,8 @@ if ((navigator as any).oscpu?.startsWith('Linux')) {
}
export type Interactivity = {
floorMeshes: () => Mesh[],
touchableMeshes: () => Mesh[],
floorMeshes: () => AbstractMesh[],
touchableMeshes: () => AbstractMesh[],
};
export type EngineOptions = {

View File

@ -38,11 +38,11 @@ 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);
currentShape.node.parent = rootMesh;
currentShape.rootnode.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;
currentShape.rootnode.parent = rootMesh;
}
during SceneProtocol.Gravity($direction: Shapes.Vector3) => {

View File

@ -1,100 +0,0 @@
import {
Mesh,
Nullable,
Scene,
StandardMaterial,
Texture,
} from '@babylonjs/core/Legacy/legacy';
import { adjust, box, subtractMany } from './shapes.js';
export interface SurfaceOptions {
height?: number,
thickness?: number,
texturePath?: string,
};
export function attachTexture(b: Mesh, scene: Scene, texturePath: string | undefined): Mesh {
if (texturePath !== void 0) {
const mat = new StandardMaterial(b.name + "_mat", scene);
mat.diffuseTexture = new Texture(texturePath, scene);
b.material = mat;
}
return b;
}
export const DEFAULT_THICKNESS = 0.125;
export async function wall(name: string, length: number, scene: Scene, options: SurfaceOptions = {}): Promise<Mesh> {
return attachTexture(box(name, length, options.height ?? 2.90, options.thickness ?? DEFAULT_THICKNESS),
scene,
options.texturePath);
}
export async function floor(name: string, width: number, depth: number, scene: Scene, options: SurfaceOptions = {}): Promise<Mesh> {
return attachTexture(box(name, width, options.thickness ?? DEFAULT_THICKNESS, depth),
scene,
options.texturePath);
}
export type Wall = {
texture: string,
thickness?: number,
holes: Array<Mesh>,
};
export async function room(name: string, width: number, depth: number, scene: Scene,
floorTexture: string,
ceilingTexture: string,
wall0: Nullable<Wall>,
wall1: Nullable<Wall>,
wall2: Nullable<Wall>,
wall3: Nullable<Wall>): Promise<Mesh> {
const th = (wallDetails: Wall): number => wallDetails.thickness ?? DEFAULT_THICKNESS;
const mkw = async (n: number, width: number, wallDetails: Wall): Promise<Mesh> => {
return adjust(subtractMany,
await wall(`${name}_${n}`, width, scene, {
texturePath: wallDetails.texture,
thickness: wallDetails.thickness,
}),
... wallDetails.holes);
};
const f = await floor(name +"_f", width, depth, scene, { texturePath: floorTexture });
f.position.y = -DEFAULT_THICKNESS / 2;
const c = await floor(name + "_c", width, depth, scene, { texturePath: ceilingTexture });
c.position.y = 2.90 + DEFAULT_THICKNESS / 2;
c.setParent(f);
if (wall0 !== null) {
const w0 = await mkw(0, width, wall0);
w0.position.z = f.scaling.z / 2 + th(wall0) / 2;
w0.setParent(f);
}
if (wall1 !== null) {
const w1 = await mkw(1, depth, wall1);
w1.position.x = f.scaling.x / 2 + th(wall1) / 2;
w1.rotation.y = Math.PI / 2;
w1.setParent(f);
}
if (wall2 !== null) {
const w2 = await mkw(2, width, wall2);
w2.position.z = -(f.scaling.z / 2 + th(wall2) / 2);
w2.rotation.y = Math.PI;
w2.setParent(f);
}
if (wall3 !== null) {
const w3 = await mkw(3, depth, wall3);
w3.position.x = -(f.scaling.x / 2 + th(wall3) / 2);
w3.rotation.y = -Math.PI / 2;
w3.setParent(f);
}
f.position.y = DEFAULT_THICKNESS / 2;
return f;
}

View File

@ -1,4 +1,5 @@
import {
AbstractMesh,
Color3,
CubeTexture,
CSG,
@ -8,7 +9,6 @@ import {
Mesh,
MeshBuilder,
Node,
PhotoDome,
Quaternion,
Scene,
SceneLoader,
@ -22,64 +22,29 @@ 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));
const c = f(... csgs);
if (ms.length > 0) {
ms.forEach(m => m.dispose());
const scene = ms[0].getScene();
const ans = c.toMesh(ms[0].name, null, scene, true);
ans.material = ms[0].material;
return ans;
} else {
return c.toMesh("adjusted");
}
}
export const subtractMany = (a: CSG, ... bs: CSG[]) => bs.reduce((a, b) => a.subtract(b), a);
export class PhotoSemiDome extends PhotoDome {
readonly _size: number;
constructor(... args: ConstructorParameters<typeof PhotoDome>) {
super(... args);
this._size = args[2].size!;
this._chopMesh();
}
_chopMesh() {
const d = this._size;
this._mesh.getChildMeshes()[0].dispose(); // remove the covering half-sphere
this._mesh = adjust(subtractMany,
this._mesh,
box(this._mesh.name + "_chop", d, d, d, 0, 0, -d/2));
this._mesh.parent = this;
}
}
export function box(name: string, width: number, height: number, depth: number, x?: number, y?: number, z?: number): Mesh {
const b = MeshBuilder.CreateBox(name, {});
b.scaling = new Vector3(width, height, depth);
b.position = new Vector3(x ?? 0, y ?? b.scaling.y/2, z ?? 0);
return b;
}
//---------------------------------------------------------------------------
export const activeFloorMeshes: Array<AbstractMesh> = [];
export const activeTouchableMeshes: Array<AbstractMesh> = [];
export class ShapeTree<N extends Node = Node> {
shapePreserve: Value;
cleanups: Array<() => void> = [];
subnodes: Promise<N[]>;
constructor (
public scene: Scene,
public shape: Shapes.Shape,
public node: N,
public rootnode: N,
subnodes?: Promise<N[]>,
) {
this.shapePreserve = Shapes.fromShape(this.shape);
this.node.metadata = {};
const metadata = {};
this.rootnode.metadata = metadata;
this.subnodes = subnodes ?? Promise.resolve([]);
this.subnodes.then(ns => ns.forEach(n => n.metadata = metadata));
}
get allnodes(): Promise<N[]> {
return this.subnodes.then(ns => [this.rootnode, ... ns]);
}
reconcile(spriteName: string, name: string, shape: Shapes.Shape): ShapeTree {
@ -88,14 +53,14 @@ export class ShapeTree<N extends Node = Node> {
} else {
this.remove();
return build(name, this.scene, shape, {
spriteName: m => m.node.metadata.spriteName = spriteName,
collisions: m => m.node.checkCollisions = true,
spriteName: async m => m.rootnode.metadata.spriteName = spriteName,
collisions: async m => (await m.allnodes).forEach(n => n.checkCollisions = true),
});
}
}
remove() {
this.node?.dispose();
this.allnodes.then(ns => ns.forEach(n => n.dispose()));
}
static empty(name: string, scene: Scene): ShapeTree {
@ -123,9 +88,9 @@ 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) };
export type MeshCustomizer = { [key: string]: ((m: ShapeTree<AbstractMesh>) => void) };
function applyCustomizer(m: ShapeTree<Mesh>, c: MeshCustomizer) {
function applyCustomizer(m: ShapeTree<AbstractMesh>, c: MeshCustomizer) {
Object.values(c).forEach(f => f(m));
}
@ -179,32 +144,38 @@ function releaseTexture(entry: CachedTexture) {
}
}
export type BuiltMesh = {
rootnode: AbstractMesh,
subnodes?: Promise<AbstractMesh[]>,
};
export function buildMesh(
name: string,
scene: Scene | null,
meshSpec: Shapes.Mesh,
): Mesh {
): BuiltMesh {
switch (meshSpec._variant) {
case "Sphere": return MeshBuilder.CreateSphere(name, {}, scene);
case "Box": return MeshBuilder.CreateBox(name, {}, scene);
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 MeshBuilder.CreateGround(name, { width: v.x, height: v.y }, scene ?? void 0);
return {
rootnode: MeshBuilder.CreateGround(
name, { width: v.x, height: v.y }, scene ?? void 0),
};
}
case "Plane": return MeshBuilder.CreatePlane(name, {}, scene);
case "Plane": return { rootnode: MeshBuilder.CreatePlane(name, {}, scene) };
case "External": {
const primary = new Mesh(name, scene);
SceneLoader.ImportMesh(
"",
meshSpec.value.path,
void 0,
scene,
meshes => meshes.forEach(m => {
m.parent = primary;
console.log('adding submesh');
}));
console.log('returning primary');
return primary;
const rootnode = new Mesh(name, scene);
return {
rootnode,
subnodes: new Promise(async resolve => {
const r =
await SceneLoader.ImportMeshAsync("", meshSpec.value.path, void 0, scene);
r.meshes.forEach(m => m.parent = rootnode);
resolve(r.meshes);
}),
};
}
}
}
@ -212,7 +183,8 @@ export function buildMesh(
export function build(name: string, scene: Scene, shape: Shapes.Shape, customize: MeshCustomizer): ShapeTree {
switch (shape._variant) {
case "Mesh": {
const t = new ShapeTree<Mesh>(scene, shape, buildMesh(name, scene, shape.value));
const m = buildMesh(name, scene, shape.value);
const t = new ShapeTree<AbstractMesh>(scene, shape, m.rootnode, m.subnodes);
applyCustomizer(t, customize);
return t;
}
@ -225,15 +197,15 @@ export function build(name: string, scene: Scene, shape: Shapes.Shape, customize
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;
t.rootnode.scaling = v3(shape.value.v);
build(name + '.inner', scene, shape.value.shape, customize).rootnode.parent = t.rootnode;
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;
t.rootnode.position = v3(shape.value.v);
build(name + '.inner', scene, shape.value.shape, customize).rootnode.parent = t.rootnode;
return t;
}
@ -241,21 +213,21 @@ 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.node.rotation = v3(shape.value.v);
t.node.rotation.scaleInPlace(2 * Math.PI);
t.rootnode.rotation = v3(shape.value.v);
t.rootnode.rotation.scaleInPlace(2 * Math.PI);
break;
case "quaternion":
t.node.rotationQuaternion = q(shape.value.q);
t.rootnode.rotationQuaternion = q(shape.value.q);
break;
}
build(name + '.inner', scene, shape.value.shape, customize).node.parent = t.node;
build(name + '.inner', scene, shape.value.shape, customize).rootnode.parent = t.rootnode;
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;
build(name + '[' + i + ']', scene, s, customize).rootnode.parent = t.rootnode;
});
return t;
}
@ -264,7 +236,7 @@ export function build(name: string, scene: Scene, shape: Shapes.Shape, customize
const entry = buildTexture(name + '.texture', scene, shape.value.spec);
const t = build(name + '.inner', scene, shape.value.shape, {
... customize,
material: m => m.node.material = entry.material,
material: async m => (await m.allnodes).forEach(n => n.material = entry.material),
});
t.cleanups.push(() => releaseTexture(entry));
return t;
@ -276,7 +248,7 @@ export function build(name: string, scene: Scene, shape: Shapes.Shape, customize
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,
material: async m => (await m.allnodes).forEach(n => n.material = mat),
});
}
@ -286,11 +258,12 @@ export function build(name: string, scene: Scene, shape: Shapes.Shape, customize
case "Floor":
return build(name, scene, shape.value.shape, {
... customize,
floor: m => {
activeFloorMeshes.push(m.node);
floor: async m => {
const nodes = await m.allnodes;
activeFloorMeshes.push(... nodes);
m.cleanups.push(() => {
const i = activeFloorMeshes.indexOf(m.node);
if (i !== -1) activeFloorMeshes.splice(i, 1);
const i = activeFloorMeshes.indexOf(nodes[0]);
if (i !== -1) activeFloorMeshes.splice(i, nodes.length);
});
},
});
@ -298,40 +271,39 @@ export function build(name: string, scene: Scene, shape: Shapes.Shape, customize
case "Nonphysical":
return build(name, scene, shape.value.shape, {
... customize,
collisions: m => {
m.node.checkCollisions = false;
},
collisions: async m => (await m.allnodes).forEach(n => n.checkCollisions = false),
});
case "Touchable":
return build(name, scene, shape.value.shape, {
... customize,
touchable: m => {
m.node.metadata.touchable = true;
activeTouchableMeshes.push(m.node);
touchable: async m => {
const nodes = await m.allnodes;
m.rootnode.metadata.touchable = true;
activeTouchableMeshes.push(... nodes);
m.cleanups.push(() => {
const i = activeTouchableMeshes.indexOf(m.node);
if (i !== -1) activeTouchableMeshes.splice(i, 1);
const i = activeTouchableMeshes.indexOf(nodes[0]);
if (i !== -1) activeTouchableMeshes.splice(i, nodes.length);
});
}
});
case "CSG": {
const t = new ShapeTree<Mesh>(
scene, shape, buildCSG(shape.value.expr).toMesh(name, null, scene));
const m = buildCSG(name, scene, shape.value.expr);
const t = new ShapeTree<AbstractMesh>(scene, shape, m.rootnode, m.subnodes);
applyCustomizer(t, customize);
return t;
}
case "Skybox": {
const t = new ShapeTree<Mesh>(scene, shape, MeshBuilder.CreateBox(name, { size: 2000 }, scene));
const t = new ShapeTree<AbstractMesh>(scene, shape, MeshBuilder.CreateBox(name, { size: 2000 }, scene));
const mat = new StandardMaterial(name, scene);
mat.backFaceCulling = false;
mat.reflectionTexture = new CubeTexture(shape.value.path, scene);
mat.reflectionTexture.coordinatesMode = Texture.SKYBOX_MODE;
mat.diffuseColor = new Color3(0, 0, 0);
mat.specularColor = new Color3(0, 0, 0);
t.node.material = mat;
t.rootnode.material = mat;
applyCustomizer(t, customize);
return t;
}
@ -344,15 +316,21 @@ export function build(name: string, scene: Scene, shape: Shapes.Shape, customize
}
}
export function buildCSG(expr: Shapes.CSGExpr): CSG {
function walk(expr: Shapes.CSGExpr, matrix: Matrix): CSG {
export function buildCSG(name: string, scene: Scene, expr: Shapes.CSGExpr): BuiltMesh {
async function walk(expr: Shapes.CSGExpr, matrix: Matrix): Promise<CSG> {
switch (expr._variant) {
case "mesh": {
const mesh = buildMesh("", null, expr.shape);
mesh.freezeWorldMatrix(matrix, true);
mesh.unfreezeWorldMatrix();
const c = CSG.FromMesh(mesh);
mesh.dispose();
const m = buildMesh("", null, expr.shape);
const nodes: Mesh[] =
([m.rootnode, ... (m.subnodes ? await m.subnodes : [])]
.filter(n => n instanceof Mesh)) as Mesh[];
nodes.forEach(n => {
n.freezeWorldMatrix(matrix, true);
n.unfreezeWorldMatrix();
});
const c = CSG.FromMesh(nodes[0]);
nodes.slice(1).forEach(n => c.unionInPlace(CSG.FromMesh(n)));
nodes.forEach(n => n.dispose());
return c;
}
case "scale":
@ -367,28 +345,43 @@ export function buildCSG(expr: Shapes.CSGExpr): CSG {
expr.v.x * 2 * Math.PI,
expr.v.z * 2 * Math.PI)));
case "subtract": {
let c = walk(expr.base, matrix);
expr.more.forEach(d => c.subtractInPlace(walk(d, matrix)));
const c = await walk(expr.base, matrix);
for (const d of expr.more) {
c.subtractInPlace(await walk(d, matrix));
}
return c;
}
case "union": {
let c = walk(expr.base, matrix);
expr.more.forEach(d => c.unionInPlace(walk(d, matrix)));
const c = await walk(expr.base, matrix);
for (const d of expr.more) {
c.unionInPlace(await walk(d, matrix));
}
return c;
}
case "intersect": {
let c = walk(expr.base, matrix);
expr.more.forEach(d => c.intersectInPlace(walk(d, matrix)));
const c = await walk(expr.base, matrix);
for (const d of expr.more) {
c.intersectInPlace(await walk(d, matrix));
}
return c;
}
case "invert": {
let c = walk(expr.shape, matrix);
const c = await walk(expr.shape, matrix);
c.inverseInPlace();
return c;
}
}
}
return walk(expr, Matrix.Identity());
const rootnode = new Mesh(name, scene);
return {
rootnode,
subnodes: new Promise(async resolve => {
const c = await walk(expr, Matrix.Identity());
const m = c.toMesh(name + ".csg", null, scene);
m.parent = rootnode;
resolve([m]);
}),
};
}
export const builder: { [key: string]: (... args: any[]) => Shapes.Shape } = {