UNPKG

lance-gg

Version:

A Node.js based real-time multiplayer game server

1,685 lines (1,485 loc) 1.03 MB
class GameWorld { constructor() { this.stepCount = 0; this.objects = {}; this.playerCount = 0; this.idCount = 0; } getNewId() { let possibleId = this.idCount; while (possibleId in this.objects) possibleId++; this.idCount = possibleId + 1; return possibleId; } queryOneObject(query) { let objs = this.queryObjects(query); return objs.length > 0 ? objs[0] : null; } queryObjects(query) { let queriedObjects = []; this.forEachObject((id, object) => { let conditions = []; conditions.push(!('id' in query) || query.id !== null && object.id === query.id); conditions.push(!('playerId' in query) || query.playerId !== null && object.playerId === query.playerId); conditions.push(!('instanceType' in query) || query.instanceType !== null && object instanceof query.instanceType); if ('components' in query) { query.components.forEach(componentClass => { conditions.push(object.hasComponent(componentClass)); }); } if (conditions.every(value => value)) { queriedObjects.push(object); if (query.returnSingle) return false; } }); return queriedObjects; } queryObject(query) { return this.queryObjects(Object.assign(query, { returnSingle: true })); } addObject(object) { this.objects[object.id] = object; } removeObject(id) { delete this.objects[id]; } forEachObject(callback) { for (let id of Object.keys(this.objects)) { let returnValue = callback(id, this.objects[id]); if (returnValue === false) break; } } } function getDefaultExportFromCjs (x) { return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; } var eventEmitter = {exports: {}}; var d$1 = {exports: {}}; // ES3 safe var _undefined$1 = void 0; var is$4 = function (value) { return value !== _undefined$1 && value !== null; }; var isValue$3 = is$4; // prettier-ignore var possibleTypes = { "object": true, "function": true, "undefined": true /* document.all */ }; var is$3 = function (value) { if (!isValue$3(value)) return false; return hasOwnProperty.call(possibleTypes, typeof value); }; var isObject$1 = is$3; var is$2 = function (value) { if (!isObject$1(value)) return false; try { if (!value.constructor) return false; return value.constructor.prototype === value; } catch (error) { return false; } }; var isPrototype = is$2; var is$1 = function (value) { if (typeof value !== "function") return false; if (!hasOwnProperty.call(value, "length")) return false; try { if (typeof value.length !== "number") return false; if (typeof value.call !== "function") return false; if (typeof value.apply !== "function") return false; } catch (error) { return false; } return !isPrototype(value); }; var isFunction = is$1; var classRe = /^\s*class[\s{/}]/, functionToString = Function.prototype.toString; var is = function (value) { if (!isFunction(value)) return false; if (classRe.test(functionToString.call(value))) return false; return true; }; var isImplemented$2 = function () { var assign = Object.assign, obj; if (typeof assign !== "function") return false; obj = { foo: "raz" }; assign(obj, { bar: "dwa" }, { trzy: "trzy" }); return obj.foo + obj.bar + obj.trzy === "razdwatrzy"; }; var isImplemented$1; var hasRequiredIsImplemented; function requireIsImplemented () { if (hasRequiredIsImplemented) return isImplemented$1; hasRequiredIsImplemented = 1; isImplemented$1 = function () { try { Object.keys("primitive"); return true; } catch (e) { return false; } }; return isImplemented$1; } // eslint-disable-next-line no-empty-function var noop = function () {}; var _undefined = noop(); // Support ES3 engines var isValue$2 = function (val) { return val !== _undefined && val !== null; }; var shim$2; var hasRequiredShim$2; function requireShim$2 () { if (hasRequiredShim$2) return shim$2; hasRequiredShim$2 = 1; var isValue = isValue$2; var keys = Object.keys; shim$2 = function (object) { return keys(isValue(object) ? Object(object) : object); }; return shim$2; } var keys; var hasRequiredKeys; function requireKeys () { if (hasRequiredKeys) return keys; hasRequiredKeys = 1; keys = requireIsImplemented()() ? Object.keys : requireShim$2(); return keys; } var validValue; var hasRequiredValidValue; function requireValidValue () { if (hasRequiredValidValue) return validValue; hasRequiredValidValue = 1; var isValue = isValue$2; validValue = function (value) { if (!isValue(value)) throw new TypeError("Cannot use null or undefined"); return value; }; return validValue; } var shim$1; var hasRequiredShim$1; function requireShim$1 () { if (hasRequiredShim$1) return shim$1; hasRequiredShim$1 = 1; var keys = requireKeys() , value = requireValidValue() , max = Math.max; shim$1 = function (dest, src /*, …srcn*/) { var error, i, length = max(arguments.length, 2), assign; dest = Object(value(dest)); assign = function (key) { try { dest[key] = src[key]; } catch (e) { if (!error) error = e; } }; for (i = 1; i < length; ++i) { src = arguments[i]; keys(src).forEach(assign); } if (error !== undefined) throw error; return dest; }; return shim$1; } var assign$1 = isImplemented$2() ? Object.assign : requireShim$1(); var isValue$1 = isValue$2; var forEach = Array.prototype.forEach, create = Object.create; var process = function (src, obj) { var key; for (key in src) obj[key] = src[key]; }; // eslint-disable-next-line no-unused-vars var normalizeOptions = function (opts1 /*, …options*/) { var result = create(null); forEach.call(arguments, function (options) { if (!isValue$1(options)) return; process(Object(options), result); }); return result; }; var str = "razdwatrzy"; var isImplemented = function () { if (typeof str.contains !== "function") return false; return str.contains("dwa") === true && str.contains("foo") === false; }; var shim; var hasRequiredShim; function requireShim () { if (hasRequiredShim) return shim; hasRequiredShim = 1; var indexOf = String.prototype.indexOf; shim = function (searchString /*, position*/) { return indexOf.call(this, searchString, arguments[1]) > -1; }; return shim; } var contains$1 = isImplemented() ? String.prototype.contains : requireShim(); var isValue = is$4 , isPlainFunction = is , assign = assign$1 , normalizeOpts = normalizeOptions , contains = contains$1; var d = (d$1.exports = function (dscr, value/*, options*/) { var c, e, w, options, desc; if (arguments.length < 2 || typeof dscr !== "string") { options = value; value = dscr; dscr = null; } else { options = arguments[2]; } if (isValue(dscr)) { c = contains.call(dscr, "c"); e = contains.call(dscr, "e"); w = contains.call(dscr, "w"); } else { c = w = true; e = false; } desc = { value: value, configurable: c, enumerable: e, writable: w }; return !options ? desc : assign(normalizeOpts(options), desc); }); d.gs = function (dscr, get, set/*, options*/) { var c, e, options, desc; if (typeof dscr !== "string") { options = set; set = get; get = dscr; dscr = null; } else { options = arguments[3]; } if (!isValue(get)) { get = undefined; } else if (!isPlainFunction(get)) { options = get; get = set = undefined; } else if (!isValue(set)) { set = undefined; } else if (!isPlainFunction(set)) { options = set; set = undefined; } if (isValue(dscr)) { c = contains.call(dscr, "c"); e = contains.call(dscr, "e"); } else { c = true; e = false; } desc = { get: get, set: set, configurable: c, enumerable: e }; return !options ? desc : assign(normalizeOpts(options), desc); }; var dExports = d$1.exports; var validCallable = function (fn) { if (typeof fn !== "function") throw new TypeError(fn + " is not a function"); return fn; }; (function (module, exports) { var d = dExports , callable = validCallable , apply = Function.prototype.apply, call = Function.prototype.call , create = Object.create, defineProperty = Object.defineProperty , defineProperties = Object.defineProperties , hasOwnProperty = Object.prototype.hasOwnProperty , descriptor = { configurable: true, enumerable: false, writable: true } , on, once, off, emit, methods, descriptors, base; on = function (type, listener) { var data; callable(listener); if (!hasOwnProperty.call(this, '__ee__')) { data = descriptor.value = create(null); defineProperty(this, '__ee__', descriptor); descriptor.value = null; } else { data = this.__ee__; } if (!data[type]) data[type] = listener; else if (typeof data[type] === 'object') data[type].push(listener); else data[type] = [data[type], listener]; return this; }; once = function (type, listener) { var once, self; callable(listener); self = this; on.call(this, type, once = function () { off.call(self, type, once); apply.call(listener, this, arguments); }); once.__eeOnceListener__ = listener; return this; }; off = function (type, listener) { var data, listeners, candidate, i; callable(listener); if (!hasOwnProperty.call(this, '__ee__')) return this; data = this.__ee__; if (!data[type]) return this; listeners = data[type]; if (typeof listeners === 'object') { for (i = 0; (candidate = listeners[i]); ++i) { if ((candidate === listener) || (candidate.__eeOnceListener__ === listener)) { if (listeners.length === 2) data[type] = listeners[i ? 0 : 1]; else listeners.splice(i, 1); } } } else { if ((listeners === listener) || (listeners.__eeOnceListener__ === listener)) { delete data[type]; } } return this; }; emit = function (type) { var i, l, listener, listeners, args; if (!hasOwnProperty.call(this, '__ee__')) return; listeners = this.__ee__[type]; if (!listeners) return; if (typeof listeners === 'object') { l = arguments.length; args = new Array(l - 1); for (i = 1; i < l; ++i) args[i - 1] = arguments[i]; listeners = listeners.slice(); for (i = 0; (listener = listeners[i]); ++i) { apply.call(listener, this, args); } } else { switch (arguments.length) { case 1: call.call(listeners, this); break; case 2: call.call(listeners, this, arguments[1]); break; case 3: call.call(listeners, this, arguments[1], arguments[2]); break; default: l = arguments.length; args = new Array(l - 1); for (i = 1; i < l; ++i) { args[i - 1] = arguments[i]; } apply.call(listeners, this, args); } } }; methods = { on: on, once: once, off: off, emit: emit }; descriptors = { on: d(on), once: d(once), off: d(off), emit: d(emit) }; base = defineProperties({}, descriptors); module.exports = exports = function (o) { return (o == null) ? create(base) : defineProperties(Object(o), descriptors); }; exports.methods = methods; } (eventEmitter, eventEmitter.exports)); var eventEmitterExports = eventEmitter.exports; var EventEmitter$4 = /*@__PURE__*/getDefaultExportFromCjs(eventEmitterExports); class Timer { constructor() { this.currentTime = 0; this.isActive = false; this.idCounter = 0; this.events = {}; } play() { this.isActive = true; } tick() { let event; let eventId; if (this.isActive) { this.currentTime++; for (eventId in this.events) { event = this.events[eventId]; if (event) { if (event.type == 'repeat') { if ((this.currentTime - event.startOffset) % event.time == 0) { event.callback.apply(event.thisContext, event.args); } } if (event.type == 'single') { if ((this.currentTime - event.startOffset) % event.time == 0) { event.callback.apply(event.thisContext, event.args); event.destroy(); } } } } } } destroyEvent(eventId) { delete this.events[eventId]; } loop(time, callback) { let timerEvent = new TimerEvent(this, 'repeat', time, callback); this.events[timerEvent.id] = timerEvent; return timerEvent; } add(time, callback, thisContext, args) { let timerEvent = new TimerEvent(this, 'single', time, callback, thisContext, args); this.events[timerEvent.id] = timerEvent; return timerEvent; } destroy(id) { delete this.events[id]; } } class TimerEvent { constructor(timer, type, time, callback, thisContext = null, args = null) { this.id = ++timer.idCounter; this.timer = timer; this.type = type; this.time = time; this.callback = callback; this.startOffset = timer.currentTime; this.thisContext = thisContext; this.args = args; this.destroy = function () { this.timer.destroy(this.id); }; } } class Trace { constructor(options) { this.options = Object.assign({ traceLevel: Trace.TRACE_DEBUG }, options); this.traceBuffer = []; this.step = 'initializing'; this.error = this.trace.bind(this, Trace.TRACE_ERROR); this.warn = this.trace.bind(this, Trace.TRACE_WARN); this.info = this.trace.bind(this, Trace.TRACE_INFO); this.debug = this.trace.bind(this, Trace.TRACE_DEBUG); this.trace = this.trace.bind(this, Trace.TRACE_ALL); } static get TRACE_ALL() { return 0; } static get TRACE_DEBUG() { return 1; } static get TRACE_INFO() { return 2; } static get TRACE_WARN() { return 3; } static get TRACE_ERROR() { return 4; } static get TRACE_NONE() { return 1000; } trace(level, dataCB) { if (level < this.options.traceLevel) return; this.traceBuffer.push({ data: dataCB(), level, step: this.step, time: new Date() }); } rotate() { let buffer = this.traceBuffer; this.traceBuffer = []; return buffer; } get length() { return this.traceBuffer.length; } setStep(s) { this.step = s; } } class GameEngine { constructor(options) { const isServerSide = (typeof window === 'undefined'); const glob = isServerSide ? global : window; glob.LANCE = { gameEngine: this }; const defaultOpts = { traceLevel: Trace.TRACE_NONE, clientIDSpace: NaN }; if (!isServerSide) defaultOpts.clientIDSpace = 1000000; this.options = Object.assign(defaultOpts, options); this.playerId = NaN; let eventEmitter = EventEmitter$4(); this.on = eventEmitter.on; this.once = eventEmitter.once; this.removeListener = eventEmitter.off; this.off = eventEmitter.off; this.emit = eventEmitter.emit; this.trace = new Trace({ traceLevel: this.options.traceLevel }); } findLocalShadow(serverObj) { for (let localId of Object.keys(this.world.objects)) { if (Number(localId) < this.options.clientIDSpace) continue; let localObj = this.world.objects[localId]; if (localObj.hasOwnProperty('inputId') && localObj.inputId === serverObj.inputId) return localObj; } return null; } initWorld(worldSettings) { this.world = new GameWorld(); if (this.options.clientIDSpace) { this.world.idCount = this.options.clientIDSpace; } this.worldSettings = Object.assign({}, worldSettings); } start() { this.trace.info(() => '========== game engine started =========='); this.initWorld(); this.timer = new Timer(); this.timer.play(); this.on('postStep', (step, isReenact) => { if (!isReenact) this.timer.tick(); }); this.emit('start', { timestamp: (new Date()).getTime() }); } step(isReenact, t, dt, physicsOnly) { if (physicsOnly && dt) { if (dt) dt /= 1000; this.physicsEngine.step(dt, objectFilter); return; } if (isReenact === undefined) throw new Error('game engine does not forward argument isReenact to super class'); isReenact = Boolean(isReenact); let step = ++this.world.stepCount; let clientIDSpace = this.options.clientIDSpace; let preStepDesc = { step, isReenact, dt }; this.emit('preStep', preStepDesc); function objectFilter(o) { return !isReenact || o.id < clientIDSpace; } if (this.physicsEngine && !this.ignorePhysics) { if (dt) dt /= 1000; this.physicsEngine.step(dt, objectFilter); } this.world.forEachObject((id, o) => { if (typeof o.refreshFromPhysics === 'function') o.refreshFromPhysics(); this.trace.trace(() => `object[${id}] after ${isReenact ? 'reenact' : 'step'} : ${o.toString()}`); }); this.emit('postStep', { step, isReenact }); } addObjectToWorld(object) { if (Number(object.id) >= this.options.clientIDSpace) { let serverCopyArrived = false; this.world.forEachObject((id, o) => { if (o.hasOwnProperty('inputId') && o.inputId === object.inputId) { serverCopyArrived = true; return false; } }); if (serverCopyArrived) { this.trace.info(() => `========== shadow object NOT added ${object.toString()} ==========`); return null; } } this.world.addObject(object); if (typeof object.onAddToWorld === 'function') object.onAddToWorld(this); this.emit('objectAdded', object); this.trace.info(() => `========== object added ${object.toString()} ==========`); return object; } processInput(inputDesc, playerId, isServer) { this.trace.info(() => `game engine processing input[${inputDesc.messageIndex}] <${inputDesc.input}> from playerId ${playerId}`); } removeObjectFromWorld(objectId) { if (typeof objectId === 'object') objectId = objectId.id; let object = this.world.objects[objectId]; if (!object) { throw new Error(`Game attempted to remove a game object which doesn't (or never did) exist, id=${objectId}`); } this.trace.info(() => `========== destroying object ${object.toString()} ==========`); if (typeof object.onRemoveFromWorld === 'function') object.onRemoveFromWorld(this); this.emit('objectDestroyed', object); this.world.removeObject(objectId); } isOwnedByPlayer(object) { return (object.playerId == this.playerId); } registerClasses(serializer) { } getPlayerGameOverResult() { return null; } } class PhysicsEngine { constructor(options) { this.options = options; this.gameEngine = options.gameEngine; if (!options.gameEngine) { console.warn('Physics engine initialized without gameEngine!'); } } step(dt, objectFilter) { } } function commonjsRequire(path) { throw new Error('Could not dynamically require "' + path + '". Please configure the dynamicRequireTargets or/and ignoreDynamicRequires option of @rollup/plugin-commonjs appropriately for this require call to work.'); } var cannon = {exports: {}}; /* * Copyright (c) 2015 cannon.js Authors * * 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. */ (function (module, exports) { !function(e){module.exports=e();}(function(){return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof commonjsRequire=="function"&&commonjsRequire;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r);}return n[o].exports}var i=typeof commonjsRequire=="function"&&commonjsRequire;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){ module.exports={ "name": "cannon", "version": "0.6.2", "description": "A lightweight 3D physics engine written in JavaScript.", "homepage": "https://github.com/schteppe/cannon.js", "author": "Stefan Hedman <schteppe@gmail.com> (http://steffe.se)", "keywords": [ "cannon.js", "cannon", "physics", "engine", "3d" ], "main": "./build/cannon.js", "engines": { "node": "*" }, "repository": { "type": "git", "url": "https://github.com/schteppe/cannon.js.git" }, "bugs": { "url": "https://github.com/schteppe/cannon.js/issues" }, "licenses": [ { "type": "MIT" } ], "devDependencies": { "jshint": "latest", "uglify-js": "latest", "nodeunit": "^0.9.0", "grunt": "~0.4.0", "grunt-contrib-jshint": "~0.1.1", "grunt-contrib-nodeunit": "^0.4.1", "grunt-contrib-concat": "~0.1.3", "grunt-contrib-uglify": "^0.5.1", "grunt-browserify": "^2.1.4", "grunt-contrib-yuidoc": "^0.5.2", "browserify": "*" }, "dependencies": {} }; },{}],2:[function(_dereq_,module,exports){ // Export classes module.exports = { version : _dereq_('../package.json').version, AABB : _dereq_('./collision/AABB'), ArrayCollisionMatrix : _dereq_('./collision/ArrayCollisionMatrix'), Body : _dereq_('./objects/Body'), Box : _dereq_('./shapes/Box'), Broadphase : _dereq_('./collision/Broadphase'), Constraint : _dereq_('./constraints/Constraint'), ContactEquation : _dereq_('./equations/ContactEquation'), Narrowphase : _dereq_('./world/Narrowphase'), ConeTwistConstraint : _dereq_('./constraints/ConeTwistConstraint'), ContactMaterial : _dereq_('./material/ContactMaterial'), ConvexPolyhedron : _dereq_('./shapes/ConvexPolyhedron'), Cylinder : _dereq_('./shapes/Cylinder'), DistanceConstraint : _dereq_('./constraints/DistanceConstraint'), Equation : _dereq_('./equations/Equation'), EventTarget : _dereq_('./utils/EventTarget'), FrictionEquation : _dereq_('./equations/FrictionEquation'), GSSolver : _dereq_('./solver/GSSolver'), GridBroadphase : _dereq_('./collision/GridBroadphase'), Heightfield : _dereq_('./shapes/Heightfield'), HingeConstraint : _dereq_('./constraints/HingeConstraint'), LockConstraint : _dereq_('./constraints/LockConstraint'), Mat3 : _dereq_('./math/Mat3'), Material : _dereq_('./material/Material'), NaiveBroadphase : _dereq_('./collision/NaiveBroadphase'), ObjectCollisionMatrix : _dereq_('./collision/ObjectCollisionMatrix'), Pool : _dereq_('./utils/Pool'), Particle : _dereq_('./shapes/Particle'), Plane : _dereq_('./shapes/Plane'), PointToPointConstraint : _dereq_('./constraints/PointToPointConstraint'), Quaternion : _dereq_('./math/Quaternion'), Ray : _dereq_('./collision/Ray'), RaycastVehicle : _dereq_('./objects/RaycastVehicle'), RaycastResult : _dereq_('./collision/RaycastResult'), RigidVehicle : _dereq_('./objects/RigidVehicle'), RotationalEquation : _dereq_('./equations/RotationalEquation'), RotationalMotorEquation : _dereq_('./equations/RotationalMotorEquation'), SAPBroadphase : _dereq_('./collision/SAPBroadphase'), SPHSystem : _dereq_('./objects/SPHSystem'), Shape : _dereq_('./shapes/Shape'), Solver : _dereq_('./solver/Solver'), Sphere : _dereq_('./shapes/Sphere'), SplitSolver : _dereq_('./solver/SplitSolver'), Spring : _dereq_('./objects/Spring'), Trimesh : _dereq_('./shapes/Trimesh'), Vec3 : _dereq_('./math/Vec3'), Vec3Pool : _dereq_('./utils/Vec3Pool'), World : _dereq_('./world/World'), }; },{"../package.json":1,"./collision/AABB":3,"./collision/ArrayCollisionMatrix":4,"./collision/Broadphase":5,"./collision/GridBroadphase":6,"./collision/NaiveBroadphase":7,"./collision/ObjectCollisionMatrix":8,"./collision/Ray":9,"./collision/RaycastResult":10,"./collision/SAPBroadphase":11,"./constraints/ConeTwistConstraint":12,"./constraints/Constraint":13,"./constraints/DistanceConstraint":14,"./constraints/HingeConstraint":15,"./constraints/LockConstraint":16,"./constraints/PointToPointConstraint":17,"./equations/ContactEquation":19,"./equations/Equation":20,"./equations/FrictionEquation":21,"./equations/RotationalEquation":22,"./equations/RotationalMotorEquation":23,"./material/ContactMaterial":24,"./material/Material":25,"./math/Mat3":27,"./math/Quaternion":28,"./math/Vec3":30,"./objects/Body":31,"./objects/RaycastVehicle":32,"./objects/RigidVehicle":33,"./objects/SPHSystem":34,"./objects/Spring":35,"./shapes/Box":37,"./shapes/ConvexPolyhedron":38,"./shapes/Cylinder":39,"./shapes/Heightfield":40,"./shapes/Particle":41,"./shapes/Plane":42,"./shapes/Shape":43,"./shapes/Sphere":44,"./shapes/Trimesh":45,"./solver/GSSolver":46,"./solver/Solver":47,"./solver/SplitSolver":48,"./utils/EventTarget":49,"./utils/Pool":51,"./utils/Vec3Pool":54,"./world/Narrowphase":55,"./world/World":56}],3:[function(_dereq_,module,exports){ var Vec3 = _dereq_('../math/Vec3'); _dereq_('../utils/Utils'); module.exports = AABB; /** * Axis aligned bounding box class. * @class AABB * @constructor * @param {Object} [options] * @param {Vec3} [options.upperBound] * @param {Vec3} [options.lowerBound] */ function AABB(options){ options = options || {}; /** * The lower bound of the bounding box. * @property lowerBound * @type {Vec3} */ this.lowerBound = new Vec3(); if(options.lowerBound){ this.lowerBound.copy(options.lowerBound); } /** * The upper bound of the bounding box. * @property upperBound * @type {Vec3} */ this.upperBound = new Vec3(); if(options.upperBound){ this.upperBound.copy(options.upperBound); } } var tmp = new Vec3(); /** * Set the AABB bounds from a set of points. * @method setFromPoints * @param {Array} points An array of Vec3's. * @param {Vec3} position * @param {Quaternion} quaternion * @param {number} skinSize * @return {AABB} The self object */ AABB.prototype.setFromPoints = function(points, position, quaternion, skinSize){ var l = this.lowerBound, u = this.upperBound, q = quaternion; // Set to the first point l.copy(points[0]); if(q){ q.vmult(l, l); } u.copy(l); for(var i = 1; i<points.length; i++){ var p = points[i]; if(q){ q.vmult(p, tmp); p = tmp; } if(p.x > u.x){ u.x = p.x; } if(p.x < l.x){ l.x = p.x; } if(p.y > u.y){ u.y = p.y; } if(p.y < l.y){ l.y = p.y; } if(p.z > u.z){ u.z = p.z; } if(p.z < l.z){ l.z = p.z; } } // Add offset if (position) { position.vadd(l, l); position.vadd(u, u); } if(skinSize){ l.x -= skinSize; l.y -= skinSize; l.z -= skinSize; u.x += skinSize; u.y += skinSize; u.z += skinSize; } return this; }; /** * Copy bounds from an AABB to this AABB * @method copy * @param {AABB} aabb Source to copy from * @return {AABB} The this object, for chainability */ AABB.prototype.copy = function(aabb){ this.lowerBound.copy(aabb.lowerBound); this.upperBound.copy(aabb.upperBound); return this; }; /** * Clone an AABB * @method clone */ AABB.prototype.clone = function(){ return new AABB().copy(this); }; /** * Extend this AABB so that it covers the given AABB too. * @method extend * @param {AABB} aabb */ AABB.prototype.extend = function(aabb){ // Extend lower bound var l = aabb.lowerBound.x; if(this.lowerBound.x > l){ this.lowerBound.x = l; } // Upper var u = aabb.upperBound.x; if(this.upperBound.x < u){ this.upperBound.x = u; } // Extend lower bound var l = aabb.lowerBound.y; if(this.lowerBound.y > l){ this.lowerBound.y = l; } // Upper var u = aabb.upperBound.y; if(this.upperBound.y < u){ this.upperBound.y = u; } // Extend lower bound var l = aabb.lowerBound.z; if(this.lowerBound.z > l){ this.lowerBound.z = l; } // Upper var u = aabb.upperBound.z; if(this.upperBound.z < u){ this.upperBound.z = u; } }; /** * Returns true if the given AABB overlaps this AABB. * @method overlaps * @param {AABB} aabb * @return {Boolean} */ AABB.prototype.overlaps = function(aabb){ var l1 = this.lowerBound, u1 = this.upperBound, l2 = aabb.lowerBound, u2 = aabb.upperBound; // l2 u2 // |---------| // |--------| // l1 u1 return ((l2.x <= u1.x && u1.x <= u2.x) || (l1.x <= u2.x && u2.x <= u1.x)) && ((l2.y <= u1.y && u1.y <= u2.y) || (l1.y <= u2.y && u2.y <= u1.y)) && ((l2.z <= u1.z && u1.z <= u2.z) || (l1.z <= u2.z && u2.z <= u1.z)); }; /** * Returns true if the given AABB is fully contained in this AABB. * @method contains * @param {AABB} aabb * @return {Boolean} */ AABB.prototype.contains = function(aabb){ var l1 = this.lowerBound, u1 = this.upperBound, l2 = aabb.lowerBound, u2 = aabb.upperBound; // l2 u2 // |---------| // |---------------| // l1 u1 return ( (l1.x <= l2.x && u1.x >= u2.x) && (l1.y <= l2.y && u1.y >= u2.y) && (l1.z <= l2.z && u1.z >= u2.z) ); }; /** * @method getCorners * @param {Vec3} a * @param {Vec3} b * @param {Vec3} c * @param {Vec3} d * @param {Vec3} e * @param {Vec3} f * @param {Vec3} g * @param {Vec3} h */ AABB.prototype.getCorners = function(a, b, c, d, e, f, g, h){ var l = this.lowerBound, u = this.upperBound; a.copy(l); b.set( u.x, l.y, l.z ); c.set( u.x, u.y, l.z ); d.set( l.x, u.y, u.z ); e.set( u.x, l.y, l.z ); f.set( l.x, u.y, l.z ); g.set( l.x, l.y, u.z ); h.copy(u); }; var transformIntoFrame_corners = [ new Vec3(), new Vec3(), new Vec3(), new Vec3(), new Vec3(), new Vec3(), new Vec3(), new Vec3() ]; /** * Get the representation of an AABB in another frame. * @method toLocalFrame * @param {Transform} frame * @param {AABB} target * @return {AABB} The "target" AABB object. */ AABB.prototype.toLocalFrame = function(frame, target){ var corners = transformIntoFrame_corners; var a = corners[0]; var b = corners[1]; var c = corners[2]; var d = corners[3]; var e = corners[4]; var f = corners[5]; var g = corners[6]; var h = corners[7]; // Get corners in current frame this.getCorners(a, b, c, d, e, f, g, h); // Transform them to new local frame for(var i=0; i !== 8; i++){ var corner = corners[i]; frame.pointToLocal(corner, corner); } return target.setFromPoints(corners); }; /** * Get the representation of an AABB in the global frame. * @method toWorldFrame * @param {Transform} frame * @param {AABB} target * @return {AABB} The "target" AABB object. */ AABB.prototype.toWorldFrame = function(frame, target){ var corners = transformIntoFrame_corners; var a = corners[0]; var b = corners[1]; var c = corners[2]; var d = corners[3]; var e = corners[4]; var f = corners[5]; var g = corners[6]; var h = corners[7]; // Get corners in current frame this.getCorners(a, b, c, d, e, f, g, h); // Transform them to new local frame for(var i=0; i !== 8; i++){ var corner = corners[i]; frame.pointToWorld(corner, corner); } return target.setFromPoints(corners); }; },{"../math/Vec3":30,"../utils/Utils":53}],4:[function(_dereq_,module,exports){ module.exports = ArrayCollisionMatrix; /** * Collision "matrix". It's actually a triangular-shaped array of whether two bodies are touching this step, for reference next step * @class ArrayCollisionMatrix * @constructor */ function ArrayCollisionMatrix() { /** * The matrix storage * @property matrix * @type {Array} */ this.matrix = []; } /** * Get an element * @method get * @param {Number} i * @param {Number} j * @return {Number} */ ArrayCollisionMatrix.prototype.get = function(i, j) { i = i.index; j = j.index; if (j > i) { var temp = j; j = i; i = temp; } return this.matrix[(i*(i + 1)>>1) + j-1]; }; /** * Set an element * @method set * @param {Number} i * @param {Number} j * @param {Number} value */ ArrayCollisionMatrix.prototype.set = function(i, j, value) { i = i.index; j = j.index; if (j > i) { var temp = j; j = i; i = temp; } this.matrix[(i*(i + 1)>>1) + j-1] = value ? 1 : 0; }; /** * Sets all elements to zero * @method reset */ ArrayCollisionMatrix.prototype.reset = function() { for (var i=0, l=this.matrix.length; i!==l; i++) { this.matrix[i]=0; } }; /** * Sets the max number of objects * @method setNumObjects * @param {Number} n */ ArrayCollisionMatrix.prototype.setNumObjects = function(n) { this.matrix.length = n*(n-1)>>1; }; },{}],5:[function(_dereq_,module,exports){ var Body = _dereq_('../objects/Body'); var Vec3 = _dereq_('../math/Vec3'); var Quaternion = _dereq_('../math/Quaternion'); _dereq_('../shapes/Shape'); _dereq_('../shapes/Plane'); module.exports = Broadphase; /** * Base class for broadphase implementations * @class Broadphase * @constructor * @author schteppe */ function Broadphase(){ /** * The world to search for collisions in. * @property world * @type {World} */ this.world = null; /** * If set to true, the broadphase uses bounding boxes for intersection test, else it uses bounding spheres. * @property useBoundingBoxes * @type {Boolean} */ this.useBoundingBoxes = false; /** * Set to true if the objects in the world moved. * @property {Boolean} dirty */ this.dirty = true; } /** * Get the collision pairs from the world * @method collisionPairs * @param {World} world The world to search in * @param {Array} p1 Empty array to be filled with body objects * @param {Array} p2 Empty array to be filled with body objects */ Broadphase.prototype.collisionPairs = function(world,p1,p2){ throw new Error("collisionPairs not implemented for this BroadPhase class!"); }; /** * Check if a body pair needs to be intersection tested at all. * @method needBroadphaseCollision * @param {Body} bodyA * @param {Body} bodyB * @return {bool} */ var Broadphase_needBroadphaseCollision_STATIC_OR_KINEMATIC = Body.STATIC | Body.KINEMATIC; Broadphase.prototype.needBroadphaseCollision = function(bodyA,bodyB){ // Check collision filter masks if( (bodyA.collisionFilterGroup & bodyB.collisionFilterMask)===0 || (bodyB.collisionFilterGroup & bodyA.collisionFilterMask)===0){ return false; } // Check types if(((bodyA.type & Broadphase_needBroadphaseCollision_STATIC_OR_KINEMATIC)!==0 || bodyA.sleepState === Body.SLEEPING) && ((bodyB.type & Broadphase_needBroadphaseCollision_STATIC_OR_KINEMATIC)!==0 || bodyB.sleepState === Body.SLEEPING)) { // Both bodies are static, kinematic or sleeping. Skip. return false; } return true; }; /** * Check if the bounding volumes of two bodies intersect. * @method intersectionTest * @param {Body} bodyA * @param {Body} bodyB * @param {array} pairs1 * @param {array} pairs2 */ Broadphase.prototype.intersectionTest = function(bodyA, bodyB, pairs1, pairs2){ if(this.useBoundingBoxes){ this.doBoundingBoxBroadphase(bodyA,bodyB,pairs1,pairs2); } else { this.doBoundingSphereBroadphase(bodyA,bodyB,pairs1,pairs2); } }; /** * Check if the bounding spheres of two bodies are intersecting. * @method doBoundingSphereBroadphase * @param {Body} bodyA * @param {Body} bodyB * @param {Array} pairs1 bodyA is appended to this array if intersection * @param {Array} pairs2 bodyB is appended to this array if intersection */ var Broadphase_collisionPairs_r = new Vec3(); // Temp objects new Vec3(); new Quaternion(); new Vec3(); Broadphase.prototype.doBoundingSphereBroadphase = function(bodyA,bodyB,pairs1,pairs2){ var r = Broadphase_collisionPairs_r; bodyB.position.vsub(bodyA.position,r); var boundingRadiusSum2 = Math.pow(bodyA.boundingRadius + bodyB.boundingRadius, 2); var norm2 = r.norm2(); if(norm2 < boundingRadiusSum2){ pairs1.push(bodyA); pairs2.push(bodyB); } }; /** * Check if the bounding boxes of two bodies are intersecting. * @method doBoundingBoxBroadphase * @param {Body} bodyA * @param {Body} bodyB * @param {Array} pairs1 * @param {Array} pairs2 */ Broadphase.prototype.doBoundingBoxBroadphase = function(bodyA,bodyB,pairs1,pairs2){ if(bodyA.aabbNeedsUpdate){ bodyA.computeAABB(); } if(bodyB.aabbNeedsUpdate){ bodyB.computeAABB(); } // Check AABB / AABB if(bodyA.aabb.overlaps(bodyB.aabb)){ pairs1.push(bodyA); pairs2.push(bodyB); } }; /** * Removes duplicate pairs from the pair arrays. * @method makePairsUnique * @param {Array} pairs1 * @param {Array} pairs2 */ var Broadphase_makePairsUnique_temp = { keys:[] }, Broadphase_makePairsUnique_p1 = [], Broadphase_makePairsUnique_p2 = []; Broadphase.prototype.makePairsUnique = function(pairs1,pairs2){ var t = Broadphase_makePairsUnique_temp, p1 = Broadphase_makePairsUnique_p1, p2 = Broadphase_makePairsUnique_p2, N = pairs1.length; for(var i=0; i!==N; i++){ p1[i] = pairs1[i]; p2[i] = pairs2[i]; } pairs1.length = 0; pairs2.length = 0; for(var i=0; i!==N; i++){ var id1 = p1[i].id, id2 = p2[i].id; var key = id1 < id2 ? id1+","+id2 : id2+","+id1; t[key] = i; t.keys.push(key); } for(var i=0; i!==t.keys.length; i++){ var key = t.keys.pop(), pairIndex = t[key]; pairs1.push(p1[pairIndex]); pairs2.push(p2[pairIndex]); delete t[key]; } }; /** * To be implemented by subcasses * @method setWorld * @param {World} world */ Broadphase.prototype.setWorld = function(world){ }; /** * Check if the bounding spheres of two bodies overlap. * @method boundingSphereCheck * @param {Body} bodyA * @param {Body} bodyB * @return {boolean} */ var bsc_dist = new Vec3(); Broadphase.boundingSphereCheck = function(bodyA,bodyB){ var dist = bsc_dist; bodyA.position.vsub(bodyB.position,dist); return Math.pow(bodyA.shape.boundingSphereRadius + bodyB.shape.boundingSphereRadius,2) > dist.norm2(); }; /** * Returns all the bodies within the AABB. * @method aabbQuery * @param {World} world * @param {AABB} aabb * @param {array} result An array to store resulting bodies in. * @return {array} */ Broadphase.prototype.aabbQuery = function(world, aabb, result){ console.warn('.aabbQuery is not implemented in this Broadphase subclass.'); return []; }; },{"../math/Quaternion":28,"../math/Vec3":30,"../objects/Body":31,"../shapes/Plane":42,"../shapes/Shape":43}],6:[function(_dereq_,module,exports){ module.exports = GridBroadphase; var Broadphase = _dereq_('./Broadphase'); var Vec3 = _dereq_('../math/Vec3'); var Shape = _dereq_('../shapes/Shape'); /** * Axis aligned uniform grid broadphase. * @class GridBroadphase * @constructor * @extends Broadphase * @todo Needs support for more than just planes and spheres. * @param {Vec3} aabbMin * @param {Vec3} aabbMax * @param {Number} nx Number of boxes along x * @param {Number} ny Number of boxes along y * @param {Number} nz Number of boxes along z */ function GridBroadphase(aabbMin,aabbMax,nx,ny,nz){ Broadphase.apply(this); this.nx = nx || 10; this.ny = ny || 10; this.nz = nz || 10; this.aabbMin = aabbMin || new Vec3(100,100,100); this.aabbMax = aabbMax || new Vec3(-100,-100,-100); var nbins = this.nx * this.ny * this.nz; if (nbins <= 0) { throw "GridBroadphase: Each dimension's n must be >0"; } this.bins = []; this.binLengths = []; //Rather than continually resizing arrays (thrashing the memory), just record length and allow them to grow this.bins.length = nbins; this.binLengths.length = nbins; for (var i=0;i<nbins;i++) { this.bins[i]=[]; this.binLengths[i]=0; } } GridBroadphase.prototype = new Broadphase(); GridBroadphase.prototype.constructor = GridBroadphase; /** * Get all the collision pairs in the physics world * @method collisionPairs * @param {World} world * @param {Array} pairs1 * @param {Array} pairs2 */ var GridBroadphase_collisionPairs_d = new Vec3(); new Vec3(); GridBroadphase.prototype.collisionPairs = function(world,pairs1,pairs2){ var N = world.numObjects(), bodies = world.bodies; var max = this.aabbMax, min = this.aabbMin, nx = this.nx, ny = this.ny, nz = this.nz; var xstep = ny*nz; var ystep = nz; var zstep = 1; var xmax = max.x, ymax = max.y, zmax = max.z, xmin = min.x, ymin = min.y, zmin = min.z; var xmult = nx / (xmax-xmin), ymult = ny / (ymax-ymin), zmult = nz / (zmax-zmin); var binsizeX = (xmax - xmin) / nx, binsizeY = (ymax - ymin) / ny, binsizeZ = (zmax - zmin) / nz; var binRadius = Math.sqrt(binsizeX*binsizeX + binsizeY*binsizeY + binsizeZ*binsizeZ) * 0.5; var types = Shape.types; var SPHERE = types.SPHERE, PLANE = types.PLANE; types.BOX; types.COMPOUND; types.CONVEXPOLYHEDRON; var bins=this.bins, binLengths=this.binLengths, Nbins=this.bins.length; // Reset bins for(var i=0; i!==Nbins; i++){ binLengths[i] = 0; } var ceil = Math.ceil; var min = Math.min; var max = Math.max; function addBoxToBins(x0,y0,z0,x1,y1,z1,bi) { var xoff0 = ((x0 - xmin) * xmult)|0, yoff0 = ((y0 - ymin) * ymult)|0, zoff0 = ((z0 - zmin) * zmult)|0, xoff1 = ceil((x1 - xmin) * xmult), yoff1 = ceil((y1 - ymin) * ymult), zoff1 = ceil((z1 - zmin) * zmult); if (xoff0 < 0) { xoff0 = 0; } else if (xoff0 >= nx) { xoff0 = nx - 1; } if (yoff0 < 0) { yoff0 = 0; } else if (yoff0 >= ny) { yoff0 = ny - 1; } if (zoff0 < 0) { zoff0 = 0; } else if (zoff0 >= nz) { zoff0 = nz - 1; } if (xoff1 < 0) { xoff1 = 0; } else if (xoff1 >= nx) { xoff1 = nx - 1; } if (yoff1 < 0) { yoff1 = 0; } else if (yoff1 >= ny) { yoff1 = ny - 1; } if (zoff1 < 0) { zoff1 = 0; } else if (zoff1 >= nz) { zoff1 = nz - 1; } xoff0 *= xstep; yoff0 *= ystep; zoff0 *= zstep; xoff1 *= xstep; yoff1 *= ystep; zoff1 *= zstep; for (var xoff = xoff0; xoff <= xoff1; xoff += xstep) { for (var yoff = yoff0; yoff <= yoff1; yoff += ystep) { for (var zoff = zoff0; zoff <= zoff1; zoff += zstep) { var idx = xoff+yoff+zoff; bins[idx][binLengths[idx]++] = bi; } } } } // Put all bodies into the bins for(var i=0; i!==N; i++){ var bi = bodies[i]; var si = bi.shape; switch(si.type){ case SPHERE: // Put in bin // check if overlap with other bins var x = bi.position.x, y = bi.position.y, z = bi.position.z; var r = si.radius; addBoxToBins(x-r, y-r, z-r, x+r, y+r, z+r, bi); break; case PLANE: if(si.worldNormalNeedsUpdate){ si.computeWorldNormal(bi.quaternion); } var planeNormal = si.worldNormal; //Relative position from origin of plane object to the first bin //Incremented as we iterate through the bins var xreset = xmin + binsizeX*0.5 - bi.position.x, yreset = ymin + binsizeY*0.5 - bi.position.y, zreset = zmin + binsizeZ*0.5 - bi.position.z; var d = GridBroadphase_collisionPairs_d; d.set(xreset, yreset, zreset); for (var xi = 0, xoff = 0; xi !== nx; xi++, xoff += xstep, d.y = yreset, d.x += binsizeX) { for (var yi = 0, yoff = 0; yi !== ny; yi++, yoff += ystep, d.z = zreset, d.y += binsizeY) { for (var zi = 0, zoff = 0; zi !== nz; zi++, zoff += zstep, d.z += binsizeZ) { if (d.dot(planeNormal) < binRadius) { var idx = xoff + yoff + zoff; bins[idx][binLengths[idx]++] = bi; } } } } break; default: if (bi.aabbNeedsUpdate) { bi.computeAABB(); } addBoxToBins( bi.aabb.lowerBound.x, bi.aabb.lowerBound.y, bi.aabb.lowerBound.z, bi.aabb.upperBound.x, bi.aabb.upperBound.y, bi.aabb.upperBound.z, bi); break; } } // Check each bin for(var i=0; i!==Nbins; i++){ var binLength = binLengths[i]; //Skip bins with no potential collisions if (binLength > 1) { var bin = bins[i]; // Do N^2 broadphase inside for(var xi=0; xi!==binLength; xi++){ var bi = bin[xi]; for(var yi=0; yi!==xi; yi++){ var bj = bin[yi]; if(this.needBroadphaseCollision(bi,bj)){ this.intersectionTest(bi,bj,pairs1,pairs2); } } } } } // for (var zi = 0, zoff=0; zi < nz; zi++, zoff+= zstep) { // console.log("layer "+zi); // for (var yi = 0, yoff=0; yi < ny; yi++, yoff += ystep) { // var row = ''; // for (var xi = 0, xoff=0; xi < nx; xi++, xoff += xstep) { // var idx = xoff + yoff + zoff; // row += ' ' + binLengths[idx]; // } // console.log(row); // } // } this.makePairsUnique(pairs1,pairs2); }; },{"../math/Vec3":30,"../shapes/Shape":43,"./Broadphase":5}],7:[function(_dereq_,module,exports){ module.exports = NaiveBroadphase; var Broadphase = _dereq_('./Broadphase'); var AABB = _dereq_('./AABB'); /** * Naive broadphase implementation, used in lack of better ones. * @class NaiveBroadphase * @constructor * @description The naive broadphase looks at all possible pairs without restriction, therefore it has complexity N^2 (which is bad) * @extends Broadphase */ function NaiveBroadphase(){ Broadphase.apply(this); } NaiveBroadphase.prototype = new Broadphase(); NaiveBroadphase.prototype.constructor = NaiveBroadphase; /** * Get all the collision pairs in the physics world * @method collisionPairs * @param {World} world * @param {Array} pairs1 * @param {Array} pairs2 */ NaiveBroadphase.prototype.collisionPairs = function(world,pairs1,pairs2){