From c319bb460a77da7a71d2db26a55a82c0ce4571ae Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Thu, 12 Jan 2023 15:22:03 +0100 Subject: [PATCH] That's better --- src/engine.ts | 4 +- src/index.ts | 4 +- src/interiors.ts | 100 --------------------- src/shapes.ts | 221 +++++++++++++++++++++++------------------------ 4 files changed, 111 insertions(+), 218 deletions(-) delete mode 100644 src/interiors.ts diff --git a/src/engine.ts b/src/engine.ts index 545ace0..98b5269 100644 --- a/src/engine.ts +++ b/src/engine.ts @@ -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 = { diff --git a/src/index.ts b/src/index.ts index 6ee5046..3a3c529 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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) => { diff --git a/src/interiors.ts b/src/interiors.ts deleted file mode 100644 index ed71fb4..0000000 --- a/src/interiors.ts +++ /dev/null @@ -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 { - 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 { - return attachTexture(box(name, width, options.thickness ?? DEFAULT_THICKNESS, depth), - scene, - options.texturePath); -} - -export type Wall = { - texture: string, - thickness?: number, - holes: Array, -}; - -export async function room(name: string, width: number, depth: number, scene: Scene, - floorTexture: string, - ceilingTexture: string, - wall0: Nullable, - wall1: Nullable, - wall2: Nullable, - wall3: Nullable): Promise { - const th = (wallDetails: Wall): number => wallDetails.thickness ?? DEFAULT_THICKNESS; - const mkw = async (n: number, width: number, wallDetails: Wall): Promise => { - 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; -} diff --git a/src/shapes.ts b/src/shapes.ts index 646151a..d5b0ad9 100644 --- a/src/shapes.ts +++ b/src/shapes.ts @@ -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 = []; -export const activeTouchableMeshes: Array = []; - -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) { - 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 = []; +export const activeTouchableMeshes: Array = []; export class ShapeTree { shapePreserve: Value; cleanups: Array<() => void> = []; + subnodes: Promise; constructor ( public scene: Scene, public shape: Shapes.Shape, - public node: N, + public rootnode: N, + subnodes?: Promise, ) { 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 { + return this.subnodes.then(ns => [this.rootnode, ... ns]); } reconcile(spriteName: string, name: string, shape: Shapes.Shape): ShapeTree { @@ -88,14 +53,14 @@ export class ShapeTree { } 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) => void) }; +export type MeshCustomizer = { [key: string]: ((m: ShapeTree) => void) }; -function applyCustomizer(m: ShapeTree, c: MeshCustomizer) { +function applyCustomizer(m: ShapeTree, c: MeshCustomizer) { Object.values(c).forEach(f => f(m)); } @@ -179,32 +144,38 @@ function releaseTexture(entry: CachedTexture) { } } +export type BuiltMesh = { + rootnode: AbstractMesh, + subnodes?: Promise, +}; + 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(scene, shape, buildMesh(name, scene, shape.value)); + const m = buildMesh(name, scene, shape.value); + const t = new ShapeTree(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( - scene, shape, buildCSG(shape.value.expr).toMesh(name, null, scene)); + const m = buildCSG(name, scene, shape.value.expr); + const t = new ShapeTree(scene, shape, m.rootnode, m.subnodes); applyCustomizer(t, customize); return t; } case "Skybox": { - const t = new ShapeTree(scene, shape, MeshBuilder.CreateBox(name, { size: 2000 }, scene)); + const t = new ShapeTree(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 { 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 } = {