That's better
This commit is contained in:
parent
df1f42ea81
commit
c319bb460a
|
@ -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 = {
|
||||
|
|
|
@ -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) => {
|
||||
|
|
100
src/interiors.ts
100
src/interiors.ts
|
@ -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;
|
||||
}
|
221
src/shapes.ts
221
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<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 } = {
|
||||
|
|
Loading…
Reference in New Issue