diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..2f6141d4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules +npm-debug.log +.DS_Store \ No newline at end of file diff --git a/README.md b/README.md index fad423fa..e6e43931 100644 --- a/README.md +++ b/README.md @@ -1,45 +1,16 @@ +# [Project 4: Shape Grammar](https://github.com/CIS700-Procedural-Graphics/Project4-Shape-Grammar) -# 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. +Created a procedural city using Shape grammars. Road map of city is first generated using a uniform radial approach creating circular main roads. Minor roads are also added to connect main roads. -**Goal:** to model an urban environment using a shape grammar. +The size and color of the buildings are dependent on the distance from the center of city. Buildings get smaller as you get closer to the center and the color palette used for the roofs of the buildings changes between every main road. -**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! +Grammar Rules for Building (ordered by priority): -## 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 +1. Scale Rule - Scale shape along X, Y or Z axis +2. Rotate Rule - Rotate shape along X, Y or Z axis +3. Translate Rule - Translate shape along X, Y or Z axis +4. Roof Rule - Add roof to shape +5. Subdivide Rule - Subdivides shape along X, Y or Z axis +6. Windows Rule - Add window to shape -## 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. 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/images/.DS_Store b/images/.DS_Store new file mode 100644 index 00000000..5008ddfc Binary files /dev/null and b/images/.DS_Store differ diff --git a/images/sunset.jpg b/images/sunset.jpg new file mode 100644 index 00000000..594ccd47 Binary files /dev/null and b/images/sunset.jpg differ diff --git a/index.html b/index.html new file mode 100644 index 00000000..c548372b --- /dev/null +++ b/index.html @@ -0,0 +1,19 @@ + + + + HW4: Shape Grammars + + + + + + diff --git a/package.json b/package.json new file mode 100644 index 00000000..488fced9 --- /dev/null +++ b/package.json @@ -0,0 +1,34 @@ +{ + "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", + "three-obj-loader": "^1.0.2", + "jquery": "^2.2.4", + "three.meshline": "^1.0.3" + }, + "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/shapes/.DS_Store b/shapes/.DS_Store new file mode 100644 index 00000000..5008ddfc Binary files /dev/null and b/shapes/.DS_Store differ diff --git a/shapes/base1.obj b/shapes/base1.obj new file mode 100644 index 00000000..2d0ed8c2 --- /dev/null +++ b/shapes/base1.obj @@ -0,0 +1,59 @@ +# This file uses centimeters as units for non-parametric coordinates. + +mtllib base1.mtl +g default +v -0.500000 0.000000 0.500000 +v 0.500000 0.000000 0.500000 +v -0.500000 0.750000 0.500000 +v 0.500000 0.750000 0.500000 +v -0.500000 0.750000 -0.500000 +v 0.500000 0.750000 -0.500000 +v -0.500000 0.000000 -0.500000 +v 0.500000 0.000000 -0.500000 +vt 0.375000 0.000000 +vt 0.375000 0.250000 +vt 0.625000 0.000000 +vt 0.625000 0.250000 +vt 0.375000 0.500000 +vt 0.625000 0.500000 +vt 0.375000 0.750000 +vt 0.625000 0.750000 +vt 0.375000 1.000000 +vt 0.625000 1.000000 +vt 0.875000 0.000000 +vt 0.875000 0.250000 +vt 0.125000 0.000000 +vt 0.125000 0.250000 +vn 0.000000 0.000000 1.000000 +vn 0.000000 0.000000 1.000000 +vn 0.000000 0.000000 1.000000 +vn 0.000000 0.000000 1.000000 +vn 0.000000 0.000000 -1.000000 +vn 0.000000 0.000000 -1.000000 +vn 0.000000 0.000000 -1.000000 +vn 0.000000 0.000000 -1.000000 +vn 0.000000 -1.000000 0.000000 +vn 0.000000 -1.000000 0.000000 +vn 0.000000 -1.000000 0.000000 +vn 0.000000 -1.000000 0.000000 +vn 1.000000 0.000000 0.000000 +vn 1.000000 0.000000 0.000000 +vn 1.000000 0.000000 0.000000 +vn 1.000000 0.000000 0.000000 +vn -1.000000 0.000000 0.000000 +vn -1.000000 0.000000 0.000000 +vn -1.000000 0.000000 0.000000 +vn -1.000000 0.000000 0.000000 +vn 0.000000 1.000000 0.000000 +vn 0.000000 1.000000 0.000000 +vn 0.000000 1.000000 0.000000 +vn 0.000000 1.000000 0.000000 +s off +g roof24 +usemtl roof2:initialShadingGroup +f 4/4/1 3/2/2 1/1/3 2/3/4 +f 7/7/5 5/5/6 6/6/7 8/8/8 +f 2/10/9 1/9/10 7/7/11 8/8/12 +f 2/3/13 8/11/14 6/12/15 4/4/16 +f 7/13/17 1/1/18 3/2/19 5/14/20 +f 3/2/21 4/4/22 6/6/23 5/14/24 diff --git a/shapes/base2.obj b/shapes/base2.obj new file mode 100644 index 00000000..b2fb58ea --- /dev/null +++ b/shapes/base2.obj @@ -0,0 +1,59 @@ +# This file uses centimeters as units for non-parametric coordinates. + +mtllib base2.mtl +g default +v -0.500000 0.000000 0.500000 +v 0.500000 0.000000 0.500000 +v -0.500000 0.666667 0.500000 +v 0.500000 0.666667 0.500000 +v -0.500000 0.666667 -0.500000 +v 0.500000 0.666667 -0.500000 +v -0.500000 0.000000 -0.500000 +v 0.500000 0.000000 -0.500000 +vt 0.375000 0.000000 +vt 0.625000 0.000000 +vt 0.375000 0.250000 +vt 0.625000 0.250000 +vt 0.375000 0.500000 +vt 0.625000 0.500000 +vt 0.375000 0.750000 +vt 0.625000 0.750000 +vt 0.375000 1.000000 +vt 0.625000 1.000000 +vt 0.875000 0.000000 +vt 0.875000 0.250000 +vt 0.125000 0.000000 +vt 0.125000 0.250000 +vn 0.000000 0.000000 1.000000 +vn 0.000000 0.000000 1.000000 +vn 0.000000 0.000000 1.000000 +vn 0.000000 0.000000 1.000000 +vn 0.000000 0.000000 -1.000000 +vn 0.000000 0.000000 -1.000000 +vn 0.000000 0.000000 -1.000000 +vn 0.000000 0.000000 -1.000000 +vn 0.000000 -1.000000 0.000000 +vn 0.000000 -1.000000 0.000000 +vn 0.000000 -1.000000 0.000000 +vn 0.000000 -1.000000 0.000000 +vn 1.000000 0.000000 0.000000 +vn 1.000000 0.000000 0.000000 +vn 1.000000 0.000000 0.000000 +vn 1.000000 0.000000 0.000000 +vn -1.000000 0.000000 0.000000 +vn -1.000000 0.000000 0.000000 +vn -1.000000 0.000000 0.000000 +vn -1.000000 0.000000 0.000000 +vn 0.000000 1.000000 0.000000 +vn 0.000000 1.000000 0.000000 +vn 0.000000 1.000000 0.000000 +vn 0.000000 1.000000 0.000000 +s off +g roof25 +usemtl initialShadingGroup +f 4/4/1 3/3/2 1/1/3 2/2/4 +f 8/8/5 7/7/6 5/5/7 6/6/8 +f 2/10/9 1/9/10 7/7/11 8/8/12 +f 2/2/13 8/11/14 6/12/15 4/4/16 +f 7/13/17 1/1/18 3/3/19 5/14/20 +f 4/4/21 6/6/22 5/14/23 3/3/24 diff --git a/shapes/base3.obj b/shapes/base3.obj new file mode 100644 index 00000000..091bcef5 --- /dev/null +++ b/shapes/base3.obj @@ -0,0 +1,59 @@ +# This file uses centimeters as units for non-parametric coordinates. + +mtllib base3.mtl +g default +v -0.500000 0.000000 0.500000 +v 0.500000 0.000000 0.500000 +v -0.500000 0.750000 0.500000 +v 0.500000 0.750000 0.500000 +v -0.500000 0.750000 -0.500000 +v 0.500000 0.750000 -0.500000 +v -0.500000 0.000000 -0.500000 +v 0.500000 0.000000 -0.500000 +vt 0.375000 0.000000 +vt 0.625000 0.000000 +vt 0.375000 0.250000 +vt 0.625000 0.250000 +vt 0.375000 0.500000 +vt 0.625000 0.500000 +vt 0.375000 0.750000 +vt 0.625000 0.750000 +vt 0.375000 1.000000 +vt 0.625000 1.000000 +vt 0.875000 0.000000 +vt 0.875000 0.250000 +vt 0.125000 0.000000 +vt 0.125000 0.250000 +vn 0.000000 0.000000 1.000000 +vn 0.000000 0.000000 1.000000 +vn 0.000000 0.000000 1.000000 +vn 0.000000 0.000000 1.000000 +vn 0.000000 0.000000 -1.000000 +vn 0.000000 0.000000 -1.000000 +vn 0.000000 0.000000 -1.000000 +vn 0.000000 0.000000 -1.000000 +vn 0.000000 -1.000000 0.000000 +vn 0.000000 -1.000000 0.000000 +vn 0.000000 -1.000000 0.000000 +vn 0.000000 -1.000000 0.000000 +vn 1.000000 0.000000 0.000000 +vn 1.000000 0.000000 0.000000 +vn 1.000000 0.000000 0.000000 +vn 1.000000 0.000000 0.000000 +vn -1.000000 0.000000 0.000000 +vn -1.000000 0.000000 0.000000 +vn -1.000000 0.000000 0.000000 +vn -1.000000 0.000000 0.000000 +vn 0.000000 1.000000 0.000000 +vn 0.000000 1.000000 0.000000 +vn 0.000000 1.000000 0.000000 +vn 0.000000 1.000000 0.000000 +s off +g roof28 +usemtl initialShadingGroup +f 1/1/1 2/2/2 4/4/3 3/3/4 +f 5/5/5 6/6/6 8/8/7 7/7/8 +f 7/7/9 8/8/10 2/10/11 1/9/12 +f 2/2/13 8/11/14 6/12/15 4/4/16 +f 7/13/17 1/1/18 3/3/19 5/14/20 +f 4/4/21 6/6/22 5/14/23 3/3/24 diff --git a/shapes/roof1.obj b/shapes/roof1.obj new file mode 100644 index 00000000..ef067f83 --- /dev/null +++ b/shapes/roof1.obj @@ -0,0 +1,42 @@ +# This file uses centimeters as units for non-parametric coordinates. + +mtllib roof1.mtl +g default +v -0.500000 0.750000 0.500000 +v 0.000000 1.000000 0.500000 +v 0.500000 0.750000 0.500000 +v -0.500000 0.750000 -0.500000 +v 0.000000 1.000000 -0.500000 +v 0.500000 0.750000 -0.500000 +vt 0.500000 0.250000 +vt 0.375000 0.250000 +vt 0.625000 0.250000 +vt 0.500000 0.500000 +vt 0.375000 0.500000 +vt 0.625000 0.500000 +vn -0.567354 0.823474 0.000000 +vn 0.000000 1.000000 0.000000 +vn 0.000000 1.000000 0.000000 +vn -0.567354 0.823474 0.000000 +vn 0.000000 1.000000 0.000000 +vn 0.567354 0.823474 0.000000 +vn 0.567354 0.823474 0.000000 +vn 0.000000 1.000000 0.000000 +vn 0.000000 0.000000 -1.000000 +vn 0.000000 0.000000 -1.000000 +vn 0.000000 0.000000 -1.000000 +vn 0.000000 0.000000 1.000000 +vn 0.000000 0.000000 1.000000 +vn 0.000000 0.000000 1.000000 +vn 0.000000 -1.000000 0.000000 +vn 0.000000 -1.000000 0.000000 +vn 0.000000 -1.000000 0.000000 +vn 0.000000 -1.000000 0.000000 +s off +g roof23 +usemtl roof2:initialShadingGroup +f 1/2/1 2/1/2 5/4/3 4/5/4 +f 2/1/5 3/3/6 6/6/7 5/4/8 +f 6/6/9 4/5/10 5/4/11 +f 2/1/12 1/2/13 3/3/14 +f 1/2/15 4/5/16 6/6/17 3/3/18 diff --git a/shapes/roof2.obj b/shapes/roof2.obj new file mode 100644 index 00000000..40c6abc0 --- /dev/null +++ b/shapes/roof2.obj @@ -0,0 +1,79 @@ +# This file uses centimeters as units for non-parametric coordinates. + +mtllib roof2.mtl +g default +v -0.500000 0.666667 0.500000 +v 0.000000 0.666667 0.500000 +v 0.500000 0.666667 0.500000 +v -0.500000 0.666667 -0.500000 +v 0.000000 0.666667 -0.500000 +v 0.500000 0.666667 -0.500000 +v -0.312111 0.887587 0.500000 +v 0.000000 1.000000 0.500000 +v 0.000000 1.000000 -0.500000 +v -0.312111 0.887587 -0.500000 +v 0.312111 0.887587 0.500000 +v 0.312111 0.887587 -0.500000 +vt 0.375000 0.250000 +vt 0.500000 0.250000 +vt 0.625000 0.250000 +vt 0.375000 0.500000 +vt 0.500000 0.500000 +vt 0.625000 0.500000 +vt 0.375000 0.250000 +vt 0.500000 0.250000 +vt 0.500000 0.500000 +vt 0.375000 0.500000 +vt 0.625000 0.250000 +vt 0.625000 0.500000 +vn -0.338862 0.940836 0.000000 +vn 0.000000 1.000000 0.000000 +vn 0.000000 1.000000 0.000000 +vn -0.338862 0.940836 0.000000 +vn 0.338862 0.940836 0.000000 +vn 0.338862 0.940836 0.000000 +vn 0.000000 0.000000 1.000000 +vn 0.000000 0.000000 1.000000 +vn 0.000000 0.000000 1.000000 +vn 0.000000 0.000000 1.000000 +vn 0.000000 0.000000 -1.000000 +vn 0.000000 0.000000 -1.000000 +vn 0.000000 0.000000 -1.000000 +vn 0.000000 0.000000 -1.000000 +vn -0.761758 0.647862 0.000000 +vn -0.761758 0.647862 0.000000 +vn -0.761758 0.647862 0.000000 +vn -0.761758 0.647862 0.000000 +vn 0.000000 0.000000 1.000000 +vn 0.000000 0.000000 1.000000 +vn 0.761758 0.647862 0.000000 +vn 0.761758 0.647862 0.000000 +vn 0.761758 0.647862 0.000000 +vn 0.761758 0.647862 0.000000 +vn 0.000000 0.000000 -1.000000 +vn 0.000000 0.000000 -1.000000 +vn 0.000000 -1.000000 0.000000 +vn 0.000000 -1.000000 0.000000 +vn 0.000000 -1.000000 0.000000 +vn 0.000000 -1.000000 0.000000 +vn 0.000000 -1.000000 0.000000 +vn 0.000000 -1.000000 0.000000 +s 1 +g roof26 +usemtl initialShadingGroup +f 7/7/1 8/8/2 9/9/3 10/10/4 +f 8/8/2 11/11/5 12/12/6 9/9/3 +s 2 +f 1/1/7 2/2/8 8/8/9 7/7/10 +s 3 +f 5/5/11 4/4/12 10/10/13 9/9/14 +s off +f 4/4/15 1/1/16 7/7/17 10/10/18 +s 2 +f 2/2/8 3/3/19 11/11/20 8/8/9 +s off +f 3/3/21 6/6/22 12/12/23 11/11/24 +s 3 +f 6/6/25 5/5/11 9/9/14 12/12/26 +s off +f 2/2/27 1/1/28 4/4/29 5/5/30 6/6/31 3/3/32 diff --git a/shapes/roof3.obj b/shapes/roof3.obj new file mode 100644 index 00000000..f01d5976 --- /dev/null +++ b/shapes/roof3.obj @@ -0,0 +1,53 @@ +# This file uses centimeters as units for non-parametric coordinates. + +mtllib roof3.mtl +g default +v -0.500000 0.750000 0.500000 +v 0.500000 0.750000 0.500000 +v -0.500000 0.750000 -0.500000 +v 0.500000 0.750000 -0.500000 +v -0.250000 1.000000 0.250000 +v 0.250000 1.000000 0.250000 +v 0.250000 1.000000 -0.250000 +v -0.250000 1.000000 -0.250000 +vt 0.375000 0.250000 +vt 0.625000 0.250000 +vt 0.375000 0.500000 +vt 0.625000 0.500000 +vt 0.375000 0.250000 +vt 0.625000 0.250000 +vt 0.625000 0.500000 +vt 0.375000 0.500000 +vn 0.000000 1.000000 0.000000 +vn 0.000000 1.000000 0.000000 +vn 0.000000 1.000000 0.000000 +vn 0.000000 1.000000 0.000000 +vn 0.000000 0.707107 0.707107 +vn 0.000000 0.707107 0.707107 +vn 0.000000 0.707107 0.707107 +vn 0.000000 0.707107 0.707107 +vn 0.707107 0.707107 0.000000 +vn 0.707107 0.707107 0.000000 +vn 0.707107 0.707107 0.000000 +vn 0.707107 0.707107 0.000000 +vn 0.000000 0.707107 -0.707107 +vn 0.000000 0.707107 -0.707107 +vn 0.000000 0.707107 -0.707107 +vn 0.000000 0.707107 -0.707107 +vn -0.707107 0.707107 0.000000 +vn -0.707107 0.707107 0.000000 +vn -0.707107 0.707107 0.000000 +vn -0.707107 0.707107 0.000000 +vn 0.000000 -1.000000 0.000000 +vn 0.000000 -1.000000 0.000000 +vn 0.000000 -1.000000 0.000000 +vn 0.000000 -1.000000 0.000000 +s off +g roof27 +usemtl initialShadingGroup +f 5/5/1 6/6/2 7/7/3 8/8/4 +f 1/1/5 2/2/6 6/6/7 5/5/8 +f 2/2/9 4/4/10 7/7/11 6/6/12 +f 4/4/13 3/3/14 8/8/15 7/7/16 +f 3/3/17 1/1/18 5/5/19 8/8/20 +f 2/2/21 1/1/22 3/3/23 4/4/24 diff --git a/src/.DS_Store b/src/.DS_Store new file mode 100644 index 00000000..5008ddfc Binary files /dev/null and b/src/.DS_Store differ diff --git a/src/city.js b/src/city.js new file mode 100644 index 00000000..94f3263a --- /dev/null +++ b/src/city.js @@ -0,0 +1,188 @@ +const THREE = require('three'); +const MeshLine = require('three.meshline'); +import ShapeGrammar, {Shape} from './shapeGrammar.js' + + +function getRandomInt(min, max) { + return Math.floor(Math.random() * (max - min + 1)) + min; +} + + +function buildingShapes(shapes, geometry, offset) { + + var lambertGray = new THREE.MeshLambertMaterial({ color: 0x555555, side: THREE.DoubleSide }); + + var sceneShapes = new THREE.Object3D(); + var singleGeometry = new THREE.Geometry(); + + for (var i = 0; i < shapes.length; i++) { + + var s = shapes[i]; + var shapeGeo = geometry[s.geometry]; + lambertGray = new THREE.MeshLambertMaterial({ color: 0x555555, side: THREE.DoubleSide }); + var shapeMesh = new THREE.Mesh(shapeGeo, lambertGray); + shapeMesh.scale.set(s.scale[0],s.scale[1],s.scale[2]); + shapeMesh.position.set(s.position[0]+offset[0],s.position[1]+offset[1],s.position[2]+offset[2]); + shapeMesh.rotation.set(s.rotation[0],s.rotation[1],s.rotation[2]); + shapeMesh.material.color.setHex( s.color ); + sceneShapes.add(shapeMesh); + + } + + return sceneShapes; +} + +function distance3D( v1, v2 ) +{ + var dx = v1[0] - v2[0]; + var dy = v1[1] - v2[1]; + var dz = v1[2] - v2[2]; + + return Math.sqrt( dx * dx + dy * dy + dz * dz ); +} + +function tooCloseToRoad(roadPostions, pos) { + + for (var i = 0; i < roadPostions.length; i++) { + + if (distance3D(roadPostions[i], pos) < 2.0) { + return true; + } + + } + + return false; +} + + +export default function City(iterations, startingRadius) { + + this.radius = 10; + this.resolution = 360; + this.radiusIncrement = 10; + + var waves_amount=3; + var wave_height=0.1*5; + + + //building variables + this.shapes = []; + this.buildingGeometry = []; + this.numOfBuildingsPerPoint = 3; //num of buildings between radial roads + this.buildingIterations = 1; + + this.doIterations = function(n) { + + var all = new THREE.Object3D(); + + var geometry = new THREE.CircleGeometry( 50, 32 ); + var material = new THREE.MeshBasicMaterial( { color: 0xC4B77C } ); + var circle = new THREE.Mesh( geometry, material ); + circle.rotation.set(-Math.PI/2,0,0); + circle.position.set(0,-1,0); + all.add(circle); + + + for (var iter = 0; iter < n; iter++) { + //console.log("do iterations road " + iter); + + var obj = new THREE.Line( new THREE.Geometry(), new THREE.LineBasicMaterial({color:0xf9f9f9, linewidth: 5})); + + //create radial roads + for (var i = 0; i <= this.resolution; i++) { + var angle = Math.PI/180.0*i; + var radius_addon = 0;//wave_height * Math.sin(angle*waves_amount); + var x = (this.radius+radius_addon) * Math.cos(angle); + var z = (this.radius+radius_addon) * Math.sin(angle); + var y=0; + obj.geometry.vertices.push(new THREE.Vector3(x, y, z)); + } + + var line = new MeshLine.MeshLine(); + line.setGeometry( obj.geometry, function( p ) { return 1.0; } ); + var lineMaterial = new MeshLine.MeshLineMaterial({color : new THREE.Color( 0x564B35 ), resolution : new THREE.Vector2(200,200)}); + var mesh = new THREE.Mesh( line.geometry, lineMaterial ); + all.add(mesh); + //all.add(obj); + + if (iter == n-1) + break; + + + //add road segments + var rand = getRandomInt(4*(iter+1),6*(iter+1)); + var roadPostions = []; + var areaDivision = this.numOfBuildingsPerPoint+1; //divide area between radial roads + + for (var i = 0; i < rand; i++) { + + var p = ((2.0 * Math.PI / rand) * i) + (2.0 * Math.PI / rand) * Math.random()/2; + //console.log(p); + var x = Math.cos(p) * this.radius; + var z = Math.sin(p) * this.radius; + var y = 0; + var dir = new THREE.Vector3(x,y,z); + //console.log(dir.x) + + x = Math.cos(p) * (this.radius+this.radiusIncrement); + z = Math.sin(p) * (this.radius+this.radiusIncrement); + var endPoint = new THREE.Vector3(x,y,z); + //console.log(endPoint); + + var l = new THREE.Line( new THREE.Geometry(), new THREE.LineBasicMaterial({color:0xf9f9f9, linewidth: 5})); + l.geometry.vertices.push(new THREE.Vector3(dir.x,dir.y,dir.z)); + l.geometry.vertices.push(new THREE.Vector3(endPoint.x,endPoint.y,endPoint.z)); + + + for (var b = 0; b < this.numOfBuildingsPerPoint; b++) { + var x = Math.cos(p) * (this.radius+this.radiusIncrement/areaDivision*(b+1)); + var z = Math.sin(p) * (this.radius+this.radiusIncrement/areaDivision*(b+1)); + var y = 0; + roadPostions.push([x,y,z]); + } + + var line = new MeshLine.MeshLine(); + line.setGeometry( l.geometry, function( p ) { return 0.5; } ); + var lineMaterial = new MeshLine.MeshLineMaterial({color : new THREE.Color( 0x564B35 ), resolution : new THREE.Vector2(200,200)}); + var mesh = new THREE.Mesh( line.geometry, lineMaterial ); + all.add(mesh); + + //all.add(l); + } + + + + //create buildings + var numBuildings = 36 * (iter+1); + for (var j = 0; j < numBuildings; j++) { + + var point = 2.0 * Math.PI / numBuildings * j; + + for (var b = 0; b < this.numOfBuildingsPerPoint; b++) { + + var x = Math.cos(point) * (this.radius+this.radiusIncrement/areaDivision*(b+1)); + var z = Math.sin(point) * (this.radius+this.radiusIncrement/areaDivision*(b+1)); + var y = 0; + + if (!tooCloseToRoad(roadPostions, [x,y,z])) { + + var shapeGrammar = new ShapeGrammar(); + shapeGrammar.shapes = this.shapes; + shapeGrammar.position = [x,y,z]; + shapeGrammar.cityRadius = this.radius; + + var buildingAll = buildingShapes(shapeGrammar.doIterations(this.buildingIterations), this.buildingGeometry, [x,y,z]); + all.add(buildingAll); + + } + + } + + } + + this.radius += this.radiusIncrement; + } + + return all; + } +} \ No newline at end of file diff --git a/src/framework.js b/src/framework.js new file mode 100644 index 00000000..ff45f5ac --- /dev/null +++ b/src/framework.js @@ -0,0 +1,73 @@ + +const THREE = require('three'); +const OrbitControls = require('three-orbit-controls')(THREE) +const OBJLoader = require('three-obj-loader')(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/main.js b/src/main.js new file mode 100644 index 00000000..933202e4 --- /dev/null +++ b/src/main.js @@ -0,0 +1,164 @@ + +const THREE = require('three'); +const MeshLine = require('three.meshline'); + +import Framework from './framework' +import ShapeGrammar, {Shape} from './shapeGrammar.js' +import City from './city.js' + + +var config = { + cityIterations : 5, + buildingIterations : 6 +}; + +// 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(70, 30, -10); + camera.lookAt(new THREE.Vector3(0,0,0)); + + /*gui.add(camera, 'fov', 0, 180).onChange(function(newVal) { + camera.updateProjectionMatrix(); + }); + + gui.add(config, 'cityIterations', 0, 10).onChange(function(newVal) { + + scene.children.forEach(function(object){ + scene.remove(object); + }); + + loadShapes(shapeNames, 0, [], [], scene, [0,0,0]); + + }); + + + gui.add(config, 'buildingIterations', 0, 6).onChange(function(newVal) { + + scene.children.forEach(function(object){ + scene.remove(object); + }); + + loadShapes(shapeNames, 0, [], [], scene, [0,0,0]); + + });*/ + + var loader = new THREE.TextureLoader(); + var texture = loader.load( 'images/sunset.jpg'); + scene.background = texture; + + var shapeNames = ['base1.obj','base2.obj','base3.obj', 'roof1.obj','roof2.obj','roof3.obj']; + + loadShapes(shapeNames, 0, [], [], scene, [0,0,0]); + +} + + +//recursively load all the axiom shapes in shapeNames +//call the shapegrammer on the axiom shapes +//render the shapes returned from shapegrammar +// shapeGeometry : holds initial shape objs +// shapeList : holds attributes of the initial shapes (axiom) +function loadShapes(shapeNames, i, shapeList, shapeGeometry, scene, pos) { + + var objLoader = new THREE.OBJLoader(); + objLoader.load('shapes/'.concat(shapeNames[i]), function(obj) { + + var shapeGeo = obj.children[0].geometry; + shapeGeometry.push(shapeGeo); + + var box = new THREE.Box3().setFromObject( obj.children[0] ); + + var shape = new Shape(shapeNames[i]); + shape.geometry = i; + shape.terminal = false; + shape.dimensions = [box.getSize().x, box.getSize().y, box.getSize().z]; + + shape.position = pos; + + var result = shape.symbol.indexOf("roof") > -1; + if (!result) + shapeList.push(shape); + + if (i < shapeNames.length-1) + loadShapes(shapeNames, i+1, shapeList, shapeGeometry, scene, pos) + + else { + + + var city = new City(); + city.shapes = shapeList; + city.buildingGeometry = shapeGeometry; + city.buildingIterations = config.buildingIterations; + var ob = city.doIterations(config.cityIterations, 5); + scene.add(ob); + + + //FOR DEBUGGING + /*var shapeGrammar = new ShapeGrammar(); + shapeGrammar.shapes = shapeList; + + var all = renderShapes(shapeGrammar.doIterations(6), shapeGeometry, [0,0,0]); + scene.add(all);*/ + + } + + }); + +} + + +//FOR DEBUGGING +function renderShapes(shapes, geometry, offset) { + + var lambertGray = new THREE.MeshLambertMaterial({ color: 0x555555, side: THREE.DoubleSide }); + + var sceneShapes = new THREE.Object3D(); + var singleGeometry = new THREE.Geometry(); + //console.log(shapes.length); + + for (var i = 0; i < shapes.length; i++) { + + var s = shapes[i]; + lambertGray = new THREE.MeshLambertMaterial({ color: 0x555555, side: THREE.DoubleSide }); + //lambertGray.wireframe = true; + //console.log(s.position); + //var shapeGeo = obj.children[0].geometry; + var shapeGeo = geometry[s.geometry]; + var shapeMesh = new THREE.Mesh(shapeGeo, lambertGray); + //var shapeMesh = geometry.children[s.geometry]; + //console.log(s.rotation); + shapeMesh.scale.set(s.scale[0],s.scale[1],s.scale[2]); + shapeMesh.position.set(s.position[0]+offset[0],s.position[1]+offset[1],s.position[2]+offset[2]); + shapeMesh.rotation.set(s.rotation[0],s.rotation[1],s.rotation[2]); + //shapeMesh.name = "roof1"; + //console.log(shapeMesh); + shapeMesh.updateMatrix(); + //singleGeometry.merge(shapeMesh.geometry, shapeMesh.matrix); + shapeMesh.material.color.setHex( s.color ); + sceneShapes.add(shapeMesh); + + } + + return sceneShapes; + +} + +// 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/shapeGrammar.js b/src/shapeGrammar.js new file mode 100644 index 00000000..82d09a7e --- /dev/null +++ b/src/shapeGrammar.js @@ -0,0 +1,354 @@ +const THREE = require('three'); +const $ = require("jquery"); + +var allRoofsColor = 0x1B5E9B; +var globalPos = [0,0,0]; +var globalRadius = 0; + +function Rule(ruleType, succ) { + this.type = ruleType; + this.predecessor = "*"; + this.cond = null; + //this.probability = prob; + this.successor = succ; + this.priority = 10; +} + +export function Shape(shapeSymbol) { + this.symbol = shapeSymbol; + this.geometry = -1; + this.position = [0,0,0]; + this.rotation = [0,0,0]; + this.scale = [1,1,1]; + this.terminal = false; + this.dimensions = []; + this.applicableRules = []; + this.color = 0xFFFFFF; +} + + +function subdivide(shape, rule) { + + + //var params = rule.successor.match(/ *\([^)]*\) */g); //save params + + //subdivide rule + //console.log(rule.successor); + var re = /([A-Za-z]):(\d+\.?\d*)/; // X:0.5 + var symbolParams = rule.successor.match(re); //get params + var successors = []; + var axis = symbolParams[1]; + var numDivision = symbolParams[2]; + + var index = 0; + + switch(axis) { + case 'X': + index = 0; + break; + case 'Y': + index = 1; + break; + case 'Z': + index = 2; + break; + default: + return [shape]; + break; + } + + //console.log(shape.dimensions); + for (var i = 0; i < numDivision; i++) { + + var newObject = $.extend(true, {}, shape); + //newObject.scale = newObject.scale.map(function(x) { return x/2.0; }); + newObject.scale[index] /= numDivision; + newObject.position[index] -= (shape.dimensions[index]*shape.scale[index] - newObject.dimensions[index]*newObject.scale[index]) / 2.0; + newObject.position[index] += (shape.dimensions[index]*shape.scale[index]/numDivision) * i; + newObject.terminal = false; + successors.push(newObject); + + } + + return successors; +} + + +function addRoofs(shape, rule) { + + var successors = [shape]; + + var newObject = $.extend(true, {}, shape); + newObject.symbol = "roof" + shape.symbol.charAt(4) + ".obj"; + newObject.geometry = parseInt(shape.symbol.charAt(4))+2; + newObject.terminal = false; + + var colors2 = [0xCFC096,0x4A741F, 0x1E3B01,0x2A1C0F,0x292B33]; //jg + var colors3 = [0xC7C7C7,0xBFFFCE,0xF6E94A,0x344317,0x478116]; //plf + var colors4 = [0xFF4823,0xFFFF86,0xB1EB8A,0x66B37B,0x029171]; //p + var colors5 = [0x314841,0x8B8B65,0xFFCB7C,0xFF8437,0xF33616]; //os + + + switch(globalRadius) { + case 10: + newObject.color = colors2[getRandomInt(0,4)]; + break; + case 20: + newObject.color = colors3[getRandomInt(0,4)]; + break; + case 30: + newObject.color = colors4[getRandomInt(0,4)]; + break; + case 40: + newObject.color = colors5[getRandomInt(0,4)]; + break; + default: + newObject.color = colors2[getRandomInt(0,4)]; + } + + successors.push(newObject); + + return successors; +} + + +function addWindows(shape, rule) { + + var side = -1; + + if (rule.successor == '+') { + side = 1; + } + + var successors = [shape]; + + var newObject = $.extend(true, {}, shape); + //newObject.scale = newObject.scale.map(function(x) { return x/2.0; }); + newObject.scale[2] /= 3.0; + newObject.scale[0] /= 3.0; + newObject.scale[1] /= 4.0; + //newObject.scale = newObject.scale.map(function(x) { return x/6.0; }); + newObject.position[1] += shape.dimensions[1]/3.0 * 1.5; + + if (newObject.position[0] >= 0) { + newObject.position[0] += shape.dimensions[0]*shape.scale[0]/2.3 * side; + } else + newObject.position[0] -= shape.dimensions[0]*shape.scale[0]/2.3 * side; + newObject.rotation = [0,Math.PI/2,0]; + newObject.terminal = false; + newObject.color = 0x000000; + + successors.push(newObject); + + return successors; +} + +function scaleShape(shape, rule) { + + var re = /([A-Za-z]):(\d+\.?\d*),([A-Za-z]):(\d+\.?\d*),([A-Za-z]):(\d+\.?\d*)/; + var symbolParams = rule.successor.match(re); //get params + + var newObject = $.extend(true, {}, shape); + + newObject.scale[0] *= parseFloat(symbolParams[2]); + newObject.scale[1] *= parseFloat(symbolParams[4]); + newObject.scale[2] *= parseFloat(symbolParams[6]); + + return [newObject]; +} + + +function translateShape(shape, rule) { + + var re = /([A-Za-z]):([+-]?\d+\.?\d*),([A-Za-z]):([+-]?\d+\.?\d*),([A-Za-z]):([+-]?\d+\.?\d*)/; + var symbolParams = rule.successor.match(re); //get params + //sconsole.log(symbolParams); + + var newObject = $.extend(true, {}, shape); + + newObject.position[0] += parseFloat(symbolParams[2]); + newObject.position[1] += parseFloat(symbolParams[4]); + newObject.position[2] += parseFloat(symbolParams[6]); + + return [newObject]; +} + + +function rotateShape(shape, rule) { + + var re = /([A-Za-z]):([+-]?\d+\.?\d*)/; // X:0.5 + var symbolParams = rule.successor.match(re); //get params + //console.log(symbolParams); + + var newObject = $.extend(true, {}, shape); + + newObject.rotation[1] += parseFloat(symbolParams[2]); + + return [newObject]; + +} + + +function applyRandomRule(shape, rules, rule_priority) { + + + + for (var i in rules) { + + var rule = rules[i]; + + if ((rule.priority == 1) && (rule_priority == 1)) { + return scaleShape(shape,rule); + } else if ((rule.priority == 2) && (rule_priority == 2)) { + return rotateShape(shape,rule); + }else if ((rule.priority == 3) && (rule_priority == 3)) { + return translateShape(shape,rule); + }else if ((rule.priority == 4) && (rule_priority == 4)) { + return addRoofs(shape,rule); + }else if ((rule.priority == 5) && (rule_priority == 5)) { + return subdivide(shape,rule); + }else if ((rule.priority == 6) && (rule_priority == 6)) { + return addWindows(shape,rule); + } + + + } + + //for every rule check if its predecessor matches the shape, if so, apply the rule + +} + +function clamp(num, min, max) { + return num <= min ? min : num >= max ? max : num; +} + +function getRandomInt(min, max) { + return Math.floor(Math.random() * (max - min + 1)) + min; +} + + +export function generateRules(i) { + + var rules = []; + + var posFactor = Math.max(Math.abs(globalPos[0])/30,Math.abs(globalPos[2])/30); + + var sx = 0.5 * clamp(posFactor, 1.0, 1.3); + var sy = 1 + (Math.random()/2*i) * posFactor;//Math.random()+1; + var sz = Math.random()/2+1 * clamp(posFactor, 1.0, 1.3);//Math.random()+1; + + var scaleRule = new Rule(0, 'X:'+ sx + + ',Y:' + sy + + ',Z:' + sz); + scaleRule.priority = 1; + rules.push(scaleRule); + + //var ry = getRandomInt(0,4)/2.0 * Math.PI; + var ry = 1.0/2.0 * Math.PI * i; + //console.log(ry); + var rotateRule = new Rule(1, 'Y:' + ry); + rotateRule.priority = 2; + rules.push(rotateRule); + + var rx = 0.0; + var rz = 0.0; + var axis = 'Z'; + + if (i % 3 == 0) { + + //rx += Math.random() > 0.5 ? 0.5 : -0.5; + rx += 0.5; + + } else if (i % 3 == 1) { + + rz += Math.random() > 0.5 ? 0.5 : -0.5; + } else if (i % 3 == 2) { + + rx -= 0.5; + axis = 'D'; + //rx += 0.5; + } + +// var rx = (Math.random()-0.5) * 2.0; +// var rz = (Math.random()-0.5) * 2.0; + + var translateRule = new Rule(2, 'X:' + rx + ',Y:0.0,Z:' + rz); + //console.log(translateRule); + translateRule.priority = 3; + rules.push(translateRule); + + var roofRule = new Rule(3, '1'); + roofRule.priority = 4; + rules.push(roofRule); + + + var subdivRule = new Rule(4, axis + ':' + getRandomInt(3, 5)); + subdivRule.priority = 5; + rules.push(subdivRule); + + var windowRule = new Rule(5, '+'); + windowRule.priority = 6; + rules.push(windowRule); + + return rules; +} + +function parseShapeGrammar(shapes, rules, iterations) { + + //var newShapeList = []; + for (var i = 0; i < iterations; i++) { + //console.log(shapes); + for (var j = shapes.length - 1; j >= 0; j--) { + + var shape = shapes[j]; + + var result = shape.symbol.indexOf("roof") > -1; + if (result) + continue; + + rules = generateRules(j); + if (!shape.terminal) { + + var successors = applyRandomRule(shape, rules, i+1); + shapes = shapes.concat(successors); + //console.log(successors); + + var index = shapes.indexOf(shape); + //console.log(index); + if (index > -1) { + shapes.splice(index, 1); + } + + } + + } + + } + + return shapes; +} + + +export default function ShapeGrammar(axiom, grammar, iterations) { + + this.rules = generateRules(); + this.shapes = []; + this.position = [0,0,0]; + this.cityRadius = 0; + + this.doIterations = function(n) { + + //var colors = [0x1B5E9B,0x1B9044,0xBB8319,0xAB342D,0x5D6168]; + var colors2 = [0xCFC096,0x4A741F, 0x1E3B01,0x2A1C0F,0x292B33]; //jg + var colors3 = [0xC7C7C7,0xBFFFCE,0xF6E94A,0x344317,0x478116]; //plf + var colors4 = [0xFF4823,0xFFFF86,0xB1EB8A,0x66B37B,0x029171]; //p + var colors5 = [0x314841,0x8B8B65,0xFFCB7C,0xFF8437,0xF33616]; //os + allRoofsColor = colors2[getRandomInt(0,4)]; + globalPos = this.position; + globalRadius = this.cityRadius; + + var num = Math.random() > 0.5 ? 1 : 3; + var s = this.shapes.slice(0, num); + return parseShapeGrammar(s, this.rules, n); + } +} \ 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