Build the ribbon ourselves for great justice

This commit is contained in:
Tony Garnock-Jones 2023-02-14 13:40:55 +01:00
parent ce9c15836b
commit 6fd5e65376
2 changed files with 102 additions and 19 deletions

View File

@ -120,16 +120,18 @@
slen F
SetPen
Home
"double" SetSideOrientation
#t SetSmooth
PenDown
[6 CW dlen F] 240 times
PenUp
]>>>>
<sprite "tt" []
<move <v 0.0 0.01 0.75>
<move <v 1.0 0.01 0.75>
<rotate <v -0.125 0.0 0.0>
<texture ["textures/oak-herringbone-5e80fb40b00c9-1200.jpg"
<v 3.0 3.0>
<v 2.0 1.0>
<v 0.0 0.0>
1.0]
<turtle [

View File

@ -6,9 +6,10 @@ import {
Ray,
Scene,
Vector3,
VertexData,
} from '@babylonjs/core/Legacy/legacy';
import { VM as BaseVM, Input, Primitives, Environment } from './cat.js';
import * as Cat from './cat.js';
export class PenState {
templatePath: Vector3[] = [new Vector3()];
@ -47,17 +48,18 @@ export class PenState {
const D2R = Math.PI / 180;
export class TurtleVM extends BaseVM<TurtleVM> {
export class TurtleVM extends Cat.VM<TurtleVM> {
container: Mesh;
meshes: Mesh[] = [];
counter = 0;
sideOrientation = Mesh.BACKSIDE; // TODO: see SideOrientation primitive below
sideOrientation = Mesh.DEFAULTSIDE;
pen = new PenState();
pos = new Vector3();
q = new Quaternion();
prevQ = this.q;
smooth = false;
get euler(): Vector3 {
return this.q.toEulerAngles();
@ -66,7 +68,7 @@ export class TurtleVM extends BaseVM<TurtleVM> {
constructor(
name: string,
scene: Scene | null,
program: Input,
program: Cat.Input,
) {
super(program, TurtlePrimitives);
this.container = new Mesh(name, scene);
@ -109,16 +111,96 @@ export class TurtleVM extends BaseVM<TurtleVM> {
throw new Error('todo');
}
const m = MeshBuilder.CreateRibbon(this.container.name + this.counter++, {
pathArray: this.pen.paths!,
sideOrientation: this.sideOrientation,
});
const m = new Mesh(this.container.name + this.counter++);
// TODO: this.sideOrientation
const vertexData = new VertexData();
const positions: number[] = [];
const indices: number[] = [];
const normals: number[] = [];
const uvs: number[] = [];
const paths = this.pen.paths!;
const pathCount = paths.length;
const stepCount = paths[0].length;
const us: number[][] = [];
const vs: number[][] = [];
const uTotal: number[] = [];
const vTotal: number[] = [];
for (let pathIndex = 0; pathIndex < pathCount; pathIndex++) {
uTotal.push(0);
us.push([]);
let prev = paths[pathIndex][0];
for (let stepIndex = 0; stepIndex < stepCount; stepIndex++) {
const curr = paths[pathIndex][stepIndex];
uTotal[pathIndex] += curr.subtract(prev).length();
us[pathIndex][stepIndex] = uTotal[pathIndex];
prev = curr;
}
}
for (let stepIndex = 0; stepIndex < stepCount; stepIndex++) {
vTotal.push(0);
vs.push([]);
let prev = paths[0][stepIndex];
for (let pathIndex = 0; pathIndex < pathCount; pathIndex++) {
const curr = paths[pathIndex][stepIndex];
vTotal[stepIndex] += curr.subtract(prev).length();
vs[stepIndex][pathIndex] = vTotal[stepIndex];
prev = curr;
}
}
function pushPoint(pathIndex: number, stepIndex: number): number {
const pointIndex = positions.length / 3;
positions.push(... paths[pathIndex][stepIndex].asArray());
uvs.push(us[pathIndex][stepIndex] / uTotal[pathIndex],
vs[stepIndex][pathIndex] / vTotal[stepIndex]);
return pointIndex;
}
const cachedIndices: { [key: string]: number } = {};
function cachedPoint(pathIndex: number, stepIndex: number): number {
const v = paths[pathIndex][stepIndex].asArray().map(n => n.toFixed(3)).toString();
if (!(v in cachedIndices)) cachedIndices[v] = pushPoint(pathIndex, stepIndex);
return cachedIndices[v];
}
const computePointIndex = this.smooth ? cachedPoint : pushPoint;
for (let pathIndex = 1; pathIndex < pathCount; pathIndex++) {
for (let stepIndex = 1; stepIndex < stepCount; stepIndex++) {
indices.push(computePointIndex(pathIndex - 1, stepIndex - 1),
computePointIndex(pathIndex - 1, stepIndex),
computePointIndex(pathIndex, stepIndex - 1));
indices.push(computePointIndex(pathIndex - 1, stepIndex),
computePointIndex(pathIndex, stepIndex),
computePointIndex(pathIndex, stepIndex - 1));
}
}
VertexData.ComputeNormals(positions, indices, normals);
VertexData._ComputeSides(this.sideOrientation, positions, indices, normals, uvs);
vertexData.positions = new Float32Array(positions);
vertexData.indices = new Int32Array(indices);
vertexData.normals = new Float32Array(normals);
vertexData.uvs = new Float32Array(uvs);
vertexData.applyToMesh(m);
// const m = MeshBuilder.CreateRibbon(this.container.name + this.counter++, {
// pathArray: this.pen.paths!,
// sideOrientation: this.sideOrientation,
// });
m.parent = this.container;
this.meshes.push(m);
}
}
export const TurtlePrimitives: Environment<TurtleVM> = Object.assign({}, Primitives, {
export const TurtlePrimitives: Cat.Environment<TurtleVM> = Object.assign({}, Cat.Primitives, {
'Home'() { this.pos = new Vector3(); this.resetQ(new Quaternion()); return []; },
'GetPos'() { return [this.pos.asArray()]; },
@ -166,19 +248,18 @@ export const TurtlePrimitives: Environment<TurtleVM> = Object.assign({}, Primiti
'PenUp'() { this.penUp(false); return []; },
'Close'() { this.penUp(true); return []; },
'SideOrientation'(s) {
// TODO: why is this back to front?? argh
'SetSmooth'(b) { this.smooth = b as boolean; return []; },
'SetSideOrientation'(s) {
switch (s) {
case "default": this.sideOrientation = Mesh.BACKSIDE; break;
case "front": this.sideOrientation = Mesh.BACKSIDE; break;
case "back": this.sideOrientation = Mesh.FRONTSIDE; break;
case "default": this.sideOrientation = Mesh.DEFAULTSIDE; break;
case "front": this.sideOrientation = Mesh.FRONTSIDE; break;
case "back": this.sideOrientation = Mesh.BACKSIDE; break;
case "double": this.sideOrientation = Mesh.DOUBLESIDE; break;
default:
break;
default: throw new TypeError("Invalid SideOrientation: " + s);
}
return [];
},
} satisfies Environment<TurtleVM>);
} satisfies Cat.Environment<TurtleVM>);
function mitredExtrude(
name: string,