diff --git a/src/lsystem.js b/src/lsystem.js index e643b6d..2ce1afb 100644 --- a/src/lsystem.js +++ b/src/lsystem.js @@ -7,6 +7,30 @@ function Rule(prob, str) { // TODO: Implement a linked list class and its requisite functions // as described in the homework writeup +var Node = { + symbol : '', + nextNode : null, + prevNode : null +}; + +function LinkedList(){ + this.head = null; + this.tail = null; + this.push = function(val) { + if (!this.head) { + this.head = {symbol: val, nextNode: null, prevNode: null}; + this.tail = this.head; + } else if (this.head == this.tail) { + var newNode = {symbol: val, nextNode: null, prevNode: this.head}; + this.head.nextNode = newNode; + this.tail = newNode; + } else { + var newNode = {symbol: val, nextNode: null, prevNode: this.tail}; + this.tail.nextNode = newNode; + this.tail = newNode; + } + }; +}; // TODO: Turn the string into linked list export function stringToLinkedList(input_string) { @@ -14,19 +38,53 @@ export function stringToLinkedList(input_string) { // you should return a linked list where the head is // at Node('F') and the tail is at Node('X') var ll = new LinkedList(); + for (var i = 0; i < input_string.length; i++) { + ll.push(input_string.charAt(i)); + } return ll; } // TODO: Return a string form of the LinkedList export function linkedListToString(linkedList) { // ex. Node1("F")->Node2("X") should be "FX" - var result = ""; + var node = linkedList.head; + var result = ''; + + while (node) { + result = result.concat(node.symbol); + node = node.nextNode; + } return result; } // TODO: Given the node to be replaced, // insert a sub-linked-list that represents replacementString function replaceNode(linkedList, node, replacementString) { + var replacementList = stringToLinkedList(replacementString); + if (linkedList.head == linkedList.tail == node) { + linkedList = replacementList; + } + else if (linkedList.head == node) { + var next = node.next; + linkedList.head = replacementList.head; + next.prevNode = replacementList.tail; + replacementList.tail.next = next; + } + else if (linkedList.tail == node) { + var prev = node.prevNode; + linkedList.tail = replacementList.tail; + prev.nextNode = replacementList.head; + replacementList.head.prevNode = prev; + } else { + var next = node.nextNode; + var prev = node.prevNode; + next.prevNode = replacementList.tail; + replacementList.tail.nextNode = next; + + prev.nextNode = replacementList.head; + replacementList.head.nextNode = prev; + } + return replacementList.tail.next; } export default function Lsystem(axiom, grammar, iterations) { @@ -64,13 +122,36 @@ export default function Lsystem(axiom, grammar, iterations) { } } + this.updateString = function(n) { + var stringResult = this.axiom; + + for (var i = 0; i < n; i++) { + var newString = ''; + for (var j = 0; j < stringResult.length; j++) { + var currentChar = stringResult.charAt(j); + if (this.grammar[currentChar]) { + newString = newString.concat(this.grammar[currentChar][0].successorString); + } else { + newString = newString.concat(currentChar); + } + } + stringResult = newString; + } + + // console.log(stringResult); + return stringResult; + } + + // 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); + + var lSystemLL = stringToLinkedList(this.updateString(n)); + return lSystemLL; } } \ No newline at end of file diff --git a/src/main.js b/src/main.js index f0c6600..bf5f420 100644 --- a/src/main.js +++ b/src/main.js @@ -6,6 +6,44 @@ import Turtle from './turtle.js' var turtle; +var settings = { + angle : 30 +} + +function rate_limit(func) { + var running = false; + var next = undefined; + + function onDone() { + running = false; // set the flag to allow the function to be called again + if (typeof next !== 'undefined') { + callFunc(next); // call the function again with the queued args + } + } + + function callFunc(args) { + if (running) { + // if the function is already running, remember the arguments passed in so we can call the func with them after we're ready + next = args; + } else { + running = true; // prevent other function calls from running until we're done + next = undefined; + func.apply(func, args); // call the func with the arguments + } + } + + // return a new function wrapping the function we want to rate limit + return function() { + // we use the same arguments but add the onDone callback as the last argument + var args = new Array(arguments.length + 1); + for (var i = 0; i < arguments.length; ++i) { + args[i] = arguments[i]; + } + args[arguments.length] = onDone; + callFunc(args); + } +} + // called after the scene loads function onLoad(framework) { var scene = framework.scene; @@ -34,13 +72,20 @@ function onLoad(framework) { }); gui.add(lsys, 'axiom').onChange(function(newVal) { - lsys.UpdateAxiom(newVal); + lsys.updateAxiom(newVal); + clearScene(turtle); doLsystem(lsys, lsys.iterations, turtle); }); - gui.add(lsys, 'iterations', 0, 12).step(1).onChange(function(newVal) { + gui.add(lsys, 'iterations', 0, 12).step(1).onChange(rate_limit(function(newVal, done) { clearScene(turtle); doLsystem(lsys, newVal, turtle); + done(); + })); + + gui.add(settings,'angle', 0, 90).onChange(function(newVal) { + clearScene(turtle); + doLsystem(lsys, lsys.iterations, turtle); }); } @@ -54,9 +99,11 @@ function clearScene(turtle) { } function doLsystem(lsystem, iterations, turtle) { - var result = lsystem.DoIterations(iterations); + var result = lsystem.doIterations(iterations); turtle.clear(); turtle = new Turtle(turtle.scene); + turtle.angle = settings.angle; + turtle.updateAngles(); turtle.renderSymbols(result); } diff --git a/src/turtle.js b/src/turtle.js index 1db2723..608e3da 100644 --- a/src/turtle.js +++ b/src/turtle.js @@ -16,20 +16,37 @@ 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.angle = 30; // 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) + '+' : this.rotateTurtle.bind(this, this.angle, 0, 0), + '-' : this.rotateTurtle.bind(this, -this.angle, 0, 0), + 'F' : this.makeCylinder.bind(this, 2, 0.1), + '[' : this.saveState.bind(this), + ']' : this.recoverState.bind(this) }; } else { this.renderGrammar = grammar; } } + saveState() { + this.stack.push(new TurtleState(this.state.pos, this.state.dir)); + } + + recoverState() { + this.state = this.stack.pop(); + } + + updateAngles() { + this.renderGrammar['+'] = this.rotateTurtle.bind(this, this.angle, 0, 0); + this.renderGrammar['-'] = this.rotateTurtle.bind(this, -this.angle, 0, 0); + } + // Resets the turtle's position to the origin // and its orientation to the Y axis clear() { @@ -46,6 +63,7 @@ export default class Turtle { // Rotate the turtle's _dir_ vector by each of the // Euler angles indicated by the input. rotateTurtle(x, y, z) { + // console.log(this.angle); var e = new THREE.Euler( x * 3.14/180, y * 3.14/180, @@ -96,17 +114,19 @@ export default class Turtle { // 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.symbol]; if (func) { func(); + // this.printState(); } }; // Invoke renderSymbol for every node in a linked list of grammar symbols. renderSymbols(linkedList) { var currentNode; - for(currentNode = linkedList.head; currentNode != null; currentNode = currentNode.next) { + for(currentNode = linkedList.head; currentNode != null; currentNode = currentNode.nextNode) { this.renderSymbol(currentNode); + // console.log(currentNode.symbol); } } } \ No newline at end of file