diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..5171c540 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +npm-debug.log \ No newline at end of file diff --git a/README.md b/README.md index fad423fa..81749a7a 100644 --- a/README.md +++ b/README.md @@ -1,45 +1,45 @@ # Project 4: Shape Grammar -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. - -**Note:** We’re well aware that a nice-looking procedural city is a lot of work for a single week. Focus on designing a nice building grammar. The city layout strategies outlined in class (the extended l-systems) are complex and not expected. We will be satisfied with something reasonably simple, just not a uniform grid! - -## Symbol Node (5 points) -Modify your symbol node class to include attributes necessary for rendering, such as -- Associated geometry instance -- Position -- Scale -- Anything else you may need - -## Grammar design (55 points) -- Design at least five shape grammar rules for producing procedural buildings. Your buildings should vary in geometry and decorative features (beyond just differently-scaled cubes!). At least some of your rules should create child geometry that is in some way dependent on its parent’s state. (20 points) - - Eg. A building may be subdivided along the x, y, or z axis into two smaller buildings - - Some of your rules must be designed to use some property about its location. (10 points) - - Your grammar should have some element of variation so your buildings are non-deterministic. Eg. your buildings sometimes subdivide along the x axis, and sometimes the y. (10 points) -- Write a renderer that will interpret the results of your shape grammar parser and adds the appropriate geometry to your scene for each symbol in your set. (10 points) - -## Create a city (30 points) -- Add a ground plane or some other base terrain to your scene (0 points, come on now) -- Using any strategy you’d like, procedurally generate features that demarcate your city into different areas in an interesting and plausible way (Just a uniform grid is neither interesting nor plausible). (20 points) - - Suggestions: roads, rivers, lakes, parks, high-population density - - Note, these features don’t have to be directly visible, like high-population density, but they should somehow be visible in the appearance or arrangement of your buildings. Eg. High population density is more likely to generate taller buildings -- Generate buildings throughout your city, using information about your city’s features. Color your buildings with a method that uses some aspect of its state. Eg. Color buildings by height, by population density, by number of rules used to generate it. (5 points) -- Document your grammar rules and general approach in the readme. (5 points) -- ??? -- Profit. - -## Make it interesting (10) -Experiment! Make your city a work of art. - - -## Warnings: -You can very easily blow up three.js with this assignment. With a very simple grammar, our medium quality machine was able to handle 100 buildings with 6 generations each, but be careful if you’re doing this all CPU-side. - -## Suggestions for the overachievers: -Go for a very high level of decorative detail! -Place buildings with a strategy such that buildings have doors and windows that are always accessible. -Generate buildings with coherent interiors -If dividing your city into lots, generate odd-shaped lots and create building meshes that match their shape ie. rather than working with cubes, extrude upwards from the building footprints you find to generate a starting mesh to subdivide rather than starting with platonic geometry. +#### Grammar design: +turtle.js is where all the magic happens. +- Everything starts with a giant cube. The cube divides and takes different paths to generate different buildings. + - The most probable case is the cube getting divided into more cubes. Also, This is the only thing that happens when the cube is too big to create other geometries. + - When a cube divides, 9 or less new cubes are formed, mostly taller than the original cube (unless it is too far away from the area with high population density). + - Sometimes, if the dividing cube is small enough and closer to the downtown, it turns into a cylinder, and cylinders have their own different life story. + - Sometimes, offsprings are not created upon division, or only a few of them are created to avoid a uniform grid pattern. This also gives rise to empty blocks. When such empty blocks are created near the downtown, a fancy spiral building with is created. If the empty blocks are far away, a monument may get created with a little probability. The monument has scale issues that need to be fixed. +- A cylinder never gets divided. Whenever a cylinder is in the list of current nodes, it gets expanded into more cylinders at each iteration and becomes a skyscraper. +- The monument starts from being a pyramid, and becomes something like a usual monument. At every iteration, pillars are added, steps are reduced, and the top part is enlarged. +- There is a limit to the number of times anything can be divided (to avoid needle buildings). +- Buildings are rotated a bit to avoid too much uniformity. Even after scaling them while rotating them, they sometimes intersect one another. +- The height of the buildings drop off with distance from the point with high population density. The point of highest density is chosen randomly (at some distance around the center), so results will vary at every execution. + +#### Issues: +- There is a texture tiling issue. Even after creating a copy of the material, changing the tiling parameters, changes them on all the cubes. +- The monument has scale issues. +- The intersection tests of geometry with the ground plane and with water bodies is buggy. +- I sort of invented my own noise algorithm because Perlin noise was too much work. It is bad. + +#### Screen Shots: +##### City : +The dropoff due to population density can be seen. +![](./samples/pop.PNG) + +##### Downtown : +The cylindrical towers can be seen here. +![](./samples/cyl1.PNG) + +##### Spiral Tower: +![](./samples/spiral1.PNG) + +![](./samples/spiral2.PNG) + +##### Monument: +![](./samples/mon1.PNG) + +![](./samples/mon2.PNG) + +![](./samples/mon3.PNG) + +#### Demo: +https://rms13.github.io/Project4-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/models/tex3.jpg b/models/tex3.jpg new file mode 100644 index 00000000..ed63c20e Binary files /dev/null and b/models/tex3.jpg differ diff --git a/package.json b/package.json new file mode 100644 index 00000000..e99188d9 --- /dev/null +++ b/package.json @@ -0,0 +1,32 @@ +{ + "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-obj-loader": "^1.0.2", + "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/samples/cyl1.PNG b/samples/cyl1.PNG new file mode 100644 index 00000000..f5599b53 Binary files /dev/null and b/samples/cyl1.PNG differ diff --git a/samples/mon1.PNG b/samples/mon1.PNG new file mode 100644 index 00000000..a75c9257 Binary files /dev/null and b/samples/mon1.PNG differ diff --git a/samples/mon2.PNG b/samples/mon2.PNG new file mode 100644 index 00000000..406330f4 Binary files /dev/null and b/samples/mon2.PNG differ diff --git a/samples/mon3.PNG b/samples/mon3.PNG new file mode 100644 index 00000000..ebb27879 Binary files /dev/null and b/samples/mon3.PNG differ diff --git a/samples/pop.PNG b/samples/pop.PNG new file mode 100644 index 00000000..d1a09e30 Binary files /dev/null and b/samples/pop.PNG differ diff --git a/samples/spiral1.PNG b/samples/spiral1.PNG new file mode 100644 index 00000000..5f8917de Binary files /dev/null and b/samples/spiral1.PNG differ diff --git a/samples/spiral2.PNG b/samples/spiral2.PNG new file mode 100644 index 00000000..f8346790 Binary files /dev/null and b/samples/spiral2.PNG differ diff --git a/src/framework.js b/src/framework.js new file mode 100644 index 00000000..9d31f553 --- /dev/null +++ b/src/framework.js @@ -0,0 +1,73 @@ + +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(0xacdbc9, 1); + + 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 +} diff --git a/src/lsystem.js b/src/lsystem.js new file mode 100644 index 00000000..b40fb804 --- /dev/null +++ b/src/lsystem.js @@ -0,0 +1,311 @@ +// 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 +// } +// +// function Node(symbol) +// { +// this.prev = null; +// this.next = null; +// this.symbol = symbol; +// this.age = 0; +// this.geometry = null; +// this.pos = null; +// this.orient = null; +// this.scale = null; +// this.name = ""; +// } + +// export class LinkedList { +// //var grammar= +// constructor() { +// this.head = null; +// this.tail = null; +// this.length = 0; +// } +// +// //ADDS B NEXT TO A +// addNext(A,B) { +// //var node = new Node(value); +// //console.log(A.symbol); +// //console.log(B.symbol); +// if(this.length===1) +// insertEnd(B); +// else { +// B.prev=A; +// B.next=A.next; +// if(A.next !== null) +// A.next.prev=B; +// A.next=B; +// this.length++; +// } +// } +// +// deleteNode(N) +// { +// if(this.length!==0) +// { +// if(this.length===1) +// { +// this.head = null; +// this.tail = null; +// this.length = 0; +// } +// else +// { +// if(N.prev!== null) +// { +// N.prev.next=N.next; +// } +// if(N.next!== null) +// { +// N.next.prev=N.prev; +// } +// this.length--; +// } +// } +// } +// +// // Creates a Node with value value at the tail +// insertEnd(gram) { +// var node = new Node(gram); +// //console.log(node.symbol); +// if (this.length!=0) { +// this.tail.next = node; +// node.prev = this.tail; +// this.tail = node; +// } else { +// this.head = node; +// this.tail = node; +// } +// this.length++; +// } +// +// // expands node N +// expand(N,grammar){ +// if(grammar[N.symbol]!=undefined) +// { +// //console.log(grammar.X[0].successorString); +// if(N.symbol==='F') +// { +// var ran=Math.random(); +// for(var i=0; grammar.F[i]!==undefined; i++) +// { +// if(grammar.F[i].probability>=ran) +// replaceNode(this,N,grammar.F[i].successorString); +// else +// ran-=grammar.F[i].probability; +// } +// } +// else if(N.symbol==='X') +// { +// var ran=Math.random(); +// for(var i=0; grammar.X[i]!==undefined; i++) +// { +// if(grammar.X[i].probability>=ran) +// replaceNode(this,N,grammar.X[i].successorString); +// else +// ran-=grammar.X[i].probability; +// } +// } +// else if(N.symbol==='A') +// { +// var ran=Math.random(); +// for(var i=0; grammar.A[i]!==undefined; i++) +// { +// if(grammar.B[i].probability>=ran) +// replaceNode(this,N,grammar.A[i].successorString); +// else +// ran-=grammar.A[i].probability; +// } +// } +// else if(N.symbol==='B') +// { +// var ran=Math.random(); +// for(var i=0; grammar.B[i]!==undefined; i++) +// { +// if(grammar.B[i].probability>=ran) +// replaceNode(this,N,grammar.B[i].successorString); +// else +// ran-=grammar.B[i].probability; +// } +// } +// } +// } +// } +// +// export function stringToLinkedList(input_string) { +// var ll = new LinkedList(); +// //console.log(input_string); +// for(var i=0; iNode2("X") should be "FX" +// var nextnode = linkedList.head; +// var result = ""; +// while(nextnode!==null) +// { +// result+=nextnode.symbol; +// nextnode=nextnode.next; +// } +// //console.log(result); +// return result; +// } +// +// function replaceNode(linkedList, node, replacementString) { +// // console.log("------------------------------"); +// // console.log(linkedListToString(linkedList)); +// // console.log(replacementString); +// // console.log(node.symbol); +// +// var newlist = stringToLinkedList(replacementString); +// +// +// if(node.next!==null) +// { +// node.next.prev=newlist.tail; +// if(newlist.tail!==null) +// newlist.tail.next=node.next; +// } +// else { +// linkedList.tail=newlist.tail; +// } +// +// node.next=newlist.head; +// if(newlist.head!==null) +// newlist.head.prev=node; +// +// linkedList.length+=newlist.length; +// +// linkedList.deleteNode(node); +// } +// +// export default function Lsystem(axiom, grammar, iterations) { +// // default LSystem +// this.axiom = "F"; +// this.F='FF-A[-AF+AF+AF]+A[+AF-AF-AF]B';//'FF-B[-AF+AF+AF]+A[+AF-AF-AF]-A[-AF+AF+AF]'; +// this.X='X'; +// this.A='A'; +// this.B='-A[-AF+AF+AF]'; +// this.ProbabilityA=1.0; +// this.ProbabilityB=0.1; +// this.grammar = {}; +// +// this.grammar['F'] = [ new Rule(1.0, this.F) ]; +// this.grammar['X'] = [ new Rule(1.0, this.X) ]; // default: '[-FX][+FX]'.. wikipedia: 'F−[[X]+X]+F[+FX]−X' +// //this.grammar['A'] = [ new Rule(0.1, '[BA]') ]; +// this.grammar['A'] = [ new Rule(this.ProbabilityA, this.A) ]; +// this.grammar['B'] = [ new Rule(this.ProbabilityB, this.B) ]; +// +// // this.grammar['F'] = [ new Rule(1.0, 'FXFX') ]; +// // this.grammar['X'] = [ new Rule(1.0, 'F−A[[X]+AX]+AF[+AFX]−AX') ]; // default: '[-FX][+FX]'.. wikipedia: 'F−[[X]+X]+F[+FX]−X' +// // //this.grammar['A'] = [ new Rule(0.1, '[BA]') ]; +// // this.grammar['A'] = [ new Rule(0.3, '[AB]') ]; +// // this.grammar['B'] = [ new Rule(0.1, 'B[[F]A[F]A[F]A[F]A[F]A[F]A]') ]; +// +// this.iterations = 0; +// +// // Set up the axiom string +// if (typeof axiom !== "undefined") { +// this.axiom = axiom; +// } +// +// // Set up the grammar as a dictionary that +// // maps a single character (symbol) to a Rule. +// if (typeof grammar !== "undefined") { +// console.log("grammar updated"); +// 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.updateGramF = function(gram) { +// // Setup axiom +// if (typeof gram !== "undefined") { +// this.grammar.F[0].successorString = gram; +// } +// } +// +// this.updateGramX = function(gram) { +// // Setup axiom +// if (typeof gram !== "undefined") { +// this.grammar.X[0].successorString = gram; +// } +// } +// +// this.updateGramA = function(gram) { +// // Setup axiom +// if (typeof gram !== "undefined") { +// this.grammar.A[0].successorString = gram; +// } +// } +// +// this.updateGramB = function(gram) { +// // Setup axiom +// if (typeof gram !== "undefined") { +// this.grammar.B[0].successorString = gram; +// } +// } +// +// this.updateProbA = function(num) { +// // Setup axiom +// if (typeof num !== "undefined") { +// this.grammar.A[0].probability = num; +// } +// } +// +// this.updateProbB = function(num) { +// // Setup axiom +// if (typeof num !== "undefined") { +// this.grammar.B[0].probability = num; +// } +// } +// +// // TODO +// // This function returns a linked list that is the result +// // of expanding the L-system's axiom n times. +// // The implementation we have provided you just returns a linked +// // list of the axiom. +// this.doIterations = function(n) { +// var lSystemLL = stringToLinkedList(this.axiom); +// //console.log("hi doit"); +// for(var i=0; i 3; i--) { + obj = turtle.scene.children[i]; + turtle.scene.remove(obj); + } +} + +function doLsystem(lsystem, iterations, turtle) { + // var result = lsystem.doIterations(iterations); + // turtle.clear(); + // turtle = new Turtle(turtle.scene); + turtle.renderSymbols(result); +} + +// 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.js b/src/turtle.js new file mode 100644 index 00000000..29808c2f --- /dev/null +++ b/src/turtle.js @@ -0,0 +1,566 @@ +//import img from './main.js' + +const THREE = require('three') +const OBJLoader = require('three-obj-loader')(THREE) + +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 +} + +export function Node(symbol) +{ + this.symbol = symbol; + this.age = 0; + this.mesh = null; + this.pos = null; + this.orient = null; + this.scale = null; + this.type = ''; + this.div = 0; + this.steps = 0; // used only for the ruins + //this.ruins = false; // used only for the ruins +} + +// var TurtleState = function(pos, dir) { +// return { +// pos: new THREE.Vector3(pos.x, pos.y, pos.z), +// dir: new THREE.Vector3(dir.x, dir.y, dir.z) +// } +// } + + + + + + +var Material1 = new THREE.MeshLambertMaterial( {color: 0xf7f7e6} ); //whitish lambert +var Material2 = new THREE.MeshPhongMaterial( {color: 0x131315} ); //metal +var Material3 = new THREE.MeshLambertMaterial( {color: 0xc45e61} ); //metal +//var texture = THREE.ImageUtils.loadTexture('crate.gif'); +var Material4;// = new THREE.MeshBasicMaterial({map: texture}); +var loader = new THREE.TextureLoader(); +loader.load('models/tex3.jpg', function ( texture ) { + texture.wrapS = THREE.RepeatWrapping; + texture.wrapT = THREE.RepeatWrapping; + texture.repeat.x = 2; + texture.repeat.y = 2; + Material4 = new THREE.MeshPhongMaterial({map: texture, overdraw: 0.5}); +}); +var objLoader = new THREE.OBJLoader(); +// var leafOBJ; +// objLoader.load('models/leaf.obj', function(obj) { +// leafOBJ = obj.children[0].geometry; +// }); + +export default class Turtle +{ + constructor(scene) { + //this.state = new TurtleState(new THREE.Vector3(0,0,0), new THREE.Vector3(0,1,0)); + this.scene = scene; + this.nodeList = []; + this.ruinsList = []; + this.iterations = 0; + this.highpop = new THREE.Vector3(Math.random()*5+0, 0, Math.random()*5+0); // high population density point in the city. + this.ruins = false; + + this.img = []; + this.img1 = []; + for(var i=0; i<40; i++) + { + this.img[i]=[]; + this.img1[i]=[]; + } + this.createTerrain(); + } + + createTerrain() { + var geometry1 = new THREE.PlaneGeometry( 20*2, 20*2, 32 ); + var material1 = new THREE.MeshPhongMaterial( {color: 0x5386ba, side: THREE.DoubleSide} ); + var plane1 = new THREE.Mesh( geometry1, material1 ); + plane1.position.set(9,-0.01,9); + plane1.rotateX(90*3.14/180); + this.scene.add( plane1 ); + + var geometry = new THREE.PlaneGeometry( 20*2, 20*2, 39, 39); + var material = new THREE.MeshLambertMaterial( {color: 0xb2ae92, wireframe: false ,side: THREE.DoubleSide} ); + var plane = new THREE.Mesh( geometry, material ); + // plane.castShadow = true; + // plane.receiveShadow = true; + plane.position.set(9,0,9); + plane.rotateX(90*3.14/180); + + for(var i=0; i<40; i++) + { + for(var j=0; j<40; j++) + { + var r=0; + //if(Math.random()<0.4) + r -= Math.random()*2; + if(i%2===0 && j%2===0 && Math.random()<0.5) + r -= Math.random()*4; + if(i%4===0 && j%4===0 && Math.random()<0.5) + r -= Math.random()*8; + if(i%8===0 && j%8===0 && Math.random()<0.5) + r -= Math.random()*16; + this.img1[i][j]= r; + } + } + + for(var i=0+2; i<40-2; i++) + { + for(var j=0+2; j<40-2; j++) + { + this.img[i][j]=0; + for(var k=-2; k<3; k++) + { + for(var l=-2; l<3; l++) + { + this.img[i][j] += this.img1[i+k][j+l]/25; + } + } + plane.geometry.vertices[i*40+j].z=this.img[i][j]+1; + //console.log(img[i][j]); + } + } + plane.name="plane"; + this.scene.add( plane ); + + } + + addNode(node) // PUSH A NODE TO THE NODELIST - AT END + { + this.nodeList.push(node); + }; + + removeNode(node) // REMOVE A NODE FROM NODELIST - BY NAME + { + var pos = this.nodeList.indexOf(node); + this.removeNodeAt(pos, 1); + }; + + removeNodeAt(pos) // REMOVE A NODE FROM NODELIST - BY POSITION IN ARRAY + { + this.nodeList.splice(pos,1); + }; + + // A function to help you debug your turtle functions + // by printing out the turtle's current state. + + // Rotate the turtle's _dir_ vector by each of the + // Euler angles indicated by the input. + // rotateTurtle(x, y, z) { + // var e = new THREE.Euler( + // x * 3.14/180, + // y * 3.14/180, + // z * 3.14/180); + // this.state.dir.applyEuler(e); + // } + + // Translate the turtle along its _dir_ vector by the distance indicated + // moveForward(dist) { + // var newVec = this.state.dir.multiplyScalar(dist); + // this.state.pos.add(newVec); + // }; + + // Make a cylinder of given length and width starting at turtle pos + // Moves turtle pos ahead to end of the new cylinder + makeCylinder(nodeCyl) { + var geometry = new THREE.CylinderGeometry(0.5,0.5,1); + var cylinder = new THREE.Mesh(geometry, Material1); + cylinder.position.set(nodeCyl.pos.x,nodeCyl.pos.y,nodeCyl.pos.z); + cylinder.rotateX(nodeCyl.orient.x); + cylinder.rotateY(nodeCyl.orient.y); + cylinder.rotateZ(nodeCyl.orient.z); + cylinder.scale.set(nodeCyl.scale.x,nodeCyl.scale.y,nodeCyl.scale.z); + nodeCyl.type = 'cylinder'; + nodeCyl.mesh = cylinder; + this.scene.add(cylinder); + }; + + makeCube(nodeCube) + { + var geometry = new THREE.CubeGeometry(1,1,1); + var mat = new THREE.MeshLambertMaterial(); + //mat.copy(Material4); + var cube = new THREE.Mesh(geometry, Material4); + cube.position.set(nodeCube.pos.x,nodeCube.pos.y,nodeCube.pos.z); + cube.rotateX(nodeCube.orient.x); + cube.rotateY(nodeCube.orient.y); + cube.rotateZ(nodeCube.orient.z); + cube.scale.set(nodeCube.scale.x,nodeCube.scale.y,nodeCube.scale.z); + // mat.map.wrapS = THREE.RepeatWrapping; + // mat.map.wrapT = THREE.RepeatWrapping; + // mat.map.repeat.x=nodeCube.scale.x; + // mat.map.repeat.y=nodeCube.scale.y; + nodeCube.type = 'cube'; + nodeCube.mesh = cube; + this.scene.add(cube); + //console.log(nodeCube); + }; + + makeRuins(nodeR,steps) + { + var geometry = new THREE.CubeGeometry(5,5,5); + var cube = new THREE.Mesh(geometry, Material1); + //var floors=99; + for (var i = 0; i < steps; i++) + { + var instance = cube.clone(); + instance.position.set(nodeR.pos.x,i*0.1*nodeR.scale.y+nodeR.scale.y/2/steps,nodeR.pos.z); + instance.scale.set((steps-i+1)*0.1,0.1,(steps-i+1)*0.1); + instance.name='ruins_stuff'+i; + this.ruinsList.push(instance); + this.scene.add(instance); + } + + nodeR.type = 'ruins'; + nodeR.steps = steps; + + // vertical scales of parts: + // var bot = Math.random() * 0.5; + // var top = (1-bot)/2; + // var mid = top; + // + // var R_top = new Node('R_TOP'); + // R_top.pos = new THREE.Vector3(pos.x,1-top,pos.z); + // R_top.scale = new THREE.Vector3(1,top,1); + // R_top.orient = new THREE.Vector3(0,0,0); + // var geometry = new THREE.CubeGeometry(1,1,1); + // var cube = new THREE.Mesh(geometry, Material3); + // cube.position.set(R_top.pos.x,R_top.pos.y,R_top.pos.z); + // cube.scale.set(R_top.scale.x,R_top.scale.y,R_top.scale.z); + // R_top.type = 'ruins_top'; + // R_top.mesh = cube; + // this.scene.add(cube); + // + // var R_bot = new Node('R_BOTTOM'); + // R_bot.pos = new THREE.Vector3(pos.x,0,pos.z); + // R_bot.scale = new THREE.Vector3(1,bot,1); + // R_bot.orient = new THREE.Vector3(0,0,0); + // var geometry = new THREE.CubeGeometry(1,1,1); + // var cube = new THREE.Mesh(geometry, Material3); + // cube.position.set(R_bot.pos.x,R_bot.pos.y,R_bot.pos.z); + // cube.scale.set(R_bot.scale.x,R_bot.scale.y,R_bot.scale.z); + // R_bot.type = 'ruins_bot'; + // R_bot.mesh = cube; + // this.scene.add(cube); + + //console.log(nodeCube); + }; + + expandRuins(nodeR) + { + console.log(nodeR.symbol); + console.log(nodeR.steps-nodeR.div-3); + // CLEAR OLD STUFF FROM THE SCENE + for (var i = 0; i < this.ruinsList.length; i++) + { + var ins=this.ruinsList.pop(); + this.scene.remove(ins); + } + + // BOTTOM STEPS + var geometry = new THREE.CubeGeometry(5,5,5); + var cube = new THREE.Mesh(geometry, Material1); + for (var i = 0; i < nodeR.steps-nodeR.div-3; i++) + { + var instance = cube.clone(); + instance.position.set(nodeR.pos.x,i*0.1*nodeR.scale.y+nodeR.scale.y/20,nodeR.pos.z); + instance.scale.set((nodeR.steps-i+1)*0.1,0.1,(nodeR.steps-i+1)*0.1); + instance.name='ruins_stuff'+nodeR.symbol+i; + this.ruinsList.push(instance); + this.scene.add(instance); + } + + // PILLARS + geometry = new THREE.CylinderGeometry(0.1,0.1,(nodeR.div+1)); + var cylinder = new THREE.Mesh(geometry, Material1); + for (var i = -nodeR.div-1; i <= nodeR.div+1; i++) + { + for(var j = -nodeR.div-1; j <= nodeR.div+1; j++) + { + if(Math.abs(i)0.5) + // symbolNode.age++; + } + else if (symbolNode.type === 'cylinder') + { + if(symbolNode.div < 5) + this.subDivCyl(symbolNode); + // if(symbolNode.age===3 && Math.random()>0.5) + // symbolNode.age++; + } + else if (symbolNode.type === 'ruins') + { + console.log('ruins'); + if(symbolNode.div < 3) + this.expandRuins(symbolNode); + // if(symbolNode.age===3 && Math.random()>0.5) + // symbolNode.age++; + } + + symbolNode.age++; + }; + + start() // AXIOM + { + var C0 = new Node('C0_0'); + C0.pos = new THREE.Vector3(0,0.5,0); + C0.scale = new THREE.Vector3(18,1,18); + C0.orient = new THREE.Vector3(0,0,0); + this.makeCube(C0); + this.addNode(C0); + + var C1 = new Node('C1_0'); + C1.pos = new THREE.Vector3(0,0.5,18); + C1.scale = new THREE.Vector3(18,1,18); + C1.orient = new THREE.Vector3(0,0,0); + this.makeCube(C1); + this.addNode(C1); + + var C2 = new Node('C2_0'); + C2.pos = new THREE.Vector3(18,0.5,18); + C2.scale = new THREE.Vector3(18,1,18); + C2.orient = new THREE.Vector3(0,0,0); + this.makeCube(C2); + this.addNode(C2); + + var C3 = new Node('C3_0'); + C3.pos = new THREE.Vector3(18,0.5,0); + C3.scale = new THREE.Vector3(18,1,18); + C3.orient = new THREE.Vector3(0,0,0); + this.makeCube(C3); + this.addNode(C3); + + //RUINS: + // var R = new Node('R'); + // R.pos = new THREE.Vector3(0,0,0); + // R.scale = new THREE.Vector3(5,5,5); + // R.orient = new THREE.Vector3(0,0,0); + // this.makeRuins(R,10); + // this.addNode(R); + + // var F = new Node('F'); + // F.pos = new THREE.Vector3(0,0,0); + // F.scale = new THREE.Vector3(1,1,1); + // F.orient = new THREE.Vector3(0,0,0); + // //F.div = symbolNode.div+1; + // this.makeFancy(F,99); + // this.addNode(F); + + // console.log(this.scene); + }; + + subDivCube(symbolNode) + { + var divided=false; + var r0= Math.random(); + for(var i=0; i<3 && (r0>0.4 || symbolNode.div===0); i++) + { + var r1= Math.random(); + var innerdivided=false; + for(var j=0; j<3 && (r1>0.3 || symbolNode.div===0); j++) + { + var r2= Math.random(); + var disthighpop = symbolNode.pos.distanceTo(this.highpop); + + if(r2<0.2 && symbolNode.div>1) + continue; + else if(r2<0.1 && symbolNode.div===1 && disthighpop<5 && innerdivided===false) + { + var F = new Node('F'+i+j); + F.pos = new THREE.Vector3(symbolNode.pos.x, symbolNode.pos.y, symbolNode.pos.z); + F.scale = new THREE.Vector3(1,1,1); + F.orient = new THREE.Vector3(0,0,0); + F.div = symbolNode.div+1; + this.makeFancy(F,99); + this.addNode(F); + divided=true; + innerdivided=true; + break; + } + else if(r2<0.1 && symbolNode.div===1 && disthighpop>20 && innerdivided===false && this.ruins===false) + { + //RUINS: + this.ruins=true; + var R = new Node('R'); + R.pos = new THREE.Vector3(symbolNode.pos.x, symbolNode.pos.y, symbolNode.pos.z); + R.scale = new THREE.Vector3(5,5,5); + R.orient = new THREE.Vector3(0,0,0); + this.makeRuins(R,10); + this.addNode(R); + divided=true; + innerdivided=true; + break; + } + + var C = new Node(symbolNode.symbol+symbolNode.age+i+j); + //console.log(symbolNode.pos.x); + C.pos = new THREE.Vector3(symbolNode.pos.x + (i-1)*symbolNode.scale.x/3, + symbolNode.pos.y,//this.scene.getObjectByName("plane").geometry.vertices[symbolNode.pos.x*40+symbolNode.pos.z], + symbolNode.pos.z + (j-1)*symbolNode.scale.z/3); + // var elev=0; + // if(C.pos.x>0 && C.pos.z>0) + // elev=this.img[Math.floor(C.pos.x)][Math.floor(C.pos.z)]; + // if(symbolNode.div>=2 && elev<0.0) + // { + // //divided=true; + // continue; + // } + // else + // { + // symbolNode.pos.y=elev; + // } + var rot = Math.random() * 5-2.5; + var scx = 0.9; + var scz = 0.9; + if(symbolNode.div<2) + { + rot=0; + scx= 1; + scz= 1; + } + C.orient = new THREE.Vector3(symbolNode.orient.x, symbolNode.orient.y + rot, symbolNode.orient.z); + + //console.log(disthighpop*disthighpop); + var scalefactor = Math.random() * (1-(disthighpop*disthighpop)/800) + 1; + C.scale = new THREE.Vector3(symbolNode.scale.x / 3 * scx, symbolNode.scale.y * scalefactor, symbolNode.scale.z / 3 * scz); + C.pos.y = C.scale.y/2; + C.div = symbolNode.div+1; + + if(symbolNode.div==2 && Math.random()>0.8 && disthighpop<5) + { + C.scale.y = Math.random()*3+3; // random height between 3 and 6 + C.pos.y = C.scale.y/2; + this.makeCylinder(C); + } + else + this.makeCube(C); + + if(symbolNode.div>=2 && C.type==='cube' && Math.random()>0.5) // some cubes will not divide + C.div=4; + + this.addNode(C); + + //innerdivided=true; + divided=true; + } + } + + if(divided===true) + { + //symbolNode.div++; + this.scene.remove(symbolNode.mesh); + this.removeNode(symbolNode); + } + }; + + subDivCyl(symbolNode) + { + if(Math.random()>0.4) + { + var C = new Node(symbolNode.symbol+symbolNode.age+'cyl'); + C.pos = new THREE.Vector3(symbolNode.pos.x, symbolNode.pos.y, symbolNode.pos.z); + C.orient = new THREE.Vector3(symbolNode.orient.x, symbolNode.orient.y, symbolNode.orient.z); + //var scalefactor = Math.random() + 0.5; + // var disthighpop = C.pos.distanceTo(this.highpop); + // var scalefactor = Math.random() * (20-disthighpop)/10; + C.scale = new THREE.Vector3(symbolNode.scale.x * 0.75, symbolNode.scale.y + Math.random()/4, symbolNode.scale.z * 0.75); + C.pos.y = symbolNode.pos.y + symbolNode.scale.y/2 + C.scale.y/2; + C.div = symbolNode.div+1; + symbolNode.div=5; + this.makeCylinder(C); + this.addNode(C); + //this.scene.remove(symbolNode.mesh); + + this.removeNode(symbolNode); + } + }; + + +} 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