aframe-lsystem-component
Version:
L-System/LSystem component for A-Frame to draw 3D turtle graphics. Using Lindenmayer as backend.
682 lines (573 loc) • 24 kB
JavaScript
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;
/******/
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ exports: {},
/******/ id: moduleId,
/******/ loaded: false
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.loaded = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ function(module, exports, __webpack_require__) {
// Require instead of importScripts because we use webpack
// with worker-loader for compiling source: https://github.com/webpack/worker-loader
let LSystem = __webpack_require__(1);
let lsystem = new LSystem({});
let timeout = {};
onmessage = function(e) {
// wait a few ms to start thread, to be able to cancel old tasks
clearTimeout(timeout);
timeout = setTimeout(function() {
lsystem.setAxiom(e.data.axiom);
lsystem.clearProductions();
for (let p of e.data.productions) {
lsystem.setProduction(p[0], p[1]);
}
lsystem.iterate(e.data.iterations);
postMessage({
result: lsystem.getString(),
initial: e.data
});
}, 20);
};
/***/ },
/* 1 */
/***/ function(module, exports) {
'use strict';
// Get a list of productions that have identical initiators,
// Output a single stochastic production. Probability per production
// is defined by amount of input productions (4 => 25% each, 2 => 50% etc.)
function transformClassicStochasticProductions(productions) {
return function transformedProduction() {
var resultList = productions; // the parser for productions shall create this list
var count = resultList.length;
var r = Math.random();
for (var i = 0; i < count; i++) {
var range = (i + 1) / count;
if (r <= range) return resultList[i];
}
console.error('Should have returned a result of the list, something is wrong here with the random numbers?.');
};
};
// TODO: Scaffold classic parametric and context sensitive stuff out of main file
// And simply require it here, eg:
// this.testClassicParametricSyntax = require(classicSyntax.testParametric)??
function testClassicParametricSyntax(axiom) {
return (/\(.+\)/.test(axiom)
);
};
// transforms things like 'A(1,2,5)B(2.5)' to
// [ {symbol: 'A', params: [1,2,5]}, {symbol: 'B', params:[25]} ]
// strips spaces
function transformClassicParametricAxiom(axiom) {
// Replace whitespaces, then split between square brackets.
var splitAxiom = axiom.replace(/\s+/g, '').split(/[\(\)]/);
// console.log('parts:', splitAxiom)
var newAxiom = [];
// Construct new axiom by getting the params and symbol.
for (var i = 0; i < splitAxiom.length - 1; i += 2) {
var params = splitAxiom[i + 1].split(',').map(Number);
newAxiom.push({ symbol: splitAxiom[i], params: params });
}
// console.log('parsed axiom:', newAxiom)
};
// transform a classic syntax production into valid JS production
// TODO: Only work on first part pf production P[0]
// -> this.transformClassicCSCondition
function transformClassicCSProduction(p) {
var _this = this;
// before continuing, check if classic syntax actually there
// example: p = ['A<B>C', 'Z']
// left should be ['A', 'B']
var left = p[0].match(/(\w+)<(\w)/);
// right should be ['B', 'C']
var right = p[0].match(/(\w)>(\w+)/);
// Not a CS-Production (no '<' or '>'),
//return original production.
if (left === null && right === null) {
return p;
}
// indexSymbol should be 'B' in A<B>C
// get it either from left side or right side if left is nonexistent
var indexSymbol = left !== null ? left[2] : right[1];
// double check: make sure that the right and left match got the same indexSymbol (B)
if (left !== null && right !== null && left[2] !== right[1]) {
throw new Error('index symbol differs in context sensitive production from left to right check.', left[2], '!==', right[1]);
}
// finally build the new (valid JS) production
// (that is being executed instead of the classic syntax,
// which can't be interpreted by the JS engine)
var transformedFunction = function transformedFunction(_ref) {
var _index = _ref.index;
var _part = _ref.part;
var _axiom = _ref.currentAxiom;
var _params = _ref.params;
var leftMatch = { result: true };
var rightMatch = { result: true };
// this can possibly be optimized (see: https://developers.google.com/speed/articles/optimizing-javascript#avoiding-pitfalls-with-closures)
//
if (left !== null) {
leftMatch = _this.match({ direction: 'left', match: left[1], index: _index, branchSymbols: '[]', ignoredSymbols: '+-&' });
}
// don't match with right side if left already false or no right match necessary
if (leftMatch.result === false || leftMatch.result === true && right === null) return leftMatch.result ? p[1] : false;
// see left!== null. could be optimized. Creating 3 variations of function
// so left/right are not checked here, which improves speed, as left/right
// are in a scope above.
if (right !== null) {
rightMatch = _this.match({ direction: 'right', match: right[2], index: _index, branchSymbols: '[]', ignoredSymbols: '+-&' });
}
// Match! On a match return either the result of given production function
// or simply return the symbol itself if its no function.
if (leftMatch.result && rightMatch.result) {
return typeof p[1] === 'function' ? p[1]({ index: _index, part: _part, currentAxiom: _axiom, params: _params, leftMatchIndices: leftMatch.matchIndices, rightMatchIndices: rightMatch.matchIndices }) : p[1];
} else {
return false;
}
};
var transformedProduction = [indexSymbol, transformedFunction];
return transformedProduction;
};
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
return typeof obj;
} : function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj;
};
var slicedToArray = function () {
function sliceIterator(arr, i) {
var _arr = [];
var _n = true;
var _d = false;
var _e = undefined;
try {
for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
_arr.push(_s.value);
if (i && _arr.length === i) break;
}
} catch (err) {
_d = true;
_e = err;
} finally {
try {
if (!_n && _i["return"]) _i["return"]();
} finally {
if (_d) throw _e;
}
}
return _arr;
}
return function (arr, i) {
if (Array.isArray(arr)) {
return arr;
} else if (Symbol.iterator in Object(arr)) {
return sliceIterator(arr, i);
} else {
throw new TypeError("Invalid attempt to destructure non-iterable instance");
}
};
}();
function LSystem(_ref) {
var axiom = _ref.axiom;
var productions = _ref.productions;
var finals = _ref.finals;
var branchSymbols = _ref.branchSymbols;
var ignoredSymbols = _ref.ignoredSymbols;
var classicParametricSyntax = _ref.classicParametricSyntax;
// faking default values until better support lands in all browser
axiom = typeof axiom !== 'undefined' ? axiom : '';
branchSymbols = typeof branchSymbols !== 'undefined' ? branchSymbols : [];
ignoredSymbols = typeof ignoredSymbols !== 'undefined' ? ignoredSymbols : [];
classicParametricSyntax = typeof classicParametricSyntax !== 'undefined' ? classicParametricSyntax : 'false';
// if using objects in axioms, as used in parametric L-Systems
this.getString = function () {
var onlySymbols = arguments.length <= 0 || arguments[0] === undefined ? true : arguments[0];
if (typeof this.axiom === 'string') return this.axiom;
if (onlySymbols === true) {
return this.axiom.reduce(function (prev, current) {
if (current.symbol === undefined) {
console.log('found:', current);
throw new Error('L-Systems that use only objects as symbols (eg: {symbol: \'F\', params: []}), cant use string symbols (eg. \'F\')! Check if you always return objects in your productions and no strings.');
}
return prev + current.symbol;
}, '');
} else {
return JSON.stringify(this.axiom);
}
};
this.setAxiom = function (axiom) {
this.axiom = axiom;
};
this.setProduction = function (A, B) {
var doAppend = arguments.length <= 2 || arguments[2] === undefined ? false : arguments[2];
var newProduction = [A, B];
if (newProduction === undefined) throw new Error('no production specified.');
if (this.parameters.allowClassicSyntax === true) {
newProduction = transformClassicCSProduction.bind(this)(newProduction);
}
var symbol = newProduction[0];
if (doAppend === true && this.productions.has(symbol)) {
var existingProduction = this.productions.get(symbol);
// If existing production results already in an array use this, otherwise
// create new array to append to.
var productionList = existingProduction[Symbol.iterator] !== undefined && typeof existingProduction !== 'string' && !(existingProduction instanceof String) ? this.productions.get(symbol) : [this.productions.get(symbol)];
productionList.push(newProduction[1]);
this.productions.set(symbol, productionList);
} else {
this.productions.set(newProduction[0], newProduction[1]);
}
};
// set multiple productions from name:value Object
this.setProductions = function (newProductions) {
if (newProductions === undefined) throw new Error('no production specified.');
this.clearProductions();
// TODO: once Object.entries() (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries) is stable, use that in combo instead of awkward for…in.
for (var condition in newProductions) {
if (newProductions.hasOwnProperty(condition)) {
this.setProduction(condition, newProductions[condition], true);
}
}
};
this.clearProductions = function () {
this.productions = new Map();
};
this.setFinal = function (symbol, final) {
var newFinal = [symbol, final];
if (newFinal === undefined) {
throw new Error('no final specified.');
}
this.finals.set(newFinal[0], newFinal[1]);
};
// set multiple finals from name:value Object
this.setFinals = function (newFinals) {
if (newFinals === undefined) throw new Error('no finals specified.');
this.finals = new Map();
for (var symbol in newFinals) {
if (newFinals.hasOwnProperty(symbol)) {
this.setFinal(symbol, newFinals[symbol]);
}
}
};
this.getProductionResult = function (p, index, part, params) {
var result = void 0;
// if p is a function, execute function and append return value
if (typeof p === 'function') {
result = p({ index: index, currentAxiom: this.axiom, part: part, params: params });
/* if p is no function and no iterable, then
it should be a string (regular) or object
directly return it then as result */
} else if (typeof p === 'string' || p instanceof String || (typeof p === 'undefined' ? 'undefined' : _typeof(p)) === 'object' && p[Symbol.iterator] === undefined) {
result = p;
// if p is a list/iterable
} else if (p[Symbol.iterator] !== undefined && typeof p !== 'string' && !(p instanceof String)) {
/*
go through the list and use
the first valid production in that list. (that returns true)
This assumes, it's a list of functions.
*/
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = p[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var _p = _step.value;
var _result = void 0;
if (_p[Symbol.iterator] !== undefined && typeof _p !== 'string' && !(_p instanceof String)) {
// If _p is itself also an Array, recursively get the result.
_result = this.getProductionResult(_p);
} else {
_result = typeof _p === 'function' ? _p({ index: index, currentAxiom: this.axiom, part: part, params: params }) : _p;
}
if (_result !== undefined && _result !== false) {
result = _result;
break;
}
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
}
return result;
};
this.applyProductions = function () {
// a axiom can be a string or an array of objects that contain the key/value 'symbol'
var newAxiom = typeof this.axiom === 'string' ? '' : [];
var index = 0;
// iterate all symbols/characters of the axiom and lookup according productions
var _iteratorNormalCompletion2 = true;
var _didIteratorError2 = false;
var _iteratorError2 = undefined;
try {
for (var _iterator2 = this.axiom[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
var part = _step2.value;
var symbol = part;
// Stuff for classic parametric L-Systems: get actual symbol and possible parameters
// params will be given the production function, if applicable.
var params = [];
if ((typeof part === 'undefined' ? 'undefined' : _typeof(part)) === 'object' && part.symbol) symbol = part.symbol;
if ((typeof part === 'undefined' ? 'undefined' : _typeof(part)) === 'object' && part.params) params = part.params;
var result = part;
if (this.productions.has(symbol)) {
var p = this.productions.get(symbol);
result = this.getProductionResult(p, index, part, params);
}
// finally add result to new axiom
if (typeof newAxiom === 'string') {
newAxiom += result;
} else {
// If result is an array, merge result into new axiom instead of pushing.
if (result.constructor === Array) {
Array.prototype.push.apply(newAxiom, result);
} else {
newAxiom.push(result);
}
}
index++;
}
// finally set new axiom and also return for convenience
} catch (err) {
_didIteratorError2 = true;
_iteratorError2 = err;
} finally {
try {
if (!_iteratorNormalCompletion2 && _iterator2.return) {
_iterator2.return();
}
} finally {
if (_didIteratorError2) {
throw _iteratorError2;
}
}
}
this.axiom = newAxiom;
return newAxiom;
};
// iterate n times
this.iterate = function () {
var n = arguments.length <= 0 || arguments[0] === undefined ? 1 : arguments[0];
this.iterations = n;
var lastIteration = void 0;
for (var iteration = 0; iteration < n; iteration++, this.iterationCount++) {
lastIteration = this.applyProductions();
}
return lastIteration;
};
this.final = function () {
var _iteratorNormalCompletion3 = true;
var _didIteratorError3 = false;
var _iteratorError3 = undefined;
try {
for (var _iterator3 = this.axiom[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
var part = _step3.value;
// if we have objects for each symbol, (when using parametric L-Systems)
// get actual identifiable symbol character
var symbol = part;
if ((typeof part === 'undefined' ? 'undefined' : _typeof(part)) === 'object' && part.symbol) symbol = part.symbol;
if (this.finals.has(symbol)) {
var finalFunction = this.finals.get(symbol);
var typeOfFinalFunction = typeof finalFunction === 'undefined' ? 'undefined' : _typeof(finalFunction);
if (typeOfFinalFunction !== 'function') {
throw Error('\'' + symbol + '\'' + ' has an object for a final function. But it is __not a function__ but a ' + typeOfFinalFunction + '!');
}
// execute symbols function
finalFunction();
} else {
// symbol has no final function
}
}
} catch (err) {
_didIteratorError3 = true;
_iteratorError3 = err;
} finally {
try {
if (!_iteratorNormalCompletion3 && _iterator3.return) {
_iterator3.return();
}
} finally {
if (_didIteratorError3) {
throw _iteratorError3;
}
}
}
};
/*
how to use match():
-----------------------
It is mainly a helper function for context sensitive productions.
If you use the classic syntax, it will by default be automatically transformed to proper
JS-Syntax.
Howerver, you can use the match helper function in your on productions:
index is the index of a production using `match`
eg. in a classic L-System
LSYS = ABCDE
B<C>DE -> 'Z'
the index of the `B<C>D -> 'Z'` production would be the index of C (which is 2) when the
production would perform match(). so (if not using the ClassicLSystem class) you'd construction your context-sensitive production from C to Z like so:
LSYS.setProduction('C', (index, axiom) => {
(LSYS.match({index, match: 'B', direction: 'left'}) &&
LSYS.match({index, match: 'DE', direction: 'right'}) ? 'Z' : 'C')
})
You can just write match({index, ...} instead of match({index: index, ..}) because of new ES6 Object initialization, see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#New_notations_in_ECMAScript_6
*/
this.match = function (_ref2) {
var axiom_ = _ref2.axiom_;
var match = _ref2.match;
var ignoredSymbols = _ref2.ignoredSymbols;
var branchSymbols = _ref2.branchSymbols;
var index = _ref2.index;
var direction = _ref2.direction;
var branchCount = 0;
var explicitBranchCount = 0;
axiom_ = axiom || this.axiom;
if (branchSymbols === undefined) branchSymbols = this.branchSymbols !== undefined ? this.branchSymbols : [];
if (ignoredSymbols === undefined) ignoredSymbols = this.ignoredSymbols !== undefined ? this.ignoredSymbols : [];
var returnMatchIndices = [];
var branchStart = void 0,
branchEnd = void 0,
axiomIndex = void 0,
loopIndexChange = void 0,
matchIndex = void 0,
matchIndexChange = void 0,
matchIndexOverflow = void 0;
// set some variables depending on the direction to match
if (direction === 'right') {
loopIndexChange = matchIndexChange = +1;
axiomIndex = index + 1;
matchIndex = 0;
matchIndexOverflow = match.length;
if (branchSymbols.length > 0) {
;
var _branchSymbols = branchSymbols;
var _branchSymbols2 = slicedToArray(_branchSymbols, 2);
branchStart = _branchSymbols2[0];
branchEnd = _branchSymbols2[1];
}
} else if (direction === 'left') {
loopIndexChange = matchIndexChange = -1;
axiomIndex = index - 1;
matchIndex = match.length - 1;
matchIndexOverflow = -1;
if (branchSymbols.length > 0) {
;
var _branchSymbols3 = branchSymbols;
var _branchSymbols4 = slicedToArray(_branchSymbols3, 2);
branchEnd = _branchSymbols4[0];
branchStart = _branchSymbols4[1];
}
} else {
throw Error(direction, 'is not a valid direction for matching.');
}
for (; axiomIndex < axiom_.length && axiomIndex >= 0; axiomIndex += loopIndexChange) {
// FIXME: what about objects with .symbol
var axiomSymbol = axiom_[axiomIndex];
// For objects match for objects `symbol`
if ((typeof axiomSymbol === 'undefined' ? 'undefined' : _typeof(axiomSymbol)) === 'object') axiomSymbol = axiomSymbol.symbol;
var matchSymbol = match[matchIndex];
// compare current symbol of axiom with current symbol of match
if (axiomSymbol === matchSymbol) {
if (branchCount === 0 || explicitBranchCount > 0) {
// if its a match and previously NOT inside branch (branchCount===0) or in explicitly wanted branch (explicitBranchCount > 0)
// if a bracket was explicitly stated in match axiom
if (axiomSymbol === branchStart) {
explicitBranchCount++;
branchCount++;
matchIndex += matchIndexChange;
} else if (axiomSymbol === branchEnd) {
explicitBranchCount = Math.max(0, explicitBranchCount - 1);
branchCount = Math.max(0, branchCount - 1);
// only increase match if we are out of explicit branch
if (explicitBranchCount === 0) {
matchIndex += matchIndexChange;
}
} else {
returnMatchIndices.push(axiomIndex);
matchIndex += matchIndexChange;
}
}
// overflowing matchIndices (matchIndex + 1 for right match, matchIndexEnd for left match )?
// -> no more matches to do. return with true, as everything matched until here
// *yay*
if (matchIndex === matchIndexOverflow) {
return { result: true, matchIndices: returnMatchIndices };
}
} else if (axiomSymbol === branchStart) {
branchCount++;
if (explicitBranchCount > 0) explicitBranchCount++;
} else if (axiomSymbol === branchEnd) {
branchCount = Math.max(0, branchCount - 1);
if (explicitBranchCount > 0) explicitBranchCount = Math.max(0, explicitBranchCount - 1);
} else if ((branchCount === 0 || explicitBranchCount > 0 && matchSymbol !== branchEnd) && ignoredSymbols.includes(axiomSymbol) === false) {
// not in branchSymbols/branch? or if in explicit branch, and not at the very end of
// condition (at the ]), and symbol not in ignoredSymbols ? then false
return { result: false, matchIndices: returnMatchIndices };
}
}
return { result: false, matchIndices: returnMatchIndices };
};
// finally init stuff
this.parameters = {
allowClassicSyntax: true
};
this.setAxiom(axiom);
this.productions = new Map();
if (productions) this.setProductions(productions);
this.branchSymbols = branchSymbols;
this.ignoredSymbols = ignoredSymbols;
this.classicParametricSyntax = classicParametricSyntax;
if (finals) this.setFinals(finals);
this.iterationCount = 0;
return this;
}
// Set classic syntax helpers to library scope to be used outside of library context
// for users eg.
LSystem.transformClassicStochasticProductions = transformClassicStochasticProductions;
LSystem.transformClassicCSProduction = transformClassicCSProduction;
LSystem.transformClassicParametricAxiom = transformClassicParametricAxiom;
LSystem.testClassicParametricSyntax = testClassicParametricSyntax;
module.exports = LSystem;
/***/ }
/******/ ]);