@rgsoft/turtle
Version:
Turtle graphics and L-Systems library
199 lines (196 loc) • 6.07 kB
JavaScript
// src/types.ts
var alphabetSymbols = ["F", "G", "H", "L", "M", "D", "U", "+", "-", "[", "]"];
var isValidAlphabetSymbol = (chr) => alphabetSymbols.includes(chr);
// src/rule.ts
var Rule = class {
constructor(rule) {
const matches = rule.replace(/\s/g, "").match(/^(.)(-|=)>(.+)$/);
if (!matches) {
throw new Error("Unrecognized rule pattern");
}
const base = matches[1];
const next = matches[3];
if (!isValidAlphabetSymbol(base)) {
throw new Error(`Symbol '${base}' not recognized in condition`);
}
this._base = base;
this._next = [];
next.split("").forEach((c) => {
if (!isValidAlphabetSymbol(c)) {
throw new Error(`Symbol '${c}' not recognized in predicate`);
}
this._next.push(c);
});
}
get base() {
return this._base;
}
get next() {
return this._next;
}
get nextString() {
return this._next.join("");
}
toString() {
return `${this.base} => ${this._next.join("")}`;
}
};
// src/lsystem.ts
var LSystem = class {
constructor(axiom, rules) {
this._axiom = "";
this._sentence = "";
this._generation = 0;
this.axiom = axiom;
this._rules = rules.map((rule) => typeof rule === "string" ? new Rule(rule) : rule);
this.init();
}
init() {
this._sentence = this._axiom;
this._generation = 0;
}
/**
*
* @param { number } n
*/
generate(n = 1) {
let nextGen;
for (let i = 0; i < n; i++) {
nextGen = "";
this._sentence.split("").forEach((c) => {
const rule = this._rules.find((r) => r.base === c);
nextGen = nextGen.concat(rule === void 0 ? c : rule.nextString);
});
this._sentence = nextGen;
this._generation++;
}
}
get sentence() {
return this._sentence;
}
get generation() {
return this._generation;
}
get rules() {
return [...this._rules];
}
set axiom(val) {
val.split("").forEach((c) => {
if (!isValidAlphabetSymbol(c)) {
throw new Error(`Symbol '${c}' not recognized in axiom`);
}
});
this._axiom = val;
this.init();
}
get axiom() {
return this._axiom;
}
};
// src/turtle.ts
var Turtle = class {
constructor(config) {
this.config = config;
/**
* @type { number }
*/
this.currentScale = 1;
this.alphabetMap = {
F: () => {
const ctx = this.config.context;
const lineWith = this.config.scale.scaleStrokeWeight ? this.config.strokeWeight * this.currentScale : this.config.strokeWeight;
const lineSize = this.config.scale.scaleStrokeSize ? this.config.strokeSize * this.currentScale : this.config.strokeSize;
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineCap = "round";
ctx.lineWidth = lineWith;
ctx.lineTo(lineSize, 0);
ctx.stroke();
ctx.translate(lineSize, 0);
},
G: () => {
const ctx = this.config.context;
const lineWith = this.config.scale.scaleStrokeWeight ? this.config.strokeWeight * this.currentScale : this.config.strokeWeight;
const lineSize = this.config.scale.scaleStrokeSize ? this.config.strokeSize * this.currentScale : this.config.strokeSize;
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineCap = "round";
ctx.lineWidth = lineWith;
ctx.lineTo(lineSize, 0);
ctx.stroke();
ctx.translate(lineSize, 0);
},
H: () => {
const ctx = this.config.context;
const lineWith = this.config.scale.scaleStrokeWeight ? this.config.strokeWeight * this.currentScale : this.config.strokeWeight;
const lineSize = this.config.scale.scaleStrokeSize ? this.config.strokeSize * this.currentScale : this.config.strokeSize;
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineCap = "round";
ctx.lineWidth = lineWith;
ctx.lineTo(lineSize, 0);
ctx.stroke();
ctx.translate(lineSize, 0);
},
L: () => {
const ctx = this.config.context;
const lineWith = this.config.scale.scaleStrokeWeight ? this.config.strokeWeight * this.currentScale : this.config.strokeWeight;
const lineSize = this.config.scale.scaleStrokeSize ? this.config.strokeSize * this.currentScale : this.config.strokeSize;
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineCap = "round";
ctx.lineWidth = lineWith;
ctx.arc(lineSize * 0.5, lineSize * 0.5, lineSize * Math.SQRT1_2, Math.PI * 1.25, Math.PI * 1.75);
ctx.arc(lineSize * 0.5, -lineSize * 0.5, lineSize * Math.SQRT1_2, Math.PI * 0.25, Math.PI * 0.75);
ctx.stroke();
},
M: () => {
const lineSize = this.config.scale.scaleStrokeSize ? this.config.strokeSize * this.currentScale : this.config.strokeSize;
this.config.context.translate(lineSize, 0);
},
D: () => this.currentScale *= this.config.scale.ratio,
U: () => this.currentScale /= this.config.scale.ratio,
"+": () => {
const angle = this.config.scale.scaleAngle ? this.config.angle * this.currentScale : this.config.angle;
this.config.context.rotate(angle);
},
"-": () => {
const angle = this.config.scale.scaleAngle ? this.config.angle * this.currentScale : this.config.angle;
this.config.context.rotate(-angle);
},
"[": () => this.config.context.save(),
"]": () => this.config.context.restore()
};
}
/**
*
* @param { string } sentence
*/
render(sentence) {
for (let i = 0; i < sentence.length; i++) {
const chr = sentence[i];
const fn = this.alphabetMap[chr];
if (typeof fn === "function") {
fn.call(this);
}
}
}
/**
*
* @param { number } x
* @param { number } y
* @param { number } angle
*/
init(x, y, angle) {
this.config.context.resetTransform();
this.config.context.translate(x, y);
this.config.context.rotate(angle);
this.currentScale = 1;
}
};
export {
LSystem,
Rule,
Turtle
};
//# sourceMappingURL=index.mjs.map