diff --git a/.gitignore b/.gitignore index 76add87..32f3d74 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,16 @@ -node_modules -dist \ No newline at end of file +dist +lib-cov +*.seed +*.log +*.csv +*.dat +*.out +*.pid +*.gz + +pids +logs +results + +npm-debug.log +node_modules/* diff --git a/LICENSE b/LICENSE index e9f46c9..2f67fdc 100644 --- a/LICENSE +++ b/LICENSE @@ -1,3 +1,4 @@ +<<<<<<< HEAD Copyright (c) 2013, Max Ogden All rights reserved. @@ -7,3 +8,27 @@ Redistributions of source code must retain the above copyright notice, this list Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of the nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +======= + +The MIT License (MIT) + +Copyright (c) 2013 Mikola Lysenko + +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. +>>>>>>> game-shell-voxel diff --git a/README.md b/README.md index 2ab27bc..c1de578 100644 --- a/README.md +++ b/README.md @@ -6,14 +6,13 @@ Learn more at http://voxeljs.com Write a voxel.js game in browser: http://voxel-creator.jit.su -hello world template repo: http://github.com/maxogden/voxel-hello-world +hello world template repo: http://github.com/deathcap/voxel-example # example ``` js -var createGame = require('voxel-engine') +var createGame = require('voxel-example') var game = createGame() -game.appendTo(document.body) ``` ### contributing @@ -214,7 +213,7 @@ emits when you move between voxels. pos has x, y, and z voxel coordinates of the emits when you move between chunks. pos has x, y, and z chunk coordinates of the chunk you just entered -### `game.on('renderChunk, function(chunk) {})` +### `game.on('renderChunk', function(chunk) {})` emits when a chunk is drawn (using the `showChunk` method). `chunk` is the full chunk object, which has the voxel data and a `.position` and `.dims` diff --git a/index.js b/index.js index ae286fb..3f2c4e9 100644 --- a/index.js +++ b/index.js @@ -1,36 +1,47 @@ +'use strict' var voxel = require('voxel') -var voxelMesh = require('voxel-mesh') var ray = require('voxel-raycast') -var texture = require('voxel-texture') -var control = require('voxel-control') -var voxelView = require('voxel-view') -var THREE = require('three') -var Stats = require('./lib/stats') +var control = require('voxel-controls') var Detector = require('./lib/detector') var inherits = require('inherits') var path = require('path') var EventEmitter = require('events').EventEmitter -if (process.browser) var interact = require('interact') -var requestAnimationFrame = require('raf') var collisions = require('collide-3d-tilemap') var aabb = require('aabb-3d') -var glMatrix = require('gl-matrix') -var vector = glMatrix.vec3 +var vector = require('gl-vec3') var SpatialEventEmitter = require('spatial-events') var regionChange = require('voxel-region-change') -var kb = require('kb-controls') -var physical = require('voxel-physical') -var pin = require('pin-it') +var physical = require('voxel-physicals') var tic = require('tic')() +var ndarray = require('ndarray') +var isndarray = require('isndarray') +var obsolete = require('obsolete') + +var createPlugins = require('voxel-plugins') +var extend = require('extend') +require('voxel-registry') +require('voxel-stitch') +require('voxel-shader') +require('voxel-mesher') +require('game-shell-fps-camera') module.exports = Game +var BUILTIN_PLUGIN_OPTS = { + 'voxel-registry': {}, + 'voxel-stitch': {}, + 'voxel-shader': {}, + 'voxel-mesher': {}, + 'game-shell-fps-camera': {}, +} + function Game(opts) { if (!(this instanceof Game)) return new Game(opts) var self = this if (!opts) opts = {} + if (opts.pluginOpts && opts.pluginOpts['voxel-engine']) opts = extend(opts, opts.pluginOpts['voxel-engine']) if (process.browser && this.notCapable(opts)) return - + // is this a client or a headless server this.isClient = Boolean( (typeof opts.isClient !== 'undefined') ? opts.isClient : process.browser ) @@ -39,12 +50,13 @@ function Game(opts) { this.setConfigurablePositions(opts) this.configureChunkLoading(opts) this.setDimensions(opts) - this.THREE = THREE + obsolete(this, 'THREE') this.vector = vector - this.glMatrix = glMatrix - this.arrayType = opts.arrayType || Uint8Array + obsolete(this, 'glMatrix', 'use your own gl-matrix, gl-vec3, or gl-vec4') + this.arrayType = opts.arrayType || {1:Uint8Array, 2:Uint16Array, 4:Uint32Array}[opts.arrayTypeSize] || Uint8Array this.cubeSize = 1 // backwards compat this.chunkSize = opts.chunkSize || 32 + this.chunkPad = opts.chunkPad || 4 // chunkDistance and removeDistance should not be set to the same thing // as it causes lag when you go back and forth on a chunk boundary @@ -55,25 +67,58 @@ function Game(opts) { this.antialias = opts.antialias this.playerHeight = opts.playerHeight || 1.62 this.meshType = opts.meshType || 'surfaceMesh' - this.mesher = opts.mesher || voxel.meshers.culled - this.materialType = opts.materialType || THREE.MeshLambertMaterial - this.materialParams = opts.materialParams || {} + + // was a 'voxel' module meshers object, now using voxel-mesher(ao-mesher) + obsolete(this, 'mesher', 'replaced by voxel-mesher') + this.items = [] this.voxels = voxel(this) - this.scene = new THREE.Scene() - this.view = opts.view || new voxelView(THREE, { - width: this.width, - height: this.height, - skyColor: this.skyColor, - antialias: this.antialias - }) - this.view.bindToScene(this.scene) - this.camera = this.view.getCamera() - if (!opts.lightsDisabled) this.addLights(this.scene) + + // was a three.js Scene instance, mainly used for scene.add(), objects, lights TODO: scene graph replacement? or can do without? + obsolete(this, 'scene') + + // hooked up three.js Scene, created three.js PerspectiveCamera, added to element + // TODO: add this.view.cameraPosition(), this.view.cameraVector()? -> [x,y,z] to game-shell-fps-camera, very useful + obsolete(this, 'view') + + // used to be a three.js PerspectiveCamera set by voxel-view; see also basic-camera but API not likely compatible (TODO: make it compatible?) + obsolete(this, 'camera') + + + + // the game-shell + if (this.isClient) /*GZ: Do not load on server, as document element is missing*/ + { + var createShell = require('gl-now') + var shellOpts = shellOpts || {} + shellOpts.clearColor = [ + (this.skyColor >> 16) / 255.0, + ((this.skyColor >> 8) & 0xff) / 255.0, + (this.skyColor & 0xff) / 255.0, + 1.0] + shellOpts.pointerLock = opts.pointerLock !== undefined ? opts.pointerLock : true + shellOpts.element = this.createContainer(opts) + var shell = createShell(shellOpts) - this.fogScale = opts.fogScale || 32 - if (!opts.fogDisabled) this.scene.fog = new THREE.Fog( this.skyColor, 0.00025, this.worldWidth() * this.fogScale ) + shell.on('gl-error', function(err) { + // normally not reached; notCapable() checks for WebGL compatibility first + document.body.appendChild(document.createTextNode('Fatal WebGL error: ' + err)) + }) + this.shell = shell + } + + // setup plugins + var plugins = createPlugins(this, {require: function(name) { + // we provide the built-in plugins ourselves; otherwise check caller's require, if any + // TODO: allow caller to override built-ins? better way to do this? + if (name in BUILTIN_PLUGIN_OPTS) { + return require(name) + } else { + return opts.require ? opts.require(name) : require(name) + } + }}) + this.collideVoxels = collisions( this.getBlock.bind(this), 1, @@ -94,42 +139,63 @@ function Game(opts) { this.chunksNeedsUpdate = {} // contains new chunks yet to be generated. Handled by game.loadPendingChunks this.pendingChunks = [] - - this.materials = texture({ - game: this, - texturePath: opts.texturePath || './textures/', - materialType: opts.materialType || THREE.MeshLambertMaterial, - materialParams: opts.materialParams || {}, - materialFlatColor: opts.materialFlatColor === true - }) - - this.materialNames = opts.materials || [['grass', 'dirt', 'grass_dirt'], 'brick', 'dirt'] + if (this.isClient) { + if (opts.exposeGlobal) window.game = window.g = this + } + + self.chunkRegion.on('change', function(newChunk) { self.removeFarChunks() }) - if (this.isClient) this.materials.load(this.materialNames) - - if (this.generateChunks) this.handleChunkGeneration() - // client side only after this point if (!this.isClient) return - - this.paused = true - this.initializeRendering(opts) - - this.showAllChunks() - setTimeout(function() { - self.asyncChunkGeneration = 'asyncChunkGeneration' in opts ? opts.asyncChunkGeneration : true - }, 2000) + // materials + if ('materials' in opts) throw new Error('opts.materials replaced with voxel-registry registerBlock()') // TODO: bridge? + + //this.paused = true // TODO: should it start paused, then unpause when pointer lock is acquired? this.initializeControls(opts) + + // setup plugins + var pluginOpts = opts.pluginOpts || {} + + for (var name in BUILTIN_PLUGIN_OPTS) { + pluginOpts[name] = pluginOpts[name] || BUILTIN_PLUGIN_OPTS[name] + } + + for (var name in pluginOpts) { + plugins.add(name, pluginOpts[name]) + } + plugins.loadAll() + + + // textures loaded, now can render chunks + this.stitcher = plugins.get('voxel-stitch') + this.stitcher.on('updatedSides', function() { + if (self.generateChunks) self.handleChunkGeneration() + self.showAllChunks() + + // TODO: fix async chunk gen, loadPendingChunks() may load 1 even if this.pendingChunks empty + setTimeout(function() { + self.asyncChunkGeneration = 'asyncChunkGeneration' in opts ? opts.asyncChunkGeneration : true + }, 2000) + }) + this.mesherPlugin = plugins.get('voxel-mesher') + + this.cameraPlugin = plugins.get('game-shell-fps-camera') // TODO: support other plugins implementing same API + + this.emit('engine-init', this); } inherits(Game, EventEmitter) +Game.prototype.toString = function() { + return 'voxel-engine' +} + // # External API Game.prototype.voxelPosition = function(gamePosition) { @@ -142,12 +208,22 @@ Game.prototype.voxelPosition = function(gamePosition) { return v } +var _position = new Array(3) Game.prototype.cameraPosition = function() { - return this.view.cameraPosition() + if (this.cameraPlugin) { + this.cameraPlugin.getPosition(_position) + } + + return _position } +var _cameraVector = vector.create() Game.prototype.cameraVector = function() { - return this.view.cameraVector() + if (this.cameraPlugin) { + this.cameraPlugin.getVector(_cameraVector) + } + + return _cameraVector } Game.prototype.makePhysical = function(target, envelope, blocksCreation) { @@ -204,6 +280,12 @@ Game.prototype.raycastVoxels = function(start, direction, maxDistance, epilson) var adjacentPosition = [0, 0, 0] var voxelPosition = this.voxelPosition(hitPosition) vector.add(adjacentPosition, voxelPosition, hitNormal) + + if (Math.abs(hitNormal[0] + hitNormal[1] + hitNormal[2]) !== 1) { + hitNormal[0] = 0 + hitNormal[1] = 1 + hitNormal[2] = 0 + } return { position: hitPosition, @@ -280,7 +362,7 @@ Game.prototype.createAdjacent = function(hit, val) { } Game.prototype.appendTo = function (element) { - this.view.appendTo(element) + // no-op; game-shell to append itself } // # Defaults/options parsing @@ -304,6 +386,7 @@ Game.prototype.defaultButtons = { , '': 'jump' , '': 'crouch' , '': 'alt' +, '': 'sprint' } // used in methods that have identity function(pos) {} @@ -320,6 +403,25 @@ Game.prototype.setConfigurablePositions = function(opts) { this.worldOrigin = wo || [0, 0, 0] } +Game.prototype.createContainer = function(opts) { + if (opts.container) return opts.container + + // based on game-shell makeDefaultContainer() + var container = document.createElement("div") + container.tabindex = 1 + container.style.position = "absolute" + container.style.left = "0px" + container.style.right = "0px" + container.style.top = "0px" + container.style.bottom = "0px" + container.style.height = "100%" + container.style.overflow = "hidden" + document.body.appendChild(container) + document.body.style.overflow = "hidden" //Prevent bounce + document.body.style.height = "100%" + return container +} + Game.prototype.setDimensions = function(opts) { if (opts.container) this.container = opts.container if (opts.container && opts.container.clientHeight) { @@ -337,11 +439,8 @@ Game.prototype.setDimensions = function(opts) { Game.prototype.notCapable = function(opts) { var self = this if( !Detector().webgl ) { - this.view = { - appendTo: function(el) { - el.appendChild(self.notCapableMessage()) - } - } + if (!this.reportedNotCapable) document.body.appendChild(self.notCapableMessage()) + this.reportedNotCapable = true // only once return true } return false @@ -358,16 +457,6 @@ Game.prototype.notCapableMessage = function() { return wrapper } -Game.prototype.onWindowResize = function() { - var width = window.innerWidth - var height = window.innerHeight - if (this.container) { - width = this.container.clientWidth - height = this.container.clientHeight - } - this.view.resizeWindow(width, height) -} - // # Physics/collision related methods Game.prototype.control = function(target) { @@ -389,9 +478,8 @@ Game.prototype.potentialCollisionSet = function() { Game.prototype.playerPosition = function() { var target = this.controls.target() - var position = target - ? target.avatar.position - : this.camera.localToWorld(this.camera.position.clone()) + if (!target) return this.cameraPosition() + var position = target.avatar.position return [position.x, position.y, position.z] } @@ -407,46 +495,26 @@ Game.prototype.playerAABB = function(position) { Game.prototype.collideTerrain = function(other, bbox, vec, resting) { var self = this - var axes = ['x', 'y', 'z'] - var vec3 = [vec.x, vec.y, vec.z] - this.collideVoxels(bbox, vec3, function hit(axis, tile, coords, dir, edge) { + this.collideVoxels(bbox, vec, function hit(axis, tile, coords, dir, edge) { if (!tile) return - if (Math.abs(vec3[axis]) < Math.abs(edge)) return - vec3[axis] = vec[axes[axis]] = edge - other.acceleration[axes[axis]] = 0 - resting[axes[axis]] = dir - other.friction[axes[(axis + 1) % 3]] = other.friction[axes[(axis + 2) % 3]] = axis === 1 ? self.friction : 1 + if (Math.abs(vec[axis]) < Math.abs(edge)) return + vec[axis] = edge + other.acceleration[axis] = 0 + resting[['x','y','z'][axis]] = dir // TODO: change to glm vec3 array? + other.friction[(axis + 1) % 3] = other.friction[(axis + 2) % 3] = axis === 1 ? self.friction : 1 return true }) } -// # Three.js specific methods - -Game.prototype.addStats = function() { - stats = new Stats() - stats.domElement.style.position = 'absolute' - stats.domElement.style.bottom = '0px' - document.body.appendChild( stats.domElement ) -} - -Game.prototype.addLights = function(scene) { - var ambientLight, directionalLight - ambientLight = new THREE.AmbientLight(0xcccccc) - scene.add(ambientLight) - var light = new THREE.DirectionalLight( 0xffffff , 1) - light.position.set( 1, 1, 0.5 ).normalize() - scene.add( light ) -} - // # Chunk related methods Game.prototype.configureChunkLoading = function(opts) { var self = this if (!opts.generateChunks) return if (!opts.generate) { - this.generate = function(x,y,z) { - return x*x+y*y+z*z <= 15*15 ? 1 : 0 // sphere world - } + this.generate = voxel.generator.Sphere + } else if (typeof opts.generate === 'string') { + this.generate = voxel.generator[opts.generate] } else { this.generate = opts.generate } @@ -486,21 +554,13 @@ Game.prototype.removeFarChunks = function(playerPosition) { if (!chunk) return var chunkPosition = chunk.position if (mesh) { - if (mesh.surfaceMesh) { - self.scene.remove(mesh.surfaceMesh) - mesh.surfaceMesh.geometry.dispose() + // dispose of the gl-vao meshes + for (var key in mesh.vertexArrayObjects) { + mesh.vertexArrayObjects[key].dispose() } - if (mesh.wireMesh) { - mesh.wireMesh.geometry.dispose() - self.scene.remove(mesh.wireMesh) - } - delete mesh.data - delete mesh.geometry - delete mesh.meshed - delete mesh.surfaceMesh - delete mesh.wireMesh } delete self.voxels.chunks[chunkIndex] + delete self.voxels.meshes[chunkIndex] self.emit('removeChunk', chunkPosition) }) self.voxels.requestMissingChunks(playerPosition) @@ -552,24 +612,46 @@ Game.prototype.showAllChunks = function() { } } -Game.prototype.showChunk = function(chunk) { +// Calculate fraction of each voxel type in chunk, for debugging +var chunkDensity = function(chunk) { + var counts = {} + var length = chunk.data.length + for (var i = 0; i < length; i += 1) { + var val = chunk.data[i] + if (!(val in counts)) counts[val] = 0 + + counts[val] += 1 + } + + var densities = {} + for (var val in counts) { + densities[val] = counts[val] / length + } + return densities +} + +Game.prototype.showChunk = function(chunk, optionalPosition) { + if (optionalPosition) chunk.position = optionalPosition + var chunkIndex = chunk.position.join('|') var bounds = this.voxels.getBounds.apply(this.voxels, chunk.position) - var scale = new THREE.Vector3(1, 1, 1) - var mesh = voxelMesh(chunk, this.mesher, scale, this.THREE) + //console.log('showChunk',chunkIndex,'density=',JSON.stringify(chunkDensity(chunk))) + + var voxelArray = isndarray(chunk) ? chunk : ndarray(chunk.voxels, chunk.dims) + var mesh = this.mesherPlugin.createVoxelMesh(this.shell.gl, voxelArray, this.stitcher.voxelSideTextureIDs, this.stitcher.voxelSideTextureSizes, chunk.position, this.chunkPad) + + if (!mesh) { + // no voxels + return null + } + this.voxels.chunks[chunkIndex] = chunk if (this.voxels.meshes[chunkIndex]) { - if (this.voxels.meshes[chunkIndex].surfaceMesh) this.scene.remove(this.voxels.meshes[chunkIndex].surfaceMesh) - if (this.voxels.meshes[chunkIndex].wireMesh) this.scene.remove(this.voxels.meshes[chunkIndex].wireMesh) + // TODO: remove mesh if exists + //if (this.voxels.meshes[chunkIndex].surfaceMesh) this.scene.remove(this.voxels.meshes[chunkIndex].surfaceMesh) + //if (this.voxels.meshes[chunkIndex].wireMesh) this.scene.remove(this.voxels.meshes[chunkIndex].wireMesh) } this.voxels.meshes[chunkIndex] = mesh - if (this.isClient) { - if (this.meshType === 'wireMesh') mesh.createWireMesh() - else mesh.createSurfaceMesh(this.materials.material) - this.materials.paint(mesh) - } - mesh.setPosition(bounds[0][0], bounds[0][1], bounds[0][2]) - mesh.addToScene(this.scene) this.emit('renderChunk', chunk) return mesh } @@ -577,20 +659,11 @@ Game.prototype.showChunk = function(chunk) { // # Debugging methods Game.prototype.addMarker = function(position) { - var geometry = new THREE.SphereGeometry( 0.1, 10, 10 ) - var material = new THREE.MeshPhongMaterial( { color: 0xffffff, shading: THREE.FlatShading } ) - var mesh = new THREE.Mesh( geometry, material ) - mesh.position.copy(position) - this.scene.add(mesh) + throw new Error('voxel-engine addMarker not yet implemented TODO: figure out how to fit this into the rendering pipeline') } Game.prototype.addAABBMarker = function(aabb, color) { - var geometry = new THREE.CubeGeometry(aabb.width(), aabb.height(), aabb.depth()) - var material = new THREE.MeshBasicMaterial({ color: color || 0xffffff, wireframe: true, transparent: true, opacity: 0.5, side: THREE.DoubleSide }) - var mesh = new THREE.Mesh(geometry, material) - mesh.position.set(aabb.x0() + aabb.width() / 2, aabb.y0() + aabb.height() / 2, aabb.z0() + aabb.depth() / 2) - this.scene.add(mesh) - return mesh + throw new Error('voxel-engine addAABBMarker not yet implemented TODO') } Game.prototype.addVoxelMarker = function(x, y, z, color) { @@ -598,26 +671,8 @@ Game.prototype.addVoxelMarker = function(x, y, z, color) { return this.addAABBMarker(bbox, color) } -Game.prototype.pin = pin - // # Misc internal methods -Game.prototype.onControlChange = function(gained, stream) { - this.paused = false - - if (!gained && !this.optout) { - this.buttons.disable() - return - } - - this.buttons.enable() - stream.pipe(this.controls.createWriteRotationStream()) -} - -Game.prototype.onControlOptOut = function() { - this.optout = true -} - Game.prototype.onFire = function(state) { this.emit('fire', this.controlling, state) } @@ -630,7 +685,7 @@ Game.prototype.tick = function(delta) { this.items[i].tick(delta) } - if (this.materials) this.materials.tick(delta) + //if (this.materials) this.materials.tick(delta) if (this.pendingChunks.length) this.loadPendingChunks() if (Object.keys(this.chunksNeedsUpdate).length > 0) this.updateDirtyChunks() @@ -639,7 +694,7 @@ Game.prototype.tick = function(delta) { this.emit('tick', delta) - if (!this.controls) return + //if (!this.controls) return // this.controls removed; still load chunks var playerPos = this.playerPosition() this.spatial.emit('position', playerPos, playerPos) } @@ -648,6 +703,7 @@ Game.prototype.render = function(delta) { this.view.render(this.scene) } +// TODO: merge with game-shell render loop? Game.prototype.initializeTimer = function(rate) { var self = this var accum = 0 @@ -682,36 +738,48 @@ Game.prototype.initializeTimer = function(rate) { } } -Game.prototype.initializeRendering = function(opts) { +// Create the buttons state object (binding => state), proxying to game-shell .wasDown(binding) +Game.prototype.proxyButtons = function() { var self = this - if (!opts.statsDisabled) self.addStats() + self.buttons = {} - window.addEventListener('resize', self.onWindowResize.bind(self), false) - - requestAnimationFrame(window).on('data', function(dt) { - self.emit('prerender', dt) - self.render(dt) - self.emit('postrender', dt) - }) - if (typeof stats !== 'undefined') { - self.on('postrender', function() { - stats.update() + Object.keys(this.shell.bindings).forEach(function(name) { + Object.defineProperty(self.buttons, name, {get: + function() { + return self.shell.pointerLock && self.shell.wasDown(name) + } }) + }) +} + +// cleanup key name - based on https://github.com/mikolalysenko/game-shell/blob/master/shell.js +var filtered_vkey = function(k) { + if(k.charAt(0) === '<' && k.charAt(k.length-1) === '>') { + k = k.substring(1, k.length-1) } + k = k.replace(/\s/g, "-") + return k } Game.prototype.initializeControls = function(opts) { - // player control - this.keybindings = opts.keybindings || this.defaultButtons - this.buttons = kb(document.body, this.keybindings) - this.buttons.disable() - this.optout = false - this.interact = interact(opts.interactElement || this.view.element, opts.interactMouseDrag) - this.interact - .on('attain', this.onControlChange.bind(this, true)) - .on('release', this.onControlChange.bind(this, false)) - .on('opt-out', this.onControlOptOut.bind(this)) + // player control - game-shell handles most controls now + + // initial keybindings passed in from options + obsolete(this, 'keybindings') + var keybindings = opts.keybindings || this.defaultButtons + for (var key in keybindings) { + var name = keybindings[key] + + // translate name for game-shell + key = filtered_vkey(key) + + this.shell.bind(name, key) + } + + obsolete(this, 'interact') + + this.proxyButtons() // sets this.buttons TODO: refresh when shell.bindings changes (bind/unbind) this.hookupControls(this.buttons, opts) } diff --git a/lib/stats.js b/lib/stats.js deleted file mode 100644 index a871df0..0000000 --- a/lib/stats.js +++ /dev/null @@ -1,145 +0,0 @@ -/** - * @author mrdoob / http://mrdoob.com/ - */ - -var Stats = function () { - - var startTime = Date.now(), prevTime = startTime; - var ms = 0, msMin = Infinity, msMax = 0; - var fps = 0, fpsMin = Infinity, fpsMax = 0; - var frames = 0, mode = 0; - - var container = document.createElement( 'div' ); - container.id = 'stats'; - container.addEventListener( 'mousedown', function ( event ) { event.preventDefault(); setMode( ++ mode % 2 ) }, false ); - container.style.cssText = 'width:80px;opacity:0.9;cursor:pointer'; - - var fpsDiv = document.createElement( 'div' ); - fpsDiv.id = 'fps'; - fpsDiv.style.cssText = 'padding:0 0 3px 3px;text-align:left;background-color:#002'; - container.appendChild( fpsDiv ); - - var fpsText = document.createElement( 'div' ); - fpsText.id = 'fpsText'; - fpsText.style.cssText = 'color:#0ff;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px'; - fpsText.innerHTML = 'FPS'; - fpsDiv.appendChild( fpsText ); - - var fpsGraph = document.createElement( 'div' ); - fpsGraph.id = 'fpsGraph'; - fpsGraph.style.cssText = 'position:relative;width:74px;height:30px;background-color:#0ff'; - fpsDiv.appendChild( fpsGraph ); - - while ( fpsGraph.children.length < 74 ) { - - var bar = document.createElement( 'span' ); - bar.style.cssText = 'width:1px;height:30px;float:left;background-color:#113'; - fpsGraph.appendChild( bar ); - - } - - var msDiv = document.createElement( 'div' ); - msDiv.id = 'ms'; - msDiv.style.cssText = 'padding:0 0 3px 3px;text-align:left;background-color:#020;display:none'; - container.appendChild( msDiv ); - - var msText = document.createElement( 'div' ); - msText.id = 'msText'; - msText.style.cssText = 'color:#0f0;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px'; - msText.innerHTML = 'MS'; - msDiv.appendChild( msText ); - - var msGraph = document.createElement( 'div' ); - msGraph.id = 'msGraph'; - msGraph.style.cssText = 'position:relative;width:74px;height:30px;background-color:#0f0'; - msDiv.appendChild( msGraph ); - - while ( msGraph.children.length < 74 ) { - - var bar = document.createElement( 'span' ); - bar.style.cssText = 'width:1px;height:30px;float:left;background-color:#131'; - msGraph.appendChild( bar ); - - } - - var setMode = function ( value ) { - - mode = value; - - switch ( mode ) { - - case 0: - fpsDiv.style.display = 'block'; - msDiv.style.display = 'none'; - break; - case 1: - fpsDiv.style.display = 'none'; - msDiv.style.display = 'block'; - break; - } - - } - - var updateGraph = function ( dom, value ) { - - var child = dom.appendChild( dom.firstChild ); - child.style.height = value + 'px'; - - } - - return { - - REVISION: 11, - - domElement: container, - - setMode: setMode, - - begin: function () { - - startTime = Date.now(); - - }, - - end: function () { - - var time = Date.now(); - - ms = time - startTime; - msMin = Math.min( msMin, ms ); - msMax = Math.max( msMax, ms ); - - msText.textContent = ms + ' MS (' + msMin + '-' + msMax + ')'; - updateGraph( msGraph, Math.min( 30, 30 - ( ms / 200 ) * 30 ) ); - - frames ++; - - if ( time > prevTime + 1000 ) { - - fps = Math.round( ( frames * 1000 ) / ( time - prevTime ) ); - fpsMin = Math.min( fpsMin, fps ); - fpsMax = Math.max( fpsMax, fps ); - - fpsText.textContent = fps + ' FPS (' + fpsMin + '-' + fpsMax + ')'; - updateGraph( fpsGraph, Math.min( 30, 30 - ( fps / 100 ) * 30 ) ); - - prevTime = time; - frames = 0; - - } - - return time; - - }, - - update: function () { - - startTime = this.end(); - - } - - } - -}; - -module.exports = Stats \ No newline at end of file diff --git a/package.json b/package.json index 678169e..8bf7a1a 100644 --- a/package.json +++ b/package.json @@ -9,32 +9,36 @@ "contributors": [ "Max Ogden (https://github.com/maxogden)", "Kumavis (https://github.com/kumavis)", - "Deathcap (https://github.com/deathcap)" + "Deathcap (https://github.com/deathcap)", + "Mikola Lysenko (https://github.com/mikolalysenko/)" ], "dependencies": { - "voxel": "0.3.1", - "voxel-mesh": "0.2.1", - "voxel-view": "0.0.6", - "voxel-raycast": "0.2.1", - "voxel-control": "0.0.7", - "voxel-texture": "0.5.6", - "voxel-physical": "0.0.10", - "voxel-region-change": "0.1.0", - "raf": "0.0.1", - "three": "0.56.0", - "pin-it": "0.0.1", - "aabb-3d": "0.0.0", - "inherits": "1.0.0", - "interact": "0.0.2", - "gl-matrix": "2.0.0", - "kb-controls": "0.0.2", - "spatial-events": "0.0.1", - "spatial-trigger": "0.0.0", - "collide-3d-tilemap": "0.0.1", - "tic": "0.2.1" + "voxel-mesher": "^0.14.0", + "voxel-shader": "^0.14.0", + "gl-now": "^1.3.1", + "ndarray": "^1.0.15", + "game-shell-fps-camera": "^0.7.0", + "voxel-stitch": "^0.10.0", + "voxel-registry": "^0.8.1", + "voxel-plugins": "^0.3.2", + "voxel": "^0.5.0", + "voxel-raycast": "^0.2.1", + "voxel-controls": "^0.1.0", + "voxel-physicals": "^0.2.0", + "voxel-region-change": "^0.1.0", + "aabb-3d": "^0.1.0", + "inherits": "^2.0.1", + "gl-vec3": "^1.0.3", + "spatial-events": "^1.1.0", + "spatial-trigger": "^0.0.0", + "collide-3d-tilemap": "^0.0.1", + "tic": "^0.2.1", + "isndarray": "^0.1.0", + "obsolete": "^0.1.0", + "extend": "^1.2.1" }, "devDependencies": { - "tape": "0.2.2" + "tape": "^2.13.2" }, "scripts": { "test": "node test.js"