Compare commits
5 Commits
a3365a5766
...
a12209cdec
Author | SHA1 | Date |
---|---|---|
Tony Garnock-Jones | a12209cdec | |
Tony Garnock-Jones | 7fba434a0d | |
Tony Garnock-Jones | 4bee2be4f0 | |
Tony Garnock-Jones | 695049ad4b | |
Tony Garnock-Jones | 3dab93e6d0 |
|
@ -26,6 +26,7 @@
|
|||
"@rollup/plugin-node-resolve": "15.0",
|
||||
"@syndicate-lang/ts-plugin": "*",
|
||||
"@syndicate-lang/tsc": "*",
|
||||
"esm": "^3.2.25",
|
||||
"rollup": "3.8",
|
||||
"rollup-plugin-sourcemaps": "0.6",
|
||||
"tslib": "2.4",
|
||||
|
|
|
@ -5,7 +5,7 @@ Variable = <variable @spriteName string @variable symbol @value any> .
|
|||
|
||||
Shape = Mesh / Light / Scale / Move / Rotate / @many [Shape ...] / Texture / Color / Sound / Name / Floor / Nonphysical / Touchable / CSG / Skybox .
|
||||
|
||||
Mesh = Sphere / Box / Ground / Plane / External .
|
||||
Mesh = Sphere / Box / Ground / Plane / External / @turtle turtle.Shape .
|
||||
|
||||
Sphere = <sphere> .
|
||||
Box = <box> .
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
version 1 .
|
||||
|
||||
Program = Block .
|
||||
Block = [Token ...] .
|
||||
Token = @i int / @d double / @b bool / @s string / @v symbol / @block Block .
|
||||
|
||||
Shape = <turtle @program Program> .
|
|
@ -92,18 +92,58 @@
|
|||
; <scale <v 0.01 0.01 0.01>
|
||||
; <external "objects/IKE020001_obj/IKEA-Arild_2_Seat_Sofa-3D.obj">>>>
|
||||
|
||||
<sprite "x" []
|
||||
<move <v 10.0 1.6 -5.0>
|
||||
<texture ["textures/oak-herringbone-5e80fb40b00c9-1200.jpg"
|
||||
<v 1.0 3.0 1.0>
|
||||
<v 0.0 0.0 0.0>]
|
||||
<sound <stream "https://streams.95bfm.com/stream95">
|
||||
<csg
|
||||
<subtract [
|
||||
<move <v 0.0 0.0 0.5> <scale <v 1.1 3.1 1.1> <mesh <box>>>>
|
||||
<move <v 0.0 0.0 0.5> <scale <v 1.0 3.0 1.0> <mesh <box>>>>
|
||||
]>
|
||||
>>>>>
|
||||
; <sprite "x" []
|
||||
; <move <v 10.0 1.6 -5.0>
|
||||
; <texture ["textures/oak-herringbone-5e80fb40b00c9-1200.jpg"
|
||||
; <v 1.0 3.0 1.0>
|
||||
; <v 0.0 0.0 0.0>]
|
||||
; <sound <stream "https://streams.95bfm.com/stream95">
|
||||
; <csg
|
||||
; <subtract [
|
||||
; <move <v 0.0 0.0 0.5> <scale <v 1.1 3.1 1.1> <mesh <box>>>>
|
||||
; <move <v 0.0 0.0 0.5> <scale <v 1.0 3.0 1.0> <mesh <box>>>>
|
||||
; ]>
|
||||
; >>>>>
|
||||
|
||||
; <sprite "tt" []
|
||||
; <move <v 0.0 1.6 0.0>
|
||||
; <turtle [
|
||||
; 1 to slen
|
||||
; 0.025 to dlen
|
||||
; 90 U
|
||||
; dlen 10 * F
|
||||
; PenDown
|
||||
; 90 L
|
||||
; [12 L dlen 2 * F] 30 times
|
||||
; 90 R
|
||||
; slen F
|
||||
; SetPen
|
||||
; Home
|
||||
; PenDown
|
||||
; [6 CW dlen F] 240 times
|
||||
; PenUp
|
||||
; ]>>>
|
||||
|
||||
<sprite "tt" []
|
||||
<move <v 0.0 0.01 0.75>
|
||||
<turtle [
|
||||
1.0 to wallHeight
|
||||
0.1 to wallDepth
|
||||
|
||||
90 U
|
||||
PenDown
|
||||
[wallHeight F 90 L wallDepth F 90 L] 2 times
|
||||
SetPen
|
||||
Home
|
||||
|
||||
[ 10 to k k / [dup F] k times drop ] to ff
|
||||
|
||||
1 CornerDivisions
|
||||
1 1 1 PenScale
|
||||
PenDown
|
||||
1 ff 90 R 1 ff 90 R 1 ff 90 R 1 ff 90 R
|
||||
PenUp
|
||||
]>>>
|
||||
|
||||
; <Exit "y" "lobby">
|
||||
; <sprite "y" []
|
||||
|
|
|
@ -0,0 +1,328 @@
|
|||
import {
|
||||
Float,
|
||||
Reader,
|
||||
Record,
|
||||
Value as PreservesValue,
|
||||
is,
|
||||
stringify,
|
||||
} from '@syndicate-lang/core';
|
||||
|
||||
export type Input = PreservesValue<any>[] | string;
|
||||
|
||||
export type Token = number | boolean | string | symbol | Token[];
|
||||
|
||||
export type Value<V extends VM<V>> = number | boolean | string | Closure<V> | Primitive<V> | Value<V>[];
|
||||
export type Closure<V extends VM<V>> = { env: EnvironmentChain<V>, code: Token[] };
|
||||
export type Primitive<V extends VM<V>> = (this: V, ... args: Value<V>[]) => Value<V>[];
|
||||
export type EnvironmentChain<V extends VM<V>> = null | { rib: Environment<V>, next: EnvironmentChain<V> };
|
||||
export type Environment<V extends VM<V>> = { [key: string]: Value<V> };
|
||||
export type Frame<V extends VM<V>> = Closure<V> & { ip: number };
|
||||
|
||||
export class FuelCell {
|
||||
fuel = 10000;
|
||||
}
|
||||
|
||||
export class RuntimeError extends Error {}
|
||||
export class FuelExhausted extends RuntimeError {}
|
||||
export class StackUnderflow extends RuntimeError {}
|
||||
export class TypeError extends RuntimeError {}
|
||||
|
||||
export class SyntaxError extends Error {}
|
||||
|
||||
export function asToken(x: PreservesValue<unknown>): Token {
|
||||
switch (typeof x) {
|
||||
case 'number':
|
||||
case 'boolean':
|
||||
case 'symbol':
|
||||
case 'string':
|
||||
return x;
|
||||
case 'object':
|
||||
if (Float.isFloat(x)) {
|
||||
return x.value;
|
||||
}
|
||||
if (Array.isArray(x) && !Record.isRecord(x)) {
|
||||
return x.map(asToken);
|
||||
}
|
||||
/* fall through */
|
||||
default:
|
||||
throw new SyntaxError('Invalid program token: ' + stringify(x));
|
||||
}
|
||||
}
|
||||
|
||||
export class VM<Self extends VM<Self>> {
|
||||
stack: Value<Self>[] = [];
|
||||
rstack: Frame<Self>[] = [];
|
||||
fuel = new FuelCell();
|
||||
debug = false;
|
||||
|
||||
static parse(program: string): Token[] {
|
||||
return new Reader(program).readToEnd().map(asToken);
|
||||
}
|
||||
|
||||
constructor(program: Input, primitives: Environment<Self> = Primitives) {
|
||||
const code = typeof program === 'string' ? VM.parse(program) : program.map(asToken);
|
||||
this.invoke({ env: { rib: primitives, next: null }, code });
|
||||
}
|
||||
|
||||
push(... vs: Value<Self>[]) {
|
||||
for (const v of vs) {
|
||||
if (v === void 0 || v === null) {
|
||||
throw new TypeError("Unexpected null/undefined");
|
||||
}
|
||||
this.stack.push(v);
|
||||
}
|
||||
}
|
||||
|
||||
pop(): Value<Self> {
|
||||
const v = this.stack.pop();
|
||||
if (v === void 0) throw new StackUnderflow("Stack underflow");
|
||||
return v;
|
||||
}
|
||||
|
||||
take(n: number): Value<Self>[] {
|
||||
if (this.stack.length < n) {
|
||||
throw new StackUnderflow("Stack underflow: need " +n+", have "+this.stack.length);
|
||||
}
|
||||
return n > 0 ? this.stack.splice(-n) : [];
|
||||
}
|
||||
|
||||
invoke(c: Closure<Self> | Primitive<Self>) {
|
||||
if (typeof c === 'function') {
|
||||
this.push(... c.call(this as unknown as Self, ... this.take(c.length)));
|
||||
} else {
|
||||
this.popToPending(); // tail calls
|
||||
this.rstack.push({ env: { rib: {}, next: c.env }, code: c.code, ip: 0 });
|
||||
}
|
||||
}
|
||||
|
||||
apply(who: string, v: Value<Self>) {
|
||||
switch (typeof v) {
|
||||
case 'function':
|
||||
return this.invoke(v);
|
||||
case 'object':
|
||||
if (!Array.isArray(v)) {
|
||||
return this.invoke(v);
|
||||
}
|
||||
/* fall through */
|
||||
default:
|
||||
throw new TypeError('Got non-callable in `'+who+'`: ' + stringify(v));
|
||||
}
|
||||
}
|
||||
|
||||
popToPending(): boolean {
|
||||
while (this.rstack.length > 0) {
|
||||
if (this.frame.ip < this.frame.code.length) {
|
||||
return true;
|
||||
}
|
||||
this.rstack.pop();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
get frame(): Frame<Self> {
|
||||
return this.rstack[this.rstack.length - 1];
|
||||
}
|
||||
|
||||
lookup(name: string): Value<Self> {
|
||||
let env = this.frame.env;
|
||||
while (env !== null) {
|
||||
const v = env.rib[name];
|
||||
if (v !== void 0) return v;
|
||||
env = env.next;
|
||||
}
|
||||
throw new Error("Unknown operator: " + name);
|
||||
}
|
||||
|
||||
step(): boolean {
|
||||
if (!this.popToPending()) return false;
|
||||
|
||||
if (this.fuel.fuel <= 0) throw new FuelExhausted("Fuel exhausted");
|
||||
this.fuel.fuel--;
|
||||
|
||||
const op = this.frame.code[this.frame.ip++];
|
||||
|
||||
switch (typeof op) {
|
||||
case 'number':
|
||||
case 'boolean':
|
||||
case 'string':
|
||||
this.push(op);
|
||||
break;
|
||||
|
||||
case 'symbol':
|
||||
const v = this.lookup(op.description!);
|
||||
switch (typeof v) {
|
||||
case 'number':
|
||||
case 'boolean':
|
||||
case 'string':
|
||||
this.push(v);
|
||||
break;
|
||||
|
||||
case 'function':
|
||||
case 'object':
|
||||
if (Array.isArray(v)) {
|
||||
this.push(v);
|
||||
} else {
|
||||
this.invoke(v);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
((_: never) => { throw new Error("Unhandled environment value: " + _); })(v);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'object':
|
||||
this.push({ env: this.frame.env, code: op });
|
||||
break;
|
||||
|
||||
default:
|
||||
((_: never) => { throw new Error("Unhandled token: " + _); })(op);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
nextToken<T extends Token>(who: string, f: ((v: Token) => v is T) = ((_v): _v is T => true)): T {
|
||||
const t = this.frame.code[this.frame.ip++];
|
||||
if (typeof t === 'undefined') throw new SyntaxError("Missing token after `"+who+"`");
|
||||
if (!f(t)) throw new SyntaxError("Syntax error after `"+who+"`");
|
||||
return t;
|
||||
}
|
||||
|
||||
nextLiteralSymbol(who: string): symbol {
|
||||
return this.nextToken(who, (v): v is symbol => typeof v === 'symbol');
|
||||
}
|
||||
|
||||
exec() {
|
||||
if (this.debug) {
|
||||
while (this.step()) {
|
||||
console.log(this._summary());
|
||||
this.popToPending();
|
||||
console.log(this.frame?.code[this.frame.ip]);
|
||||
}
|
||||
} else {
|
||||
while (this.step()) {}
|
||||
}
|
||||
}
|
||||
|
||||
_summary(): string {
|
||||
function cstr(c: Token): string {
|
||||
if (typeof c === 'number') {
|
||||
return '' + c;
|
||||
} else if (Array.isArray(c)) {
|
||||
return '[' + c.map(cstr).join(' ') + ']';
|
||||
} else {
|
||||
return stringify(c);
|
||||
}
|
||||
}
|
||||
function vstr(v: Value<Self>): string {
|
||||
switch (typeof v) {
|
||||
case 'number':
|
||||
return '' + v;
|
||||
case 'function':
|
||||
return '#' + v.name;
|
||||
case 'object':
|
||||
if (Array.isArray(v)) {
|
||||
return '[' + v.map(vstr).join(' ') + ']';
|
||||
} else {
|
||||
return '#<closure ' + cstr(v.code) + '>';
|
||||
}
|
||||
default:
|
||||
return stringify(v);
|
||||
}
|
||||
}
|
||||
return `${this.rstack.length}: ${vstr(this.stack)}`;
|
||||
}
|
||||
}
|
||||
|
||||
export const Primitives: Environment<any> = {
|
||||
'+'(a, b) { return [(a as number) + (b as number)]; },
|
||||
'-'(a, b) { return [(a as number) - (b as number)]; },
|
||||
'*'(a, b) { return [(a as number) * (b as number)]; },
|
||||
'/'(a, b) { return [(a as number) / (b as number)]; },
|
||||
'%'(a, b) { return [(a as number) % (b as number)]; },
|
||||
'neg'(v) { return [-(v as number)]; },
|
||||
|
||||
'to'() {
|
||||
const n_or_ns = this.nextToken('to', (v: any): v is (symbol | symbol[]) =>
|
||||
typeof v === 'symbol' || (Array.isArray(v) && v.every(w => typeof w === 'symbol')));
|
||||
const ns = Array.isArray(n_or_ns) ? n_or_ns : [n_or_ns];
|
||||
const vs = this.take(ns.length);
|
||||
const env = this.frame.env!;
|
||||
for (let i = 0; i < ns.length; i++) env.rib[ns[i].description!] = vs[i];
|
||||
return [];
|
||||
},
|
||||
'quote'() {
|
||||
return [this.lookup(this.nextLiteralSymbol('quote').description!)];
|
||||
},
|
||||
'apply'(v) {
|
||||
this.apply('apply', v);
|
||||
return [];
|
||||
},
|
||||
|
||||
'eq'(a, b) { return [is(a, b)]; },
|
||||
'lt'(a, b) { return [a < b]; },
|
||||
'gt'(a, b) { return [a > b]; },
|
||||
'le'(a, b) { return [a <= b]; },
|
||||
'ge'(a, b) { return [a >= b]; },
|
||||
|
||||
'swap'(v, w) { return [w, v]; },
|
||||
'dup'(v) { return [v, v]; },
|
||||
'over'(v, w) { return [v, w, v]; },
|
||||
'rot'(v, w, x) { return [w, x, v]; },
|
||||
'-rot'(v, w, x) { return [x, v, w]; },
|
||||
'drop'(_v) { return []; },
|
||||
|
||||
'take'(n) { return [this.take(n as number)]; },
|
||||
'++'(vs, ws) {
|
||||
(vs as Value<any>[]).push(... (ws as Value<any>[]));
|
||||
return [vs];
|
||||
},
|
||||
'!'(vs, w) {
|
||||
(vs as Value<any>[]).push(w);
|
||||
return [vs];
|
||||
},
|
||||
|
||||
'?'(n, vs) { return [(vs as Value<any>[])[n as number]]; },
|
||||
'length'(a) { return [(a as Value<any>[]).length]; },
|
||||
'save'() { return [this.take(this.stack.length)]; },
|
||||
'restore'(vs) { return (vs as Value<any>[]); },
|
||||
'untake'(vs) { (vs as Value<any>[]).push((vs as Value<any>[]).length); return (vs as Value<any>[]); },
|
||||
|
||||
'not'(v) { return [!v]; },
|
||||
|
||||
'if'(v, t) { if (!!v) this.apply('if', t); return []; },
|
||||
'ifelse'(v, t, f) { if (!!v) this.apply('ifelse', t); else this.apply('ifelse', f); return []; },
|
||||
};
|
||||
|
||||
function _code(s: string): Closure<any> {
|
||||
return ({ env: { rib: Primitives, next: null }, code: VM.parse(s) });
|
||||
}
|
||||
|
||||
Object.assign(Primitives, {
|
||||
'prepend': _code('to [vs n] n take vs ++'),
|
||||
'times': _code('dup 0 le [drop drop] [to [c n] c quote c n 1 - times] ifelse'),
|
||||
'iota': _code('to [n] 0 [dup 1 +] n times take'),
|
||||
'map': _code('to [vs f] 0 [dup vs ? f swap 1 + ] vs length times take'),
|
||||
'flatMap': _code('to [vs f] 0 take 0 [dup vs ? f rot swap ++ swap 1 +] vs length times drop'),
|
||||
'filter': _code('to [vs f] vs [dup f [1 take] [drop 0 take] ifelse] flatMap'),
|
||||
});
|
||||
|
||||
// function R(s: string) {
|
||||
// console.log('\n' + s);
|
||||
// try {
|
||||
// const vm = new VM(s);
|
||||
// vm.exec();
|
||||
// console.log(s, '-->', vm.stack);
|
||||
// } catch (e) {
|
||||
// console.error(s, '-/->', (e as Error).message);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// R('[dup 0 eq [drop] [dup 1 - x] ifelse] to x 5 x')
|
||||
// R('[dup 0 eq [drop] [dup to v 1 - x v] ifelse] to x 5 x')
|
||||
// R('[1 - dup 0 lt [drop] [dup x] ifelse] to x 5 x')
|
||||
// R('[1 - dup 0 lt [drop] [dup to v x v] ifelse] to x 5 x')
|
||||
// R('[3] 5 times');
|
||||
// R('2 3 4 save [2 *] map restore');
|
||||
// R('20 iota [dup 3 % 0 eq [1 take] [drop 0 take] ifelse] flatMap restore');
|
||||
// R('20 iota [3 % 0 eq] filter restore');
|
|
@ -299,7 +299,7 @@ export class RunningEngine {
|
|||
|
||||
jump() {
|
||||
if (Math.abs(this.camera.cameraDirection.y) < 0.1) {
|
||||
this.camera.cameraDirection.y += 1 * this.frameRateScale;
|
||||
this.camera.cameraDirection.y += 2 * 9.81 * this.frameRateScale;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,9 +20,10 @@ import {
|
|||
Vector2,
|
||||
Vector3,
|
||||
} from '@babylonjs/core/Legacy/legacy';
|
||||
import { Dataflow, IdentityMap, KeyedDictionary, Ref, Value, is } from "@syndicate-lang/core";
|
||||
import { Dataflow, IdentityMap, KeyedDictionary, Ref, Value, is, fromJS } from "@syndicate-lang/core";
|
||||
|
||||
import * as Shapes from './gen/shapes.js';
|
||||
import { TurtleVM } from './turtle.js';
|
||||
|
||||
export class Environment {
|
||||
fields = new IdentityMap<symbol, Dataflow.Field<Value<Ref>>>();
|
||||
|
@ -260,6 +261,12 @@ export function buildMesh(
|
|||
}),
|
||||
};
|
||||
}
|
||||
case "turtle": {
|
||||
const t = new TurtleVM(name, scene, meshSpec.value.program.map(fromJS));
|
||||
t.debug = true;
|
||||
t.exec();
|
||||
return { rootnode: t.container };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,183 @@
|
|||
import {
|
||||
Mesh,
|
||||
MeshBuilder,
|
||||
Quaternion,
|
||||
Scene,
|
||||
Vector3,
|
||||
} from '@babylonjs/core/Legacy/legacy';
|
||||
|
||||
import { VM as BaseVM, Input, Primitives, Environment } from './cat.js';
|
||||
|
||||
export class PenState {
|
||||
templatePath: Vector3[] = [new Vector3()];
|
||||
paths: Vector3[][] | null = null;
|
||||
templateScale = new Vector3(1, 1, 1);
|
||||
|
||||
get isDown(): boolean {
|
||||
return this.paths !== null;
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.templatePath = [new Vector3()];
|
||||
this.paths = null;
|
||||
}
|
||||
|
||||
set() {
|
||||
if (!this.paths) throw new Error("Cannot set pen with no paths");
|
||||
this.templatePath = this.paths[0];
|
||||
this.paths = null;
|
||||
}
|
||||
|
||||
down() {
|
||||
this.paths = this.templatePath.map(_p => []);
|
||||
}
|
||||
|
||||
push(pos: Vector3, q: Quaternion) {
|
||||
this.templatePath.forEach((p, i) => {
|
||||
const r = new Vector3();
|
||||
p.multiplyToRef(this.templateScale, r);
|
||||
r.rotateByQuaternionToRef(q, r);
|
||||
r.addInPlace(pos);
|
||||
this.paths![i].push(r);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const D2R = Math.PI / 180;
|
||||
|
||||
export class TurtleVM extends BaseVM<TurtleVM> {
|
||||
container: Mesh;
|
||||
counter = 0;
|
||||
sideOrientation = Mesh.BACKSIDE; // TODO: see SideOrientation primitive below
|
||||
|
||||
pen = new PenState();
|
||||
pos = new Vector3();
|
||||
q = new Quaternion();
|
||||
cornerDivisions = 0;
|
||||
|
||||
get euler(): Vector3 {
|
||||
return this.q.toEulerAngles();
|
||||
}
|
||||
|
||||
constructor(
|
||||
name: string,
|
||||
scene: Scene | null,
|
||||
program: Input,
|
||||
) {
|
||||
super(program, TurtlePrimitives);
|
||||
this.container = new Mesh(name, scene);
|
||||
}
|
||||
|
||||
forwardBy(dist: number) {
|
||||
const v = new Vector3(0, 0, dist);
|
||||
v.rotateByQuaternionToRef(this.q, v);
|
||||
this.pos.addInPlace(v);
|
||||
if (this.pen.isDown) this.pen.push(this.pos, this.q);
|
||||
}
|
||||
|
||||
setQ(newQ: Quaternion) {
|
||||
if (this.cornerDivisions === 0) {
|
||||
this.q = newQ;
|
||||
} else {
|
||||
this.q = newQ;
|
||||
this.pen.push(this.pos, this.q);
|
||||
}
|
||||
}
|
||||
|
||||
rotate(y: number, x: number, z: number) {
|
||||
const e = this.euler;
|
||||
e.addInPlaceFromFloats(x * D2R, y * D2R, z * D2R);
|
||||
this.setQ(e.toQuaternion());
|
||||
}
|
||||
|
||||
relativeRotate(y: number, x: number, z: number) {
|
||||
this.setQ(this.q.multiply(Quaternion.FromEulerAngles(x * D2R, y * D2R, z * D2R)));
|
||||
}
|
||||
|
||||
penDown() {
|
||||
this.pen.down();
|
||||
this.pen.push(this.pos, this.q);
|
||||
}
|
||||
|
||||
penUp(close: boolean) {
|
||||
if (!this.pen.isDown) return;
|
||||
|
||||
if (close) {
|
||||
throw new Error('todo');
|
||||
}
|
||||
|
||||
const m = MeshBuilder.CreateRibbon(this.container.name + this.counter++, {
|
||||
pathArray: this.pen.paths!,
|
||||
sideOrientation: this.sideOrientation,
|
||||
});
|
||||
m.parent = this.container;
|
||||
}
|
||||
}
|
||||
|
||||
export const TurtlePrimitives: Environment<TurtleVM> = Object.assign({}, Primitives, {
|
||||
'Home'() { this.pos = new Vector3(); this.q = new Quaternion(); return []; },
|
||||
|
||||
'GetPos'() { return [this.pos.asArray()]; },
|
||||
'GetX'() { return [this.pos.x]; },
|
||||
'GetY'() { return [this.pos.y]; },
|
||||
'GetZ'() { return [this.pos.z]; },
|
||||
'SetX'(v) { this.pos.x = v as number; return []; },
|
||||
'SetY'(v) { this.pos.y = v as number; return []; },
|
||||
'SetZ'(v) { this.pos.z = v as number; return []; },
|
||||
'SetPos'(x, y, z) {
|
||||
this.pos.set(x as number, y as number, z as number);
|
||||
return [];
|
||||
},
|
||||
|
||||
'GetHeading'() { return [this.euler.asArray().map(v => v / D2R)]; },
|
||||
'GetRX'() { return [this.euler.x / D2R]; },
|
||||
'GetRY'() { return [this.euler.y / D2R]; },
|
||||
'GetRZ'() { return [this.euler.z / D2R]; },
|
||||
'SetRX'(v) { this.q.x = v as number * D2R; return []; },
|
||||
'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);
|
||||
return [];
|
||||
},
|
||||
|
||||
'F'(dist) { this.forwardBy(dist as number); return []; },
|
||||
'B'(dist) { this.forwardBy(-(dist as number)); return []; },
|
||||
|
||||
'RX'(degrees) { this.rotate(0, degrees as number, 0); return []; },
|
||||
'RY'(degrees) { this.rotate(degrees as number, 0, 0); return []; },
|
||||
'RZ'(degrees) { this.rotate(0, 0, degrees as number); return []; },
|
||||
|
||||
'U'(degrees) { this.relativeRotate(0, -(degrees as number), 0); return []; },
|
||||
'D'(degrees) { this.relativeRotate(0, degrees as number, 0); return []; },
|
||||
'L'(degrees) { this.relativeRotate(-(degrees as number), 0, 0); return []; },
|
||||
'R'(degrees) { this.relativeRotate(degrees as number, 0, 0); return []; },
|
||||
'CW'(degrees) { this.relativeRotate(0, 0, -(degrees as number)); return []; },
|
||||
'CCW'(degrees) { this.relativeRotate(0, 0, degrees as number); return []; },
|
||||
|
||||
'ClearPen'() { this.pen.clear(); return []; },
|
||||
'SetPen'() { this.pen.set(); return []; },
|
||||
|
||||
'PenScale'(sx, sy, sz) {
|
||||
this.pen.templateScale = new Vector3(sx as number, sy as number, sz as number);
|
||||
return [];
|
||||
},
|
||||
'PenDown'() { this.penDown(); return []; },
|
||||
'PenUp'() { this.penUp(false); return []; },
|
||||
'Close'() { this.penUp(true); return []; },
|
||||
|
||||
'SideOrientation'(s) {
|
||||
// TODO: why is this back to front?? argh
|
||||
switch (s) {
|
||||
case "default": this.sideOrientation = Mesh.BACKSIDE; break;
|
||||
case "front": this.sideOrientation = Mesh.BACKSIDE; break;
|
||||
case "back": this.sideOrientation = Mesh.FRONTSIDE; break;
|
||||
case "double": this.sideOrientation = Mesh.DOUBLESIDE; break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return [];
|
||||
},
|
||||
|
||||
'CornerDivisions'(n) { this.cornerDivisions = n as number; return []; },
|
||||
} satisfies Environment<TurtleVM>);
|
|
@ -278,6 +278,11 @@ escalade@^3.1.1:
|
|||
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
|
||||
integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
|
||||
|
||||
esm@^3.2.25:
|
||||
version "3.2.25"
|
||||
resolved "https://registry.yarnpkg.com/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10"
|
||||
integrity sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==
|
||||
|
||||
estree-walker@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700"
|
||||
|
|
Loading…
Reference in New Issue