├── .gitignore
├── Gruntfile.js
├── README.md
├── dist
├── assets
│ └── tiles
│ │ ├── arbre.jpg
│ │ ├── basetexture.jpg
│ │ └── tiles.babylon
├── css
│ └── main.css
├── index.html
└── js
│ ├── .baseDir.js
│ ├── Game.js
│ ├── Preloader.js
│ ├── Tile.js
│ ├── Timer.js
│ └── libs
│ └── babylon.max.js
├── package.json
├── sass
├── _loader.scss
└── main.scss
├── screen.jpg
└── ts
├── .baseDir.ts
├── Game.ts
├── Preloader.ts
├── Tile.ts
├── Timer.ts
├── tsconfig.json
└── typings
└── babylon.d.ts
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | node_modules
3 | .tmp
4 | *.map
5 | .tscache
6 | .history
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | module.exports = function (grunt) {
2 |
3 | require('time-grunt')(grunt);
4 |
5 | // load all grunt tasks
6 | require('jit-grunt')(grunt);
7 |
8 | grunt.initConfig({
9 |
10 | // Clean dist folder (all except lib folder)
11 | clean: {
12 | js: ["dist/css","dist/js/.baseDir*","dist/js/*", "!dist/js/libs/**"],
13 | dist: ["dist/js/*", "!dist/js/index.js", "!dist/js/libs/**"]
14 | },
15 |
16 | // Compilation from TypeScript to ES5²
17 | ts: {
18 | dev: {
19 | src : ['ts/**/*.ts', 'ts/typings/**/*'],
20 | outDir: "dist/js",
21 | options:{
22 | module: 'amd',
23 | target: 'es5',
24 | declaration: false,
25 | sourceMap:true,
26 | removeComments:false
27 | }
28 | },
29 | dist: {
30 | src : ['ts/**/*.ts'],
31 | outDir: "dist/js",
32 | options:{
33 | module: 'amd',
34 | target: 'es5',
35 | declaration: false,
36 | sourceMap:false,
37 | removeComments:true
38 | }
39 | }
40 | },
41 | // Watches content related changes
42 | watch : {
43 | js : {
44 | files: ['ts/**/*.ts'],
45 | tasks: ['ts:dev']
46 | },
47 | sass : {
48 | files: ['sass/**/*.scss'],
49 | tasks: ['sass','postcss:debug']
50 | },
51 | html : {
52 | files: ['html/**/*.html'],
53 | tasks: ['bake']
54 | }
55 | },
56 | // Build dist version
57 | uglify : {
58 | dist: {
59 | options: {
60 | compress:true,
61 | beautify: false
62 | },
63 | files: {
64 | 'dist/js/index.js': ['dist/js/**/*.js', '!dist/js/libs/**/*.js']
65 | }
66 | }
67 | },
68 | // Sass compilation. Produce an extended css file in css folder
69 | sass : {
70 | options: {
71 | sourcemap:'none',
72 | style: 'expanded'
73 | },
74 | dist : {
75 | files: {
76 | 'dist/css/main.css': 'sass/main.scss'
77 | }
78 | }
79 | },
80 | // Auto prefixer css
81 | postcss : {
82 | debug : {
83 | options: {
84 | processors: [
85 | require('autoprefixer')({browsers: 'last 2 versions'})
86 | ]
87 | },
88 | src: 'dist/css/main.css'
89 | },
90 | dist: {
91 | options: {
92 | processors: [
93 | require('autoprefixer')({browsers: 'last 2 versions'}),
94 | require('cssnano')()
95 | ]
96 | },
97 | src: 'dist/css/main.css'
98 | }
99 | },
100 | //Server creation
101 | connect: {
102 | server: {
103 | options: {
104 | port: 3000,
105 | base: '.'
106 | }
107 | },
108 | test: {
109 | options: {
110 | port: 3000,
111 | base: '.',
112 | keepalive:true
113 | }
114 | }
115 | },
116 | // Open default browser
117 | open: {
118 | local: {
119 | path: 'http://localhost:3000/dist'
120 | }
121 | }
122 | });
123 |
124 | grunt.registerTask('default', 'Compile and watch source files', [
125 | 'dev',
126 | 'connect:server',
127 | 'open',
128 | 'watch'
129 | ]);
130 |
131 | grunt.registerTask('run', 'Run the webserver and watch files', [
132 | 'connect:server',
133 | 'open',
134 | 'watch'
135 | ]);
136 |
137 | grunt.registerTask('dev', 'build dev version', [
138 | 'clean:js',
139 | 'ts:dev',
140 | 'sass',
141 | 'postcss:debug'
142 | ]);
143 |
144 | grunt.registerTask('test', 'test dist version', [
145 | 'open',
146 | 'connect:test'
147 | ]);
148 |
149 | grunt.registerTask('dist', 'build dist version', [
150 | 'clean:js',
151 | 'ts:dist',
152 | 'sass',
153 | 'postcss:dist',
154 | 'uglify', // compile js files in index.js
155 | 'clean:dist' // remove js file
156 | ]);
157 |
158 | };
159 |
160 |
161 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #Procedural City
2 |
3 | 
4 |
5 | Made with Babylon.js.
6 |
--------------------------------------------------------------------------------
/dist/assets/tiles/arbre.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kimoen/procedural-city/0da8339f69749c4f4d33446e92c2ff4508556dfe/dist/assets/tiles/arbre.jpg
--------------------------------------------------------------------------------
/dist/assets/tiles/basetexture.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kimoen/procedural-city/0da8339f69749c4f4d33446e92c2ff4508556dfe/dist/assets/tiles/basetexture.jpg
--------------------------------------------------------------------------------
/dist/css/main.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | width: 100%;
3 | height: 100%;
4 | margin: 0;
5 | padding: 0;
6 | overflow: hidden;
7 | font-family: sans-serif; }
8 |
9 | #game-canvas {
10 | width: 100%;
11 | height: 100%; }
12 |
13 | .loader {
14 | display: -ms-flexbox;
15 | display: flex;
16 | -ms-flex-pack: center;
17 | justify-content: center;
18 | -ms-flex-align: center;
19 | align-items: center;
20 | background-color: #333;
21 | font-size: 40pt;
22 | font-family: sans-serif;
23 | color: #eee;
24 | letter-spacing: 10px;
25 | position: absolute;
26 | width: 100%;
27 | height: 100%;
28 | top: 0;
29 | left: 0;
30 | z-index: 2; }
31 |
--------------------------------------------------------------------------------
/dist/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Starter
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | LOADING...
24 |
25 |
26 |
27 |
28 |
29 |
30 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/dist/js/.baseDir.js:
--------------------------------------------------------------------------------
1 | // Ignore this file. See https://github.com/grunt-ts/grunt-ts/issues/77
2 | //# sourceMappingURL=.baseDir.js.map
--------------------------------------------------------------------------------
/dist/js/Game.js:
--------------------------------------------------------------------------------
1 | var Game = (function () {
2 | function Game(canvasId) {
3 | var _this = this;
4 | this._allTiles = [];
5 | var canvas = document.getElementById(canvasId);
6 | this.engine = new BABYLON.Engine(canvas, true);
7 | this.engine.enableOfflineSupport = false;
8 | this.scene = null;
9 | window.addEventListener("resize", function () {
10 | _this.engine.resize();
11 | });
12 | this._initScene();
13 | }
14 | Game.prototype._run = function () {
15 | var _this = this;
16 | this.scene.executeWhenReady(function () {
17 | // Remove loader
18 | var loader = document.querySelector("#loader");
19 | loader.style.display = "none";
20 | _this._initGame();
21 | _this.engine.runRenderLoop(function () {
22 | _this.scene.render();
23 | });
24 | });
25 | };
26 | Game.prototype._initScene = function () {
27 | this.scene = new BABYLON.Scene(this.engine);
28 | this.scene.clearColor = BABYLON.Color3.FromInts(0, 163, 136);
29 | var camera = new BABYLON.FreeCamera('', new BABYLON.Vector3(-6, 21, -36), this.scene);
30 | camera.rotation = new BABYLON.Vector3(0.44, 0.73, 0);
31 | camera.attachControl(this.engine.getRenderingCanvas());
32 | var light = new BABYLON.HemisphericLight('', new BABYLON.Vector3(0, 1, 0), this.scene);
33 | light.intensity = 0.4;
34 | var dir = new BABYLON.DirectionalLight('', new BABYLON.Vector3(0.5, -1, 0.25), this.scene);
35 | dir.position.copyFrom(dir.direction.scale(-10));
36 | var shadowGenerator = new BABYLON.ShadowGenerator(2048, dir);
37 | shadowGenerator.useBlurVarianceShadowMap = true;
38 | shadowGenerator.setDarkness(0.05);
39 | shadowGenerator.bias = 0.0001;
40 | shadowGenerator.blurScale = 2;
41 | this.shadows = shadowGenerator;
42 | var loader = new Preloader(this);
43 | loader.callback = this._run.bind(this);
44 | loader.loadAssets();
45 | };
46 | Game.prototype._initGame = function () {
47 | // this.scene.debugLayer.show();
48 | var _this = this;
49 | // Get available tiles
50 | for (var _i = 0, _a = this.scene.meshes; _i < _a.length; _i++) {
51 | var mesh = _a[_i];
52 | if (mesh.name.substr(0, 1) == '0' || mesh.name.substr(0, 1) == '1') {
53 | var tile = new Tile(mesh.name);
54 | this._allTiles.push(tile);
55 | this._allTiles.push(tile.rotatedCopy(1));
56 | this._allTiles.push(tile.rotatedCopy(2));
57 | this._allTiles.push(tile.rotatedCopy(3));
58 | }
59 | }
60 | // Create base building
61 | var baseBuilding = BABYLON.MeshBuilder.CreateBox('baseBuilding', { size: 0.5 }, this.scene);
62 | var buildingMat = new BABYLON.StandardMaterial('baseBuildingMat', this.scene);
63 | buildingMat.diffuseColor = BABYLON.Color3.White();
64 | buildingMat.specularColor = BABYLON.Color3.Black();
65 | baseBuilding.material = buildingMat;
66 | baseBuilding.setEnabled(false);
67 | // Build city
68 | var len = 30;
69 | var city = [];
70 | var timers = [];
71 | var delay = 0;
72 | var _loop_1 = function(j) {
73 | city[j] = [];
74 | var _loop_2 = function(i) {
75 | var timer = new Timer(delay, this_1.scene, { autodestroy: true });
76 | timer.callback = function () {
77 | var sel = _this.findNextTile(i, j, city);
78 | sel.pos.x = i * 3;
79 | sel.pos.z = -j * 3;
80 | sel.draw(_this.createAsset(sel.name, Game.CLONE, sel.toString()), _this);
81 | city[j][i] = sel;
82 | };
83 | timers.push(timer);
84 | delay += 150;
85 | };
86 | for (var i = 0; i < len; i++) {
87 | _loop_2(i);
88 | }
89 | };
90 | var this_1 = this;
91 | for (var j = 0; j < len; j++) {
92 | _loop_1(j);
93 | }
94 | for (var _b = 0, timers_1 = timers; _b < timers_1.length; _b++) {
95 | var t = timers_1[_b];
96 | t.start();
97 | }
98 | };
99 | // i -> line
100 | // j -> line
101 | Game.prototype.findNextTile = function (i, j, city) {
102 | // Get last tile of the city
103 | var previousLine = null, topTile = null, leftTile = null;
104 | if (j > 0) {
105 | previousLine = city[j - 1];
106 | topTile = city[j - 1][i];
107 | }
108 | if (i > 0) {
109 | leftTile = city[j][i - 1];
110 | }
111 | // random sort tiles
112 | this._shuffle(this._allTiles);
113 | return this._findMatchingTile(topTile, Tile.BOT, leftTile, Tile.RIGHT);
114 | };
115 | Game.prototype._findMatchingTile = function (tile, face, tile2, face2) {
116 | for (var i = 0; i < this._allTiles.length; i++) {
117 | if (this._allTiles[i].canBe2(face, tile, face2, tile2)) {
118 | return this._allTiles[i];
119 | }
120 | }
121 | };
122 | Game.prototype.createAsset = function (name, mode, newName) {
123 | if (mode === void 0) { mode = Game.SELF; }
124 | if (newName === void 0) { newName = ''; }
125 | var mesh = this.scene.getMeshByName(name);
126 | var res = null;
127 | switch (mode) {
128 | case Game.SELF:
129 | res = mesh;
130 | mesh.setEnabled(true);
131 | break;
132 | case Game.CLONE:
133 | res = mesh.clone(newName);
134 | break;
135 | case Game.INSTANCE:
136 | res = mesh.createInstance(newName);
137 | break;
138 | }
139 | return res;
140 | };
141 | Game.prototype._shuffle = function (array) {
142 | var currentIndex = array.length, temporaryValue, randomIndex;
143 | // While there remain elements to shuffle...
144 | while (0 !== currentIndex) {
145 | // Pick a remaining element...
146 | randomIndex = Math.floor(Math.random() * currentIndex);
147 | currentIndex -= 1;
148 | // And swap it with the current element.
149 | temporaryValue = array[currentIndex];
150 | array[currentIndex] = array[randomIndex];
151 | array[randomIndex] = temporaryValue;
152 | }
153 | return array;
154 | };
155 | Game.SELF = 0;
156 | Game.CLONE = 1;
157 | Game.INSTANCE = 2;
158 | return Game;
159 | }());
160 | //# sourceMappingURL=Game.js.map
--------------------------------------------------------------------------------
/dist/js/Preloader.js:
--------------------------------------------------------------------------------
1 | var Preloader = (function () {
2 | function Preloader(game) {
3 | this._loader = null;
4 | this._game = game;
5 | this._scene = this._game.scene;
6 | this._loader = new BABYLON.AssetsManager(this._scene);
7 | this._loader.useDefaultLoadingScreen = false;
8 | this._loader.onFinish = this._onFinish.bind(this);
9 | }
10 | Preloader.prototype.loadAssets = function () {
11 | this._addMesh('tiles');
12 | this._loader.load();
13 | };
14 | Preloader.prototype._onFinish = function () {
15 | this.callback();
16 | };
17 | Preloader.prototype._addMesh = function (folder, name) {
18 | if (name) {
19 | var task = this._loader.addMeshTask(name, '', "assets/" + folder + "/", name + ".babylon");
20 | }
21 | else {
22 | var task = this._loader.addMeshTask(folder, '', "assets/" + folder + "/", folder + ".babylon");
23 | }
24 | task.onSuccess = this._addMeshAssetToGame.bind(this);
25 | };
26 | Preloader.prototype._addMeshAssetToGame = function (t) {
27 | console.group();
28 | for (var _i = 0, _a = t.loadedMeshes; _i < _a.length; _i++) {
29 | var m = _a[_i];
30 | m.setEnabled(false);
31 | console.log("%c Loaded : " + m.name, 'background: #333; color: #bada55');
32 | }
33 | console.log("%c Finished : " + t.name, 'background: #333; color: #bada55');
34 | console.groupEnd();
35 | };
36 | return Preloader;
37 | }());
38 | //# sourceMappingURL=Preloader.js.map
--------------------------------------------------------------------------------
/dist/js/Tile.js:
--------------------------------------------------------------------------------
1 | // 0 1 2
2 | // 3 4 5
3 | // 6 7 8
4 | var Tile = (function () {
5 | function Tile(name) {
6 | this._numbers = [];
7 | this.pos = new BABYLON.Vector3(0, 0, 0);
8 | this._angle = 0;
9 | this.name = name;
10 | // Creates numbers
11 | var tmp = name.split('');
12 | for (var a in tmp) {
13 | this._numbers[a] = parseInt(tmp[a], 10);
14 | }
15 | }
16 | Tile.prototype.toString = function () {
17 | return this._numbers.toString();
18 | };
19 | // => top : (0 1 2)
20 | // => right : (2 5 8)
21 | // => bot : (6 7 8)
22 | // => left : (0 3 6)
23 | Tile.prototype.getFace = function (face) {
24 | var res = [];
25 | switch (face) {
26 | case Tile.TOP:
27 | res = [this._numbers[0], this._numbers[1], this._numbers[2]];
28 | break;
29 | case Tile.RIGHT:
30 | res = [this._numbers[2], this._numbers[5], this._numbers[8]];
31 | break;
32 | case Tile.BOT:
33 | res = [this._numbers[6], this._numbers[7], this._numbers[8]];
34 | break;
35 | case Tile.LEFT:
36 | res = [this._numbers[0], this._numbers[3], this._numbers[6]];
37 | break;
38 | }
39 | return res;
40 | };
41 | // 0 : (0,3,6)
42 | // 1 : (1,4,7)
43 | // 2 : (2,5,8)
44 | Tile.prototype._getCol = function (nb) {
45 | var res = [];
46 | switch (nb) {
47 | case 0:
48 | res = [this._numbers[0], this._numbers[3], this._numbers[6]];
49 | break;
50 | case 1:
51 | res = [this._numbers[1], this._numbers[4], this._numbers[7]];
52 | break;
53 | case 2:
54 | res = [this._numbers[2], this._numbers[5], this._numbers[8]];
55 | break;
56 | }
57 | return res;
58 | };
59 | // anticlockwise rotate
60 | Tile.prototype._rotate = function () {
61 | this._numbers = this._getCol(2).concat(this._getCol(1).concat(this._getCol(0)));
62 | this._angle += Math.PI / 2;
63 | };
64 | // anticlockwise rotate 'nb' times
65 | Tile.prototype.rotate = function (nb) {
66 | for (var i = 0; i < nb; i++) {
67 | this._rotate();
68 | }
69 | };
70 | Tile.prototype.rotatedCopy = function (nb) {
71 | var tile = new Tile(this.name);
72 | tile.rotate(nb);
73 | return tile;
74 | };
75 | // This tile can be set on the 'face' of the 'otherTile' ?
76 | Tile.prototype.canBe = function (onFace, otherTile) {
77 | // IF no other tile is given, then this tile can fit anywhere
78 | if (!otherTile) {
79 | return true;
80 | }
81 | var face = this.getFace((onFace + 2) % 4);
82 | var otherface = otherTile.getFace(onFace);
83 | var res = true;
84 | for (var i = 0; i < 3; i++) {
85 | res = res && face[i] == otherface[i];
86 | }
87 | return res;
88 | };
89 | // This tile can be on the 'face' of otherTile1 and on the 'face2" of the otherTile2 ?
90 | Tile.prototype.canBe2 = function (onFace1, otherTile1, onFace2, otherTile2) {
91 | return this.canBe(onFace1, otherTile1) && this.canBe(onFace2, otherTile2);
92 | };
93 | Tile.prototype.draw = function (mesh, game) {
94 | mesh.position.copyFrom(this.pos);
95 | mesh.receiveShadows = true;
96 | var dx = [-1, 0, 1];
97 | var dz = [1, 0, -1];
98 | // get original numbers
99 | var tmp = [];
100 | tmp = this.name.split('');
101 | for (var a in tmp) {
102 | tmp[a] = parseInt(tmp[a], 10);
103 | }
104 | for (var i = 0; i < tmp.length; i++) {
105 | if (tmp[i] == 0 && Math.random() > 0.6) {
106 | var box = game.createAsset('baseBuilding', Game.INSTANCE);
107 | box.scaling.y = this._randomInt(1, 5);
108 | box.scaling.x = this._randomInt(1, 3);
109 | box.scaling.z = this._randomInt(1, 3);
110 | game.shadows.getShadowMap().renderList.push(box);
111 | box.position.x = dx[i % 3];
112 | box.position.y = box.scaling.y / 4;
113 | box.position.z = dz[Math.floor(i / 3)];
114 | box.parent = mesh;
115 | }
116 | else if (tmp[i] == 0 && Math.random() > 0.7) {
117 | var arbre = game.createAsset('arbre', Game.INSTANCE);
118 | game.shadows.getShadowMap().renderList.push(arbre);
119 | arbre.position.x = dx[i % 3];
120 | arbre.position.z = dz[Math.floor(i / 3)];
121 | arbre.parent = mesh;
122 | }
123 | }
124 | mesh.rotation.y = -this._angle;
125 | // Magic optimisation !
126 | mesh.freezeWorldMatrix();
127 | mesh.getDescendants().forEach(function (m) {
128 | m.freezeWorldMatrix();
129 | });
130 | };
131 | Tile.prototype._randomInt = function (min, max) {
132 | if (min === max) {
133 | return (min);
134 | }
135 | var random = Math.random();
136 | return Math.floor(((random * (max - min)) + min));
137 | };
138 | Tile.TOP = 0;
139 | Tile.RIGHT = 1;
140 | Tile.BOT = 2;
141 | Tile.LEFT = 3;
142 | return Tile;
143 | }());
144 | //# sourceMappingURL=Tile.js.map
--------------------------------------------------------------------------------
/dist/js/Timer.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Builds a new timer. A timer can delay an action, and repeat this action X times
3 | * @param time The time in milliseconds
4 | * @param scene The scene
5 | * @param callback The callback function called when the timer is finished
6 | * @param options.autostart If set to true, the timer will autostart. False by default.
7 | *@param options.autodestroy If set to true, the timer will autodestroy at the end of all callback functions. False by default
8 | *@param options.repeat If set, the callback action will be repeated the specified number of times. 1 by default. Set to -1 if repeat infinitely
9 | *@param options.immediate If set, the callback action will be called before waiting 'time' ms. false by default.
10 | */
11 | var Timer = (function () {
12 | function Timer(time, scene, options) {
13 | if (options === void 0) { options = {}; }
14 | this.scene = scene;
15 | this.maxTime = this.currentTime = time;
16 | // True if the timer is finished, false otherwise
17 | this.isOver = false;
18 | // True if the timer is paused, false otherwise
19 | this.paused = false;
20 | // True if the timer has been started, false otherwise
21 | this.started = false;
22 | // Function to be repeated when the timer is finished
23 | this.callback = null;
24 | // Function to be called when the timer is finished (no more repeat counts)
25 | this.onFinish = null;
26 | //If set, the callback action will be repeated the specified number of times
27 | this.repeat = options.repeat || 1;
28 | this.repeatInit = this.repeat;
29 | // Should the timer start directly after its creation ?
30 | this.autostart = options.autostart || false;
31 | // Should the timer destroy itself after completion ?
32 | this.autodestroy = options.autodestroy || false;
33 | // Should the timer call 'callback function' immediately after starting ?
34 | this.immediate = options.immediate || false;
35 | this.registeredFunction = this._checkIfUpdate.bind(this);
36 | scene.registerBeforeRender(this.registeredFunction);
37 | // Start the timer is set to autostart
38 | if (this.autostart) {
39 | this.start();
40 | }
41 | }
42 | /**
43 | * Check if this timer can be updated
44 | */
45 | Timer.prototype._checkIfUpdate = function () {
46 | if (this.started && !this.isOver && !this.paused) {
47 | this._update();
48 | }
49 | };
50 | /**
51 | * Reset the timer. Do not reset its options!
52 | */
53 | Timer.prototype.reset = function () {
54 | this.currentTime = this.maxTime;
55 | this.isOver = false;
56 | this.started = false;
57 | this.paused = false;
58 | };
59 | /**
60 | * Reset the timer and its repeat number
61 | */
62 | Timer.prototype.hardReset = function () {
63 | this.reset();
64 | this.repeat = this.repeatInit;
65 | };
66 | /**
67 | * Start the timer
68 | */
69 | Timer.prototype.start = function () {
70 | this.started = true;
71 | };
72 | /**
73 | * Pause the timer
74 | */
75 | Timer.prototype.pause = function () {
76 | this.paused = true;
77 | };
78 | /**
79 | * Stop the timer, and reset it.
80 | * @param destroy If set to true, the timer is deleted.
81 | */
82 | Timer.prototype.stop = function (destroy) {
83 | this.started = false;
84 | this.reset();
85 | if (this.autodestroy || destroy) {
86 | this.destroy();
87 | }
88 | };
89 | /**
90 | * Destory the timer
91 | * @private
92 | */
93 | Timer.prototype.destroy = function () {
94 | // Unregister update function
95 | this.scene.unregisterBeforeRender(this.registeredFunction);
96 | };
97 | /**
98 | * Unpause the timer
99 | */
100 | Timer.prototype.resume = function () {
101 | this.paused = false;
102 | };
103 | /**
104 | * The update function
105 | * @private
106 | */
107 | Timer.prototype._update = function () {
108 | this.currentTime -= this.scene.getEngine().getDeltaTime();
109 | if (this.currentTime <= 0 || this.immediate) {
110 | // reset immediate
111 | this.immediate = false;
112 | // The delay is finished, run the callback
113 | this.isOver = true;
114 | if (this.repeat != -1) {
115 | this.repeat--;
116 | }
117 | if (this.callback) {
118 | this.callback();
119 | }
120 | if (this.repeat > 0 || this.repeat == -1) {
121 | this.reset();
122 | this.start();
123 | }
124 | else {
125 | // Call the onFinish action
126 | if (this.onFinish) {
127 | this.onFinish();
128 | }
129 | // Autodestroy
130 | if (this.autodestroy) {
131 | this.destroy();
132 | }
133 | }
134 | }
135 | };
136 | return Timer;
137 | }());
138 | //# sourceMappingURL=Timer.js.map
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bjs-starter",
3 | "version": "1.0.0",
4 | "devDependencies": {
5 | "autoprefixer": "^6.1.2",
6 | "cssnano": "^3.4.0",
7 | "grunt": "latest",
8 | "grunt-contrib-clean": "^0.7.0",
9 | "grunt-contrib-connect": "^0.11.2",
10 | "grunt-contrib-watch": "latest",
11 | "grunt-newer": "latest",
12 | "grunt-open": "^0.2.3",
13 | "grunt-postcss": "^0.7.1",
14 | "grunt-sass": "latest",
15 | "grunt-ts": "5.5.1",
16 | "jit-grunt": "latest",
17 | "time-grunt": "latest"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/sass/_loader.scss:
--------------------------------------------------------------------------------
1 | .loader {
2 | display:flex;
3 | justify-content: center;
4 | align-items: center;
5 |
6 | background-color:#333;
7 |
8 | font-size:40pt;
9 | font-family: sans-serif;
10 | color:#eee;
11 | letter-spacing: 10px;
12 |
13 | position:absolute;
14 | width:100%;
15 | height:100%;
16 | top:0;
17 | left:0;
18 | z-index:2;
19 | }
--------------------------------------------------------------------------------
/sass/main.scss:
--------------------------------------------------------------------------------
1 | html, body {
2 | width: 100%;
3 | height:100%;
4 | margin:0;
5 | padding:0;
6 | overflow:hidden;
7 | font-family : sans-serif;
8 | }
9 |
10 | #game-canvas {
11 | width:100%;
12 | height:100%;
13 | }
14 |
15 | @import "loader";
--------------------------------------------------------------------------------
/screen.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Kimoen/procedural-city/0da8339f69749c4f4d33446e92c2ff4508556dfe/screen.jpg
--------------------------------------------------------------------------------
/ts/.baseDir.ts:
--------------------------------------------------------------------------------
1 | // Ignore this file. See https://github.com/grunt-ts/grunt-ts/issues/77
--------------------------------------------------------------------------------
/ts/Game.ts:
--------------------------------------------------------------------------------
1 | class Game {
2 |
3 | private engine: BABYLON.Engine;
4 | public scene: BABYLON.Scene;
5 |
6 | private _allTiles : Array = [];
7 |
8 | public shadows : BABYLON.ShadowGenerator;
9 |
10 | constructor(canvasId: string) {
11 |
12 | let canvas: HTMLCanvasElement = document.getElementById(canvasId);
13 | this.engine = new BABYLON.Engine(canvas, true);
14 | this.engine.enableOfflineSupport = false;
15 |
16 | this.scene = null;
17 |
18 | window.addEventListener("resize", () => {
19 | this.engine.resize();
20 | });
21 |
22 | this._initScene();
23 | }
24 |
25 | private _run() {
26 | this.scene.executeWhenReady(() => {
27 |
28 | // Remove loader
29 | var loader = document.querySelector("#loader");
30 | loader.style.display = "none";
31 |
32 | this._initGame();
33 |
34 | this.engine.runRenderLoop(() => {
35 | this.scene.render();
36 | });
37 | });
38 | }
39 |
40 | private _initScene() {
41 |
42 | this.scene = new BABYLON.Scene(this.engine);
43 | this.scene.clearColor = BABYLON.Color3.FromInts(0,163,136);
44 |
45 | let camera = new BABYLON.FreeCamera('', new BABYLON.Vector3(-6, 21, -36), this.scene);
46 | camera.rotation = new BABYLON.Vector3(0.44,0.73, 0);
47 | camera.attachControl(this.engine.getRenderingCanvas());
48 | let light = new BABYLON.HemisphericLight('', new BABYLON.Vector3(0, 1, 0), this.scene);
49 | light.intensity = 0.4;
50 |
51 | let dir = new BABYLON.DirectionalLight('', new BABYLON.Vector3(0.5,-1,0.25), this.scene);
52 | dir.position.copyFrom(dir.direction.scale(-10));
53 |
54 | var shadowGenerator = new BABYLON.ShadowGenerator(2048, dir);
55 | shadowGenerator.useBlurVarianceShadowMap = true;
56 | shadowGenerator.setDarkness(0.05);
57 | shadowGenerator.bias = 0.0001;
58 | shadowGenerator.blurScale = 2;
59 | this.shadows = shadowGenerator;
60 |
61 | let loader = new Preloader(this);
62 | loader.callback = this._run.bind(this);
63 | loader.loadAssets();
64 |
65 | }
66 |
67 |
68 | private _initGame () {
69 | // this.scene.debugLayer.show();
70 |
71 | // Get available tiles
72 | for (let mesh of this.scene.meshes) {
73 | if (mesh.name.substr(0,1) == '0' || mesh.name.substr(0,1) == '1' ){
74 | let tile = new Tile(mesh.name);
75 | this._allTiles.push(tile);
76 | this._allTiles.push(tile.rotatedCopy(1));
77 | this._allTiles.push(tile.rotatedCopy(2));
78 | this._allTiles.push(tile.rotatedCopy(3));
79 | }
80 | }
81 |
82 | // Create base building
83 | let baseBuilding = BABYLON.MeshBuilder.CreateBox('baseBuilding', {size:0.5}, this.scene);
84 | let buildingMat = new BABYLON.StandardMaterial('baseBuildingMat', this.scene);
85 | buildingMat.diffuseColor = BABYLON.Color3.White();
86 | buildingMat.specularColor = BABYLON.Color3.Black();
87 | baseBuilding.material = buildingMat;
88 | baseBuilding.setEnabled(false);
89 |
90 | // Build city
91 | let len = 30;
92 | let city = [];
93 |
94 | var timers = [];
95 | var delay = 0;
96 |
97 | for (let j=0; j {
102 | let sel = this.findNextTile(i, j, city);
103 |
104 | sel.pos.x = i*3;
105 | sel.pos.z = -j*3;
106 | sel.draw(this.createAsset(sel.name, Game.CLONE, sel.toString()), this);
107 | city[j][i] = sel;
108 | };
109 | timers.push(timer);
110 | delay += 150;
111 | }
112 | }
113 |
114 | for (let t of timers) {
115 | t.start();
116 | }
117 | }
118 |
119 | // i -> line
120 | // j -> line
121 | private findNextTile(i:number, j:number, city:Array>) {
122 | // Get last tile of the city
123 | let previousLine = null, topTile = null, leftTile = null;
124 | if (j>0){
125 | previousLine = city[j-1];
126 | topTile = city[j-1][i];
127 | }
128 | if (i>0) {
129 | leftTile = city[j][i-1];
130 | }
131 |
132 | // random sort tiles
133 | this._shuffle(this._allTiles);
134 | return this._findMatchingTile(topTile, Tile.BOT, leftTile, Tile.RIGHT);
135 | }
136 |
137 | private _findMatchingTile(tile:Tile, face:number, tile2:Tile, face2:number) {
138 | for (var i = 0; i < this._allTiles.length; i++) {
139 | if (this._allTiles[i].canBe2(face, tile, face2, tile2)) {
140 | return this._allTiles[i];
141 | }
142 | }
143 | }
144 |
145 | public static SELF : number = 0;
146 | public static CLONE : number = 1;
147 | public static INSTANCE : number = 2;
148 |
149 | public createAsset(name:string, mode:number=Game.SELF, newName:string='') : BABYLON.Mesh {
150 | let mesh : BABYLON.Mesh = this.scene.getMeshByName(name);
151 | let res = null;
152 | switch (mode) {
153 | case Game.SELF:
154 | res = mesh;
155 | mesh.setEnabled(true);
156 | break;
157 | case Game.CLONE:
158 | res = mesh.clone(newName);
159 | break;
160 |
161 | case Game.INSTANCE:
162 | res = mesh.createInstance(newName);
163 | break;
164 | }
165 | return res;
166 | }
167 |
168 |
169 | private _shuffle(array) {
170 | var currentIndex = array.length, temporaryValue, randomIndex;
171 |
172 | // While there remain elements to shuffle...
173 | while (0 !== currentIndex) {
174 | // Pick a remaining element...
175 | randomIndex = Math.floor(Math.random() * currentIndex);
176 | currentIndex -= 1;
177 |
178 | // And swap it with the current element.
179 | temporaryValue = array[currentIndex];
180 | array[currentIndex] = array[randomIndex];
181 | array[randomIndex] = temporaryValue;
182 | }
183 |
184 | return array;
185 | }
186 |
187 |
188 | }
189 |
--------------------------------------------------------------------------------
/ts/Preloader.ts:
--------------------------------------------------------------------------------
1 | class Preloader {
2 |
3 | private _game : Game;
4 | private _scene : BABYLON.Scene;
5 | private _loader : BABYLON.AssetsManager = null;
6 |
7 | // Function called when the loader is over
8 | public callback : () => void;
9 |
10 | constructor(game:Game) {
11 | this._game = game;
12 | this._scene = this._game.scene;
13 |
14 | this._loader = new BABYLON.AssetsManager(this._scene);
15 | this._loader.useDefaultLoadingScreen = false;
16 | this._loader.onFinish = this._onFinish.bind(this);
17 |
18 | }
19 |
20 | public loadAssets() {
21 |
22 | this._addMesh('tiles');
23 | this._loader.load();
24 | }
25 |
26 | private _onFinish() {
27 | this.callback();
28 | }
29 |
30 | private _addMesh(folder :string, name?:string ) {
31 | if (name) {
32 | var task = this._loader.addMeshTask(name, '', `assets/${folder}/`, `${name}.babylon`);
33 | } else {
34 | var task = this._loader.addMeshTask(folder, '', `assets/${folder}/`, `${folder}.babylon`);
35 | }
36 | task.onSuccess = this._addMeshAssetToGame.bind(this);
37 | }
38 |
39 | private _addMeshAssetToGame(t: BABYLON.MeshAssetTask) {
40 |
41 | console.group();
42 | for (let m of t.loadedMeshes) {
43 | m.setEnabled(false);
44 | console.log(`%c Loaded : ${m.name}`, 'background: #333; color: #bada55');
45 | }
46 | console.log(`%c Finished : ${t.name}`, 'background: #333; color: #bada55');
47 |
48 | console.groupEnd();
49 | }
50 | }
--------------------------------------------------------------------------------
/ts/Tile.ts:
--------------------------------------------------------------------------------
1 | // 0 1 2
2 | // 3 4 5
3 | // 6 7 8
4 | class Tile {
5 |
6 | private _numbers : Array = [];
7 | public pos : BABYLON.Vector3 = new BABYLON.Vector3(0,0,0);
8 | private _angle : number = 0;
9 | public name : string;
10 |
11 | public static TOP:number = 0;
12 | public static RIGHT:number = 1;
13 | public static BOT:number = 2;
14 | public static LEFT:number = 3;
15 |
16 | constructor(name) {
17 |
18 | this.name = name;
19 |
20 | // Creates numbers
21 | let tmp = name.split('');
22 | for (let a in tmp) {
23 | this._numbers[a] = parseInt(tmp[a], 10);
24 | }
25 | }
26 |
27 | public toString() : string {
28 | return this._numbers.toString();
29 | }
30 |
31 | // => top : (0 1 2)
32 | // => right : (2 5 8)
33 | // => bot : (6 7 8)
34 | // => left : (0 3 6)
35 | public getFace(face:number) : Array {
36 | let res = [];
37 | switch (face) {
38 | case Tile.TOP:
39 | res = [this._numbers[0], this._numbers[1], this._numbers[2]]
40 | break;
41 | case Tile.RIGHT:
42 | res = [this._numbers[2], this._numbers[5], this._numbers[8]];
43 | break;
44 | case Tile.BOT:
45 | res = [this._numbers[6], this._numbers[7], this._numbers[8]];
46 | break;
47 | case Tile.LEFT:
48 | res = [this._numbers[0], this._numbers[3], this._numbers[6]];
49 | break;
50 | }
51 | return res;
52 | }
53 |
54 |
55 | // 0 : (0,3,6)
56 | // 1 : (1,4,7)
57 | // 2 : (2,5,8)
58 | private _getCol(nb) : Array {
59 | let res = [];
60 | switch (nb) {
61 | case 0:
62 | res = [this._numbers[0], this._numbers[3], this._numbers[6]];
63 | break;
64 | case 1:
65 | res = [this._numbers[1], this._numbers[4], this._numbers[7]];
66 | break;
67 | case 2:
68 | res = [this._numbers[2], this._numbers[5], this._numbers[8]];
69 | break;
70 | }
71 | return res;
72 | }
73 | // anticlockwise rotate
74 | private _rotate() {
75 | this._numbers = this._getCol(2).concat(this._getCol(1).concat(this._getCol(0)));
76 | this._angle += Math.PI/2;
77 | }
78 |
79 | // anticlockwise rotate 'nb' times
80 | public rotate(nb:number) {
81 | for (let i=0; i 0.6) {
128 | let box = game.createAsset('baseBuilding', Game.INSTANCE);
129 | box.scaling.y = this._randomInt(1,5);
130 | box.scaling.x = this._randomInt(1,3);
131 | box.scaling.z = this._randomInt(1,3);
132 | game.shadows.getShadowMap().renderList.push(box);
133 |
134 | box.position.x = dx[i % 3];
135 | box.position.y = box.scaling.y/4;
136 | box.position.z = dz[Math.floor(i / 3)];
137 | box.parent = mesh;
138 | } else if (tmp[i]==0 && Math.random() > 0.7) {
139 |
140 | let arbre = game.createAsset('arbre', Game.INSTANCE);
141 | game.shadows.getShadowMap().renderList.push(arbre);
142 | arbre.position.x = dx[i % 3];
143 | arbre.position.z = dz[Math.floor(i / 3)];
144 | arbre.parent = mesh;
145 | }
146 | }
147 | mesh.rotation.y = -this._angle;
148 |
149 | // Magic optimisation !
150 | mesh.freezeWorldMatrix();
151 | mesh.getDescendants().forEach((m) => {
152 | ( m).freezeWorldMatrix();
153 | });
154 | }
155 |
156 | private _randomInt(min, max) {
157 | if (min === max) {
158 | return (min);
159 | }
160 | var random = Math.random();
161 | return Math.floor(((random * (max - min)) + min));
162 | }
163 | }
--------------------------------------------------------------------------------
/ts/Timer.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Builds a new timer. A timer can delay an action, and repeat this action X times
3 | * @param time The time in milliseconds
4 | * @param scene The scene
5 | * @param callback The callback function called when the timer is finished
6 | * @param options.autostart If set to true, the timer will autostart. False by default.
7 | *@param options.autodestroy If set to true, the timer will autodestroy at the end of all callback functions. False by default
8 | *@param options.repeat If set, the callback action will be repeated the specified number of times. 1 by default. Set to -1 if repeat infinitely
9 | *@param options.immediate If set, the callback action will be called before waiting 'time' ms. false by default.
10 | */
11 | class Timer {
12 |
13 | private scene : BABYLON.Scene;
14 | private registeredFunction : () => void;
15 |
16 | /** The timer max time (in ms) */
17 | public maxTime : number;
18 | /** The timer current time */
19 | public currentTime : number;
20 | /** True if the timer is finished, false otherwise */
21 | public isOver : boolean;
22 | /** True if the timer is paused, false otherwise */
23 | public paused : boolean;
24 | /** True if the timer has been started, false otherwise */
25 | public started : boolean;
26 | /** Function to be repeated when the timer is finished */
27 | public callback: () => void;
28 | /** Function to be called when the timer is finished (no more repeat counts) */
29 | public onFinish: () => void;
30 | /**If set, the callback action will be repeated the specified number of times */
31 | public repeat : number;
32 | private repeatInit : number;
33 | /** Should the timer start directly after its creation ? */
34 | public autostart : boolean;
35 | /** Should the timer call 'callback function' immediately after starting ? */
36 | public immediate : boolean;
37 | /** Should the timer destroy itself after completion ? */
38 | public autodestroy : boolean;
39 |
40 |
41 | constructor(time: number, scene:BABYLON.Scene, options:{repeat?:number, autostart?:boolean, autodestroy?:boolean, immediate?:boolean} = {}) {
42 |
43 | this.scene = scene;
44 |
45 | this.maxTime = this.currentTime = time;
46 |
47 | // True if the timer is finished, false otherwise
48 | this.isOver = false;
49 |
50 | // True if the timer is paused, false otherwise
51 | this.paused = false;
52 |
53 | // True if the timer has been started, false otherwise
54 | this.started = false;
55 |
56 | // Function to be repeated when the timer is finished
57 | this.callback = null;
58 |
59 | // Function to be called when the timer is finished (no more repeat counts)
60 | this.onFinish = null;
61 |
62 | //If set, the callback action will be repeated the specified number of times
63 | this.repeat = options.repeat || 1;
64 | this.repeatInit = this.repeat;
65 |
66 | // Should the timer start directly after its creation ?
67 | this.autostart = options.autostart || false;
68 |
69 | // Should the timer destroy itself after completion ?
70 | this.autodestroy = options.autodestroy || false;
71 |
72 | // Should the timer call 'callback function' immediately after starting ?
73 | this.immediate = options.immediate || false;
74 |
75 | this.registeredFunction = this._checkIfUpdate.bind(this);
76 | scene.registerBeforeRender(this.registeredFunction);
77 |
78 | // Start the timer is set to autostart
79 | if (this.autostart) {
80 | this.start();
81 | }
82 | }
83 |
84 | /**
85 | * Check if this timer can be updated
86 | */
87 | private _checkIfUpdate () : void {
88 | if (this.started && !this.isOver && !this.paused) {
89 | this._update();
90 | }
91 | }
92 |
93 | /**
94 | * Reset the timer. Do not reset its options!
95 | */
96 | public reset() {
97 | this.currentTime = this.maxTime;
98 | this.isOver = false;
99 | this.started = false;
100 | this.paused = false;
101 | }
102 |
103 | /**
104 | * Reset the timer and its repeat number
105 | */
106 | public hardReset() {
107 | this.reset();
108 | this.repeat = this.repeatInit;
109 | }
110 |
111 | /**
112 | * Start the timer
113 | */
114 | public start() {
115 | this.started = true;
116 | }
117 |
118 | /**
119 | * Pause the timer
120 | */
121 | public pause() {
122 | this.paused = true;
123 | }
124 |
125 | /**
126 | * Stop the timer, and reset it.
127 | * @param destroy If set to true, the timer is deleted.
128 | */
129 | public stop(destroy : boolean) {
130 | this.started = false;
131 | this.reset();
132 | if (this.autodestroy || destroy) {
133 | this.destroy();
134 | }
135 | }
136 |
137 | /**
138 | * Destory the timer
139 | * @private
140 | */
141 | private destroy() {
142 | // Unregister update function
143 | this.scene.unregisterBeforeRender(this.registeredFunction);
144 | }
145 |
146 | /**
147 | * Unpause the timer
148 | */
149 | public resume() {
150 | this.paused = false;
151 | }
152 |
153 | /**
154 | * The update function
155 | * @private
156 | */
157 | private _update() {
158 |
159 | this.currentTime -= this.scene.getEngine().getDeltaTime();
160 |
161 | if (this.currentTime <= 0 || this.immediate) {
162 | // reset immediate
163 | this.immediate = false;
164 |
165 | // The delay is finished, run the callback
166 | this.isOver = true;
167 | if (this.repeat != -1) {
168 | this.repeat--;
169 | }
170 | if (this.callback) {
171 | this.callback();
172 | }
173 |
174 | if (this.repeat > 0 || this.repeat == -1) {
175 | this.reset();
176 | this.start();
177 | } else {
178 | // Call the onFinish action
179 | if (this.onFinish) {
180 | this.onFinish();
181 | }
182 | // Autodestroy
183 | if (this.autodestroy) {
184 | this.destroy();
185 | }
186 | }
187 | }
188 | }
189 | }
--------------------------------------------------------------------------------
/ts/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5"
4 | },
5 | "files": [
6 | "typings/babylon.d.ts",
7 | "Game.ts",
8 | "Tile.ts",
9 | "Preloader.ts",
10 | "Timer.ts"
11 | ]
12 | }
--------------------------------------------------------------------------------