diff --git a/scene/lobby.pr b/scene/lobby.pr index 7befe5d..14c16c6 100644 --- a/scene/lobby.pr +++ b/scene/lobby.pr @@ -120,16 +120,18 @@ slen F SetPen Home + "double" SetSideOrientation + #t SetSmooth PenDown [6 CW dlen F] 240 times PenUp ]>>>> + + 1.0] { +export class TurtleVM extends Cat.VM { 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 { 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 { 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 = Object.assign({}, Primitives, { +export const TurtlePrimitives: Cat.Environment = 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 = 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); +} satisfies Cat.Environment); function mitredExtrude( name: string,