quadtree2
Version:
JavaScript implementation of quadtree datastructure for collision detection.
397 lines (335 loc) • 9.78 kB
JavaScript
;(function inject(clean, precision, undef) {
function Vec2(x, y) {
if (!(this instanceof Vec2)) {
return new Vec2(x, y);
}
if('object' === typeof x && x) {
this.y = x.y || 0;
this.x = x.x || 0;
return;
}
this.x = Vec2.clean(x || 0);
this.y = Vec2.clean(y || 0);
}
Vec2.prototype = {
change : function(fn) {
if (fn) {
if (this.observers) {
this.observers.push(fn);
} else {
this.observers = [fn];
}
} else if (this.observers) {
for (var i=this.observers.length-1; i>=0; i--) {
this.observers[i](this);
}
}
return this;
},
ignore : function(fn) {
if (this.observers) {
var o = this.observers, l = o.length;
while(l--) {
o[l] === fn && o.splice(l, 1);
}
}
return this;
},
// set x and y
set: function(x, y, silent) {
if('number' != typeof x) {
silent = y;
y = x.y;
x = x.x;
}
if(this.x === x && this.y === y)
return this;
this.x = Vec2.clean(x);
this.y = Vec2.clean(y);
if(silent !== false)
return this.change();
},
// reset x and y to zero
zero : function() {
return this.set(0, 0);
},
// return a new vector with the same component values
// as this one
clone : function() {
return new Vec2(this.x, this.y);
},
// negate the values of this vector
negate : function(returnNew) {
if (returnNew) {
return new Vec2(-this.x, -this.y);
} else {
return this.set(-this.x, -this.y);
}
},
// Add the incoming `vec2` vector to this vector
add : function(vec2, returnNew) {
if (!returnNew) {
this.x += vec2.x; this.y += vec2.y;
return this.change();
} else {
// Return a new vector if `returnNew` is truthy
return new Vec2(
this.x + vec2.x,
this.y + vec2.y
);
}
},
// Subtract the incoming `vec2` from this vector
subtract : function(vec2, returnNew) {
if (!returnNew) {
this.x -= vec2.x; this.y -= vec2.y;
return this.change();
} else {
// Return a new vector if `returnNew` is truthy
return new Vec2(
this.x - vec2.x,
this.y - vec2.y
);
}
},
// Multiply this vector by the incoming `vec2`
multiply : function(vec2, returnNew) {
var x,y;
if ('number' !== typeof vec2) { //.x !== undef) {
x = vec2.x;
y = vec2.y;
// Handle incoming scalars
} else {
x = y = vec2;
}
if (!returnNew) {
return this.set(this.x * x, this.y * y);
} else {
return new Vec2(
this.x * x,
this.y * y
);
}
},
// Rotate this vector. Accepts a `Rotation` or angle in radians.
//
// Passing a truthy `inverse` will cause the rotation to
// be reversed.
//
// If `returnNew` is truthy, a new
// `Vec2` will be created with the values resulting from
// the rotation. Otherwise the rotation will be applied
// to this vector directly, and this vector will be returned.
rotate : function(r, inverse, returnNew) {
var
x = this.x,
y = this.y,
cos = Math.cos(r),
sin = Math.sin(r),
rx, ry;
inverse = (inverse) ? -1 : 1;
rx = cos * x - (inverse * sin) * y;
ry = (inverse * sin) * x + cos * y;
if (returnNew) {
return new Vec2(rx, ry);
} else {
return this.set(rx, ry);
}
},
// Calculate the length of this vector
length : function() {
var x = this.x, y = this.y;
return Math.sqrt(x * x + y * y);
},
// Get the length squared. For performance, use this instead of `Vec2#length` (if possible).
lengthSquared : function() {
var x = this.x, y = this.y;
return x*x+y*y;
},
// Return the distance betwen this `Vec2` and the incoming vec2 vector
// and return a scalar
distance : function(vec2) {
var x = this.x - vec2.x;
var y = this.y - vec2.y;
return Math.sqrt(x*x + y*y);
},
// Convert this vector into a unit vector.
// Returns the length.
normalize : function(returnNew) {
var length = this.length();
// Collect a ratio to shrink the x and y coords
var invertedLength = (length < Number.MIN_VALUE) ? 0 : 1/length;
if (!returnNew) {
// Convert the coords to be greater than zero
// but smaller than or equal to 1.0
return this.set(this.x * invertedLength, this.y * invertedLength);
} else {
return new Vec2(this.x * invertedLength, this.y * invertedLength);
}
},
// Determine if another `Vec2`'s components match this one's
// also accepts 2 scalars
equal : function(v, w) {
if (w === undef) {
w = v.y;
v = v.x;
}
return (Vec2.clean(v) === this.x && Vec2.clean(w) === this.y);
},
// Return a new `Vec2` that contains the absolute value of
// each of this vector's parts
abs : function(returnNew) {
var x = Math.abs(this.x), y = Math.abs(this.y);
if (returnNew) {
return new Vec2(x, y);
} else {
return this.set(x, y);
}
},
// Return a new `Vec2` consisting of the smallest values
// from this vector and the incoming
//
// When returnNew is truthy, a new `Vec2` will be returned
// otherwise the minimum values in either this or `v` will
// be applied to this vector.
min : function(v, returnNew) {
var
tx = this.x,
ty = this.y,
vx = v.x,
vy = v.y,
x = tx < vx ? tx : vx,
y = ty < vy ? ty : vy;
if (returnNew) {
return new Vec2(x, y);
} else {
return this.set(x, y);
}
},
// Return a new `Vec2` consisting of the largest values
// from this vector and the incoming
//
// When returnNew is truthy, a new `Vec2` will be returned
// otherwise the minimum values in either this or `v` will
// be applied to this vector.
max : function(v, returnNew) {
var
tx = this.x,
ty = this.y,
vx = v.x,
vy = v.y,
x = tx > vx ? tx : vx,
y = ty > vy ? ty : vy;
if (returnNew) {
return new Vec2(x, y);
} else {
return this.set(x, y);
}
},
// Clamp values into a range.
// If this vector's values are lower than the `low`'s
// values, then raise them. If they are higher than
// `high`'s then lower them.
//
// Passing returnNew as true will cause a new Vec2 to be
// returned. Otherwise, this vector's values will be clamped
clamp : function(low, high, returnNew) {
var ret = this.min(high, true).max(low);
if (returnNew) {
return ret;
} else {
return this.set(ret.x, ret.y);
}
},
// Perform linear interpolation between two vectors
// amount is a decimal between 0 and 1
lerp : function(vec, amount) {
return this.add(vec.subtract(this, true).multiply(amount), true);
},
// Get the skew vector such that dot(skew_vec, other) == cross(vec, other)
skew : function() {
// Returns a new vector.
return new Vec2(-this.y, this.x);
},
// calculate the dot product between
// this vector and the incoming
dot : function(b) {
return Vec2.clean(this.x * b.x + b.y * this.y);
},
// calculate the perpendicular dot product between
// this vector and the incoming
perpDot : function(b) {
return Vec2.clean(this.x * b.y - this.y * b.x);
},
// Determine the angle between two vec2s
angleTo : function(vec) {
return Math.atan2(this.perpDot(vec), this.dot(vec));
},
// Divide this vector's components by a scalar
divide : function(vec2, returnNew) {
var x,y;
if ('number' !== typeof vec2) {
x = vec2.x;
y = vec2.y;
// Handle incoming scalars
} else {
x = y = vec2;
}
if (x === 0 || y === 0) {
throw new Error('division by zero')
}
if (isNaN(x) || isNaN(y)) {
throw new Error('NaN detected');
}
if (returnNew) {
return new Vec2(this.x / x, this.y / y);
}
return this.set(this.x / x, this.y / y);
},
isPointOnLine : function(start, end) {
return (start.y - this.y) * (start.x - end.x) ===
(start.y - end.y) * (start.x - this.x);
},
toArray: function() {
return [this.x, this.y];
},
fromArray: function(array) {
return this.set(array[0], array[1]);
},
toJSON: function () {
return {x: this.x, y: this.y};
},
toString: function() {
return '(' + this.x + ', ' + this.y + ')';
}
};
Vec2.fromArray = function(array) {
return new Vec2(array[0], array[1]);
};
// Floating point stability
Vec2.precision = precision || 8;
var p = Math.pow(10, Vec2.precision);
Vec2.clean = clean || function(val) {
if (isNaN(val)) {
throw new Error('NaN detected');
}
if (!isFinite(val)) {
throw new Error('Infinity detected');
}
if(Math.round(val) === val) {
return val;
}
return Math.round(val * p)/p;
};
Vec2.inject = inject;
if(!clean) {
Vec2.fast = inject(function (k) { return k; });
// Expose, but also allow creating a fresh Vec2 subclass.
if (typeof module !== 'undefined' && typeof module.exports == 'object') {
module.exports = Vec2;
} else {
window.Vec2 = window.Vec2 || Vec2;
}
}
return Vec2;
})();