Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Initial commit.

Added Dark Corners engine. No levels, assets, website or editor.
  • Loading branch information...
commit c0b9e8fc01c3aaaa84e7b7bd67b86e83e2e5c910 0 parents
@tapio authored
Showing with 89,310 additions and 0 deletions.
  1. +22 −0 LICENSE
  2. +39 −0 Makefile
  3. +50 −0 README.md
  4. +2,073 −0 build/game.js
  5. +82 −0 build/game.min.js
  6. +40,400 −0 build/libs.js
  7. +611 −0 build/libs.min.js
  8. +76 −0 css/style.css
  9. +26 −0 game.html
  10. +48 −0 game_dev.html
  11. +122 −0 js/block.js
  12. +49 −0 js/cache.js
  13. +67 −0 js/config.js
  14. +200 −0 js/controls.js
  15. +451 −0 js/dungeon.js
  16. +57 −0 js/lights.js
  17. +251 −0 js/main.js
  18. +65 −0 js/map.js
  19. +201 −0 js/mapgen.js
  20. +132 −0 js/particles.js
  21. +83 −0 js/plane.js
  22. +143 −0 js/ui.js
  23. +174 −0 js/utils.js
  24. +60 −0 libs/Detector.js
  25. +2,413 −0 libs/ammo.js
  26. +6 −0 libs/bootstrap.min.js
  27. +94 −0 libs/dat.gui.min.js
  28. +48 −0 libs/fireworks-bundle.min.js
  29. +2 −0  libs/jquery-1.8.1.min.js
  30. +1,377 −0 libs/physi.js
  31. +1,054 −0 libs/physijs_worker.js
  32. +124 −0 libs/postprocessing/EffectComposer.js
  33. +457 −0 libs/postprocessing/Passes.js
  34. +1,916 −0 libs/postprocessing/ShaderExtras.js
  35. +6 −0 libs/stats.min.js
  36. +36,318 −0 libs/three.min.js
  37. +13 −0 package.json
22 LICENSE
@@ -0,0 +1,22 @@
+Parts of this project may have different license - see readme.md
+
+Copyright (c) 2012 Tapio Vierros
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
39 Makefile
@@ -0,0 +1,39 @@
+.SUFFIXES: .md .html
+
+all: build
+
+server:
+ python -m SimpleHTTPServer
+
+chrome:
+ google-chrome --allow-file-access-from-files
+
+hint:
+ jshint js/*.js assets/*.js editor/*.js
+
+help: README.html CONTRIBUTING.html ART-TODO.html CODE-TODO.html
+
+.md.html:
+ markdown $< > build/$@
+
+deploy:
+ git checkout gh-pages
+ git merge master
+ git push origin gh-pages
+ git checkout master
+
+build: concat minify
+
+concat:
+ cat `grep "<script " game_dev.html | cut -d'"' -f2 | egrep "^libs/"` > build/libs.js
+ cat `grep "<script " game_dev.html | cut -d'"' -f2 | egrep "(^js/)|(^assets/)"` > build/game.js
+
+minify: concat
+ cat build/libs.js | uglifyjs -nc --max-line-len 1024 > build/libs.min.js
+ cat build/game.js | uglifyjs -nc --max-line-len 512 > build/game.min.js
+
+clean:
+ rm build/*.html
+
+.PHONY: all server chrome hint help deploy build clean
+
50 README.md
@@ -0,0 +1,50 @@
+Dark Corners
+============
+
+This is 3d first person game prototype with a level editor.
+The main design guideline is sacrificing artistic liberties for quickness of content creation.
+See http://tapio.github.com/darkcorners/ for more information and demo.
+
+
+System Requirements
+-------------------
+
+* Latest version of Chrome or Firefox
+ * Chrome offers noticeable better performance
+ * IE does not support WebGL and Opera has issues
+* Good dedicated GPU and drivers (both must not be blacklisted for WebGL by the browser vendors)
+* Decent CPU so that JavaScript runs well
+
+
+Acknowledgements
+----------------
+
+This is based on several JS libraries, most prominently:
+
+* Three.js - http://mrdoob.github.com/three.js/
+ * Graphics engine
+* Physijs - http://chandlerprall.github.com/Physijs/
+ * Physics engine
+* Fireworks.js - http://jeromeetienne.github.com/fireworks.js/
+ * Particle engine
+* dat.GUI - http://code.google.com/p/dat-gui/
+ * Settings and editor GUI
+
+With art assets, Blendswap (http://www.blendswap.com/) and especially
+Open Game Art (http://opengameart.org/) have been invaluable.
+
+
+Copyright
+---------
+
+Unless otherwise stated, code in `js/` and all `.html` files are mine and
+licensed under MIT (see LICENSE file).
+
+Third-party code is in `libs/` and belongs to their respective owners.
+Their licenses are mentioned in the files.
+
+The licenses of models and textures in `assets/` can be
+viewed from their respective `readme.txt` files.
+However, all of them are available under at least one the following licenses:
+CC0/PD, CC-BY-3.0, CC-BY-SA-3.0
+
2,073 build/game.js
@@ -0,0 +1,2073 @@
+"use strict";
+var UNIT = 1;
+
+if (!Detector.webgl) {
+ Detector.addGetWebGLMessage();
+ document.getElementById('container').innerHTML = "";
+}
+
+Physijs.scripts.worker = 'libs/physijs_worker.js';
+Physijs.scripts.ammo = '../libs/ammo.js';
+
+var hashParams = (function() {
+ var params = {}, param;
+ var q = window.location.hash.replace('#', '').split('&');
+ for (var i = 0; i < q.length; ++i) {
+ param = q[i].split('=');
+ params[param[0]] = param[1];
+ }
+ return params;
+})();
+
+var CONFIG = {
+ showStats: true,
+ quarterMode: false,
+ postprocessing: true,
+ particles: true,
+ maxLights: 4,
+ maxShadows: 2,
+ antialias: true,
+ anisotropy: 0, // 0 = auto
+ shadows: true,
+ softShadows: true,
+ physicalShading: true,
+ normalMapping: true,
+ specularMapping: true,
+ linearTextureFilter: true,
+ bloom: true,
+ SSAO: true,
+ FXAA: false
+};
+
+var updateConfig = function() {
+ lightManager.maxLights = CONFIG.maxLights;
+ lightManager.maxShadows = CONFIG.maxShadows;
+ renderer.shadowMapEnabled = CONFIG.shadows;
+ renderer.shadowMapSoft = CONFIG.softShadows;
+ renderer.physicallyBasedShading = CONFIG.physicalShading;
+ passes.bloom.enabled = CONFIG.bloom;
+ passes.ssao.enabled = CONFIG.SSAO;
+ passes.fxaa.enabled = CONFIG.FXAA;
+ var statDisplay = CONFIG.showStats ? "block" : "none";
+ if (renderStats) renderStats.domElement.style.display = statDisplay;
+ if (physicsStats) physicsStats.domElement.style.display = statDisplay;
+ if (rendererInfo) rendererInfo.style.display = statDisplay;
+ localStorage.setItem("CONFIG", JSON.stringify(CONFIG));
+};
+
+(function() {
+ var loadedConfig = localStorage.getItem("CONFIG");
+ if (loadedConfig) {
+ loadedConfig = JSON.parse(loadedConfig);
+ for (var prop in loadedConfig) {
+ if (loadedConfig.hasOwnProperty(prop) && typeof loadedConfig[prop] !== "function")
+ CONFIG[prop] = loadedConfig[prop];
+ }
+ }
+})();
+"use strict";
+function Cache() {
+ this.models = {};
+ this.geometries = {};
+ this.materials = {};
+ var self = this;
+ var loader = new THREE.JSONLoader(true);
+ loader.statusDomElement.style.left = "0px";
+ loader.statusDomElement.style.fontSize = "1.8em";
+ loader.statusDomElement.style.width = "auto";
+ loader.statusDomElement.style.color = "#c00";
+ document.body.appendChild(loader.statusDomElement);
+ var modelsPending = 0;
+
+ this.loadModel = function(path, callback) {
+ var m = this.models[path];
+ if (!m) { // First time request for this model
+ this.models[path] = [ callback ];
+ loader.statusDomElement.style.display = "block";
+ modelsPending++;
+ loader.load(path, function(geometry) {
+ var mm = self.models[path];
+ for (var i = 0; i < mm.length; ++i)
+ mm[i](geometry);
+ self.models[path] = geometry;
+ modelsPending--;
+ if (modelsPending == 0)
+ loader.statusDomElement.style.display = "none";
+ });
+ } else if (m instanceof Array) { // Pending
+ m.push(callback);
+ } else // Already loaded
+ callback(m);
+ };
+
+ this.getGeometry = function(name, generator) {
+ var g = this.geometries[name];
+ if (g) return g;
+ this.geometries[name] = g = generator();
+ return g;
+ };
+
+ this.getMaterial = function(name) {
+ var t = this.materials[name];
+ if (t) return t;
+ this.materials[name] = t = createMaterial(name);
+ return t;
+ };
+}
+"use strict";
+/**
+ * original author mrdoob / http://mrdoob.com/ (THREE.PlaneGeometry)
+ * based on http://papervision3d.googlecode.com/svn/trunk/as3/trunk/src/org/papervision3d/objects/primitives/Plane.as
+ */
+
+function PlaneGeometry(width, height, segmentsX, segmentsY, dir, uRepeat, vRepeat, randDisplace) {
+
+ THREE.Geometry.call(this);
+
+ var ix, iz,
+ width_half = width / 2,
+ height_half = height / 2,
+ gridX = segmentsX || 1,
+ gridZ = segmentsY || 1,
+ gridX1 = gridX + 1,
+ gridZ1 = gridZ + 1,
+ segment_width = width / gridX,
+ segment_height = height / gridZ,
+ normal = new THREE.Vector3(),
+ xmul = new THREE.Vector3(),
+ ymul = new THREE.Vector3();
+
+ uRepeat = uRepeat || 1;
+ vRepeat = vRepeat || 1;
+ randDisplace = randDisplace || 0;
+
+ dir = dir || "pz";
+ switch (dir) {
+ case "nx": normal.x = -1; xmul.z = -1; ymul.y = 1; break;
+ case "px": normal.x = 1; xmul.z = -1; ymul.y = -1; break;
+ case "ny": normal.y = -1; xmul.x = -1; ymul.z = 1; break;
+ case "py": normal.y = 1; xmul.x = -1; ymul.z = -1; break;
+ case "nz": normal.z = -1; xmul.x = 1; ymul.y = 1; break;
+ case "pz": normal.z = 1; xmul.x = 1; ymul.y = -1; break;
+ default: console.error("Unknown plane direction " + dir);
+ }
+ var displace = new THREE.Vector3();
+
+ for (iz = 0; iz < gridZ1; iz++) {
+ for (ix = 0; ix < gridX1; ix++) {
+ var x = ix * segment_width - width_half;
+ var y = iz * segment_height - height_half;
+ var vert = new THREE.Vector3( x * xmul.x, x * xmul.y, x * xmul.z );
+ vert.set( vert.x + y * ymul.x, vert.y + y * ymul.y, vert.z + y * ymul.z );
+ // Random displacement?
+ if (randDisplace && ix > 0 && ix < gridX1 - 1) {
+ displace.copy(normal);
+ displace.multiplyScalar(-randDisplace + Math.random() * randDisplace * 2);
+ vert.addSelf(displace);
+ }
+ this.vertices.push(vert);
+ }
+ }
+
+ for ( iz = 0; iz < gridZ; iz ++ ) {
+ for ( ix = 0; ix < gridX; ix ++ ) {
+ var a = ix + gridX1 * iz;
+ var b = ix + gridX1 * ( iz + 1 );
+ var c = ( ix + 1 ) + gridX1 * ( iz + 1 );
+ var d = ( ix + 1 ) + gridX1 * iz;
+
+ var face = new THREE.Face4( a, b, c, d );
+ face.normal.copy( normal );
+ face.vertexNormals.push( normal.clone(), normal.clone(), normal.clone(), normal.clone() );
+ face.materialIndex = 0;
+
+ this.faces.push( face );
+ this.faceVertexUvs[ 0 ].push([
+ new THREE.UV( ix / gridX * uRepeat, (1 - iz / gridZ) * vRepeat ),
+ new THREE.UV( ix / gridX * uRepeat, (1 - (iz + 1) / gridZ) * vRepeat ),
+ new THREE.UV( (ix + 1) / gridX * uRepeat, (1 - (iz+ 1) / gridZ) * vRepeat ),
+ new THREE.UV( (ix + 1) / gridX * uRepeat, (1 - iz / gridZ) * vRepeat )
+ ]);
+ }
+ }
+
+ this.computeCentroids();
+ if (randDisplace)
+ this.computeVertexNormals();
+};
+
+PlaneGeometry.prototype = Object.create( THREE.Geometry.prototype );
+"use strict";
+/**
+ * original author mrdoob / http://mrdoob.com/ (THREE.CubeGeometry)
+ * based on http://papervision3d.googlecode.com/svn/trunk/as3/trunk/src/org/papervision3d/objects/primitives/Cube.as
+ */
+
+function BlockGeometry(width, height, depth, segmentsWidth, segmentsHeight, segmentsDepth, materials, sides, uRepeat, vRepeat, randDisplace) {
+ THREE.Geometry.call(this);
+
+ var scope = this,
+ width_half = width / 2,
+ height_half = height / 2,
+ depth_half = depth / 2;
+
+ uRepeat = uRepeat || 1;
+ vRepeat = vRepeat || 1;
+ randDisplace = randDisplace || 0;
+
+ var mpx, mpy, mpz, mnx, mny, mnz;
+
+ if (materials !== undefined) {
+ if (materials instanceof Array) {
+ this.materials = materials;
+ } else {
+ this.materials = [];
+ for (var i = 0; i < 6; i ++) {
+ this.materials.push(materials);
+ }
+ }
+ mpx = 0; mnx = 1; mpy = 2; mny = 3; mpz = 4; mnz = 5;
+ } else {
+ this.materials = [];
+ }
+
+ this.sides = { px: true, nx: true, py: true, ny: true, pz: true, nz: true };
+
+ if (sides) {
+ for (var s in sides) {
+ if (this.sides[s] !== undefined) {
+ this.sides[s] = sides[s];
+ }
+ }
+ }
+
+ this.sides.px && buildPlane('z', 'y', - 1, - 1, depth, height, width_half, mpx); // px
+ this.sides.nx && buildPlane('z', 'y', 1, - 1, depth, height, - width_half, mnx); // nx
+ this.sides.py && buildPlane('x', 'z', - 1, 1, width, depth, height_half, mpy, true); // py
+ this.sides.ny && buildPlane('x', 'z', - 1, - 1, width, depth, - height_half, mny, true); // ny
+ this.sides.pz && buildPlane('x', 'y', 1, - 1, width, height, depth_half, mpz); // pz
+ this.sides.nz && buildPlane('x', 'y', - 1, - 1, width, height, - depth_half, mnz); // nz
+
+ function buildPlane(u, v, udir, vdir, width, height, depth, material, flipNormal) {
+ var w, ix, iy,
+ gridX = segmentsWidth || 1,
+ gridY = segmentsHeight || 1,
+ width_half = width / 2,
+ height_half = height / 2,
+ offset = scope.vertices.length;
+
+ if ((u === 'x' && v === 'y') || (u === 'y' && v === 'x')) {
+ w = 'z';
+ } else if ((u === 'x' && v === 'z') || (u === 'z' && v === 'x')) {
+ w = 'y';
+ gridY = segmentsDepth || 1;
+ } else if ((u === 'z' && v === 'y') || (u === 'y' && v === 'z')) {
+ w = 'x';
+ gridX = segmentsDepth || 1;
+ }
+
+ var gridX1 = gridX + 1,
+ gridY1 = gridY + 1,
+ segment_width = width / gridX,
+ segment_height = height / gridY,
+ normal = new THREE.Vector3();
+
+ normal[w] = depth > 0 ? 1 : - 1;
+ if (flipNormal) normal[w] *= -1;
+
+ for (iy = 0; iy < gridY1; iy ++) {
+ for (ix = 0; ix < gridX1; ix ++) {
+ var vector = new THREE.Vector3();
+ vector[u] = (ix * segment_width - width_half) * udir;
+ vector[v] = (iy * segment_height - height_half) * vdir;
+ vector[w] = depth;
+ // Random displacement?
+ if (randDisplace && ix > 0 && ix < gridX1 - 1)
+ vector[w] += -randDisplace + Math.random() * randDisplace * 2;
+
+ scope.vertices.push(vector);
+ }
+ }
+
+ for (iy = 0; iy < gridY; iy++) {
+ for (ix = 0; ix < gridX; ix++) {
+ var a = ix + gridX1 * iy;
+ var b = ix + gridX1 * (iy + 1);
+ var c = (ix + 1) + gridX1 * (iy + 1);
+ var d = (ix + 1) + gridX1 * iy;
+
+ var face = new THREE.Face4(a + offset, b + offset, c + offset, d + offset);
+ face.normal.copy(normal);
+ face.vertexNormals.push(normal.clone(), normal.clone(), normal.clone(), normal.clone());
+ face.materialIndex = material;
+
+ scope.faces.push(face);
+ scope.faceVertexUvs[0].push([
+ new THREE.UV(ix / gridX * uRepeat, (1 - iy / gridY) * vRepeat),
+ new THREE.UV(ix / gridX * uRepeat, (1 - (iy + 1) / gridY) * vRepeat),
+ new THREE.UV((ix + 1) / gridX * uRepeat, (1- (iy + 1) / gridY) * vRepeat),
+ new THREE.UV((ix + 1) / gridX * uRepeat, (1 - iy / gridY) * vRepeat)
+ ]);
+ }
+ }
+ }
+
+ this.computeCentroids();
+ this.mergeVertices();
+ if (randDisplace)
+ this.computeVertexNormals();
+};
+
+BlockGeometry.prototype = Object.create(THREE.Geometry.prototype);
+"use strict";
+function LightManager(params) {
+ params = params || {};
+ this.maxLights = params.maxLights || 4;
+ this.maxShadows = params.maxShadows || 2;
+ this.lights = [];
+ this.shadows = [];
+
+ this.addLight = function(light) {
+ this.lights.push(light);
+ };
+
+ this.addShadow = function(light) {
+ this.shadows.push(light);
+ };
+
+ var updateSkip = 0;
+ var v1 = new THREE.Vector2();
+ var v2 = new THREE.Vector2();
+
+ this.update = function(observer) {
+ if (++updateSkip <= 20) return; // Perhaps should also/instead check moved distance?
+ else updateSkip = 0;
+
+ function angleDist(a, b) {
+ v1.set(a.x - observer.position.x, a.z - observer.position.z).normalize();
+ v2.set(b.x - observer.position.x, b.z - observer.position.z).normalize();
+ return Math.acos(v1.dot(v2));
+ }
+ function distSq(a, b) {
+ var dx = b.x - a.x, dz = b.z - a.z;
+ return dx * dx + dz * dz;
+ }
+ function sortByDist(a, b) {
+ return distSq(a.position, observer.position) - distSq(b.position, observer.position);
+ }
+ var i, used = 0;
+
+ this.lights.sort(sortByDist);
+ for (i = 0; i < this.lights.length; ++i) {
+ if (used < this.maxLights && (
+ angleDist(this.lights[i].position, controls.target) < Math.PI * 0.4 ||
+ distSq(this.lights[i].position, observer.position) < 1.5 * this.lights[i].distance * this.lights[i].distance))
+ {
+ this.lights[i].visible = true;
+ ++used;
+ } else this.lights[i].visible = false;
+ }
+
+ this.shadows.sort(sortByDist);
+ for (i = 0; i < this.shadows.length; ++i) {
+ if (i < this.maxShadows) this.shadows[i].castShadow = true;
+ else this.shadows[i].castShadow = false;
+ }
+
+ };
+}
+"use strict";
+var _textures = [];
+
+function loadTexture(path, opts) {
+ opts = opts || {};
+ var image = new Image();
+ image.onload = function() { texture.needsUpdate = true; };
+ image.src = path;
+ var texture = new THREE.Texture(
+ image,
+ new THREE.UVMapping(),
+ THREE.RepeatWrapping,
+ THREE.RepeatWrapping,
+ CONFIG.linearTextureFilter ? THREE.LinearFilter : THREE.NearestFilter,
+ CONFIG.linearTextureFilter ? THREE.LinearMipMapLinearFilter : THREE.NearestFilter,
+ opts.alpha ? THREE.RGBAFormat : THREE.RGBFormat,
+ THREE.UnsignedByteType,
+ CONFIG.anisotropy
+ );
+ _textures.push(texture);
+ return texture;
+}
+
+
+function updateTextures() {
+ for (var i = 0; i < _textures.length; ++i) {
+ _textures[i].magFilter = CONFIG.linearTextureFilter ? THREE.LinearFilter : THREE.NearestFilter;
+ _textures[i].minFilter = CONFIG.linearTextureFilter ? THREE.LinearMipMapLinearFilter : THREE.NearestFilter;
+ _textures[i].anisotropy = CONFIG.anisotropy;
+ _textures[i].needsUpdate = true;
+ }
+ updateConfig();
+}
+
+
+function fixAnisotropy(mat, value) {
+ if (!mat) return;
+ value = value || CONFIG.anisotropy;
+
+ function fixAnisotropyTex(tex) {
+ if (!tex) return;
+ tex.anisotropy = value;
+ tex.needsUpdate = true;
+ }
+
+ if (mat instanceof THREE.ShaderMaterial) {
+ fixAnisotropyTex(mat.uniforms.tDiffuse.value);
+ fixAnisotropyTex(mat.uniforms.tNormal.value);
+ fixAnisotropyTex(mat.uniforms.tSpecular.value);
+ fixAnisotropyTex(mat.uniforms.tAO.value);
+ fixAnisotropyTex(mat.uniforms.tCube.value);
+ fixAnisotropyTex(mat.uniforms.tDisplacement.value);
+ } else {
+ fixAnisotropyTex(mat.map);
+ fixAnisotropyTex(mat.normalMap);
+ fixAnisotropyTex(mat.specularMap);
+ fixAnisotropyTex(mat.lightMap);
+ }
+}
+
+
+function updateMaterials() {
+ for (var i in cache.materials) {
+ if (!cache.materials.hasOwnProperty(i)) continue;
+ cache.materials[i].needsUpdate = true;
+ // Also affected: shadows, soft shadows, physical shading
+ }
+ updateConfig();
+}
+
+
+function createMaterial(name) {
+ var texture_path = "assets/textures/";
+ var ambient = 0x333333, diffuse = 0xbbbbbb, specular = 0xffffff, shininess = 30, scale = 1.0;
+ /*var shader = THREE.ShaderUtils.lib["normal"];
+ var uniforms = THREE.UniformsUtils.clone(shader.uniforms);
+
+ uniforms["tDiffuse"].value = loadTexture(texture_path + name + ".jpg");
+ uniforms["tSpecular"].value = loadTexture(texture_path + "specular/" + name + ".jpg");
+ uniforms["tNormal"].value = loadTexture(texture_path + "normal/" + name + ".jpg");
+ uniforms["uShininess"].value = shininess;
+ //uniforms["uNormalScale"].value = new THREE.Vector2(1, 1);
+
+ //uniforms["tDisplacement"].texture = loadTexture(texture_path + "height/" + name + ".jpg");
+ //uniforms["uDisplacementBias"].value = - 0.428408 * scale;
+ //uniforms["uDisplacementScale"].value = 2.436143 * scale;
+
+ uniforms["enableAO"].value = false;
+ uniforms["enableDiffuse"].value = true;
+ uniforms["enableSpecular"].value = true;
+ uniforms["enableReflection"].value = false;
+ uniforms["enableDisplacement"].value = false;
+
+ uniforms["uDiffuseColor"].value.setHex(diffuse);
+ uniforms["uSpecularColor"].value.setHex(specular);
+ uniforms["uAmbientColor"].value.setHex(ambient);
+
+ uniforms["uDiffuseColor"].value.convertGammaToLinear();
+ uniforms["uSpecularColor"].value.convertGammaToLinear();
+ uniforms["uAmbientColor"].value.convertGammaToLinear();
+
+ //uniforms["wrapRGB"].value.set(0.575, 0.5, 0.5);
+ return new THREE.ShaderMaterial({
+ fragmentShader: shader.fragmentShader,
+ vertexShader: shader.vertexShader,
+ uniforms: uniforms,
+ fog: true,
+ lights: true
+ });*/
+
+ return new THREE.MeshPhongMaterial({
+ ambient: ambient,
+ diffuse: diffuse,
+ specular: specular,
+ shininess: shininess,
+ perPixel: true,
+ map: loadTexture(texture_path + name + ".jpg"),
+ specularMap: CONFIG.specularMapping ? loadTexture(texture_path + "specular/" + name + ".jpg") : undefined,
+ normalMap: CONFIG.normalMapping ? loadTexture(texture_path + "normal/" + name + ".jpg") : undefined
+ });
+
+}
+
+
+function dumpInfo() {
+ var gl = renderer.context;
+ var gl_info = {
+ "Version": gl.getParameter(gl.VERSION),
+ "Shading language": gl.getParameter(gl.SHADING_LANGUAGE_VERSION),
+ "Vendor": gl.getParameter(gl.VENDOR),
+ "Renderer": gl.getParameter(gl.RENDERER),
+ "Max varying vectors": gl.getParameter(gl.MAX_VARYING_VECTORS),
+ "Max vertex attribs": gl.getParameter(gl.MAX_VERTEX_ATTRIBS),
+ "Max vertex uniform vectors": gl.getParameter(gl.MAX_VERTEX_UNIFORM_VECTORS),
+ "Max fragment uniform vectors": gl.getParameter(gl.MAX_FRAGMENT_UNIFORM_VECTORS),
+ "Max renderbuffer size": gl.getParameter(gl.MAX_RENDERBUFFER_SIZE),
+ "Max texture size": gl.getParameter(gl.MAX_TEXTURE_SIZE),
+ "Max cube map texture size": gl.getParameter(gl.MAX_CUBE_MAP_TEXTURE_SIZE),
+ "Max texture image units": gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS),
+ "Max vertex texture units": gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS),
+ "Max combined texture units": gl.getParameter(gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS),
+ "Max viewport dims": gl.getParameter(gl.MAX_VIEWPORT_DIMS)[0] + "x" + gl.getParameter(gl.MAX_VIEWPORT_DIMS)[1]
+ };
+ console.log("WebGL info: ", gl_info);
+}
+
+function screenshot() {
+ var dataUrl = renderer.domElement.toDataURL("image/png");
+ window.open(dataUrl, "_blank");
+}
+
+
+var performance = window.performance || {};
+performance.now = (function() {
+ return performance.now ||
+ performance.mozNow ||
+ performance.msNow ||
+ performance.oNow ||
+ performance.webkitNow ||
+ function() { return new Date().getTime(); };
+})();
+
+function Profiler(name) {
+ name = name || "Profiling";
+ name += ": ";
+ this.start = function() {
+ this.time = performance.now();
+ };
+ this.end = function() {
+ var diff = performance.now() - this.time;
+ console.log(name + diff + "ms");
+ };
+ this.start();
+}
+"use strict";
+// Most of the contents from this file is adapted from examples of firework.js
+// http://jeromeetienne.github.com/fireworks.js/
+
+
+// Particle system initializer for simple particle flames
+function particleSystemCreator(emitter, position, color, texture) {
+ var i, geometry = new THREE.Geometry();
+ // Init vertices
+ for (i = 0; i < emitter.nParticles(); i++)
+ geometry.vertices.push(new THREE.Vector3());
+ // Init colors
+ geometry.colors = new Array(emitter.nParticles());
+ for (i = 0; i < emitter.nParticles(); i++)
+ geometry.colors[i] = new THREE.Color();
+ // Init material
+ var material = new THREE.ParticleBasicMaterial({
+ color: new THREE.Color(color).getHex(),
+ size: 0.3,
+ sizeAttenuation: true,
+ vertexColors: true,
+ map: texture || Fireworks.ProceduralTextures.buildTexture(),
+ blending: THREE.AdditiveBlending,
+ depthWrite: false,
+ transparent: true
+ });
+ // Init particle system
+ var particleSystem = new THREE.ParticleSystem(geometry, material);
+ particleSystem.dynamic = true;
+ particleSystem.sortParticles = true;
+ particleSystem.position = position;
+ scene.add(particleSystem);
+ return particleSystem;
+}
+
+// Create a simple fire emitter
+function createSimpleFire(position) {
+ var emitter = Fireworks.createEmitter({ nParticles: 30 });
+ emitter.effectsStackBuilder()
+ .spawnerSteadyRate(20)
+ .position(Fireworks.createShapeSphere(0, 0, 0, 0.1))
+ .velocity(Fireworks.createShapePoint(0, 1, 0))
+ .lifeTime(0.3, 0.6)
+ .renderToThreejsParticleSystem({
+ particleSystem: particleSystemCreator(emitter, position, 0xee8800)
+ }).back()
+ .start();
+ return emitter;
+}
+
+
+// Create a teleporter emitter
+var _novaTexture = loadTexture("assets/particles/nova.png", { alpha: true });
+function createTeleporterParticles(position) {
+ var emitter = Fireworks.createEmitter({ nParticles: 30 });
+ emitter.effectsStackBuilder()
+ .spawnerSteadyRate(20)
+ .position(Fireworks.createShapeSphere(0, 0, 0, 0.3))
+ .velocity(Fireworks.createShapePoint(0, 1, 0))
+ .lifeTime(0.6, 1.2)
+ .renderToThreejsParticleSystem({
+ particleSystem: particleSystemCreator(emitter, position, 0x0088ee, _novaTexture)
+ }).back()
+ .start();
+ return emitter;
+}
+
+
+// Create a torch fire emitter
+var _fireTexture = loadTexture("assets/particles/flame.png", { alpha: true });
+function createTexturedFire(parent) {
+ var numSprites = 8;
+ var emitter = Fireworks.createEmitter({ nParticles: 20 });
+ emitter.effectsStackBuilder()
+ .spawnerSteadyRate(15)
+ .position(Fireworks.createShapeSphere(0, 0.05, 0, 0.05))
+ .velocity(Fireworks.createShapePoint(0, 1.5, 0))
+ .lifeTime(0.1, 0.3)
+ .friction(0.99)
+ //.randomVelocityDrift(Fireworks.createVector(0.1,2,0))
+ .createEffect('scale', {
+ origin: 1/1000,
+ factor: 1.02
+ }).onBirth(function(particle) {
+ var object3d = particle.get('threejsObject3D').object3d;
+ var scale = this.opts.origin;
+ object3d.scale.set(scale*1.5, scale*4)
+ }).onUpdate(function(particle, deltaTime) {
+ var object3d = particle.get('threejsObject3D').object3d;
+ object3d.scale.multiplyScalar(this.opts.factor);
+ }).back()
+ .createEffect('rotation')
+ .onBirth(function(particle) {
+ var object3d = particle.get('threejsObject3D').object3d;
+ object3d.rotation = Math.random() * Math.PI * 2;
+ }).back()
+ .createEffect('opacity', {
+ gradient: Fireworks.createLinearGradient()
+ .push(0.00, 0.00)
+ .push(0.05, 1.00)
+ .push(0.99, 1.00)
+ .push(1.00, 0.00)
+ }).onUpdate(function(particle) {
+ var object3d = particle.get('threejsObject3D').object3d;
+ var canonAge = particle.get('lifeTime').normalizedAge();
+ object3d.opacity = this.opts.gradient.get(canonAge);
+ }).back()
+ .renderToThreejsObject3D({
+ container: parent,
+ create: function() {
+ var object3d = new THREE.Sprite({
+ useScreenCoordinates: false,
+ map: _fireTexture,
+ blending: THREE.AdditiveBlending,
+ transparent: true
+ });
+ object3d.uvScale.set(1, 1 / numSprites)
+ return object3d;
+ }
+ })
+ .createEffect("updateSpritesheet")
+ .onUpdate(function(particle, deltaTime) {
+ var object3d = particle.get('threejsObject3D').object3d;
+ var canonAge = particle.get('lifeTime').normalizedAge();
+ var imageIdx = Math.floor(canonAge * (numSprites));
+ var uvOffsetY = imageIdx * 1 / numSprites;
+ object3d.uvOffset.set(0, uvOffsetY)
+ }).back()
+ .back()
+ .start();
+ return emitter;
+}
+"use strict";
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ * @author paulirish / http://paulirish.com/
+ * @author tapio / http://tapio.github.com/
+ */
+
+// Based on THREE.FirstPersonControls
+function Controls(object, handlers, domElement) {
+ this.object = object;
+ this.handlers = handlers || {};
+ this.target = new THREE.Vector3(0, 0, 0);
+ this.domElement = (domElement !== undefined) ? domElement : document;
+
+ this.movementSpeed = 1.0;
+ this.lookSpeed = 0.005;
+ this.lookVertical = true;
+ this.autoForward = false;
+ this.mouseEnabled = true;
+ this.active = true;
+
+ this.constrainVerticalLook = false;
+ this.verticalMin = 0;
+ this.verticalMax = Math.PI;
+
+ this.pointerLockEnabled = false;
+ this.mouseFallback = false;
+
+ this.mouseX = 0;
+ this.mouseY = 0;
+
+ var lat = 0, lon = 0, phi = 0, theta = 0;
+ var moveForward = false, moveBackward = false;
+ var moveLeft = false, moveRight = false;
+ var moveUp = false, moveDown = false;
+ var viewHalfX = 0, viewHalfY = 0;
+
+ if (this.domElement !== document) {
+ this.domElement.setAttribute('tabindex', -1);
+ }
+
+ //
+
+ this.setYAngle = function(angle) {
+ lon = angle;
+ }
+
+ this.reset = function() {
+ lat = 0; lon = 0;
+ };
+
+ this.handleResize = function () {
+ if (this.domElement === document) {
+ viewHalfX = window.innerWidth / 2;
+ viewHalfY = window.innerHeight / 2;
+ } else {
+ viewHalfX = this.domElement.offsetWidth / 2;
+ viewHalfY = this.domElement.offsetHeight / 2;
+ }
+ };
+
+ this.onMouseDown = function (event) {
+ event.preventDefault();
+ if (this.domElement !== document) this.domElement.focus();
+ if (this.pointerLockEnabled) event.stopPropagation();
+ if (this.mouseEnabled && this.handlers.mouse)
+ this.handlers.mouse(event.button);
+ };
+
+ this.onMouseUp = function (event) {
+ event.preventDefault();
+ if (this.pointerLockEnabled) event.stopPropagation();
+ if (this.mouseEnabled) {
+ switch (event.button) {
+ case 0: break;
+ case 2: break;
+ }
+ }
+ };
+
+ this.onMouseMove = function (event) {
+ function limit(a, lo, hi) { return a < lo ? lo : (a > hi ? hi : a); }
+ if (this.pointerLockEnabled) {
+ if (event.mozMovementX === 0 && event.mozMovementY === 0) return; // Firefox fires 0-movement event right after real one
+ this.mouseX = event.movementX || event.webkitMovementX || event.mozMovementX || 0;
+ this.mouseY = event.movementY || event.webkitMovementY || event.mozMovementY || 0;
+ this.mouseX = limit(this.mouseX * 20, -600, 600);
+ this.mouseY = limit(this.mouseY * 20, -600, 600);
+ } else if (this.domElement === document) {
+ this.mouseX = event.pageX - viewHalfX;
+ this.mouseY = event.pageY - viewHalfY;
+ } else {
+ this.mouseX = event.pageX - this.domElement.offsetLeft - viewHalfX;
+ this.mouseY = event.pageY - this.domElement.offsetTop - viewHalfY;
+ }
+ };
+
+ this.onKeyDown = function (event) {
+ //event.preventDefault();
+ switch (event.keyCode) {
+ case 38: /*up*/
+ case 87: /*W*/ moveForward = true; break;
+ case 37: /*left*/
+ case 65: /*A*/ moveLeft = true; break;
+ case 40: /*down*/
+ case 83: /*S*/ moveBackward = true; break;
+ case 39: /*right*/
+ case 68: /*D*/ moveRight = true; break;
+ case 81: /*Q*/ this.active = !this.active; break;
+ case 123: /*F12*/ screenshot(); break;
+ }
+ };
+
+ this.onKeyUp = function (event) {
+ switch(event.keyCode) {
+ case 38: /*up*/
+ case 87: /*W*/ moveForward = false; break;
+ case 37: /*left*/
+ case 65: /*A*/ moveLeft = false; break;
+ case 40: /*down*/
+ case 83: /*S*/ moveBackward = false; break;
+ case 39: /*right*/
+ case 68: /*D*/ moveRight = false; break;
+ }
+ };
+
+ this.update = function(delta) {
+ if (!this.active) return;
+
+ var actualMoveSpeed = delta * this.movementSpeed,
+ actualLookSpeed = this.mouseEnabled ? delta * this.lookSpeed : 0,
+ cameraPosition = this.object.position;
+
+ // Looking
+
+ if (this.pointerLockEnabled ||
+ (this.mouseFallback && this.mouseX * this.mouseX + this.mouseY * this.mouseY > 5000))
+ {
+ lon += this.mouseX * actualLookSpeed;
+ if (this.lookVertical)
+ lat -= this.mouseY * actualLookSpeed;
+ }
+
+ lat = Math.max(-85, Math.min(85, lat));
+ phi = (90 - lat) * Math.PI / 180;
+ theta = lon * Math.PI / 180;
+
+ if (this.constrainVerticalLook)
+ phi = THREE.Math.mapLinear(phi, 0, Math.PI, this.verticalMin, this.verticalMax);
+
+ this.target.x = cameraPosition.x + 100 * Math.sin(phi) * Math.cos(theta);
+ this.target.y = cameraPosition.y + 100 * Math.cos(phi);
+ this.target.z = cameraPosition.z + 100 * Math.sin(phi) * Math.sin(theta);
+
+ if (this.pointerLockEnabled) {
+ this.mouseX = 0;
+ this.mouseY = 0;
+ }
+
+ this.object.lookAt(this.target);
+
+ // Movement
+
+ if (moveForward || (this.autoForward && !moveBackward)) {
+ this.object.translateZ(-actualMoveSpeed);
+ } else if (moveBackward) {
+ this.object.translateZ(actualMoveSpeed);
+ }
+
+ if (moveLeft) {
+ this.object.translateX(-actualMoveSpeed);
+ } else if (moveRight) {
+ this.object.translateX(actualMoveSpeed);
+ }
+
+ if (moveUp) {
+ this.object.translateY(actualMoveSpeed);
+ } else if (moveDown) {
+ this.object.translateY(-actualMoveSpeed);
+ }
+
+ };
+
+
+ this.domElement.addEventListener('contextmenu', function (event) { event.preventDefault(); }, false);
+ this.domElement.addEventListener('mousemove', bind(this, this.onMouseMove), false);
+ this.domElement.addEventListener('mousedown', bind(this, this.onMouseDown), false);
+ this.domElement.addEventListener('mouseup', bind(this, this.onMouseUp), false);
+ this.domElement.addEventListener('keydown', bind(this, this.onKeyDown), false);
+ this.domElement.addEventListener('keyup', bind(this, this.onKeyUp), false);
+
+ function bind(scope, fn) {
+ return function () {
+ fn.apply(scope, arguments);
+ };
+ }
+
+ this.handleResize();
+};
+var assets = {
+ objects: {
+ "fern-01": { randScale: 0.3, noShadows: true },
+ "fern-02": { randScale: 0.3, noShadows: true },
+ "plant-01": { randScale: 0.3, noShadows: true },
+ "plant-02": { randScale: 0.3, noShadows: true },
+ "plant-03": { randScale: 0.3, noShadows: true },
+ "plant-04": { randScale: 0.3, noShadows: true },
+ "plant-05": { randScale: 0.3, noShadows: true },
+ "rock-01": { collision: "sphere", mass: 400, randScale: 0.4 },
+ "rock-02": { collision: "box", mass: 400, randScale: 0.4 },
+ "rock-03": { collision: "box", mass: 400, randScale: 0.4 },
+ "rock-04": { collision: "box", mass: 500, randScale: 0.4 },
+ "barrel": { collision: "cylinder", mass: 250 },
+ "box": { collision: "box", mass: 150 },
+ "bucket-broken": { collision: "cylinder", mass: 50 },
+ "netted-jar": { collision: "cylinder", mass: 100 },
+ "vase-01": { collision: "cylinder", mass: 75 },
+ "table-big": { collision: "box", mass: 1000 },
+ "table-old": { collision: "box", mass: 800 },
+ "chair-01": { collision: "box", mass: 200 },
+ "bench-church": { collision: "box", mass: 1000 },
+ "torch-standing": { collision: "cylinder", mass: 150 },
+ "mine-cart": { collision: "box", mass: 900 },
+ "obelisk-01": { collision: "box", mass: 0 },
+ "obelisk-02": { collision: "box", mass: 0 },
+ "pillar-broken-01": { collision: "cylinder", mass: 0 },
+ "pillar-broken-02": { collision: "cylinder", mass: 0 },
+ "pillar-greek": { collision: "cylinder", mass: 0 },
+ "forge": { collision: "concave", mass: 0 },
+ "door-barred": { collision: "box", mass: 400, door: true },
+ "teleporter": { collision: "cylinder", mass: 0 }
+ },
+ items: {
+ "key": {},
+ "knife": {},
+ "health-potion": {},
+ "mana-potion": {}
+ },
+ materials: {
+ "rock-01": { roughness: 0.15 },
+ "rock-02": { roughness: 0.15 },
+ "rock-03": { roughness: 0.15 },
+ "rock-04": { roughness: 0.15 }
+ },
+ monsters: {
+ "cerberus": { collision: "box", character: { speed: 120 }, animation: { duration: 750 } },
+ "spider": { collision: "box", character: { speed: 120 }, animation: { duration: 750 } },
+ "minotaur": { collision: "box", character: { speed: 400 }, animation: { duration: 750 } }
+ },
+ environments: {
+ "cave": {
+ wall: [ "rock-01", "rock-02", "rock-03", "rock-04" ],
+ floor: [ "sand-01", "sand-02", "sand-03", "sand-04" ],
+ ceiling: [ "rock-01", "rock-02", "rock-03", "rock-04" ],
+ objects: [ "rock-01", "rock-02", "rock-03", "rock-04" ]
+ },
+ "mine": {
+ wall: [ "rock-01", "rock-02", "rock-03", "rock-04" ],
+ floor: [ "rock-01", "rock-02", "rock-03", "rock-04" ],
+ ceiling: [ "rock-01", "rock-02", "rock-03", "rock-04" ],
+ objects: [ "mine-cart", "rock-01", "rock-02", "rock-03", "rock-04", "barrel", "box", "netted-jar", "table-old" ]
+ },
+ "dungeon": {
+ wall: [ "stone-01", "stone-02", "stone-03" ],
+ floor: [ "stone-floor-02", "stone-floor-05" ],
+ ceiling: [ "stone-01" ],
+ objects: [ "barrel", "box", "netted-jar", "table-old", "chair-01" ]
+ },
+ "castle": {
+ wall: [ "stone-01", "tiles-01", "tiles-02" ],
+ floor: [ "stone-floor-01", "stone-floor-03", "stone-floor-04", "stone-floor-06", "wood-floor-01" ],
+ ceiling: [ "stone-01" ],
+ objects: [ "barrel", "netted-jar", "table-big", "chair-01" ]
+ },
+ "temple": {
+ wall: [ "stone-01", "tiles-01", "tiles-02" ],
+ floor: [ "stone-floor-01", "stone-floor-03", "stone-floor-04", "stone-floor-06", "wood-floor-01" ],
+ ceiling: [ "stone-01" ],
+ objects: [ "pillar-greek", "pillar-greek", "netted-jar", "table-old", "chair-01" ]
+ }
+ }
+};
+"use strict";
+var VOID = " ";
+var OPEN = ".";
+var WALL = "#";
+var DIAG = "%";
+
+function Map(w, h, data) {
+ this.map = new Array(w * h);
+
+ if (data && data.length && data instanceof Array) {
+ for (var j = 0; j < h; ++j) {
+ for (var i = 0; i < w; ++i) {
+ this.map[j * w + i] = data[j][i];
+ }
+ }
+ } else if (data) {
+ for (var k = 0; k < w * h; ++k) this.map[k] = data;
+ }
+
+ this.get = function(x, y, fallback) {
+ if (x < 0 || x >= w || y < 0 || y >= h) return fallback || null;
+ return this.map[y * w + x];
+ };
+
+ this.put = function(x, y, what) {
+ if (x < 0 || x >= w || y < 0 || y >= h) return;
+ this.map[y * w + x] = what;
+ };
+
+ this.toJSON = function() {
+ var res = new Array(h);
+ for (var j = 0; j < h; ++j) {
+ res[j] = "";
+ for (var i = 0; i < w; ++i) {
+ res[j] += this.map[j * w + i];
+ }
+ }
+ return res;
+ };
+
+ this.replace = function(oldval, newval) {
+ for (var j = 0; j < h; ++j) {
+ for (var i = 0; i < w; ++i) {
+ if (this.map[j * w + i] == oldval)
+ this.map[j * w + i] = newval;
+ }
+ }
+ };
+
+ function floodFill(map, x, y, target, filler, skip) {
+ var cell = map.get(x, y);
+ if (cell != target && cell != skip) return;
+ if (cell != skip)
+ map.map[y * w + x] = filler;
+ floodFill(map, x-1, y, target, filler, skip);
+ floodFill(map, x+1, y, target, filler, skip);
+ floodFill(map, x, y-1, target, filler, skip);
+ floodFill(map, x, y+1, target, filler, skip);
+ };
+
+ this.fill = function(x, y, target, filler, skip) {
+ floodFill(this, x, y, target, filler, skip);
+ };
+
+}
+"use strict";
+function MapGen() {
+ var self = this;
+
+ // Checks if the given position overlaps with the given array of objects
+ function testOverlap(pos, objects, tolerance) {
+ if (!objects.length) return false;
+ tolerance = tolerance ? tolerance * tolerance : 0.000001; // Distance squared
+ for (var i = 0; i < objects.length; ++i) {
+ var dx = objects[i].position.x - pos.x, dz = objects[i].position.z - pos.z;
+ if (dx * dx + dz * dz <= tolerance) return true;
+ }
+ return false;
+ }
+
+ // Checks if the given grid position is a corridor
+ function testCorridor(pos, map) {
+ var count = 0;
+ count += map.get(pos.x-1, pos.z) == WALL ? 1 : 0;
+ count += map.get(pos.x+1, pos.z) == WALL ? 1 : 0;
+ count += map.get(pos.x, pos.z-1) == WALL ? 1 : 0;
+ count += map.get(pos.x, pos.z+1) == WALL ? 1 : 0;
+ return count == 2;
+ }
+
+ this.generateMap = function(level) {
+ var width = level.width = rand(25,35);
+ var depth = level.depth = rand(25,35);
+ level.map = new Map(width, depth, WALL);
+ var i, j;
+
+ // Materials
+ level.env = randProp(assets.environments);
+ level.materials = {
+ floor: randElem(level.env.floor),
+ ceiling: randElem(level.env.ceiling),
+ wall: randElem(level.env.wall)
+ };
+
+ // Create rooms
+ var roomsize = rand(3,4);
+ var rooms = Math.floor(width * depth / (roomsize * roomsize * 4));
+ var x = rand(roomsize+1, width-roomsize-1);
+ var z = rand(roomsize+1, depth-roomsize-1);
+ var ox, oz, swapx, swapz;
+
+ for (var room = 0; room < rooms; ++room) {
+ var rw = rand(2, roomsize);
+ var rd = rand(2, roomsize);
+ var xx = x - rand(0, rw-1);
+ var zz = z - rand(0, rd-1);
+
+ // Floor for the room
+ for (j = zz; j < zz + rd; ++j)
+ for (i = xx; i < xx + rw; ++i)
+ level.map.put(i, j, OPEN);
+
+ ox = x; oz = z;
+
+ // Don't create a dead end corridor
+ if (room == rooms-1) break;
+
+ // Pick new room location
+ do {
+ x = rand(roomsize+1, width-roomsize-1);
+ z = rand(roomsize+1, depth-roomsize-1);
+ } while (level.map.get(x,z) == WALL && Math.abs(ox-x) + Math.abs(oz-z) >= 30);
+
+ // Do corridors
+ swapx = x < ox;
+ for (i = swapx ? x : ox; i < (swapx ? ox : x); ++i)
+ level.map.put(i, oz, OPEN);
+ swapz = z < oz;
+ for (j = swapz ? z : oz; j < (swapz ? oz : z); ++j)
+ level.map.put(x, j, OPEN);
+ }
+
+ // Count open space
+ level.floorCount = 0;
+ for (z = 0; z < depth; ++z)
+ for (x = 0; x < width; ++x)
+ if (level.map.get(x,z) == OPEN) level.floorCount++;
+
+ // Place start
+ do {
+ level.start[0] = rand(1, width-2);
+ level.start[1] = rand(1, depth-2);
+ } while (level.map.get(level.start[0], level.start[1]) == WALL);
+
+ // Place exit
+ do {
+ level.exit[0] = rand(1, width-2);
+ level.exit[1] = rand(1, depth-2);
+ } while (level.map.get(level.exit[0], level.exit[1]) == WALL);
+ level.exit[0] += 0.5;
+ level.exit[1] += 0.5;
+ };
+
+ this.generateLights = function(level) {
+ // Point lights
+ var nLights = Math.floor(level.floorCount / 20);
+ var pos = new THREE.Vector3();
+ var i = 0;
+ while (i < nLights) {
+ // Pick a random place
+ pos.x = rand(0, level.width-1);
+ pos.z = rand(0, level.depth-1);
+ // Make sure we are not inside a wall
+ if (level.map.get(pos.x, pos.z) == WALL) continue;
+ // Pick a random cardinal direction
+ var dir = rand(0,3) * Math.PI * 0.5;
+ var dx = Math.round(Math.cos(dir));
+ var dz = -Math.round(Math.sin(dir));
+ // Travel until wall found
+ while (level.map.get(pos.x, pos.z, WALL) != WALL) {
+ pos.x += dx;
+ pos.z += dz;
+ }
+ // Back away to wall face
+ pos.x = pos.x - 0.6 * dx + 0.5;
+ pos.z = pos.z - 0.6 * dz + 0.5;
+ pos.y = level.roomHeight * 0.7;
+ if (testOverlap(pos, level.lights, 4.1)) continue;
+ ++i;
+ // Actual light
+ level.lights.push({
+ position: { x: pos.x, y: pos.y, z: pos.z },
+ target: { x: pos.x - dx * 1.1, y: pos.y - 1, z: pos.z - dz * 1.1 }
+ });
+ }
+ };
+
+ this.generateObjects = function(level) {
+ // Placement
+ var nObjects = Math.floor(level.floorCount / 8);
+ var pos = new THREE.Vector3();
+ var i = 0;
+ while (i < nObjects) {
+ // Pick a random place
+ pos.x = rand(0, level.width-1);
+ pos.z = rand(0, level.depth-1);
+ // Make sure we are not inside a wall
+ if (level.map.get(pos.x, pos.z) == WALL) continue;
+ // TODO: Place most near walls
+ // TODO: Groups, stacks, etc?
+
+ if (testCorridor(pos, level.map)) continue;
+
+ pos.x += 0.5;
+ pos.z += 0.5;
+ pos.y = null; // Auto
+
+ if (testOverlap(pos, level.objects, 1.4)) continue;
+ ++i;
+
+ var objname = randElem(level.env.objects);
+ level.objects.push({
+ name: objname,
+ position: { x: pos.x, y: pos.y, z: pos.z }
+ });
+ }
+ };
+
+ this.generate = function() {
+ var level = {
+ map: [],
+ objects: [],
+ lights: [],
+ gridSize: 2,
+ roomHeight: 3,
+ start: [ 0, 0 ],
+ exit: [ 0, 0 ]
+ };
+ this.generateMap(level);
+ this.generateLights(level);
+ this.generateObjects(level);
+ return level;
+ };
+}
+
+
+
+
+function randProp(obj) {
+ var result, count = 0;
+ for (var prop in obj)
+ if (Math.random() < 1.0 / ++count) result = prop;
+ return obj[result];
+}
+
+function randElem(arr) {
+ return arr[(Math.random() * arr.length) | 0];
+}
+
+function rand(lo, hi) {
+ return lo + Math.floor(Math.random() * (hi - lo + 1));
+}
+
+function randf(lo, hi) {
+ return lo + Math.random() * (hi - lo);
+}
+"use strict";
+function Dungeon(scene, player, levelName) {
+ var self = this;
+ this.loaded = false;
+ this.objects = [];
+ this.monsters = [];
+ var dummy_material = new THREE.MeshBasicMaterial({color: 0x000000});
+ var debug_material = new THREE.MeshBasicMaterial({color: 0xff00ff});
+
+ function objectHandler(level, pos, ang, def) {
+ return function(geometry) {
+ if (!def) def = {};
+ var obj, mass = def.mass || 0;
+
+ // Preprocessing
+ if (def.character) mass = 100000;
+ var scale = 1.0;
+ if (def.randScale) {
+ scale += randf(-def.randScale, def.randScale);
+ mass *= scale;
+ }
+ if (def.animation) geometry.computeMorphNormals();
+ if (!geometry.boundingBox) geometry.computeBoundingBox();
+
+ // Handle materials
+ for (var m = 0; m < geometry.materials.length; ++m) {
+ fixAnisotropy(geometry.materials[m]);
+ if (def.animation) {
+ geometry.materials[m].morphTargets = true;
+ geometry.materials[m].morphNormals = true;
+ }
+ }
+ var mat = geometry.materials.length > 1 ? new THREE.MeshFaceMaterial() : geometry.materials[0];
+
+ // Mesh creation
+ if (def.collision) {
+ var material = Physijs.createMaterial(mat, 0.7, 0.2); // friction, restition
+ if (def.collision == "plane")
+ obj = new Physijs.PlaneMesh(geometry, material, mass);
+ else if (def.collision == "box")
+ obj = new Physijs.BoxMesh(geometry, material, mass);
+ else if (def.collision == "sphere")
+ obj = new Physijs.SphereMesh(geometry, material, mass);
+ else if (def.collision == "cylinder")
+ obj = new Physijs.CylinderMesh(geometry, material, mass);
+ else if (def.collision == "cone")
+ obj = new Physijs.ConeMesh(geometry, material, mass);
+ else if (def.collision == "convex")
+ obj = new Physijs.ConvexMesh(geometry, material, mass);
+ else if (def.collision == "concave")
+ obj = new Physijs.ConcaveMesh(geometry, material, mass);
+ else throw "Unsupported collision mesh type " + def.collision;
+ self.objects.push(obj);
+ } else {
+ obj = new THREE.Mesh(geometry, mat);
+ }
+
+ // Positioning
+ if (def.door) {
+ // Fix door positions
+ pos.x = Math.floor(pos.x) + 0.5;
+ pos.z = Math.floor(pos.z) + 0.5;
+ }
+ pos.x *= level.gridSize;
+ pos.z *= level.gridSize;
+ if (!pos.y && pos.y !== 0) { // Auto-height
+ pos.y = 0.5 * (geometry.boundingBox.max.y - geometry.boundingBox.min.y) + 0.001;
+ }
+ obj.position.copy(pos);
+ if (ang) obj.rotation.y = ang / 180 * Math.PI;
+
+ // Other attributes
+ obj.scale.set(scale, scale, scale);
+ if (!def.noShadows && !def.animation) {
+ obj.castShadow = true;
+ obj.receiveShadow = true;
+ }
+ if (mass === 0) {
+ obj.matrixAutoUpdate = false;
+ obj.updateMatrix();
+ }
+
+ // Handle animated meshes
+ if (def.animation) {
+ obj.visible = false;
+ self.monsters.push(obj);
+ obj.mesh = new THREE.MorphAnimMesh(geometry, mat);
+ obj.mesh.duration = def.animation.duration;
+ obj.mesh.time = obj.mesh.duration * Math.random();
+ if (!def.noShadows) {
+ obj.mesh.castShadow = true;
+ obj.mesh.receiveShadow = true;
+ }
+ obj.add(obj.mesh);
+ }
+
+ // Finalize
+ scene.add(obj);
+ if (def.character && def.collision) obj.setAngularFactor({ x: 0, y: 0, z: 0 });
+ if (def.character) obj.speed = def.character.speed;
+ if (def.door) {
+ obj.setAngularFactor({ x: 0, y: 1, z: 0 });
+ // Hinge
+ var hingepos = obj.position.clone();
+ var hingedist = 0.5 * (geometry.boundingBox.max.x - geometry.boundingBox.min.x) - 0.1
+ hingepos.x -= Math.cos(obj.rotation.y) * hingedist;
+ hingepos.z += Math.sin(obj.rotation.y) * hingedist;
+ var constraint = new Physijs.HingeConstraint(
+ obj,
+ hingepos,
+ new THREE.Vector3(0, 1, 0) // Hinge axisAxis along which the hinge lies - in this case it is the X axis
+ );
+ scene.addConstraint(constraint);
+ constraint.setLimits(
+ -Math.PI / 2 * 0.95 + obj.rotation.y, // minimum angle of motion, in radians
+ Math.PI / 2 * 0.95 + obj.rotation.y, // maximum angle of motion, in radians
+ 0.3, // bias_factor, applied as a factor to constraint error
+ 0.01 // relaxation_factor, controls bounce at limit (0.0 == no bounce)
+ );
+ }
+ };
+ }
+
+ this.generateMesh = function(level) {
+ var sqrt2 = Math.sqrt(2);
+ var block_mat = cache.getMaterial(level.materials.wall);
+ var block_params = {};
+ if (assets.materials[level.materials.wall] && assets.materials[level.materials.wall].roughness)
+ block_params.roughness = assets.materials[level.materials.wall].roughness;
+
+ // Level geometry
+ var geometry = new THREE.Geometry(), mesh;
+ var cell, px, nx, pz, nz, py, ny, tess, cube, hash, rot;
+ for (var j = 0; j < level.depth; ++j) {
+ for (var i = 0; i < level.width; ++i) {
+ px = nx = pz = nz = py = ny = 0;
+ cell = level.map.get(i, j, OPEN);
+ if (cell === OPEN) continue;
+ if (cell === WALL || cell === DIAG) {
+ px = level.map.get(i + 1, j) == OPEN ? 1 : 0;
+ nx = level.map.get(i - 1, j) == OPEN ? 2 : 0;
+ pz = level.map.get(i, j + 1) == OPEN ? 4 : 0;
+ nz = level.map.get(i, j - 1) == OPEN ? 8 : 0;
+ // If wall completely surrounded by walls, skip
+ hash = px + nx + pz + nz;
+ if (hash === 0) continue;
+ tess = block_params.roughness ? 10 : 0;
+ rot = 0;
+ if (cell === DIAG && (hash == 5 || hash == 6 || hash == 9 || hash == 10)) {
+ cube = new PlaneGeometry(level.gridSize * sqrt2, level.roomHeight, tess, tess,
+ "px", level.gridSize * sqrt2 / 2, level.roomHeight / 2, block_params.roughness);
+ if (hash == 5) rot = -45 / 180 * Math.PI;
+ else if (hash == 6) rot = -135 / 180 * Math.PI;
+ else if (hash == 9) rot = 45 / 180 * Math.PI;
+ else if (hash == 10) rot = 135 / 180 * Math.PI;
+ cube.materials = [ block_mat ];
+ } else {
+ cube = new BlockGeometry(level.gridSize, level.roomHeight, level.gridSize,
+ tess, tess, tess, block_mat,
+ { px: px, nx: nx, py: 0, ny: 0, pz: pz, nz: nz },
+ level.gridSize/2, level.roomHeight/2, block_params.roughness);
+ }
+ mesh = new THREE.Mesh(cube);
+ mesh.position.x = (i + 0.5) * level.gridSize;
+ mesh.position.y = 0.5 * level.roomHeight;
+ mesh.position.z = (j + 0.5) * level.gridSize;
+ mesh.rotation.y = rot;
+ THREE.GeometryUtils.merge(geometry, mesh);
+
+ // Collision body
+ if (cell === DIAG) {
+ cube = new THREE.CubeGeometry(0.01, level.roomHeight, level.gridSize * sqrt2);
+ } else {
+ // Bounding box needs tweaking if there is only one side in the block
+ cube.computeBoundingBox();
+ if (Math.abs(cube.boundingBox.max.x - cube.boundingBox.min.x) <= 0.5) {
+ cube.boundingBox.min.x = -0.5 * level.gridSize;
+ cube.boundingBox.max.x = 0.5 * level.gridSize;
+ }
+ if (Math.abs(cube.boundingBox.max.z - cube.boundingBox.min.z) <= 0.5) {
+ cube.boundingBox.min.z = -0.5 * level.gridSize;
+ cube.boundingBox.max.z = 0.5 * level.gridSize;
+ }
+ }
+ var wallbody = new Physijs.BoxMesh(cube, dummy_material, 0);
+ wallbody.position.copy(mesh.position);
+ wallbody.visible = false;
+ scene.add(wallbody);
+ if (cell === DIAG) {
+ wallbody.rotation.y = rot;
+ wallbody.__dirtyRotation = true;
+ }
+ }
+ }
+ }
+
+ // Ceiling, no collision needed
+ var ceiling_plane = new THREE.Mesh(
+ new PlaneGeometry(level.gridSize * level.width, level.gridSize * level.depth,
+ 1, 1, "ny", level.width, level.depth),
+ cache.getMaterial(level.materials.ceiling)
+ );
+ ceiling_plane.position.set(level.gridSize * level.width * 0.5, level.roomHeight, level.gridSize * level.depth * 0.5);
+ ceiling_plane.matrixAutoUpdate = false;
+ ceiling_plane.updateMatrix();
+ scene.add(ceiling_plane);
+
+ // Floor with collision
+ var floor_plane = new Physijs.PlaneMesh(
+ new PlaneGeometry(level.gridSize * level.width, level.gridSize * level.depth,
+ 1, 1, "py", level.width, level.depth),
+ Physijs.createMaterial(cache.getMaterial(level.materials.floor), 0.9, 0.0), // friction, restitution
+ 0 // mass
+ );
+ floor_plane.position.set(level.gridSize * level.width * 0.5, 0.0, level.gridSize * level.depth * 0.5);
+ floor_plane.receiveShadow = true;
+ floor_plane.matrixAutoUpdate = false;
+ floor_plane.updateMatrix();
+ scene.add(floor_plane);
+
+ // Level mesh
+ geometry.computeTangents();
+ mesh = new THREE.Mesh(geometry, new THREE.MeshFaceMaterial());
+ mesh.receiveShadow = true;
+ mesh.matrixAutoUpdate = false;
+ mesh.updateMatrix();
+ scene.add(mesh);
+
+ // Exit
+ cache.loadModel("assets/models/teleporter/teleporter.js",
+ objectHandler(level, new THREE.Vector3().set(level.exit[0], null, level.exit[1]), 0, assets.objects.teleporter));
+ if (CONFIG.particles)
+ this.exitParticles = createTeleporterParticles(
+ new THREE.Vector3(level.exit[0] * level.gridSize, 0.5, level.exit[1] * level.gridSize));
+ };
+
+ this.addLights = function(level) {
+ // Torch model load callback
+ function torchHandler(pos, rot) {
+ return function(geometry) {
+ for (var m = 0; m < geometry.materials.length; ++m)
+ fixAnisotropy(geometry.materials[m]);
+ var mat = geometry.materials.length > 1 ? new THREE.MeshFaceMaterial() : geometry.materials[0];
+ var obj = new THREE.Mesh(geometry, mat);
+ obj.position.copy(pos);
+ obj.rotation.y = rot;
+ obj.castShadow = true;
+ obj.receiveShadow = true;
+ obj.matrixAutoUpdate = false;
+ obj.updateMatrix();
+ scene.add(obj);
+ };
+ }
+
+ // Ambient
+ scene.add(new THREE.AmbientLight(0xaaaaaa));
+
+ // Point lights
+ var vec = new THREE.Vector2();
+ var target = new THREE.Vector3();
+ for (var i = 0; i < level.lights.length; ++i) {
+ if (level.lights[i].position.y === undefined)
+ level.lights[i].position.y = 2;
+ // Actual light
+ var light = new THREE.PointLight(0xffffaa, 1, 2 * level.gridSize);
+ light.position.copy(level.lights[i].position);
+ var torch = "assets/models/torch/torch.js";
+
+ // Snap to wall
+ // Create wall candidates for checking which wall is closest to the light
+ vec.set(level.lights[i].position.x|0, level.lights[i].position.z|0);
+ var candidates = [
+ { x: vec.x + 0.5, y: vec.y, a: Math.PI },
+ { x: vec.x + 1.0, y: vec.y + 0.5, a: Math.PI/2 },
+ { x: vec.x + 0.5, y: vec.y + 1.0, a: 0 },
+ { x: vec.x, y: vec.y + 0.5, a: -Math.PI/2 }
+ ];
+ vec.set(level.lights[i].position.x, level.lights[i].position.z);
+ // Find the closest
+ var snapped = { d: 1000 };
+ for (var j = 0; j < candidates.length; ++j) {
+ candidates[j].d = vec.distanceToSquared(candidates[j]);
+ if (candidates[j].d < snapped.d) snapped = candidates[j];
+ }
+ // Position the light to the wall
+ light.position.x = snapped.x;
+ light.position.z = snapped.y;
+ // Get wall normal vector
+ vec.set((level.lights[i].position.x|0) + 0.5, (level.lights[i].position.z|0) + 0.5);
+ vec.subSelf(snapped).multiplyScalar(2);
+ // Check if there actually is a wall
+ if (level.map.get((light.position.x - vec.x * 0.5)|0, (light.position.z - vec.y * 0.5)|0) == WALL) {
+ // Move out of the wall
+ light.position.x += vec.x * 0.08;
+ light.position.z += vec.y * 0.08;
+ target.set(light.position.x + vec.x , light.position.y - 1, light.position.z + vec.y);
+ } else {
+ // Switch to ceiling hanging light
+ torch = Math.random() < 0.5 ? "assets/models/torch-hanging-01/torch-hanging-01.js"
+ : "assets/models/torch-hanging-02/torch-hanging-02.js";
+ light.position.x = (level.lights[i].position.x|0) + 0.5;
+ light.position.y = level.roomHeight - 0.9;
+ light.position.z = (level.lights[i].position.z|0) + 0.5;
+ target.copy(light.position);
+ target.y -= 1;
+ }
+
+ light.position.x *= level.gridSize;
+ light.position.z *= level.gridSize;
+ target.x *= level.gridSize;
+ target.z *= level.gridSize;
+ light.matrixAutoUpdate = false;
+ light.updateMatrix();
+ scene.add(light);
+ lightManager.addLight(light);
+
+ // Shadow casting light
+ var light2 = new THREE.SpotLight(0xffffaa, light.intensity, light.distance);
+ light2.position.copy(light.position);
+ light2.position.y = level.roomHeight;
+ light2.target.position.copy(target);
+ light2.angle = Math.PI / 2;
+ light2.castShadow = true;
+ light2.onlyShadow = true;
+ light2.shadowCameraNear = 0.1 * UNIT;
+ light2.shadowCameraFar = light.distance * 1.5 * UNIT;
+ light2.shadowCameraFov = 100;
+ light2.shadowBias = -0.0002;
+ light2.shadowDarkness = 0.3;
+ light2.shadowMapWidth = 512;
+ light2.shadowMapHeight = 512;
+ light2.shadowCameraVisible = false;
+ light2.matrixAutoUpdate = false;
+ light2.updateMatrix();
+ scene.add(light2);
+ lightManager.addShadow(light2);
+
+ // Mesh
+ cache.loadModel(torch, torchHandler(new THREE.Vector3().copy(light.position), snapped.a));
+
+ // Flame
+ if (CONFIG.particles)
+ light.emitter = createTexturedFire(light);
+ }
+
+ // Player's torch
+ player.light = new THREE.PointLight(0xccccaa, 1, level.gridSize * 3);
+ scene.add(player.light);
+ player.shadow = new THREE.SpotLight(player.light.color, player.light.intensity, player.light.distance);
+ player.shadow.angle = Math.PI / 4;
+ player.shadow.onlyShadow = true;
+ player.shadow.castShadow = true;
+ player.shadow.shadowCameraNear = 0.1 * UNIT;
+ player.shadow.shadowCameraFar = 10 * UNIT;
+ player.shadow.shadowCameraFov = 90;
+ player.shadow.shadowBias = -0.0002;
+ player.shadow.shadowDarkness = 0.3;
+ player.shadow.shadowMapWidth = 1024;
+ player.shadow.shadowMapHeight = 1024;
+ player.shadow.shadowCameraVisible = false;
+ scene.add(player.shadow);
+ };
+
+ this.addObjects = function(level) {
+ if (!level.objects) return;
+ for (var i = 0; i < level.objects.length; ++i) {
+ var name = level.objects[i].name;
+ cache.loadModel("assets/models/" + name + "/" + name + ".js",
+ objectHandler(level, new THREE.Vector3().copy(level.objects[i].position),
+ level.objects[i].angle, assets.objects[name]));
+ }
+ };
+
+ this.addItems = function(level) {
+ if (!level.items) return;
+ var pos = new THREE.Vector3();
+ for (var i = 0; i < level.items.length; ++i) {
+ var name = level.items[i].name;
+ pos.copy(level.items[i].position);
+ pos.y = 1.2;
+ cache.loadModel("assets/models/" + name + "/" + name + ".js",
+ objectHandler(level, pos, 0, assets.items[name]));
+ }
+ };
+
+ this.addMonsters = function(level) {
+ if (!level.monsters) return;
+ for (var i = 0; i < level.monsters.length; ++i) {
+ var name = level.monsters[i].name;
+ cache.loadModel("assets/monsters/" + name + "/" + name + ".js",
+ objectHandler(level, new THREE.Vector3().copy(level.monsters[i].position), 0, assets.monsters[name]));
+ }
+ };
+
+ this.getTriggerAt = function(pos) {
+ if (!this.level || !this.level.triggers) return false;
+ var triggers = this.level.triggers;
+ for (var i = 0; i < triggers.length; ++i) {
+ if (Math.abs(pos.x - triggers[i].position.x * this.level.gridSize) <= 1 &&
+ Math.abs(pos.z - triggers[i].position.z * this.level.gridSize) <= 1)
+ return triggers[i];
+ }
+ };
+
+ this.isAtExit = function(pos) {
+ return this.level &&
+ Math.abs(pos.x - this.level.exit[0] * this.level.gridSize) < 0.5 &&
+ Math.abs(pos.z - this.level.exit[1] * this.level.gridSize) < 0.5;
+ };
+
+ function processLevel(level) {
+ if (typeof(level) == "string")
+ level = JSON.parse(level);
+ if (level.map instanceof Array)
+ level.map = new Map(level.width, level.depth, level.map);
+
+ player.geometry.computeBoundingBox();
+ player.position.x = level.start[0] * level.gridSize;
+ player.position.y = 0.5 * (player.geometry.boundingBox.max.y - player.geometry.boundingBox.min.y) + 0.001;
+ player.position.z = level.start[1] * level.gridSize;
+ if (level.startAngle)
+ controls.setYAngle(level.startAngle);
+ scene.add(pl);
+ pl.setAngularFactor({ x: 0, y: 0, z: 0 });
+
+ self.generateMesh(level);
+ self.addLights(level);
+ self.addObjects(level);
+ self.addItems(level);
+ self.addMonsters(level);
+ lightManager.update(pl);
+ self.level = level;
+ self.loaded = true;
+ }
+
+ levelName = levelName || hashParams.level || "cave-test";
+ if (levelName == "rand") {
+ var gen = new MapGen();
+ processLevel(gen.generate());
+ } else if (levelName.length > 24) {
+ var json = window.atob(levelName);
+ processLevel(JSON.parse(json));
+ } else {
+ $.get("assets/levels/" + levelName + ".json", processLevel);
+ }
+
+ this.serialize = function() {
+ return JSON.stringify(this.level);
+ }
+
+}
+"use strict";
+var renderStats, physicsStats, rendererInfo;
+
+function initUI() {
+ var container = document.getElementById('container');
+
+ container.innerHTML = "";
+ container.appendChild(renderer.domElement);
+
+ renderStats = new Stats();
+ renderStats.domElement.style.position = 'absolute';
+ renderStats.domElement.style.bottom = '0px';
+ container.appendChild(renderStats.domElement);
+
+ physicsStats = new Stats();
+ physicsStats.domElement.style.position = 'absolute';
+ physicsStats.domElement.style.bottom = '0px';
+ physicsStats.domElement.style.left = '85px';
+ container.appendChild(physicsStats.domElement);
+
+ rendererInfo = document.createElement("div");
+ rendererInfo.style.position = 'absolute';
+ rendererInfo.style.bottom = '0px';
+ rendererInfo.style.left = '170px';
+ rendererInfo.style.color = '#f08';
+ rendererInfo.style.textAlign = 'left';
+ rendererInfo.style.backgroundColor = 'rgba(0, 0, 0, 0.33)';
+ container.appendChild(rendererInfo);
+
+ container.requestPointerLock = container.requestPointerLock ||
+ container.mozRequestPointerLock || container.webkitRequestPointerLock;
+
+ container.requestFullscreen = container.requestFullscreen ||
+ container.mozRequestFullscreen || container.mozRequestFullScreen || container.webkitRequestFullscreen;
+
+ $(window).resize(onWindowResize);
+ $(window).blur(pause);
+ $(window).focus(resume);
+ $("#instructions").click(function() {
+ // Firefox doesn't support fullscreenless pointer lock, so resort to this hack
+ if (/Firefox/i.test(navigator.userAgent)) {
+ var onFullscreenChange = function(event) {
+ if (document.fullscreenElement || document.mozFullscreenElement || document.mozFullScreenElement) {
+ document.removeEventListener('fullscreenchange', onFullscreenChange);
+ document.removeEventListener('mozfullscreenchange', onFullscreenChange);
+ container.requestPointerLock();
+ }
+ };
+ document.addEventListener('fullscreenchange', onFullscreenChange, false);
+ document.addEventListener('mozfullscreenchange', onFullscreenChange, false);
+ container.requestFullscreen();
+ } else {
+ container.requestPointerLock();
+ }
+ });
+
+ document.addEventListener('pointerlockchange', onPointerLockChange, false);
+ document.addEventListener('webkitpointerlockchange', onPointerLockChange, false);
+ document.addEventListener('mozpointerlockchange', onPointerLockChange, false);
+ $("#instructions").show();
+
+ // GUI controls
+ var gui = new dat.GUI();
+ gui.add(CONFIG, "showStats").onChange(updateConfig);
+ gui.add(CONFIG, "quarterMode").onChange(function() { updateConfig(); onWindowResize(); });
+ gui.add(controls, "mouseFallback");
+ gui.add(window, "editLevel");
+ var guiRenderer = gui.addFolder("Renderer options (reload required)");
+ guiRenderer.add(CONFIG, "antialias").onChange(updateConfig);
+ guiRenderer.add(CONFIG, "physicalShading").onChange(updateConfig);
+ guiRenderer.add(CONFIG, "normalMapping").onChange(updateConfig);
+ guiRenderer.add(CONFIG, "specularMapping").onChange(updateConfig);
+ guiRenderer.add(CONFIG, "particles").onChange(updateConfig);
+ guiRenderer.add(window, "reload");
+ var guiLighting = gui.addFolder("Light and shadow");
+ guiLighting.add(CONFIG, "maxLights", 0, 6).step(1).onChange(updateConfig);
+ guiLighting.add(CONFIG, "maxShadows", 0, 6).step(1).onChange(updateConfig);
+ guiLighting.add(CONFIG, "shadows").onChange(updateMaterials);
+ guiLighting.add(CONFIG, "softShadows").onChange(updateMaterials);
+ var guiTextures = gui.addFolder("Texture options");
+ guiTextures.add(CONFIG, "anisotropy", 1, renderer.getMaxAnisotropy()).step(1).onChange(updateTextures);
+ guiTextures.add(CONFIG, "linearTextureFilter").onChange(updateTextures);
+ var guiPostproc = gui.addFolder("Post-processing");
+ guiPostproc.add(CONFIG, "postprocessing").onChange(updateConfig);
+ guiPostproc.add(CONFIG, "SSAO").onChange(updateConfig);
+ guiPostproc.add(CONFIG, "FXAA").onChange(updateConfig);
+ guiPostproc.add(CONFIG, "bloom").onChange(updateConfig);
+ gui.close();
+}
+
+var messageTimer = null;
+function displayMessage(msg) {
+ if (messageTimer)
+ window.clearTimeout(messageTimer);
+ $("#message").html(msg).fadeIn(2000);
+ messageTimer = window.setTimeout(function() {
+ $("#message").fadeOut(5000);
+ messageTimer = null;
+ }, 5000);
+}
+
+function editLevel() {
+ var url = "editor/index.html#level=" + window.btoa(dungeon.serialize());
+ window.open(url, "_blank");
+}
+
+function onWindowResize() {
+ var scale = CONFIG.quarterMode ? 0.5 : 1;
+ pl.camera.aspect = window.innerWidth / window.innerHeight;
+ pl.camera.updateProjectionMatrix();
+ renderer.setSize(window.innerWidth * scale, window.innerHeight * scale);
+ colorTarget = new THREE.WebGLRenderTarget(window.innerWidth * scale, window.innerHeight * scale, renderTargetParametersRGB);
+ composer.reset(colorTarget);
+ depthTarget = new THREE.WebGLRenderTarget(window.innerWidth * scale, window.innerHeight * scale, renderTargetParametersRGBA);
+ depthPassPlugin.renderTarget = depthTarget;
+ passes.ssao.uniforms.tDepth.value = depthTarget;
+ passes.ssao.uniforms.size.value.set(window.innerWidth * scale, window.innerHeight * scale);
+ passes.fxaa.uniforms.resolution.value.set(scale/window.innerWidth, scale/window.innerHeight);
+ controls.handleResize();
+}
+
+function onPointerLockChange() {
+ if (document.pointerLockElement || document.mozPointerLockElement || document.webkitPointerLockElement) {
+ controls.pointerLockEnabled = true;
+ $("#instructions").hide();
+ } else {
+ controls.pointerLockEnabled = false;
+ $("#instructions").show();
+ }
+}
+
+function pause() {
+ controls.active = false;
+}
+
+function resume() {
+ controls.active = true;
+}
+
+function reload() {
+ updateConfig();
+ window.location.reload();
+}
+"use strict";
+var pl, controls, scene, renderer, composer;
+var renderTargetParametersRGBA, renderTargetParametersRGB;
+var colorTarget, depthTarget, depthPassPlugin;
+var lightManager, dungeon;
+var clock = new THREE.Clock();
+var cache = new Cache();
+var passes = {};
+
+function init() {
+ scene = new Physijs.Scene();
+ scene.setGravity(new THREE.Vector3(0, -10 * UNIT, 0));
+ scene.fog = new THREE.FogExp2(0x000000, 0.05);
+ scene.addEventListener('update', function() {
+ if (CONFIG.showStats) physicsStats.update();
+ });
+
+ pl = new Physijs.CapsuleMesh(
+ new THREE.CylinderGeometry(0.8 * UNIT, 0.8 * UNIT, 2 * UNIT),
+ new THREE.MeshBasicMaterial({ color: 0xff00ff }),
+ 100
+ );
+ pl.visible = false;
+ // Add pl later to the scene
+
+ pl.camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1 * UNIT, 30 * UNIT);
+
+ controls = new Controls(pl.camera, { mouse: mouseHandler });
+ controls.movementSpeed = 10 * UNIT;
+ controls.lookSpeed = 0.5;
+ controls.lookVertical = true;
+ controls.constrainVerticalLook = true;
+ controls.verticalMin = 1.1;
+ controls.verticalMax = 2.2;
+
+ renderer = new THREE.WebGLRenderer({
+ clearColor: 0x000000,
+ maxLights: CONFIG.maxLights + 2, // Player light is separate
+ antialias: CONFIG.antialias,
+ preserveDrawingBuffer: true
+ });
+ renderer.setSize(window.innerWidth, window.innerHeight);
+ renderer.shadowMapEnabled = CONFIG.shadows;
+ renderer.shadowMapSoft = CONFIG.softShadows;
+ renderer.shadowMapDebug = false;
+ renderer.gammaInput = true;
+ renderer.gammaOutput = true;
+ renderer.physicallyBasedShading = CONFIG.physicalShading;
+ renderer.autoClear = false;
+ if (!CONFIG.anisotropy) CONFIG.anisotropy = renderer.getMaxAnisotropy();
+
+ renderTargetParametersRGB = { minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBFormat };
+ renderTargetParametersRGBA = { minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBAFormat };
+ depthTarget = new THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight, renderTargetParametersRGBA);
+ colorTarget = new THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight, renderTargetParametersRGB);
+
+ // Postprocessing effects
+ passes.scene = new THREE.RenderPass(scene, pl.camera);
+ passes.ssao = new THREE.ShaderPass(THREE.ShaderExtras.ssao);
+ passes.ssao.uniforms.tDepth.value = depthTarget;
+ passes.ssao.uniforms.size.value.set(window.innerWidth, window.innerHeight);
+ passes.ssao.uniforms.cameraNear.value = pl.camera.near;
+ passes.ssao.uniforms.cameraFar.value = pl.camera.far;
+ passes.ssao.uniforms.fogNear.value = scene.fog.near;
+ passes.ssao.uniforms.fogFar.value = scene.fog.far;
+ passes.ssao.uniforms.fogEnabled.value = 0;
+ passes.ssao.uniforms.aoClamp.value = 0.4;
+ passes.ssao.uniforms.onlyAO.value = 0;
+ passes.fxaa = new THREE.ShaderPass(THREE.ShaderExtras.fxaa);
+ passes.fxaa.uniforms.resolution.value.set(1/window.innerWidth, 1/window.innerHeight);
+ passes.bloom = new THREE.BloomPass(0.5);
+ passes.adjust = new THREE.ShaderPass(THREE.ShaderExtras.hueSaturation);
+ passes.adjust.uniforms.saturation.value = 0.2;
+
+ composer = new THREE.EffectComposer(renderer, colorTarget);
+ //composer.addPass(passes.scene);
+ composer.addPass(passes.ssao);
+ composer.addPass(passes.fxaa);
+ composer.addPass(passes.bloom);
+ composer.addPass(passes.adjust);
+ composer.passes[composer.passes.length - 1].renderToScreen = true;
+
+ // Depth pass
+ depthPassPlugin = new THREE.DepthPassPlugin();
+ depthPassPlugin.renderTarget = depthTarget;
+ renderer.addPrePlugin(depthPassPlugin);
+
+ if (CONFIG.quarterMode) onWindowResize();
+
+ resetLevel();
+ updateConfig();
+ dumpInfo();
+ initUI();
+}
+
+function resetLevel(levelName) {
+ // TODO: Reloadless reset?
+ if (dungeon) {
+ if (levelName) window.location.hash = "#level=" + levelName;
+ window.location.reload(true);
+ }
+ lightManager = new LightManager({ maxLights: CONFIG.maxLights, maxShadows: CONFIG.maxShadows });
+ dungeon = new Dungeon(scene, pl, levelName);
+}
+
+function mouseHandler(button) {
+ var _vector = new THREE.Vector3(0, 0, 1);
+ var projector = new THREE.Projector();
+ projector.unprojectVector(_vector, pl.camera);
+ var ray = new THREE.Ray(pl.camera.position, _vector.subSelf(pl.camera.position).normalize());
+ var intersections = ray.intersectObjects(dungeon.objects);
+ if (intersections.length > 0) {
+ var target = intersections[0].object;
+ if (target.position.distanceToSquared(pl.position) < 9)
+ target.applyCentralImpulse(_vector.multiplyScalar(10000));
+ }
+}
+
+function animate(dt) {
+ if (!dungeon.loaded) return;
+ function getAnim(time) { return Math.abs(time - (time|0) - 0.5) * 2.0; }
+ function fract(num) { return num - (num|0); }
+ var i, v = new THREE.Vector3();
+
+ for (i = 0; i < dungeon.monsters.length; ++i) {
+ var monster = dungeon.monsters[i];
+ // Look at player
+ v.copy(pl.position);
+ v.subSelf(monster.position);
+ v.y = 0;
+ monster.mesh.lookAt(v.normalize());
+ // Move?
+ if (monster.position.distanceToSquared(pl.position) > 4) {
+ monster.mesh.updateAnimation(1000 * dt);
+ monster.setLinearVelocity(v.multiplyScalar(monster.speed * dt));
+ } else {
+ monster.setLinearVelocity(v.set(0,0,0));
+ }
+ }
+
+ // Lights
+ var timeNow = new Date().getTime();
+ for (i = 0; i < lightManager.lights.length; ++i) {
+ var light = lightManager.lights[i];
+ var anim = timeNow / (1000.0 + i);
+ light.intensity = 0.5 + 0.5 * getAnim(anim);
+ if (light.visible && light.emitter)
+ light.emitter.update(dt).render();
+ }
+ if (dungeon.exitParticles) dungeon.exitParticles.update(dt).render();
+
+ // Player light
+ pl.light.intensity = 0.5 + 0.5 * getAnim(timeNow / 1000.0);
+ pl.light.position.set(pl.position.x, pl.position.y + 0.2, pl.position.z);
+ pl.shadow.position.copy(pl.light.position);
+ pl.shadow.target.position.copy(controls.target);
+
+ // Player weapon
+ if (pl.rhand) {
+ pl.rhand.position.set(pl.position.x, pl.position.y, pl.position.z);
+ pl.rhand.rotation.copy(pl.camera.rotation);
+ pl.rhand.updateMatrix();
+ pl.rhand.rotation.y += Math.PI/3;
+ pl.rhand.rotation.z += Math.PI/2;
+ pl.rhand.translateX(0.2*UNIT);
+ pl.rhand.translateY(0.2*UNIT);
+ pl.rhand.translateZ(-0.5*UNIT);
+ }
+
+ // Trigger?
+ var trigger = dungeon.getTriggerAt(pl.position);
+ if (trigger) {
+ if (trigger.type == "message") displayMessage(trigger.message);
+ }
+
+ // Exit?
+ if (dungeon.isAtExit(pl.position))
+ resetLevel(dungeon.level.next);
+}
+
+$(document).ready(function() {
+ var v0 = new THREE.Vector3();
+ var v1 = new THREE.Vector3();
+
+ function formatRenderInfo(info) {
+ var report = [
+ "Prog:", info.memory.programs,
+ "Geom:", info.memory.geometries,
+ "Tex:", info.memory.textures,
+ "Calls:", info.render.calls,
+ "Verts:", info.render.vertices,
+ "Faces:", info.render.faces,
+ "Pts:", info.render.points
+ ];
+ return report.join(' ');
+ }
+
+ function render() {
+ requestAnimationFrame(render);
+ if (!dungeon.loaded) return;
+
+ // Player movement, controls and physics
+ var dt = clock.getDelta();
+ // Take note of the position
+ v0.set(pl.camera.position.x, 0, pl.camera.position.z);
+ // Let controls update the position
+ controls.update(dt);
+ // Get the new position
+ v1.set(pl.camera.position.x, 0, pl.camera.position.z);
+ // Subtract them to get the velocity
+ v1.subSelf(v0);
+ // Convert the velocity unit to per second
+ v1.divideScalar(dt * UNIT);
+ // We only use the planar velocity, so we preserve the old y-velocity
+ var vy = pl.getLinearVelocity().y;
+ // Set the velocity, but disallow jumping/flying, i.e. upwards velocity
+ pl.setLinearVelocity({ x: v1.x, y: vy < 0 ? vy : 0, z: v1.z });
+ // Simulate physics
+ scene.simulate();
+ // Put the camera/controls back to the real, simulated position
+ // FIXME: 0.5 below is magic number to rise camera
+ controls.object.position.set(pl.position.x, pl.position.y + 0.5, pl.position.z);
+
+ animate(dt);
+ lightManager.update(pl);
+ renderer.clear();
+ if (CONFIG.postprocessing) {
+ renderer.shadowMapEnabled = CONFIG.shadows;
+ depthPassPlugin.enabled = true;
+ renderer.render(scene, pl.camera, composer.renderTarget2, true);
+ if (CONFIG.showStats) rendererInfo.innerHTML = formatRenderInfo(renderer.info);
+ renderer.shadowMapEnabled = false;
+ depthPassPlugin.enabled = false;
+ composer.render(dt);
+ } else {
+ renderer.render(scene, pl.camera);
+ if (CONFIG.showStats) rendererInfo.innerHTML = formatRenderInfo();
+ }
+
+ if (CONFIG.showStats) renderStats.update();
+ }
+
+ init();
+ render();
+ displayMessage("Welcome.");
+});
82 build/game.min.js
@@ -0,0 +1,82 @@
+"use strict";function Cache(){this.models={},this.geometries={},this.materials={};var e=this,t=new THREE.JSONLoader(!0);t.statusDomElement.style.left="0px",t.statusDomElement.style.fontSize="1.8em",t.statusDomElement.style.width="auto",t.statusDomElement.style.color="#c00",document.body.appendChild(t.statusDomElement);var n=0;this.loadModel=function(r,i){var s=this.models[r];s?s instanceof Array?s.push(i):i(s):(this.models[r]=[i],t.statusDomElement.style.display="block",n++,t.load(r,function(i){var s=e.models
+[r];for(var o=0;o<s.length;++o)s[o](i);e.models[r]=i,n--,n==0&&(t.statusDomElement.style.display="none")}))},this.getGeometry=function(e,t){var n=this.geometries[e];return n?n:(this.geometries[e]=n=t(),n)},this.getMaterial=function(e){var t=this.materials[e];return t?t:(this.materials[e]=t=createMaterial(e),t)}}function PlaneGeometry(e,t,n,r,i,s,o,u){THREE.Geometry.call(this);var a,f,l=e/2,c=t/2,h=n||1,p=r||1,d=h+1,v=p+1,m=e/h,g=t/p,y=new THREE.Vector3,b=new THREE.Vector3,w=new THREE.Vector3;s=s||1,o=o||1,u=
+u||0,i=i||"pz";switch(i){case"nx":y.x=-1,b.z=-1,w.y=1;break;case"px":y.x=1,b.z=-1,w.y=-1;break;case"ny":y.y=-1,b.x=-1,w.z=1;break;case"py":y.y=1,b.x=-1,w.z=-1;break;case"nz":y.z=-1,b.x=1,w.y=1;break;case"pz":y.z=1,b.x=1,w.y=-1;break;default:console.error("Unknown plane direction "+i)}var E=new THREE.Vector3;for(f=0;f<v;f++)for(a=0;a<d;a++){var S=a*m-l,x=f*g-c,T=new THREE.Vector3(S*b.x,S*b.y,S*b.z);T.set(T.x+x*w.x,T.y+x*w.y,T.z+x*w.z),u&&a>0&&a<d-1&&(E.copy(y),E.multiplyScalar(-u+Math.random()*u*2),T.addSelf(
+E)),this.vertices.push(T)}for(f=0;f<p;f++)for(a=0;a<h;a++){var N=a+d*f,C=a+d*(f+1),k=a+1+d*(f+1),L=a+1+d*f,A=new THREE.Face4(N,C,k,L);A.normal.copy(y),A.vertexNormals.push(y.clone(),y.clone(),y.clone(),y.clone()),A.materialIndex=0,this.faces.push(A),this.faceVertexUvs[0].push([new THREE.UV(a/h*s,(1-f/p)*o),new THREE.UV(a/h*s,(1-(f+1)/p)*o),new THREE.UV((a+1)/h*s,(1-(f+1)/p)*o),new THREE.UV((a+1)/h*s,(1-f/p)*o)])}this.computeCentroids(),u&&this.computeVertexNormals()}function BlockGeometry(e,t,n,r,i,s,o,u,a,f
+,l){function x(e,t,n,o,u,h,p,d,v){var m,g,y,b=r||1,w=i||1,E=u/2,S=h/2,x=c.vertices.length;if(e==="x"&&t==="y"||e==="y"&&t==="x")m="z";else if(e==="x"&&t==="z"||e==="z"&&t==="x")m="y",w=s||1;else if(e==="z"&&t==="y"||e==="y"&&t==="z")m="x",b=s||1;var T=b+1,N=w+1,C=u/b,k=h/w,L=new THREE.Vector3;L[m]=p>0?1:-1,v&&(L[m]*=-1);for(y=0;y<N;y++)for(g=0;g<T;g++){var A=new THREE.Vector3;A[e]=(g*C-E)*n,A[t]=(y*k-S)*o,A[m]=p,l&&g>0&&g<T-1&&(A[m]+=-l+Math.random()*l*2),c.vertices.push(A)}for(y=0;y<w;y++)for(g=0;g<b;g++){var O=
+g+T*y,M=g+T*(y+1),_=g+1+T*(y+1),D=g+1+T*y,P=new THREE.Face4(O+x,M+x,_+x,D+x);P.normal.copy(L),P.vertexNormals.push(L.clone(),L.clone(),L.clone(),L.clone()),P.materialIndex=d,c.faces.push(P),c.faceVertexUvs[0].push([new THREE.UV(g/b*a,(1-y/w)*f),new THREE.UV(g/b*a,(1-(y+1)/w)*f),new THREE.UV((g+1)/b*a,(1-(y+1)/w)*f),new THREE.UV((g+1)/b*a,(1-y/w)*f)])}}THREE.Geometry.call(this);var c=this,h=e/2,p=t/2,d=n/2;a=a||1,f=f||1,l=l||0;var v,m,g,y,b,w;if(o!==undefined){if(o instanceof Array)this.materials=o;else{this.
+materials=[];for(var E=0;E<6;E++)this.materials.push(o)}v=0,y=1,m=2,b=3,g=4,w=5}else this.materials=[];this.sides={px:!0,nx:!0,py:!0,ny:!0,pz:!0,nz:!0};if(u)for(var S in u)this.sides[S]!==undefined&&(this.sides[S]=u[S]);this.sides.px&&x("z","y",-1,-1,n,t,h,v),this.sides.nx&&x("z","y",1,-1,n,t,-h,y),this.sides.py&&x("x","z",-1,1,e,n,p,m,!0),this.sides.ny&&x("x","z",-1,-1,e,n,-p,b,!0),this.sides.pz&&x("x","y",1,-1,e,t,d,g),this.sides.nz&&x("x","y",-1,-1,e,t,-d,w),this.computeCentroids(),this.mergeVertices(),l&&
+this.computeVertexNormals()}function