aframe-lsystem-component
Version:
L-System/LSystem component for A-Frame to draw 3D turtle graphics. Using Lindenmayer as backend.
1,410 lines (1,169 loc) • 79.4 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] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = 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;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, {
/******/ configurable: false,
/******/ enumerable: true,
/******/ get: getter
/******/ });
/******/ }
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_lindenmayer__ = __webpack_require__(1);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_worker_loader_inline_fallback_false_worker_js__ = __webpack_require__(2);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_worker_loader_inline_fallback_false_worker_js___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_1_worker_loader_inline_fallback_false_worker_js__);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__primitives_a_lsystem_js__ = __webpack_require__(4);
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__primitives_a_lsystem_js___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_2__primitives_a_lsystem_js__);
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"); } }; }();
if (typeof AFRAME === 'undefined') {
throw new Error('Component attempted to register before AFRAME was available.');
}
// As we use webpack for compiling the source, it's used to bundle the
// web worker into a blob via: https://github.com/webpack/worker-loader
/**
* Lindenmayer-System component for A-Frame.
*/
function parseFromTo(value) {
let flatResult = value.split(/(\w)\s*:\s*/).filter(part => part.length !== 0);
let result = [];
for (var i = 0; i < flatResult.length; i += 2) {
result.push([flatResult[i], flatResult[i + 1]]);
}
return result;
}
AFRAME.registerComponent('lsystem', {
schema: {
axiom: {
type: 'string',
default: 'F'
},
productions: {
default: 'F:FF',
// return an array of production tuples ([[from, to], ['F', 'F+F']])
parse: value => parseFromTo(value).map((_ref) => {
var _ref2 = _slicedToArray(_ref, 2);
let from = _ref2[0],
to = _ref2[1];
return [from, to.replace(/\s/g, '')];
})
},
// A: blue line, red line, yellow line B: red line
segmentMixins: {
type: 'string',
parse: function parse(value) {
let mixinsForSymbol = new Map();
let result = parseFromTo(value);
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = result[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
let _ref3 = _step.value;
var _ref4 = _slicedToArray(_ref3, 2);
let from = _ref4[0];
let to = _ref4[1];
to = to.replace(/[\[\]]/g, '').split(',');
mixinsForSymbol.set(from, to);
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
return mixinsForSymbol;
}
},
iterations: {
type: 'int',
default: 1
},
angle: {
default: 90.0
},
translateAxis: {
type: 'string',
default: 'y',
parse: function parse(value) {
value = value.toLowerCase();
if (value === 'x') {
return new THREE.Vector3(1, 0, 0);
} else if (value === 'y') {
return new THREE.Vector3(0, 1, 0);
} else if (value === 'z') {
return new THREE.Vector3(0, 0, 1);
} else {
throw new Error('translateAxis has to be a string: "x", "y" or "z"');
}
}
},
scaleFactor: {
default: 1.0
},
dynamicSegmentLength: {
default: true
},
mergeGeometries: {
type: 'boolean',
default: true
},
functionsInProductions: {
type: 'boolean',
default: true
}
},
/**
* Called once when component is attached. Generally for initial setup.
*/
init: function init() {
this.sceneEl = document.querySelector('a-scene');
let self = this;
this.initWorker();
this.X = new THREE.Vector3(1, 0, 0);
this.Y = new THREE.Vector3(0, 1, 0);
this.Z = new THREE.Vector3(0, 0, 1);
this.xPosRotation = new THREE.Quaternion();
this.xNegRotation = new THREE.Quaternion();
this.yPosRotation = new THREE.Quaternion();
this.yNegRotation = new THREE.Quaternion();
this.zPosRotation = new THREE.Quaternion();
this.zNegRotation = new THREE.Quaternion();
this.yReverseRotation = new THREE.Quaternion();
this.xPosRotation = new THREE.Quaternion();
this.xNegRotation = new THREE.Quaternion();
this.yPosRotation = new THREE.Quaternion();
this.yNegRotation = new THREE.Quaternion();
this.zPosRotation = new THREE.Quaternion();
this.zNegRotation = new THREE.Quaternion();
this.yReverseRotation = new THREE.Quaternion();
this.segmentLengthFactor = 1.0;
this.transformationSegment = new THREE.Object3D();
this.transformationSegmentTemplate = this.transformationSegment.clone();
let scaleFactor = self.data.scaleFactor;
this.colorIndex = 0;
this.lineWidth = 0.0005;
this.lineLength = 0.125;
this.LSystem = new __WEBPACK_IMPORTED_MODULE_0_lindenmayer__["a" /* default */]({
axiom: 'F',
productions: { 'F': 'F' },
finals: {
/* As a default F is already defined as final, new ones get added automatically
by parsing the segment mixins. If no segment mixin for any symbol is defined
it wont get a final function and therefore not render.
*/
'+': () => {
self.transformationSegment.quaternion.multiply(self.yPosRotation);
},
'-': () => {
self.transformationSegment.quaternion.multiply(self.yNegRotation);
},
'&': () => {
self.transformationSegment.quaternion.multiply(self.zNegRotation);
},
'^': () => {
self.transformationSegment.quaternion.multiply(self.zPosRotation);
},
'\\': () => {
self.transformationSegment.quaternion.multiply(self.xNegRotation);
},
'<': () => {
self.transformationSegment.quaternion.multiply(self.xNegRotation);
},
'/': () => {
self.transformationSegment.quaternion.multiply(self.xPosRotation);
},
'>': () => {
self.transformationSegment.quaternion.multiply(self.xPosRotation);
},
'|': () => {
self.transformationSegment.quaternion.multiply(self.yReverseRotation);
},
'!': () => {
self.segmentLengthFactor *= scaleFactor;
self.transformationSegment.scale.set(self.transformationSegment.scale.x *= scaleFactor, self.transformationSegment.scale.y *= scaleFactor, self.transformationSegment.scale.z *= scaleFactor);
self.colorIndex++;
},
'\'': () => {
self.segmentLengthFactor *= 1.0 / scaleFactor;
self.transformationSegment.scale.set(self.transformationSegment.scale.x *= 1.0 / scaleFactor, self.transformationSegment.scale.y *= 1.0 / scaleFactor, self.transformationSegment.scale.z *= 1.0 / scaleFactor);
self.colorIndex = Math.max(0, self.colorIndex - 1);
},
'[': () => {
self.stack.push(self.transformationSegment.clone());
},
']': () => {
self.transformationSegment = self.stack.pop();
}
}
});
},
/**
* Called when component is attached and when component data changes.
* Generally modifies the entity based on the data.
*/
update: function update(oldData) {
// var diffData = diff(data, oldData || {});
// console.log(diffData);
// TODO: Check if only angle changed or axiom or productions
//
let self = this;
if (this.data.mergeGeometries === false && this.segmentElementGroupsMap !== undefined) {
var _iteratorNormalCompletion2 = true;
var _didIteratorError2 = false;
var _iteratorError2 = undefined;
try {
for (var _iterator2 = this.segmentElementGroupsMap.values()[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
let segmentElGroup = _step2.value;
segmentElGroup.removeObject3D('mesh');
segmentElGroup.innerHTML = '';
}
} catch (err) {
_didIteratorError2 = true;
_iteratorError2 = err;
} finally {
try {
if (!_iteratorNormalCompletion2 && _iterator2.return) {
_iterator2.return();
}
} finally {
if (_didIteratorError2) {
throw _iteratorError2;
}
}
}
}
if (Object.keys(oldData).length === 0) {
this.updateLSystem();
this.updateSegmentMixins();
this.updateTurtleGraphics();
} else {
let visualChange = false;
if (oldData.axiom && oldData.axiom !== this.data.axiom || oldData.iterations && oldData.iterations !== this.data.iterations || oldData.productions && JSON.stringify(oldData.productions) !== JSON.stringify(this.data.productions)) {
this.updateLSystem();
visualChange = true;
}
if (oldData.segmentMixins !== undefined && JSON.stringify(Array.from(oldData.segmentMixins.entries())) !== JSON.stringify(Array.from(this.data.segmentMixins.entries()))) {
this.updateSegmentMixins();
visualChange = true;
}
if (visualChange || oldData.angle && oldData.angle !== this.data.angle) {
this.updateTurtleGraphics();
} else {
// console.log('nothing changed in update?');
// this.updateLSystem();
// this.updateSegmentMixins();
}
}
},
// if this.dynamicSegmentLength===true use this function to set the length
// depending on segments geometries bbox
calculateSegmentLength: function calculateSegmentLength(mixin, geometry) {
if (this.segmentLengthMap.has(mixin)) return this.segmentLengthMap.get(mixin);
geometry.computeBoundingBox();
let segmentLength;
if (this.data.translateAxis.equals(this.X)) {
segmentLength = Math.abs(geometry.boundingBox.min.x - geometry.boundingBox.max.x);
} else if (this.data.translateAxis.equals(this.Y)) {
segmentLength = Math.abs(geometry.boundingBox.min.y - geometry.boundingBox.max.y);
} else if (this.data.translateAxis.equals(this.Z)) {
segmentLength = Math.abs(geometry.boundingBox.min.z - geometry.boundingBox.max.z);
}
this.segmentLengthMap.set(mixin, segmentLength);
return segmentLength;
},
initWorker: function initWorker() {
this.worker = new __WEBPACK_IMPORTED_MODULE_1_worker_loader_inline_fallback_false_worker_js___default.a();
},
pushSegment: function pushSegment(symbol) {
let self = this;
let currentQuaternion = self.transformationSegment.quaternion;
let currentPosition = self.transformationSegment.position;
let currentScale = self.transformationSegment.scale;
// Cap colorIndex to maximum mixins defined for the symbol.
let cappedColorIndex = Math.min(this.colorIndex, this.data.segmentMixins.get(symbol).length - 1);
let mixin = this.mixinMap.get(symbol + cappedColorIndex);
if (this.data.mergeGeometries === false) {
let newSegment = document.createElement('a-entity');
newSegment.setAttribute('mixin', mixin);
newSegment.addEventListener('loaded', e => {
// Offset child element of object3D, to rotate around end point
// IMPORTANT: It may change that A-Frame puts objects into a group
let segmentLength = self.segmentLengthMap.get(mixin);
newSegment.object3D.children[0].translateOnAxis(self.data.translateAxis, segmentLength * self.segmentLengthFactor / 2);
newSegment.object3D.quaternion.copy(currentQuaternion);
newSegment.object3D.position.copy(currentPosition);
newSegment.object3D.scale.copy(currentScale);
}, { once: true });
this.segmentElementGroupsMap.get(symbol + cappedColorIndex).appendChild(newSegment);
} else {
let segmentObject3D = this.segmentObjects3DMap.get(symbol + cappedColorIndex);
let newSegmentObject3D = segmentObject3D.clone();
newSegmentObject3D.matrixAutoUpdate = false;
newSegmentObject3D.quaternion.copy(currentQuaternion);
newSegmentObject3D.position.copy(currentPosition);
newSegmentObject3D.scale.copy(currentScale);
newSegmentObject3D.updateMatrix();
this.mergeGroups.get(symbol + cappedColorIndex).geometry.merge(newSegmentObject3D.geometry, newSegmentObject3D.matrix);
}
let segmentLength = this.segmentLengthMap.get(mixin);
this.transformationSegment.translateOnAxis(this.data.translateAxis, segmentLength * this.segmentLengthFactor);
},
updateLSystem: function updateLSystem() {
let self = this;
// post params to worker
let params = {
axiom: this.data.axiom,
productions: this.data.productions,
iterations: this.data.iterations
};
if (Date.now() - this.worker.startTime > 1000) {
// if we got user input, but worker is running for over a second
// terminate old worker and start new one.
this.worker.terminate();
this.initWorker();
}
this.worker.startTime = Date.now();
this.workerPromise = new Promise((resolve, reject) => {
this.worker.onmessage = e => {
self.LSystem.setAxiom(e.data.result);
resolve();
};
});
this.worker.postMessage(params);
return this.workerPromise;
},
updateSegmentMixins: function updateSegmentMixins() {
let self = this;
this.el.innerHTML = '';
// Map for remembering the elements holding differnt segment types
this.segmentElementGroupsMap = new Map();
this.mixinMap = new Map();
// Construct a map with keys = `symbol + colorIndex` from data.segmentMixins
var _iteratorNormalCompletion3 = true;
var _didIteratorError3 = false;
var _iteratorError3 = undefined;
try {
for (var _iterator3 = this.data.segmentMixins[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
let _ref5 = _step3.value;
var _ref6 = _slicedToArray(_ref5, 2);
let symbol = _ref6[0];
let mixinList = _ref6[1];
for (let i = 0; i < mixinList.length; i++) {
this.mixinMap.set(symbol + i, mixinList[i]);
}
}
// Map for buffering geometries for use in pushSegments()
// when merging geometries ourselves and not by appending a `mixin` attributes,
// as done with `mergeGeometry = false`.
} catch (err) {
_didIteratorError3 = true;
_iteratorError3 = err;
} finally {
try {
if (!_iteratorNormalCompletion3 && _iterator3.return) {
_iterator3.return();
}
} finally {
if (_didIteratorError3) {
throw _iteratorError3;
}
}
}
this.segmentObjects3DMap = new Map();
this.segmentLengthMap = new Map();
this.mergeGroups = new Map();
this.mixinPromises = [];
// Collect mixin info by pre-appending segment elements with their mixin
// Then use the generated geometry etc.
if (this.data.segmentMixins && this.data.segmentMixins.length !== 0) {
// Go through every symbols segmentMixins as defined by user
var _iteratorNormalCompletion4 = true;
var _didIteratorError4 = false;
var _iteratorError4 = undefined;
try {
for (var _iterator4 = this.data.segmentMixins[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) {
let el = _step4.value;
var _el = _slicedToArray(el, 2);
let symbol = _el[0],
mixinList = _el[1];
// Set final functions for each symbol that has a mixin defined
this.LSystem.setFinal(symbol, () => {
self.pushSegment.bind(self, symbol)();
});
// And iterate the MixinList to buffer the segments or calculate segment lengths…
for (let i = 0; i < mixinList.length; i++) {
let mixinColorIndex = i;
let mixin = mixinList[mixinColorIndex];
self.mixinPromises.push(new Promise((resolve, reject) => {
// Save mixinColorIndex for async promise below.
let segmentElGroup = document.createElement('a-entity');
segmentElGroup.setAttribute('id', mixin + '-group-' + mixinColorIndex + Math.floor(Math.random() * 10000));
// TODO: Put it all under this.mergeData
segmentElGroup.setAttribute('geometry', 'buffer', false);
segmentElGroup.setAttribute('mixin', mixin);
segmentElGroup.addEventListener('loaded', function (e) {
let segmentObject = segmentElGroup.getObject3D('mesh').clone();
// Make sure the geometry is actually unique
// AFrame sets the same geometry for multiple entities. As we modify
// the geometry per entity we need to have unique geometry instances.
// TODO: hm, maybe try to use instanced geometry and offset on object?
segmentElGroup.getObject3D('mesh').geometry.dispose();
segmentObject.geometry = segmentObject.geometry.clone();
let segmentLength = self.calculateSegmentLength(mixin, segmentObject.geometry);
// Do some additional stuff like buffering 3D objects / geometry
// if we want to merge geometries.
if (self.data.mergeGeometries === true) {
// Offset geometry by half segmentLength to get the rotation point right.
let translation = self.data.translateAxis.clone().multiplyScalar(segmentLength * self.segmentLengthFactor / 2);
// IMPORTANT!!!
// TODO: Try to use pivot object instead of translating geometry
// this may help in reusing geometry and not needing to clone it (see above)?
// see: https://github.com/mrdoob/three.js/issues/1364
//and
// see: http://stackoverflow.com/questions/28848863/threejs-how-to-rotate-around-objects-own-center-instead-of-world-center
segmentObject.geometry.translate(translation.x, translation.y, translation.z);
self.segmentObjects3DMap.set(symbol + mixinColorIndex, segmentObject);
}
segmentElGroup.removeObject3D('mesh');
resolve();
}, { once: true });
if (this.segmentElementGroupsMap.has(symbol + mixinColorIndex)) {
let previousElGroup = this.segmentElementGroupsMap.get(symbol + mixinColorIndex);
this.segmentElementGroupsMap.delete(symbol + mixinColorIndex);
this.el.removeChild(previousElGroup);
}
this.segmentElementGroupsMap.set(symbol + mixinColorIndex, segmentElGroup);
this.el.appendChild(segmentElGroup);
}));
}
}
} catch (err) {
_didIteratorError4 = true;
_iteratorError4 = err;
} finally {
try {
if (!_iteratorNormalCompletion4 && _iterator4.return) {
_iterator4.return();
}
} finally {
if (_didIteratorError4) {
throw _iteratorError4;
}
}
}
}
},
updateTurtleGraphics: async function updateTurtleGraphics() {
await Promise.all([...this.mixinPromises, this.workerPromise]);
// The main segment used for saving transformations (rotation, translation, scale(?))
this.transformationSegment.copy(this.transformationSegmentTemplate);
// set merge groups
if (this.data.mergeGeometries === true) {
var _iteratorNormalCompletion5 = true;
var _didIteratorError5 = false;
var _iteratorError5 = undefined;
try {
for (var _iterator5 = this.segmentObjects3DMap[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) {
let _ref7 = _step5.value;
var _ref8 = _slicedToArray(_ref7, 2);
let id = _ref8[0];
let segmentObject = _ref8[1];
this.mergeGroups.set(id, new THREE.Mesh(new THREE.Geometry(), segmentObject.material));
}
} catch (err) {
_didIteratorError5 = true;
_iteratorError5 = err;
} finally {
try {
if (!_iteratorNormalCompletion5 && _iterator5.return) {
_iterator5.return();
}
} finally {
if (_didIteratorError5) {
throw _iteratorError5;
}
}
}
} // We push copies of this.transformationSegment on branch symbols inside this array.
this.stack = [];
let angle = this.data.angle;
// Set quaternions based on angle slider
this.xPosRotation.setFromAxisAngle(this.X, Math.PI / 180 * angle);
this.xNegRotation.setFromAxisAngle(this.X, Math.PI / 180 * -angle);
this.yPosRotation.setFromAxisAngle(this.Y, Math.PI / 180 * angle);
this.yNegRotation.setFromAxisAngle(this.Y, Math.PI / 180 * -angle);
this.yReverseRotation.setFromAxisAngle(this.Y, Math.PI / 180 * 180);
this.zPosRotation.setFromAxisAngle(this.Z, Math.PI / 180 * angle);
this.zNegRotation.setFromAxisAngle(this.Z, Math.PI / 180 * -angle);
//
// this.geometry = new THREE.CylinderGeometry(this.lineWidth, this.lineWidth, self.data.lineLength, 3);
// this.geometry.rotateZ((Math.PI / 180) * 90);
// this.geometry.translate( -(this.data.segmentLength/2), 0, 0 );
// for (let face of this.geometry.faces) {
// face.color.setHex(this.colors[colorIndex]);
// }
// this.geometry.colorsNeedUpdate = true;
this.LSystem.final();
// finally set the merged meshes to be visible.
if (this.data.mergeGeometries === true) {
var _iteratorNormalCompletion6 = true;
var _didIteratorError6 = false;
var _iteratorError6 = undefined;
try {
for (var _iterator6 = this.segmentElementGroupsMap[Symbol.iterator](), _step6; !(_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done); _iteratorNormalCompletion6 = true) {
let tuple = _step6.value;
var _tuple = _slicedToArray(tuple, 2);
let symbolWithColorIndex = _tuple[0],
elGroup = _tuple[1];
let mergeGroup = this.mergeGroups.get(symbolWithColorIndex);
// Remove unused element groups inside our element
if (mergeGroup.geometry.vertices.length === 0) {
this.el.removeChild(elGroup);
} else {
elGroup.setObject3D('mesh', this.mergeGroups.get(symbolWithColorIndex));
elGroup.setAttribute('mixin', this.mixinMap.get(symbolWithColorIndex));
}
}
} catch (err) {
_didIteratorError6 = true;
_iteratorError6 = err;
} finally {
try {
if (!_iteratorNormalCompletion6 && _iterator6.return) {
_iterator6.return();
}
} finally {
if (_didIteratorError6) {
throw _iteratorError6;
}
}
}
}
},
/**
* Called when a component is removed (e.g., via removeAttribute).
* Generally undoes all modifications to the entity.
*/
remove: function remove() {},
/**
* Called on each scene tick.
*/
tick: function tick(t) {
// console.log(this.parentEl === undefined);
// console.log('\nTICK\n', t);
},
/**
* Called when entity pauses.
* Use to stop or remove any dynamic or background behavior such as events.
*/
pause: function pause() {},
/**
* Called when entity resumes.
* Use to continue or add any dynamic or background behavior such as events.
*/
play: function play() {}
});
/***/ }),
/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"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.)
// These transformers get a classic ABOP snytax as input and return a standardized
// production object in the form of ['F',
// {
// successor:String/Iterable
// [alternatively]stochasticSuccessors: Iterable of standardized objects with mandatory weight fields,
// leftCtx: iterable/string,
// rightCtx: Iterable/String,
// condition: Function }]
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: implement it!
// 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)
}
function transformClassicCSProduction(p) {
// before continuing, check if classic syntax actually there
// example: p = ['A<B>C', 'Z']
// left should be ['A', 'B']
var left = p[0].match(/(.+)<(.)/);
// right should be ['B', 'C']
var right = p[0].match(/(.)>(.+)/);
// Not a CS-Production (no '<' or '>'),
//return original production.
if (left === null && right === null) {
return p;
}
var predecessor = void 0;
// create new production object _or_ use the one set by the user
var productionObject = p[1].successor || p[1].successors ? p[1] : { successor: p[1] };
if (left !== null) {
predecessor = left[2];
productionObject.leftCtx = left[1];
}
if (right !== null) {
predecessor = right[1];
productionObject.rightCtx = right[2];
}
return [predecessor, productionObject];
}
function stringToObjects(string) {
if (typeof string !== 'string' && string instanceof String === false) return string;
var transformed = [];
for (var _iterator = string, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) {
var _ref;
if (_isArray) {
if (_i >= _iterator.length) break;
_ref = _iterator[_i++];
} else {
_i = _iterator.next();
if (_i.done) break;
_ref = _i.value;
}
var symbol = _ref;
transformed.push({ symbol });
}return transformed;
}
// TODO: continue here
// transform p to {successor: p}
// if applicable also transform strings into array of {symbol: String} objects
// TODO: make more modular! dont have forceObjects in here
function normalizeProductionRightSide(p, forceObjects) {
if (p.hasOwnProperty('successors')) {
for (var i = 0; i < p.successors.length; i++) {
p.successors[i] = normalizeProductionRightSide(p.successors[i], forceObjects);
}
} else if (p.hasOwnProperty('successor') === false) {
p = { successor: p };
}
if (forceObjects && p.hasOwnProperty('successor')) {
p.successor = stringToObjects(p.successor);
}
return p;
}
function normalizeProduction(p, forceObjects) {
p[1] = normalizeProductionRightSide(p[1], forceObjects);
return p;
}
function LSystem(_ref) {
var _ref$axiom = _ref.axiom,
axiom = _ref$axiom === undefined ? '' : _ref$axiom,
productions = _ref.productions,
finals = _ref.finals,
_ref$branchSymbols = _ref.branchSymbols,
branchSymbols = _ref$branchSymbols === undefined ? '' : _ref$branchSymbols,
_ref$ignoredSymbols = _ref.ignoredSymbols,
ignoredSymbols = _ref$ignoredSymbols === undefined ? '' : _ref$ignoredSymbols,
_ref$allowClassicSynt = _ref.allowClassicSyntax,
allowClassicSyntax = _ref$allowClassicSynt === undefined ? true : _ref$allowClassicSynt,
_ref$classicParametri = _ref.classicParametricSyntax,
classicParametricSyntax = _ref$classicParametri === undefined ? false : _ref$classicParametri,
_ref$forceObjects = _ref.forceObjects,
forceObjects = _ref$forceObjects === undefined ? false : _ref$forceObjects,
_ref$debug = _ref.debug,
debug = _ref$debug === undefined ? false : _ref$debug;
// TODO: forceObject to be more intelligent based on other productions??
this.setAxiom = function (axiom) {
this.axiom = this.forceObjects ? stringToObjects(axiom) : axiom;
};
this.getRaw = function () {
return this.axiom;
};
// if using objects in axioms, as used in parametric L-Systems
this.getString = function () {
var onlySymbols = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
if (typeof this.axiom === 'string') return this.axiom;
if (onlySymbols === true) {
return this.axiom.reduce((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.getStringResult = this.getString;
this.setProduction = function (from, to) {
var allowAppendingMultiSuccessors = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
var newProduction = [from, to];
if (newProduction === undefined) throw new Error('no production specified.');
if (to.successor && to.successors) {
throw new Error('You can not have both a "successor" and a "successors" field in your production!');
}
// Apply production transformers and normalizations
if (this.allowClassicSyntax === true) {
newProduction = transformClassicCSProduction(newProduction, this.ignoredSymbols);
}
newProduction = normalizeProduction(newProduction, this.forceObjects);
// check wether production is stochastic
newProduction[1].isStochastic = newProduction[1].successors !== undefined && newProduction[1].successors.every(successor => successor.weight !== undefined);
if (newProduction[1].isStochastic) {
// calculate weight sum
newProduction[1].weightSum = 0;
for (var _iterator = newProduction[1].successors, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) {
var _ref2;
if (_isArray) {
if (_i >= _iterator.length) break;
_ref2 = _iterator[_i++];
} else {
_i = _iterator.next();
if (_i.done) break;
_ref2 = _i.value;
}
var s = _ref2;
newProduction[1].weightSum += s.weight;
}
}
var symbol = newProduction[0];
if (allowAppendingMultiSuccessors === true && this.productions.has(symbol)) {
var existingProduction = this.productions.get(symbol);
var singleSuccessor = existingProduction.successor;
var multiSuccessors = existingProduction.successors;
if (singleSuccessor && !multiSuccessors) {
// replace existing prod with new obj and add previous successor as first elem
// to new successors field.
existingProduction = { successors: [existingProduction] };
}
existingProduction.successors.push(newProduction[1]);
this.productions.set(symbol, existingProduction);
} else {
this.productions.set(symbol, newProduction[1]);
}
};
// set multiple productions from name:value Object
// TODO: ALLOW TUPLE/ARRAY
this.setProductions = function (newProductions) {
if (newProductions === undefined) throw new Error('no production specified.');
this.clearProductions();
for (var _iterator2 = Object.entries(newProductions), _isArray2 = Array.isArray(_iterator2), _i2 = 0, _iterator2 = _isArray2 ? _iterator2 : _iterator2[Symbol.iterator]();;) {
var _ref4;
if (_isArray2) {
if (_i2 >= _iterator2.length) break;
_ref4 = _iterator2[_i2++];
} else {
_i2 = _iterator2.next();
if (_i2.done) break;
_ref4 = _i2.value;
}
var _ref3 = _ref4;
var from = _ref3[0];
var to = _ref3[1];
this.setProduction(from, to, 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]);
}
}
};
//var hasWeight = el => el.weight !== undefined;
this.getProductionResult = function (p, index, part, params) {
var recursive = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
var contextSensitive = p.leftCtx !== undefined || p.rightCtx !== undefined;
var conditional = p.condition !== undefined;
var stochastic = false;
var result = false;
var precheck = true;
// Check if condition is true, only then continue to check left and right contexts
if (conditional && p.condition({ index, currentAxiom: this.axiom, part, params }) === false) {
precheck = false;
} else if (contextSensitive) {
if (p.leftCtx !== undefined && p.rightCtx !== undefined) {
precheck = this.match({ direction: 'left', match: p.leftCtx, index: index, branchSymbols: '[]' }).result && this.match({ direction: 'right', match: p.rightCtx, index: index, branchSymbols: '[]', ignoredSymbols: ignoredSymbols }).result;
} else if (p.leftCtx !== undefined) {
precheck = this.match({ direction: 'left', match: p.leftCtx, index: index, branchSymbols: '[]' }).result;
} else if (p.rightCtx !== undefined) {
precheck = this.match({ direction: 'right', match: p.rightCtx, index: index, branchSymbols: '[]' }).result;
}
}
// If conditions and context don't allow product, keep result = false
if (precheck === false) {
result = false;
}
// If p has multiple successors
else if (p.successors) {
// This could be stochastic successors or multiple functions
// Tread every element in the list as an individual production object
// For stochastic productions (if all prods in the list have a 'weight' property)
// Get a random number then pick a production from the list according to their weight
var currentWeight, threshWeight;
if (p.isStochastic) {
threshWeight = Math.random() * p.weightSum;
currentWeight = 0;
}
/*
go through the list and use
the first valid production in that list. (that returns true)
This assumes, it's a list of functions.
No recursion here: no successors inside successors.
*/
for (var _iterator3 = p.successors, _isArray3 = Array.isArray(_iterator3), _i3 = 0, _iterator3 = _isArray3 ? _iterator3 : _iterator3[Symbol.iterator]();;) {
var _ref5;
if (_isArray3) {
if (_i3 >= _iterator3.length) break;
_ref5 = _iterator3[_i3++];
} else {
_i3 = _iterator3.next();
if (_i3.done) break;
_ref5 = _i3.value;
}
var _p = _ref5;
if (p.isStochastic) {
currentWeight += _p.weight;
if (currentWeight < threshWeight) continue;
}
// If currentWeight >= thresWeight, a production is choosen stochastically
// and evaluated recursively because it , kax also have rightCtx, leftCtx and condition to further inhibit production. This is not standard L-System behaviour though!
// last true is for recursiv call
// TODO: refactor getProductionResult to use an object
var _result = this.getProductionResult(_p, index, part, params, true);
// console.log(part, p.successors);
// console.log(result);
// console.log("\n");
if (_result !== undefined && _result !== false) {
result = _result;
break;
}
}
}
// if successor is a function, execute function and append return value
else if (typeof p.successor === 'function') {
result = p.successor({ index, currentAxiom: this.axiom, part, params });
} else {
result = p.successor;
}
if (!result) {
// Allow undefined or false results for recursive calls of this func
return recursive ? result : part;
}
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
for (var _iterator4 = this.axiom, _isArray4 = Array.isArray(_iterator4), _i4 = 0, _iterator4 = _isArray4 ? _iterator4 : _iterator4[Symbol.iterator]();;) {
var _ref6;
if (_isArray4) {
if (_i4 >= _iterator4.length) break;
_ref6 = _iterator4[_i4++];
} else {
_i4 = _iterator4.next();
if (_i4.done) break;
_ref6 = _i4.value;
}
var part = _ref6;
// Stuff for classic parametric L-Systems: get actual symbol and possible parameters
// params will be given the production function, if applicable.
var symbol = part.symbol || part;
var params = part.params || [];
var result = part;
if (this.productions.has(symbol)) {
var p = this.productions.get(symbol);
result = this.getProductionResult(p, index, part, params);
}
// Got result. Now add result to new axiom.
if (typeof newAxiom === 'string') {
newAxiom += result;
} else if (result instanceof Array) {
// If result is an array, merge result into new axiom instead of pushing.
Array.prototype.push.apply(newAxiom, result);
} else {
newAxiom.push(result);
}
index++;
}
// finally set new axiom and also return it for convenience.
this.axiom = newAxiom;
return newAxiom;
};
this.iterate = function () {
var n = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
this.iterations = n;
var lastIteration = void 0;
for (var iteration = 0; iteration < n; iteration++) {
lastIteration = this.applyProductions();
}
return lastIteration;
};
this.final = function (externalArg) {
var index = 0;
for (var _iterator5 = this.axiom, _isArray5 = Array.isArray(_iterator5), _i5 = 0, _iterator5 = _isArray5 ? _iterator5 : _iterator5[Symbol.iterator]();;) {
var _ref7;
if (_isArray5) {
if (_i5 >= _iterator5.length) break;
_ref7 = _iterator5[_i5++];
} else {
_i5 = _iterator5.next();
if (_i5.done) break;
_ref7 = _i5.value;
}
var part = _ref7;
// if we have objects for each symbol, (when using parametric L-Systems)
// get actual identifiable symbol character
var symbol = part;
if (typeof part === 'object' && part.symbol) symbol = part.symbol;
if (this.finals.has(symbol)) {
var finalFunction = this.finals.get(symbol);
var typeOfFinalFunction = 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
// supply in first argument an details object with current index and part
// and in the first argument inject the external argument (like a render target)
finalFunction({ index, part }, externalArg);
} else {
// symbol has no final function
}
index++;
}
};
/*
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 (_ref8) {
var axiom_ = _ref8.axiom_,
match = _ref8.match,
ignoredSymbols = _ref8.ignoredSymbols,
branchSymbols = _ref8.branchSymbols,
index = _ref8.index,
direction = _ref8.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;
branchStart = _branchSymbols[0];
branchEnd = _branchSymbols[1];
}
} else if (direction === 'left') {
loopIndexChange = matchIndexChange = -1;
axiomIndex = index - 1;
matchIndex = match.length - 1;
matchIndexOverflow = -1;
if (branchSymbols.length > 0) {
var _branchSymbols2 = branchSymbols;
branchEnd = _branchSymbols2[0];
branchStart = _branchSymbols2[1];
}
} else {
throw Error(direction, 'is not a valid direction for matching.');
}
for (; axiomIndex < axiom_.length && axiomIndex >= 0; axiomIndex += loopIndexChange) {
var axiomSymbol = axiom_[axiomIndex].symbol || axiom_[axiomIndex];
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 };
};
this.ignoredSymbols = ignoredSymbols;
this.debug = debug;
this.branchSymbols = branchSymbols;
this.allowClassicSyntax = allowClassicSyntax;
this.classicParametricSyntax = classicParametricSyntax;
this.forceObjects = forceObjects;
this.setAxiom(axiom);
this.clearProductions();
if (productions) this.setProductions(productions);
if (finals) this.setFinals(finals);
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;
/* harmony default export */ __webpack_exports__["a"] = (LSystem);
/***/ }),
/* 2 */
/***/ (function(module, exports, __webpack_require__) {
module.exports = function() {
return __webpack_require__(3)("/******/ (function(modules) { // webpackBootstrap\n/******/ \t// The module cache\n/******/ \tvar installedModules = {};\n/******/\n/******/ \t// The require function\n/******/ \tfunction __webpack_require__(moduleId) {\n/******/\n/******/ \t\t// Check if module is in cache\n/******/ \t\tif(installedModules[moduleId]) {\n/******/ \t\t\treturn installedModules[mod