diff --git a/README.md b/README.md
index fad423fa..97a8cf15 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,28 @@
# Project 4: Shape Grammar
+# Stauffer
+
+Another hack job, my apologies.
+
+I implemented a grammar of sorts to choose different rules for making the buildings of different styles. It has these options:
+
+- ground floor layout (i.e. sequence of window, wall, door segments)
+- upper floor layout (all the same for a given style, and each building is one style)
+- roof height
+- relative dimensions of wall/window/door units
+- colors of each segment
+
+Randomization uses current time to seed style selection and height and width/depth of buildings.
+A "beautiful" population density simulation is used to have building height decay from the center of the "city".
+
+I started implementing grammer for sub-dividing individual buildings but failed.
+
+Sorry, I didn't have time to merge the geometry to speed rendering.
+
+
+
+===============================
For this assignment you'll be building directly off of Project 3. To make things easier to keep track of, please fork and clone this repository [https://github.com/CIS700-Procedural-Graphics/Project4-Shape-Grammar](https://github.com/CIS700-Procedural-Graphics/Project4-Shape-Grammar) and copy your Project 3 code to start.
**Goal:** to model an urban environment using a shape grammar.
diff --git a/deploy.js b/deploy.js
new file mode 100644
index 00000000..9defe7c3
--- /dev/null
+++ b/deploy.js
@@ -0,0 +1,38 @@
+var colors = require('colors');
+var path = require('path');
+var git = require('simple-git')(__dirname);
+var deploy = require('gh-pages-deploy');
+var packageJSON = require('require-module')('./package.json');
+
+var success = 1;
+git.fetch('origin', 'master', function(err) {
+ if (err) throw err;
+ git.status(function(err, status) {
+ if (err) throw err;
+ if (!status.isClean()) {
+ success = 0;
+ console.error('Error: You have uncommitted changes! Please commit them first'.red);
+ }
+
+ if (status.current !== 'master') {
+ success = 0;
+ console.warn('Warning: Please deploy from the master branch!'.yellow)
+ }
+
+ git.diffSummary(['origin/master'], function(err, diff) {
+ if (err) throw err;
+
+ if (diff.files.length || diff.insertions || diff.deletions) {
+ success = 0;
+ console.error('Error: Current branch is different from origin/master! Please push all changes first'.red)
+ }
+
+ if (success) {
+ var cfg = packageJSON['gh-pages-deploy'] || {};
+ var buildCmd = deploy.getFullCmd(cfg);
+ deploy.displayCmds(deploy.getFullCmd(cfg));
+ deploy.execBuild(buildCmd, cfg);
+ }
+ })
+ })
+})
\ No newline at end of file
diff --git a/index.html b/index.html
new file mode 100644
index 00000000..e609adf4
--- /dev/null
+++ b/index.html
@@ -0,0 +1,19 @@
+
+
+
+ HW2: LSystems
+
+
+
+
+
+
diff --git a/package.json b/package.json
new file mode 100644
index 00000000..be683fcb
--- /dev/null
+++ b/package.json
@@ -0,0 +1,31 @@
+{
+ "scripts": {
+ "start": "webpack-dev-server --hot --inline",
+ "build": "webpack",
+ "deploy": "node deploy.js"
+ },
+ "gh-pages-deploy": {
+ "prep": [
+ "build"
+ ],
+ "noprompt": true
+ },
+ "dependencies": {
+ "dat-gui": "^0.5.0",
+ "gl-matrix": "^2.3.2",
+ "stats-js": "^1.0.0-alpha1",
+ "three": "^0.82.1",
+ "three-orbit-controls": "^82.1.0"
+ },
+ "devDependencies": {
+ "babel-core": "^6.18.2",
+ "babel-loader": "^6.2.8",
+ "babel-preset-es2015": "^6.18.0",
+ "colors": "^1.1.2",
+ "gh-pages-deploy": "^0.4.2",
+ "simple-git": "^1.65.0",
+ "webpack": "^1.13.3",
+ "webpack-dev-server": "^1.16.2",
+ "webpack-glsl-loader": "^1.0.1"
+ }
+}
diff --git a/src/building.js b/src/building.js
new file mode 100644
index 00000000..4b790210
--- /dev/null
+++ b/src/building.js
@@ -0,0 +1,422 @@
+const THREE = require('three')
+/*
+building - doesn't get rendered
+
+ style - string name of style
+ office1
+ office2 - a different style of office building if we get that far
+ resident1 - residential style
+ resident2 - variation of residential if we get that far
+
+ constraints - will be passed by routine that decides where to make a building. It will designate params
+ based on context
+
+ style-based constraints
+ minFloors - use to decide when to stop subdiv in height
+ minWidth, minDepth - wall units - min width/depth a building can be before stopping subdiv along width/depth
+
+ location-based constraints
+ startHeight - start height of the building in floors. effectively the max height
+ startWidth, startDepth - starting width & depth - in wall units
+ this is location-based becuase it will come from algorithm that places buildings
+ orientation - vector facing from front of building, so it can face the road nicely
+ center - center of building on xz plane
+
+ subunits
+ - subunits of this building, so should get same architectural style
+ - start with one subunit the size of startFloors & startWidth
+ - for simplicity, a building is never divided into separate buildings, only into separate subunits of the same building
+ each subunit gets processed for components. minimum height is one floor
+ - subunits get checked for minWidth and minFloors before getting possibly div'ed again
+
+ state
+ terminated - true/false. starts false, set to true when should no longer be sub-div'ed
+ onGround - true/false. if it's on the ground, it'll need a door and maybe other treatment
+ height - in floors
+ width/depth - in wall units
+
+ operators
+ checkForTerminate - check against min height/width/depth - terminate if any one of these is at/near minimum?
+ maybe apply to output of a rule operation before finalizing the rule operation
+
+ rules
+ These can be grouped/changed based on style of building
+ subdiv
+ terminate - randomly choose to terminate subunit
+ split vertically - randomized top/bottom ratio - split in xz plane. top unit may be resized to be more narrow/shallow.
+ Maybe check if new dim is < min for the dim. If so, set to dim and set terminate flag.
+ split width - split in yz - option for how much separation between new subunits, >= 0
+ split depth - split in xy
+ resize height/width/depth - all in one? remember, always make smaller
+
+ once subdiv'ing is done (all subunits are termianted), move on to component operations
+
+components - objects for architectural details. do these just get rendered on top? would be easier that way
+
+ non-terminal units: (need more processing)
+ roof / tower / dome - primitive (not ready for rendering)
+ floors - do all floors in a building get same style, i.e. window/wall distribution?
+ maybe first floor gets special handing, including being taller, and all others are the same
+
+ terminal/render units: (i.e. to be rendered out)
+ windows
+ sills
+ doors
+ roof - renderable
+
+ can we make these meshes children of parent mesh, and place them all relative to parent, then will
+ they move along with parent if we move the parent?
+
+ rules once subdiv's are all done:
+
+ generate floors (non-terminal)
+ basic floors
+ floors with balconies - remember, always make things by resizing, so need to shrink floors that
+
+ generate floor components
+ make windows / wall units
+ make doors / wall / windows - only for ground floor
+ make sills
+ only between 1st and 2nd floors or top and 2nd from top for simplicity?
+
+ add roof
+
+
+*/
+
+export class BuildingStyle {
+ constructor( styleVer, probability, minWDH, roofType, roofHeight, groundLayout, upperLayout, modelWHD, colors ){
+ this.styleVersion = styleVer;
+ this.probability = probability;
+ this.minWDH = minWDH;
+ this.roofType = roofType;
+ this.roofHeight = roofHeight;
+ this.groundLayout = groundLayout;
+ this.upperLayout = upperLayout;
+ this.modelWHD = modelWHD; //scaling from wall/floor units to model units
+ this.colors = colors;
+ }
+
+ generateMesh( typeStr ) {
+ var mesh;
+ var geometry;
+ var material;
+ switch( typeStr ){
+ case 'wl' :
+ geometry = new THREE.PlaneGeometry(1.0, 1.0, 5, 5);
+ material = new THREE.MeshLambertMaterial( {color: this.colors[0]} ); //wall
+ break;
+ case 'wn' :
+ geometry = new THREE.PlaneGeometry(1.0, 1.0, 5, 5);
+ material = new THREE.MeshLambertMaterial( {color: this.colors[1]} );
+ break;
+ case 'd' :
+ geometry = new THREE.PlaneGeometry(1.0, 1.0, 5, 5);
+ material = new THREE.MeshLambertMaterial( {color: this.colors[2]} );
+ break;
+ case 'roofFlat' :
+ geometry = new THREE.BoxGeometry(1.0, 1.0, 1.0);
+ material = new THREE.MeshLambertMaterial( {color: this.colors[3]} );
+ break;
+ case 'cap' : //thin roof
+ geometry = new THREE.BoxGeometry(1.0, 1.0, 1.0);
+ material = new THREE.MeshLambertMaterial( {color: this.colors[0]} ); //wall color
+ default:
+ break;
+ }
+ return new THREE.Mesh( geometry, material );
+ }
+}
+
+export class SubdivRule {
+ constructor( probability, rule, val1, val2 ){
+ this.probability = probability;
+ this.rule = rule; //string
+ this.val1 = val1; //contextual value
+ this.val2 = val2;
+ }
+}
+
+////////////// Styles /////////////////////
+
+var g_styleRules = {};
+g_styleRules['test'] = [
+ // name prob minWDH roof type & height ground floor layout upper floor layout modelWHD colors: wall, window, door, roof
+ new BuildingStyle('test.1', 0.2, [3,3,6], 'roofFlat', 1.6, ['wl','wl','wn','d','d','wn','wl'], ['wl','wn','wn','wn', 'wn'], [ 2,4,1 ], [ 0x554444, 0x884411, 0x441155, 0x550044 ]),
+ new BuildingStyle('test.2', 0.2, [3,3,6], 'roofFlat', 0.2, ['wn','d','wn',], ['wn','wl'], [ 2,4.5,1 ], [ 0x484444, 0x888822, 0x111155, 0x005544 ]),
+ new BuildingStyle('test.3', 0.2, [3,3,6], 'roofFlat', 1.2, ['wl','wn','d','wn','wl'], ['wl','wl','wn','wn'], [ 2,4.5,1 ], [ 0x484444, 0x888822, 0x111155, 0x005544 ]),
+ new BuildingStyle('test.4', 0.2, [3,3,6], 'roofFlat', 3.2, ['wn','d','d','wl','d','d'], ['wl','wn','wl','wl','wl','wn'], [ 2,3,1 ], [ 0x00AA44, 0x888822, 0x111155, 0x005544 ]),
+ new BuildingStyle('test.5', 0.2, [3,3,6], 'roofFlat', 0.8, ['wl','d',], ['wn','wn','wl'], [ 2,4,1 ], [ 0xAA0044, 0x22CC22, 0x1133AA, 0x554400 ]),
+];
+
+var g_SubdivRules = {};
+g_SubdivRules['test'] = [
+ new SubdivRule( 0.1, 'split', 0, 0.4 ), //splitWidth
+ new SubdivRule( 0.1, 'split', 1, 0.5 ), //splitDepth (using WDH here) for val1
+ new SubdivRule( 0.1, 'split', 2, 0.6 ),
+ new SubdivRule( 0.1, 'scale', 0, 0.7 ),
+ new SubdivRule( 0.1, 'scale', 1, 0.7 ),
+ new SubdivRule( 0.1, 'scale', 2, 0.7 ),
+ new SubdivRule( 0.3, 'terminate', 0, 0 )
+];
+
+////////////////////////////////////////////
+
+
+var getRandom = function( seed1, seed2 ){
+ var vec = new THREE.Vector2(seed1,seed2);
+ return Math.abs( ( Math.sin( vec.dot( new THREE.Vector2(12.9898,78.233))) * 43758.5453 ) % 1 );
+}
+
+export default class Building {
+
+ //ctor
+ //params are as described above in overview comments
+ //style - passing style will populate style-specific constraints for the building
+ // geometry objects to render
+ constructor( styleStr, center/*pass new object*/, orientation/*pass new object*/, startHeight, startWidth, startDepth, scene, recursionDepth) {
+ this.startWDH = [startWidth, startDepth, startHeight];
+ //console.log('start WDH: ', this.startWDH);
+ this.orientation = orientation; //unit 3D vector facing out from front of building in xz plane
+ this.center = center;
+
+ this.scene = scene;
+
+ this.finalWDH = [startWidth, startDepth, startHeight]; //init this
+ this.baseFloor = 0; //used by units that end up on top of other units
+
+ this.isOnGround = false; //true/false if this unit is beneath a neither unit, i.e. shouldn't get a roof
+ this.supportsRoof = false; //true if it can take a room, i.e. is on top of any other sub-units
+ this.render = false; //Set to true when should stop sub-dividing and should be drawn
+
+ this.recursionDepth = recursionDepth; //fail-safe
+
+ //console.log('styleRules in ctor ', this.styleRules);
+
+ //// Style ////
+
+ //random seeds - simple for now
+ //Set BEFORE calling getBuildingStyleFromRule
+ this.userSeed = this.scene.stauffUserSeed; //get from user
+ this.seed1 = (center.x + this.userSeed) % 83;
+ this.seed2 = (center.y + this.userSeed) % 53;
+ //console.log( 'ctor: seed1, seed2: ', this.seed1, this.seed2);
+
+ this.styleStr = styleStr; //copy or ref, doesn't matter
+ }
+
+ getRule( ruleArr, extraSeed1, extraSeed2 ) {
+ var rand = getRandom( this.seed1 + extraSeed1, this.seed2 + extraSeed2 ); // [0,1], hopefully nicely distributed
+ //console.log( 'getRule: rand, seed1, seed2: ', rand, this.seed1, this.seed2);
+ var cutoff = 0;
+ var result = '';
+ //console.log('getBSFR: seed1, styleRules: ', this.seed1, this.styleRules);
+
+ //NOTE: don't use array.ForEach here, cuz we can't break out of that loop
+ for( var i = 0; i < ruleArr.length; i++ ) {
+ var element = ruleArr[i];
+ cutoff += element.probability;
+ if( rand <= cutoff ){
+ return element;
+ }
+ };
+ return '';
+ }
+
+ getBuildingStyleFromRule( styleStr ) {
+ var ruleArr = g_styleRules[styleStr];
+ return this.getRule( ruleArr, 0 );
+ }
+
+ //generate everyhting, recursively
+ //For top-level building, call this after you create the object with new.
+ //sub-units will call this after they're created
+ generate() {
+ //console.log('generate: this: ', this);
+ this.style = this.getBuildingStyleFromRule(this.styleStr);
+ console.log('generate: chose style: ', this.style.styleVersion);
+
+
+ this.render = true; //do this until subDivide is working
+ this.isOnGround = true;
+ this.supportsRoof = true;
+
+ //subdivide
+ // resize current building or generate N new buildings/units
+ this.subDivide();
+ //console.log('generate: done with subDivide');
+
+ //If it results in a terminal building/sub-unit, flag is set,
+ // and now we make the components and render objects
+ if( this.render ){
+ //console.log('generate: is render..proceeding..');
+ //generate floors, and then walls/windows/doors
+ //keep track of total height, including partial-floor height for ledges or other stuff
+ this.generateFloors();
+ //need a roof?
+ this.generateRoof();
+ }
+
+ }
+
+ //split a building along a dimension
+ //creates two new buildings
+ split( dim, proportion /*ignoring*/){
+ var newWDH = this.finalWDH.slice(); //copies
+ newWDH[dim] = Math.max( newWDH[dim] / 2, this.style.minWDH[dim] );
+ if( dim == 2) { //height
+ //Just scale the current one and terminate it and create one on top
+ this.render = true;
+ this.finalWDH[2] = Math.max( this.finalWDH[2] / 2, this.style.minWDH[2] );
+ this.supportsRoof = false;
+ //shrink width of top
+ newWDH[0] = Math.max( newWDH[0] - 2, this.style.minWDH[0] );
+ var build = new Building( this.styleStr, this.center, this.orientation, newWDH[2], newWDH[0], newWDH[1], this.scene, this.recursionDepth + 1 );
+ build.baseFloor = this.finalWDH[2];
+ build.isOnGround = false;
+ build.generate();
+ } else{
+ //splitting width or depth
+ //create two new buildings, don't render this one
+ for( var i=-1; i < 2; i+=2 ) {
+ var offset=[0,0];
+ offset[dim] = newWDH[dim] / 2 * i;
+ var b = new Building( this.styleStr,
+ new THREE.Vector3( this.center.x + (offset[0] * this.style.modelWHD[0]) , this.center.y + (offset[1] * this.style.modelWHD[2]), this.center.z ),
+ this.orientation, newWDH[2], newWDH[0], newWDH[1], this.scene, this.recursionDepth + 1 );
+ b.baseFloor = this.baseFloor;
+ b.isOnGround = this.isOnGround;
+ b.supportsRoof = this.supportsRoof;
+ b.generate();
+ }
+ }
+ }
+
+ //Create 0 or more subunits
+ //Use
+ subDivide() {
+ //Otherwise
+ // choose a rule
+ // change this one and/or make new subunits
+ // call generate() on any new subunits
+ this.finalWDH = this.startWDH;
+
+ var ruleArr = g_SubdivRules[this.styleStr];
+ var date = new Date();
+ var extraSeed1 = (date.getTime() % 13);
+ var extraSeed2 = -(date.getTime() % 11);
+ var rule = this.getRule( ruleArr, extraSeed1, extraSeed2 );
+
+ if( this.recursionDepth > 4 ){
+ console.log(" ====== RECURSION DEPTH LIMIT MET ============ ");
+ this.render = true;
+ }
+ else {
+ var done = false;
+ while( ! done ){
+ switch( rule.rule ){
+
+ default : //TESTING
+ //this.split(0, 0); not quite working
+ done = true; //don't continue with this one
+ break;
+ }
+ }
+ }
+
+ }
+
+ generateFloors() {
+ //start with ground floor, facing forward
+ this.generateOneFloor( 0, this.isOnGround );
+ for( var fl = 1; fl < this.finalWDH[2]; fl++ )
+ this.generateOneFloor( fl, false );
+ }
+
+ generateOneFloor( floorNumber, isGroundFloor ) {
+ //Four sides to a floor
+ var outVector = new THREE.Vector3( this.orientation.x, this.orientation.y, this.orientation.z );
+ for( var side = 0; side < 4; side++ ){
+
+ //Determine floor layout
+ //Call a method based on style?
+ //Test:
+ var layout = [];
+ if( isGroundFloor)
+ layout = this.style.groundLayout;
+ else
+ layout = this.style.upperLayout;
+ //Now generate geometry
+ var numUnits = this.finalWDH[ side % 2 ];
+ var centerUnit = numUnits / 2.0 - 0.5;
+ var fwdOffset = this.finalWDH[ (side+1) % 2 ] / 2; //get the other of width or depth
+ for( var unit=0; unit < numUnits; unit++) {
+ //try to get offset right to align with axes
+ //console.log('calling building.generateMesh');
+ var index = unit % layout.length; //layout is fixed length, so just repeat or truncate as necessary
+ this.generateWallMesh( layout[index], unit - centerUnit, fwdOffset, floorNumber, side * 90 )
+ };
+
+ }
+ }
+
+ generateWallMesh( typeStr, lateralOffsetWallUnits, fwdOffsetWallUnits, heightFloorUnits, rotation ){
+ //console.log('in generateWallMesh: ', typeStr, lateralOffsetWallUnits, fwdOffsetWallUnits, heightFloorUnits, rotation);
+ var mesh = this.style.generateMesh( typeStr );
+
+ var modelWHD = this.style.modelWHD;
+ //Scale to size
+ mesh.scale.set( modelWHD[0], modelWHD[1], modelWHD[2] );
+
+ //Move into position
+ mesh.position.set( this.center.x, this.center.y, this.center.z );
+
+ //Orientation
+ // orient to face along building orientation (TODO)
+ //Rotate into orientation for the wall that it's in
+ mesh.rotateY( rotation / 180 * 3.141592 );
+
+ //translate
+ var x = modelWHD[0] * lateralOffsetWallUnits;
+ var y = modelWHD[1] * (heightFloorUnits + this.baseFloor + 0.5); //move up half wall-unit to get center at center of floor height
+ var z = modelWHD[0] * fwdOffsetWallUnits; //use wall width for this
+ mesh.translateX( x );
+ mesh.translateY( y );
+ mesh.translateZ( z );
+
+ //add to scene
+ this.scene.add( mesh );
+ }
+
+ generateRoof() {
+ var roofType;
+ var roofHeight;
+
+ if( this.supportsRoof ) {//does it need a roof? if not, just a flat top, i.e. bottom unit of multi-unit building
+ roofType = this.style.roofType;
+ roofHeight = this.style.roofHeight;
+ } else {
+ roofType = 'cap';
+ roofHeight = 0.1;
+ }
+
+ var mesh = this.style.generateMesh( roofType );
+
+ //scale
+ var modelWHD = this.style.modelWHD;
+ mesh.scale.set( modelWHD[0] * this.finalWDH[0], modelWHD[1] * roofHeight, modelWHD[0] * this.finalWDH[1] );
+
+ //Orient
+ //TODO
+
+ //move
+ var x = this.center.x;
+ var y = this.center.y + ( this.finalWDH[2] + roofHeight / 2 ) * modelWHD[1];
+ var z = this.center.z;
+ mesh.translateX( x );
+ mesh.translateY( y );
+ mesh.translateZ( z );
+ this.scene.add( mesh );
+ }
+
+ } //end class Building
\ No newline at end of file
diff --git a/src/framework.js b/src/framework.js
new file mode 100644
index 00000000..76f901a5
--- /dev/null
+++ b/src/framework.js
@@ -0,0 +1,72 @@
+
+const THREE = require('three');
+const OrbitControls = require('three-orbit-controls')(THREE)
+import Stats from 'stats-js'
+import DAT from 'dat-gui'
+
+// when the scene is done initializing, the function passed as `callback` will be executed
+// then, every frame, the function passed as `update` will be executed
+function init(callback, update) {
+ var stats = new Stats();
+ stats.setMode(1);
+ stats.domElement.style.position = 'absolute';
+ stats.domElement.style.left = '0px';
+ stats.domElement.style.top = '0px';
+ document.body.appendChild(stats.domElement);
+
+ var gui = new DAT.GUI();
+
+ var framework = {
+ gui: gui,
+ stats: stats
+ };
+
+ // run this function after the window loads
+ window.addEventListener('load', function() {
+
+ var scene = new THREE.Scene();
+ var camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 );
+ var renderer = new THREE.WebGLRenderer( { antialias: true } );
+ renderer.setPixelRatio(window.devicePixelRatio);
+ renderer.setSize(window.innerWidth, window.innerHeight);
+ renderer.setClearColor(0x020202, 0);
+
+ var controls = new OrbitControls(camera, renderer.domElement);
+ controls.enableDamping = true;
+ controls.enableZoom = true;
+ controls.target.set(0, 0, 0);
+ controls.rotateSpeed = 0.3;
+ controls.zoomSpeed = 1.0;
+ controls.panSpeed = 2.0;
+
+ document.body.appendChild(renderer.domElement);
+
+ // resize the canvas when the window changes
+ window.addEventListener('resize', function() {
+ camera.aspect = window.innerWidth / window.innerHeight;
+ camera.updateProjectionMatrix();
+ renderer.setSize(window.innerWidth, window.innerHeight);
+ }, false);
+
+ // assign THREE.js objects to the object we will return
+ framework.scene = scene;
+ framework.camera = camera;
+ framework.renderer = renderer;
+
+ // begin the animation loop
+ (function tick() {
+ stats.begin();
+ update(framework); // perform any requested updates
+ renderer.render(scene, camera); // render the scene
+ stats.end();
+ requestAnimationFrame(tick); // register to call this again when the browser renders a new frame
+ })();
+
+ // we will pass the scene, gui, renderer, camera, etc... to the callback function
+ return callback(framework);
+ });
+}
+
+export default {
+ init: init
+}
\ No newline at end of file
diff --git a/src/lsystem.js b/src/lsystem.js
new file mode 100644
index 00000000..72fb974b
--- /dev/null
+++ b/src/lsystem.js
@@ -0,0 +1,270 @@
+const THREE = require('three'); // older modules are imported like this. You shouldn't have to worry about this much
+
+// 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
+}
+
+// TODO: Implement a linked list class and its requisite functions
+// as described in the homework writeup
+export class Node {
+ constructor(character, prev, next){
+ this.character = character;
+ this.prev = prev;
+ this.next = next;
+ }
+}
+
+export class LinkedList {
+ constructor() {
+ this.head = null;
+ }
+ append( node ){
+ if( this.head === null ){
+ this.head = node;
+ return;
+ }
+ var n = this.head;
+ var done = false;
+ while( ! done ) {
+ if( n.next === null ){
+ n.next = node;
+ return;
+ }
+ n = n.next;
+ }
+ }
+}
+
+//Find the last node in a list of node, not necessarily
+// a LinkedList
+export function getLastNode( firstNode ) {
+ var lastNode = firstNode;
+ while( lastNode.next !== null )
+ lastNode = lastNode.next;
+ return lastNode;
+}
+
+//Append node B to node A
+//Returns A
+export function appendNode( A, B ){
+ A.next = B;
+ B.prev = A;
+ return A;
+}
+
+// Take a string and make linked nodes,
+// but not a full LinkedList.
+// Returns the first node
+export function stringToNodes(input_string){
+ var prev = null;
+ var first = null
+ for( var i=0; i < input_string.length; i++ ){
+ var node = new Node( input_string.charAt(i), prev, null );
+ if( first === null )
+ //Begin of list
+ first = node;
+ else
+ //Point predecessor to this new node
+ prev.next = node;
+ //point the node to its predecessor
+ node.prev = prev;
+ //set up for next iteration
+ prev = node;
+ }
+ return first;
+}
+// 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')
+ var ll = new LinkedList();
+ ll.head = stringToNodes(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 = "";
+ var node = linkedList.head;
+ while( node !== null ){
+ result += node.character;
+ node = node.next;
+ }
+ return result;
+}
+
+// TODO: Given the node to be replaced,
+// insert a sub-linked-list that represents replacementString
+function replaceNode(linkedList, node, replacementString) {
+ var firstNewNode = stringToNodes( replacementString );
+ firstNewNode.prev = node.prev;
+ var next = null;
+ if( linkedList.head === node ){
+ next = node.next;
+ linkedList.head = firstNewNode;
+ } else {
+ node.prev.next = firstNewNode;
+ next = node.next;
+ }
+ //point to the old node's next node
+ var lastNewNode = getLastNode( firstNewNode );
+ lastNewNode.next = next;
+ if( next !== null )
+ next.prev = lastNewNode;
+}
+
+export default function Lsystem(axiom, grammar, iterations) {
+ // default LSystem
+ this.axiom = "FA";
+ this.grammar = {};
+ this.grammar['A'] = [
+ new Rule(0.55, '[YY-CFA][YY+CFA]'),
+ new Rule(0.25, '[yyy--CFA][y++CFA]'),
+ new Rule(0.2, '[ZZcccA]' )
+ ];
+ this.grammar['B'] = [
+ new Rule(0.0, ''),
+ new Rule(0.0, ''),
+ new Rule(0.0, '' )
+ ];
+ this.grammar['C'] = [
+ new Rule(0.0, ''),
+ new Rule(0.0, ''),
+ new Rule(0.0, '' )
+ ];
+ this.iterations = 10;
+ this.prevIterations = -1;
+
+ this.startingRotations = [10,0,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;
+ }
+ }
+
+ this.getRandom = function(x,y){
+ var vec = new THREE.Vector2(x,y);
+ return Math.abs( ( Math.sin( vec.dot( new THREE.Vector2(12.9898,78.233))) * 43758.5453 ) % 1 );
+ }
+
+ //Normalize rule probabilities
+ this.normalizeRuleProbabilities = function () {
+ for (var property in this.grammar) {
+ if (this.grammar.hasOwnProperty(property)) {
+ // do stuff
+ var ruleArr = this.grammar[property];
+ var sum = 0;
+ for( var i = 0; i < ruleArr.length; i++ ) {
+ sum += ruleArr[i].probability;
+ };
+ if( sum > 0)
+ for( var i = 0; i < ruleArr.length; i++ ) {
+ ruleArr[i].probability /= sum;
+ };
+ }
+ }
+
+ }
+ //For this node's character, check if there's an expansion for it
+ // and return the string. Otherwise just return ''.
+ this.getRuleExpansion = function( node, seed1, seed2 ){
+ if( this.grammar[node.character] === undefined ){
+ //console.log('getRuleExp: no expansion for ', node.character);
+ return '';
+ }
+ //console.log('getRuleExp: expanding ', node.character);
+
+ var date = new Date();
+ seed1 += (date.getTime() % 7);
+ seed2 -= (date.getTime() % 11);
+
+ //We've got a match. Choose rule variation based on probability.
+ //Assumes cumulative probability w/in a rule character is 1.0
+ var rand = this.getRandom(seed1, seed2); // [0,1], hopefully nicely distributed
+ var cutoff = 0;
+ var result = '';
+ var ruleArr = this.grammar[node.character];
+ //NOTE: don't use array.ForEach here, cuz we can't break out of that loop
+ for( var i = 0; i < ruleArr.length; i++ ) {
+ var element = ruleArr[i];
+ cutoff += element.probability;
+ if( rand <= cutoff ){
+ //Make a list of linked nodes from the string
+ //console.log('getRuleExp - matched - element: ', element);
+ result = element.successorString;
+ //console.log('getRuleExp: chose rule ', result, ' rand, cutoff: ', rand, cutoff );
+ break;
+ }
+ };
+
+ //should only get here when total probs for a rule set don't sum to 1.0
+ if( result == '' && cutoff >= 1.0 )
+ //alert('getRuleExpansion...oops!');
+ console.log('WARNING: getRuleExpansion...oops! No result. cutoff, rand: ', cutoff, rand);
+ return result;
+ }
+
+ // 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(numIts) {
+ //Get the linked list representing the axom
+ var lSystemLL = StringToLinkedList(this.axiom);
+ /*console.log('axiom: ', this.axiom);
+ console.log('lSystemLL ', lSystemLL );
+ console.log('head ', lSystemLL.head );
+ console.log('last ', getLastNode(lSystemLL.head) );*/
+
+ //Normalize the rule probabilities
+ this.normalizeRuleProbabilities();
+
+ //Run through N interations of expanding rules
+ //0 iterations is just the axiom, so for n==1 we just do one iteration
+ for( var iter = 1; iter <= numIts; iter++ ){
+ for( var node = lSystemLL.head; node !== null; node=node.next ){
+ //For this node's character, check if there's an expansion for it
+ // and return the new node list for it. Otherwise just return this node.
+ var string = this.getRuleExpansion( node, iter+1, numIts );
+ //console.log('doIts: string: ', string);
+ if( string != '' )
+ replaceNode(lSystemLL, node, string);
+ //debug
+ //var str = LinkedListToString(lSystemLL);
+ //console.log('iteration ', iter, ' of ', numIts);
+ //console.log('lsys ll to string: ', str );
+ }
+ }
+
+ var str = LinkedListToString(lSystemLL);
+ console.log('Final lsys ll to string: ', str );
+ return lSystemLL;
+ }
+}
\ No newline at end of file
diff --git a/src/main.js b/src/main.js
new file mode 100644
index 00000000..e53b3ae5
--- /dev/null
+++ b/src/main.js
@@ -0,0 +1,108 @@
+
+const THREE = require('three'); // older modules are imported like this. You shouldn't have to worry about this much
+import Framework from './framework'
+//import Lsystem, {LinkedListToString} from './lsystem.js'
+//import Turtle from './turtle.js'
+import Building from './building.js'
+
+// called after the scene loads
+function onLoad(framework) {
+ var scene = framework.scene;
+ var camera = framework.camera;
+ var renderer = framework.renderer;
+ var gui = framework.gui;
+ var stats = framework.stats;
+
+ // initialize a simple box and material
+ var directionalLight = new THREE.DirectionalLight( 0xffffff, 1 );
+ directionalLight.color.setHSL(0.1, 1, 0.95);
+ directionalLight.position.set(1, 3, 2);
+ directionalLight.position.multiplyScalar(10);
+ scene.add(directionalLight);
+
+ var light = new THREE.AmbientLight( 0x404040 ); // soft white light
+ scene.add( light );
+
+ // set camera position
+ camera.position.set(200, 300, 180);
+ camera.lookAt(new THREE.Vector3(0,0,0));
+
+ ////// gui
+
+ gui.add(camera, 'fov', 0, 180).onChange(function(newVal) {
+ camera.updateProjectionMatrix();
+ });
+
+ var obj = { generate:function(){ makeBuildings(framework) }};
+
+ gui.add(obj,'generate');
+
+ //user-changeable seed
+ //hack into scene
+ scene.stauffUserSeed = 37;
+ gui.add( scene, 'stauffUserSeed', 0, 9999).name('user seed').step(1);
+
+ makeBuildings(framework);
+
+}
+
+function getRandom(seed){
+ //random number
+ var date = new Date();
+ var seed1 = seed + (date.getTime() % 13);
+ var seed2 = seed - (date.getTime() % 17);
+ var vec = new THREE.Vector2(seed1,seed2);
+ return Math.abs( ( Math.sin( vec.dot( new THREE.Vector2(12.9898,78.233))) * 43758.5453 ) % 1 );
+}
+
+function makeBuildings(framework){
+ //clear scene
+ var obj;
+ for( var i = framework.scene.children.length - 1; i > 3; i--) {
+ obj = framework.scene.children[i];
+ framework.scene.remove(obj);
+ }
+
+ //quick regular grid
+
+ var xbound = 100;
+ var zbound = 100;
+ var xstep = 50;
+ var zstep = 50;
+ for( var xoff = -xbound; xoff < xbound; xoff += xstep ){
+ for( var zoff = -zbound; zoff < zbound; zoff += zstep ){
+ var center = new THREE.Vector3( xoff, 0, zoff);
+ var heightScale = 1.1 - ( Math.sqrt( xoff * xoff + zoff * zoff ) / Math.sqrt( xbound * xbound + zbound * zbound ) );
+ var height = Math.ceil(heightScale * 30 /*floors*/) + getRandom( framework.scene.stauffUserSeed ) * 4;
+ var diff = getRandom( framework.scene.stauffUserSeed ) * 8
+ //generate!
+ var building = new Building(
+ 'test', /* style */
+ center, /*center*/
+ new THREE.Vector3(0,0,1), /*orientation*/
+ height, /*startHeight in floors*/
+ 20 - diff, /*startWidth in wall units */
+ 20 - diff / 2, /*startDepth*/
+ framework.scene,
+ 0 /*recursion depth*/);
+ //hack in a diff random number
+ framework.scene.stauffUserSeed = getRandom( 81 ) * 81;
+
+ //generate and render
+ building.generate();
+ }
+ }
+ //ground plane
+ var geometry = new THREE.PlaneGeometry(400.0, 400.0, 5, 5);
+ var material = new THREE.MeshBasicMaterial( {color: 0x333333} );
+ var mesh = new THREE.Mesh( geometry, material );
+ mesh.rotateX( -3.14159 / 2 );
+ framework.scene.add(mesh);
+
+}
+// called on frame updates
+function onUpdate(framework) {
+}
+
+// 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-proj3.js b/src/turtle-proj3.js
new file mode 100644
index 00000000..b30df6c0
--- /dev/null
+++ b/src/turtle-proj3.js
@@ -0,0 +1,185 @@
+const THREE = require('three')
+
+// A class used to encapsulate the state of a turtle at a given moment.
+// 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, color/* val [0,1]*/, rotArr ) {
+ var rotCopy = [];
+ rotCopy[0] = rotArr[0];
+ rotCopy[1] = rotArr[1];
+ rotCopy[2] = rotArr[2];
+
+ return {
+ pos: new THREE.Vector3(pos.x, pos.y, pos.z),
+ dir: new THREE.Vector3(dir.x, dir.y, dir.z),
+ color: color,
+ rot: rotCopy
+ }
+}
+
+export default class Turtle {
+
+ constructor(scene, startingRotations, grammar) {
+ //this.state = new TurtleState(new THREE.Vector3(0,0,0), new THREE.Vector3(0,1,0), 0, [30,0,0]);
+ this.scene = scene;
+ this.rotStep = 4;
+
+ // 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, 1),
+ '-' : this.rotateTurtle.bind(this, -1),
+ 'X' : this.rotateChange.bind(this, [this.rotStep,0,0]),
+ 'x' : this.rotateChange.bind(this, [-this.rotStep,0,0]),
+ 'Y' : this.rotateChange.bind(this, [0,this.rotStep,0]),
+ 'y' : this.rotateChange.bind(this, [0,-this.rotStep,0]),
+ 'Z' : this.rotateChange.bind(this, [0,0,this.rotStep]),
+ 'z' : this.rotateChange.bind(this, [0,0,-this.rotStep]),
+ 'F' : this.makeCylinder.bind(this, 2, 0.1),
+ '[' : this.pushState.bind(this),
+ ']' : this.popState.bind(this),
+ 'C' : this.changeColor.bind(this, 0.8),
+ 'c' : this.changeColor.bind(this, -0.8),
+ };
+ } else {
+ this.renderGrammar = grammar;
+ }
+
+ this.stateStack = [];
+ this.startingRotations = startingRotations;
+ //console.log('turtle ctor about to call clear');
+ this.clear(); //sets new state
+ }
+
+
+ // Resets the turtle's position to the origin
+ // and its orientation to the Y axis
+ clear() {
+ //console.log('clear: startingRots: ', this.startingRotations );
+ this.state = new TurtleState(new THREE.Vector3(0,0,0), new THREE.Vector3(0,1,0), 0, this.startingRotations );
+ this.stateStack = [];
+ }
+
+ // A function to help you debug your turtle functions
+ // by printing out the turtle's current state.
+ printState() {
+ console.log(this.state.pos)
+ console.log(this.state.dir)
+ }
+
+ // Copy a state to stack
+ pushState() {
+ //Note that TurtleState makes new Vector3 obj's from the input, as best I can tell
+ this.stateStack.push( new TurtleState( this.state.pos, this.state.dir, this.state.color, this.state.rot ) );
+
+ }
+ // Pop the state stack, return ref
+ popState() {
+ this.state = this.stateStack.pop();
+ }
+
+ //Change the state color value (single float) by amount
+ changeColor( amount ) {
+ this.state.color += amount;
+ this.state.color %= 1;
+ //console.log('changeColor: new: ', this.state.color);
+ }
+
+ // Rotate the turtle's _dir_ vector by each of the
+ // Euler angles indicated by the input.
+ rotateTurtle(sign) {
+ //console.log('rotateTurtle: state.rot ', this.state.rot, ' sign ', sign, ' x ', x);
+ var e = new THREE.Euler(
+ this.state.rot[0] * sign * 3.14/180,
+ this.state.rot[1] * sign * 3.14/180,
+ this.state.rot[2] * sign * 3.14/180);
+ this.state.dir.applyEuler(e);
+ }
+
+ rotateChange( change ) {
+ for( var i = 0; i < change.length; i++ )
+ this.state.rot[i] += change[i];
+ }
+
+ // Translate the turtle along the input vector.
+ // Does NOT change the turtle's _dir_ vector
+ 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);
+ };
+
+ // Get a color based on [0,1] range.
+ // Return a THREE.Color obj
+ getColor( colorValIn ) {
+ //Simply color ramp.
+ //Domain [0,0.5] goes from min to max, and [0.5,1]] goes max to min
+ // to allow it to cycle if you just keep incrementing colorVal
+ var colorVal = Math.abs(colorValIn) % 1;
+ if( colorVal <= 0.5 )
+ colorVal *=2;
+ else
+ colorVal = 1 - colorVal;
+
+ //simple color ramp
+ var r = Math.pow( colorVal, 1 );
+ var g = Math.pow( colorVal, 3 );
+ var b = 1 - Math.pow( colorVal, 1);
+
+ return new THREE.Color( r, g, b );
+ }
+
+ // 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: 0x11cc11} );
+ material.color = this.getColor( this.state.color);
+
+ 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.dir);
+ 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.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);
+ };
+
+ // 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.
+ renderSymbol(symbolNode) {
+ var func = this.renderGrammar[symbolNode.character];
+ if (func) {
+ func();
+ }
+ };
+
+ // 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) {
+ this.renderSymbol(currentNode);
+ }
+ }
+}
\ No newline at end of file
diff --git a/webpack.config.js b/webpack.config.js
new file mode 100644
index 00000000..57dce485
--- /dev/null
+++ b/webpack.config.js
@@ -0,0 +1,28 @@
+const path = require('path');
+
+module.exports = {
+ entry: path.join(__dirname, "src/main"),
+ output: {
+ filename: "./bundle.js"
+ },
+ module: {
+ loaders: [
+ {
+ test: /\.js$/,
+ exclude: /(node_modules|bower_components)/,
+ loader: 'babel',
+ query: {
+ presets: ['es2015']
+ }
+ },
+ {
+ test: /\.glsl$/,
+ loader: "webpack-glsl"
+ },
+ ]
+ },
+ devtool: 'source-map',
+ devServer: {
+ port: 7000
+ }
+}
\ No newline at end of file