From 4a784110deeb528267f758e3ed165fbe599c6544 Mon Sep 17 00:00:00 2001 From: Brian Tong Date: Mon, 6 Feb 2017 00:16:23 -0500 Subject: [PATCH 1/7] add gui for rules --- src/lsystem.js | 93 ++++++++++++++++++++++++++++++++++++++++++-------- src/main.js | 29 +++++++++++++--- 2 files changed, 103 insertions(+), 19 deletions(-) diff --git a/src/lsystem.js b/src/lsystem.js index e643b6d..acef680 100644 --- a/src/lsystem.js +++ b/src/lsystem.js @@ -7,24 +7,64 @@ function Rule(prob, str) { // TODO: Implement a linked list class and its requisite functions // as described in the homework writeup +function LinkedList() { + this.head = undefined; + this.tail = undefined; +} + +function LinkedListNode() { + this.character = ''; + this.prev = undefined; + this.next = undefined; +} -// TODO: Turn the string into linked list -export function stringToLinkedList(input_string) { +// 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 + // you should return a linked list where the head is // at Node('F') and the tail is at Node('X') var ll = new LinkedList(); + + switch (input_string.lenth) { + case 0: + console.log("StringToLinkedList: input length 0"); + break; + default: + console.log("StringToLinkedList: parsing"); + var prev = undefined; + for (var i = 0; i < input_string.length; i++) { + var node = new LinkedListNode(); + node.character = input_string.charAt(i); + node.prev = prev; + if (prev) { + prev.next = node; + } + prev = node; + + if (i == 0) { + ll.head = node; + } + + if (i == input_string.length - 1) { + ll.tail = node; + } + + console.log(node.character); + } + break; + } + return ll; } // TODO: Return a string form of the LinkedList -export function linkedListToString(linkedList) { +export function LinkedListToString(linkedList) { // ex. Node1("F")->Node2("X") should be "FX" var result = ""; return result; } -// TODO: Given the node to be replaced, +// TODO: Given the node to be replaced, // insert a sub-linked-list that represents replacementString function replaceNode(linkedList, node, replacementString) { } @@ -36,41 +76,64 @@ export default function Lsystem(axiom, grammar, iterations) { this.grammar['X'] = [ new Rule(1.0, '[-FX][+FX]') ]; - this.iterations = 0; - + this.iterations = 0; + // Set up the axiom string if (typeof axiom !== "undefined") { this.axiom = axiom; } - // Set up the grammar as a dictionary that + // 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 + + // 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 + // A function to alter the axiom string stored // in the L-system - this.updateAxiom = function(axiom) { + this.UpdateAxiom = function(axiom) { // Setup axiom if (typeof axiom !== "undefined") { this.axiom = axiom; } } + this.UpdateRules = function(rules) { + this.grammar = {}; + for (var key in rules) { + var entry = rules[key]; + var prob = entry.Prob; + var data = entry.Rule.replace(/\s/g, "").split("="); + if (data.length == 2) { + var symbol = data[0]; + var rule = data[1]; + var R = new Rule(prob, rule); + if (!this.grammar[symbol]) { + this.grammar[symbol] = []; + } + this.grammar[symbol].push(R); + } else { + console.log("Invalid Rule: " + key); + } + } + console.log(this.grammar); + } + // TODO - // This function returns a linked list that is the result + // 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) { + this.DoIterations = function(n) { + console.log("start iterations"); var lSystemLL = StringToLinkedList(this.axiom); + console.log("end iterations"); return lSystemLL; } -} \ No newline at end of file +} diff --git a/src/main.js b/src/main.js index f0c6600..509e9cf 100644 --- a/src/main.js +++ b/src/main.js @@ -5,6 +5,9 @@ import Lsystem, {LinkedListToString} from './lsystem.js' import Turtle from './turtle.js' var turtle; +var numRules = 0; +var rules = {}; +var probs = {}; // called after the scene loads function onLoad(framework) { @@ -33,15 +36,32 @@ function onLoad(framework) { camera.updateProjectionMatrix(); }); + gui.add(lsys, 'iterations', 0, 12).step(1).onChange(function(newVal) { + clearScene(turtle); + doLsystem(lsys, newVal, turtle); + }); + gui.add(lsys, 'axiom').onChange(function(newVal) { lsys.UpdateAxiom(newVal); doLsystem(lsys, lsys.iterations, turtle); }); - gui.add(lsys, 'iterations', 0, 12).step(1).onChange(function(newVal) { - clearScene(turtle); - doLsystem(lsys, newVal, turtle); - }); + var obj = { addRule:function(){ + console.log("clicked"); + rules[numRules] = { + Rule: "", + Prob: 1.0 + }; + gui.add(rules[numRules], 'Rule').onChange(function() { + lsys.UpdateRules(rules); + }); + gui.add(rules[numRules], 'Prob', 0.0, 1.0).onChange(function() { + lsys.UpdateRules(rules); + }); + numRules++; + }}; + gui.add(obj,'addRule'); + } // clears the scene by removing all geometries added by turtle.js @@ -55,6 +75,7 @@ function clearScene(turtle) { function doLsystem(lsystem, iterations, turtle) { var result = lsystem.DoIterations(iterations); + console.log(result); turtle.clear(); turtle = new Turtle(turtle.scene); turtle.renderSymbols(result); From 445b2eec3a7207813dfe09723b7d84cd913523b6 Mon Sep 17 00:00:00 2001 From: Brian Tong Date: Mon, 6 Feb 2017 01:18:53 -0500 Subject: [PATCH 2/7] add axiom expansion --- src/lsystem.js | 55 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/src/lsystem.js b/src/lsystem.js index acef680..22c7566 100644 --- a/src/lsystem.js +++ b/src/lsystem.js @@ -69,6 +69,27 @@ export function LinkedListToString(linkedList) { function replaceNode(linkedList, node, replacementString) { } +function pickRuleFromDistr(distribution) { + var rules = []; + var probs = []; + for (var i = 0; i < distribution.length; i++) { + rules.push(distribution[i].successorString); + probs.push(distribution[i].probability); + } + var minProb = Math.min.apply(null, probs); + var probs = probs.map(function(x) { return Math.round(x / minProb); }); + var cmlProbs = []; + probs.reduce(function(a,b,i) { return cmlProbs[i] = a+b; }, 0); + + var r = Math.random() * cmlProbs[cmlProbs.length-1]-0.0000001; // ensure always less than max + for (var i = 0; i < cmlProbs.length; i++) { + if (r < cmlProbs[i]) { + return rules[i]; + } + } + console.log("No Rules to pick from!"); +} + export default function Lsystem(axiom, grammar, iterations) { // default LSystem this.axiom = "FX"; @@ -77,6 +98,7 @@ export default function Lsystem(axiom, grammar, iterations) { new Rule(1.0, '[-FX][+FX]') ]; this.iterations = 0; + this.expansions = {}; // Set up the axiom string if (typeof axiom !== "undefined") { @@ -106,6 +128,7 @@ export default function Lsystem(axiom, grammar, iterations) { this.UpdateRules = function(rules) { this.grammar = {}; + this.expansions = {}; for (var key in rules) { var entry = rules[key]; var prob = entry.Prob; @@ -125,6 +148,31 @@ export default function Lsystem(axiom, grammar, iterations) { console.log(this.grammar); } + this.DoExpansion = function(n) { + if (n < 0) { + throw 'Invalid number of expansions!'; + } + + if (n == 0) { + console.log("dis one"); + this.expansions[0] = this.axiom; + } else if (!this.expansions[n]) { + var prev = this.DoExpansion(n-1); + var expsn = []; + for (var i = 0; i < prev.length; i++) { + var c = prev.charAt(i); + if (this.grammar[c] && this.grammar[c].length > 0) { + var r = pickRuleFromDistr(this.grammar[c]); + expsn.push(r); + } else { + expsn.push(c); + } + } + this.expansions[n] = expsn.join(""); + } + return this.expansions[n]; + } + // TODO // This function returns a linked list that is the result // of expanding the L-system's axiom n times. @@ -132,7 +180,12 @@ export default function Lsystem(axiom, grammar, iterations) { // list of the axiom. this.DoIterations = function(n) { console.log("start iterations"); - var lSystemLL = StringToLinkedList(this.axiom); + if (!this.expansions[n]) { + console.log("doing expansions"); + this.DoExpansion(n); + } + console.log(this.expansions); + var lSystemLL = StringToLinkedList(this.expansions[n]); console.log("end iterations"); return lSystemLL; } From 04d2d683c46bfd3a1a92b110a6ba1e9981886728 Mon Sep 17 00:00:00 2001 From: Brian Tong Date: Mon, 6 Feb 2017 01:40:14 -0500 Subject: [PATCH 3/7] fix probability scaling (prevent divide by zero) --- src/lsystem.js | 56 +++++++++++++++++++------------------------------- src/main.js | 5 ++++- 2 files changed, 25 insertions(+), 36 deletions(-) diff --git a/src/lsystem.js b/src/lsystem.js index 22c7566..7d4ff8d 100644 --- a/src/lsystem.js +++ b/src/lsystem.js @@ -25,33 +25,23 @@ export function StringToLinkedList(input_string) { // at Node('F') and the tail is at Node('X') var ll = new LinkedList(); - switch (input_string.lenth) { - case 0: - console.log("StringToLinkedList: input length 0"); - break; - default: - console.log("StringToLinkedList: parsing"); - var prev = undefined; - for (var i = 0; i < input_string.length; i++) { - var node = new LinkedListNode(); - node.character = input_string.charAt(i); - node.prev = prev; - if (prev) { - prev.next = node; - } - prev = node; - - if (i == 0) { - ll.head = node; - } + var prev = undefined; + for (var i = 0; i < input_string.length; i++) { + var node = new LinkedListNode(); + node.character = input_string.charAt(i); + node.prev = prev; + if (prev) { + prev.next = node; + } + prev = node; - if (i == input_string.length - 1) { - ll.tail = node; - } + if (i == 0) { + ll.head = node; + } - console.log(node.character); - } - break; + if (i == input_string.length - 1) { + ll.tail = node; + } } return ll; @@ -76,7 +66,8 @@ function pickRuleFromDistr(distribution) { rules.push(distribution[i].successorString); probs.push(distribution[i].probability); } - var minProb = Math.min.apply(null, probs); + var filtered = probs.filter(function(val) { return val > 0; }); + var minProb = Math.min.apply(null, filtered); var probs = probs.map(function(x) { return Math.round(x / minProb); }); var cmlProbs = []; probs.reduce(function(a,b,i) { return cmlProbs[i] = a+b; }, 0); @@ -94,9 +85,9 @@ export default function Lsystem(axiom, grammar, iterations) { // default LSystem this.axiom = "FX"; this.grammar = {}; - this.grammar['X'] = [ - new Rule(1.0, '[-FX][+FX]') - ]; + // this.grammar['X'] = [ + // new Rule(1.0, '[-FX][+FX]') + // ]; this.iterations = 0; this.expansions = {}; @@ -123,6 +114,7 @@ export default function Lsystem(axiom, grammar, iterations) { // Setup axiom if (typeof axiom !== "undefined") { this.axiom = axiom; + this.expansions = {}; } } @@ -145,7 +137,6 @@ export default function Lsystem(axiom, grammar, iterations) { console.log("Invalid Rule: " + key); } } - console.log(this.grammar); } this.DoExpansion = function(n) { @@ -154,7 +145,6 @@ export default function Lsystem(axiom, grammar, iterations) { } if (n == 0) { - console.log("dis one"); this.expansions[0] = this.axiom; } else if (!this.expansions[n]) { var prev = this.DoExpansion(n-1); @@ -179,14 +169,10 @@ export default function Lsystem(axiom, grammar, iterations) { // The implementation we have provided you just returns a linked // list of the axiom. this.DoIterations = function(n) { - console.log("start iterations"); if (!this.expansions[n]) { - console.log("doing expansions"); this.DoExpansion(n); } - console.log(this.expansions); var lSystemLL = StringToLinkedList(this.expansions[n]); - console.log("end iterations"); return lSystemLL; } } diff --git a/src/main.js b/src/main.js index 509e9cf..9548717 100644 --- a/src/main.js +++ b/src/main.js @@ -54,9 +54,13 @@ function onLoad(framework) { }; gui.add(rules[numRules], 'Rule').onChange(function() { lsys.UpdateRules(rules); + clearScene(turtle); + doLsystem(lsys, lsys.iterations, turtle); }); gui.add(rules[numRules], 'Prob', 0.0, 1.0).onChange(function() { lsys.UpdateRules(rules); + clearScene(turtle); + doLsystem(lsys, lsys.iterations, turtle); }); numRules++; }}; @@ -75,7 +79,6 @@ function clearScene(turtle) { function doLsystem(lsystem, iterations, turtle) { var result = lsystem.DoIterations(iterations); - console.log(result); turtle.clear(); turtle = new Turtle(turtle.scene); turtle.renderSymbols(result); From 2806578b6a4dcae6cfb21d9ebb6b412baa8c7116 Mon Sep 17 00:00:00 2001 From: Brian Tong Date: Mon, 6 Feb 2017 02:22:18 -0500 Subject: [PATCH 4/7] add save state --- src/lsystem.js | 1 + src/main.js | 1 + src/turtle.js | 29 ++++++++++++++++++++--------- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/lsystem.js b/src/lsystem.js index 7d4ff8d..869f927 100644 --- a/src/lsystem.js +++ b/src/lsystem.js @@ -139,6 +139,7 @@ export default function Lsystem(axiom, grammar, iterations) { } } + this.DoExpansion = function(n) { if (n < 0) { throw 'Invalid number of expansions!'; diff --git a/src/main.js b/src/main.js index 9548717..1e88d84 100644 --- a/src/main.js +++ b/src/main.js @@ -81,6 +81,7 @@ function doLsystem(lsystem, iterations, turtle) { var result = lsystem.DoIterations(iterations); turtle.clear(); turtle = new Turtle(turtle.scene); + console.log("new Render"); turtle.renderSymbols(result); } diff --git a/src/turtle.js b/src/turtle.js index 1db2723..97064cb 100644 --- a/src/turtle.js +++ b/src/turtle.js @@ -10,12 +10,13 @@ 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.stateStack = []; // TODO: Start by adding rules for '[' and ']' then more! // Make sure to implement the functions for the new rules inside Turtle @@ -23,17 +24,27 @@ export default class Turtle { this.renderGrammar = { '+' : this.rotateTurtle.bind(this, 30, 0, 0), '-' : this.rotateTurtle.bind(this, -30, 0, 0), - 'F' : this.makeCylinder.bind(this, 2, 0.1) + 'F' : this.makeCylinder.bind(this, 2, 0.1), + '[' : this.saveState.bind(this), + ']' : this.restoreState.bind(this) }; } else { this.renderGrammar = grammar; } } + saveState() { + this.stateStack.push(new TurtleState(this.state.pos, this.state.dir)); + } + + restoreState() { + this.state = this.stateStack.pop(); + } + // 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 +54,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( @@ -65,7 +76,7 @@ export default class Turtle { 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) { @@ -91,9 +102,9 @@ export default class Turtle { //Scoot the turtle forward by len units this.moveForward(len/2); }; - + // 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]; @@ -109,4 +120,4 @@ export default class Turtle { this.renderSymbol(currentNode); } } -} \ No newline at end of file +} From 170b4317e98c217cc71ad7841cae84c421c8eb41 Mon Sep 17 00:00:00 2001 From: Brian Tong Date: Mon, 6 Feb 2017 13:29:49 -0500 Subject: [PATCH 5/7] working on orthogonal --- src/lsystem.js | 3 +-- src/main.js | 8 ++++++- src/turtle.js | 59 +++++++++++++++++++++++++++++++++++++++++--------- 3 files changed, 57 insertions(+), 13 deletions(-) diff --git a/src/lsystem.js b/src/lsystem.js index 869f927..89a7cb0 100644 --- a/src/lsystem.js +++ b/src/lsystem.js @@ -138,8 +138,7 @@ export default function Lsystem(axiom, grammar, iterations) { } } } - - + this.DoExpansion = function(n) { if (n < 0) { throw 'Invalid number of expansions!'; diff --git a/src/main.js b/src/main.js index 1e88d84..a31b6be 100644 --- a/src/main.js +++ b/src/main.js @@ -47,11 +47,17 @@ function onLoad(framework) { }); var obj = { addRule:function(){ - console.log("clicked"); rules[numRules] = { Rule: "", Prob: 1.0 }; + + var updateRules = function() { + lsys.UpdateRules(rules, i); + clearScene(turtle); + doLsystem(lsys, lsys.iterations, turtle); + }; + gui.add(rules[numRules], 'Rule').onChange(function() { lsys.UpdateRules(rules); clearScene(turtle); diff --git a/src/turtle.js b/src/turtle.js index 97064cb..2b1e522 100644 --- a/src/turtle.js +++ b/src/turtle.js @@ -4,29 +4,39 @@ const THREE = require('three') // The Turtle class contains one TurtleState member variable. // You are free to add features to this state class, // such as color or whimiscality -var TurtleState = function(pos, dir) { +var TurtleState = function(pos, dir, ortho) { return { pos: new THREE.Vector3(pos.x, pos.y, pos.z), - dir: new THREE.Vector3(dir.x, dir.y, dir.z) + dir: new THREE.Vector3(dir.x, dir.y, dir.z), + ortho: new THREE.Vector3(ortho.x, ortho.y, ortho.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.state = new TurtleState(new THREE.Vector3(0,0,0), new THREE.Vector3(0,1,0), new THREE.Vector3(1,0,0)); this.scene = scene; this.stateStack = []; - +// // 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), + 'Y' : this.rotateTurtle.bind(this, 0, 30, 0), + 'y' : this.rotateTurtle.bind(this, 0, -30, 0), + 'Z' : this.rotateTurtle.bind(this, 0, 0, 30), + 'z' : this.rotateTurtle.bind(this, 0, 0, -30), + 'F' : this.makeCylinder.bind(this, 2, 0.1, 0x00cccc), '[' : this.saveState.bind(this), - ']' : this.restoreState.bind(this) + ']' : this.restoreState.bind(this), + 'A' : this.makeOrthoCylinder.bind(this, 2, 0.1, 0xf4e542), + 'C' : this.makeOrthoCylinder.bind(this, 2, 0.1, 0x5ba5e5), + 'T' : this.makeOrthoCylinder.bind(this, 2, 0.1, 0xe53e32), + 'G' : this.makeOrthoCylinder.bind(this, 2, 0.1, 0x8ee55b), + 'I' : this.makeCylinder.bind(this, 2, 0.1, 0x8ee55b) }; } else { this.renderGrammar = grammar; @@ -34,7 +44,7 @@ export default class Turtle { } saveState() { - this.stateStack.push(new TurtleState(this.state.pos, this.state.dir)); + this.stateStack.push(new TurtleState(this.state.pos, this.state.dir, this.state.ortho)); } restoreState() { @@ -44,7 +54,7 @@ export default class Turtle { // 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), new THREE.Vector3(1,0,0)); } // A function to help you debug your turtle functions @@ -62,6 +72,9 @@ export default class Turtle { y * 3.14/180, z * 3.14/180); this.state.dir.applyEuler(e); + this.state.ortho.applyEuler(e); + console.log(this.state.dir); + console.log(this.state.ortho); } // Translate the turtle along the input vector. @@ -79,9 +92,9 @@ export default class Turtle { // 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) { + makeCylinder(len, width, colorcode) { var geometry = new THREE.CylinderGeometry(width, width, len); - var material = new THREE.MeshBasicMaterial( {color: 0x00cccc} ); + var material = new THREE.MeshBasicMaterial( {color: colorcode} ); var cylinder = new THREE.Mesh( geometry, material ); this.scene.add( cylinder ); @@ -103,6 +116,32 @@ export default class Turtle { this.moveForward(len/2); }; + // Make a cylinder of given length and width starting at turtle pos + // Moves turtle pos ahead to end of the new cylinder + makeOrthoCylinder(len, width, colorcode) { + var geometry = new THREE.CylinderGeometry(width, width, len); + var material = new THREE.MeshBasicMaterial( {color: colorcode} ); + var cylinder = new THREE.Mesh( geometry, material ); + this.scene.add( cylinder ); + + //Orient the cylinder to the turtle's current direction + var quat = new THREE.Quaternion(); + quat.setFromUnitVectors(new THREE.Vector3(0,1,0), this.state.ortho); + var mat4 = new THREE.Matrix4(); + mat4.makeRotationFromQuaternion(quat); + cylinder.applyMatrix(mat4); + + + //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.ortho.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); + }; + // Call the function to which the input symbol is bound. // Look in the Turtle's constructor for examples of how to bind // functions to grammar symbols. From ee1c69ed88d801058d4e9f524d79fe3224f1f8cb Mon Sep 17 00:00:00 2001 From: Brian Tong Date: Tue, 7 Feb 2017 19:40:33 -0500 Subject: [PATCH 6/7] added messy code-also controls for angle and toggle lines. --- src/lsystem.js | 10 +++-- src/main.js | 113 +++++++++++++++++++++++++++++++++++++++++++------ src/turtle.js | 102 +++++++++++++++++++++++++++++++++++--------- 3 files changed, 188 insertions(+), 37 deletions(-) diff --git a/src/lsystem.js b/src/lsystem.js index 89a7cb0..d2cb281 100644 --- a/src/lsystem.js +++ b/src/lsystem.js @@ -50,7 +50,11 @@ export function StringToLinkedList(input_string) { // TODO: Return a string form of the LinkedList export function LinkedListToString(linkedList) { // ex. Node1("F")->Node2("X") should be "FX" - var result = ""; + var temp = []; + for (var node = linkedList.head; node != linkedList.tail; node = node.next) { + temp.push(node.character); + } + var result = temp.join(""); return result; } @@ -83,7 +87,7 @@ function pickRuleFromDistr(distribution) { export default function Lsystem(axiom, grammar, iterations) { // default LSystem - this.axiom = "FX"; + this.axiom = "X"; this.grammar = {}; // this.grammar['X'] = [ // new Rule(1.0, '[-FX][+FX]') @@ -138,7 +142,7 @@ export default function Lsystem(axiom, grammar, iterations) { } } } - + this.DoExpansion = function(n) { if (n < 0) { throw 'Invalid number of expansions!'; diff --git a/src/main.js b/src/main.js index a31b6be..d0ef935 100644 --- a/src/main.js +++ b/src/main.js @@ -4,10 +4,25 @@ import Framework from './framework' import Lsystem, {LinkedListToString} from './lsystem.js' import Turtle from './turtle.js' +var settings = { + lines: true, + angle: 90.0 +}; var turtle; -var numRules = 0; -var rules = {}; -var probs = {}; +var numRules = 7; +var rules = { + 0: {Rule: "X=[RX+T]RX+T", Prob: 1.0}, + 1: {Rule: "R=-R", Prob: 1.0}, + 2: {Rule: "R=+R", Prob: 1.0}, + 3: {Rule: "R=zR", Prob: 1.0}, + 4: {Rule: "R=ZR", Prob: 1.0}, + 4: {Rule: "R=yR", Prob: 1.0}, + 4: {Rule: "R=YR", Prob: 1.0}, + 5: {Rule: "R=T", Prob: 1.0}, + 6: {Rule: "R=A", Prob: 1.0}, + // 7: {Rule: "R=P", Prob: 0.5}, +}; +var removeable_items = []; // called after the scene loads function onLoad(framework) { @@ -30,47 +45,83 @@ function onLoad(framework) { // initialize LSystem and a Turtle to draw var lsys = new Lsystem(); - turtle = new Turtle(scene); + turtle = new Turtle(scene, undefined, settings); gui.add(camera, 'fov', 0, 180).onChange(function(newVal) { camera.updateProjectionMatrix(); }); gui.add(lsys, 'iterations', 0, 12).step(1).onChange(function(newVal) { + console.log("iters") clearScene(turtle); doLsystem(lsys, newVal, turtle); }); gui.add(lsys, 'axiom').onChange(function(newVal) { lsys.UpdateAxiom(newVal); + clearScene(turtle); doLsystem(lsys, lsys.iterations, turtle); }); - var obj = { addRule:function(){ + gui.add(settings, 'angle', 30, 90).onChange(function() { + lsys.UpdateRules(rules); + clearScene(turtle); + doLsystem(lsys, lsys.iterations, turtle); + console.log("set: "+settings.angle); + }); + + var addRuleFunc = { addRule:function(){ rules[numRules] = { Rule: "", Prob: 1.0 }; - var updateRules = function() { + var ritem = gui.add(rules[numRules], 'Rule').onChange(function() { + lsys.UpdateRules(rules); + clearScene(turtle); + doLsystem(lsys, lsys.iterations, turtle); + }); + var pitem = gui.add(rules[numRules], 'Prob', 0.0, 1.0).onChange(function() { + lsys.UpdateRules(rules); + clearScene(turtle); + doLsystem(lsys, lsys.iterations, turtle); + }); + numRules++; + removeable_items.push(ritem); + removeable_items.push(pitem); + }}; + gui.add(addRuleFunc,'addRule'); + + gui.add(settings, 'lines').onChange(function(newVal) { + clearScene(turtle); + doLsystem(lsys, lsys.iterations, turtle, newVal); + }); + + for (var i = 0; i < numRules; i++) { + var updateRules = rate_limit(function() { lsys.UpdateRules(rules, i); clearScene(turtle); doLsystem(lsys, lsys.iterations, turtle); - }; + }); - gui.add(rules[numRules], 'Rule').onChange(function() { + var ritem = gui.add(rules[i], 'Rule').onChange(function() { lsys.UpdateRules(rules); clearScene(turtle); doLsystem(lsys, lsys.iterations, turtle); }); - gui.add(rules[numRules], 'Prob', 0.0, 1.0).onChange(function() { + var pitem = gui.add(rules[i], 'Prob', 0.0, 1.0).onChange(function() { lsys.UpdateRules(rules); clearScene(turtle); doLsystem(lsys, lsys.iterations, turtle); }); - numRules++; - }}; - gui.add(obj,'addRule'); + removeable_items.push(ritem); + removeable_items.push(pitem); + } + + // update scene + lsys.UpdateRules(rules, i); + clearScene(turtle); + doLsystem(lsys, lsys.iterations, turtle); } @@ -86,8 +137,7 @@ function clearScene(turtle) { function doLsystem(lsystem, iterations, turtle) { var result = lsystem.DoIterations(iterations); turtle.clear(); - turtle = new Turtle(turtle.scene); - console.log("new Render"); + turtle = new Turtle(turtle.scene, undefined, settings); turtle.renderSymbols(result); } @@ -95,5 +145,40 @@ function doLsystem(lsystem, iterations, turtle) { function onUpdate(framework) { } +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); + } +} + + // when the scene is done initializing, it will call onLoad, then on frame updates, call onUpdate Framework.init(onLoad, onUpdate); diff --git a/src/turtle.js b/src/turtle.js index 2b1e522..541663b 100644 --- a/src/turtle.js +++ b/src/turtle.js @@ -14,35 +14,56 @@ var TurtleState = function(pos, dir, ortho) { export default class Turtle { - constructor(scene, grammar) { - this.state = new TurtleState(new THREE.Vector3(0,0,0), new THREE.Vector3(0,1,0), new THREE.Vector3(1,0,0)); + constructor(scene, grammar, settings) { + var initDir = new THREE.Vector3(0,1,0); + var initOrtho = new THREE.Vector3(0,1,0); + var e = new THREE.Euler(90 * 3.14/180, 0, 0); + initOrtho.applyEuler(e); + console.log(settings); + + this.state = new TurtleState(new THREE.Vector3(0,0,0), initDir, initOrtho); this.scene = scene; this.stateStack = []; -// + this.lines = settings ? settings.lines : true; + this.angle = settings ? settings.angle : 30; + + this.planeUniform = { + image: { + type: "t", + value: THREE.ImageUtils.loadTexture('./src/nebula1.png') + }, + }; + // 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), - 'Y' : this.rotateTurtle.bind(this, 0, 30, 0), - 'y' : this.rotateTurtle.bind(this, 0, -30, 0), - 'Z' : this.rotateTurtle.bind(this, 0, 0, 30), - 'z' : this.rotateTurtle.bind(this, 0, 0, -30), + '+' : this.rotateTurtle.bind(this, this.angle, 0, 0), + '-' : this.rotateTurtle.bind(this, -this.angle, 0, 0), + 'Y' : this.rotateTurtle.bind(this, 0, this.angle, 0), + 'y' : this.rotateTurtle.bind(this, 0, -this.angle, 0), + 'Z' : this.rotateTurtle.bind(this, 0, 0, this.angle), + 'z' : this.rotateTurtle.bind(this, 0, 0, -this.angle), 'F' : this.makeCylinder.bind(this, 2, 0.1, 0x00cccc), '[' : this.saveState.bind(this), ']' : this.restoreState.bind(this), - 'A' : this.makeOrthoCylinder.bind(this, 2, 0.1, 0xf4e542), - 'C' : this.makeOrthoCylinder.bind(this, 2, 0.1, 0x5ba5e5), - 'T' : this.makeOrthoCylinder.bind(this, 2, 0.1, 0xe53e32), - 'G' : this.makeOrthoCylinder.bind(this, 2, 0.1, 0x8ee55b), - 'I' : this.makeCylinder.bind(this, 2, 0.1, 0x8ee55b) + 'S' : this.swapOrthoDir.bind(this), + 'T' : this.makeStar.bind(this, 2, 0.1, 0x00cccc), + 'A' : this.makeStar.bind(this, 2, 0.05, 0xff2222), + 'P' : this.makePlane.bind(this, 2, 10, 0xff0000) }; } else { this.renderGrammar = grammar; } } + swapOrthoDir() { + var newdir = new THREE.Vector3(this.state.ortho.x, this.state.ortho.y, this.state.ortho.z); + var newortho = new THREE.Vector3(this.state.dir.x, this.state.dir.y, this.state.dir.z); + this.state.ortho = newortho; + this.state.dir = newdir; + } + saveState() { this.stateStack.push(new TurtleState(this.state.pos, this.state.dir, this.state.ortho)); } @@ -116,32 +137,73 @@ export default class Turtle { this.moveForward(len/2); }; - // Make a cylinder of given length and width starting at turtle pos - // Moves turtle pos ahead to end of the new cylinder - makeOrthoCylinder(len, width, colorcode) { - var geometry = new THREE.CylinderGeometry(width, width, len); + makeStar(len, width, colorcode) { + var geometry = new THREE.BoxGeometry(width, width, width); + var material = new THREE.MeshBasicMaterial( {color: colorcode} ); + var mesh = new THREE.Mesh( geometry, material ); + this.scene.add( mesh ); + + var geometry = new THREE.CylinderGeometry(0.005, 0.005, 2); var material = new THREE.MeshBasicMaterial( {color: colorcode} ); var cylinder = new THREE.Mesh( geometry, material ); this.scene.add( cylinder ); //Orient the cylinder to the turtle's current direction var quat = new THREE.Quaternion(); - quat.setFromUnitVectors(new THREE.Vector3(0,1,0), this.state.ortho); + quat.setFromUnitVectors(new THREE.Vector3(0,1,0), this.state.dir); var mat4 = new THREE.Matrix4(); mat4.makeRotationFromQuaternion(quat); + mesh.applyMatrix(mat4); cylinder.applyMatrix(mat4); + cylinder.visible = this.lines; //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.ortho.multiplyScalar(0.5 * len)); + var trans = this.state.pos.add(this.state.dir.multiplyScalar(0.5 * len)); mat5.makeTranslation(trans.x, trans.y, trans.z); + mesh.applyMatrix(mat5); cylinder.applyMatrix(mat5); //Scoot the turtle forward by len units this.moveForward(len/2); }; + makePlane(len, width, colorcode) { + var geometry = new THREE.PlaneGeometry(width, width, 1); + var material = new THREE.ShaderMaterial({ + uniforms: this.planeUniform, + vertexShader: require('./shaders/nebula-vert.glsl'), + fragmentShader: require('./shaders/nebula-frag.glsl') + }); + material.transparent = true; + material.side = THREE.DoubleSide; + + var mesh = new THREE.Mesh( geometry, material ); + this.scene.add( mesh ); + + //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(); + mat4.makeRotationFromQuaternion(quat); + mesh.applyMatrix(mat4); + + + //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); + mesh.applyMatrix(mat5); + + //Scoot the turtle forward by len units + this.moveForward(len/2); + }; + + UpdateCameraDir(vec) { + this.planeUniform.cameraDir = new THREE.Uniform(vec); + }; + // Call the function to which the input symbol is bound. // Look in the Turtle's constructor for examples of how to bind // functions to grammar symbols. From 20befd3690a9b64abef176f5d4c4ab2ca8225e62 Mon Sep 17 00:00:00 2001 From: Brian Tong Date: Tue, 7 Feb 2017 20:59:35 -0500 Subject: [PATCH 7/7] Update README.md --- README.md | 70 +++++++------------------------------------------------ 1 file changed, 8 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index 5ca56be..64add4d 100644 --- a/README.md +++ b/README.md @@ -1,67 +1,13 @@ -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) +# L-System +For this project, I wanted to create a L-System based star system generator. This still needs a bit more polish but I think it looks pretty good! -# L-System Parser +![Star System](https://i.imgur.com/CUcrrRw.png) -lsystem.js contains classes for L-system, Rule, and LinkedList. Here’s our suggested structure: +Playing around with the parameters, you can get an interesting digital effect if the rotation angles are set to 90 degrees: +![Digital Effect](https://i.imgur.com/LoYa65r.png) -**The Symbol Nodes/Linked List:** +Demo: https://iambrian.github.io/Project3-LSystems/ -Rather than representing our symbols as a string like in many L-system implementations, we prefer to use a linked list. This allows us to store additional information about each symbol at time of parsing (e.g. what iteration was this symbol added in?) Since we’re adding and replacing symbols at each iteration, we also save on the overhead of creating and destroying strings, since linked lists of course make it easy to add and remove nodes. You should write a Linked List class with Nodes that contain at least the following information: - -- The next node in the linked list -- The previous node in the linked list -- The grammar symbol at theis point in the overal string - -We also recommend that you write the following functions to interact with your linked list: - -- A function to symmetrically link two nodes together (e.g. Node A’s next is Node B, and Node B’s prev is Node A) -- A function to expand one of the symbol nodes of the linked list by replacing it with several new nodes. This function should look at the list of rules associated with the symbol in the linked list’s grammar dictionary, then generate a uniform random number between 0 and 1 in order to determine which of the Rules should be used to expand the symbol node. You will refer to a Rule’s probability and compare it to your random number in order to determine which Rule should be chosen. - -**Rules:** - -These are containers for the preconditions, postconditions and probability of a single replacement operation. They should operate on a symbol node in your linked list. - -**L-system:** - -This is the parser, which will loop through your linked list of symbol nodes and apply rules at each iteration. - -Implement the following functions in L-System so that you can apply grammar rules to your axiom given some number of iterations. More details and implementation suggestions about functions can be found in the TODO comments - -- `stringToLinkedList(input_string)` -- `linkedListToString(linkedList)` -- `replaceNode(linkedList, node, replacementString)` -- `doIterations(num)` - -## Turtle - -`turtle.js` has a function called renderSymbol that takes in a single node of a linked list and performs an operation to change the turtle’s state based on the symbol contained in the node. Usually, the turtle’s change in state will result in some sort of rendering output, such as drawing a cylinder when the turtle moves forward. We have provided you with a few example functions to illustrate how to write your own functions to be called by renderSymbol; these functions are rotateTurtle, moveTurtle, moveForward, and makeCylinder. If you inspect the constructor of the Turtle class, you can see how to associate an operation with a grammar symbol. - -- Modify turtle.js to support operations associated with the symbols `[` and `]` - - When you parse `[` you need to store the current turtle state somewhere - - When you parse `]` you need to set your turtle’s state to the most recently stored state. Think of this a pushing and popping turtle states on and off a stack. For example, given `F[+F][-F]`, the turtle should draw a Y shape. Note that your program must be capable of storing many turtle states at once in a stack. - -- In addition to operations for `[` and `]`, you must invent operations for any three symbols of your choosing. - - -## Interactivity - -Using dat.GUI and the examples provided in the reference code, make some aspect of your demo an interactive variable. For example, you could modify: - -1. the axiom -2. Your input grammer rules and their probability -3. the angle of rotation of the turtle -4. the size or color or material of the cylinder the turtle draws, etc! - -## L-System Plants - -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! -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 - -# Publishing Your code - -Running `npm run deploy` will automatically build your project and push it to gh-pages where it will be visible at `username.github.io/repo-name`. NOTE: You MUST commit AND push all changes to your MASTER branch before doing this or you may lose your work. The `git` command must also be available in your terminal or command prompt. If you're using Windows, it's a good idea to use Git Bash. \ No newline at end of file +# Future Ideas +Procedurally innervating a mesh