diff --git a/scene/lobby.pr b/scene/lobby.pr index 5b4b0f1..713e4ac 100644 --- a/scene/lobby.pr +++ b/scene/lobby.pr @@ -105,45 +105,52 @@ ; ]> ; >>>>> -; -; >> + + >>> - + + + 1.0] + >> + PenDown + 1 ff 90 rr 1 ff 90 rr 1 ff 90 rr 1 ff 90 rr + PenUp + ]>>>>> ; ; { container: Mesh; + meshes: Mesh[] = []; + counter = 0; sideOrientation = Mesh.BACKSIDE; // TODO: see SideOrientation primitive below pen = new PenState(); pos = new Vector3(); q = new Quaternion(); - cornerDivisions = 0; + prevQ = this.q; get euler(): Vector3 { return this.q.toEulerAngles(); @@ -76,12 +80,11 @@ export class TurtleVM extends BaseVM { } setQ(newQ: Quaternion) { - if (this.cornerDivisions === 0) { - this.q = newQ; - } else { - this.q = newQ; - this.pen.push(this.pos, this.q); - } + this.q = newQ; + } + + resetQ(newQ: Quaternion) { + this.q = this.prevQ = newQ; } rotate(y: number, x: number, z: number) { @@ -111,11 +114,12 @@ export class TurtleVM extends BaseVM { sideOrientation: this.sideOrientation, }); m.parent = this.container; + this.meshes.push(m); } } export const TurtlePrimitives: Environment = Object.assign({}, Primitives, { - 'Home'() { this.pos = new Vector3(); this.q = new Quaternion(); return []; }, + 'Home'() { this.pos = new Vector3(); this.resetQ(new Quaternion()); return []; }, 'GetPos'() { return [this.pos.asArray()]; }, 'GetX'() { return [this.pos.x]; }, @@ -137,7 +141,7 @@ export const TurtlePrimitives: Environment = Object.assign({}, Primiti 'SetRY'(v) { this.q.y = v as number * D2R; return []; }, 'SetRZ'(v) { this.q.z = v as number * D2R; return []; }, 'SetHeading'(x, y, z) { - this.q = Quaternion.FromEulerAngles(x as number * D2R, y as number * D2R, z as number * D2R); + this.resetQ(Quaternion.FromEulerAngles(x as number * D2R, y as number * D2R, z as number * D2R)); return []; }, @@ -178,6 +182,62 @@ export const TurtlePrimitives: Environment = Object.assign({}, Primiti } return []; }, - - 'CornerDivisions'(n) { this.cornerDivisions = n as number; return []; }, } satisfies Environment); + +function mitredExtrude( + name: string, + options: { shape: Vector3[], path: Vector3[], close?: boolean }, + scene: Scene, +): Mesh { + const shape = options.shape; + const path = options.path; + const closed = options.close ?? false; + + function miterNormal(v1: Vector3, v2: Vector3): Vector3 { + return Vector3.Cross(Vector3.Cross(v2, v1), v2.subtract(v1)); + } + + var allPaths = []; + + for (var s = 0; s < shape.length; s++) { + let axisZ = path[1].subtract(path[0]).normalize(); + const axisX = Vector3.Cross(scene.activeCamera!.position, axisZ).normalize(); + const axisY = Vector3.Cross(axisZ, axisX); + let startPoint = path[0].add(axisX.scale(shape[s].x)).add(axisY.scale(shape[s].y)); + const ribbonPath = [startPoint]; + for (var p = 0; p < path.length - 2; p++) { + const nextAxisZ = path[p + 2].subtract(path[p + 1]).normalize(); + + startPoint = startPoint.add(axisZ.scale(new Ray(startPoint, axisZ).intersectsPlane(Plane.FromPositionAndNormal(path[p + 1], miterNormal(axisZ, nextAxisZ)))!)); + + ribbonPath.push(startPoint); + axisZ = nextAxisZ; + } + // Last Point + if (closed) { + let nextAxisZ = path[0].subtract(path[path.length - 1]).normalize(); + startPoint = startPoint.add(axisZ.scale(new Ray(startPoint, axisZ).intersectsPlane(Plane.FromPositionAndNormal(path[path.length - 1], miterNormal(axisZ, nextAxisZ)))!)); + + ribbonPath.push(startPoint); + axisZ = nextAxisZ; + + nextAxisZ = path[1].subtract(path[0]).normalize(); + startPoint = startPoint.add(axisZ.scale(new Ray(startPoint, axisZ).intersectsPlane(Plane.FromPositionAndNormal(path[0], miterNormal(axisZ, nextAxisZ)))!)); + + ribbonPath.shift(); + ribbonPath.unshift(startPoint); + } else { + startPoint = startPoint.add(axisZ.scale(new Ray(startPoint, axisZ).intersectsPlane(Plane.FromPositionAndNormal(path[path.length - 1], axisZ))!)); + + ribbonPath.push(startPoint); + } + allPaths.push(ribbonPath); + } + + return MeshBuilder.CreateRibbon(name, { + pathArray: allPaths, + sideOrientation: Mesh.DOUBLESIDE, + closeArray: true, + closePath: closed, + }, scene); +}