diff --git a/README.md b/README.md index 5ca56be..3c439e0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,44 @@ The objective of this assignment is to create an L System parser and generate interesting looking plants. Start by forking and then cloning this repository: [https://github.com/CIS700-Procedural-Graphics/Project3-LSystems](https://github.com/CIS700-Procedural-Graphics/Project3-LSystems) +# Description + +**Linked List:** + +I began by implementing a basic doubly-linked list in `lsystem.js` using the new ES6 class methodology. I also added a file `tests.js` (and added an npm command `npm run tests`) where I tested out the linked list functionality. + +**LSystem Implementation:** + +Then, I added additional methods to my linked list implementation to transform it into more of an "LSystem" use case. For example, I wrote the method `replaceNode()` that replaces a character in the linked list with its replacement rule, and the method `doIterations()`, which runs multiple iterations of the LSystem replacement process. My LSystem can also handle multiple rules with a given probability distribution. + +**Turtle:** + +After finishing the linked list and LSystem, I started on the turtle implementation. I added a number of member variables to my turtle (including `rotY`, `rotZ`, `flowerColor`, etc...) to allow for the turtle's rendering to be customized by the user. I added four additional grammar rules, listed below: + +- `<`: Rotate in the Y direction X degrees +- `>`: Rotate in the Y direction -X degrees +- `O`: Draw a flower +- `L`: Draw a leaf + +I also added THREE.JS geometries for leaves and flowers (which are essentially just basic shapes). + +**GUI:** + +Almost every aspect of the turtle and the LSystem can be customized in the dat.gui sidebar. The LSystem iterations, initial axiom, and rules (probabilities and replacements) can all be tweaked. Additionally, the turtle's rotations (in both Y and Z), cylinder dimensions, and all colors can be customized. + +**Design technique:** + +To be honest, I essentially just added and tweaked rules here and there until I was able to generate a plant I thought looked cool. I experimented with different probabilities, and tried to position leaves and flowers in reasonable looking positions. + +**Screenshots:** + +![Screenshot A](https://raw.githubusercontent.com/zelliott/Project3-LSystems/master/images/plant_a.png) + +![Screenshot B](https://raw.githubusercontent.com/zelliott/Project3-LSystems/master/images/plant_b.png) + +![Screenshot C](https://raw.githubusercontent.com/zelliott/Project3-LSystems/master/images/plant_c.png) + + # L-System Parser lsystem.js contains classes for L-system, Rule, and LinkedList. Here’s our suggested structure: @@ -57,7 +95,7 @@ Using dat.GUI and the examples provided in the reference code, make some aspect Design a grammar for a new procedural plant! As the preceding parts of this assignment are basic computer science tasks, this is where you should spend the bulk of your time on this assignment. Come up with new grammar rules and include screenshots of your plants in your README. For inspiration, take a look at Example 7: Fractal Plant in Wikipedia: https://en.wikipedia.org/wiki/L-system Your procedural plant must have the following features -1. Grow in 3D. Take advantage of three.js! +1. Grow in 3D. Take advantage of three.js! 2. Have flowers or leaves that are added as a part of the grammar 3. Variation. Different instances of your plant should look distinctly different! 4. A twist. Broccoli trees are cool and all, but we hope to see sometime a little more surprising in your grammars diff --git a/compiled/src/lsystem.js b/compiled/src/lsystem.js new file mode 100644 index 0000000..e7a75fc --- /dev/null +++ b/compiled/src/lsystem.js @@ -0,0 +1,290 @@ +// A class that represents a symbol replacement rule to +// be used when expanding an L-system grammar. +'use strict'; + +Object.defineProperty(exports, '__esModule', { + value: true +}); + +var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + +exports.stringToLinkedList = stringToLinkedList; +exports.linkedListToString = linkedListToString; +exports['default'] = Lsystem; + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } + +function Rule(prob, str) { + this.probability = prob; // The probability that this Rule will be used when replacing a character in the grammar string + this.successorString = str; // The string that will replace the char that maps to this Rule +} + +var ListNode = function ListNode(data) { + _classCallCheck(this, ListNode); + + this.prev = null; + this.next = null; + this.data = data; +}; + +exports.ListNode = ListNode; + +var LinkedList = (function () { + function LinkedList() { + _classCallCheck(this, LinkedList); + + this.head = null; + this.tail = null; + } + + // TODO: Turn the string into linked list + + _createClass(LinkedList, [{ + key: 'add', + value: function add(node) { + + if (!this.tail) { + this.head = node; + this.tail = node; + + return; + } + + node.prev = this.tail; + this.tail.next = node; + this.tail = node; + } + }, { + key: 'addFront', + value: function addFront(node) { + var n = this.head; + + this.head = node; + node.next = n; + n.prev = node; + } + }, { + key: 'addBack', + value: function addBack(node) { + var n = this.tail; + + this.tail = node; + n.next = node; + node.prev = n; + } + }, { + key: 'addAt', + value: function addAt(index, node) { + var n = this.head; + var i = 0; + var size = this.size(); + + if (index == 0) { + this.addFront(node); + return; + } + + if (index == size) { + this.addBack(node); + return; + } + + while (n) { + if (i == index) { + var prev = n.prev; + var next = n; + + if (prev) { + prev.next = node; + } + + node.prev = prev; + node.next = next; + + if (next) { + next.prev = node; + } + + return; + } + + n = n.next; + i++; + } + + throw new Error('Unable to add node at this index'); + } + }, { + key: 'removeFront', + value: function removeFront() { + var n = this.head; + + this.head = n.next; + this.head.prev = null; + } + }, { + key: 'removeBack', + value: function removeBack() { + var n = this.tail; + + this.tail = n.prev; + this.tail.next = null; + } + }, { + key: 'removeAt', + value: function removeAt(index) { + var n = this.head; + var i = 0; + var size = this.size(); + + if (index == 0) { + this.removeFront(); + return; + } + + if (index == size) { + this.removeBack(); + return; + } + + while (n) { + if (i == index) { + var prev = n.prev; + var next = n.next; + + if (prev) { + prev.next = next; + } + + if (next) { + next.prev = prev; + } + + return; + } + + n = n.next; + i++; + } + + throw new Error('Unable to remove node at this index'); + } + }, { + key: 'getAt', + value: function getAt(index) { + var n = this.head; + var i = 0; + + while (n) { + if (i == index) { + return n.data; + } + + n = n.next; + i++; + } + + throw new Error('Unable to get node at this index'); + } + }, { + key: 'clear', + value: function clear() { + this.head = null; + this.tail = null; + } + }, { + key: 'size', + value: function size() { + var n = this.head; + var i = 0; + + while (n) { + i++; + n = n.next; + } + + return i; + } + }, { + key: 'print', + value: function print() { + var n = this.head; + var ret = []; + + while (n) { + ret.push(n.data); + n = n.next; + } + + return ret; + } + }]); + + return LinkedList; +})(); + +exports.LinkedList = LinkedList; + +function stringToLinkedList(input_string) { + // ex. assuming input_string = "F+X" + // you should return a linked list where the head is + // at Node('F') and the tail is at Node('X') + var ll = new LinkedList(); + return ll; +} + +// TODO: Return a string form of the LinkedList + +function linkedListToString(linkedList) { + // ex. Node1("F")->Node2("X") should be "FX" + var result = ""; + return result; +} + +// TODO: Given the node to be replaced, +// insert a sub-linked-list that represents replacementString +function replaceNode(linkedList, node, replacementString) {} + +function Lsystem(axiom, grammar, iterations) { + // default LSystem + this.axiom = "FX"; + this.grammar = {}; + this.grammar['X'] = [new Rule(1.0, '[-FX][+FX]')]; + this.iterations = 0; + + // Set up the axiom string + if (typeof axiom !== "undefined") { + this.axiom = axiom; + } + + // Set up the grammar as a dictionary that + // maps a single character (symbol) to a Rule. + if (typeof grammar !== "undefined") { + this.grammar = Object.assign({}, grammar); + } + + // Set up iterations (the number of times you + // should expand the axiom in DoIterations) + if (typeof iterations !== "undefined") { + this.iterations = iterations; + } + + // A function to alter the axiom string stored + // in the L-system + this.updateAxiom = function (axiom) { + // Setup axiom + if (typeof axiom !== "undefined") { + this.axiom = axiom; + } + }; + + // TODO + // This function returns a linked list that is the result + // of expanding the L-system's axiom n times. + // The implementation we have provided you just returns a linked + // list of the axiom. + this.doIterations = function (n) { + var lSystemLL = StringToLinkedList(this.axiom); + return lSystemLL; + }; +} \ No newline at end of file diff --git a/compiled/src/tests.js b/compiled/src/tests.js new file mode 100644 index 0000000..1bd6614 --- /dev/null +++ b/compiled/src/tests.js @@ -0,0 +1,11 @@ +'use strict'; + +var _lsystemJs = require('./lsystem.js'); + +var list = new _lsystemJs.LinkedList(); +list.add(new _lsystemJs.ListNode(0)); +list.add(new _lsystemJs.ListNode(1)); +list.add(new _lsystemJs.ListNode(2)); + +console.log(list.getAt(2)); +console.log(list.size()); \ No newline at end of file diff --git a/images/plant_a.png b/images/plant_a.png new file mode 100644 index 0000000..5ada824 Binary files /dev/null and b/images/plant_a.png differ diff --git a/images/plant_b.png b/images/plant_b.png new file mode 100644 index 0000000..6eaf07b Binary files /dev/null and b/images/plant_b.png differ diff --git a/images/plant_c.png b/images/plant_c.png new file mode 100644 index 0000000..38cd891 Binary files /dev/null and b/images/plant_c.png differ diff --git a/package.json b/package.json index be683fc..8b957a7 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,8 @@ "scripts": { "start": "webpack-dev-server --hot --inline", "build": "webpack", - "deploy": "node deploy.js" + "deploy": "node deploy.js", + "tests": "babel src/tests.js src/lsystem.js -d compiled && node compiled/src/tests.js" }, "gh-pages-deploy": { "prep": [ @@ -13,6 +14,7 @@ "dependencies": { "dat-gui": "^0.5.0", "gl-matrix": "^2.3.2", + "lodash": "^4.17.4", "stats-js": "^1.0.0-alpha1", "three": "^0.82.1", "three-orbit-controls": "^82.1.0" diff --git a/src/lsystem.js b/src/lsystem.js index e643b6d..fbbe0ac 100644 --- a/src/lsystem.js +++ b/src/lsystem.js @@ -1,76 +1,358 @@ // A class that represents a symbol replacement rule to // be used when expanding an L-system grammar. function Rule(prob, str) { - this.probability = prob; // The probability that this Rule will be used when replacing a character in the grammar string - this.successorString = str; // The string that will replace the char that maps to this Rule + this.probability = prob; + this.successorString = str; } -// TODO: Implement a linked list class and its requisite functions -// as described in the homework writeup +export class ListNode { + constructor(data) { + this.prev = null; + this.next = null; + this.data = data; + } +} + +export class LinkedList { + constructor() { + this.head = null; + this.tail = null; + } + + add(node) { + + if (!this.tail) { + this.head = node; + this.tail = node; + + return; + } + + node.prev = this.tail; + this.tail.next = node; + this.tail = node; + } + + addFront(node) { + let n = this.head; + + this.head = node; + node.next = n; + n.prev = node; + } + + addBack(node) { + let n = this.tail; + + this.tail = node; + n.next = node; + node.prev = n; + } + + addAt(index, node) { + let n = this.head; + let i = 0; + let size = this.size(); + + if (index == 0) { + this.addFront(node); + return; + } + + if (index == size) { + this.addBack(node); + return; + } + + while (n) { + if (i == index) { + let prev = n.prev; + let next = n; + + if (prev) { + prev.next = node; + } + + node.prev = prev; + node.next = next; + + if (next) { + next.prev = node; + } + + return; + } + + n = n.next; + i++; + } + + throw new Error('Unable to add node at this index'); + } + + removeFront() { + let n = this.head; + + this.head = n.next; + this.head.prev = null; + } + + removeBack() { + let n = this.tail; + + this.tail = n.prev; + this.tail.next = null; + } + + removeAt(index) { + let n = this.head; + let i = 0; + let size = this.size(); + + if (index == 0) { + this.removeFront(); + return; + } + + if (index == size) { + this.removeBack(); + return; + } + + while (n) { + if (i == index) { + let prev = n.prev; + let next = n.next; + + if (prev) { + prev.next = next; + } + + if (next) { + next.prev = prev; + } + + return; + } + + n = n.next; + i++; + } + + throw new Error('Unable to remove node at this index'); + } + + getAt(index) { + let n = this.head; + let i = 0; + + while (n) { + if (i == index) { + return n.data; + } + + n = n.next; + i++; + } + + throw new Error('Unable to get node at this index'); + } -// TODO: Turn the string into linked list -export function stringToLinkedList(input_string) { - // ex. assuming input_string = "F+X" - // you should return a linked list where the head is - // at Node('F') and the tail is at Node('X') + clear() { + this.head = null; + this.tail = null; + } + + size() { + let n = this.head; + let i = 0; + + while (n) { + i++; + n = n.next; + } + + return i; + } + + print() { + let n = this.head; + let ret = []; + + while (n) { + ret.push(n.data); + n = n.next; + } + + return ret; + } + + fromString(input) { + let arr = input.split(''); + + for (let i = 0; i < arr.length; i++) { + let n = new ListNode(arr[i]); + this.add(n); + } + } + + toString() { + return this.print().join(''); + } +} + +// Turn the string into linked list +export function StringToLinkedList(input_string) { var ll = new LinkedList(); + ll.fromString(input_string); + return ll; } -// TODO: Return a string form of the LinkedList -export function linkedListToString(linkedList) { - // ex. Node1("F")->Node2("X") should be "FX" - var result = ""; +// Return a string form of the LinkedList +export function LinkedListToString(linkedList) { + var result = linkedList.toString(); + return result; } -// TODO: Given the node to be replaced, -// insert a sub-linked-list that represents replacementString -function replaceNode(linkedList, node, replacementString) { +// Replace node with the nodes generated from rules +function replaceNode(linkedList, node, index, rules) { + var ranges = []; + var start = 0; + + // Convert the probabilities to ranges + for (var i = 0; i < rules.length; i++) { + var rule = rules[i]; + var prob = rule.probability; + + var range = { + lo: start, + hi: start + prob + }; + + ranges.push(range); + start += prob; + } + + var i = 0; + var rand = Math.random() * start; + + // Choose a random number and select the corresponding range + for (var i = 0; i < ranges.length; i++) { + var range = ranges[i]; + + if (rand >= range.lo && rand < range.hi) { + break; + } + } + + var replace = rules[i].successorString; + var arr = replace.split(''); + var next = node.next; + var start = node; + + // Based on our rule selection, make the replacement. + for (var i = 0; i < arr.length; i++) { + var a = arr[i]; + var n = new ListNode(a); + + node.next = n; + n.prev = node; + + node = n; + } + + if (next) { + node.next = next; + next.prev = node; + } + + linkedList.removeAt(index); + + return replace.length; } export default function Lsystem(axiom, grammar, iterations) { - // default LSystem - this.axiom = "FX"; + + // Default LSystem + this.axiom = 'FX'; this.grammar = {}; this.grammar['X'] = [ - new Rule(1.0, '[-FX][+FX]') + new Rule(0.25, '[+F][>FF { + var i = 1; + var grammarFolder = gui.addFolder(char); + + _.each(rules, rule => { + + grammarFolder.add(rule, 'probability').name('Rule ' + i + ' prob').onFinishChange(function(newVal) { + doLsystem(lsys, lsys.iterations, turtle); + }); + + grammarFolder.add(rule, 'successorString').name('Rule ' + i + ' replace').onFinishChange(function(newVal) { + doLsystem(lsys, lsys.iterations, turtle); + }); + + i++; + }); + }); + + + var turtleFolder = gui.addFolder('Turtle'); + + turtleFolder.add(turtle, 'rotY').name('Rotation Y').onFinishChange(function(newVal) { + doLsystem(lsys, lsys.iterations, turtle); + }); + + turtleFolder.add(turtle, 'rotZ').name('Rotation Z').onFinishChange(function(newVal) { + doLsystem(lsys, lsys.iterations, turtle); }); -} -// clears the scene by removing all geometries added by turtle.js -function clearScene(turtle) { - var obj; - for( var i = turtle.scene.children.length - 1; i > 3; i--) { - obj = turtle.scene.children[i]; - turtle.scene.remove(obj); - } + turtleFolder.add(turtle, 'cylX').name('Cylinder width').onFinishChange(function(newVal) { + doLsystem(lsys, lsys.iterations, turtle); + }); + + turtleFolder.add(turtle, 'cylY').name('Cylinder height').onFinishChange(function(newVal) { + doLsystem(lsys, lsys.iterations, turtle); + }); + + turtleFolder.addColor(turtle, 'cylColor').name('Cylinder color').onChange(function(newVal) { + doLsystem(lsys, lsys.iterations, turtle); + }); + + turtleFolder.addColor(turtle, 'leafColor').name('Leaf color').onChange(function(newVal) { + doLsystem(lsys, lsys.iterations, turtle); + }); + + turtleFolder.addColor(turtle, 'flowerColor').name('Flower color').onChange(function(newVal) { + doLsystem(lsys, lsys.iterations, turtle); + }); + + doLsystem(lsys, lsys.iterations, turtle); } function doLsystem(lsystem, iterations, turtle) { - var result = lsystem.DoIterations(iterations); - turtle.clear(); - turtle = new Turtle(turtle.scene); - turtle.renderSymbols(result); + var result = lsystem.DoIterations(iterations); + + turtle.clear(); + turtle.clearScene(); + turtle.updateGrammar(); + turtle.renderSymbols(result); } // called on frame updates diff --git a/src/tests.js b/src/tests.js new file mode 100644 index 0000000..a135177 --- /dev/null +++ b/src/tests.js @@ -0,0 +1,9 @@ +import { ListNode, LinkedList } from './lsystem.js' + +var list = new LinkedList(); +list.add(new ListNode(0)); +list.add(new ListNode(1)); +list.add(new ListNode(2)); + +console.log(list.getAt(2)); +console.log(list.size()); \ No newline at end of file diff --git a/src/turtle.js b/src/turtle.js index 1db2723..04a1dd7 100644 --- a/src/turtle.js +++ b/src/turtle.js @@ -1,4 +1,5 @@ const THREE = require('three') +const _ = require('lodash'); // A class used to encapsulate the state of a turtle at a given moment. // The Turtle class contains one TurtleState member variable. @@ -10,30 +11,50 @@ var TurtleState = function(pos, dir) { dir: new THREE.Vector3(dir.x, dir.y, dir.z) } } - + export default class Turtle { - + constructor(scene, grammar) { this.state = new TurtleState(new THREE.Vector3(0,0,0), new THREE.Vector3(0,1,0)); this.scene = scene; + this.stack = []; + this.rotZ = 45; + this.rotY = 45; + this.cylY = 2; + this.cylX = 0.1; + this.cylColor = 0x89c73c; + this.leafColor = 0xe1e241; + this.flowerColor = 0xff5b27; + + this.updateGrammar(); + } - // TODO: Start by adding rules for '[' and ']' then more! - // Make sure to implement the functions for the new rules inside Turtle - if (typeof grammar === "undefined") { - this.renderGrammar = { - '+' : this.rotateTurtle.bind(this, 30, 0, 0), - '-' : this.rotateTurtle.bind(this, -30, 0, 0), - 'F' : this.makeCylinder.bind(this, 2, 0.1) - }; - } else { - this.renderGrammar = grammar; + updateGrammar() { + this.renderGrammar = { + '+': this.rotateTurtle.bind(this, 0, 0, this.rotZ), + '-': this.rotateTurtle.bind(this, 0, 0, -1 * this.rotZ), + '<': this.rotateTurtle.bind(this, this.rotY, this.rotY, 0), + '>': this.rotateTurtle.bind(this, -1 * this.rotY, -1 * this.rotY, 0), + 'F': this.makeCylinder.bind(this, this.cylY, this.cylX), + '[': this.saveState.bind(this), + ']': this.returnToState.bind(this), + 'L': this.makeLeaf.bind(this), + 'O': this.makeFlower.bind(this) + }; + } + + clearScene() { + var obj; + for (var i = this.scene.children.length - 1; i > 0; i--) { + obj = this.scene.children[i]; + this.scene.remove(obj); } } // Resets the turtle's position to the origin // and its orientation to the Y axis clear() { - this.state = new TurtleState(new THREE.Vector3(0,0,0), new THREE.Vector3(0,1,0)); + this.state = new TurtleState(new THREE.Vector3(0,0,0), new THREE.Vector3(0,1,0)); } // A function to help you debug your turtle functions @@ -43,7 +64,7 @@ export default class Turtle { console.log(this.state.dir) } - // Rotate the turtle's _dir_ vector by each of the + // Rotate the turtle's _dir_ vector by each of the // Euler angles indicated by the input. rotateTurtle(x, y, z) { var e = new THREE.Euler( @@ -58,23 +79,23 @@ export default class Turtle { moveTurtle(x, y, z) { var new_vec = THREE.Vector3(x, y, z); this.state.pos.add(new_vec); - }; + } // Translate the turtle along its _dir_ vector by the distance indicated moveForward(dist) { var newVec = this.state.dir.multiplyScalar(dist); this.state.pos.add(newVec); - }; - + } + // Make a cylinder of given length and width starting at turtle pos // Moves turtle pos ahead to end of the new cylinder makeCylinder(len, width) { var geometry = new THREE.CylinderGeometry(width, width, len); - var material = new THREE.MeshBasicMaterial( {color: 0x00cccc} ); + var material = new THREE.MeshBasicMaterial({ color: this.cylColor }); var cylinder = new THREE.Mesh( geometry, material ); - this.scene.add( cylinder ); + this.scene.add(cylinder); - //Orient the cylinder to the turtle's current direction + // Orient the cylinder to the turtle's current direction var quat = new THREE.Quaternion(); quat.setFromUnitVectors(new THREE.Vector3(0,1,0), this.state.dir); var mat4 = new THREE.Matrix4(); @@ -82,25 +103,92 @@ export default class Turtle { cylinder.applyMatrix(mat4); - //Move the cylinder so its base rests at the turtle's current position + // Move the cylinder so its base rests at the turtle's current position var mat5 = new THREE.Matrix4(); var trans = this.state.pos.add(this.state.dir.multiplyScalar(0.5 * len)); mat5.makeTranslation(trans.x, trans.y, trans.z); cylinder.applyMatrix(mat5); - //Scoot the turtle forward by len units - this.moveForward(len/2); - }; - + // Scoot the turtle forward by len units + this.moveForward(len / 2); + } + + makeLeaf() { + var shape = new THREE.Shape(); + var points = [ + new THREE.Vector2(0, 0), + new THREE.Vector2(0.5, 0), + new THREE.Vector2(0.5, 0.5) + ]; + + shape.fromPoints(points); + + var extrudeSettings = { + steps: 1, + amount: 0.1, + bevelEnabled: true, + bevelThickness: 0, + bevelSize: 0, + bevelSegments: 1 + }; + + var geometry = new THREE.ExtrudeGeometry( shape, extrudeSettings ); + var material = new THREE.MeshBasicMaterial({ color: this.leafColor }); + + var leaf = new THREE.Mesh(geometry, material); + this.scene.add(leaf); + + var mat5 = new THREE.Matrix4(); + var trans = this.state.pos; + mat5.makeTranslation(trans.x, trans.y, trans.z); + leaf.applyMatrix(mat5); + } + + makeFlower() { + var centers = [ + new THREE.Vector2(0.1, 0), + new THREE.Vector2(-0.2, -0.2), + new THREE.Vector2(-0.2, 0.2) + ]; + + for (var i = 0; i < centers.length; i++) { + var center = centers[i]; + var geometry = new THREE.CylinderGeometry(0.2, 0.2, this.cylX + 0.2, 32); + var material = new THREE.MeshBasicMaterial({ color: this.flowerColor }); + var petal = new THREE.Mesh( geometry, material ); + this.scene.add(petal); + + var quat = new THREE.Quaternion(); + var forward = new THREE.Vector3(0, 0, 1); + quat.setFromUnitVectors(new THREE.Vector3(0,1,0), forward); + var mat4 = new THREE.Matrix4(); + mat4.makeRotationFromQuaternion(quat); + petal.applyMatrix(mat4); + + var mat5 = new THREE.Matrix4(); + var trans = this.state.pos; + mat5.makeTranslation(trans.x + center.x + this.cylX, trans.y + center.y, trans.z); + petal.applyMatrix(mat5); + } + } + + saveState() { + this.stack.push(new TurtleState(this.state.pos, this.state.dir)); + } + + returnToState() { + this.state = this.stack.pop(); + } + // Call the function to which the input symbol is bound. - // Look in the Turtle's constructor for examples of how to bind + // Look in the Turtle's constructor for examples of how to bind // functions to grammar symbols. renderSymbol(symbolNode) { - var func = this.renderGrammar[symbolNode.character]; + var func = this.renderGrammar[symbolNode.data]; if (func) { func(); } - }; + } // Invoke renderSymbol for every node in a linked list of grammar symbols. renderSymbols(linkedList) {