From b97193afb0da1010176868857f15e27911529bf5 Mon Sep 17 00:00:00 2001 From: mgstauffer Date: Wed, 15 Feb 2017 23:59:26 -0500 Subject: [PATCH 1/4] working on it... --- deploy.js | 38 +++++++ index.html | 19 ++++ package.json | 31 +++++ src/building.js | 265 +++++++++++++++++++++++++++++++++++++++++++ src/framework.js | 72 ++++++++++++ src/lsystem.js | 270 ++++++++++++++++++++++++++++++++++++++++++++ src/main.js | 55 +++++++++ src/turtle-proj3.js | 185 ++++++++++++++++++++++++++++++ webpack.config.js | 28 +++++ 9 files changed, 963 insertions(+) create mode 100644 deploy.js create mode 100644 index.html create mode 100644 package.json create mode 100644 src/building.js create mode 100644 src/framework.js create mode 100644 src/lsystem.js create mode 100644 src/main.js create mode 100644 src/turtle-proj3.js create mode 100644 webpack.config.js 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..833b8713 --- /dev/null +++ b/src/building.js @@ -0,0 +1,265 @@ +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 + + +*/ + +var buildingStyleConstraints = function( minFloors, minWidth, minDepth ){ + return { + minFloors: minFloors, + minWidth: minWidth, + minDepth: minDepth + } +} + +//style test +var generateMesh_test = function( typeStr ) { + var mesh; + var geometry; + var material; + switch( typeStr ){ + case 'wall' : + geometry = new THREE.PlaneGeometry(1.0, 1.0, 5, 5); + material = new THREE.MeshBasicMaterial( {color: 0x555555} ); + break; + case 'window' : + geometry = new THREE.PlaneGeometry(1.0, 1.0, 5, 5); + material = new THREE.MeshBasicMaterial( {color: 0x118811} ); + break; + case 'door' : + geometry = new THREE.PlaneGeometry(1.0, 1.0, 5, 5); + material = new THREE.MeshBasicMaterial( {color: 0x771111} ); + break; + case 'roof' : + geometry = new THREE.BoxGeometry(1.0, 1.0, 1.0); + material = new THREE.MeshBasicMaterial( {color: 0x888811} ); + break; + + default: + break; + } + return new THREE.Mesh( geometry, material ); +} + +export class BuildingStyle { + constructor( styleStr ){ + + styleStr = styleStr; //copies + + //constraints + var constraints = {} + var generateMesh = function(){}; + var wallScaleWHD = []; + switch( styleStr ){ + case 'test' : + constraints = new buildingStyleConstraints( 10, 6, 6 ); + generateMesh = generateMesh_test; + //Scale of wall units to geometry units + //width, height, depth (x,y,z) + wallScaleWHD = [ 2,4,1 ]; + break; + case 'office1' : + constraints = new buildingStyleConstraints( 5, 6, 6 ); + break; + default: //throw an error here + break; + } + } +} + +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) { + this.startWDH = [startWidth, startDepth, startHeight]; + this.orientation = orientation; //unit 3D vector facing out from front of building in xz plane + this.center = center; + + this.styleStr = styleStr; //copy or ref, doesn't matter + this.style = new BuildingStyle(styleStr); + this.scene = scene; + + this.WDH = [startWidth, startDepth, startHeight]; + + 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.terminal = false; //Set to true when should stop sub-dividing + } + + //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() { + //subdivide + // resize current building or generate N new buildings/units + this.subDivide(); + + + //If it results in a terminal building/sub-unit, flag is set, + // and now we make the components and render objects + if( this.terminal ){ + //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(); + } + + } + + //Create 0 or more subunits + //Use + subDivide() { + //Test - just return original + this.terminal = true; + this.isOnGround = true; + this.supportsRoof = true; + + //Otherwise + // choose a rule + // change this one and/or make new subunits + // call generate() on any new subunits + } + + generateFloors() { + //start with ground floor, facing forward + this.generateOneFloor( 0, this.isOnGround, 0 ); + for( var fl = 1; fl < this.WHD[1]; fl++ ) + this.generateOneFloor( 1, false, fl * 90 ); + } + + generateOneFloor( floorNumber, isGroundFloor, rotation /*rotation in degrees from front orientation */ ) { + //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 = new []; + //resize to width or depth + var length = this.WDH[ side % 2 ]; + layout[ length - 1 ] = 'junk'; + //start by filling with walls + layout.fill('wall'); + layout[1] = 'window'; //testing + layout[length-2] = 'window'; + if( isGroundFloor ) + layout[ length / 2 ] = 'door' + + //Now generate geometry + var centerIndex = layout.length / 2.0 - 0.5; + var fwdOffset = this.WDH[ (side+1) % 2 ] / 2; //get the other dim + layout.forEach( function(item, index) { + //try to get offset right to align with axes + this.generateMesh( item, index - centerIndex, fwdOffset, floorNumber, rotation ) + } ); + + } + } + + generateRoof() { + + } + + generateMesh( typeStr, lateralOffsetWallUnits, fwdOffsetWallUnits, heightFloorUnits, rotation ){ + var mesh = this.style.generateMesh( typeStr ); + //Scale to size + mesh.scale.set( style.wallScaleWHD[0], style.wallScaleWHD[1], style.wallScaleWHD[2] ); + + //Rotate + mesh.rotateY( rotation / 180 * 3.141592 ); + + //Move into position + var x = style.wallScaleWHD[0] * lateralOffsetWallUnits; + var y = style.wallScaleWHD[1] * (heightFloorUnits + 0.5); //move up half wall-unit to get center at center of floor height + var z = style.wallScaleWHD[2] * fwdOffsetWallUnits; + mesh.position.set( x, y, z); + + //add to scene + 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..06c47330 --- /dev/null +++ b/src/main.js @@ -0,0 +1,55 @@ + +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); + + // set camera position + camera.position.set(0, 0, 10); + camera.lookAt(new THREE.Vector3(0,0,0)); + + ////// gui + + gui.add(camera, 'fov', 0, 180).onChange(function(newVal) { + camera.updateProjectionMatrix(); + }); + + + //generate! + var building = new Building( + 'test', /* style */ + new THREE.Vector3(0,0,0), /*center*/ + new THREE.Vector3(0,0,1), /*orientation*/ + 10, /*startHeight in floors*/ + 6, /*startWidth in wall units */ + 4, /*startDepth*/ + scene); + + var geometry = new THREE.PlaneGeometry(5.0, 5.0, 5, 5); + var material = new THREE.MeshBasicMaterial( {color: 0x888811} ); + var mesh = new THREE.Mesh( geometry, material ); + 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 From eb2f301b7d0419f80a6d5eb75d94e67ee2506d8c Mon Sep 17 00:00:00 2001 From: mgstauffer Date: Thu, 16 Feb 2017 23:52:57 -0500 Subject: [PATCH 2/4] done enough except readme --- src/building.js | 355 ++++++++++++++++++++++++++++++++++-------------- src/main.js | 83 +++++++++-- 2 files changed, 324 insertions(+), 114 deletions(-) diff --git a/src/building.js b/src/building.js index 833b8713..4b790210 100644 --- a/src/building.js +++ b/src/building.js @@ -84,103 +84,172 @@ components - objects for architectural details. do these just get rendered on to */ -var buildingStyleConstraints = function( minFloors, minWidth, minDepth ){ - return { - minFloors: minFloors, - minWidth: minWidth, - minDepth: minDepth - } -} - -//style test -var generateMesh_test = function( typeStr ) { - var mesh; - var geometry; - var material; - switch( typeStr ){ - case 'wall' : - geometry = new THREE.PlaneGeometry(1.0, 1.0, 5, 5); - material = new THREE.MeshBasicMaterial( {color: 0x555555} ); - break; - case 'window' : - geometry = new THREE.PlaneGeometry(1.0, 1.0, 5, 5); - material = new THREE.MeshBasicMaterial( {color: 0x118811} ); - break; - case 'door' : - geometry = new THREE.PlaneGeometry(1.0, 1.0, 5, 5); - material = new THREE.MeshBasicMaterial( {color: 0x771111} ); - break; - case 'roof' : - geometry = new THREE.BoxGeometry(1.0, 1.0, 1.0); - material = new THREE.MeshBasicMaterial( {color: 0x888811} ); - break; - - default: - break; +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; } - return new THREE.Mesh( geometry, material ); -} -export class BuildingStyle { - constructor( styleStr ){ - - styleStr = styleStr; //copies - - //constraints - var constraints = {} - var generateMesh = function(){}; - var wallScaleWHD = []; - switch( styleStr ){ - case 'test' : - constraints = new buildingStyleConstraints( 10, 6, 6 ); - generateMesh = generateMesh_test; - //Scale of wall units to geometry units - //width, height, depth (x,y,z) - wallScaleWHD = [ 2,4,1 ]; + 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 'office1' : - constraints = new buildingStyleConstraints( 5, 6, 6 ); + case 'd' : + geometry = new THREE.PlaneGeometry(1.0, 1.0, 5, 5); + material = new THREE.MeshLambertMaterial( {color: this.colors[2]} ); break; - default: //throw an error here + 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) { + 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.styleStr = styleStr; //copy or ref, doesn't matter - this.style = new BuildingStyle(styleStr); this.scene = scene; - this.WDH = [startWidth, startDepth, startHeight]; + 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.terminal = false; //Set to true when should stop sub-dividing + 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.terminal ){ + 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(); @@ -190,28 +259,81 @@ export default class Building { } + //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() { - //Test - just return original - this.terminal = true; - this.isOnGround = true; - this.supportsRoof = true; - //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, 0 ); - for( var fl = 1; fl < this.WHD[1]; fl++ ) - this.generateOneFloor( 1, false, fl * 90 ); + this.generateOneFloor( 0, this.isOnGround ); + for( var fl = 1; fl < this.finalWDH[2]; fl++ ) + this.generateOneFloor( fl, false ); } - generateOneFloor( floorNumber, isGroundFloor, rotation /*rotation in degrees from front orientation */ ) { + 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++ ){ @@ -219,47 +341,82 @@ export default class Building { //Determine floor layout //Call a method based on style? //Test: - var layout = new []; - //resize to width or depth - var length = this.WDH[ side % 2 ]; - layout[ length - 1 ] = 'junk'; - //start by filling with walls - layout.fill('wall'); - layout[1] = 'window'; //testing - layout[length-2] = 'window'; - if( isGroundFloor ) - layout[ length / 2 ] = 'door' - + var layout = []; + if( isGroundFloor) + layout = this.style.groundLayout; + else + layout = this.style.upperLayout; //Now generate geometry - var centerIndex = layout.length / 2.0 - 0.5; - var fwdOffset = this.WDH[ (side+1) % 2 ] / 2; //get the other dim - layout.forEach( function(item, index) { + 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 - this.generateMesh( item, index - centerIndex, fwdOffset, floorNumber, rotation ) - } ); + //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 ) + }; } } - generateRoof() { - - } - - generateMesh( typeStr, lateralOffsetWallUnits, fwdOffsetWallUnits, heightFloorUnits, rotation ){ + 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( style.wallScaleWHD[0], style.wallScaleWHD[1], style.wallScaleWHD[2] ); + mesh.scale.set( modelWHD[0], modelWHD[1], modelWHD[2] ); - //Rotate - mesh.rotateY( rotation / 180 * 3.141592 ); - //Move into position - var x = style.wallScaleWHD[0] * lateralOffsetWallUnits; - var y = style.wallScaleWHD[1] * (heightFloorUnits + 0.5); //move up half wall-unit to get center at center of floor height - var z = style.wallScaleWHD[2] * fwdOffsetWallUnits; - mesh.position.set( x, y, z); + 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/main.js b/src/main.js index 06c47330..af6f6ad1 100644 --- a/src/main.js +++ b/src/main.js @@ -20,8 +20,11 @@ function onLoad(framework) { 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(0, 0, 10); + camera.position.set(200, 400, 500); camera.lookAt(new THREE.Vector3(0,0,0)); ////// gui @@ -30,23 +33,73 @@ function onLoad(framework) { 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); + +} - //generate! - var building = new Building( - 'test', /* style */ - new THREE.Vector3(0,0,0), /*center*/ - new THREE.Vector3(0,0,1), /*orientation*/ - 10, /*startHeight in floors*/ - 6, /*startWidth in wall units */ - 4, /*startDepth*/ - scene); - - var geometry = new THREE.PlaneGeometry(5.0, 5.0, 5, 5); - var material = new THREE.MeshBasicMaterial( {color: 0x888811} ); - var mesh = new THREE.Mesh( geometry, material ); - scene.add(mesh); +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 ) * 5 + //generate! + var building = new Building( + 'test', /* style */ + center, /*center*/ + new THREE.Vector3(0,0,1), /*orientation*/ + height, /*startHeight in floors*/ + 18 - diff, /*startWidth in wall units */ + 18 - 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) { } From cde6b7f56f86d1b52358c8f39366f3ff86c28423 Mon Sep 17 00:00:00 2001 From: mgstauffer Date: Thu, 16 Feb 2017 23:54:54 -0500 Subject: [PATCH 3/4] done enough except readme 2 --- src/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.js b/src/main.js index af6f6ad1..f145ac45 100644 --- a/src/main.js +++ b/src/main.js @@ -24,7 +24,7 @@ function onLoad(framework) { scene.add( light ); // set camera position - camera.position.set(200, 400, 500); + camera.position.set(200, 300, 180); camera.lookAt(new THREE.Vector3(0,0,0)); ////// gui From f40e2d1c349cc7071b556331c4e7811abe3de004 Mon Sep 17 00:00:00 2001 From: mgstauffer Date: Fri, 17 Feb 2017 00:01:55 -0500 Subject: [PATCH 4/4] submission --- README.md | 22 ++++++++++++++++++++++ src/main.js | 6 +++--- 2 files changed, 25 insertions(+), 3 deletions(-) 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/src/main.js b/src/main.js index f145ac45..e53b3ae5 100644 --- a/src/main.js +++ b/src/main.js @@ -74,15 +74,15 @@ function makeBuildings(framework){ 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 ) * 5 + 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*/ - 18 - diff, /*startWidth in wall units */ - 18 - diff / 2, /*startDepth*/ + 20 - diff, /*startWidth in wall units */ + 20 - diff / 2, /*startDepth*/ framework.scene, 0 /*recursion depth*/); //hack in a diff random number