├── .babelrc ├── .gitignore ├── LICENSE ├── README.md ├── dist ├── .gitkeep ├── aframe-lsystem-component.js └── aframe-lsystem-component.min.js ├── examples ├── 2d_kochflake │ └── index.html ├── animated hilbert curve │ └── index.html ├── animated tree │ └── index.html ├── forrest │ └── index.html ├── hilbertcurve │ └── index.html ├── index.html ├── libs │ ├── aframe-animation-component.js │ ├── aframe-lsystem-component.js │ ├── aframe-randomizer-components.min.js │ └── aframe.js ├── multiple mixins │ └── index.html ├── multiple productions │ └── index.html ├── primitive │ └── index.html ├── tests │ └── index.html └── tree │ └── index.html ├── index.js ├── package-lock.json ├── package.json ├── primitives └── a-lsystem.js ├── webpack.config.js ├── webpack.config.minify.js └── worker.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "targets": { 5 | "browsers": ["last 2 Chrome versions", "Firefox ESR"], 6 | "uglify": false 7 | }, 8 | "exclude": ["transform-regenerator", "es6.set"], 9 | "modules": false, 10 | "loose": false 11 | }] 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .sw[ponm] 2 | examples/build.js 3 | examples/node_modules/ 4 | gh-pages 5 | node_modules/ 6 | npm-debug.log 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Tom Brewe 4 | Copyright (c) 2015 Kevin Ngo (Boilerplate code) 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## aframe-lsystem-component 2 | 3 | A L-System component for [A-Frame](https://aframe.io) which use the L-System library [lindenmayer](https://github.com/nylki/lindenmayer) as backend. 4 | It renders L-Systems via the *turtle graphic* technique to create procedurally generated geometry. 5 | 6 | [![](https://cloud.githubusercontent.com/assets/1710598/18224914/273eab36-71e6-11e6-82a5-826e0f603ea1.jpg) 7 | ](http://nylki.github.io/aframe-lsystem-component/) 8 | 9 | ### Properties 10 | 11 | | Property | Description | Default Value | 12 | | ---------------------- | ----------------------------------------------------------------------------------------------------- | ------- | 13 | | axiom | (string) Initiator/initial string/axiom. | `'F'` | 14 | | productions | (string) Productions `from`:`to`. Separate by acomma. eg: `productions: F:FF, X:F+X+F` | `'F:F'` | 15 | | iterations | (int) How many times the productions should be applied | `1` | 16 | | angle | (number) Degree change to apply for rotation symbols like, `+`, `-`, `>`, `<` etc. | `45.0` | 17 | | segmentMixins | (list) For any symbol you want to be rendered, you need to assign them [mixins](https://aframe.io/docs/0.3.0/core/mixins.html) here. Let's say you want F and X to be rendered, then you could write `segmentMixins: F:blue line X:big sphere`. You may define multiple mixins per symbol if you plan to use `!` and `'` in your L-System to increment/decrement the mixin index, which directly relates to your *segmentMixins*. Eg. `F: red line,blue line,green line` with an Axiom `F!F!F` will produce exactly three lines with those colors. Be sure though to actually define mixins you want to use in you assets. Take a look at some of the examples to get a better idea how this works, eg. the [multi-mixin example](https://github.com/nylki/aframe-lsystem-component/blob/master/examples/multiple%20mixins/index.html). | | 18 | | scaleFactor | (number) If you use `!` and `'` in your L-System (see also `segmentMixins` above), this factor controls the size decrease/increase of subsequent segments. | `1.0` | 19 | 20 | 21 | #### advanced properties 22 | Usually you don't need to touch the following, but in some situations, you might need or want to. 23 | 24 | | Property | Description | Default Value | 25 | | ---------------------- | ----------------------------------------------------------------------------------------------------- | ------- | 26 | | translateAxis | (string) `'x'`, `'y'` or `'z'`, defines the axis on which to translate along when adding a segment (or moving the *turtle* via lowercase symbols of segments). Changing this to `'x'` is often necessary if you 1:1 copy examples from a textbook. | `'y'` | 27 | | mergeGeometries | (boolean) Set false if you want an Object3D per segment. Degrades rendering performance when `false`! | `true` | 28 | 29 | ### Usage 30 | A very basic L-System entity could look like: 31 | 32 | ```.html 33 | 34 | ``` 35 | Please refer to the examples for some practical usage examples. 36 | 37 | If you want to learn more about L-Systems in general, I recommend the [overview article at wikipedia](https://en.wikipedia.org/wiki/L-system). 38 | And if you want to dive deep in, you can read the [Algorithmic Beauty of Plants](http://algorithmicbotany.org/papers/#abop), the classic by Aristid Lindenmayer and Przemyslaw Prusinkiewicz. 39 | 40 | In case you are already familiar with L-Systems or turtle graphics, 41 | here is a list of all supported symbols and their interpretation in this component: 42 | 43 | - `+` rotates Y around defined angles 44 | - `-` rotates Y around defined -angles 45 | - `&` rotates Z around defined angles 46 | - `^` rotates Z around defined -angles 47 | - `\` rotates X around defined -angles 48 | - `<` rotates X around defined -angles 49 | - `/` rotates X around defined angles 50 | - `>` rotates X around defined angles 51 | - `|` rotates Y around defined 180 degree 52 | - `!` increments segment index (next `segmentMixin` per symbol if defined). Also applies `scaleFactor` to next segments. 53 | - `'` decrements segment index (previous `segmentMixin` per symbol if defined). Also applies 1.0 / `scaleFactor` to next segments. 54 | - `[` starts branch 55 | - `]` ends branch 56 | 57 | Besides those *turtle graphic* symbols, you define your own symbols like `F` for drawing actual geometry like lines or flowers. 58 | However if you want your symbol to be rendered, you need to define an entry in `segmentMixins`, like so: 59 | 60 | ```.html 61 | 62 | ``` 63 | Be sure that you define your [mixins](https://aframe.io/docs/0.3.0/core/mixins.html) in your `` at the beginning of your scene. 64 | A fallback geometry and material if you don't define your segmentMixins is not yet implemented, but will be soon :) 65 | 66 | 67 | 68 | It's also possible to use context sensitive productions like: 69 | ```.html 70 | 71 | ``` 72 | 73 | Parametric and stochastic productions are not yet implemented in the component. 74 | Native JS function parsing for productions, as the backend library allows, might added to this 75 | component, but is not yet done. 76 | 77 | Please take a look at the examples to get an idea how to use the component. PRs are welcome! :) 78 | 79 | #### Browser Installation 80 | 81 | Install and use by directly including the [browser files](dist): 82 | 83 | ```.html 84 | 85 | My A-Frame Scene 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | ``` 103 | 104 | #### NPM Installation 105 | 106 | Install via NPM: 107 | 108 | ```bash 109 | npm install aframe-lsystem-component 110 | ``` 111 | 112 | Then register and use. 113 | 114 | ```js 115 | require('aframe'); 116 | require('aframe-lsystem-component'); 117 | ``` 118 | -------------------------------------------------------------------------------- /dist/.gitkeep: -------------------------------------------------------------------------------- 1 | `npm run dist` to generate browser files. 2 | -------------------------------------------------------------------------------- /dist/aframe-lsystem-component.js: -------------------------------------------------------------------------------- 1 | /******/ (function(modules) { // webpackBootstrap 2 | /******/ // The module cache 3 | /******/ var installedModules = {}; 4 | /******/ 5 | /******/ // The require function 6 | /******/ function __webpack_require__(moduleId) { 7 | /******/ 8 | /******/ // Check if module is in cache 9 | /******/ if(installedModules[moduleId]) { 10 | /******/ return installedModules[moduleId].exports; 11 | /******/ } 12 | /******/ // Create a new module (and put it into the cache) 13 | /******/ var module = installedModules[moduleId] = { 14 | /******/ i: moduleId, 15 | /******/ l: false, 16 | /******/ exports: {} 17 | /******/ }; 18 | /******/ 19 | /******/ // Execute the module function 20 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 21 | /******/ 22 | /******/ // Flag the module as loaded 23 | /******/ module.l = true; 24 | /******/ 25 | /******/ // Return the exports of the module 26 | /******/ return module.exports; 27 | /******/ } 28 | /******/ 29 | /******/ 30 | /******/ // expose the modules object (__webpack_modules__) 31 | /******/ __webpack_require__.m = modules; 32 | /******/ 33 | /******/ // expose the module cache 34 | /******/ __webpack_require__.c = installedModules; 35 | /******/ 36 | /******/ // define getter function for harmony exports 37 | /******/ __webpack_require__.d = function(exports, name, getter) { 38 | /******/ if(!__webpack_require__.o(exports, name)) { 39 | /******/ Object.defineProperty(exports, name, { 40 | /******/ configurable: false, 41 | /******/ enumerable: true, 42 | /******/ get: getter 43 | /******/ }); 44 | /******/ } 45 | /******/ }; 46 | /******/ 47 | /******/ // getDefaultExport function for compatibility with non-harmony modules 48 | /******/ __webpack_require__.n = function(module) { 49 | /******/ var getter = module && module.__esModule ? 50 | /******/ function getDefault() { return module['default']; } : 51 | /******/ function getModuleExports() { return module; }; 52 | /******/ __webpack_require__.d(getter, 'a', getter); 53 | /******/ return getter; 54 | /******/ }; 55 | /******/ 56 | /******/ // Object.prototype.hasOwnProperty.call 57 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; 58 | /******/ 59 | /******/ // __webpack_public_path__ 60 | /******/ __webpack_require__.p = ""; 61 | /******/ 62 | /******/ // Load entry module and return exports 63 | /******/ return __webpack_require__(__webpack_require__.s = 0); 64 | /******/ }) 65 | /************************************************************************/ 66 | /******/ ([ 67 | /* 0 */ 68 | /***/ (function(module, __webpack_exports__, __webpack_require__) { 69 | 70 | "use strict"; 71 | Object.defineProperty(__webpack_exports__, "__esModule", { value: true }); 72 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_lindenmayer__ = __webpack_require__(1); 73 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_1_worker_loader_inline_fallback_false_worker_js__ = __webpack_require__(2); 74 | /* 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__); 75 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__primitives_a_lsystem_js__ = __webpack_require__(4); 76 | /* harmony import */ var __WEBPACK_IMPORTED_MODULE_2__primitives_a_lsystem_js___default = __webpack_require__.n(__WEBPACK_IMPORTED_MODULE_2__primitives_a_lsystem_js__); 77 | 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"); } }; }(); 78 | 79 | if (typeof AFRAME === 'undefined') { 80 | throw new Error('Component attempted to register before AFRAME was available.'); 81 | } 82 | 83 | 84 | 85 | // As we use webpack for compiling the source, it's used to bundle the 86 | // web worker into a blob via: https://github.com/webpack/worker-loader 87 | 88 | 89 | 90 | 91 | /** 92 | * Lindenmayer-System component for A-Frame. 93 | */ 94 | 95 | function parseFromTo(value) { 96 | let flatResult = value.split(/(\w)\s*:\s*/).filter(part => part.length !== 0); 97 | let result = []; 98 | for (var i = 0; i < flatResult.length; i += 2) { 99 | result.push([flatResult[i], flatResult[i + 1]]); 100 | } 101 | return result; 102 | } 103 | 104 | AFRAME.registerComponent('lsystem', { 105 | schema: { 106 | 107 | axiom: { 108 | type: 'string', 109 | default: 'F' 110 | }, 111 | 112 | productions: { 113 | default: 'F:FF', 114 | // return an array of production tuples ([[from, to], ['F', 'F+F']]) 115 | parse: value => parseFromTo(value).map((_ref) => { 116 | var _ref2 = _slicedToArray(_ref, 2); 117 | 118 | let from = _ref2[0], 119 | to = _ref2[1]; 120 | return [from, to.replace(/\s/g, '')]; 121 | }) 122 | }, 123 | 124 | // A: blue line, red line, yellow line B: red line 125 | segmentMixins: { 126 | type: 'string', 127 | parse: function parse(value) { 128 | 129 | let mixinsForSymbol = new Map(); 130 | let result = parseFromTo(value); 131 | var _iteratorNormalCompletion = true; 132 | var _didIteratorError = false; 133 | var _iteratorError = undefined; 134 | 135 | try { 136 | for (var _iterator = result[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { 137 | let _ref3 = _step.value; 138 | 139 | var _ref4 = _slicedToArray(_ref3, 2); 140 | 141 | let from = _ref4[0]; 142 | let to = _ref4[1]; 143 | 144 | to = to.replace(/[\[\]]/g, '').split(','); 145 | mixinsForSymbol.set(from, to); 146 | } 147 | } catch (err) { 148 | _didIteratorError = true; 149 | _iteratorError = err; 150 | } finally { 151 | try { 152 | if (!_iteratorNormalCompletion && _iterator.return) { 153 | _iterator.return(); 154 | } 155 | } finally { 156 | if (_didIteratorError) { 157 | throw _iteratorError; 158 | } 159 | } 160 | } 161 | 162 | return mixinsForSymbol; 163 | } 164 | }, 165 | 166 | iterations: { 167 | type: 'int', 168 | default: 1 169 | }, 170 | 171 | angle: { 172 | default: 90.0 173 | }, 174 | 175 | translateAxis: { 176 | type: 'string', 177 | default: 'y', 178 | parse: function parse(value) { 179 | value = value.toLowerCase(); 180 | if (value === 'x') { 181 | return new THREE.Vector3(1, 0, 0); 182 | } else if (value === 'y') { 183 | return new THREE.Vector3(0, 1, 0); 184 | } else if (value === 'z') { 185 | return new THREE.Vector3(0, 0, 1); 186 | } else { 187 | throw new Error('translateAxis has to be a string: "x", "y" or "z"'); 188 | } 189 | } 190 | }, 191 | 192 | scaleFactor: { 193 | default: 1.0 194 | }, 195 | 196 | dynamicSegmentLength: { 197 | default: true 198 | }, 199 | 200 | mergeGeometries: { 201 | type: 'boolean', 202 | default: true 203 | }, 204 | 205 | functionsInProductions: { 206 | type: 'boolean', 207 | default: true 208 | } 209 | }, 210 | 211 | /** 212 | * Called once when component is attached. Generally for initial setup. 213 | */ 214 | init: function init() { 215 | 216 | this.sceneEl = document.querySelector('a-scene'); 217 | 218 | let self = this; 219 | 220 | this.initWorker(); 221 | 222 | this.X = new THREE.Vector3(1, 0, 0); 223 | this.Y = new THREE.Vector3(0, 1, 0); 224 | this.Z = new THREE.Vector3(0, 0, 1); 225 | this.xPosRotation = new THREE.Quaternion(); 226 | this.xNegRotation = new THREE.Quaternion(); 227 | this.yPosRotation = new THREE.Quaternion(); 228 | this.yNegRotation = new THREE.Quaternion(); 229 | this.zPosRotation = new THREE.Quaternion(); 230 | this.zNegRotation = new THREE.Quaternion(); 231 | this.yReverseRotation = new THREE.Quaternion(); 232 | this.xPosRotation = new THREE.Quaternion(); 233 | this.xNegRotation = new THREE.Quaternion(); 234 | this.yPosRotation = new THREE.Quaternion(); 235 | this.yNegRotation = new THREE.Quaternion(); 236 | this.zPosRotation = new THREE.Quaternion(); 237 | this.zNegRotation = new THREE.Quaternion(); 238 | this.yReverseRotation = new THREE.Quaternion(); 239 | this.segmentLengthFactor = 1.0; 240 | 241 | this.transformationSegment = new THREE.Object3D(); 242 | this.transformationSegmentTemplate = this.transformationSegment.clone(); 243 | 244 | let scaleFactor = self.data.scaleFactor; 245 | 246 | this.colorIndex = 0; 247 | this.lineWidth = 0.0005; 248 | this.lineLength = 0.125; 249 | 250 | this.LSystem = new __WEBPACK_IMPORTED_MODULE_0_lindenmayer__["a" /* default */]({ 251 | axiom: 'F', 252 | productions: { 'F': 'F' }, 253 | finals: { 254 | /* As a default F is already defined as final, new ones get added automatically 255 | by parsing the segment mixins. If no segment mixin for any symbol is defined 256 | it wont get a final function and therefore not render. 257 | */ 258 | '+': () => { 259 | self.transformationSegment.quaternion.multiply(self.yPosRotation); 260 | }, 261 | '-': () => { 262 | self.transformationSegment.quaternion.multiply(self.yNegRotation); 263 | }, 264 | '&': () => { 265 | self.transformationSegment.quaternion.multiply(self.zNegRotation); 266 | }, 267 | '^': () => { 268 | self.transformationSegment.quaternion.multiply(self.zPosRotation); 269 | }, 270 | '\\': () => { 271 | self.transformationSegment.quaternion.multiply(self.xNegRotation); 272 | }, 273 | '<': () => { 274 | self.transformationSegment.quaternion.multiply(self.xNegRotation); 275 | }, 276 | '/': () => { 277 | self.transformationSegment.quaternion.multiply(self.xPosRotation); 278 | }, 279 | '>': () => { 280 | self.transformationSegment.quaternion.multiply(self.xPosRotation); 281 | }, 282 | '|': () => { 283 | self.transformationSegment.quaternion.multiply(self.yReverseRotation); 284 | }, 285 | '!': () => { 286 | self.segmentLengthFactor *= scaleFactor; 287 | self.transformationSegment.scale.set(self.transformationSegment.scale.x *= scaleFactor, self.transformationSegment.scale.y *= scaleFactor, self.transformationSegment.scale.z *= scaleFactor); 288 | self.colorIndex++; 289 | }, 290 | '\'': () => { 291 | self.segmentLengthFactor *= 1.0 / scaleFactor; 292 | 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); 293 | self.colorIndex = Math.max(0, self.colorIndex - 1); 294 | }, 295 | '[': () => { 296 | self.stack.push(self.transformationSegment.clone()); 297 | }, 298 | ']': () => { 299 | self.transformationSegment = self.stack.pop(); 300 | } 301 | } 302 | }); 303 | }, 304 | 305 | /** 306 | * Called when component is attached and when component data changes. 307 | * Generally modifies the entity based on the data. 308 | */ 309 | update: function update(oldData) { 310 | // var diffData = diff(data, oldData || {}); 311 | // console.log(diffData); 312 | 313 | // TODO: Check if only angle changed or axiom or productions 314 | // 315 | let self = this; 316 | 317 | if (this.data.mergeGeometries === false && this.segmentElementGroupsMap !== undefined) { 318 | var _iteratorNormalCompletion2 = true; 319 | var _didIteratorError2 = false; 320 | var _iteratorError2 = undefined; 321 | 322 | try { 323 | for (var _iterator2 = this.segmentElementGroupsMap.values()[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { 324 | let segmentElGroup = _step2.value; 325 | 326 | segmentElGroup.removeObject3D('mesh'); 327 | segmentElGroup.innerHTML = ''; 328 | } 329 | } catch (err) { 330 | _didIteratorError2 = true; 331 | _iteratorError2 = err; 332 | } finally { 333 | try { 334 | if (!_iteratorNormalCompletion2 && _iterator2.return) { 335 | _iterator2.return(); 336 | } 337 | } finally { 338 | if (_didIteratorError2) { 339 | throw _iteratorError2; 340 | } 341 | } 342 | } 343 | } 344 | 345 | if (Object.keys(oldData).length === 0) { 346 | this.updateLSystem(); 347 | this.updateSegmentMixins(); 348 | this.updateTurtleGraphics(); 349 | } else { 350 | 351 | let visualChange = false; 352 | 353 | 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)) { 354 | 355 | this.updateLSystem(); 356 | visualChange = true; 357 | } 358 | 359 | if (oldData.segmentMixins !== undefined && JSON.stringify(Array.from(oldData.segmentMixins.entries())) !== JSON.stringify(Array.from(this.data.segmentMixins.entries()))) { 360 | this.updateSegmentMixins(); 361 | visualChange = true; 362 | } 363 | 364 | if (visualChange || oldData.angle && oldData.angle !== this.data.angle) { 365 | 366 | this.updateTurtleGraphics(); 367 | } else { 368 | // console.log('nothing changed in update?'); 369 | // this.updateLSystem(); 370 | // this.updateSegmentMixins(); 371 | } 372 | } 373 | }, 374 | 375 | // if this.dynamicSegmentLength===true use this function to set the length 376 | // depending on segments geometries bbox 377 | calculateSegmentLength: function calculateSegmentLength(mixin, geometry) { 378 | if (this.segmentLengthMap.has(mixin)) return this.segmentLengthMap.get(mixin); 379 | geometry.computeBoundingBox(); 380 | let segmentLength; 381 | if (this.data.translateAxis.equals(this.X)) { 382 | segmentLength = Math.abs(geometry.boundingBox.min.x - geometry.boundingBox.max.x); 383 | } else if (this.data.translateAxis.equals(this.Y)) { 384 | segmentLength = Math.abs(geometry.boundingBox.min.y - geometry.boundingBox.max.y); 385 | } else if (this.data.translateAxis.equals(this.Z)) { 386 | segmentLength = Math.abs(geometry.boundingBox.min.z - geometry.boundingBox.max.z); 387 | } 388 | this.segmentLengthMap.set(mixin, segmentLength); 389 | return segmentLength; 390 | }, 391 | 392 | initWorker: function initWorker() { 393 | this.worker = new __WEBPACK_IMPORTED_MODULE_1_worker_loader_inline_fallback_false_worker_js___default.a(); 394 | }, 395 | 396 | pushSegment: function pushSegment(symbol) { 397 | 398 | let self = this; 399 | let currentQuaternion = self.transformationSegment.quaternion; 400 | let currentPosition = self.transformationSegment.position; 401 | let currentScale = self.transformationSegment.scale; 402 | 403 | // Cap colorIndex to maximum mixins defined for the symbol. 404 | let cappedColorIndex = Math.min(this.colorIndex, this.data.segmentMixins.get(symbol).length - 1); 405 | 406 | let mixin = this.mixinMap.get(symbol + cappedColorIndex); 407 | 408 | if (this.data.mergeGeometries === false) { 409 | let newSegment = document.createElement('a-entity'); 410 | newSegment.setAttribute('mixin', mixin); 411 | 412 | newSegment.addEventListener('loaded', e => { 413 | // Offset child element of object3D, to rotate around end point 414 | // IMPORTANT: It may change that A-Frame puts objects into a group 415 | 416 | let segmentLength = self.segmentLengthMap.get(mixin); 417 | 418 | newSegment.object3D.children[0].translateOnAxis(self.data.translateAxis, segmentLength * self.segmentLengthFactor / 2); 419 | 420 | newSegment.object3D.quaternion.copy(currentQuaternion); 421 | newSegment.object3D.position.copy(currentPosition); 422 | newSegment.object3D.scale.copy(currentScale); 423 | }, { once: true }); 424 | this.segmentElementGroupsMap.get(symbol + cappedColorIndex).appendChild(newSegment); 425 | } else { 426 | let segmentObject3D = this.segmentObjects3DMap.get(symbol + cappedColorIndex); 427 | let newSegmentObject3D = segmentObject3D.clone(); 428 | newSegmentObject3D.matrixAutoUpdate = false; 429 | newSegmentObject3D.quaternion.copy(currentQuaternion); 430 | newSegmentObject3D.position.copy(currentPosition); 431 | newSegmentObject3D.scale.copy(currentScale); 432 | 433 | newSegmentObject3D.updateMatrix(); 434 | this.mergeGroups.get(symbol + cappedColorIndex).geometry.merge(newSegmentObject3D.geometry, newSegmentObject3D.matrix); 435 | } 436 | let segmentLength = this.segmentLengthMap.get(mixin); 437 | this.transformationSegment.translateOnAxis(this.data.translateAxis, segmentLength * this.segmentLengthFactor); 438 | }, 439 | 440 | updateLSystem: function updateLSystem() { 441 | let self = this; 442 | 443 | // post params to worker 444 | let params = { 445 | axiom: this.data.axiom, 446 | productions: this.data.productions, 447 | iterations: this.data.iterations 448 | }; 449 | 450 | if (Date.now() - this.worker.startTime > 1000) { 451 | // if we got user input, but worker is running for over a second 452 | // terminate old worker and start new one. 453 | this.worker.terminate(); 454 | this.initWorker(); 455 | } 456 | 457 | this.worker.startTime = Date.now(); 458 | 459 | this.workerPromise = new Promise((resolve, reject) => { 460 | 461 | this.worker.onmessage = e => { 462 | self.LSystem.setAxiom(e.data.result); 463 | resolve(); 464 | }; 465 | }); 466 | 467 | this.worker.postMessage(params); 468 | return this.workerPromise; 469 | }, 470 | 471 | updateSegmentMixins: function updateSegmentMixins() { 472 | let self = this; 473 | 474 | this.el.innerHTML = ''; 475 | 476 | // Map for remembering the elements holding differnt segment types 477 | this.segmentElementGroupsMap = new Map(); 478 | 479 | this.mixinMap = new Map(); 480 | // Construct a map with keys = `symbol + colorIndex` from data.segmentMixins 481 | var _iteratorNormalCompletion3 = true; 482 | var _didIteratorError3 = false; 483 | var _iteratorError3 = undefined; 484 | 485 | try { 486 | for (var _iterator3 = this.data.segmentMixins[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { 487 | let _ref5 = _step3.value; 488 | 489 | var _ref6 = _slicedToArray(_ref5, 2); 490 | 491 | let symbol = _ref6[0]; 492 | let mixinList = _ref6[1]; 493 | 494 | for (let i = 0; i < mixinList.length; i++) { 495 | this.mixinMap.set(symbol + i, mixinList[i]); 496 | } 497 | } 498 | 499 | // Map for buffering geometries for use in pushSegments() 500 | // when merging geometries ourselves and not by appending a `mixin` attributes, 501 | // as done with `mergeGeometry = false`. 502 | } catch (err) { 503 | _didIteratorError3 = true; 504 | _iteratorError3 = err; 505 | } finally { 506 | try { 507 | if (!_iteratorNormalCompletion3 && _iterator3.return) { 508 | _iterator3.return(); 509 | } 510 | } finally { 511 | if (_didIteratorError3) { 512 | throw _iteratorError3; 513 | } 514 | } 515 | } 516 | 517 | this.segmentObjects3DMap = new Map(); 518 | 519 | this.segmentLengthMap = new Map(); 520 | this.mergeGroups = new Map(); 521 | 522 | this.mixinPromises = []; 523 | 524 | // Collect mixin info by pre-appending segment elements with their mixin 525 | // Then use the generated geometry etc. 526 | if (this.data.segmentMixins && this.data.segmentMixins.length !== 0) { 527 | 528 | // Go through every symbols segmentMixins as defined by user 529 | var _iteratorNormalCompletion4 = true; 530 | var _didIteratorError4 = false; 531 | var _iteratorError4 = undefined; 532 | 533 | try { 534 | for (var _iterator4 = this.data.segmentMixins[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { 535 | let el = _step4.value; 536 | 537 | var _el = _slicedToArray(el, 2); 538 | 539 | let symbol = _el[0], 540 | mixinList = _el[1]; 541 | // Set final functions for each symbol that has a mixin defined 542 | 543 | this.LSystem.setFinal(symbol, () => { 544 | self.pushSegment.bind(self, symbol)(); 545 | }); 546 | 547 | // And iterate the MixinList to buffer the segments or calculate segment lengths… 548 | for (let i = 0; i < mixinList.length; i++) { 549 | let mixinColorIndex = i; 550 | let mixin = mixinList[mixinColorIndex]; 551 | 552 | self.mixinPromises.push(new Promise((resolve, reject) => { 553 | // Save mixinColorIndex for async promise below. 554 | 555 | let segmentElGroup = document.createElement('a-entity'); 556 | segmentElGroup.setAttribute('id', mixin + '-group-' + mixinColorIndex + Math.floor(Math.random() * 10000)); 557 | 558 | // TODO: Put it all under this.mergeData 559 | segmentElGroup.setAttribute('geometry', 'buffer', false); 560 | segmentElGroup.setAttribute('mixin', mixin); 561 | segmentElGroup.addEventListener('loaded', function (e) { 562 | let segmentObject = segmentElGroup.getObject3D('mesh').clone(); 563 | 564 | // Make sure the geometry is actually unique 565 | // AFrame sets the same geometry for multiple entities. As we modify 566 | // the geometry per entity we need to have unique geometry instances. 567 | // TODO: hm, maybe try to use instanced geometry and offset on object? 568 | segmentElGroup.getObject3D('mesh').geometry.dispose(); 569 | segmentObject.geometry = segmentObject.geometry.clone(); 570 | 571 | let segmentLength = self.calculateSegmentLength(mixin, segmentObject.geometry); 572 | 573 | // Do some additional stuff like buffering 3D objects / geometry 574 | // if we want to merge geometries. 575 | if (self.data.mergeGeometries === true) { 576 | 577 | // Offset geometry by half segmentLength to get the rotation point right. 578 | let translation = self.data.translateAxis.clone().multiplyScalar(segmentLength * self.segmentLengthFactor / 2); 579 | 580 | // IMPORTANT!!! 581 | // TODO: Try to use pivot object instead of translating geometry 582 | // this may help in reusing geometry and not needing to clone it (see above)? 583 | // see: https://github.com/mrdoob/three.js/issues/1364 584 | //and 585 | // see: http://stackoverflow.com/questions/28848863/threejs-how-to-rotate-around-objects-own-center-instead-of-world-center 586 | segmentObject.geometry.translate(translation.x, translation.y, translation.z); 587 | 588 | self.segmentObjects3DMap.set(symbol + mixinColorIndex, segmentObject); 589 | } 590 | 591 | segmentElGroup.removeObject3D('mesh'); 592 | resolve(); 593 | }, { once: true }); 594 | 595 | if (this.segmentElementGroupsMap.has(symbol + mixinColorIndex)) { 596 | let previousElGroup = this.segmentElementGroupsMap.get(symbol + mixinColorIndex); 597 | this.segmentElementGroupsMap.delete(symbol + mixinColorIndex); 598 | this.el.removeChild(previousElGroup); 599 | } 600 | 601 | this.segmentElementGroupsMap.set(symbol + mixinColorIndex, segmentElGroup); 602 | this.el.appendChild(segmentElGroup); 603 | })); 604 | } 605 | } 606 | } catch (err) { 607 | _didIteratorError4 = true; 608 | _iteratorError4 = err; 609 | } finally { 610 | try { 611 | if (!_iteratorNormalCompletion4 && _iterator4.return) { 612 | _iterator4.return(); 613 | } 614 | } finally { 615 | if (_didIteratorError4) { 616 | throw _iteratorError4; 617 | } 618 | } 619 | } 620 | } 621 | }, 622 | 623 | updateTurtleGraphics: async function updateTurtleGraphics() { 624 | 625 | await Promise.all([...this.mixinPromises, this.workerPromise]); 626 | // The main segment used for saving transformations (rotation, translation, scale(?)) 627 | this.transformationSegment.copy(this.transformationSegmentTemplate); 628 | 629 | // set merge groups 630 | if (this.data.mergeGeometries === true) { 631 | var _iteratorNormalCompletion5 = true; 632 | var _didIteratorError5 = false; 633 | var _iteratorError5 = undefined; 634 | 635 | try { 636 | for (var _iterator5 = this.segmentObjects3DMap[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) { 637 | let _ref7 = _step5.value; 638 | 639 | var _ref8 = _slicedToArray(_ref7, 2); 640 | 641 | let id = _ref8[0]; 642 | let segmentObject = _ref8[1]; 643 | 644 | this.mergeGroups.set(id, new THREE.Mesh(new THREE.Geometry(), segmentObject.material)); 645 | } 646 | } catch (err) { 647 | _didIteratorError5 = true; 648 | _iteratorError5 = err; 649 | } finally { 650 | try { 651 | if (!_iteratorNormalCompletion5 && _iterator5.return) { 652 | _iterator5.return(); 653 | } 654 | } finally { 655 | if (_didIteratorError5) { 656 | throw _iteratorError5; 657 | } 658 | } 659 | } 660 | } // We push copies of this.transformationSegment on branch symbols inside this array. 661 | this.stack = []; 662 | 663 | let angle = this.data.angle; 664 | 665 | // Set quaternions based on angle slider 666 | this.xPosRotation.setFromAxisAngle(this.X, Math.PI / 180 * angle); 667 | this.xNegRotation.setFromAxisAngle(this.X, Math.PI / 180 * -angle); 668 | 669 | this.yPosRotation.setFromAxisAngle(this.Y, Math.PI / 180 * angle); 670 | this.yNegRotation.setFromAxisAngle(this.Y, Math.PI / 180 * -angle); 671 | this.yReverseRotation.setFromAxisAngle(this.Y, Math.PI / 180 * 180); 672 | 673 | this.zPosRotation.setFromAxisAngle(this.Z, Math.PI / 180 * angle); 674 | this.zNegRotation.setFromAxisAngle(this.Z, Math.PI / 180 * -angle); 675 | // 676 | // this.geometry = new THREE.CylinderGeometry(this.lineWidth, this.lineWidth, self.data.lineLength, 3); 677 | // this.geometry.rotateZ((Math.PI / 180) * 90); 678 | // this.geometry.translate( -(this.data.segmentLength/2), 0, 0 ); 679 | // for (let face of this.geometry.faces) { 680 | // face.color.setHex(this.colors[colorIndex]); 681 | // } 682 | // this.geometry.colorsNeedUpdate = true; 683 | 684 | this.LSystem.final(); 685 | // finally set the merged meshes to be visible. 686 | if (this.data.mergeGeometries === true) { 687 | var _iteratorNormalCompletion6 = true; 688 | var _didIteratorError6 = false; 689 | var _iteratorError6 = undefined; 690 | 691 | try { 692 | for (var _iterator6 = this.segmentElementGroupsMap[Symbol.iterator](), _step6; !(_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done); _iteratorNormalCompletion6 = true) { 693 | let tuple = _step6.value; 694 | 695 | var _tuple = _slicedToArray(tuple, 2); 696 | 697 | let symbolWithColorIndex = _tuple[0], 698 | elGroup = _tuple[1]; 699 | 700 | 701 | let mergeGroup = this.mergeGroups.get(symbolWithColorIndex); 702 | // Remove unused element groups inside our element 703 | if (mergeGroup.geometry.vertices.length === 0) { 704 | this.el.removeChild(elGroup); 705 | } else { 706 | elGroup.setObject3D('mesh', this.mergeGroups.get(symbolWithColorIndex)); 707 | elGroup.setAttribute('mixin', this.mixinMap.get(symbolWithColorIndex)); 708 | } 709 | } 710 | } catch (err) { 711 | _didIteratorError6 = true; 712 | _iteratorError6 = err; 713 | } finally { 714 | try { 715 | if (!_iteratorNormalCompletion6 && _iterator6.return) { 716 | _iterator6.return(); 717 | } 718 | } finally { 719 | if (_didIteratorError6) { 720 | throw _iteratorError6; 721 | } 722 | } 723 | } 724 | } 725 | }, 726 | /** 727 | * Called when a component is removed (e.g., via removeAttribute). 728 | * Generally undoes all modifications to the entity. 729 | */ 730 | remove: function remove() {}, 731 | 732 | /** 733 | * Called on each scene tick. 734 | */ 735 | tick: function tick(t) { 736 | // console.log(this.parentEl === undefined); 737 | // console.log('\nTICK\n', t); 738 | }, 739 | 740 | /** 741 | * Called when entity pauses. 742 | * Use to stop or remove any dynamic or background behavior such as events. 743 | */ 744 | pause: function pause() {}, 745 | 746 | /** 747 | * Called when entity resumes. 748 | * Use to continue or add any dynamic or background behavior such as events. 749 | */ 750 | play: function play() {} 751 | }); 752 | 753 | /***/ }), 754 | /* 1 */ 755 | /***/ (function(module, __webpack_exports__, __webpack_require__) { 756 | 757 | "use strict"; 758 | // Get a list of productions that have identical initiators, 759 | // Output a single stochastic production. Probability per production 760 | // is defined by amount of input productions (4 => 25% each, 2 => 50% etc.) 761 | 762 | 763 | // These transformers get a classic ABOP snytax as input and return a standardized 764 | // production object in the form of ['F', 765 | // { 766 | // successor:String/Iterable 767 | // [alternatively]stochasticSuccessors: Iterable of standardized objects with mandatory weight fields, 768 | // leftCtx: iterable/string, 769 | // rightCtx: Iterable/String, 770 | // condition: Function }] 771 | 772 | function transformClassicStochasticProductions(productions) { 773 | 774 | return function transformedProduction() { 775 | var resultList = productions; // the parser for productions shall create this list 776 | var count = resultList.length; 777 | 778 | var r = Math.random(); 779 | for (var i = 0; i < count; i++) { 780 | var range = (i + 1) / count; 781 | if (r <= range) return resultList[i]; 782 | } 783 | 784 | console.error('Should have returned a result of the list, something is wrong here with the random numbers?.'); 785 | }; 786 | } 787 | 788 | // TODO: implement it! 789 | 790 | 791 | // TODO: Scaffold classic parametric and context sensitive stuff out of main file 792 | // And simply require it here, eg: 793 | // this.testClassicParametricSyntax = require(classicSyntax.testParametric)?? 794 | function testClassicParametricSyntax(axiom) { 795 | return (/\(.+\)/.test(axiom) 796 | ); 797 | } 798 | 799 | // transforms things like 'A(1,2,5)B(2.5)' to 800 | // [ {symbol: 'A', params: [1,2,5]}, {symbol: 'B', params:[25]} ] 801 | // strips spaces 802 | function transformClassicParametricAxiom(axiom) { 803 | 804 | // Replace whitespaces, then split between square brackets. 805 | var splitAxiom = axiom.replace(/\s+/g, '').split(/[\(\)]/); 806 | // console.log('parts:', splitAxiom) 807 | var newAxiom = []; 808 | // Construct new axiom by getting the params and symbol. 809 | for (var i = 0; i < splitAxiom.length - 1; i += 2) { 810 | var params = splitAxiom[i + 1].split(',').map(Number); 811 | newAxiom.push({ symbol: splitAxiom[i], params: params }); 812 | } 813 | // console.log('parsed axiom:', newAxiom) 814 | } 815 | 816 | function transformClassicCSProduction(p) { 817 | 818 | // before continuing, check if classic syntax actually there 819 | // example: p = ['AC', 'Z'] 820 | 821 | // left should be ['A', 'B'] 822 | var left = p[0].match(/(.+)<(.)/); 823 | 824 | // right should be ['B', 'C'] 825 | var right = p[0].match(/(.)>(.+)/); 826 | 827 | // Not a CS-Production (no '<' or '>'), 828 | //return original production. 829 | if (left === null && right === null) { 830 | return p; 831 | } 832 | 833 | var predecessor = void 0; 834 | // create new production object _or_ use the one set by the user 835 | var productionObject = p[1].successor || p[1].successors ? p[1] : { successor: p[1] }; 836 | if (left !== null) { 837 | predecessor = left[2]; 838 | productionObject.leftCtx = left[1]; 839 | } 840 | if (right !== null) { 841 | predecessor = right[1]; 842 | productionObject.rightCtx = right[2]; 843 | } 844 | 845 | return [predecessor, productionObject]; 846 | } 847 | 848 | function stringToObjects(string) { 849 | if (typeof string !== 'string' && string instanceof String === false) return string; 850 | var transformed = []; 851 | for (var _iterator = string, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) { 852 | var _ref; 853 | 854 | if (_isArray) { 855 | if (_i >= _iterator.length) break; 856 | _ref = _iterator[_i++]; 857 | } else { 858 | _i = _iterator.next(); 859 | if (_i.done) break; 860 | _ref = _i.value; 861 | } 862 | 863 | var symbol = _ref; 864 | transformed.push({ symbol }); 865 | }return transformed; 866 | } 867 | 868 | // TODO: continue here 869 | 870 | 871 | // transform p to {successor: p} 872 | // if applicable also transform strings into array of {symbol: String} objects 873 | // TODO: make more modular! dont have forceObjects in here 874 | function normalizeProductionRightSide(p, forceObjects) { 875 | 876 | if (p.hasOwnProperty('successors')) { 877 | for (var i = 0; i < p.successors.length; i++) { 878 | p.successors[i] = normalizeProductionRightSide(p.successors[i], forceObjects); 879 | } 880 | } else if (p.hasOwnProperty('successor') === false) { 881 | p = { successor: p }; 882 | } 883 | 884 | if (forceObjects && p.hasOwnProperty('successor')) { 885 | p.successor = stringToObjects(p.successor); 886 | } 887 | 888 | return p; 889 | } 890 | 891 | function normalizeProduction(p, forceObjects) { 892 | 893 | p[1] = normalizeProductionRightSide(p[1], forceObjects); 894 | return p; 895 | } 896 | 897 | function LSystem(_ref) { 898 | var _ref$axiom = _ref.axiom, 899 | axiom = _ref$axiom === undefined ? '' : _ref$axiom, 900 | productions = _ref.productions, 901 | finals = _ref.finals, 902 | _ref$branchSymbols = _ref.branchSymbols, 903 | branchSymbols = _ref$branchSymbols === undefined ? '' : _ref$branchSymbols, 904 | _ref$ignoredSymbols = _ref.ignoredSymbols, 905 | ignoredSymbols = _ref$ignoredSymbols === undefined ? '' : _ref$ignoredSymbols, 906 | _ref$allowClassicSynt = _ref.allowClassicSyntax, 907 | allowClassicSyntax = _ref$allowClassicSynt === undefined ? true : _ref$allowClassicSynt, 908 | _ref$classicParametri = _ref.classicParametricSyntax, 909 | classicParametricSyntax = _ref$classicParametri === undefined ? false : _ref$classicParametri, 910 | _ref$forceObjects = _ref.forceObjects, 911 | forceObjects = _ref$forceObjects === undefined ? false : _ref$forceObjects, 912 | _ref$debug = _ref.debug, 913 | debug = _ref$debug === undefined ? false : _ref$debug; 914 | 915 | 916 | // TODO: forceObject to be more intelligent based on other productions?? 917 | 918 | this.setAxiom = function (axiom) { 919 | this.axiom = this.forceObjects ? stringToObjects(axiom) : axiom; 920 | }; 921 | 922 | this.getRaw = function () { 923 | return this.axiom; 924 | }; 925 | 926 | // if using objects in axioms, as used in parametric L-Systems 927 | this.getString = function () { 928 | var onlySymbols = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true; 929 | 930 | if (typeof this.axiom === 'string') return this.axiom; 931 | if (onlySymbols === true) { 932 | return this.axiom.reduce((prev, current) => { 933 | if (current.symbol === undefined) { 934 | console.log('found:', current); 935 | 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.'); 936 | } 937 | return prev + current.symbol; 938 | }, ''); 939 | } else { 940 | return JSON.stringify(this.axiom); 941 | } 942 | }; 943 | 944 | this.getStringResult = this.getString; 945 | 946 | this.setProduction = function (from, to) { 947 | var allowAppendingMultiSuccessors = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; 948 | 949 | var newProduction = [from, to]; 950 | if (newProduction === undefined) throw new Error('no production specified.'); 951 | 952 | if (to.successor && to.successors) { 953 | throw new Error('You can not have both a "successor" and a "successors" field in your production!'); 954 | } 955 | 956 | // Apply production transformers and normalizations 957 | if (this.allowClassicSyntax === true) { 958 | newProduction = transformClassicCSProduction(newProduction, this.ignoredSymbols); 959 | } 960 | 961 | newProduction = normalizeProduction(newProduction, this.forceObjects); 962 | 963 | // check wether production is stochastic 964 | newProduction[1].isStochastic = newProduction[1].successors !== undefined && newProduction[1].successors.every(successor => successor.weight !== undefined); 965 | 966 | if (newProduction[1].isStochastic) { 967 | // calculate weight sum 968 | newProduction[1].weightSum = 0; 969 | for (var _iterator = newProduction[1].successors, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) { 970 | var _ref2; 971 | 972 | if (_isArray) { 973 | if (_i >= _iterator.length) break; 974 | _ref2 = _iterator[_i++]; 975 | } else { 976 | _i = _iterator.next(); 977 | if (_i.done) break; 978 | _ref2 = _i.value; 979 | } 980 | 981 | var s = _ref2; 982 | 983 | newProduction[1].weightSum += s.weight; 984 | } 985 | } 986 | 987 | var symbol = newProduction[0]; 988 | if (allowAppendingMultiSuccessors === true && this.productions.has(symbol)) { 989 | 990 | var existingProduction = this.productions.get(symbol); 991 | var singleSuccessor = existingProduction.successor; 992 | var multiSuccessors = existingProduction.successors; 993 | 994 | if (singleSuccessor && !multiSuccessors) { 995 | // replace existing prod with new obj and add previous successor as first elem 996 | // to new successors field. 997 | existingProduction = { successors: [existingProduction] }; 998 | } 999 | existingProduction.successors.push(newProduction[1]); 1000 | this.productions.set(symbol, existingProduction); 1001 | } else { 1002 | this.productions.set(symbol, newProduction[1]); 1003 | } 1004 | }; 1005 | 1006 | // set multiple productions from name:value Object 1007 | // TODO: ALLOW TUPLE/ARRAY 1008 | this.setProductions = function (newProductions) { 1009 | if (newProductions === undefined) throw new Error('no production specified.'); 1010 | this.clearProductions(); 1011 | 1012 | for (var _iterator2 = Object.entries(newProductions), _isArray2 = Array.isArray(_iterator2), _i2 = 0, _iterator2 = _isArray2 ? _iterator2 : _iterator2[Symbol.iterator]();;) { 1013 | var _ref4; 1014 | 1015 | if (_isArray2) { 1016 | if (_i2 >= _iterator2.length) break; 1017 | _ref4 = _iterator2[_i2++]; 1018 | } else { 1019 | _i2 = _iterator2.next(); 1020 | if (_i2.done) break; 1021 | _ref4 = _i2.value; 1022 | } 1023 | 1024 | var _ref3 = _ref4; 1025 | var from = _ref3[0]; 1026 | var to = _ref3[1]; 1027 | 1028 | this.setProduction(from, to, true); 1029 | } 1030 | }; 1031 | 1032 | this.clearProductions = function () { 1033 | this.productions = new Map(); 1034 | }; 1035 | 1036 | this.setFinal = function (symbol, final) { 1037 | var newFinal = [symbol, final]; 1038 | if (newFinal === undefined) { 1039 | throw new Error('no final specified.'); 1040 | } 1041 | this.finals.set(newFinal[0], newFinal[1]); 1042 | }; 1043 | 1044 | // set multiple finals from name:value Object 1045 | this.setFinals = function (newFinals) { 1046 | if (newFinals === undefined) throw new Error('no finals specified.'); 1047 | this.finals = new Map(); 1048 | for (var symbol in newFinals) { 1049 | if (newFinals.hasOwnProperty(symbol)) { 1050 | this.setFinal(symbol, newFinals[symbol]); 1051 | } 1052 | } 1053 | }; 1054 | 1055 | //var hasWeight = el => el.weight !== undefined; 1056 | this.getProductionResult = function (p, index, part, params) { 1057 | var recursive = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false; 1058 | 1059 | 1060 | var contextSensitive = p.leftCtx !== undefined || p.rightCtx !== undefined; 1061 | var conditional = p.condition !== undefined; 1062 | var stochastic = false; 1063 | var result = false; 1064 | var precheck = true; 1065 | 1066 | // Check if condition is true, only then continue to check left and right contexts 1067 | if (conditional && p.condition({ index, currentAxiom: this.axiom, part, params }) === false) { 1068 | precheck = false; 1069 | } else if (contextSensitive) { 1070 | if (p.leftCtx !== undefined && p.rightCtx !== undefined) { 1071 | 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; 1072 | } else if (p.leftCtx !== undefined) { 1073 | precheck = this.match({ direction: 'left', match: p.leftCtx, index: index, branchSymbols: '[]' }).result; 1074 | } else if (p.rightCtx !== undefined) { 1075 | precheck = this.match({ direction: 'right', match: p.rightCtx, index: index, branchSymbols: '[]' }).result; 1076 | } 1077 | } 1078 | 1079 | // If conditions and context don't allow product, keep result = false 1080 | if (precheck === false) { 1081 | result = false; 1082 | } 1083 | 1084 | // If p has multiple successors 1085 | else if (p.successors) { 1086 | // This could be stochastic successors or multiple functions 1087 | // Tread every element in the list as an individual production object 1088 | // For stochastic productions (if all prods in the list have a 'weight' property) 1089 | // Get a random number then pick a production from the list according to their weight 1090 | 1091 | var currentWeight, threshWeight; 1092 | if (p.isStochastic) { 1093 | threshWeight = Math.random() * p.weightSum; 1094 | currentWeight = 0; 1095 | } 1096 | /* 1097 | go through the list and use 1098 | the first valid production in that list. (that returns true) 1099 | This assumes, it's a list of functions. 1100 | No recursion here: no successors inside successors. 1101 | */ 1102 | for (var _iterator3 = p.successors, _isArray3 = Array.isArray(_iterator3), _i3 = 0, _iterator3 = _isArray3 ? _iterator3 : _iterator3[Symbol.iterator]();;) { 1103 | var _ref5; 1104 | 1105 | if (_isArray3) { 1106 | if (_i3 >= _iterator3.length) break; 1107 | _ref5 = _iterator3[_i3++]; 1108 | } else { 1109 | _i3 = _iterator3.next(); 1110 | if (_i3.done) break; 1111 | _ref5 = _i3.value; 1112 | } 1113 | 1114 | var _p = _ref5; 1115 | 1116 | if (p.isStochastic) { 1117 | currentWeight += _p.weight; 1118 | if (currentWeight < threshWeight) continue; 1119 | } 1120 | // If currentWeight >= thresWeight, a production is choosen stochastically 1121 | // and evaluated recursively because it , kax also have rightCtx, leftCtx and condition to further inhibit production. This is not standard L-System behaviour though! 1122 | 1123 | // last true is for recursiv call 1124 | // TODO: refactor getProductionResult to use an object 1125 | var _result = this.getProductionResult(_p, index, part, params, true); 1126 | // console.log(part, p.successors); 1127 | // console.log(result); 1128 | // console.log("\n"); 1129 | if (_result !== undefined && _result !== false) { 1130 | result = _result; 1131 | break; 1132 | } 1133 | } 1134 | } 1135 | // if successor is a function, execute function and append return value 1136 | else if (typeof p.successor === 'function') { 1137 | 1138 | result = p.successor({ index, currentAxiom: this.axiom, part, params }); 1139 | } else { 1140 | result = p.successor; 1141 | } 1142 | 1143 | if (!result) { 1144 | // Allow undefined or false results for recursive calls of this func 1145 | return recursive ? result : part; 1146 | } 1147 | return result; 1148 | }; 1149 | 1150 | this.applyProductions = function () { 1151 | // a axiom can be a string or an array of objects that contain the key/value 'symbol' 1152 | var newAxiom = typeof this.axiom === 'string' ? '' : []; 1153 | var index = 0; 1154 | 1155 | // iterate all symbols/characters of the axiom and lookup according productions 1156 | for (var _iterator4 = this.axiom, _isArray4 = Array.isArray(_iterator4), _i4 = 0, _iterator4 = _isArray4 ? _iterator4 : _iterator4[Symbol.iterator]();;) { 1157 | var _ref6; 1158 | 1159 | if (_isArray4) { 1160 | if (_i4 >= _iterator4.length) break; 1161 | _ref6 = _iterator4[_i4++]; 1162 | } else { 1163 | _i4 = _iterator4.next(); 1164 | if (_i4.done) break; 1165 | _ref6 = _i4.value; 1166 | } 1167 | 1168 | var part = _ref6; 1169 | 1170 | 1171 | // Stuff for classic parametric L-Systems: get actual symbol and possible parameters 1172 | // params will be given the production function, if applicable. 1173 | 1174 | var symbol = part.symbol || part; 1175 | var params = part.params || []; 1176 | 1177 | var result = part; 1178 | if (this.productions.has(symbol)) { 1179 | var p = this.productions.get(symbol); 1180 | result = this.getProductionResult(p, index, part, params); 1181 | } 1182 | 1183 | // Got result. Now add result to new axiom. 1184 | if (typeof newAxiom === 'string') { 1185 | newAxiom += result; 1186 | } else if (result instanceof Array) { 1187 | // If result is an array, merge result into new axiom instead of pushing. 1188 | Array.prototype.push.apply(newAxiom, result); 1189 | } else { 1190 | newAxiom.push(result); 1191 | } 1192 | index++; 1193 | } 1194 | 1195 | // finally set new axiom and also return it for convenience. 1196 | this.axiom = newAxiom; 1197 | return newAxiom; 1198 | }; 1199 | 1200 | this.iterate = function () { 1201 | var n = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1; 1202 | 1203 | this.iterations = n; 1204 | var lastIteration = void 0; 1205 | for (var iteration = 0; iteration < n; iteration++) { 1206 | lastIteration = this.applyProductions(); 1207 | } 1208 | return lastIteration; 1209 | }; 1210 | 1211 | this.final = function (externalArg) { 1212 | var index = 0; 1213 | for (var _iterator5 = this.axiom, _isArray5 = Array.isArray(_iterator5), _i5 = 0, _iterator5 = _isArray5 ? _iterator5 : _iterator5[Symbol.iterator]();;) { 1214 | var _ref7; 1215 | 1216 | if (_isArray5) { 1217 | if (_i5 >= _iterator5.length) break; 1218 | _ref7 = _iterator5[_i5++]; 1219 | } else { 1220 | _i5 = _iterator5.next(); 1221 | if (_i5.done) break; 1222 | _ref7 = _i5.value; 1223 | } 1224 | 1225 | var part = _ref7; 1226 | 1227 | 1228 | // if we have objects for each symbol, (when using parametric L-Systems) 1229 | // get actual identifiable symbol character 1230 | var symbol = part; 1231 | if (typeof part === 'object' && part.symbol) symbol = part.symbol; 1232 | 1233 | if (this.finals.has(symbol)) { 1234 | var finalFunction = this.finals.get(symbol); 1235 | var typeOfFinalFunction = typeof finalFunction; 1236 | if (typeOfFinalFunction !== 'function') { 1237 | throw Error('\'' + symbol + '\'' + ' has an object for a final function. But it is __not a function__ but a ' + typeOfFinalFunction + '!'); 1238 | } 1239 | // execute symbols function 1240 | // supply in first argument an details object with current index and part 1241 | // and in the first argument inject the external argument (like a render target) 1242 | finalFunction({ index, part }, externalArg); 1243 | } else { 1244 | // symbol has no final function 1245 | } 1246 | index++; 1247 | } 1248 | }; 1249 | 1250 | /* 1251 | how to use match(): 1252 | ----------------------- 1253 | It is mainly a helper function for context sensitive productions. 1254 | If you use the classic syntax, it will by default be automatically transformed to proper 1255 | JS-Syntax. 1256 | Howerver, you can use the match helper function in your on productions: 1257 | 1258 | index is the index of a production using `match` 1259 | eg. in a classic L-System 1260 | 1261 | LSYS = ABCDE 1262 | BDE -> 'Z' 1263 | 1264 | the index of the `BD -> 'Z'` production would be the index of C (which is 2) when the 1265 | production would perform match(). so (if not using the ClassicLSystem class) you'd construction your context-sensitive production from C to Z like so: 1266 | 1267 | LSYS.setProduction('C', (index, axiom) => { 1268 | (LSYS.match({index, match: 'B', direction: 'left'}) && 1269 | LSYS.match({index, match: 'DE', direction: 'right'}) ? 'Z' : 'C') 1270 | }) 1271 | 1272 | 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 1273 | */ 1274 | 1275 | this.match = function (_ref8) { 1276 | var axiom_ = _ref8.axiom_, 1277 | match = _ref8.match, 1278 | ignoredSymbols = _ref8.ignoredSymbols, 1279 | branchSymbols = _ref8.branchSymbols, 1280 | index = _ref8.index, 1281 | direction = _ref8.direction; 1282 | 1283 | 1284 | var branchCount = 0; 1285 | var explicitBranchCount = 0; 1286 | axiom_ = axiom_ || this.axiom; 1287 | if (branchSymbols === undefined) branchSymbols = this.branchSymbols !== undefined ? this.branchSymbols : []; 1288 | if (ignoredSymbols === undefined) ignoredSymbols = this.ignoredSymbols !== undefined ? this.ignoredSymbols : []; 1289 | var returnMatchIndices = []; 1290 | 1291 | var branchStart = void 0, 1292 | branchEnd = void 0, 1293 | axiomIndex = void 0, 1294 | loopIndexChange = void 0, 1295 | matchIndex = void 0, 1296 | matchIndexChange = void 0, 1297 | matchIndexOverflow = void 0; 1298 | // set some variables depending on the direction to match 1299 | 1300 | if (direction === 'right') { 1301 | loopIndexChange = matchIndexChange = +1; 1302 | axiomIndex = index + 1; 1303 | matchIndex = 0; 1304 | matchIndexOverflow = match.length; 1305 | if (branchSymbols.length > 0) { 1306 | 1307 | var _branchSymbols = branchSymbols; 1308 | branchStart = _branchSymbols[0]; 1309 | branchEnd = _branchSymbols[1]; 1310 | } 1311 | } else if (direction === 'left') { 1312 | loopIndexChange = matchIndexChange = -1; 1313 | axiomIndex = index - 1; 1314 | matchIndex = match.length - 1; 1315 | matchIndexOverflow = -1; 1316 | if (branchSymbols.length > 0) { 1317 | 1318 | var _branchSymbols2 = branchSymbols; 1319 | branchEnd = _branchSymbols2[0]; 1320 | branchStart = _branchSymbols2[1]; 1321 | } 1322 | } else { 1323 | throw Error(direction, 'is not a valid direction for matching.'); 1324 | } 1325 | 1326 | for (; axiomIndex < axiom_.length && axiomIndex >= 0; axiomIndex += loopIndexChange) { 1327 | 1328 | var axiomSymbol = axiom_[axiomIndex].symbol || axiom_[axiomIndex]; 1329 | var matchSymbol = match[matchIndex]; 1330 | 1331 | // compare current symbol of axiom with current symbol of match 1332 | if (axiomSymbol === matchSymbol) { 1333 | 1334 | if (branchCount === 0 || explicitBranchCount > 0) { 1335 | // if its a match and previously NOT inside branch (branchCount===0) or in explicitly wanted branch (explicitBranchCount > 0) 1336 | 1337 | // if a bracket was explicitly stated in match axiom 1338 | if (axiomSymbol === branchStart) { 1339 | explicitBranchCount++; 1340 | branchCount++; 1341 | matchIndex += matchIndexChange; 1342 | } else if (axiomSymbol === branchEnd) { 1343 | explicitBranchCount = Math.max(0, explicitBranchCount - 1); 1344 | branchCount = Math.max(0, branchCount - 1); 1345 | // only increase match if we are out of explicit branch 1346 | 1347 | if (explicitBranchCount === 0) { 1348 | 1349 | matchIndex += matchIndexChange; 1350 | } 1351 | } else { 1352 | returnMatchIndices.push(axiomIndex); 1353 | matchIndex += matchIndexChange; 1354 | } 1355 | } 1356 | 1357 | // overflowing matchIndices (matchIndex + 1 for right match, matchIndexEnd for left match )? 1358 | // -> no more matches to do. return with true, as everything matched until here 1359 | // *yay* 1360 | if (matchIndex === matchIndexOverflow) { 1361 | return { result: true, matchIndices: returnMatchIndices }; 1362 | } 1363 | } else if (axiomSymbol === branchStart) { 1364 | branchCount++; 1365 | if (explicitBranchCount > 0) explicitBranchCount++; 1366 | } else if (axiomSymbol === branchEnd) { 1367 | branchCount = Math.max(0, branchCount - 1); 1368 | if (explicitBranchCount > 0) explicitBranchCount = Math.max(0, explicitBranchCount - 1); 1369 | } else if ((branchCount === 0 || explicitBranchCount > 0 && matchSymbol !== branchEnd) && ignoredSymbols.includes(axiomSymbol) === false) { 1370 | // not in branchSymbols/branch? or if in explicit branch, and not at the very end of 1371 | // condition (at the ]), and symbol not in ignoredSymbols ? then false 1372 | return { result: false, matchIndices: returnMatchIndices }; 1373 | } 1374 | } 1375 | 1376 | return { result: false, matchIndices: returnMatchIndices }; 1377 | }; 1378 | 1379 | this.ignoredSymbols = ignoredSymbols; 1380 | this.debug = debug; 1381 | this.branchSymbols = branchSymbols; 1382 | this.allowClassicSyntax = allowClassicSyntax; 1383 | this.classicParametricSyntax = classicParametricSyntax; 1384 | this.forceObjects = forceObjects; 1385 | 1386 | this.setAxiom(axiom); 1387 | 1388 | this.clearProductions(); 1389 | if (productions) this.setProductions(productions); 1390 | if (finals) this.setFinals(finals); 1391 | 1392 | return this; 1393 | } 1394 | 1395 | // Set classic syntax helpers to library scope to be used outside of library context 1396 | // for users eg. 1397 | LSystem.transformClassicStochasticProductions = transformClassicStochasticProductions; 1398 | LSystem.transformClassicCSProduction = transformClassicCSProduction; 1399 | LSystem.transformClassicParametricAxiom = transformClassicParametricAxiom; 1400 | LSystem.testClassicParametricSyntax = testClassicParametricSyntax; 1401 | 1402 | /* harmony default export */ __webpack_exports__["a"] = (LSystem); 1403 | 1404 | 1405 | /***/ }), 1406 | /* 2 */ 1407 | /***/ (function(module, exports, __webpack_require__) { 1408 | 1409 | module.exports = function() { 1410 | 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[moduleId].exports;\n/******/ \t\t}\n/******/ \t\t// Create a new module (and put it into the cache)\n/******/ \t\tvar module = installedModules[moduleId] = {\n/******/ \t\t\ti: moduleId,\n/******/ \t\t\tl: false,\n/******/ \t\t\texports: {}\n/******/ \t\t};\n/******/\n/******/ \t\t// Execute the module function\n/******/ \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n/******/\n/******/ \t\t// Flag the module as loaded\n/******/ \t\tmodule.l = true;\n/******/\n/******/ \t\t// Return the exports of the module\n/******/ \t\treturn module.exports;\n/******/ \t}\n/******/\n/******/\n/******/ \t// expose the modules object (__webpack_modules__)\n/******/ \t__webpack_require__.m = modules;\n/******/\n/******/ \t// expose the module cache\n/******/ \t__webpack_require__.c = installedModules;\n/******/\n/******/ \t// define getter function for harmony exports\n/******/ \t__webpack_require__.d = function(exports, name, getter) {\n/******/ \t\tif(!__webpack_require__.o(exports, name)) {\n/******/ \t\t\tObject.defineProperty(exports, name, {\n/******/ \t\t\t\tconfigurable: false,\n/******/ \t\t\t\tenumerable: true,\n/******/ \t\t\t\tget: getter\n/******/ \t\t\t});\n/******/ \t\t}\n/******/ \t};\n/******/\n/******/ \t// getDefaultExport function for compatibility with non-harmony modules\n/******/ \t__webpack_require__.n = function(module) {\n/******/ \t\tvar getter = module && module.__esModule ?\n/******/ \t\t\tfunction getDefault() { return module['default']; } :\n/******/ \t\t\tfunction getModuleExports() { return module; };\n/******/ \t\t__webpack_require__.d(getter, 'a', getter);\n/******/ \t\treturn getter;\n/******/ \t};\n/******/\n/******/ \t// Object.prototype.hasOwnProperty.call\n/******/ \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n/******/\n/******/ \t// __webpack_public_path__\n/******/ \t__webpack_require__.p = \"\";\n/******/\n/******/ \t// Load entry module and return exports\n/******/ \treturn __webpack_require__(__webpack_require__.s = 0);\n/******/ })\n/************************************************************************/\n/******/ ([\n/* 0 */\n/***/ (function(module, __webpack_exports__, __webpack_require__) {\n\n\"use strict\";\nObject.defineProperty(__webpack_exports__, \"__esModule\", { value: true });\n/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0_lindenmayer__ = __webpack_require__(1);\n// Require instead of importScripts because we use webpack\n// with worker-loader for compiling source: https://github.com/webpack/worker-loader\n\nlet lsystem = new __WEBPACK_IMPORTED_MODULE_0_lindenmayer__[\"a\" /* default */]({});\nlet timeout = {};\n\nonmessage = function onmessage(e) {\n // wait a few ms to start thread, to be able to cancel old tasks\n clearTimeout(timeout);\n timeout = setTimeout(function () {\n\n lsystem.setAxiom(e.data.axiom);\n\n lsystem.clearProductions();\n var _iteratorNormalCompletion = true;\n var _didIteratorError = false;\n var _iteratorError = undefined;\n\n try {\n for (var _iterator = e.data.productions[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {\n let p = _step.value;\n\n lsystem.setProduction(p[0], p[1]);\n }\n } catch (err) {\n _didIteratorError = true;\n _iteratorError = err;\n } finally {\n try {\n if (!_iteratorNormalCompletion && _iterator.return) {\n _iterator.return();\n }\n } finally {\n if (_didIteratorError) {\n throw _iteratorError;\n }\n }\n }\n\n lsystem.iterate(e.data.iterations);\n\n postMessage({\n result: lsystem.getString(),\n initial: e.data\n });\n }, 20);\n};\n\n/***/ }),\n/* 1 */\n/***/ (function(module, __webpack_exports__, __webpack_require__) {\n\n\"use strict\";\n// Get a list of productions that have identical initiators,\n// Output a single stochastic production. Probability per production\n// is defined by amount of input productions (4 => 25% each, 2 => 50% etc.)\n\n\n// These transformers get a classic ABOP snytax as input and return a standardized\n// production object in the form of ['F',\n// {\n// successor:String/Iterable\n// [alternatively]stochasticSuccessors: Iterable of standardized objects with mandatory weight fields,\n// leftCtx: iterable/string,\n// rightCtx: Iterable/String,\n// condition: Function }]\n\nfunction transformClassicStochasticProductions(productions) {\n\n return function transformedProduction() {\n var resultList = productions; // the parser for productions shall create this list\n var count = resultList.length;\n\n var r = Math.random();\n for (var i = 0; i < count; i++) {\n var range = (i + 1) / count;\n if (r <= range) return resultList[i];\n }\n\n console.error('Should have returned a result of the list, something is wrong here with the random numbers?.');\n };\n}\n\n// TODO: implement it!\n\n\n// TODO: Scaffold classic parametric and context sensitive stuff out of main file\n// And simply require it here, eg:\n// this.testClassicParametricSyntax = require(classicSyntax.testParametric)??\nfunction testClassicParametricSyntax(axiom) {\n return (/\\(.+\\)/.test(axiom)\n );\n}\n\n// transforms things like 'A(1,2,5)B(2.5)' to\n// [ {symbol: 'A', params: [1,2,5]}, {symbol: 'B', params:[25]} ]\n// strips spaces\nfunction transformClassicParametricAxiom(axiom) {\n\n // Replace whitespaces, then split between square brackets.\n var splitAxiom = axiom.replace(/\\s+/g, '').split(/[\\(\\)]/);\n // console.log('parts:', splitAxiom)\n var newAxiom = [];\n // Construct new axiom by getting the params and symbol.\n for (var i = 0; i < splitAxiom.length - 1; i += 2) {\n var params = splitAxiom[i + 1].split(',').map(Number);\n newAxiom.push({ symbol: splitAxiom[i], params: params });\n }\n // console.log('parsed axiom:', newAxiom)\n}\n\nfunction transformClassicCSProduction(p) {\n\n // before continuing, check if classic syntax actually there\n // example: p = ['AC', 'Z']\n\n // left should be ['A', 'B']\n var left = p[0].match(/(.+)<(.)/);\n\n // right should be ['B', 'C']\n var right = p[0].match(/(.)>(.+)/);\n\n // Not a CS-Production (no '<' or '>'),\n //return original production.\n if (left === null && right === null) {\n return p;\n }\n\n var predecessor = void 0;\n // create new production object _or_ use the one set by the user\n var productionObject = p[1].successor || p[1].successors ? p[1] : { successor: p[1] };\n if (left !== null) {\n predecessor = left[2];\n productionObject.leftCtx = left[1];\n }\n if (right !== null) {\n predecessor = right[1];\n productionObject.rightCtx = right[2];\n }\n\n return [predecessor, productionObject];\n}\n\nfunction stringToObjects(string) {\n if (typeof string !== 'string' && string instanceof String === false) return string;\n var transformed = [];\n for (var _iterator = string, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) {\n var _ref;\n\n if (_isArray) {\n if (_i >= _iterator.length) break;\n _ref = _iterator[_i++];\n } else {\n _i = _iterator.next();\n if (_i.done) break;\n _ref = _i.value;\n }\n\n var symbol = _ref;\n transformed.push({ symbol });\n }return transformed;\n}\n\n// TODO: continue here\n\n\n// transform p to {successor: p}\n// if applicable also transform strings into array of {symbol: String} objects\n// TODO: make more modular! dont have forceObjects in here\nfunction normalizeProductionRightSide(p, forceObjects) {\n\n if (p.hasOwnProperty('successors')) {\n for (var i = 0; i < p.successors.length; i++) {\n p.successors[i] = normalizeProductionRightSide(p.successors[i], forceObjects);\n }\n } else if (p.hasOwnProperty('successor') === false) {\n p = { successor: p };\n }\n\n if (forceObjects && p.hasOwnProperty('successor')) {\n p.successor = stringToObjects(p.successor);\n }\n\n return p;\n}\n\nfunction normalizeProduction(p, forceObjects) {\n\n p[1] = normalizeProductionRightSide(p[1], forceObjects);\n return p;\n}\n\nfunction LSystem(_ref) {\n\tvar _ref$axiom = _ref.axiom,\n\t axiom = _ref$axiom === undefined ? '' : _ref$axiom,\n\t productions = _ref.productions,\n\t finals = _ref.finals,\n\t _ref$branchSymbols = _ref.branchSymbols,\n\t branchSymbols = _ref$branchSymbols === undefined ? '' : _ref$branchSymbols,\n\t _ref$ignoredSymbols = _ref.ignoredSymbols,\n\t ignoredSymbols = _ref$ignoredSymbols === undefined ? '' : _ref$ignoredSymbols,\n\t _ref$allowClassicSynt = _ref.allowClassicSyntax,\n\t allowClassicSyntax = _ref$allowClassicSynt === undefined ? true : _ref$allowClassicSynt,\n\t _ref$classicParametri = _ref.classicParametricSyntax,\n\t classicParametricSyntax = _ref$classicParametri === undefined ? false : _ref$classicParametri,\n\t _ref$forceObjects = _ref.forceObjects,\n\t forceObjects = _ref$forceObjects === undefined ? false : _ref$forceObjects,\n\t _ref$debug = _ref.debug,\n\t debug = _ref$debug === undefined ? false : _ref$debug;\n\n\n\t// TODO: forceObject to be more intelligent based on other productions??\n\n\tthis.setAxiom = function (axiom) {\n\t\tthis.axiom = this.forceObjects ? stringToObjects(axiom) : axiom;\n\t};\n\n\tthis.getRaw = function () {\n\t\treturn this.axiom;\n\t};\n\n\t// if using objects in axioms, as used in parametric L-Systems\n\tthis.getString = function () {\n\t\tvar onlySymbols = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;\n\n\t\tif (typeof this.axiom === 'string') return this.axiom;\n\t\tif (onlySymbols === true) {\n\t\t\treturn this.axiom.reduce((prev, current) => {\n\t\t\t\tif (current.symbol === undefined) {\n\t\t\t\t\tconsole.log('found:', current);\n\t\t\t\t\tthrow 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.');\n\t\t\t\t}\n\t\t\t\treturn prev + current.symbol;\n\t\t\t}, '');\n\t\t} else {\n\t\t\treturn JSON.stringify(this.axiom);\n\t\t}\n\t};\n\n\tthis.getStringResult = this.getString;\n\n\tthis.setProduction = function (from, to) {\n\t\tvar allowAppendingMultiSuccessors = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;\n\n\t\tvar newProduction = [from, to];\n\t\tif (newProduction === undefined) throw new Error('no production specified.');\n\n\t\tif (to.successor && to.successors) {\n\t\t\tthrow new Error('You can not have both a \"successor\" and a \"successors\" field in your production!');\n\t\t}\n\n\t\t// Apply production transformers and normalizations\n\t\tif (this.allowClassicSyntax === true) {\n\t\t\tnewProduction = transformClassicCSProduction(newProduction, this.ignoredSymbols);\n\t\t}\n\n\t\tnewProduction = normalizeProduction(newProduction, this.forceObjects);\n\n\t\t// check wether production is stochastic\n\t\tnewProduction[1].isStochastic = newProduction[1].successors !== undefined && newProduction[1].successors.every(successor => successor.weight !== undefined);\n\n\t\tif (newProduction[1].isStochastic) {\n\t\t\t// calculate weight sum\n\t\t\tnewProduction[1].weightSum = 0;\n\t\t\tfor (var _iterator = newProduction[1].successors, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) {\n\t\t\t\tvar _ref2;\n\n\t\t\t\tif (_isArray) {\n\t\t\t\t\tif (_i >= _iterator.length) break;\n\t\t\t\t\t_ref2 = _iterator[_i++];\n\t\t\t\t} else {\n\t\t\t\t\t_i = _iterator.next();\n\t\t\t\t\tif (_i.done) break;\n\t\t\t\t\t_ref2 = _i.value;\n\t\t\t\t}\n\n\t\t\t\tvar s = _ref2;\n\n\t\t\t\tnewProduction[1].weightSum += s.weight;\n\t\t\t}\n\t\t}\n\n\t\tvar symbol = newProduction[0];\n\t\tif (allowAppendingMultiSuccessors === true && this.productions.has(symbol)) {\n\n\t\t\tvar existingProduction = this.productions.get(symbol);\n\t\t\tvar singleSuccessor = existingProduction.successor;\n\t\t\tvar multiSuccessors = existingProduction.successors;\n\n\t\t\tif (singleSuccessor && !multiSuccessors) {\n\t\t\t\t// replace existing prod with new obj and add previous successor as first elem\n\t\t\t\t// to new successors field.\n\t\t\t\texistingProduction = { successors: [existingProduction] };\n\t\t\t}\n\t\t\texistingProduction.successors.push(newProduction[1]);\n\t\t\tthis.productions.set(symbol, existingProduction);\n\t\t} else {\n\t\t\tthis.productions.set(symbol, newProduction[1]);\n\t\t}\n\t};\n\n\t// set multiple productions from name:value Object\n\t// TODO: ALLOW TUPLE/ARRAY\n\tthis.setProductions = function (newProductions) {\n\t\tif (newProductions === undefined) throw new Error('no production specified.');\n\t\tthis.clearProductions();\n\n\t\tfor (var _iterator2 = Object.entries(newProductions), _isArray2 = Array.isArray(_iterator2), _i2 = 0, _iterator2 = _isArray2 ? _iterator2 : _iterator2[Symbol.iterator]();;) {\n\t\t\tvar _ref4;\n\n\t\t\tif (_isArray2) {\n\t\t\t\tif (_i2 >= _iterator2.length) break;\n\t\t\t\t_ref4 = _iterator2[_i2++];\n\t\t\t} else {\n\t\t\t\t_i2 = _iterator2.next();\n\t\t\t\tif (_i2.done) break;\n\t\t\t\t_ref4 = _i2.value;\n\t\t\t}\n\n\t\t\tvar _ref3 = _ref4;\n\t\t\tvar from = _ref3[0];\n\t\t\tvar to = _ref3[1];\n\n\t\t\tthis.setProduction(from, to, true);\n\t\t}\n\t};\n\n\tthis.clearProductions = function () {\n\t\tthis.productions = new Map();\n\t};\n\n\tthis.setFinal = function (symbol, final) {\n\t\tvar newFinal = [symbol, final];\n\t\tif (newFinal === undefined) {\n\t\t\tthrow new Error('no final specified.');\n\t\t}\n\t\tthis.finals.set(newFinal[0], newFinal[1]);\n\t};\n\n\t// set multiple finals from name:value Object\n\tthis.setFinals = function (newFinals) {\n\t\tif (newFinals === undefined) throw new Error('no finals specified.');\n\t\tthis.finals = new Map();\n\t\tfor (var symbol in newFinals) {\n\t\t\tif (newFinals.hasOwnProperty(symbol)) {\n\t\t\t\tthis.setFinal(symbol, newFinals[symbol]);\n\t\t\t}\n\t\t}\n\t};\n\n\t//var hasWeight = el => el.weight !== undefined;\n\tthis.getProductionResult = function (p, index, part, params) {\n\t\tvar recursive = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;\n\n\n\t\tvar contextSensitive = p.leftCtx !== undefined || p.rightCtx !== undefined;\n\t\tvar conditional = p.condition !== undefined;\n\t\tvar stochastic = false;\n\t\tvar result = false;\n\t\tvar precheck = true;\n\n\t\t// Check if condition is true, only then continue to check left and right contexts\n\t\tif (conditional && p.condition({ index, currentAxiom: this.axiom, part, params }) === false) {\n\t\t\tprecheck = false;\n\t\t} else if (contextSensitive) {\n\t\t\tif (p.leftCtx !== undefined && p.rightCtx !== undefined) {\n\t\t\t\tprecheck = this.match({ direction: 'left', match: p.leftCtx, index: index, branchSymbols: '[]' }).result && this.match({ direction: 'right', match: p.rightCtx, index: index, branchSymbols: '[]', ignoredSymbols: ignoredSymbols }).result;\n\t\t\t} else if (p.leftCtx !== undefined) {\n\t\t\t\tprecheck = this.match({ direction: 'left', match: p.leftCtx, index: index, branchSymbols: '[]' }).result;\n\t\t\t} else if (p.rightCtx !== undefined) {\n\t\t\t\tprecheck = this.match({ direction: 'right', match: p.rightCtx, index: index, branchSymbols: '[]' }).result;\n\t\t\t}\n\t\t}\n\n\t\t// If conditions and context don't allow product, keep result = false\n\t\tif (precheck === false) {\n\t\t\tresult = false;\n\t\t}\n\n\t\t// If p has multiple successors\n\t\telse if (p.successors) {\n\t\t\t\t// This could be stochastic successors or multiple functions\n\t\t\t\t// Tread every element in the list as an individual production object\n\t\t\t\t// For stochastic productions (if all prods in the list have a 'weight' property)\n\t\t\t\t// Get a random number then pick a production from the list according to their weight\n\n\t\t\t\tvar currentWeight, threshWeight;\n\t\t\t\tif (p.isStochastic) {\n\t\t\t\t\tthreshWeight = Math.random() * p.weightSum;\n\t\t\t\t\tcurrentWeight = 0;\n\t\t\t\t}\n\t\t\t\t/*\n go through the list and use\n the first valid production in that list. (that returns true)\n This assumes, it's a list of functions.\n No recursion here: no successors inside successors.\n */\n\t\t\t\tfor (var _iterator3 = p.successors, _isArray3 = Array.isArray(_iterator3), _i3 = 0, _iterator3 = _isArray3 ? _iterator3 : _iterator3[Symbol.iterator]();;) {\n\t\t\t\t\tvar _ref5;\n\n\t\t\t\t\tif (_isArray3) {\n\t\t\t\t\t\tif (_i3 >= _iterator3.length) break;\n\t\t\t\t\t\t_ref5 = _iterator3[_i3++];\n\t\t\t\t\t} else {\n\t\t\t\t\t\t_i3 = _iterator3.next();\n\t\t\t\t\t\tif (_i3.done) break;\n\t\t\t\t\t\t_ref5 = _i3.value;\n\t\t\t\t\t}\n\n\t\t\t\t\tvar _p = _ref5;\n\n\t\t\t\t\tif (p.isStochastic) {\n\t\t\t\t\t\tcurrentWeight += _p.weight;\n\t\t\t\t\t\tif (currentWeight < threshWeight) continue;\n\t\t\t\t\t}\n\t\t\t\t\t// If currentWeight >= thresWeight, a production is choosen stochastically\n\t\t\t\t\t// and evaluated recursively because it , kax also have rightCtx, leftCtx and condition to further inhibit production. This is not standard L-System behaviour though!\n\n\t\t\t\t\t// last true is for recursiv call\n\t\t\t\t\t// TODO: refactor getProductionResult to use an object\n\t\t\t\t\tvar _result = this.getProductionResult(_p, index, part, params, true);\n\t\t\t\t\t// console.log(part, p.successors);\n\t\t\t\t\t// console.log(result);\n\t\t\t\t\t// console.log(\"\\n\");\n\t\t\t\t\tif (_result !== undefined && _result !== false) {\n\t\t\t\t\t\tresult = _result;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t// if successor is a function, execute function and append return value\n\t\t\telse if (typeof p.successor === 'function') {\n\n\t\t\t\t\tresult = p.successor({ index, currentAxiom: this.axiom, part, params });\n\t\t\t\t} else {\n\t\t\t\t\tresult = p.successor;\n\t\t\t\t}\n\n\t\tif (!result) {\n\t\t\t// Allow undefined or false results for recursive calls of this func\n\t\t\treturn recursive ? result : part;\n\t\t}\n\t\treturn result;\n\t};\n\n\tthis.applyProductions = function () {\n\t\t// a axiom can be a string or an array of objects that contain the key/value 'symbol'\n\t\tvar newAxiom = typeof this.axiom === 'string' ? '' : [];\n\t\tvar index = 0;\n\n\t\t// iterate all symbols/characters of the axiom and lookup according productions\n\t\tfor (var _iterator4 = this.axiom, _isArray4 = Array.isArray(_iterator4), _i4 = 0, _iterator4 = _isArray4 ? _iterator4 : _iterator4[Symbol.iterator]();;) {\n\t\t\tvar _ref6;\n\n\t\t\tif (_isArray4) {\n\t\t\t\tif (_i4 >= _iterator4.length) break;\n\t\t\t\t_ref6 = _iterator4[_i4++];\n\t\t\t} else {\n\t\t\t\t_i4 = _iterator4.next();\n\t\t\t\tif (_i4.done) break;\n\t\t\t\t_ref6 = _i4.value;\n\t\t\t}\n\n\t\t\tvar part = _ref6;\n\n\n\t\t\t// Stuff for classic parametric L-Systems: get actual symbol and possible parameters\n\t\t\t// params will be given the production function, if applicable.\n\n\t\t\tvar symbol = part.symbol || part;\n\t\t\tvar params = part.params || [];\n\n\t\t\tvar result = part;\n\t\t\tif (this.productions.has(symbol)) {\n\t\t\t\tvar p = this.productions.get(symbol);\n\t\t\t\tresult = this.getProductionResult(p, index, part, params);\n\t\t\t}\n\n\t\t\t// Got result. Now add result to new axiom.\n\t\t\tif (typeof newAxiom === 'string') {\n\t\t\t\tnewAxiom += result;\n\t\t\t} else if (result instanceof Array) {\n\t\t\t\t// If result is an array, merge result into new axiom instead of pushing.\n\t\t\t\tArray.prototype.push.apply(newAxiom, result);\n\t\t\t} else {\n\t\t\t\tnewAxiom.push(result);\n\t\t\t}\n\t\t\tindex++;\n\t\t}\n\n\t\t// finally set new axiom and also return it for convenience.\n\t\tthis.axiom = newAxiom;\n\t\treturn newAxiom;\n\t};\n\n\tthis.iterate = function () {\n\t\tvar n = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;\n\n\t\tthis.iterations = n;\n\t\tvar lastIteration = void 0;\n\t\tfor (var iteration = 0; iteration < n; iteration++) {\n\t\t\tlastIteration = this.applyProductions();\n\t\t}\n\t\treturn lastIteration;\n\t};\n\n\tthis.final = function (externalArg) {\n\t\tvar index = 0;\n\t\tfor (var _iterator5 = this.axiom, _isArray5 = Array.isArray(_iterator5), _i5 = 0, _iterator5 = _isArray5 ? _iterator5 : _iterator5[Symbol.iterator]();;) {\n\t\t\tvar _ref7;\n\n\t\t\tif (_isArray5) {\n\t\t\t\tif (_i5 >= _iterator5.length) break;\n\t\t\t\t_ref7 = _iterator5[_i5++];\n\t\t\t} else {\n\t\t\t\t_i5 = _iterator5.next();\n\t\t\t\tif (_i5.done) break;\n\t\t\t\t_ref7 = _i5.value;\n\t\t\t}\n\n\t\t\tvar part = _ref7;\n\n\n\t\t\t// if we have objects for each symbol, (when using parametric L-Systems)\n\t\t\t// get actual identifiable symbol character\n\t\t\tvar symbol = part;\n\t\t\tif (typeof part === 'object' && part.symbol) symbol = part.symbol;\n\n\t\t\tif (this.finals.has(symbol)) {\n\t\t\t\tvar finalFunction = this.finals.get(symbol);\n\t\t\t\tvar typeOfFinalFunction = typeof finalFunction;\n\t\t\t\tif (typeOfFinalFunction !== 'function') {\n\t\t\t\t\tthrow Error('\\'' + symbol + '\\'' + ' has an object for a final function. But it is __not a function__ but a ' + typeOfFinalFunction + '!');\n\t\t\t\t}\n\t\t\t\t// execute symbols function\n\t\t\t\t// supply in first argument an details object with current index and part\n\t\t\t\t// and in the first argument inject the external argument (like a render target)\n\t\t\t\tfinalFunction({ index, part }, externalArg);\n\t\t\t} else {\n\t\t\t\t// symbol has no final function\n\t\t\t}\n\t\t\tindex++;\n\t\t}\n\t};\n\n\t/*\n \thow to use match():\n \t-----------------------\n \tIt is mainly a helper function for context sensitive productions.\n \tIf you use the classic syntax, it will by default be automatically transformed to proper\n \tJS-Syntax.\n \tHowerver, you can use the match helper function in your on productions:\n \n \tindex is the index of a production using `match`\n \teg. in a classic L-System\n \n \tLSYS = ABCDE\n \tBDE -> 'Z'\n \n \tthe index of the `BD -> 'Z'` production would be the index of C (which is 2) when the\n \tproduction would perform match(). so (if not using the ClassicLSystem class) you'd construction your context-sensitive production from C to Z like so:\n \n \tLSYS.setProduction('C', (index, axiom) => {\n \t\t(LSYS.match({index, match: 'B', direction: 'left'}) &&\n \t\t LSYS.match({index, match: 'DE', direction: 'right'}) ? 'Z' : 'C')\n \t})\n \n \tYou 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\n \t*/\n\n\tthis.match = function (_ref8) {\n\t\tvar axiom_ = _ref8.axiom_,\n\t\t match = _ref8.match,\n\t\t ignoredSymbols = _ref8.ignoredSymbols,\n\t\t branchSymbols = _ref8.branchSymbols,\n\t\t index = _ref8.index,\n\t\t direction = _ref8.direction;\n\n\n\t\tvar branchCount = 0;\n\t\tvar explicitBranchCount = 0;\n\t\taxiom_ = axiom_ || this.axiom;\n\t\tif (branchSymbols === undefined) branchSymbols = this.branchSymbols !== undefined ? this.branchSymbols : [];\n\t\tif (ignoredSymbols === undefined) ignoredSymbols = this.ignoredSymbols !== undefined ? this.ignoredSymbols : [];\n\t\tvar returnMatchIndices = [];\n\n\t\tvar branchStart = void 0,\n\t\t branchEnd = void 0,\n\t\t axiomIndex = void 0,\n\t\t loopIndexChange = void 0,\n\t\t matchIndex = void 0,\n\t\t matchIndexChange = void 0,\n\t\t matchIndexOverflow = void 0;\n\t\t// set some variables depending on the direction to match\n\n\t\tif (direction === 'right') {\n\t\t\tloopIndexChange = matchIndexChange = +1;\n\t\t\taxiomIndex = index + 1;\n\t\t\tmatchIndex = 0;\n\t\t\tmatchIndexOverflow = match.length;\n\t\t\tif (branchSymbols.length > 0) {\n\t\t\t\t\n\t\t\t\tvar _branchSymbols = branchSymbols;\n\t\t\t\tbranchStart = _branchSymbols[0];\n\t\t\t\tbranchEnd = _branchSymbols[1];\n\t\t\t}\n\t\t} else if (direction === 'left') {\n\t\t\tloopIndexChange = matchIndexChange = -1;\n\t\t\taxiomIndex = index - 1;\n\t\t\tmatchIndex = match.length - 1;\n\t\t\tmatchIndexOverflow = -1;\n\t\t\tif (branchSymbols.length > 0) {\n\t\t\t\t\n\t\t\t\tvar _branchSymbols2 = branchSymbols;\n\t\t\t\tbranchEnd = _branchSymbols2[0];\n\t\t\t\tbranchStart = _branchSymbols2[1];\n\t\t\t}\n\t\t} else {\n\t\t\tthrow Error(direction, 'is not a valid direction for matching.');\n\t\t}\n\n\t\tfor (; axiomIndex < axiom_.length && axiomIndex >= 0; axiomIndex += loopIndexChange) {\n\n\t\t\tvar axiomSymbol = axiom_[axiomIndex].symbol || axiom_[axiomIndex];\n\t\t\tvar matchSymbol = match[matchIndex];\n\n\t\t\t// compare current symbol of axiom with current symbol of match\n\t\t\tif (axiomSymbol === matchSymbol) {\n\n\t\t\t\tif (branchCount === 0 || explicitBranchCount > 0) {\n\t\t\t\t\t// if its a match and previously NOT inside branch (branchCount===0) or in explicitly wanted branch (explicitBranchCount > 0)\n\n\t\t\t\t\t// if a bracket was explicitly stated in match axiom\n\t\t\t\t\tif (axiomSymbol === branchStart) {\n\t\t\t\t\t\texplicitBranchCount++;\n\t\t\t\t\t\tbranchCount++;\n\t\t\t\t\t\tmatchIndex += matchIndexChange;\n\t\t\t\t\t} else if (axiomSymbol === branchEnd) {\n\t\t\t\t\t\texplicitBranchCount = Math.max(0, explicitBranchCount - 1);\n\t\t\t\t\t\tbranchCount = Math.max(0, branchCount - 1);\n\t\t\t\t\t\t// only increase match if we are out of explicit branch\n\n\t\t\t\t\t\tif (explicitBranchCount === 0) {\n\n\t\t\t\t\t\t\tmatchIndex += matchIndexChange;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturnMatchIndices.push(axiomIndex);\n\t\t\t\t\t\tmatchIndex += matchIndexChange;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// overflowing matchIndices (matchIndex + 1 for right match, matchIndexEnd for left match )?\n\t\t\t\t// -> no more matches to do. return with true, as everything matched until here\n\t\t\t\t// *yay*\n\t\t\t\tif (matchIndex === matchIndexOverflow) {\n\t\t\t\t\treturn { result: true, matchIndices: returnMatchIndices };\n\t\t\t\t}\n\t\t\t} else if (axiomSymbol === branchStart) {\n\t\t\t\tbranchCount++;\n\t\t\t\tif (explicitBranchCount > 0) explicitBranchCount++;\n\t\t\t} else if (axiomSymbol === branchEnd) {\n\t\t\t\tbranchCount = Math.max(0, branchCount - 1);\n\t\t\t\tif (explicitBranchCount > 0) explicitBranchCount = Math.max(0, explicitBranchCount - 1);\n\t\t\t} else if ((branchCount === 0 || explicitBranchCount > 0 && matchSymbol !== branchEnd) && ignoredSymbols.includes(axiomSymbol) === false) {\n\t\t\t\t// not in branchSymbols/branch? or if in explicit branch, and not at the very end of\n\t\t\t\t// condition (at the ]), and symbol not in ignoredSymbols ? then false\n\t\t\t\treturn { result: false, matchIndices: returnMatchIndices };\n\t\t\t}\n\t\t}\n\n\t\treturn { result: false, matchIndices: returnMatchIndices };\n\t};\n\n\tthis.ignoredSymbols = ignoredSymbols;\n\tthis.debug = debug;\n\tthis.branchSymbols = branchSymbols;\n\tthis.allowClassicSyntax = allowClassicSyntax;\n\tthis.classicParametricSyntax = classicParametricSyntax;\n\tthis.forceObjects = forceObjects;\n\n\tthis.setAxiom(axiom);\n\n\tthis.clearProductions();\n\tif (productions) this.setProductions(productions);\n\tif (finals) this.setFinals(finals);\n\n\treturn this;\n}\n\n// Set classic syntax helpers to library scope to be used outside of library context\n// for users eg.\nLSystem.transformClassicStochasticProductions = transformClassicStochasticProductions;\nLSystem.transformClassicCSProduction = transformClassicCSProduction;\nLSystem.transformClassicParametricAxiom = transformClassicParametricAxiom;\nLSystem.testClassicParametricSyntax = testClassicParametricSyntax;\n\n/* harmony default export */ __webpack_exports__[\"a\"] = (LSystem);\n\n\n/***/ })\n/******/ ]);", null); 1411 | }; 1412 | 1413 | /***/ }), 1414 | /* 3 */ 1415 | /***/ (function(module, exports) { 1416 | 1417 | // http://stackoverflow.com/questions/10343913/how-to-create-a-web-worker-from-a-string 1418 | 1419 | var URL = window.URL || window.webkitURL; 1420 | module.exports = function(content, url) { 1421 | try { 1422 | try { 1423 | var blob; 1424 | try { // BlobBuilder = Deprecated, but widely implemented 1425 | var BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder; 1426 | blob = new BlobBuilder(); 1427 | blob.append(content); 1428 | blob = blob.getBlob(); 1429 | } catch(e) { // The proposed API 1430 | blob = new Blob([content]); 1431 | } 1432 | return new Worker(URL.createObjectURL(blob)); 1433 | } catch(e) { 1434 | return new Worker('data:application/javascript,' + encodeURIComponent(content)); 1435 | } 1436 | } catch(e) { 1437 | if (!url) { 1438 | throw Error('Inline worker is not supported'); 1439 | } 1440 | return new Worker(url); 1441 | } 1442 | } 1443 | 1444 | 1445 | /***/ }), 1446 | /* 4 */ 1447 | /***/ (function(module, exports) { 1448 | 1449 | AFRAME.registerPrimitive('a-lsystem', { 1450 | defaultComponents: { 1451 | lsystem: { 1452 | axiom: 'F', 1453 | productions: 'F:F++F++F++F', 1454 | iterations: 3, 1455 | angle: 60 1456 | } 1457 | }, 1458 | 1459 | mappings: { 1460 | axiom: 'lsystem.axiom', 1461 | productions: 'lsystem.productions', 1462 | segmentMixins: 'lsystem.segmentMixins', 1463 | iterations: 'lsystem.iterations', 1464 | angle: 'lsystem.angle' 1465 | } 1466 | }); 1467 | 1468 | /***/ }) 1469 | /******/ ]); -------------------------------------------------------------------------------- /dist/aframe-lsystem-component.min.js: -------------------------------------------------------------------------------- 1 | !function(t){function e(i){if(s[i])return s[i].exports;var o=s[i]={i:i,l:!1,exports:{}};return t[i].call(o.exports,o,o.exports,e),o.l=!0,o.exports}var s={};e.m=t,e.c=s,e.d=function(t,s,i){e.o(t,s)||Object.defineProperty(t,s,{configurable:!1,enumerable:!0,get:i})},e.n=function(t){var s=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(s,"a",s),s},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=0)}([function(t,e,s){"use strict";function i(t){let e=t.split(/(\w)\s*:\s*/).filter(t=>0!==t.length),s=[];for(var i=0;ii(t).map(t=>{var e=l(t,2);return[e[0],e[1].replace(/\s/g,"")]})},segmentMixins:{type:"string",parse:function(t){var e,s,o,r,n,a;let c=new Map,h=i(t);e=!0,s=!1,o=void 0;try{for(r=h[Symbol.iterator]();!(e=(n=r.next()).done);e=!0){let t=n.value,e=(a=l(t,2))[0],s=a[1];s=s.replace(/[\[\]]/g,"").split(","),c.set(e,s)}}catch(t){s=!0,o=t}finally{try{!e&&r.return&&r.return()}finally{if(s)throw o}}return c}},iterations:{type:"int",default:1},angle:{default:90},translateAxis:{type:"string",default:"y",parse:function(t){if("x"===(t=t.toLowerCase()))return new THREE.Vector3(1,0,0);if("y"===t)return new THREE.Vector3(0,1,0);if("z"===t)return new THREE.Vector3(0,0,1);throw Error('translateAxis has to be a string: "x", "y" or "z"')}},scaleFactor:{default:1},dynamicSegmentLength:{default:!0},mergeGeometries:{type:"boolean",default:!0},functionsInProductions:{type:"boolean",default:!0}},init:function(){this.sceneEl=document.querySelector("a-scene");let t=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,this.transformationSegment=new THREE.Object3D,this.transformationSegmentTemplate=this.transformationSegment.clone();let e=t.data.scaleFactor;this.colorIndex=0,this.lineWidth=5e-4,this.lineLength=.125,this.LSystem=new o.a({axiom:"F",productions:{F:"F"},finals:{"+":()=>{t.transformationSegment.quaternion.multiply(t.yPosRotation)},"-":()=>{t.transformationSegment.quaternion.multiply(t.yNegRotation)},"&":()=>{t.transformationSegment.quaternion.multiply(t.zNegRotation)},"^":()=>{t.transformationSegment.quaternion.multiply(t.zPosRotation)},"\\":()=>{t.transformationSegment.quaternion.multiply(t.xNegRotation)},"<":()=>{t.transformationSegment.quaternion.multiply(t.xNegRotation)},"/":()=>{t.transformationSegment.quaternion.multiply(t.xPosRotation)},">":()=>{t.transformationSegment.quaternion.multiply(t.xPosRotation)},"|":()=>{t.transformationSegment.quaternion.multiply(t.yReverseRotation)},"!":()=>{t.segmentLengthFactor*=e,t.transformationSegment.scale.set(t.transformationSegment.scale.x*=e,t.transformationSegment.scale.y*=e,t.transformationSegment.scale.z*=e),t.colorIndex++},"'":()=>{t.segmentLengthFactor*=1/e,t.transformationSegment.scale.set(t.transformationSegment.scale.x*=1/e,t.transformationSegment.scale.y*=1/e,t.transformationSegment.scale.z*=1/e),t.colorIndex=Math.max(0,t.colorIndex-1)},"[":()=>{t.stack.push(t.transformationSegment.clone())},"]":()=>{t.transformationSegment=t.stack.pop()}}})},update:function(t){var e,s,i,o,r;if(!1===this.data.mergeGeometries&&void 0!==this.segmentElementGroupsMap){e=!0,s=!1,i=void 0;try{for(o=this.segmentElementGroupsMap.values()[Symbol.iterator]();!(e=(r=o.next()).done);e=!0){let t=r.value;t.removeObject3D("mesh"),t.innerHTML=""}}catch(t){s=!0,i=t}finally{try{!e&&o.return&&o.return()}finally{if(s)throw i}}}if(0===Object.keys(t).length)this.updateLSystem(),this.updateSegmentMixins(),this.updateTurtleGraphics();else{let e=!1;(t.axiom&&t.axiom!==this.data.axiom||t.iterations&&t.iterations!==this.data.iterations||t.productions&&JSON.stringify(t.productions)!==JSON.stringify(this.data.productions))&&(this.updateLSystem(),e=!0),void 0!==t.segmentMixins&&JSON.stringify(Array.from(t.segmentMixins.entries()))!==JSON.stringify(Array.from(this.data.segmentMixins.entries()))&&(this.updateSegmentMixins(),e=!0),(e||t.angle&&t.angle!==this.data.angle)&&this.updateTurtleGraphics()}},calculateSegmentLength:function(t,e){if(this.segmentLengthMap.has(t))return this.segmentLengthMap.get(t);let s;return e.computeBoundingBox(),this.data.translateAxis.equals(this.X)?s=Math.abs(e.boundingBox.min.x-e.boundingBox.max.x):this.data.translateAxis.equals(this.Y)?s=Math.abs(e.boundingBox.min.y-e.boundingBox.max.y):this.data.translateAxis.equals(this.Z)&&(s=Math.abs(e.boundingBox.min.z-e.boundingBox.max.z)),this.segmentLengthMap.set(t,s),s},initWorker:function(){this.worker=new n.a},pushSegment:function(t){let e=this,s=e.transformationSegment.quaternion,i=e.transformationSegment.position,o=e.transformationSegment.scale,r=Math.min(this.colorIndex,this.data.segmentMixins.get(t).length-1),n=this.mixinMap.get(t+r);if(!1===this.data.mergeGeometries){let a=document.createElement("a-entity");a.setAttribute("mixin",n),a.addEventListener("loaded",()=>{let t=e.segmentLengthMap.get(n);a.object3D.children[0].translateOnAxis(e.data.translateAxis,t*e.segmentLengthFactor/2),a.object3D.quaternion.copy(s),a.object3D.position.copy(i),a.object3D.scale.copy(o)},{once:!0}),this.segmentElementGroupsMap.get(t+r).appendChild(a)}else{let e=this.segmentObjects3DMap.get(t+r).clone();e.matrixAutoUpdate=!1,e.quaternion.copy(s),e.position.copy(i),e.scale.copy(o),e.updateMatrix(),this.mergeGroups.get(t+r).geometry.merge(e.geometry,e.matrix)}let a=this.segmentLengthMap.get(n);this.transformationSegment.translateOnAxis(this.data.translateAxis,a*this.segmentLengthFactor)},updateLSystem:function(){let t=this,e={axiom:this.data.axiom,productions:this.data.productions,iterations:this.data.iterations};return Date.now()-this.worker.startTime>1e3&&(this.worker.terminate(),this.initWorker()),this.worker.startTime=Date.now(),this.workerPromise=new Promise(e=>{this.worker.onmessage=(s=>{t.LSystem.setAxiom(s.data.result),e()})}),this.worker.postMessage(e),this.workerPromise},updateSegmentMixins:function(){var t,e,s,i,o,r,n,a,c,h,u,m;let f=this;this.el.innerHTML="",this.segmentElementGroupsMap=new Map,this.mixinMap=new Map,t=!0,e=!1,s=void 0;try{for(i=this.data.segmentMixins[Symbol.iterator]();!(t=(o=i.next()).done);t=!0){let t=o.value,e=(r=l(t,2))[0],s=r[1];for(let t=0;t{f.pushSegment.bind(f,e)()});for(let t=0;t{let s=document.createElement("a-entity");if(s.setAttribute("id",o+"-group-"+i+Math.floor(1e4*Math.random())),s.setAttribute("geometry","buffer",!1),s.setAttribute("mixin",o),s.addEventListener("loaded",function(){let r=s.getObject3D("mesh").clone();s.getObject3D("mesh").geometry.dispose(),r.geometry=r.geometry.clone();let n=f.calculateSegmentLength(o,r.geometry);if(!0===f.data.mergeGeometries){let t=f.data.translateAxis.clone().multiplyScalar(n*f.segmentLengthFactor/2);r.geometry.translate(t.x,t.y,t.z),f.segmentObjects3DMap.set(e+i,r)}s.removeObject3D("mesh"),t()},{once:!0}),this.segmentElementGroupsMap.has(e+i)){let t=this.segmentElementGroupsMap.get(e+i);this.segmentElementGroupsMap.delete(e+i),this.el.removeChild(t)}this.segmentElementGroupsMap.set(e+i,s),this.el.appendChild(s)}))}}}catch(t){a=!0,c=t}finally{try{!n&&h.return&&h.return()}finally{if(a)throw c}}}},updateTurtleGraphics:async function(){var t,e,s,i,o,r,n,a,c,h,u,m;if(await Promise.all([...this.mixinPromises,this.workerPromise]),this.transformationSegment.copy(this.transformationSegmentTemplate),!0===this.data.mergeGeometries){t=!0,e=!1,s=void 0;try{for(i=this.segmentObjects3DMap[Symbol.iterator]();!(t=(o=i.next()).done);t=!0){let t=o.value,e=(r=l(t,2))[0],s=r[1];this.mergeGroups.set(e,new THREE.Mesh(new THREE.Geometry,s.material))}}catch(t){e=!0,s=t}finally{try{!t&&i.return&&i.return()}finally{if(e)throw s}}}this.stack=[];let f=this.data.angle;if(this.xPosRotation.setFromAxisAngle(this.X,Math.PI/180*f),this.xNegRotation.setFromAxisAngle(this.X,Math.PI/180*-f),this.yPosRotation.setFromAxisAngle(this.Y,Math.PI/180*f),this.yNegRotation.setFromAxisAngle(this.Y,Math.PI/180*-f),this.yReverseRotation.setFromAxisAngle(this.Y,Math.PI/180*180),this.zPosRotation.setFromAxisAngle(this.Z,Math.PI/180*f),this.zNegRotation.setFromAxisAngle(this.Z,Math.PI/180*-f),this.LSystem.final(),!0===this.data.mergeGeometries){n=!0,a=!1,c=void 0;try{for(h=this.segmentElementGroupsMap[Symbol.iterator]();!(n=(u=h.next()).done);n=!0){let t=u.value,e=(m=l(t,2))[0],s=m[1];0===this.mergeGroups.get(e).geometry.vertices.length?this.el.removeChild(s):(s.setObject3D("mesh",this.mergeGroups.get(e)),s.setAttribute("mixin",this.mixinMap.get(e)))}}catch(t){a=!0,c=t}finally{try{!n&&h.return&&h.return()}finally{if(a)throw c}}}},remove:function(){},tick:function(){},pause:function(){},play:function(){}})},function(t,e){"use strict";function s(t){let e,s=t[0].match(/(.+)<(.)/),i=t[0].match(/(.)>(.+)/);if(null===s&&null===i)return t;let o=t[1].successor||t[1].successors?t[1]:{successor:t[1]};return null!==s&&(e=s[2],o.leftCtx=s[1]),null!==i&&(e=i[1],o.rightCtx=i[2]),[e,o]}function i(t){if("string"!=typeof t&&t instanceof String==!1)return t;let e=[];for(let s of t)e.push({symbol:s});return e}function o(t,e){return t[1]=function t(e,s){if(e.hasOwnProperty("successors"))for(var o=0;o{if(void 0===e.symbol)throw console.log("found:",e),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 t+e.symbol},""):JSON.stringify(this.axiom)}setProduction(t,e,i=!1){let r=[t,e];if(void 0===r)throw Error("no production specified.");if(e.successor&&e.successors)throw Error('You can not have both a "successor" and a "successors" field in your production!');if(!0===this.allowClassicSyntax&&(r=s(r)),(r=o(r,this.forceObjects))[1].isStochastic=void 0!==r[1].successors&&r[1].successors.every(t=>void 0!==t.weight),r[1].isStochastic)for(let t of(r[1].weightSum=0,r[1].successors))r[1].weightSum+=t.weight;let n=r[0];if(!0===i&&this.productions.has(n)){let t=this.productions.get(n),e=t.successor,s=t.successors;e&&!s&&(t={successors:[t]}),t.successors.push(r[1]),this.productions.set(n,t)}else this.productions.set(n,r[1])}setProductions(t){if(void 0===t)throw Error("no production specified.");for(let e of(this.clearProductions(),Object.entries(t))){let t=e[0],s=e[1];this.setProduction(t,s,!0)}}clearProductions(){this.productions=new Map}setFinal(t,e){let s=[t,e];if(void 0===s)throw Error("no final specified.");this.finals.set(s[0],s[1])}setFinals(t){if(void 0===t)throw Error("no finals specified.");for(let e in this.finals=new Map,t)t.hasOwnProperty(e)&&this.setFinal(e,t[e])}getProductionResult(t,e,s,i,o=!1){let r=void 0!==t.leftCtx||void 0!==t.rightCtx,n=!1,a=!0;if(void 0!==t.condition&&!1===t.condition({index:e,currentAxiom:this.axiom,part:s,params:i})?a=!1:r&&(void 0!==t.leftCtx&&void 0!==t.rightCtx?a=this.match({direction:"left",match:t.leftCtx,index:e,branchSymbols:this.branchSymbols,ignoredSymbols:this.ignoredSymbols}).result&&this.match({direction:"right",match:t.rightCtx,index:e,branchSymbols:this.branchSymbols,ignoredSymbols:this.ignoredSymbols}).result:void 0!==t.leftCtx?a=this.match({direction:"left",match:t.leftCtx,index:e,branchSymbols:this.branchSymbols,ignoredSymbols:this.ignoredSymbols}).result:void 0!==t.rightCtx&&(a=this.match({direction:"right",match:t.rightCtx,index:e,branchSymbols:this.branchSymbols,ignoredSymbols:this.ignoredSymbols}).result)),!1===a)n=!1;else if(t.successors){let o,r;for(let a of(t.isStochastic&&(r=Math.random()*t.weightSum,o=0),t.successors)){if(t.isStochastic&&r>(o+=a.weight))continue;let l=this.getProductionResult(a,e,s,i,!0);if(void 0!==l&&!1!==l){n=l;break}}}else n="function"==typeof t.successor?t.successor({index:e,currentAxiom:this.axiom,part:s,params:i}):t.successor;return n||(o?n:s)}applyProductions(){let t="string"==typeof this.axiom?"":[],e=0;for(let s of this.axiom){let i=s.symbol||s,o=s.params||[],r=s;if(this.productions.has(i)){let t=this.productions.get(i);r=this.getProductionResult(t,e,s,o)}"string"==typeof t?t+=r:r instanceof Array?t.push(...r):t.push(r),e++}return this.axiom=t,t}iterate(t=1){let e;this.iterations=t;for(let s=0;t>s;s++)e=this.applyProductions();return e}final(t){let e=0;for(let s of this.axiom){let i=s;if("object"==typeof s&&s.symbol&&(i=s.symbol),this.finals.has(i)){let o=this.finals.get(i),r=typeof o;if("function"!==r)throw Error("'"+i+"' has an object for a final function. But it is __not a function__ but a "+r+"!");o({index:e,part:s},t)}e++}}match({axiom_:t,match:e,ignoredSymbols:s,branchSymbols:i,index:o,direction:r}){var n,a;let l=0,c=0;t=t||this.axiom,void 0===i&&(i=void 0!==this.branchSymbols?this.branchSymbols:[]),void 0===s&&(s=void 0!==this.ignoredSymbols?this.ignoredSymbols:[]);let h,u,m,f,d,g,y,p=[];if("right"===r)f=g=1,m=o+1,d=0,y=e.length,i.length>0&&(h=(n=i)[0],u=n[1]);else{if("left"!==r)throw Error(r,"is not a valid direction for matching.");f=g=-1,m=o-1,d=e.length-1,y=-1,i.length>0&&(u=(a=i)[0],h=a[1])}for(;m=0;m+=f){let i=t[m].symbol||t[m],o=e[d];if(i===o){if((0===l||c>0)&&(i===h?(c++,l++,d+=g):i===u?(c=Math.max(0,c-1),l=Math.max(0,l-1),0===c&&(d+=g)):(p.push(m),d+=g)),d===y)return{result:!0,matchIndices:p}}else if(i===h)l++,c>0&&c++;else if(i===u)l=Math.max(0,l-1),c>0&&(c=Math.max(0,c-1));else if((0===l||c>0&&o!==u)&&!1===s.includes(i))return{result:!1,matchIndices:p}}return{result:!1,matchIndices:p}}}LSystem.getStringResult=LSystem.getString,LSystem.transformClassicStochasticProductions=function(t){return function(){let e=t,s=e.length,i=Math.random();for(let t=0;s>t;t++)if((t+1)/s>=i)return e[t];console.error("Should have returned a result of the list, something is wrong here with the random numbers?.")}},LSystem.transformClassicCSProduction=s,LSystem.transformClassicParametricAxiom=function(t){let e=t.replace(/\s+/g,"").split(/[\(\)]/),s=[];for(let t=0;t(.+)/);if(null===o&&null===i)return t;let e=t[1].successor||t[1].successors?t[1]:{successor:t[1]};return null!==o&&(s=o[2],e.leftCtx=o[1]),null!==i&&(s=i[1],e.rightCtx=i[2]),[s,e]}function i(t){if("string"!=typeof t&&t instanceof String==!1)return t;let s=[];for(let o of t)s.push({symbol:o});return s}function e(t,s){return t[1]=function t(s,o){if(s.hasOwnProperty("successors"))for(var e=0;e{if(void 0===s.symbol)throw console.log("found:",s),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 t+s.symbol},""):JSON.stringify(this.axiom)}setProduction(t,s,i=!1){let r=[t,s];if(void 0===r)throw Error("no production specified.");if(s.successor&&s.successors)throw Error(\'You can not have both a "successor" and a "successors" field in your production!\');if(!0===this.allowClassicSyntax&&(r=o(r)),(r=e(r,this.forceObjects))[1].isStochastic=void 0!==r[1].successors&&r[1].successors.every(t=>void 0!==t.weight),r[1].isStochastic)for(let t of(r[1].weightSum=0,r[1].successors))r[1].weightSum+=t.weight;let n=r[0];if(!0===i&&this.productions.has(n)){let t=this.productions.get(n),s=t.successor,o=t.successors;s&&!o&&(t={successors:[t]}),t.successors.push(r[1]),this.productions.set(n,t)}else this.productions.set(n,r[1])}setProductions(t){if(void 0===t)throw Error("no production specified.");for(let s of(this.clearProductions(),Object.entries(t))){let t=s[0],o=s[1];this.setProduction(t,o,!0)}}clearProductions(){this.productions=new Map}setFinal(t,s){let o=[t,s];if(void 0===o)throw Error("no final specified.");this.finals.set(o[0],o[1])}setFinals(t){if(void 0===t)throw Error("no finals specified.");for(let s in this.finals=new Map,t)t.hasOwnProperty(s)&&this.setFinal(s,t[s])}getProductionResult(t,s,o,i,e=!1){let r=void 0!==t.leftCtx||void 0!==t.rightCtx,n=!1,c=!0;if(void 0!==t.condition&&!1===t.condition({index:s,currentAxiom:this.axiom,part:o,params:i})?c=!1:r&&(void 0!==t.leftCtx&&void 0!==t.rightCtx?c=this.match({direction:"left",match:t.leftCtx,index:s,branchSymbols:this.branchSymbols,ignoredSymbols:this.ignoredSymbols}).result&&this.match({direction:"right",match:t.rightCtx,index:s,branchSymbols:this.branchSymbols,ignoredSymbols:this.ignoredSymbols}).result:void 0!==t.leftCtx?c=this.match({direction:"left",match:t.leftCtx,index:s,branchSymbols:this.branchSymbols,ignoredSymbols:this.ignoredSymbols}).result:void 0!==t.rightCtx&&(c=this.match({direction:"right",match:t.rightCtx,index:s,branchSymbols:this.branchSymbols,ignoredSymbols:this.ignoredSymbols}).result)),!1===c)n=!1;else if(t.successors){let e,r;for(let c of(t.isStochastic&&(r=Math.random()*t.weightSum,e=0),t.successors)){if(t.isStochastic&&r>(e+=c.weight))continue;let a=this.getProductionResult(c,s,o,i,!0);if(void 0!==a&&!1!==a){n=a;break}}}else n="function"==typeof t.successor?t.successor({index:s,currentAxiom:this.axiom,part:o,params:i}):t.successor;return n||(e?n:o)}applyProductions(){let t="string"==typeof this.axiom?"":[],s=0;for(let o of this.axiom){let i=o.symbol||o,e=o.params||[],r=o;if(this.productions.has(i)){let t=this.productions.get(i);r=this.getProductionResult(t,s,o,e)}"string"==typeof t?t+=r:r instanceof Array?t.push(...r):t.push(r),s++}return this.axiom=t,t}iterate(t=1){let s;this.iterations=t;for(let o=0;t>o;o++)s=this.applyProductions();return s}final(t){let s=0;for(let o of this.axiom){let i=o;if("object"==typeof o&&o.symbol&&(i=o.symbol),this.finals.has(i)){let e=this.finals.get(i),r=typeof e;if("function"!==r)throw Error("\'"+i+"\' has an object for a final function. But it is __not a function__ but a "+r+"!");e({index:s,part:o},t)}s++}}match({axiom_:t,match:s,ignoredSymbols:o,branchSymbols:i,index:e,direction:r}){var n,c;let a=0,l=0;t=t||this.axiom,void 0===i&&(i=void 0!==this.branchSymbols?this.branchSymbols:[]),void 0===o&&(o=void 0!==this.ignoredSymbols?this.ignoredSymbols:[]);let u,h,f,d,m,y,b,g=[];if("right"===r)d=y=1,f=e+1,m=0,b=s.length,i.length>0&&(u=(n=i)[0],h=n[1]);else{if("left"!==r)throw Error(r,"is not a valid direction for matching.");d=y=-1,f=e-1,m=s.length-1,b=-1,i.length>0&&(h=(c=i)[0],u=c[1])}for(;f=0;f+=d){let i=t[f].symbol||t[f],e=s[m];if(i===e){if((0===a||l>0)&&(i===u?(l++,a++,m+=y):i===h?(l=Math.max(0,l-1),a=Math.max(0,a-1),0===l&&(m+=y)):(g.push(f),m+=y)),m===b)return{result:!0,matchIndices:g}}else if(i===u)a++,l>0&&l++;else if(i===h)a=Math.max(0,a-1),l>0&&(l=Math.max(0,l-1));else if((0===a||l>0&&e!==h)&&!1===o.includes(i))return{result:!1,matchIndices:g}}return{result:!1,matchIndices:g}}}LSystem.getStringResult=LSystem.getString,LSystem.transformClassicStochasticProductions=function(t){return function(){let s=t,o=s.length,i=Math.random();for(let t=0;o>t;t++)if((t+1)/o>=i)return s[t];console.error("Should have returned a result of the list, something is wrong here with the random numbers?.")}},LSystem.transformClassicCSProduction=o,LSystem.transformClassicParametricAxiom=function(t){let s=t.replace(/\\s+/g,"").split(/[\\(\\)]/),o=[];for(let t=0;t 2 | 3 | 4 | 5 | A-Frame L-System Component - 2D Koch Snow Flake 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /examples/animated hilbert curve/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | A-Frame L-System Component - Hilbert Curve 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /examples/animated tree/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | A-Frame L-System Component - Animated Tree 4 | 5 | 6 | 7 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /examples/forrest/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | A-Frame L-System Component - Forrest 4 | 5 | 6 | 7 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /examples/hilbertcurve/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | A-Frame L-System Component - Hilbert Curve 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | A-Frame Example Component 4 | 5 | 22 | 23 | 24 |

A-Frame L-System Component

25 | Hilbert Curve 26 | Animated Hilbert Curve 27 |

This example shows how to use the L-System component to render a 4-iteration Hilbert Curve in A-Frame.

28 | 29 | 2D Koch Snowflake 30 | 31 | multiple mixins (geometry and color changes) 32 |

Demonstrating how to use ! and ' to increment or decrement the mixin index. This makes it possible to use all kinds of materials and geometries for symbols. Make sure to also check the source code of the examples! :)

33 | 34 | Tree 35 |

This example shows how to use the L-System component to render a tree in A-Frame.

36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | Forrest 44 |

This example shows how to use the L-System component to render a multiple trees in A-Frame.

45 | 46 |
47 |
48 | Fork me on GitHub 49 |
50 |
51 | 52 | 53 | -------------------------------------------------------------------------------- /examples/libs/aframe-animation-component.js: -------------------------------------------------------------------------------- 1 | !function(t){function n(r){if(e[r])return e[r].exports;var a=e[r]={exports:{},id:r,loaded:!1};return t[r].call(a.exports,a,a.exports,n),a.loaded=!0,a.exports}var e={};return n.m=t,n.c=e,n.p="",n(0)}([function(t,n,e){function r(t,n,e){var r=n.from||s(t,n.property);return AFRAME.utils.extend({},e,{targets:[{aframeProperty:r}],aframeProperty:n.to,update:function(){c(t,n.property,this.targets[0].aframeProperty)}})}function a(t,n,e){var r=s(t,n.property);n.from&&(r=AFRAME.utils.coordinates.parse(n.from));var a=AFRAME.utils.coordinates.parse(n.to);return AFRAME.utils.extend({},e,{targets:[r],update:function(){c(t,n.property,this.targets[0])}},a)}function i(t,n){var e=n.split("."),r=e[0],a=e[1],i=t.components[r]||AFRAME.components[r];return i?a?i.schema[a].type:i.schema.type:null}var o=e(1);if("undefined"==typeof AFRAME)throw new Error("Component attempted to register before AFRAME was available.");var u=AFRAME.utils,s=u.entity.getComponentProperty,c=u.entity.setComponentProperty;u.styleParser.parse;AFRAME.registerComponent("animation",{schema:{delay:{"default":0},dir:{"default":""},dur:{"default":1e3},easing:{"default":"easeInQuad"},elasticity:{"default":400},from:{"default":""},loop:{"default":!1},property:{"default":""},repeat:{"default":0},startEvents:{type:"array"},pauseEvents:{type:"array"},to:{"default":""}},multiple:!0,init:function(){this.animation=null,this.animationIsPlaying=!1,this.config=null,this.playAnimationBound=this.playAnimation.bind(this),this.pauseAnimationBound=this.pauseAnimation.bind(this),this.repeat=0},update:function(){var t=this.attrName,n=this.data,e=this.el,u=i(e,n.property),s=this;this.repeat=n.repeat;var c={autoplay:!1,begin:function(){e.emit("animation-begin"),e.emit(t+"-begin")},complete:function(){e.emit("animation-complete"),e.emit(t+"-complete"),--s.repeat>0&&s.animation.play()},direction:n.dir,duration:n.dur,easing:n.easing,elasticity:n.elasticity,loop:n.loop},f=r;"vec2"!==u&&"vec3"!==u&&"vec4"!==u||(f=a),this.pauseAnimation(),this.config=f(e,n,c),this.animation=o(this.config),this.data.startEvents.length||(this.animationIsPlaying=!0),this.removeEventListeners(),this.addEventListeners()},remove:function(){this.pauseAnimation(),this.removeEventListeners()},pause:function(){this.pauseAnimation(),this.removeEventListeners()},play:function(){this.animation&&this.animationIsPlaying&&(this.playAnimation(),this.addEventListeners())},addEventListeners:function(){var t=this,n=this.data,e=this.el;n.startEvents.map(function(n){e.addEventListener(n,t.playAnimationBound)}),n.pauseEvents.map(function(n){e.addEventListener(n,t.pauseAnimationBound)})},removeEventListeners:function(){var t=this,n=this.data,e=this.el;n.startEvents.map(function(n){e.removeEventListener(n,t.playAnimationBound)}),n.pauseEvents.map(function(n){e.removeEventListener(n,t.pauseAnimationBound)})},playAnimation:function(){this.animation&&(this.animation.restart(),this.animationIsPlaying=!0)},pauseAnimation:function(){this.animation&&(this.animation.pause(),this.animationIsPlaying=!1)}})},function(t,n,e){var r,a,i;!function(e,o){a=[],r=o,i="function"==typeof r?r.apply(n,a):r,!(void 0!==i&&(t.exports=i))}(this,function(){var t,n="1.1.0",e={duration:1e3,delay:0,loop:!1,autoplay:!0,direction:"normal",easing:"easeOutElastic",elasticity:400,round:!1,begin:void 0,update:void 0,complete:void 0},r=["translateX","translateY","translateZ","rotate","rotateX","rotateY","rotateZ","scale","scaleX","scaleY","scaleZ","skewX","skewY"],a="transform",i=function(){return{array:function(t){return Array.isArray(t)},object:function(t){return Object.prototype.toString.call(t).indexOf("Object")>-1},svg:function(t){return t instanceof SVGElement},dom:function(t){return t.nodeType||i.svg(t)},number:function(t){return!isNaN(parseInt(t))},string:function(t){return"string"==typeof t},func:function(t){return"function"==typeof t},undef:function(t){return"undefined"==typeof t},"null":function(t){return"null"==typeof t},hex:function(t){return/(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(t)},rgb:function(t){return/^rgb/.test(t)},rgba:function(t){return/^rgba/.test(t)},hsl:function(t){return/^hsl/.test(t)},color:function(t){return i.hex(t)||i.rgb(t)||i.rgba(t)||i.hsl(t)}}}(),o=function(){var t={},n=["Quad","Cubic","Quart","Quint","Expo"],e={Sine:function(t){return 1-Math.cos(t*Math.PI/2)},Circ:function(t){return 1-Math.sqrt(1-t*t)},Elastic:function(t,n){if(0===t||1===t)return t;var e=1-Math.min(n,998)/1e3,r=t/1,a=r-1,i=e/(2*Math.PI)*Math.asin(1);return-(Math.pow(2,10*a)*Math.sin((a-i)*(2*Math.PI)/e))},Back:function(t){return t*t*(3*t-2)},Bounce:function(t){for(var n,e=4;t<((n=Math.pow(2,--e))-1)/11;);return 1/Math.pow(4,3-e)-7.5625*Math.pow((3*n-2)/22-t,2)}};return n.forEach(function(t,n){e[t]=function(t){return Math.pow(t,n+2)}}),Object.keys(e).forEach(function(n){var r=e[n];t["easeIn"+n]=r,t["easeOut"+n]=function(t,n){return 1-r(1-t,n)},t["easeInOut"+n]=function(t,n){return t<.5?r(2*t,n)/2:1-r(t*-2+2,n)/2}}),t.linear=function(t){return t},t}(),u=function(t){return i.string(t)?t:t+""},s=function(t){return t.replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase()},c=function(t){if(i.color(t))return!1;try{var n=document.querySelectorAll(t);return n}catch(e){return!1}},f=function(t,n){return Math.floor(Math.random()*(n-t+1))+t},l=function(t){return t.reduce(function(t,n){return t.concat(i.array(n)?l(n):n)},[])},p=function(t){return i.array(t)?t:(i.string(t)&&(t=c(t)||t),t instanceof NodeList||t instanceof HTMLCollection?[].slice.call(t):[t])},m=function(t,n){return t.some(function(t){return t===n})},d=function(t,n){var e={};return t.forEach(function(t){var r=JSON.stringify(n.map(function(n){return t[n]}));e[r]=e[r]||[],e[r].push(t)}),Object.keys(e).map(function(t){return e[t]})},h=function(t){return t.filter(function(t,n,e){return e.indexOf(t)===n})},v=function(t){var n={};for(var e in t)n[e]=t[e];return n},g=function(t,n){for(var e in n)t[e]=i.undef(t[e])?n[e]:t[e];return t},y=function(t){var n=/^#?([a-f\d])([a-f\d])([a-f\d])$/i,t=t.replace(n,function(t,n,e,r){return n+n+e+e+r+r}),e=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(t),r=parseInt(e[1],16),a=parseInt(e[2],16),i=parseInt(e[3],16);return"rgb("+r+","+a+","+i+")"},b=function(t){var n,e,r,t=/hsl\((\d+),\s*([\d.]+)%,\s*([\d.]+)%\)/g.exec(t),a=parseInt(t[1])/360,i=parseInt(t[2])/100,o=parseInt(t[3])/100,u=function(t,n,e){return e<0&&(e+=1),e>1&&(e-=1),e<1/6?t+6*(n-t)*e:e<.5?n:e<2/3?t+(n-t)*(2/3-e)*6:t};if(0==i)n=e=r=o;else{var s=o<.5?o*(1+i):o+i-o*i,c=2*o-s;n=u(c,s,a+1/3),e=u(c,s,a),r=u(c,s,a-1/3)}return"rgb("+255*n+","+255*e+","+255*r+")"},A=function(t){return i.rgb(t)||i.rgba(t)?t:i.hex(t)?y(t):i.hsl(t)?b(t):void 0},E=function(t){return/([\+\-]?[0-9|auto\.]+)(%|px|pt|em|rem|in|cm|mm|ex|pc|vw|vh|deg)?/.exec(t)[2]},M=function(t,n,e){return E(n)?n:t.indexOf("translate")>-1?E(e)?n+E(e):n+"px":t.indexOf("rotate")>-1||t.indexOf("skew")>-1?n+"deg":n},x=function(t,n){if(n in t.style)return getComputedStyle(t).getPropertyValue(s(n))||"0"},w=function(t,n){var e=n.indexOf("scale")>-1?1:0,r=t.style.transform;if(!r)return e;for(var a=/(\w+)\((.+?)\)/g,i=[],o=[],u=[];i=a.exec(r);)o.push(i[1]),u.push(i[2]);var s=u.filter(function(t,e){return o[e]===n});return s.length?s[0]:e},I=function(t,n){return i.dom(t)&&m(r,n)?"transform":i.dom(t)&&"transform"!==n&&x(t,n)?"css":i.dom(t)&&(t.getAttribute(n)||i.svg(t)&&t[n])?"attribute":i["null"](t[n])||i.undef(t[n])?void 0:"object"},P=function(t,n){switch(I(t,n)){case"transform":return w(t,n);case"css":return x(t,n);case"attribute":return t.getAttribute(n)}return t[n]||0},L=function(t,n,e){if(i.color(n))return A(n);if(E(n))return n;var r=E(E(t.to)?t.to:t.from);return!r&&e&&(r=E(e)),r?n+r:n},O=function(t){var n=/-?\d*\.?\d+/g;return{original:t,numbers:u(t).match(n)?u(t).match(n).map(Number):[0],strings:u(t).split(n)}},k=function(t,n,e){return n.reduce(function(n,r,a){var r=r?r:e[a-1];return n+t[a-1]+r})},F=function(t){var t=t?l(i.array(t)?t.map(p):p(t)):[];return t.map(function(t,n){return{target:t,id:n}})},j=function(t,n){var r=[];for(var a in t)if(!e.hasOwnProperty(a)&&"targets"!==a){var o=i.object(t[a])?v(t[a]):{value:t[a]};o.name=a,r.push(g(o,n))}return r},C=function(t,n,e,r){var a=p(i.func(e)?e(t,r):e);return{from:a.length>1?a[0]:P(t,n),to:a.length>1?a[1]:a[0]}},R=function(t,n,e,r){var a={};if("transform"===e)a.from=t+"("+M(t,n.from,n.to)+")",a.to=t+"("+M(t,n.to)+")";else{var i="css"===e?x(r,t):void 0;a.from=L(n,n.from,i),a.to=L(n,n.to,i)}return{from:O(a.from),to:O(a.to)}},B=function(t,n){var e=[];return t.forEach(function(r,a){var o=r.target;return n.forEach(function(n){var u=I(o,n.name);if(u){var s=C(o,n.name,n.value,a),c=v(n);c.animatables=r,c.type=u,c.from=R(n.name,s,c.type,o).from,c.to=R(n.name,s,c.type,o).to,c.round=i.color(s.from)||c.round?1:0,c.delay=(i.func(c.delay)?c.delay(o,a,t.length):c.delay)/J.speed,c.duration=(i.func(c.duration)?c.duration(o,a,t.length):c.duration)/J.speed,e.push(c)}})}),e},N=function(t,n){var e=B(t,n),r=d(e,["name","from","to","delay","duration"]);return r.map(function(t){var n=v(t[0]);return n.animatables=t.map(function(t){return t.animatables}),n.totalDuration=n.delay+n.duration,n})},S=function(t,n){t.tweens.forEach(function(e){var r=e.to,a=e.from,i=t.duration-(e.delay+e.duration);e.from=r,e.to=a,n&&(e.delay=i)}),t.reversed=!t.reversed},T=function(t){if(t.length)return Math.max.apply(Math,t.map(function(t){return t.totalDuration}))},$=function(t){var n=[],e=[];return t.tweens.forEach(function(t){"css"!==t.type&&"transform"!==t.type||(n.push("css"===t.type?s(t.name):"transform"),t.animatables.forEach(function(t){e.push(t.target)}))}),{properties:h(n).join(", "),elements:h(e)}},V=function(t){var n=$(t);n.elements.forEach(function(t){t.style.willChange=n.properties})},X=function(t){var n=$(t);n.elements.forEach(function(t){t.style.removeProperty("will-change")})},Y=function(t){var n=i.string(t)?c(t)[0]:t;return{path:n,value:n.getTotalLength()}},Q=function(t,n){var e=t.path,r=t.value*n,a=function(a){var i=a||0,o=n>1?t.value+i:r+i;return e.getPointAtLength(o)},i=a(),o=a(-1),u=a(1);switch(t.name){case"translateX":return i.x;case"translateY":return i.y;case"rotate":return 180*Math.atan2(u.y-o.y,u.x-o.x)/Math.PI}},Z=function(t,n){var e=Math.min(Math.max(n-t.delay,0),t.duration),r=e/t.duration,a=t.to.numbers.map(function(n,e){var a=t.from.numbers[e],i=o[t.easing](r,t.elasticity),u=t.path?Q(t,i):a+i*(n-a);return u=t.round?Math.round(u*t.round)/t.round:u});return k(a,t.to.strings,t.from.strings)},q=function(n,e){var r;n.currentTime=e,n.progress=e/n.duration*100;for(var i=0;i=r.delay&&(r.begin(n),r.begin=void 0),e.current>=n.duration&&(r.loop?(e.start=t,"alternate"===r.direction&&S(n,!0),i.number(r.loop)&&r.loop--):(n.ended=!0,n.pause(),r.complete&&r.complete(n)),e.last=0)},n.seek=function(t){q(n,t/100*n.duration)},n.pause=function(){X(n);var t=z.indexOf(n);t>-1&&z.splice(t,1)},n.play=function(t){n.pause(),t&&(n=g(D(g(t,n.settings)),n)),e.start=0,e.last=n.ended?0:n.currentTime;var r=n.settings;"reverse"===r.direction&&S(n),"alternate"!==r.direction||r.loop||(r.loop=1),V(n),z.push(n),G||H()},n.restart=function(){n.reversed&&S(n),n.pause(),n.seek(0),n.play()},n.settings.autoplay&&n.play(),n},K=function(t){for(var n=l(i.array(t)?t.map(p):p(t)),e=z.length-1;e>=0;e--)for(var r=z[e],a=r.tweens,o=a.length-1;o>=0;o--)for(var u=a[o].animatables,s=u.length-1;s>=0;s--)m(n,u[s].target)&&(u.splice(s,1),u.length||a.splice(o,1),a.length||r.pause())};return J.version=n,J.speed=1,J.list=z,J.remove=K,J.easings=o,J.getValue=P,J.path=Y,J.random=f,J})}]); 2 | -------------------------------------------------------------------------------- /examples/libs/aframe-lsystem-component.js: -------------------------------------------------------------------------------- 1 | !function(t){function e(i){if(s[i])return s[i].exports;var o=s[i]={i:i,l:!1,exports:{}};return t[i].call(o.exports,o,o.exports,e),o.l=!0,o.exports}var s={};e.m=t,e.c=s,e.d=function(t,s,i){e.o(t,s)||Object.defineProperty(t,s,{configurable:!1,enumerable:!0,get:i})},e.n=function(t){var s=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(s,"a",s),s},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=0)}([function(t,e,s){"use strict";function i(t){let e=t.split(/(\w)\s*:\s*/).filter(t=>0!==t.length),s=[];for(var i=0;ii(t).map(t=>{var e=l(t,2);return[e[0],e[1].replace(/\s/g,"")]})},segmentMixins:{type:"string",parse:function(t){var e,s,o,r,n,a;let c=new Map,h=i(t);e=!0,s=!1,o=void 0;try{for(r=h[Symbol.iterator]();!(e=(n=r.next()).done);e=!0){let t=n.value,e=(a=l(t,2))[0],s=a[1];s=s.replace(/[\[\]]/g,"").split(","),c.set(e,s)}}catch(t){s=!0,o=t}finally{try{!e&&r.return&&r.return()}finally{if(s)throw o}}return c}},iterations:{type:"int",default:1},angle:{default:90},translateAxis:{type:"string",default:"y",parse:function(t){if("x"===(t=t.toLowerCase()))return new THREE.Vector3(1,0,0);if("y"===t)return new THREE.Vector3(0,1,0);if("z"===t)return new THREE.Vector3(0,0,1);throw Error('translateAxis has to be a string: "x", "y" or "z"')}},scaleFactor:{default:1},dynamicSegmentLength:{default:!0},mergeGeometries:{type:"boolean",default:!0},functionsInProductions:{type:"boolean",default:!0}},init:function(){this.sceneEl=document.querySelector("a-scene");let t=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,this.transformationSegment=new THREE.Object3D,this.transformationSegmentTemplate=this.transformationSegment.clone();let e=t.data.scaleFactor;this.colorIndex=0,this.lineWidth=5e-4,this.lineLength=.125,this.LSystem=new o.a({axiom:"F",productions:{F:"F"},finals:{"+":()=>{t.transformationSegment.quaternion.multiply(t.yPosRotation)},"-":()=>{t.transformationSegment.quaternion.multiply(t.yNegRotation)},"&":()=>{t.transformationSegment.quaternion.multiply(t.zNegRotation)},"^":()=>{t.transformationSegment.quaternion.multiply(t.zPosRotation)},"\\":()=>{t.transformationSegment.quaternion.multiply(t.xNegRotation)},"<":()=>{t.transformationSegment.quaternion.multiply(t.xNegRotation)},"/":()=>{t.transformationSegment.quaternion.multiply(t.xPosRotation)},">":()=>{t.transformationSegment.quaternion.multiply(t.xPosRotation)},"|":()=>{t.transformationSegment.quaternion.multiply(t.yReverseRotation)},"!":()=>{t.segmentLengthFactor*=e,t.transformationSegment.scale.set(t.transformationSegment.scale.x*=e,t.transformationSegment.scale.y*=e,t.transformationSegment.scale.z*=e),t.colorIndex++},"'":()=>{t.segmentLengthFactor*=1/e,t.transformationSegment.scale.set(t.transformationSegment.scale.x*=1/e,t.transformationSegment.scale.y*=1/e,t.transformationSegment.scale.z*=1/e),t.colorIndex=Math.max(0,t.colorIndex-1)},"[":()=>{t.stack.push(t.transformationSegment.clone())},"]":()=>{t.transformationSegment=t.stack.pop()}}})},update:function(t){var e,s,i,o,r;if(!1===this.data.mergeGeometries&&void 0!==this.segmentElementGroupsMap){e=!0,s=!1,i=void 0;try{for(o=this.segmentElementGroupsMap.values()[Symbol.iterator]();!(e=(r=o.next()).done);e=!0){let t=r.value;t.removeObject3D("mesh"),t.innerHTML=""}}catch(t){s=!0,i=t}finally{try{!e&&o.return&&o.return()}finally{if(s)throw i}}}if(0===Object.keys(t).length)this.updateLSystem(),this.updateSegmentMixins(),this.updateTurtleGraphics();else{let e=!1;(t.axiom&&t.axiom!==this.data.axiom||t.iterations&&t.iterations!==this.data.iterations||t.productions&&JSON.stringify(t.productions)!==JSON.stringify(this.data.productions))&&(this.updateLSystem(),e=!0),void 0!==t.segmentMixins&&JSON.stringify(Array.from(t.segmentMixins.entries()))!==JSON.stringify(Array.from(this.data.segmentMixins.entries()))&&(this.updateSegmentMixins(),e=!0),(e||t.angle&&t.angle!==this.data.angle)&&this.updateTurtleGraphics()}},calculateSegmentLength:function(t,e){if(this.segmentLengthMap.has(t))return this.segmentLengthMap.get(t);let s;return e.computeBoundingBox(),this.data.translateAxis.equals(this.X)?s=Math.abs(e.boundingBox.min.x-e.boundingBox.max.x):this.data.translateAxis.equals(this.Y)?s=Math.abs(e.boundingBox.min.y-e.boundingBox.max.y):this.data.translateAxis.equals(this.Z)&&(s=Math.abs(e.boundingBox.min.z-e.boundingBox.max.z)),this.segmentLengthMap.set(t,s),s},initWorker:function(){this.worker=new n.a},pushSegment:function(t){let e=this,s=e.transformationSegment.quaternion,i=e.transformationSegment.position,o=e.transformationSegment.scale,r=Math.min(this.colorIndex,this.data.segmentMixins.get(t).length-1),n=this.mixinMap.get(t+r);if(!1===this.data.mergeGeometries){let a=document.createElement("a-entity");a.setAttribute("mixin",n),a.addEventListener("loaded",()=>{let t=e.segmentLengthMap.get(n);a.object3D.children[0].translateOnAxis(e.data.translateAxis,t*e.segmentLengthFactor/2),a.object3D.quaternion.copy(s),a.object3D.position.copy(i),a.object3D.scale.copy(o)},{once:!0}),this.segmentElementGroupsMap.get(t+r).appendChild(a)}else{let e=this.segmentObjects3DMap.get(t+r).clone();e.matrixAutoUpdate=!1,e.quaternion.copy(s),e.position.copy(i),e.scale.copy(o),e.updateMatrix(),this.mergeGroups.get(t+r).geometry.merge(e.geometry,e.matrix)}let a=this.segmentLengthMap.get(n);this.transformationSegment.translateOnAxis(this.data.translateAxis,a*this.segmentLengthFactor)},updateLSystem:function(){let t=this,e={axiom:this.data.axiom,productions:this.data.productions,iterations:this.data.iterations};return Date.now()-this.worker.startTime>1e3&&(this.worker.terminate(),this.initWorker()),this.worker.startTime=Date.now(),this.workerPromise=new Promise(e=>{this.worker.onmessage=(s=>{t.LSystem.setAxiom(s.data.result),e()})}),this.worker.postMessage(e),this.workerPromise},updateSegmentMixins:function(){var t,e,s,i,o,r,n,a,c,h,u,m;let f=this;this.el.innerHTML="",this.segmentElementGroupsMap=new Map,this.mixinMap=new Map,t=!0,e=!1,s=void 0;try{for(i=this.data.segmentMixins[Symbol.iterator]();!(t=(o=i.next()).done);t=!0){let t=o.value,e=(r=l(t,2))[0],s=r[1];for(let t=0;t{f.pushSegment.bind(f,e)()});for(let t=0;t{let s=document.createElement("a-entity");if(s.setAttribute("id",o+"-group-"+i+Math.floor(1e4*Math.random())),s.setAttribute("geometry","buffer",!1),s.setAttribute("mixin",o),s.addEventListener("loaded",function(){let r=s.getObject3D("mesh").clone();s.getObject3D("mesh").geometry.dispose(),r.geometry=r.geometry.clone();let n=f.calculateSegmentLength(o,r.geometry);if(!0===f.data.mergeGeometries){let t=f.data.translateAxis.clone().multiplyScalar(n*f.segmentLengthFactor/2);r.geometry.translate(t.x,t.y,t.z),f.segmentObjects3DMap.set(e+i,r)}s.removeObject3D("mesh"),t()},{once:!0}),this.segmentElementGroupsMap.has(e+i)){let t=this.segmentElementGroupsMap.get(e+i);this.segmentElementGroupsMap.delete(e+i),this.el.removeChild(t)}this.segmentElementGroupsMap.set(e+i,s),this.el.appendChild(s)}))}}}catch(t){a=!0,c=t}finally{try{!n&&h.return&&h.return()}finally{if(a)throw c}}}},updateTurtleGraphics:async function(){var t,e,s,i,o,r,n,a,c,h,u,m;if(await Promise.all([...this.mixinPromises,this.workerPromise]),this.transformationSegment.copy(this.transformationSegmentTemplate),!0===this.data.mergeGeometries){t=!0,e=!1,s=void 0;try{for(i=this.segmentObjects3DMap[Symbol.iterator]();!(t=(o=i.next()).done);t=!0){let t=o.value,e=(r=l(t,2))[0],s=r[1];this.mergeGroups.set(e,new THREE.Mesh(new THREE.Geometry,s.material))}}catch(t){e=!0,s=t}finally{try{!t&&i.return&&i.return()}finally{if(e)throw s}}}this.stack=[];let f=this.data.angle;if(this.xPosRotation.setFromAxisAngle(this.X,Math.PI/180*f),this.xNegRotation.setFromAxisAngle(this.X,Math.PI/180*-f),this.yPosRotation.setFromAxisAngle(this.Y,Math.PI/180*f),this.yNegRotation.setFromAxisAngle(this.Y,Math.PI/180*-f),this.yReverseRotation.setFromAxisAngle(this.Y,Math.PI/180*180),this.zPosRotation.setFromAxisAngle(this.Z,Math.PI/180*f),this.zNegRotation.setFromAxisAngle(this.Z,Math.PI/180*-f),this.LSystem.final(),!0===this.data.mergeGeometries){n=!0,a=!1,c=void 0;try{for(h=this.segmentElementGroupsMap[Symbol.iterator]();!(n=(u=h.next()).done);n=!0){let t=u.value,e=(m=l(t,2))[0],s=m[1];0===this.mergeGroups.get(e).geometry.vertices.length?this.el.removeChild(s):(s.setObject3D("mesh",this.mergeGroups.get(e)),s.setAttribute("mixin",this.mixinMap.get(e)))}}catch(t){a=!0,c=t}finally{try{!n&&h.return&&h.return()}finally{if(a)throw c}}}},remove:function(){},tick:function(){},pause:function(){},play:function(){}})},function(t,e){"use strict";function s(t){let e,s=t[0].match(/(.+)<(.)/),i=t[0].match(/(.)>(.+)/);if(null===s&&null===i)return t;let o=t[1].successor||t[1].successors?t[1]:{successor:t[1]};return null!==s&&(e=s[2],o.leftCtx=s[1]),null!==i&&(e=i[1],o.rightCtx=i[2]),[e,o]}function i(t){if("string"!=typeof t&&t instanceof String==!1)return t;let e=[];for(let s of t)e.push({symbol:s});return e}function o(t,e){return t[1]=function t(e,s){if(e.hasOwnProperty("successors"))for(var o=0;o{if(void 0===e.symbol)throw console.log("found:",e),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 t+e.symbol},""):JSON.stringify(this.axiom)}setProduction(t,e,i=!1){let r=[t,e];if(void 0===r)throw Error("no production specified.");if(e.successor&&e.successors)throw Error('You can not have both a "successor" and a "successors" field in your production!');if(!0===this.allowClassicSyntax&&(r=s(r)),(r=o(r,this.forceObjects))[1].isStochastic=void 0!==r[1].successors&&r[1].successors.every(t=>void 0!==t.weight),r[1].isStochastic)for(let t of(r[1].weightSum=0,r[1].successors))r[1].weightSum+=t.weight;let n=r[0];if(!0===i&&this.productions.has(n)){let t=this.productions.get(n),e=t.successor,s=t.successors;e&&!s&&(t={successors:[t]}),t.successors.push(r[1]),this.productions.set(n,t)}else this.productions.set(n,r[1])}setProductions(t){if(void 0===t)throw Error("no production specified.");for(let e of(this.clearProductions(),Object.entries(t))){let t=e[0],s=e[1];this.setProduction(t,s,!0)}}clearProductions(){this.productions=new Map}setFinal(t,e){let s=[t,e];if(void 0===s)throw Error("no final specified.");this.finals.set(s[0],s[1])}setFinals(t){if(void 0===t)throw Error("no finals specified.");for(let e in this.finals=new Map,t)t.hasOwnProperty(e)&&this.setFinal(e,t[e])}getProductionResult(t,e,s,i,o=!1){let r=void 0!==t.leftCtx||void 0!==t.rightCtx,n=!1,a=!0;if(void 0!==t.condition&&!1===t.condition({index:e,currentAxiom:this.axiom,part:s,params:i})?a=!1:r&&(void 0!==t.leftCtx&&void 0!==t.rightCtx?a=this.match({direction:"left",match:t.leftCtx,index:e,branchSymbols:this.branchSymbols,ignoredSymbols:this.ignoredSymbols}).result&&this.match({direction:"right",match:t.rightCtx,index:e,branchSymbols:this.branchSymbols,ignoredSymbols:this.ignoredSymbols}).result:void 0!==t.leftCtx?a=this.match({direction:"left",match:t.leftCtx,index:e,branchSymbols:this.branchSymbols,ignoredSymbols:this.ignoredSymbols}).result:void 0!==t.rightCtx&&(a=this.match({direction:"right",match:t.rightCtx,index:e,branchSymbols:this.branchSymbols,ignoredSymbols:this.ignoredSymbols}).result)),!1===a)n=!1;else if(t.successors){let o,r;for(let a of(t.isStochastic&&(r=Math.random()*t.weightSum,o=0),t.successors)){if(t.isStochastic&&r>(o+=a.weight))continue;let l=this.getProductionResult(a,e,s,i,!0);if(void 0!==l&&!1!==l){n=l;break}}}else n="function"==typeof t.successor?t.successor({index:e,currentAxiom:this.axiom,part:s,params:i}):t.successor;return n||(o?n:s)}applyProductions(){let t="string"==typeof this.axiom?"":[],e=0;for(let s of this.axiom){let i=s.symbol||s,o=s.params||[],r=s;if(this.productions.has(i)){let t=this.productions.get(i);r=this.getProductionResult(t,e,s,o)}"string"==typeof t?t+=r:r instanceof Array?t.push(...r):t.push(r),e++}return this.axiom=t,t}iterate(t=1){let e;this.iterations=t;for(let s=0;t>s;s++)e=this.applyProductions();return e}final(t){let e=0;for(let s of this.axiom){let i=s;if("object"==typeof s&&s.symbol&&(i=s.symbol),this.finals.has(i)){let o=this.finals.get(i),r=typeof o;if("function"!==r)throw Error("'"+i+"' has an object for a final function. But it is __not a function__ but a "+r+"!");o({index:e,part:s},t)}e++}}match({axiom_:t,match:e,ignoredSymbols:s,branchSymbols:i,index:o,direction:r}){var n,a;let l=0,c=0;t=t||this.axiom,void 0===i&&(i=void 0!==this.branchSymbols?this.branchSymbols:[]),void 0===s&&(s=void 0!==this.ignoredSymbols?this.ignoredSymbols:[]);let h,u,m,f,d,g,y,p=[];if("right"===r)f=g=1,m=o+1,d=0,y=e.length,i.length>0&&(h=(n=i)[0],u=n[1]);else{if("left"!==r)throw Error(r,"is not a valid direction for matching.");f=g=-1,m=o-1,d=e.length-1,y=-1,i.length>0&&(u=(a=i)[0],h=a[1])}for(;m=0;m+=f){let i=t[m].symbol||t[m],o=e[d];if(i===o){if((0===l||c>0)&&(i===h?(c++,l++,d+=g):i===u?(c=Math.max(0,c-1),l=Math.max(0,l-1),0===c&&(d+=g)):(p.push(m),d+=g)),d===y)return{result:!0,matchIndices:p}}else if(i===h)l++,c>0&&c++;else if(i===u)l=Math.max(0,l-1),c>0&&(c=Math.max(0,c-1));else if((0===l||c>0&&o!==u)&&!1===s.includes(i))return{result:!1,matchIndices:p}}return{result:!1,matchIndices:p}}}LSystem.getStringResult=LSystem.getString,LSystem.transformClassicStochasticProductions=function(t){return function(){let e=t,s=e.length,i=Math.random();for(let t=0;s>t;t++)if((t+1)/s>=i)return e[t];console.error("Should have returned a result of the list, something is wrong here with the random numbers?.")}},LSystem.transformClassicCSProduction=s,LSystem.transformClassicParametricAxiom=function(t){let e=t.replace(/\s+/g,"").split(/[\(\)]/),s=[];for(let t=0;t(.+)/);if(null===o&&null===i)return t;let e=t[1].successor||t[1].successors?t[1]:{successor:t[1]};return null!==o&&(s=o[2],e.leftCtx=o[1]),null!==i&&(s=i[1],e.rightCtx=i[2]),[s,e]}function i(t){if("string"!=typeof t&&t instanceof String==!1)return t;let s=[];for(let o of t)s.push({symbol:o});return s}function e(t,s){return t[1]=function t(s,o){if(s.hasOwnProperty("successors"))for(var e=0;e{if(void 0===s.symbol)throw console.log("found:",s),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 t+s.symbol},""):JSON.stringify(this.axiom)}setProduction(t,s,i=!1){let r=[t,s];if(void 0===r)throw Error("no production specified.");if(s.successor&&s.successors)throw Error(\'You can not have both a "successor" and a "successors" field in your production!\');if(!0===this.allowClassicSyntax&&(r=o(r)),(r=e(r,this.forceObjects))[1].isStochastic=void 0!==r[1].successors&&r[1].successors.every(t=>void 0!==t.weight),r[1].isStochastic)for(let t of(r[1].weightSum=0,r[1].successors))r[1].weightSum+=t.weight;let n=r[0];if(!0===i&&this.productions.has(n)){let t=this.productions.get(n),s=t.successor,o=t.successors;s&&!o&&(t={successors:[t]}),t.successors.push(r[1]),this.productions.set(n,t)}else this.productions.set(n,r[1])}setProductions(t){if(void 0===t)throw Error("no production specified.");for(let s of(this.clearProductions(),Object.entries(t))){let t=s[0],o=s[1];this.setProduction(t,o,!0)}}clearProductions(){this.productions=new Map}setFinal(t,s){let o=[t,s];if(void 0===o)throw Error("no final specified.");this.finals.set(o[0],o[1])}setFinals(t){if(void 0===t)throw Error("no finals specified.");for(let s in this.finals=new Map,t)t.hasOwnProperty(s)&&this.setFinal(s,t[s])}getProductionResult(t,s,o,i,e=!1){let r=void 0!==t.leftCtx||void 0!==t.rightCtx,n=!1,c=!0;if(void 0!==t.condition&&!1===t.condition({index:s,currentAxiom:this.axiom,part:o,params:i})?c=!1:r&&(void 0!==t.leftCtx&&void 0!==t.rightCtx?c=this.match({direction:"left",match:t.leftCtx,index:s,branchSymbols:this.branchSymbols,ignoredSymbols:this.ignoredSymbols}).result&&this.match({direction:"right",match:t.rightCtx,index:s,branchSymbols:this.branchSymbols,ignoredSymbols:this.ignoredSymbols}).result:void 0!==t.leftCtx?c=this.match({direction:"left",match:t.leftCtx,index:s,branchSymbols:this.branchSymbols,ignoredSymbols:this.ignoredSymbols}).result:void 0!==t.rightCtx&&(c=this.match({direction:"right",match:t.rightCtx,index:s,branchSymbols:this.branchSymbols,ignoredSymbols:this.ignoredSymbols}).result)),!1===c)n=!1;else if(t.successors){let e,r;for(let c of(t.isStochastic&&(r=Math.random()*t.weightSum,e=0),t.successors)){if(t.isStochastic&&r>(e+=c.weight))continue;let a=this.getProductionResult(c,s,o,i,!0);if(void 0!==a&&!1!==a){n=a;break}}}else n="function"==typeof t.successor?t.successor({index:s,currentAxiom:this.axiom,part:o,params:i}):t.successor;return n||(e?n:o)}applyProductions(){let t="string"==typeof this.axiom?"":[],s=0;for(let o of this.axiom){let i=o.symbol||o,e=o.params||[],r=o;if(this.productions.has(i)){let t=this.productions.get(i);r=this.getProductionResult(t,s,o,e)}"string"==typeof t?t+=r:r instanceof Array?t.push(...r):t.push(r),s++}return this.axiom=t,t}iterate(t=1){let s;this.iterations=t;for(let o=0;t>o;o++)s=this.applyProductions();return s}final(t){let s=0;for(let o of this.axiom){let i=o;if("object"==typeof o&&o.symbol&&(i=o.symbol),this.finals.has(i)){let e=this.finals.get(i),r=typeof e;if("function"!==r)throw Error("\'"+i+"\' has an object for a final function. But it is __not a function__ but a "+r+"!");e({index:s,part:o},t)}s++}}match({axiom_:t,match:s,ignoredSymbols:o,branchSymbols:i,index:e,direction:r}){var n,c;let a=0,l=0;t=t||this.axiom,void 0===i&&(i=void 0!==this.branchSymbols?this.branchSymbols:[]),void 0===o&&(o=void 0!==this.ignoredSymbols?this.ignoredSymbols:[]);let u,h,f,d,m,y,b,g=[];if("right"===r)d=y=1,f=e+1,m=0,b=s.length,i.length>0&&(u=(n=i)[0],h=n[1]);else{if("left"!==r)throw Error(r,"is not a valid direction for matching.");d=y=-1,f=e-1,m=s.length-1,b=-1,i.length>0&&(h=(c=i)[0],u=c[1])}for(;f=0;f+=d){let i=t[f].symbol||t[f],e=s[m];if(i===e){if((0===a||l>0)&&(i===u?(l++,a++,m+=y):i===h?(l=Math.max(0,l-1),a=Math.max(0,a-1),0===l&&(m+=y)):(g.push(f),m+=y)),m===b)return{result:!0,matchIndices:g}}else if(i===u)a++,l>0&&l++;else if(i===h)a=Math.max(0,a-1),l>0&&(l=Math.max(0,l-1));else if((0===a||l>0&&e!==h)&&!1===o.includes(i))return{result:!1,matchIndices:g}}return{result:!1,matchIndices:g}}}LSystem.getStringResult=LSystem.getString,LSystem.transformClassicStochasticProductions=function(t){return function(){let s=t,o=s.length,i=Math.random();for(let t=0;o>t;t++)if((t+1)/o>=i)return s[t];console.error("Should have returned a result of the list, something is wrong here with the random numbers?.")}},LSystem.transformClassicCSProduction=o,LSystem.transformClassicParametricAxiom=function(t){let s=t.replace(/\\s+/g,"").split(/[\\(\\)]/),o=[];for(let t=0;t 2 | 3 | Testing experimental things 4 | 5 | 6 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 28 | 29 | 30 | 31 | 32 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /examples/multiple productions/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Testing experimental things 4 | 5 | 6 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 28 | 29 | 30 | 31 | 32 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /examples/primitive/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | A-Frame L-System Component - Hilbert Curve 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Testing experimental things 4 | 5 | 6 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 29 | 30 | 32 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /examples/tree/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | A-Frame L-System Component - Tree 4 | 5 | 6 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | if (typeof AFRAME === 'undefined') { 2 | throw new Error('Component attempted to register before AFRAME was available.'); 3 | } 4 | 5 | import LSystem from 'lindenmayer'; 6 | 7 | // As we use webpack for compiling the source, it's used to bundle the 8 | // web worker into a blob via: https://github.com/webpack/worker-loader 9 | import LSystemWorker from 'worker-loader?inline&fallback=false!./worker.js'; 10 | 11 | import './primitives/a-lsystem.js'; 12 | 13 | /** 14 | * Lindenmayer-System component for A-Frame. 15 | */ 16 | 17 | function parseFromTo(value) { 18 | let flatResult = value.split(/(\w)\s*:\s*/).filter(part => part.length !== 0); 19 | let result = []; 20 | for (var i = 0; i < flatResult.length; i+=2) { 21 | result.push([flatResult[i], flatResult[i+1]]); 22 | } 23 | return result; 24 | } 25 | 26 | AFRAME.registerComponent('lsystem', { 27 | schema: { 28 | 29 | axiom: { 30 | type: 'string', 31 | default: 'F' 32 | }, 33 | 34 | productions: { 35 | default: 'F:FF', 36 | // return an array of production tuples ([[from, to], ['F', 'F+F']]) 37 | parse: (value) => parseFromTo(value).map(([from, to]) => [from, to.replace(/\s/g, '')]) 38 | }, 39 | 40 | // A: blue line, red line, yellow line B: red line 41 | segmentMixins: { 42 | type: 'string', 43 | parse: function (value) { 44 | 45 | let mixinsForSymbol = new Map(); 46 | let result = parseFromTo(value); 47 | for (let [from, to] of result) { 48 | to = to.replace(/[\[\]]/g, '').split(','); 49 | mixinsForSymbol.set(from, to); 50 | } 51 | return mixinsForSymbol; 52 | } 53 | }, 54 | 55 | iterations: { 56 | type: 'int', 57 | default: 1 58 | }, 59 | 60 | angle: { 61 | default: 90.0 62 | }, 63 | 64 | translateAxis: { 65 | type: 'string', 66 | default: 'y', 67 | parse: function(value) { 68 | value = value.toLowerCase(); 69 | if (value === 'x') { 70 | return new THREE.Vector3(1, 0, 0); 71 | } else if (value === 'y') { 72 | return new THREE.Vector3(0, 1, 0); 73 | } else if (value === 'z') { 74 | return new THREE.Vector3(0, 0, 1); 75 | } else { 76 | throw new Error('translateAxis has to be a string: "x", "y" or "z"'); 77 | } 78 | } 79 | }, 80 | 81 | scaleFactor: { 82 | default: 1.0 83 | }, 84 | 85 | dynamicSegmentLength: { 86 | default: true 87 | }, 88 | 89 | mergeGeometries: { 90 | type: 'boolean', 91 | default: true 92 | }, 93 | 94 | functionsInProductions: { 95 | type: 'boolean', 96 | default: true 97 | } 98 | }, 99 | 100 | /** 101 | * Called once when component is attached. Generally for initial setup. 102 | */ 103 | init: function () { 104 | 105 | this.sceneEl = document.querySelector('a-scene'); 106 | 107 | let self = this; 108 | 109 | this.initWorker(); 110 | 111 | this.X = new THREE.Vector3(1, 0, 0); 112 | this.Y = new THREE.Vector3(0, 1, 0); 113 | this.Z = new THREE.Vector3(0, 0, 1); 114 | this.xPosRotation = new THREE.Quaternion(); 115 | this.xNegRotation = new THREE.Quaternion(); 116 | this.yPosRotation = new THREE.Quaternion(); 117 | this.yNegRotation = new THREE.Quaternion(); 118 | this.zPosRotation = new THREE.Quaternion(); 119 | this.zNegRotation = new THREE.Quaternion(); 120 | this.yReverseRotation = new THREE.Quaternion(); 121 | this.xPosRotation = new THREE.Quaternion(); 122 | this.xNegRotation = new THREE.Quaternion(); 123 | this.yPosRotation = new THREE.Quaternion(); 124 | this.yNegRotation = new THREE.Quaternion(); 125 | this.zPosRotation = new THREE.Quaternion(); 126 | this.zNegRotation = new THREE.Quaternion(); 127 | this.yReverseRotation = new THREE.Quaternion(); 128 | this.segmentLengthFactor = 1.0; 129 | 130 | this.transformationSegment = new THREE.Object3D(); 131 | this.transformationSegmentTemplate = this.transformationSegment.clone(); 132 | 133 | let scaleFactor = self.data.scaleFactor; 134 | 135 | this.colorIndex = 0; 136 | this.lineWidth = 0.0005; 137 | this.lineLength = 0.125; 138 | 139 | this.LSystem = new LSystem({ 140 | axiom: 'F', 141 | productions: {'F': 'F'}, 142 | finals: { 143 | /* As a default F is already defined as final, new ones get added automatically 144 | by parsing the segment mixins. If no segment mixin for any symbol is defined 145 | it wont get a final function and therefore not render. 146 | */ 147 | '+': () => { self.transformationSegment.quaternion.multiply(self.yPosRotation);}, 148 | '-': () => { self.transformationSegment.quaternion.multiply(self.yNegRotation);}, 149 | '&': () => { self.transformationSegment.quaternion.multiply(self.zNegRotation);}, 150 | '^': () => { self.transformationSegment.quaternion.multiply(self.zPosRotation);}, 151 | '\\': () =>{ self.transformationSegment.quaternion.multiply(self.xNegRotation);}, 152 | '<': () => { self.transformationSegment.quaternion.multiply(self.xNegRotation);}, 153 | '/': () => { self.transformationSegment.quaternion.multiply(self.xPosRotation);}, 154 | '>': () => { self.transformationSegment.quaternion.multiply(self.xPosRotation);}, 155 | '|': () => { self.transformationSegment.quaternion.multiply(self.yReverseRotation);}, 156 | '!': () => { 157 | self.segmentLengthFactor *= scaleFactor; 158 | self.transformationSegment.scale.set( 159 | self.transformationSegment.scale.x *= scaleFactor, self.transformationSegment.scale.y *= scaleFactor, self.transformationSegment.scale.z *= scaleFactor 160 | ); 161 | self.colorIndex++; 162 | }, 163 | '\'': () => { 164 | self.segmentLengthFactor *= (1.0 / scaleFactor); 165 | self.transformationSegment.scale.set( 166 | self.transformationSegment.scale.x *= (1.0 / scaleFactor), self.transformationSegment.scale.y *= (1.0 / scaleFactor), self.transformationSegment.scale.z *= (1.0 / scaleFactor) 167 | ); 168 | self.colorIndex = Math.max(0, self.colorIndex - 1); 169 | }, 170 | '[': () => { self.stack.push(self.transformationSegment.clone()); }, 171 | ']': () => { self.transformationSegment = self.stack.pop(); } 172 | } 173 | }); 174 | 175 | }, 176 | 177 | /** 178 | * Called when component is attached and when component data changes. 179 | * Generally modifies the entity based on the data. 180 | */ 181 | update: function (oldData) { 182 | // var diffData = diff(data, oldData || {}); 183 | // console.log(diffData); 184 | 185 | // TODO: Check if only angle changed or axiom or productions 186 | // 187 | let self = this; 188 | 189 | 190 | if (this.data.mergeGeometries === false && this.segmentElementGroupsMap !== undefined) { 191 | for (let segmentElGroup of this.segmentElementGroupsMap.values()) { 192 | segmentElGroup.removeObject3D('mesh'); 193 | segmentElGroup.innerHTML = ''; 194 | } 195 | } 196 | 197 | if (Object.keys(oldData).length === 0) { 198 | this.updateLSystem(); 199 | this.updateSegmentMixins(); 200 | this.updateTurtleGraphics(); 201 | 202 | } else { 203 | 204 | let visualChange = false; 205 | 206 | 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))) { 207 | 208 | this.updateLSystem(); 209 | visualChange = true; 210 | 211 | } 212 | 213 | if (oldData.segmentMixins !== undefined && JSON.stringify(Array.from(oldData.segmentMixins.entries())) !== JSON.stringify(Array.from(this.data.segmentMixins.entries())) ) { 214 | this.updateSegmentMixins(); 215 | visualChange = true; 216 | 217 | 218 | } 219 | 220 | if (visualChange || oldData.angle && oldData.angle !== this.data.angle) { 221 | 222 | this.updateTurtleGraphics(); 223 | 224 | } else { 225 | // console.log('nothing changed in update?'); 226 | // this.updateLSystem(); 227 | // this.updateSegmentMixins(); 228 | } 229 | } 230 | 231 | }, 232 | 233 | // if this.dynamicSegmentLength===true use this function to set the length 234 | // depending on segments geometries bbox 235 | calculateSegmentLength: function (mixin, geometry) { 236 | if (this.segmentLengthMap.has(mixin)) return this.segmentLengthMap.get(mixin); 237 | geometry.computeBoundingBox(); 238 | let segmentLength; 239 | if (this.data.translateAxis.equals(this.X) ) { 240 | segmentLength = Math.abs(geometry.boundingBox.min.x - geometry.boundingBox.max.x); 241 | } else if (this.data.translateAxis.equals(this.Y)) { 242 | segmentLength = Math.abs(geometry.boundingBox.min.y - geometry.boundingBox.max.y); 243 | } else if (this.data.translateAxis.equals(this.Z)) { 244 | segmentLength = Math.abs(geometry.boundingBox.min.z - geometry.boundingBox.max.z); 245 | } 246 | this.segmentLengthMap.set(mixin, segmentLength); 247 | return segmentLength; 248 | 249 | }, 250 | 251 | initWorker: function() { 252 | this.worker = new LSystemWorker(); 253 | }, 254 | 255 | pushSegment: function(symbol) { 256 | 257 | let self = this; 258 | let currentQuaternion = self.transformationSegment.quaternion; 259 | let currentPosition = self.transformationSegment.position; 260 | let currentScale = self.transformationSegment.scale; 261 | 262 | 263 | // Cap colorIndex to maximum mixins defined for the symbol. 264 | let cappedColorIndex = Math.min(this.colorIndex, this.data.segmentMixins.get(symbol).length - 1); 265 | 266 | let mixin = this.mixinMap.get(symbol + cappedColorIndex); 267 | 268 | if (this.data.mergeGeometries === false) { 269 | let newSegment = document.createElement('a-entity'); 270 | newSegment.setAttribute('mixin', mixin); 271 | 272 | newSegment.addEventListener('loaded', (e) => { 273 | // Offset child element of object3D, to rotate around end point 274 | // IMPORTANT: It may change that A-Frame puts objects into a group 275 | 276 | let segmentLength = self.segmentLengthMap.get(mixin); 277 | 278 | newSegment.object3D.children[0].translateOnAxis(self.data.translateAxis, (segmentLength * self.segmentLengthFactor) / 2); 279 | 280 | newSegment.object3D.quaternion.copy(currentQuaternion); 281 | newSegment.object3D.position.copy(currentPosition); 282 | newSegment.object3D.scale.copy(currentScale); 283 | }, 284 | {once: true}); 285 | this.segmentElementGroupsMap.get(symbol + cappedColorIndex).appendChild(newSegment); 286 | 287 | } else { 288 | let segmentObject3D = this.segmentObjects3DMap.get(symbol + cappedColorIndex); 289 | let newSegmentObject3D = segmentObject3D.clone(); 290 | newSegmentObject3D.matrixAutoUpdate = false; 291 | newSegmentObject3D.quaternion.copy(currentQuaternion); 292 | newSegmentObject3D.position.copy(currentPosition); 293 | newSegmentObject3D.scale.copy(currentScale); 294 | 295 | newSegmentObject3D.updateMatrix(); 296 | this.mergeGroups.get(symbol + cappedColorIndex).geometry.merge(newSegmentObject3D.geometry, newSegmentObject3D.matrix); 297 | } 298 | let segmentLength = this.segmentLengthMap.get(mixin); 299 | this.transformationSegment.translateOnAxis(this.data.translateAxis, segmentLength * this.segmentLengthFactor); 300 | }, 301 | 302 | updateLSystem: function () { 303 | let self = this; 304 | 305 | // post params to worker 306 | let params = { 307 | axiom: this.data.axiom, 308 | productions: this.data.productions, 309 | iterations: this.data.iterations 310 | }; 311 | 312 | if (Date.now() - this.worker.startTime > 1000 ) { 313 | // if we got user input, but worker is running for over a second 314 | // terminate old worker and start new one. 315 | this.worker.terminate(); 316 | this.initWorker(); 317 | } 318 | 319 | this.worker.startTime = Date.now(); 320 | 321 | this.workerPromise = new Promise((resolve, reject) => { 322 | 323 | this.worker.onmessage = (e) => { 324 | self.LSystem.setAxiom(e.data.result); 325 | resolve(); 326 | }; 327 | }); 328 | 329 | this.worker.postMessage(params); 330 | return this.workerPromise; 331 | }, 332 | 333 | updateSegmentMixins: function () { 334 | let self = this; 335 | 336 | this.el.innerHTML = ''; 337 | 338 | // Map for remembering the elements holding differnt segment types 339 | this.segmentElementGroupsMap = new Map(); 340 | 341 | 342 | this.mixinMap = new Map(); 343 | // Construct a map with keys = `symbol + colorIndex` from data.segmentMixins 344 | for (let [symbol, mixinList] of this.data.segmentMixins) { 345 | for (let i = 0; i < mixinList.length; i++) { 346 | this.mixinMap.set(symbol + i, mixinList[i]); 347 | } 348 | } 349 | 350 | // Map for buffering geometries for use in pushSegments() 351 | // when merging geometries ourselves and not by appending a `mixin` attributes, 352 | // as done with `mergeGeometry = false`. 353 | this.segmentObjects3DMap = new Map(); 354 | 355 | this.segmentLengthMap = new Map(); 356 | this.mergeGroups = new Map(); 357 | 358 | this.mixinPromises = []; 359 | 360 | 361 | // Collect mixin info by pre-appending segment elements with their mixin 362 | // Then use the generated geometry etc. 363 | if (this.data.segmentMixins && this.data.segmentMixins.length !== 0) { 364 | 365 | // Go through every symbols segmentMixins as defined by user 366 | for (let el of this.data.segmentMixins) { 367 | let [symbol, mixinList] = el; 368 | // Set final functions for each symbol that has a mixin defined 369 | this.LSystem.setFinal(symbol, () => {self.pushSegment.bind(self, symbol)();}); 370 | 371 | // And iterate the MixinList to buffer the segments or calculate segment lengths… 372 | for (let i = 0; i < mixinList.length; i++) { 373 | let mixinColorIndex = i; 374 | let mixin = mixinList[mixinColorIndex]; 375 | 376 | self.mixinPromises.push(new Promise((resolve, reject) => { 377 | // Save mixinColorIndex for async promise below. 378 | 379 | let segmentElGroup = document.createElement('a-entity'); 380 | segmentElGroup.setAttribute('id', mixin + '-group-' + mixinColorIndex + Math.floor(Math.random() * 10000)); 381 | 382 | // TODO: Put it all under this.mergeData 383 | segmentElGroup.setAttribute('geometry', 'buffer', false); 384 | segmentElGroup.setAttribute('mixin', mixin); 385 | segmentElGroup.addEventListener('loaded', function (e) { 386 | let segmentObject = segmentElGroup.getObject3D('mesh').clone(); 387 | 388 | // Make sure the geometry is actually unique 389 | // AFrame sets the same geometry for multiple entities. As we modify 390 | // the geometry per entity we need to have unique geometry instances. 391 | // TODO: hm, maybe try to use instanced geometry and offset on object? 392 | segmentElGroup.getObject3D('mesh').geometry.dispose(); 393 | segmentObject.geometry = (segmentObject.geometry.clone()); 394 | 395 | let segmentLength = self.calculateSegmentLength(mixin, segmentObject.geometry); 396 | 397 | // Do some additional stuff like buffering 3D objects / geometry 398 | // if we want to merge geometries. 399 | if (self.data.mergeGeometries === true) { 400 | 401 | // Offset geometry by half segmentLength to get the rotation point right. 402 | let translation = self.data.translateAxis.clone().multiplyScalar((segmentLength * self.segmentLengthFactor)/2); 403 | 404 | 405 | 406 | 407 | // IMPORTANT!!! 408 | // TODO: Try to use pivot object instead of translating geometry 409 | // this may help in reusing geometry and not needing to clone it (see above)? 410 | // see: https://github.com/mrdoob/three.js/issues/1364 411 | //and 412 | // see: http://stackoverflow.com/questions/28848863/threejs-how-to-rotate-around-objects-own-center-instead-of-world-center 413 | segmentObject.geometry.translate(translation.x, translation.y, translation.z); 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | self.segmentObjects3DMap.set(symbol + mixinColorIndex, segmentObject ); 422 | 423 | } 424 | 425 | segmentElGroup.removeObject3D('mesh'); 426 | resolve(); 427 | }, {once: true}); 428 | 429 | 430 | if (this.segmentElementGroupsMap.has(symbol + mixinColorIndex)) { 431 | let previousElGroup = this.segmentElementGroupsMap.get(symbol + mixinColorIndex); 432 | this.segmentElementGroupsMap.delete(symbol + mixinColorIndex); 433 | this.el.removeChild(previousElGroup); 434 | } 435 | 436 | this.segmentElementGroupsMap.set(symbol + mixinColorIndex, segmentElGroup); 437 | this.el.appendChild(segmentElGroup); 438 | 439 | 440 | })); 441 | } 442 | } 443 | } 444 | }, 445 | 446 | updateTurtleGraphics: async function() { 447 | 448 | await Promise.all([...this.mixinPromises, this.workerPromise]); 449 | // The main segment used for saving transformations (rotation, translation, scale(?)) 450 | this.transformationSegment.copy(this.transformationSegmentTemplate); 451 | 452 | // set merge groups 453 | if (this.data.mergeGeometries === true) 454 | for (let [id, segmentObject] of this.segmentObjects3DMap) { 455 | this.mergeGroups.set(id, new THREE.Mesh( 456 | new THREE.Geometry(), segmentObject.material 457 | )); 458 | } 459 | 460 | 461 | // We push copies of this.transformationSegment on branch symbols inside this array. 462 | this.stack = []; 463 | 464 | 465 | let angle = this.data.angle; 466 | 467 | // Set quaternions based on angle slider 468 | this.xPosRotation.setFromAxisAngle( this.X, (Math.PI / 180) * angle ); 469 | this.xNegRotation.setFromAxisAngle( this.X, (Math.PI / 180) * -angle ); 470 | 471 | this.yPosRotation.setFromAxisAngle( this.Y, (Math.PI / 180) * angle ); 472 | this.yNegRotation.setFromAxisAngle( this.Y, (Math.PI / 180) * -angle ); 473 | this.yReverseRotation.setFromAxisAngle( this.Y, (Math.PI / 180) * 180 ); 474 | 475 | this.zPosRotation.setFromAxisAngle( this.Z, (Math.PI / 180) * angle ); 476 | this.zNegRotation.setFromAxisAngle( this.Z, (Math.PI / 180) * -angle ); 477 | // 478 | // this.geometry = new THREE.CylinderGeometry(this.lineWidth, this.lineWidth, self.data.lineLength, 3); 479 | // this.geometry.rotateZ((Math.PI / 180) * 90); 480 | // this.geometry.translate( -(this.data.segmentLength/2), 0, 0 ); 481 | // for (let face of this.geometry.faces) { 482 | // face.color.setHex(this.colors[colorIndex]); 483 | // } 484 | // this.geometry.colorsNeedUpdate = true; 485 | 486 | this.LSystem.final(); 487 | // finally set the merged meshes to be visible. 488 | if (this.data.mergeGeometries === true) { 489 | for (let tuple of this.segmentElementGroupsMap) { 490 | let [symbolWithColorIndex, elGroup] = tuple; 491 | 492 | let mergeGroup = this.mergeGroups.get(symbolWithColorIndex); 493 | // Remove unused element groups inside our element 494 | if (mergeGroup.geometry.vertices.length === 0) { 495 | this.el.removeChild(elGroup); 496 | } else { 497 | elGroup.setObject3D('mesh', this.mergeGroups.get(symbolWithColorIndex)); 498 | elGroup.setAttribute('mixin', this.mixinMap.get(symbolWithColorIndex)); 499 | } 500 | } 501 | } 502 | 503 | }, 504 | /** 505 | * Called when a component is removed (e.g., via removeAttribute). 506 | * Generally undoes all modifications to the entity. 507 | */ 508 | remove: function () { 509 | 510 | }, 511 | 512 | /** 513 | * Called on each scene tick. 514 | */ 515 | tick: function (t) { 516 | // console.log(this.parentEl === undefined); 517 | // console.log('\nTICK\n', t); 518 | }, 519 | 520 | /** 521 | * Called when entity pauses. 522 | * Use to stop or remove any dynamic or background behavior such as events. 523 | */ 524 | pause: function () { 525 | }, 526 | 527 | /** 528 | * Called when entity resumes. 529 | * Use to continue or add any dynamic or background behavior such as events. 530 | */ 531 | play: function () { 532 | }, 533 | }); 534 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aframe-lsystem-component", 3 | "version": "0.2", 4 | "description": "L-System/LSystem component for A-Frame to draw 3D turtle graphics. Using Lindenmayer as backend.", 5 | "main": "dist/aframe-lsystem-component.js", 6 | "browser": "dist/aframe-lsystem-component.min.js", 7 | "scripts": { 8 | "dev": "npm run dist; cp dist/aframe-lsystem-component.min.js examples/libs/aframe-lsystem-component.js; cp node_modules/aframe/dist/aframe-master.js examples/libs/aframe.js", 9 | "dist": "webpack --module-bind worker-loader && webpack --config webpack.config.minify.js --module-bind worker-loader", 10 | "postpublish": "npm run dist", 11 | "preghpages": "rm -rf gh-pages && mkdir gh-pages && cp -r examples/* gh-pages", 12 | "ghpages": "npm run dev && npm run preghpages && ghpages -p gh-pages" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/nylki/aframe-lsystem-component.git" 17 | }, 18 | "keywords": [ 19 | "aframe", 20 | "aframe-component", 21 | "lsystem", 22 | "turtle graphics", 23 | "l-system", 24 | "fractal", 25 | "aframe-vr", 26 | "vr", 27 | "mozvr", 28 | "webvr" 29 | ], 30 | "author": "Tom Brewe ", 31 | "license": "MIT", 32 | "bugs": { 33 | "url": "https://github.com/nylki/aframe-lsystem-component/issues" 34 | }, 35 | "homepage": "https://github.com/nylki/aframe-lsystem-component#readme", 36 | "devDependencies": { 37 | "aframe": "^0.7.1", 38 | "babel-core": "^6.26.3", 39 | "babel-loader": "^6.4.1", 40 | "babel-preset-env": "^1.7.0", 41 | "ghpages": "^0.0.8", 42 | "randomcolor": "^0.4.4", 43 | "uglify-es": "^3.3.8", 44 | "uglifyjs-webpack-plugin": "^1.1.6", 45 | "webpack": "^3.12.0", 46 | "worker-loader": "^0.8.1" 47 | }, 48 | "dependencies": { 49 | "lindenmayer": "^1.5.0" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /primitives/a-lsystem.js: -------------------------------------------------------------------------------- 1 | AFRAME.registerPrimitive('a-lsystem', { 2 | defaultComponents: { 3 | lsystem: { 4 | axiom: 'F', 5 | productions: 'F:F++F++F++F', 6 | iterations: 3, 7 | angle: 60 8 | } 9 | }, 10 | 11 | mappings: { 12 | axiom: 'lsystem.axiom', 13 | productions: 'lsystem.productions', 14 | segmentMixins: 'lsystem.segmentMixins', 15 | iterations: 'lsystem.iterations', 16 | angle: 'lsystem.angle' 17 | } 18 | }); 19 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); 3 | const webpack = require('webpack'); 4 | 5 | module.exports = { 6 | target: 'web', 7 | entry: './index.js', 8 | output: { 9 | path: path.resolve(__dirname, 'dist'), 10 | filename: 'aframe-lsystem-component.min.js' 11 | }, 12 | module: { 13 | rules: [ 14 | { 15 | test: /\.js$/, 16 | exclude: /(node_modules)/, 17 | use: { 18 | loader: 'babel-loader', 19 | options: { 20 | babelrc: true 21 | } 22 | } 23 | }, 24 | 25 | ] 26 | }, 27 | plugins: [ 28 | 29 | ], 30 | }; 31 | -------------------------------------------------------------------------------- /webpack.config.minify.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); 3 | const webpack = require('webpack'); 4 | 5 | module.exports = { 6 | target: 'web', 7 | entry: './index.js', 8 | output: { 9 | path: path.resolve(__dirname, 'dist'), 10 | filename: 'aframe-lsystem-component.min.js' 11 | }, 12 | module: { 13 | rules: [ 14 | { 15 | test: /\.js$/, 16 | exclude: /(node_modules)/, 17 | use: { 18 | loader: 'babel-loader', 19 | options: { 20 | babelrc: true 21 | } 22 | } 23 | }, 24 | 25 | ] 26 | }, 27 | plugins: [ 28 | new UglifyJSPlugin({ 29 | sourceMap: true, 30 | uglifyOptions: { 31 | compress: {dead_code: true, conditionals: true, evaluate: true, loops: true, unused: true, reduce_vars: true, passes: 1, hoist_funs: true, hoist_vars: true, inline: true, keep_fargs: false, unsafe: true, comparisons: true, unsafe_comps: true}, 32 | mangle: {keep_fnames: false, reserved: ['LSystem', 'LSystemWorker', 'worker'], toplevel: true}, 33 | ie8: false 34 | } 35 | }) 36 | ], 37 | }; 38 | -------------------------------------------------------------------------------- /worker.js: -------------------------------------------------------------------------------- 1 | // Require instead of importScripts because we use webpack 2 | // with worker-loader for compiling source: https://github.com/webpack/worker-loader 3 | import LSystem from 'lindenmayer'; 4 | let lsystem = new LSystem({}); 5 | let timeout = {}; 6 | 7 | onmessage = function(e) { 8 | // wait a few ms to start thread, to be able to cancel old tasks 9 | clearTimeout(timeout); 10 | timeout = setTimeout(function() { 11 | 12 | lsystem.setAxiom(e.data.axiom); 13 | 14 | lsystem.clearProductions(); 15 | for (let p of e.data.productions) { 16 | lsystem.setProduction(p[0], p[1]); 17 | } 18 | lsystem.iterate(e.data.iterations); 19 | 20 | postMessage({ 21 | result: lsystem.getString(), 22 | initial: e.data 23 | }); 24 | 25 | }, 20); 26 | 27 | }; 28 | --------------------------------------------------------------------------------