UNPKG

lance-gg

Version:

A Node.js based real-time multiplayer game server

1,920 lines (1,699 loc) 896 kB
import fs$1 from 'fs'; import require$$0 from 'path'; 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 = is$3; var is$2 = function (value) { if (!isObject(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$1 = 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$1(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) { } } var p2 = {exports: {}}; var vec2$q = {exports: {}}; /* global P2_ARRAY_TYPE */ var Utils_1 = Utils$a; /** * Misc utility functions * @class Utils * @constructor */ function Utils$a(){} /** * Append the values in array b to the array a. See <a href="http://stackoverflow.com/questions/1374126/how-to-append-an-array-to-an-existing-javascript-array/1374131#1374131">this</a> for an explanation. * @method appendArray * @static * @param {Array} a * @param {Array} b */ Utils$a.appendArray = function(a,b){ if (b.length < 150000) { a.push.apply(a, b); } else { for (var i = 0, len = b.length; i !== len; ++i) { a.push(b[i]); } } }; /** * Garbage free Array.splice(). Does not allocate a new array. * @method splice * @static * @param {Array} array * @param {Number} index * @param {Number} howmany */ Utils$a.splice = function(array,index,howmany){ howmany = howmany || 1; for (var i=index, len=array.length-howmany; i < len; i++){ array[i] = array[i + howmany]; } array.length = len; }; /** * The array type to use for internal numeric computations throughout the library. Float32Array is used if it is available, but falls back on Array. If you want to set array type manually, inject it via the global variable P2_ARRAY_TYPE. See example below. * @static * @property {function} ARRAY_TYPE * @example * <script> * <!-- Inject your preferred array type before loading p2.js --> * P2_ARRAY_TYPE = Array; * </script> * <script src="p2.js"></script> */ if(typeof P2_ARRAY_TYPE !== 'undefined') { Utils$a.ARRAY_TYPE = P2_ARRAY_TYPE; } else if (typeof Float32Array !== 'undefined'){ Utils$a.ARRAY_TYPE = Float32Array; } else { Utils$a.ARRAY_TYPE = Array; } /** * Extend an object with the properties of another * @static * @method extend * @param {object} a * @param {object} b */ Utils$a.extend = function(a,b){ for(var key in b){ a[key] = b[key]; } }; /** * Extend an options object with default values. * @static * @method defaults * @param {object} options The options object. May be falsy: in this case, a new object is created and returned. * @param {object} defaults An object containing default values. * @return {object} The modified options object. */ Utils$a.defaults = function(options, defaults){ options = options || {}; for(var key in defaults){ if(!(key in options)){ options[key] = defaults[key]; } } return options; }; /* Copyright (c) 2013, Brandon Jones, Colin MacKenzie IV. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * 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. 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 vec2 object from glMatrix, with some extensions and some removed methods. See http://glmatrix.net. * @class vec2 */ var vec2$p = vec2$q.exports = {}; var Utils$9 = Utils_1; /** * Make a cross product and only return the z component * @method crossLength * @static * @param {Array} a * @param {Array} b * @return {Number} */ vec2$p.crossLength = function(a,b){ return a[0] * b[1] - a[1] * b[0]; }; /** * Cross product between a vector and the Z component of a vector * @method crossVZ * @static * @param {Array} out * @param {Array} vec * @param {Number} zcomp * @return {Number} */ vec2$p.crossVZ = function(out, vec, zcomp){ vec2$p.rotate(out,vec,-Math.PI/2);// Rotate according to the right hand rule vec2$p.scale(out,out,zcomp); // Scale with z return out; }; /** * Cross product between a vector and the Z component of a vector * @method crossZV * @static * @param {Array} out * @param {Number} zcomp * @param {Array} vec * @return {Number} */ vec2$p.crossZV = function(out, zcomp, vec){ vec2$p.rotate(out,vec,Math.PI/2); // Rotate according to the right hand rule vec2$p.scale(out,out,zcomp); // Scale with z return out; }; /** * Rotate a vector by an angle * @method rotate * @static * @param {Array} out * @param {Array} a * @param {Number} angle */ vec2$p.rotate = function(out,a,angle){ if(angle !== 0){ var c = Math.cos(angle), s = Math.sin(angle), x = a[0], y = a[1]; out[0] = c*x -s*y; out[1] = s*x +c*y; } else { out[0] = a[0]; out[1] = a[1]; } }; /** * Rotate a vector 90 degrees clockwise * @method rotate90cw * @static * @param {Array} out * @param {Array} a * @param {Number} angle */ vec2$p.rotate90cw = function(out, a) { var x = a[0]; var y = a[1]; out[0] = y; out[1] = -x; }; /** * Transform a point position to local frame. * @method toLocalFrame * @param {Array} out * @param {Array} worldPoint * @param {Array} framePosition * @param {Number} frameAngle */ vec2$p.toLocalFrame = function(out, worldPoint, framePosition, frameAngle){ vec2$p.copy(out, worldPoint); vec2$p.sub(out, out, framePosition); vec2$p.rotate(out, out, -frameAngle); }; /** * Transform a point position to global frame. * @method toGlobalFrame * @param {Array} out * @param {Array} localPoint * @param {Array} framePosition * @param {Number} frameAngle */ vec2$p.toGlobalFrame = function(out, localPoint, framePosition, frameAngle){ vec2$p.copy(out, localPoint); vec2$p.rotate(out, out, frameAngle); vec2$p.add(out, out, framePosition); }; /** * Transform a vector to local frame. * @method vectorToLocalFrame * @param {Array} out * @param {Array} worldVector * @param {Number} frameAngle */ vec2$p.vectorToLocalFrame = function(out, worldVector, frameAngle){ vec2$p.rotate(out, worldVector, -frameAngle); }; /** * Transform a point position to global frame. * @method toGlobalFrame * @param {Array} out * @param {Array} localVector * @param {Number} frameAngle */ vec2$p.vectorToGlobalFrame = function(out, localVector, frameAngle){ vec2$p.rotate(out, localVector, frameAngle); }; /** * Compute centroid of a triangle spanned by vectors a,b,c. See http://easycalculation.com/analytical/learn-centroid.php * @method centroid * @static * @param {Array} out * @param {Array} a * @param {Array} b * @param {Array} c * @return {Array} The out object */ vec2$p.centroid = function(out, a, b, c){ vec2$p.add(out, a, b); vec2$p.add(out, out, c); vec2$p.scale(out, out, 1/3); return out; }; /** * Creates a new, empty vec2 * @static * @method create * @return {Array} a new 2D vector */ vec2$p.create = function() { var out = new Utils$9.ARRAY_TYPE(2); out[0] = 0; out[1] = 0; return out; }; /** * Creates a new vec2 initialized with values from an existing vector * @static * @method clone * @param {Array} a vector to clone * @return {Array} a new 2D vector */ vec2$p.clone = function(a) { var out = new Utils$9.ARRAY_TYPE(2); out[0] = a[0]; out[1] = a[1]; return out; }; /** * Creates a new vec2 initialized with the given values * @static * @method fromValues * @param {Number} x X component * @param {Number} y Y component * @return {Array} a new 2D vector */ vec2$p.fromValues = function(x, y) { var out = new Utils$9.ARRAY_TYPE(2); out[0] = x; out[1] = y; return out; }; /** * Copy the values from one vec2 to another * @static * @method copy * @param {Array} out the receiving vector * @param {Array} a the source vector * @return {Array} out */ vec2$p.copy = function(out, a) { out[0] = a[0]; out[1] = a[1]; return out; }; /** * Set the components of a vec2 to the given values * @static * @method set * @param {Array} out the receiving vector * @param {Number} x X component * @param {Number} y Y component * @return {Array} out */ vec2$p.set = function(out, x, y) { out[0] = x; out[1] = y; return out; }; /** * Adds two vec2's * @static * @method add * @param {Array} out the receiving vector * @param {Array} a the first operand * @param {Array} b the second operand * @return {Array} out */ vec2$p.add = function(out, a, b) { out[0] = a[0] + b[0]; out[1] = a[1] + b[1]; return out; }; /** * Subtracts two vec2's * @static * @method subtract * @param {Array} out the receiving vector * @param {Array} a the first operand * @param {Array} b the second operand * @return {Array} out */ vec2$p.subtract = function(out, a, b) { out[0] = a[0] - b[0]; out[1] = a[1] - b[1]; return out; }; /** * Alias for vec2.subtract * @static * @method sub */ vec2$p.sub = vec2$p.subtract; /** * Multiplies two vec2's * @static * @method multiply * @param {Array} out the receiving vector * @param {Array} a the first operand * @param {Array} b the second operand * @return {Array} out */ vec2$p.multiply = function(out, a, b) { out[0] = a[0] * b[0]; out[1] = a[1] * b[1]; return out; }; /** * Alias for vec2.multiply * @static * @method mul */ vec2$p.mul = vec2$p.multiply; /** * Divides two vec2's * @static * @method divide * @param {Array} out the receiving vector * @param {Array} a the first operand * @param {Array} b the second operand * @return {Array} out */ vec2$p.divide = function(out, a, b) { out[0] = a[0] / b[0]; out[1] = a[1] / b[1]; return out; }; /** * Alias for vec2.divide * @static * @method div */ vec2$p.div = vec2$p.divide; /** * Scales a vec2 by a scalar number * @static * @method scale * @param {Array} out the receiving vector * @param {Array} a the vector to scale * @param {Number} b amount to scale the vector by * @return {Array} out */ vec2$p.scale = function(out, a, b) { out[0] = a[0] * b; out[1] = a[1] * b; return out; }; /** * Calculates the euclidian distance between two vec2's * @static * @method distance * @param {Array} a the first operand * @param {Array} b the second operand * @return {Number} distance between a and b */ vec2$p.distance = function(a, b) { var x = b[0] - a[0], y = b[1] - a[1]; return Math.sqrt(x*x + y*y); }; /** * Alias for vec2.distance * @static * @method dist */ vec2$p.dist = vec2$p.distance; /** * Calculates the squared euclidian distance between two vec2's * @static * @method squaredDistance * @param {Array} a the first operand * @param {Array} b the second operand * @return {Number} squared distance between a and b */ vec2$p.squaredDistance = function(a, b) { var x = b[0] - a[0], y = b[1] - a[1]; return x*x + y*y; }; /** * Alias for vec2.squaredDistance * @static * @method sqrDist */ vec2$p.sqrDist = vec2$p.squaredDistance; /** * Calculates the length of a vec2 * @static * @method length * @param {Array} a vector to calculate length of * @return {Number} length of a */ vec2$p.length = function (a) { var x = a[0], y = a[1]; return Math.sqrt(x*x + y*y); }; /** * Alias for vec2.length * @method len * @static */ vec2$p.len = vec2$p.length; /** * Calculates the squared length of a vec2 * @static * @method squaredLength * @param {Array} a vector to calculate squared length of * @return {Number} squared length of a */ vec2$p.squaredLength = function (a) { var x = a[0], y = a[1]; return x*x + y*y; }; /** * Alias for vec2.squaredLength * @static * @method sqrLen */ vec2$p.sqrLen = vec2$p.squaredLength; /** * Negates the components of a vec2 * @static * @method negate * @param {Array} out the receiving vector * @param {Array} a vector to negate * @return {Array} out */ vec2$p.negate = function(out, a) { out[0] = -a[0]; out[1] = -a[1]; return out; }; /** * Normalize a vec2 * @static * @method normalize * @param {Array} out the receiving vector * @param {Array} a vector to normalize * @return {Array} out */ vec2$p.normalize = function(out, a) { var x = a[0], y = a[1]; var len = x*x + y*y; if (len > 0) { //TODO: evaluate use of glm_invsqrt here? len = 1 / Math.sqrt(len); out[0] = a[0] * len; out[1] = a[1] * len; } return out; }; /** * Calculates the dot product of two vec2's * @static * @method dot * @param {Array} a the first operand * @param {Array} b the second operand * @return {Number} dot product of a and b */ vec2$p.dot = function (a, b) { return a[0] * b[0] + a[1] * b[1]; }; /** * Returns a string representation of a vector * @static * @method str * @param {Array} vec vector to represent as a string * @return {String} string representation of the vector */ vec2$p.str = function (a) { return 'vec2(' + a[0] + ', ' + a[1] + ')'; }; /** * Linearly interpolate/mix two vectors. * @static * @method lerp * @param {Array} out * @param {Array} a First vector * @param {Array} b Second vector * @param {number} t Lerp factor */ vec2$p.lerp = function (out, a, b, t) { var ax = a[0], ay = a[1]; out[0] = ax + t * (b[0] - ax); out[1] = ay + t * (b[1] - ay); return out; }; /** * Reflect a vector along a normal. * @static * @method reflect * @param {Array} out * @param {Array} vector * @param {Array} normal */ vec2$p.reflect = function(out, vector, normal){ var dot = vector[0] * normal[0] + vector[1] * normal[1]; out[0] = vector[0] - 2 * normal[0] * dot; out[1] = vector[1] - 2 * normal[1] * dot; }; /** * Get the intersection point between two line segments. * @static * @method getLineSegmentsIntersection * @param {Array} out * @param {Array} p0 * @param {Array} p1 * @param {Array} p2 * @param {Array} p3 * @return {boolean} True if there was an intersection, otherwise false. */ vec2$p.getLineSegmentsIntersection = function(out, p0, p1, p2, p3) { var t = vec2$p.getLineSegmentsIntersectionFraction(p0, p1, p2, p3); if(t < 0){ return false; } else { out[0] = p0[0] + (t * (p1[0] - p0[0])); out[1] = p0[1] + (t * (p1[1] - p0[1])); return true; } }; /** * Get the intersection fraction between two line segments. If successful, the intersection is at p0 + t * (p1 - p0) * @static * @method getLineSegmentsIntersectionFraction * @param {Array} p0 * @param {Array} p1 * @param {Array} p2 * @param {Array} p3 * @return {number} A number between 0 and 1 if there was an intersection, otherwise -1. */ vec2$p.getLineSegmentsIntersectionFraction = function(p0, p1, p2, p3) { var s1_x = p1[0] - p0[0]; var s1_y = p1[1] - p0[1]; var s2_x = p3[0] - p2[0]; var s2_y = p3[1] - p2[1]; var s, t; s = (-s1_y * (p0[0] - p2[0]) + s1_x * (p0[1] - p2[1])) / (-s2_x * s1_y + s1_x * s2_y); t = ( s2_x * (p0[1] - p2[1]) - s2_y * (p0[0] - p2[0])) / (-s2_x * s1_y + s1_x * s2_y); if (s >= 0 && s <= 1 && t >= 0 && t <= 1) { // Collision detected return t; } return -1; // No collision }; var vec2Exports = vec2$q.exports; var vec2$o = vec2Exports ; var AABB_1 = AABB$2; /** * Axis aligned bounding box class. * @class AABB * @constructor * @param {Object} [options] * @param {Array} [options.upperBound] * @param {Array} [options.lowerBound] */ function AABB$2(options){ /** * The lower bound of the bounding box. * @property lowerBound * @type {Array} */ this.lowerBound = vec2$o.create(); if(options && options.lowerBound){ vec2$o.copy(this.lowerBound, options.lowerBound); } /** * The upper bound of the bounding box. * @property upperBound * @type {Array} */ this.upperBound = vec2$o.create(); if(options && options.upperBound){ vec2$o.copy(this.upperBound, options.upperBound); } } var tmp$2 = vec2$o.create(); /** * Set the AABB bounds from a set of points, transformed by the given position and angle. * @method setFromPoints * @param {Array} points An array of vec2's. * @param {Array} position * @param {number} angle * @param {number} skinSize Some margin to be added to the AABB. */ AABB$2.prototype.setFromPoints = function(points, position, angle, skinSize){ var l = this.lowerBound, u = this.upperBound; if(typeof(angle) !== "number"){ angle = 0; } // Set to the first point if(angle !== 0){ vec2$o.rotate(l, points[0], angle); } else { vec2$o.copy(l, points[0]); } vec2$o.copy(u, l); // Compute cosines and sines just once var cosAngle = Math.cos(angle), sinAngle = Math.sin(angle); for(var i = 1; i<points.length; i++){ var p = points[i]; if(angle !== 0){ var x = p[0], y = p[1]; tmp$2[0] = cosAngle * x -sinAngle * y; tmp$2[1] = sinAngle * x +cosAngle * y; p = tmp$2; } for(var j=0; j<2; j++){ if(p[j] > u[j]){ u[j] = p[j]; } if(p[j] < l[j]){ l[j] = p[j]; } } } // Add offset if(position){ vec2$o.add(this.lowerBound, this.lowerBound, position); vec2$o.add(this.upperBound, this.upperBound, position); } if(skinSize){ this.lowerBound[0] -= skinSize; this.lowerBound[1] -= skinSize; this.upperBound[0] += skinSize; this.upperBound[1] += skinSize; } }; /** * Copy bounds from an AABB to this AABB * @method copy * @param {AABB} aabb */ AABB$2.prototype.copy = function(aabb){ vec2$o.copy(this.lowerBound, aabb.lowerBound); vec2$o.copy(this.upperBound, aabb.upperBound); }; /** * Extend this AABB so that it covers the given AABB too. * @method extend * @param {AABB} aabb */ AABB$2.prototype.extend = function(aabb){ // Loop over x and y var i = 2; while(i--){ // Extend lower bound var l = aabb.lowerBound[i]; if(this.lowerBound[i] > l){ this.lowerBound[i] = l; } // Upper var u = aabb.upperBound[i]; if(this.upperBound[i] < u){ this.upperBound[i] = u; } } }; /** * Returns true if the given AABB overlaps this AABB. * @method overlaps * @param {AABB} aabb * @return {Boolean} */ AABB$2.prototype.overlaps = function(aabb){ var l1 = this.lowerBound, u1 = this.upperBound, l2 = aabb.lowerBound, u2 = aabb.upperBound; // l2 u2 // |---------| // |--------| // l1 u1 return ((l2[0] <= u1[0] && u1[0] <= u2[0]) || (l1[0] <= u2[0] && u2[0] <= u1[0])) && ((l2[1] <= u1[1] && u1[1] <= u2[1]) || (l1[1] <= u2[1] && u2[1] <= u1[1])); }; /** * @method containsPoint * @param {Array} point * @return {boolean} */ AABB$2.prototype.containsPoint = function(point){ var l = this.lowerBound, u = this.upperBound; return l[0] <= point[0] && point[0] <= u[0] && l[1] <= point[1] && point[1] <= u[1]; }; /** * Check if the AABB is hit by a ray. * @method overlapsRay * @param {Ray} ray * @return {number} -1 if no hit, a number between 0 and 1 if hit. */ AABB$2.prototype.overlapsRay = function(ray){ // ray.direction is unit direction vector of ray var dirFracX = 1 / ray.direction[0]; var dirFracY = 1 / ray.direction[1]; // this.lowerBound is the corner of AABB with minimal coordinates - left bottom, rt is maximal corner var t1 = (this.lowerBound[0] - ray.from[0]) * dirFracX; var t2 = (this.upperBound[0] - ray.from[0]) * dirFracX; var t3 = (this.lowerBound[1] - ray.from[1]) * dirFracY; var t4 = (this.upperBound[1] - ray.from[1]) * dirFracY; var tmin = Math.max(Math.max(Math.min(t1, t2), Math.min(t3, t4))); var tmax = Math.min(Math.min(Math.max(t1, t2), Math.max(t3, t4))); // if tmax < 0, ray (line) is intersecting AABB, but whole AABB is behing us if (tmax < 0){ //t = tmax; return -1; } // if tmin > tmax, ray doesn't intersect AABB if (tmin > tmax){ //t = tmax; return -1; } return tmin; }; var Scalar_1 = Scalar$2; /** * Scalar functions * @class Scalar */ function Scalar$2(){} /** * Check if two scalars are equal * @static * @method eq * @param {Number} a * @param {Number} b * @param {Number} [precision] * @return {Boolean} */ Scalar$2.eq = function(a,b,precision){ precision = precision || 0; return Math.abs(a-b) < precision; }; var Scalar$1 = Scalar_1; var Line_1$1 = Line$2; /** * Container for line-related functions * @class Line */ function Line$2(){} /** * Compute the intersection between two lines. * @static * @method lineInt * @param {Array} l1 Line vector 1 * @param {Array} l2 Line vector 2 * @param {Number} precision Precision to use when checking if the lines are parallel * @return {Array} The intersection point. */ Line$2.lineInt = function(l1,l2,precision){ precision = precision || 0; var i = [0,0]; // point var a1, b1, c1, a2, b2, c2, det; // scalars a1 = l1[1][1] - l1[0][1]; b1 = l1[0][0] - l1[1][0]; c1 = a1 * l1[0][0] + b1 * l1[0][1]; a2 = l2[1][1] - l2[0][1]; b2 = l2[0][0] - l2[1][0]; c2 = a2 * l2[0][0] + b2 * l2[0][1]; det = a1 * b2 - a2*b1; if (!Scalar$1.eq(det, 0, precision)) { // lines are not parallel i[0] = (b2 * c1 - b1 * c2) / det; i[1] = (a1 * c2 - a2 * c1) / det; } return i; }; /** * Checks if two line segments intersects. * @method segmentsIntersect * @param {Array} p1 The start vertex of the first line segment. * @param {Array} p2 The end vertex of the first line segment. * @param {Array} q1 The start vertex of the second line segment. * @param {Array} q2 The end vertex of the second line segment. * @return {Boolean} True if the two line segments intersect */ Line$2.segmentsIntersect = function(p1, p2, q1, q2){ var dx = p2[0] - p1[0]; var dy = p2[1] - p1[1]; var da = q2[0] - q1[0]; var db = q2[1] - q1[1]; // segments are parallel if(da*dy - db*dx == 0) return false; var s = (dx * (q1[1] - p1[1]) + dy * (p1[0] - q1[0])) / (da * dy - db * dx); var t = (da * (p1[1] - q1[1]) + db * (q1[0] - p1[0])) / (db * dx - da * dy); return (s>=0 && s<=1 && t>=0 && t<=1); }; var Point_1 = Point$1; /** * Point related functions * @class Point */ function Point$1(){} /** * Get the area of a triangle spanned by the three given points. Note that the area will be negative if the points are not given in counter-clockwise order. * @static * @method area * @param {Array} a * @param {Array} b * @param {Array} c * @return {Number} */ Point$1.area = function(a,b,c){ return (((b[0] - a[0])*(c[1] - a[1]))-((c[0] - a[0])*(b[1] - a[1]))); }; Point$1.left = function(a,b,c){ return Point$1.area(a,b,c) > 0; }; Point$1.leftOn = function(a,b,c) { return Point$1.area(a, b, c) >= 0; }; Point$1.right = function(a,b,c) { return Point$1.area(a, b, c) < 0; }; Point$1.rightOn = function(a,b,c) { return Point$1.area(a, b, c) <= 0; }; var tmpPoint1 = [], tmpPoint2 = []; /** * Check if three points are collinear * @method collinear * @param {Array} a * @param {Array} b * @param {Array} c * @param {Number} [thresholdAngle=0] Threshold angle to use when comparing the vectors. The function will return true if the angle between the resulting vectors is less than this value. Use zero for max precision. * @return {Boolean} */ Point$1.collinear = function(a,b,c,thresholdAngle) { if(!thresholdAngle) return Point$1.area(a, b, c) == 0; else { var ab = tmpPoint1, bc = tmpPoint2; ab[0] = b[0]-a[0]; ab[1] = b[1]-a[1]; bc[0] = c[0]-b[0]; bc[1] = c[1]-b[1]; var dot = ab[0]*bc[0] + ab[1]*bc[1], magA = Math.sqrt(ab[0]*ab[0] + ab[1]*ab[1]), magB = Math.sqrt(bc[0]*bc[0] + bc[1]*bc[1]), angle = Math.acos(dot/(magA*magB)); return angle < thresholdAngle; } }; Point$1.sqdist = function(a,b){ var dx = b[0] - a[0]; var dy = b[1] - a[1]; return dx * dx + dy * dy; }; var Line$1 = Line_1$1 , Point = Point_1 , Scalar = Scalar_1; var Polygon_1 = Polygon; /** * Polygon class. * @class Polygon * @constructor */ function Polygon(){ /** * Vertices that this polygon consists of. An array of array of numbers, example: [[0,0],[1,0],..] * @property vertices * @type {Array} */ this.vertices = []; } /** * Get a vertex at position i. It does not matter if i is out of bounds, this function will just cycle. * @method at * @param {Number} i * @return {Array} */ Polygon.prototype.at = function(i){ var v = this.vertices, s = v.length; return v[i < 0 ? i % s + s : i % s]; }; /** * Get first vertex * @method first * @return {Array} */ Polygon.prototype.first = function(){ return this.vertices[0]; }; /** * Get last vertex * @method last * @return {Array} */ Polygon.prototype.last = function(){ return this.vertices[this.vertices.length-1]; }; /** * Clear the polygon data * @method clear * @return {Array} */ Polygon.prototype.clear = function(){ this.vertices.length = 0; }; /** * Append points "from" to "to"-1 from an other polygon "poly" onto this one. * @method append * @param {Polygon} poly The polygon to get points from. * @param {Number} from The vertex index in "poly". * @param {Number} to The end vertex index in "poly". Note that this vertex is NOT included when appending. * @return {Array} */ Polygon.prototype.append = function(poly,from,to){ if(typeof(from) == "undefined") throw new Error("From is not given!"); if(typeof(to) == "undefined") throw new Error("To is not given!"); if(to-1 < from) throw new Error("lol1"); if(to > poly.vertices.length) throw new Error("lol2"); if(from < 0) throw new Error("lol3"); for(var i=from; i<to; i++){ this.vertices.push(poly.vertices[i]); } }; /** * Make sure that the polygon vertices are ordered counter-clockwise. * @method makeCCW */ Polygon.prototype.makeCCW = function(){ var br = 0, v = this.vertices; // find bottom right point for (var i = 1; i < this.vertices.length; ++i) { if (v[i][1] < v[br][1] || (v[i][1] == v[br][1] && v[i][0] > v[br][0])) { br = i; } } // reverse poly if clockwise if (!Point.left(this.at(br - 1), this.at(br), this.at(br + 1))) { this.reverse(); } }; /** * Reverse the vertices in the polygon * @method reverse */ Polygon.prototype.reverse = function(){ var tmp = []; for(var i=0, N=this.vertices.length; i!==N; i++){ tmp.push(this.vertices.pop()); } this.vertices = tmp; }; /** * Check if a point in the polygon is a reflex point * @method isReflex * @param {Number} i * @return {Boolean} */ Polygon.prototype.isReflex = function(i){ return Point.right(this.at(i - 1), this.at(i), this.at(i + 1)); }; var tmpLine1=[], tmpLine2=[]; /** * Check if two vertices in the polygon can see each other * @method canSee * @param {Number} a Vertex index 1 * @param {Number} b Vertex index 2 * @return {Boolean} */ Polygon.prototype.canSee = function(a,b) { var p, dist, l1=tmpLine1, l2=tmpLine2; if (Point.leftOn(this.at(a + 1), this.at(a), this.at(b)) && Point.rightOn(this.at(a - 1), this.at(a), this.at(b))) { return false; } dist = Point.sqdist(this.at(a), this.at(b)); for (var i = 0; i !== this.vertices.length; ++i) { // for each edge if ((i + 1) % this.vertices.length === a || i === a) // ignore incident edges continue; if (Point.leftOn(this.at(a), this.at(b), this.at(i + 1)) && Point.rightOn(this.at(a), this.at(b), this.at(i))) { // if diag intersects an edge l1[0] = this.at(a); l1[1] = this.at(b); l2[0] = this.at(i); l2[1] = this.at(i + 1); p = Line$1.lineInt(l1,l2); if (Point.sqdist(this.at(a), p) < dist) { // if edge is blocking visibility to b return false; } } } return true; }; /** * Copy the polygon from vertex i to vertex j. * @method copy * @param {Number} i * @param {Number} j * @param {Polygon} [targetPoly] Optional target polygon to save in. * @return {Polygon} The resulting copy. */ Polygon.prototype.copy = function(i,j,targetPoly){ var p = targetPoly || new Polygon(); p.clear(); if (i < j) { // Insert all vertices from i to j for(var k=i; k<=j; k++) p.vertices.push(this.vertices[k]); } else { // Insert vertices 0 to j for(var k=0; k<=j; k++) p.vertices.push(this.vertices[k]); // Insert vertices i to end for(var k=i; k<this.vertices.length; k++) p.vertices.pu