1474 lines
47 KiB
JavaScript
1474 lines
47 KiB
JavaScript
class Bump {
|
||
constructor(renderingEngine = PIXI) {
|
||
if (renderingEngine === undefined) throw new Error("Please assign a rendering engine in the constructor before using bump.js");
|
||
|
||
//Find out which rendering engine is being used (the default is Pixi)
|
||
this.renderer = "";
|
||
|
||
//If the `renderingEngine` is Pixi, set up Pixi object aliases
|
||
if (renderingEngine.ParticleContainer && renderingEngine.Sprite) {
|
||
this.renderer = "pixi";
|
||
}
|
||
}
|
||
|
||
//`addCollisionProperties` adds extra properties to sprites to help
|
||
//simplify the collision code. It won't add these properties if they
|
||
//already exist on the sprite. After these properties have been
|
||
//added, this methods adds a Boolean property to the sprite called `_bumpPropertiesAdded`
|
||
//and sets it to `true` to flag that the sprite has these
|
||
//new properties
|
||
addCollisionProperties(sprite) {
|
||
|
||
//Add properties to Pixi sprites
|
||
if (this.renderer === "pixi") {
|
||
|
||
//gx
|
||
if (sprite.gx === undefined) {
|
||
Object.defineProperty(sprite, "gx", {
|
||
get(){return sprite.getGlobalPosition().x},
|
||
enumerable: true, configurable: true
|
||
});
|
||
}
|
||
|
||
//gy
|
||
if (sprite.gy === undefined) {
|
||
Object.defineProperty(sprite, "gy", {
|
||
get(){return sprite.getGlobalPosition().y},
|
||
enumerable: true, configurable: true
|
||
});
|
||
}
|
||
|
||
//centerX
|
||
if (sprite.centerX === undefined) {
|
||
Object.defineProperty(sprite, "centerX", {
|
||
get(){return sprite.x + sprite.width / 2},
|
||
enumerable: true, configurable: true
|
||
});
|
||
}
|
||
|
||
//centerY
|
||
if (sprite.centerY === undefined) {
|
||
Object.defineProperty(sprite, "centerY", {
|
||
get(){return sprite.y + sprite.height / 2},
|
||
enumerable: true, configurable: true
|
||
});
|
||
}
|
||
|
||
//halfWidth
|
||
if (sprite.halfWidth === undefined) {
|
||
Object.defineProperty(sprite, "halfWidth", {
|
||
get(){return sprite.width / 2},
|
||
enumerable: true, configurable: true
|
||
});
|
||
}
|
||
|
||
//halfHeight
|
||
if (sprite.halfHeight === undefined) {
|
||
Object.defineProperty(sprite, "halfHeight", {
|
||
get(){return sprite.height / 2},
|
||
enumerable: true, configurable: true
|
||
});
|
||
}
|
||
|
||
//xAnchorOffset
|
||
if (sprite.xAnchorOffset === undefined) {
|
||
Object.defineProperty(sprite, "xAnchorOffset", {
|
||
get(){
|
||
if (sprite.anchor !== undefined) {
|
||
return sprite.height * sprite.anchor.x;
|
||
} else {
|
||
return 0;
|
||
}
|
||
},
|
||
enumerable: true, configurable: true
|
||
});
|
||
}
|
||
|
||
//yAnchorOffset
|
||
if (sprite.yAnchorOffset === undefined) {
|
||
Object.defineProperty(sprite, "yAnchorOffset", {
|
||
get(){
|
||
if (sprite.anchor !== undefined) {
|
||
return sprite.width * sprite.anchor.y;
|
||
} else {
|
||
return 0;
|
||
}
|
||
},
|
||
enumerable: true, configurable: true
|
||
});
|
||
}
|
||
|
||
if (sprite.circular && sprite.radius === undefined) {
|
||
Object.defineProperty(sprite, "radius", {
|
||
get(){return sprite.width / 2},
|
||
enumerable: true, configurable: true
|
||
});
|
||
}
|
||
|
||
//Earlier code - not needed now.
|
||
/*
|
||
Object.defineProperties(sprite, {
|
||
"gx": {
|
||
get(){return sprite.getGlobalPosition().x},
|
||
enumerable: true, configurable: true
|
||
},
|
||
"gy": {
|
||
get(){return sprite.getGlobalPosition().y},
|
||
enumerable: true, configurable: true
|
||
},
|
||
"centerX": {
|
||
get(){return sprite.x + sprite.width / 2},
|
||
enumerable: true, configurable: true
|
||
},
|
||
"centerY": {
|
||
get(){return sprite.y + sprite.height / 2},
|
||
enumerable: true, configurable: true
|
||
},
|
||
"halfWidth": {
|
||
get(){return sprite.width / 2},
|
||
enumerable: true, configurable: true
|
||
},
|
||
"halfHeight": {
|
||
get(){return sprite.height / 2},
|
||
enumerable: true, configurable: true
|
||
},
|
||
"xAnchorOffset": {
|
||
get(){
|
||
if (sprite.anchor !== undefined) {
|
||
return sprite.height * sprite.anchor.x;
|
||
} else {
|
||
return 0;
|
||
}
|
||
},
|
||
enumerable: true, configurable: true
|
||
},
|
||
"yAnchorOffset": {
|
||
get(){
|
||
if (sprite.anchor !== undefined) {
|
||
return sprite.width * sprite.anchor.y;
|
||
} else {
|
||
return 0;
|
||
}
|
||
},
|
||
enumerable: true, configurable: true
|
||
}
|
||
});
|
||
*/
|
||
}
|
||
|
||
//Add a Boolean `_bumpPropertiesAdded` property to the sprite to flag it
|
||
//as having these new properties
|
||
sprite._bumpPropertiesAdded = true;
|
||
}
|
||
|
||
/*
|
||
hitTestPoint
|
||
------------
|
||
|
||
Use it to find out if a point is touching a circlular or rectangular sprite.
|
||
Parameters:
|
||
a. An object with `x` and `y` properties.
|
||
b. A sprite object with `x`, `y`, `centerX` and `centerY` properties.
|
||
If the sprite has a `radius` property, the function will interpret
|
||
the shape as a circle.
|
||
*/
|
||
|
||
hitTestPoint(point, sprite) {
|
||
|
||
//Add collision properties
|
||
if (!sprite._bumpPropertiesAdded) this.addCollisionProperties(sprite);
|
||
|
||
let shape, left, right, top, bottom, vx, vy, magnitude, hit;
|
||
|
||
//Find out if the sprite is rectangular or circular depending
|
||
//on whether it has a `radius` property
|
||
if (sprite.radius) {
|
||
shape = "circle";
|
||
} else {
|
||
shape = "rectangle";
|
||
}
|
||
|
||
//Rectangle
|
||
if (shape === "rectangle") {
|
||
|
||
//Get the position of the sprite's edges
|
||
left = sprite.x - sprite.xAnchorOffset;
|
||
right = sprite.x + sprite.width - sprite.xAnchorOffset;
|
||
top = sprite.y - sprite.yAnchorOffset;
|
||
bottom = sprite.y + sprite.height - sprite.yAnchorOffset;
|
||
|
||
//Find out if the point is intersecting the rectangle
|
||
hit = point.x > left && point.x < right && point.y > top && point.y < bottom;
|
||
}
|
||
|
||
//Circle
|
||
if (shape === "circle") {
|
||
|
||
//Find the distance between the point and the
|
||
//center of the circle
|
||
let vx = point.x - sprite.x - (sprite.width / 2) + sprite.xAnchorOffset,
|
||
vy = point.y - sprite.y - (sprite.height / 2) + sprite.yAnchorOffset,
|
||
magnitude = Math.sqrt(vx * vx + vy * vy);
|
||
|
||
//The point is intersecting the circle if the magnitude
|
||
//(distance) is less than the circle's radius
|
||
hit = magnitude < sprite.radius;
|
||
}
|
||
|
||
//`hit` will be either `true` or `false`
|
||
return hit;
|
||
}
|
||
|
||
/*
|
||
hitTestCircle
|
||
-------------
|
||
|
||
Use it to find out if two circular sprites are touching.
|
||
Parameters:
|
||
a. A sprite object with `centerX`, `centerY` and `radius` properties.
|
||
b. A sprite object with `centerX`, `centerY` and `radius`.
|
||
*/
|
||
|
||
hitTestCircle(c1, c2, global = false) {
|
||
|
||
//Add collision properties
|
||
if (!c1._bumpPropertiesAdded) this.addCollisionProperties(c1);
|
||
if (!c2._bumpPropertiesAdded) this.addCollisionProperties(c2);
|
||
|
||
let vx, vy, magnitude, combinedRadii, hit;
|
||
|
||
//Calculate the vector between the circles’ center points
|
||
if (global) {
|
||
//Use global coordinates
|
||
vx = (c2.gx + (c2.width / 2) - c2.xAnchorOffset) - (c1.gx + (c1.width / 2) - c1.xAnchorOffset);
|
||
vy = (c2.gy + (c2.width / 2) - c2.yAnchorOffset) - (c1.gy + (c1.width / 2) - c1.yAnchorOffset);
|
||
} else {
|
||
//Use local coordinates
|
||
vx = (c2.x + (c2.width / 2) - c2.xAnchorOffset) - (c1.x + (c1.width / 2) - c1.xAnchorOffset);
|
||
vy = (c2.y + (c2.width / 2) - c2.yAnchorOffset) - (c1.y + (c1.width / 2) - c1.yAnchorOffset);
|
||
}
|
||
|
||
//Find the distance between the circles by calculating
|
||
//the vector's magnitude (how long the vector is)
|
||
magnitude = Math.sqrt(vx * vx + vy * vy);
|
||
|
||
//Add together the circles' total radii
|
||
combinedRadii = c1.radius + c2.radius;
|
||
|
||
//Set `hit` to `true` if the distance between the circles is
|
||
//less than their `combinedRadii`
|
||
hit = magnitude < combinedRadii;
|
||
|
||
//`hit` will be either `true` or `false`
|
||
return hit;
|
||
}
|
||
|
||
/*
|
||
circleCollision
|
||
---------------
|
||
|
||
Use it to prevent a moving circular sprite from overlapping and optionally
|
||
bouncing off a non-moving circular sprite.
|
||
Parameters:
|
||
a. A sprite object with `x`, `y` `centerX`, `centerY` and `radius` properties.
|
||
b. A sprite object with `x`, `y` `centerX`, `centerY` and `radius` properties.
|
||
c. Optional: true or false to indicate whether or not the first sprite
|
||
should bounce off the second sprite.
|
||
The sprites can contain an optional mass property that should be greater than 1.
|
||
|
||
*/
|
||
|
||
circleCollision(c1, c2, bounce = false, global = false) {
|
||
|
||
//Add collision properties
|
||
if (!c1._bumpPropertiesAdded) this.addCollisionProperties(c1);
|
||
if (!c2._bumpPropertiesAdded) this.addCollisionProperties(c2);
|
||
|
||
let magnitude, combinedRadii, overlap,
|
||
vx, vy, dx, dy, s = {},
|
||
hit = false;
|
||
|
||
//Calculate the vector between the circles’ center points
|
||
|
||
if (global) {
|
||
//Use global coordinates
|
||
vx = (c2.gx + (c2.width / 2) - c2.xAnchorOffset) - (c1.gx + (c1.width / 2) - c1.xAnchorOffset);
|
||
vy = (c2.gy + (c2.width / 2) - c2.yAnchorOffset) - (c1.gy + (c1.width / 2) - c1.yAnchorOffset);
|
||
} else {
|
||
//Use local coordinates
|
||
vx = (c2.x + (c2.width / 2) - c2.xAnchorOffset) - (c1.x + (c1.width / 2) - c1.xAnchorOffset);
|
||
vy = (c2.y + (c2.width / 2) - c2.yAnchorOffset) - (c1.y + (c1.width / 2) - c1.yAnchorOffset);
|
||
}
|
||
|
||
//Find the distance between the circles by calculating
|
||
//the vector's magnitude (how long the vector is)
|
||
magnitude = Math.sqrt(vx * vx + vy * vy);
|
||
|
||
//Add together the circles' combined half-widths
|
||
combinedRadii = c1.radius + c2.radius;
|
||
|
||
//Figure out if there's a collision
|
||
if (magnitude < combinedRadii) {
|
||
|
||
//Yes, a collision is happening
|
||
hit = true;
|
||
|
||
//Find the amount of overlap between the circles
|
||
overlap = combinedRadii - magnitude;
|
||
|
||
//Add some "quantum padding". This adds a tiny amount of space
|
||
//between the circles to reduce their surface tension and make
|
||
//them more slippery. "0.3" is a good place to start but you might
|
||
//need to modify this slightly depending on the exact behaviour
|
||
//you want. Too little and the balls will feel sticky, too much
|
||
//and they could start to jitter if they're jammed together
|
||
let quantumPadding = 0.3;
|
||
overlap += quantumPadding;
|
||
|
||
//Normalize the vector
|
||
//These numbers tell us the direction of the collision
|
||
dx = vx / magnitude;
|
||
dy = vy / magnitude;
|
||
|
||
//Move circle 1 out of the collision by multiplying
|
||
//the overlap with the normalized vector and subtract it from
|
||
//circle 1's position
|
||
c1.x -= overlap * dx;
|
||
c1.y -= overlap * dy;
|
||
|
||
//Bounce
|
||
if (bounce) {
|
||
//Create a collision vector object, `s` to represent the bounce "surface".
|
||
//Find the bounce surface's x and y properties
|
||
//(This represents the normal of the distance vector between the circles)
|
||
s.x = vy;
|
||
s.y = -vx;
|
||
|
||
//Bounce c1 off the surface
|
||
this.bounceOffSurface(c1, s);
|
||
}
|
||
}
|
||
return hit;
|
||
}
|
||
|
||
/*
|
||
movingCircleCollision
|
||
---------------------
|
||
|
||
Use it to make two moving circles bounce off each other.
|
||
Parameters:
|
||
a. A sprite object with `x`, `y` `centerX`, `centerY` and `radius` properties.
|
||
b. A sprite object with `x`, `y` `centerX`, `centerY` and `radius` properties.
|
||
The sprites can contain an optional mass property that should be greater than 1.
|
||
|
||
*/
|
||
|
||
movingCircleCollision(c1, c2, global = false) {
|
||
|
||
//Add collision properties
|
||
if (!c1._bumpPropertiesAdded) this.addCollisionProperties(c1);
|
||
if (!c2._bumpPropertiesAdded) this.addCollisionProperties(c2);
|
||
|
||
let combinedRadii, overlap, xSide, ySide,
|
||
//`s` refers to the distance vector between the circles
|
||
s = {},
|
||
p1A = {},
|
||
p1B = {},
|
||
p2A = {},
|
||
p2B = {},
|
||
hit = false;
|
||
|
||
//Apply mass, if the circles have mass properties
|
||
c1.mass = c1.mass || 1;
|
||
c2.mass = c2.mass || 1;
|
||
|
||
//Calculate the vector between the circles’ center points
|
||
if (global) {
|
||
|
||
//Use global coordinates
|
||
s.vx = (c2.gx + c2.radius - c2.xAnchorOffset) - (c1.gx + c1.radius - c1.xAnchorOffset);
|
||
s.vy = (c2.gy + c2.radius - c2.yAnchorOffset) - (c1.gy + c1.radius - c1.yAnchorOffset);
|
||
} else {
|
||
|
||
//Use local coordinates
|
||
s.vx = (c2.x + c2.radius - c2.xAnchorOffset) - (c1.x + c1.radius - c1.xAnchorOffset);
|
||
s.vy = (c2.y + c2.radius - c2.yAnchorOffset) - (c1.y + c1.radius - c1.yAnchorOffset);
|
||
}
|
||
|
||
//Find the distance between the circles by calculating
|
||
//the vector's magnitude (how long the vector is)
|
||
s.magnitude = Math.sqrt(s.vx * s.vx + s.vy * s.vy);
|
||
|
||
//Add together the circles' combined half-widths
|
||
combinedRadii = c1.radius + c2.radius;
|
||
|
||
//Figure out if there's a collision
|
||
if (s.magnitude < combinedRadii) {
|
||
|
||
//Yes, a collision is happening
|
||
hit = true;
|
||
|
||
//Find the amount of overlap between the circles
|
||
overlap = combinedRadii - s.magnitude;
|
||
|
||
//Add some "quantum padding" to the overlap
|
||
overlap += 0.3;
|
||
|
||
//Normalize the vector.
|
||
//These numbers tell us the direction of the collision
|
||
s.dx = s.vx / s.magnitude;
|
||
s.dy = s.vy / s.magnitude;
|
||
|
||
//Find the collision vector.
|
||
//Divide it in half to share between the circles, and make it absolute
|
||
s.vxHalf = Math.abs(s.dx * overlap / 2);
|
||
s.vyHalf = Math.abs(s.dy * overlap / 2);
|
||
|
||
//Find the side that the collision is occurring on
|
||
(c1.x > c2.x) ? xSide = 1 : xSide = -1;
|
||
(c1.y > c2.y) ? ySide = 1 : ySide = -1;
|
||
|
||
//Move c1 out of the collision by multiplying
|
||
//the overlap with the normalized vector and adding it to
|
||
//the circles' positions
|
||
c1.x = c1.x + (s.vxHalf * xSide);
|
||
c1.y = c1.y + (s.vyHalf * ySide);
|
||
|
||
//Move c2 out of the collision
|
||
c2.x = c2.x + (s.vxHalf * -xSide);
|
||
c2.y = c2.y + (s.vyHalf * -ySide);
|
||
|
||
//1. Calculate the collision surface's properties
|
||
|
||
//Find the surface vector's left normal
|
||
s.lx = s.vy;
|
||
s.ly = -s.vx;
|
||
|
||
//2. Bounce c1 off the surface (s)
|
||
|
||
//Find the dot product between c1 and the surface
|
||
let dp1 = c1.vx * s.dx + c1.vy * s.dy;
|
||
|
||
//Project c1's velocity onto the collision surface
|
||
p1A.x = dp1 * s.dx;
|
||
p1A.y = dp1 * s.dy;
|
||
|
||
//Find the dot product of c1 and the surface's left normal (s.lx and s.ly)
|
||
let dp2 = c1.vx * (s.lx / s.magnitude) + c1.vy * (s.ly / s.magnitude);
|
||
|
||
//Project the c1's velocity onto the surface's left normal
|
||
p1B.x = dp2 * (s.lx / s.magnitude);
|
||
p1B.y = dp2 * (s.ly / s.magnitude);
|
||
|
||
//3. Bounce c2 off the surface (s)
|
||
|
||
//Find the dot product between c2 and the surface
|
||
let dp3 = c2.vx * s.dx + c2.vy * s.dy;
|
||
|
||
//Project c2's velocity onto the collision surface
|
||
p2A.x = dp3 * s.dx;
|
||
p2A.y = dp3 * s.dy;
|
||
|
||
//Find the dot product of c2 and the surface's left normal (s.lx and s.ly)
|
||
let dp4 = c2.vx * (s.lx / s.magnitude) + c2.vy * (s.ly / s.magnitude);
|
||
|
||
//Project c2's velocity onto the surface's left normal
|
||
p2B.x = dp4 * (s.lx / s.magnitude);
|
||
p2B.y = dp4 * (s.ly / s.magnitude);
|
||
|
||
//4. Calculate the bounce vectors
|
||
|
||
//Bounce c1
|
||
//using p1B and p2A
|
||
c1.bounce = {};
|
||
c1.bounce.x = p1B.x + p2A.x;
|
||
c1.bounce.y = p1B.y + p2A.y;
|
||
|
||
//Bounce c2
|
||
//using p1A and p2B
|
||
c2.bounce = {};
|
||
c2.bounce.x = p1A.x + p2B.x;
|
||
c2.bounce.y = p1A.y + p2B.y;
|
||
|
||
//Add the bounce vector to the circles' velocity
|
||
//and add mass if the circle has a mass property
|
||
c1.vx = c1.bounce.x / c1.mass;
|
||
c1.vy = c1.bounce.y / c1.mass;
|
||
c2.vx = c2.bounce.x / c2.mass;
|
||
c2.vy = c2.bounce.y / c2.mass;
|
||
}
|
||
return hit;
|
||
}
|
||
/*
|
||
multipleCircleCollision
|
||
-----------------------
|
||
|
||
Checks all the circles in an array for a collision against
|
||
all the other circles in an array, using `movingCircleCollision` (above)
|
||
*/
|
||
|
||
multipleCircleCollision(arrayOfCircles, global = false) {
|
||
for (let i = 0; i < arrayOfCircles.length; i++) {
|
||
|
||
//The first circle to use in the collision check
|
||
var c1 = arrayOfCircles[i];
|
||
for (let j = i + 1; j < arrayOfCircles.length; j++) {
|
||
|
||
//The second circle to use in the collision check
|
||
let c2 = arrayOfCircles[j];
|
||
|
||
//Check for a collision and bounce the circles apart if
|
||
//they collide. Use an optional `mass` property on the sprite
|
||
//to affect the bounciness of each marble
|
||
this.movingCircleCollision(c1, c2, global);
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
rectangleCollision
|
||
------------------
|
||
|
||
Use it to prevent two rectangular sprites from overlapping.
|
||
Optionally, make the first rectangle bounce off the second rectangle.
|
||
Parameters:
|
||
a. A sprite object with `x`, `y` `centerX`, `centerY`, `halfWidth` and `halfHeight` properties.
|
||
b. A sprite object with `x`, `y` `centerX`, `centerY`, `halfWidth` and `halfHeight` properties.
|
||
c. Optional: true or false to indicate whether or not the first sprite
|
||
should bounce off the second sprite.
|
||
*/
|
||
|
||
rectangleCollision(
|
||
r1, r2, bounce = false, global = true
|
||
) {
|
||
|
||
//Add collision properties
|
||
if (!r1._bumpPropertiesAdded) this.addCollisionProperties(r1);
|
||
if (!r2._bumpPropertiesAdded) this.addCollisionProperties(r2);
|
||
|
||
let collision, combinedHalfWidths, combinedHalfHeights,
|
||
overlapX, overlapY, vx, vy;
|
||
|
||
//Calculate the distance vector
|
||
if (global) {
|
||
vx = (r1.gx + r1.halfWidth - r1.xAnchorOffset) - (r2.gx + r2.halfWidth - r2.xAnchorOffset);
|
||
vy = (r1.gy + r1.halfHeight - r1.yAnchorOffset) - (r2.gy + r2.halfHeight - r2.yAnchorOffset);
|
||
} else {
|
||
//vx = r1.centerX - r2.centerX;
|
||
//vy = r1.centerY - r2.centerY;
|
||
vx = (r1.x + r1.halfWidth - r1.xAnchorOffset) - (r2.x + r2.halfWidth - r2.xAnchorOffset);
|
||
vy = (r1.y + r1.halfHeight - r1.yAnchorOffset) - (r2.y + r2.halfHeight - r2.yAnchorOffset);
|
||
}
|
||
|
||
//Figure out the combined half-widths and half-heights
|
||
combinedHalfWidths = r1.halfWidth + r2.halfWidth;
|
||
combinedHalfHeights = r1.halfHeight + r2.halfHeight;
|
||
|
||
//Check whether vx is less than the combined half widths
|
||
if (Math.abs(vx) < combinedHalfWidths) {
|
||
|
||
//A collision might be occurring!
|
||
//Check whether vy is less than the combined half heights
|
||
if (Math.abs(vy) < combinedHalfHeights) {
|
||
|
||
//A collision has occurred! This is good!
|
||
//Find out the size of the overlap on both the X and Y axes
|
||
overlapX = combinedHalfWidths - Math.abs(vx);
|
||
overlapY = combinedHalfHeights - Math.abs(vy);
|
||
|
||
//The collision has occurred on the axis with the
|
||
//*smallest* amount of overlap. Let's figure out which
|
||
//axis that is
|
||
|
||
if (overlapX >= overlapY) {
|
||
//The collision is happening on the X axis
|
||
//But on which side? vy can tell us
|
||
|
||
if (vy > 0) {
|
||
collision = "top";
|
||
//Move the rectangle out of the collision
|
||
r1.y = r1.y + overlapY;
|
||
} else {
|
||
collision = "bottom";
|
||
//Move the rectangle out of the collision
|
||
r1.y = r1.y - overlapY;
|
||
}
|
||
|
||
//Bounce
|
||
if (bounce) {
|
||
r1.vy *= -1;
|
||
|
||
/*Alternative
|
||
//Find the bounce surface's vx and vy properties
|
||
var s = {};
|
||
s.vx = r2.x - r2.x + r2.width;
|
||
s.vy = 0;
|
||
|
||
//Bounce r1 off the surface
|
||
//this.bounceOffSurface(r1, s);
|
||
*/
|
||
|
||
}
|
||
} else {
|
||
//The collision is happening on the Y axis
|
||
//But on which side? vx can tell us
|
||
|
||
if (vx > 0) {
|
||
collision = "left";
|
||
//Move the rectangle out of the collision
|
||
r1.x = r1.x + overlapX;
|
||
} else {
|
||
collision = "right";
|
||
//Move the rectangle out of the collision
|
||
r1.x = r1.x - overlapX;
|
||
}
|
||
|
||
//Bounce
|
||
if (bounce) {
|
||
r1.vx *= -1;
|
||
|
||
/*Alternative
|
||
//Find the bounce surface's vx and vy properties
|
||
var s = {};
|
||
s.vx = 0;
|
||
s.vy = r2.y - r2.y + r2.height;
|
||
|
||
//Bounce r1 off the surface
|
||
this.bounceOffSurface(r1, s);
|
||
*/
|
||
|
||
}
|
||
}
|
||
} else {
|
||
//No collision
|
||
}
|
||
} else {
|
||
//No collision
|
||
}
|
||
|
||
//Return the collision string. it will be either "top", "right",
|
||
//"bottom", or "left" depending on which side of r1 is touching r2.
|
||
return collision;
|
||
}
|
||
|
||
/*
|
||
hitTestRectangle
|
||
----------------
|
||
|
||
Use it to find out if two rectangular sprites are touching.
|
||
Parameters:
|
||
a. A sprite object with `centerX`, `centerY`, `halfWidth` and `halfHeight` properties.
|
||
b. A sprite object with `centerX`, `centerY`, `halfWidth` and `halfHeight` properties.
|
||
|
||
*/
|
||
|
||
hitTestRectangle(r1, r2, global = false) {
|
||
|
||
//Add collision properties
|
||
if (!r1._bumpPropertiesAdded) this.addCollisionProperties(r1);
|
||
if (!r2._bumpPropertiesAdded) this.addCollisionProperties(r2);
|
||
|
||
let hit, combinedHalfWidths, combinedHalfHeights, vx, vy;
|
||
|
||
//A variable to determine whether there's a collision
|
||
hit = false;
|
||
|
||
//Calculate the distance vector
|
||
if (global) {
|
||
vx = (r1.gx + r1.halfWidth - r1.xAnchorOffset) - (r2.gx + r2.halfWidth - r2.xAnchorOffset);
|
||
vy = (r1.gy + r1.halfHeight - r1.yAnchorOffset) - (r2.gy + r2.halfHeight - r2.yAnchorOffset);
|
||
} else {
|
||
vx = (r1.x + r1.halfWidth - r1.xAnchorOffset) - (r2.x + r2.halfWidth - r2.xAnchorOffset);
|
||
vy = (r1.y + r1.halfHeight - r1.yAnchorOffset) - (r2.y + r2.halfHeight - r2.yAnchorOffset);
|
||
}
|
||
|
||
//Figure out the combined half-widths and half-heights
|
||
combinedHalfWidths = r1.halfWidth + r2.halfWidth;
|
||
combinedHalfHeights = r1.halfHeight + r2.halfHeight;
|
||
|
||
//Check for a collision on the x axis
|
||
if (Math.abs(vx) < combinedHalfWidths) {
|
||
|
||
//A collision might be occuring. Check for a collision on the y axis
|
||
if (Math.abs(vy) < combinedHalfHeights) {
|
||
|
||
//There's definitely a collision happening
|
||
hit = true;
|
||
} else {
|
||
|
||
//There's no collision on the y axis
|
||
hit = false;
|
||
}
|
||
} else {
|
||
|
||
//There's no collision on the x axis
|
||
hit = false;
|
||
}
|
||
|
||
//`hit` will be either `true` or `false`
|
||
return hit;
|
||
}
|
||
|
||
/*
|
||
hitTestCircleRectangle
|
||
----------------
|
||
|
||
Use it to find out if a circular shape is touching a rectangular shape
|
||
Parameters:
|
||
a. A sprite object with `centerX`, `centerY`, `halfWidth` and `halfHeight` properties.
|
||
b. A sprite object with `centerX`, `centerY`, `halfWidth` and `halfHeight` properties.
|
||
|
||
*/
|
||
|
||
hitTestCircleRectangle(c1, r1, global = false) {
|
||
|
||
//Add collision properties
|
||
if (!r1._bumpPropertiesAdded) this.addCollisionProperties(r1);
|
||
if (!c1._bumpPropertiesAdded) this.addCollisionProperties(c1);
|
||
|
||
let region, collision, c1x, c1y, r1x, r1y;
|
||
|
||
//Use either global or local coordinates
|
||
if (global) {
|
||
c1x = c1.gx;
|
||
c1y = c1.gy
|
||
r1x = r1.gx;
|
||
r1y = r1.gy;
|
||
} else {
|
||
c1x = c1.x;
|
||
c1y = c1.y;
|
||
r1x = r1.x;
|
||
r1y = r1.y;
|
||
}
|
||
|
||
//Is the circle above the rectangle's top edge?
|
||
if (c1y - c1.yAnchorOffset < r1y - r1.halfHeight - r1.yAnchorOffset) {
|
||
|
||
//If it is, we need to check whether it's in the
|
||
//top left, top center or top right
|
||
if (c1x - c1.xAnchorOffset < r1x - 1 - r1.halfWidth - r1.xAnchorOffset) {
|
||
region = "topLeft";
|
||
} else if (c1x - c1.xAnchorOffset > r1x + 1 + r1.halfWidth - r1.xAnchorOffset) {
|
||
region = "topRight";
|
||
} else {
|
||
region = "topMiddle";
|
||
}
|
||
}
|
||
|
||
//The circle isn't above the top edge, so it might be
|
||
//below the bottom edge
|
||
else if (c1y - c1.yAnchorOffset > r1y + r1.halfHeight - r1.yAnchorOffset) {
|
||
|
||
//If it is, we need to check whether it's in the bottom left,
|
||
//bottom center, or bottom right
|
||
if (c1x - c1.xAnchorOffset < r1x - 1 - r1.halfWidth - r1.xAnchorOffset) {
|
||
region = "bottomLeft";
|
||
} else if (c1x - c1.xAnchorOffset > r1x + 1 + r1.halfWidth - r1.xAnchorOffset) {
|
||
region = "bottomRight";
|
||
} else {
|
||
region = "bottomMiddle";
|
||
}
|
||
}
|
||
|
||
//The circle isn't above the top edge or below the bottom edge,
|
||
//so it must be on the left or right side
|
||
else {
|
||
if (c1x - c1.xAnchorOffset < r1x - r1.halfWidth - r1.xAnchorOffset) {
|
||
region = "leftMiddle";
|
||
} else {
|
||
region = "rightMiddle";
|
||
}
|
||
}
|
||
|
||
//Is this the circle touching the flat sides
|
||
//of the rectangle?
|
||
if (region === "topMiddle" || region === "bottomMiddle" || region === "leftMiddle" || region === "rightMiddle") {
|
||
|
||
//Yes, it is, so do a standard rectangle vs. rectangle collision test
|
||
collision = this.hitTestRectangle(c1, r1, global);
|
||
}
|
||
|
||
//The circle is touching one of the corners, so do a
|
||
//circle vs. point collision test
|
||
else {
|
||
let point = {};
|
||
|
||
switch (region) {
|
||
case "topLeft":
|
||
point.x = r1x - r1.xAnchorOffset;
|
||
point.y = r1y - r1.yAnchorOffset;
|
||
break;
|
||
|
||
case "topRight":
|
||
point.x = r1x + r1.width - r1.xAnchorOffset;
|
||
point.y = r1y - r1.yAnchorOffset;
|
||
break;
|
||
|
||
case "bottomLeft":
|
||
point.x = r1x - r1.xAnchorOffset;
|
||
point.y = r1y + r1.height - r1.yAnchorOffset;
|
||
break;
|
||
|
||
case "bottomRight":
|
||
point.x = r1x + r1.width - r1.xAnchorOffset;
|
||
point.y = r1y + r1.height - r1.yAnchorOffset;
|
||
}
|
||
|
||
//Check for a collision between the circle and the point
|
||
collision = this.hitTestCirclePoint(c1, point, global);
|
||
}
|
||
|
||
//Return the result of the collision.
|
||
//The return value will be `undefined` if there's no collision
|
||
if (collision) {
|
||
return region;
|
||
} else {
|
||
return collision;
|
||
}
|
||
}
|
||
|
||
/*
|
||
hitTestCirclePoint
|
||
------------------
|
||
|
||
Use it to find out if a circular shape is touching a point
|
||
Parameters:
|
||
a. A sprite object with `centerX`, `centerY`, and `radius` properties.
|
||
b. A point object with `x` and `y` properties.
|
||
|
||
*/
|
||
|
||
hitTestCirclePoint(c1, point, global = false) {
|
||
|
||
//Add collision properties
|
||
if (!c1._bumpPropertiesAdded) this.addCollisionProperties(c1);
|
||
|
||
//A point is just a circle with a diameter of
|
||
//1 pixel, so we can cheat. All we need to do is an ordinary circle vs. circle
|
||
//Collision test. Just supply the point with the properties
|
||
//it needs
|
||
point.diameter = 1;
|
||
point.width = point.diameter;
|
||
point.radius = 0.5;
|
||
point.centerX = point.x;
|
||
point.centerY = point.y;
|
||
point.gx = point.x;
|
||
point.gy = point.y;
|
||
point.xAnchorOffset = 0;
|
||
point.yAnchorOffset = 0;
|
||
point._bumpPropertiesAdded = true;
|
||
return this.hitTestCircle(c1, point, global);
|
||
}
|
||
|
||
/*
|
||
circleRectangleCollision
|
||
------------------------
|
||
|
||
Use it to bounce a circular shape off a rectangular shape
|
||
Parameters:
|
||
a. A sprite object with `centerX`, `centerY`, `halfWidth` and `halfHeight` properties.
|
||
b. A sprite object with `centerX`, `centerY`, `halfWidth` and `halfHeight` properties.
|
||
|
||
*/
|
||
|
||
circleRectangleCollision(
|
||
c1, r1, bounce = false, global = false
|
||
) {
|
||
|
||
//Add collision properties
|
||
if (!r1._bumpPropertiesAdded) this.addCollisionProperties(r1);
|
||
if (!c1._bumpPropertiesAdded) this.addCollisionProperties(c1);
|
||
|
||
let region, collision, c1x, c1y, r1x, r1y;
|
||
|
||
//Use either the global or local coordinates
|
||
if (global) {
|
||
c1x = c1.gx;
|
||
c1y = c1.gy;
|
||
r1x = r1.gx;
|
||
r1y = r1.gy;
|
||
} else {
|
||
c1x = c1.x;
|
||
c1y = c1.y;
|
||
r1x = r1.x;
|
||
r1y = r1.y;
|
||
}
|
||
|
||
//Is the circle above the rectangle's top edge?
|
||
if (c1y - c1.yAnchorOffset < r1y - r1.halfHeight - r1.yAnchorOffset) {
|
||
|
||
//If it is, we need to check whether it's in the
|
||
//top left, top center or top right
|
||
if (c1x - c1.xAnchorOffset < r1x - 1 - r1.halfWidth - r1.xAnchorOffset) {
|
||
region = "topLeft";
|
||
} else if (c1x - c1.xAnchorOffset > r1x + 1 + r1.halfWidth - r1.xAnchorOffset) {
|
||
region = "topRight";
|
||
} else {
|
||
region = "topMiddle";
|
||
}
|
||
}
|
||
|
||
//The circle isn't above the top edge, so it might be
|
||
//below the bottom edge
|
||
else if (c1y - c1.yAnchorOffset > r1y + r1.halfHeight - r1.yAnchorOffset) {
|
||
|
||
//If it is, we need to check whether it's in the bottom left,
|
||
//bottom center, or bottom right
|
||
if (c1x - c1.xAnchorOffset < r1x - 1 - r1.halfWidth - r1.xAnchorOffset) {
|
||
region = "bottomLeft";
|
||
} else if (c1x - c1.xAnchorOffset > r1x + 1 + r1.halfWidth - r1.xAnchorOffset) {
|
||
region = "bottomRight";
|
||
} else {
|
||
region = "bottomMiddle";
|
||
}
|
||
}
|
||
|
||
//The circle isn't above the top edge or below the bottom edge,
|
||
//so it must be on the left or right side
|
||
else {
|
||
if (c1x - c1.xAnchorOffset < r1x - r1.halfWidth - r1.xAnchorOffset) {
|
||
region = "leftMiddle";
|
||
} else {
|
||
region = "rightMiddle";
|
||
}
|
||
}
|
||
|
||
//Is this the circle touching the flat sides
|
||
//of the rectangle?
|
||
if (region === "topMiddle" || region === "bottomMiddle" || region === "leftMiddle" || region === "rightMiddle") {
|
||
|
||
//Yes, it is, so do a standard rectangle vs. rectangle collision test
|
||
collision = this.rectangleCollision(c1, r1, bounce, global);
|
||
}
|
||
|
||
//The circle is touching one of the corners, so do a
|
||
//circle vs. point collision test
|
||
else {
|
||
let point = {};
|
||
|
||
switch (region) {
|
||
case "topLeft":
|
||
point.x = r1x - r1.xAnchorOffset;
|
||
point.y = r1y - r1.yAnchorOffset;
|
||
break;
|
||
|
||
case "topRight":
|
||
point.x = r1x + r1.width - r1.xAnchorOffset;
|
||
point.y = r1y - r1.yAnchorOffset;
|
||
break;
|
||
|
||
case "bottomLeft":
|
||
point.x = r1x - r1.xAnchorOffset;
|
||
point.y = r1y + r1.height - r1.yAnchorOffset;
|
||
break;
|
||
|
||
case "bottomRight":
|
||
point.x = r1x + r1.width - r1.xAnchorOffset;
|
||
point.y = r1y + r1.height - r1.yAnchorOffset;
|
||
}
|
||
|
||
//Check for a collision between the circle and the point
|
||
collision = this.circlePointCollision(c1, point, bounce, global);
|
||
}
|
||
|
||
if (collision) {
|
||
return region;
|
||
} else {
|
||
return collision;
|
||
}
|
||
}
|
||
|
||
/*
|
||
circlePointCollision
|
||
--------------------
|
||
|
||
Use it to boucnce a circle off a point.
|
||
Parameters:
|
||
a. A sprite object with `centerX`, `centerY`, and `radius` properties.
|
||
b. A point object with `x` and `y` properties.
|
||
|
||
*/
|
||
|
||
circlePointCollision(c1, point, bounce = false, global = false) {
|
||
|
||
//Add collision properties
|
||
if (!c1._bumpPropertiesAdded) this.addCollisionProperties(c1);
|
||
|
||
//A point is just a circle with a diameter of
|
||
//1 pixel, so we can cheat. All we need to do is an ordinary circle vs. circle
|
||
//Collision test. Just supply the point with the properties
|
||
//it needs
|
||
point.diameter = 1;
|
||
point.width = point.diameter;
|
||
point.radius = 0.5;
|
||
point.centerX = point.x;
|
||
point.centerY = point.y;
|
||
point.gx = point.x;
|
||
point.gy = point.y;
|
||
point.xAnchorOffset = 0;
|
||
point.yAnchorOffset = 0;
|
||
point._bumpPropertiesAdded = true;
|
||
return this.circleCollision(c1, point, bounce, global);
|
||
}
|
||
|
||
/*
|
||
bounceOffSurface
|
||
----------------
|
||
|
||
Use this to bounce an object off another object.
|
||
Parameters:
|
||
a. An object with `v.x` and `v.y` properties. This represents the object that is colliding
|
||
with a surface.
|
||
b. An object with `x` and `y` properties. This represents the surface that the object
|
||
is colliding into.
|
||
The first object can optionally have a mass property that's greater than 1. The mass will
|
||
be used to dampen the bounce effect.
|
||
*/
|
||
|
||
bounceOffSurface(o, s) {
|
||
|
||
//Add collision properties
|
||
if (!o._bumpPropertiesAdded) this.addCollisionProperties(o);
|
||
|
||
let dp1, dp2,
|
||
p1 = {},
|
||
p2 = {},
|
||
bounce = {},
|
||
mass = o.mass || 1;
|
||
|
||
//1. Calculate the collision surface's properties
|
||
//Find the surface vector's left normal
|
||
s.lx = s.y;
|
||
s.ly = -s.x;
|
||
|
||
//Find its magnitude
|
||
s.magnitude = Math.sqrt(s.x * s.x + s.y * s.y);
|
||
|
||
//Find its normalized values
|
||
s.dx = s.x / s.magnitude;
|
||
s.dy = s.y / s.magnitude;
|
||
|
||
//2. Bounce the object (o) off the surface (s)
|
||
|
||
//Find the dot product between the object and the surface
|
||
dp1 = o.vx * s.dx + o.vy * s.dy;
|
||
|
||
//Project the object's velocity onto the collision surface
|
||
p1.vx = dp1 * s.dx;
|
||
p1.vy = dp1 * s.dy;
|
||
|
||
//Find the dot product of the object and the surface's left normal (s.lx and s.ly)
|
||
dp2 = o.vx * (s.lx / s.magnitude) + o.vy * (s.ly / s.magnitude);
|
||
|
||
//Project the object's velocity onto the surface's left normal
|
||
p2.vx = dp2 * (s.lx / s.magnitude);
|
||
p2.vy = dp2 * (s.ly / s.magnitude);
|
||
|
||
//Reverse the projection on the surface's left normal
|
||
p2.vx *= -1;
|
||
p2.vy *= -1;
|
||
|
||
//Add up the projections to create a new bounce vector
|
||
bounce.x = p1.vx + p2.vx;
|
||
bounce.y = p1.vy + p2.vy;
|
||
|
||
//Assign the bounce vector to the object's velocity
|
||
//with optional mass to dampen the effect
|
||
o.vx = bounce.x / mass;
|
||
o.vy = bounce.y / mass;
|
||
}
|
||
|
||
/*
|
||
contain
|
||
-------
|
||
`contain` can be used to contain a sprite with `x` and
|
||
`y` properties inside a rectangular area.
|
||
|
||
The `contain` function takes four arguments: a sprite with `x` and `y`
|
||
properties, an object literal with `x`, `y`, `width` and `height` properties. The
|
||
third argument is a Boolean (true/false) value that determines if the sprite
|
||
should bounce when it hits the edge of the container. The fourth argument
|
||
is an extra user-defined callback function that you can call when the
|
||
sprite hits the container
|
||
```js
|
||
contain(anySprite, {x: 0, y: 0, width: 512, height: 512}, true, callbackFunction);
|
||
```
|
||
The code above will contain the sprite's position inside the 512 by
|
||
512 pixel area defined by the object. If the sprite hits the edges of
|
||
the container, it will bounce. The `callBackFunction` will run if
|
||
there's a collision.
|
||
|
||
An additional feature of the `contain` method is that if the sprite
|
||
has a `mass` property, it will be used to dampen the sprite's bounce
|
||
in a natural looking way.
|
||
|
||
If the sprite bumps into any of the containing object's boundaries,
|
||
the `contain` function will return a value that tells you which side
|
||
the sprite bumped into: “left”, “top”, “right” or “bottom”. Here's how
|
||
you could keep the sprite contained and also find out which boundary
|
||
it hit:
|
||
```js
|
||
//Contain the sprite and find the collision value
|
||
let collision = contain(anySprite, {x: 0, y: 0, width: 512, height: 512});
|
||
|
||
//If there's a collision, display the boundary that the collision happened on
|
||
if(collision) {
|
||
if collision.has("left") console.log("The sprite hit the left");
|
||
if collision.has("top") console.log("The sprite hit the top");
|
||
if collision.has("right") console.log("The sprite hit the right");
|
||
if collision.has("bottom") console.log("The sprite hit the bottom");
|
||
}
|
||
```
|
||
If the sprite doesn't hit a boundary, the value of
|
||
`collision` will be `undefined`.
|
||
*/
|
||
|
||
/*
|
||
contain(sprite, container, bounce = false, extra = undefined) {
|
||
|
||
//Helper methods that compensate for any possible shift the the
|
||
//sprites' anchor points
|
||
let nudgeAnchor = (o, value, axis) => {
|
||
if (o.anchor !== undefined) {
|
||
if (o.anchor[axis] !== 0) {
|
||
return value * ((1 - o.anchor[axis]) - o.anchor[axis]);
|
||
} else {
|
||
return value;
|
||
}
|
||
} else {
|
||
return value;
|
||
}
|
||
};
|
||
|
||
let compensateForAnchor = (o, value, axis) => {
|
||
if (o.anchor !== undefined) {
|
||
if (o.anchor[axis] !== 0) {
|
||
return value * o.anchor[axis];
|
||
} else {
|
||
return 0;
|
||
}
|
||
} else {
|
||
return 0;
|
||
}
|
||
};
|
||
|
||
let compensateForAnchors = (a, b, property1, property2) => {
|
||
return compensateForAnchor(a, a[property1], property2) + compensateForAnchor(b, b[property1], property2)
|
||
};
|
||
//Create a set called `collision` to keep track of the
|
||
//boundaries with which the sprite is colliding
|
||
let collision = new Set();
|
||
|
||
//Left
|
||
if (sprite.x - compensateForAnchor(sprite, sprite.width, "x") < container.x - sprite.parent.gx - compensateForAnchor(container, container.width, "x")) {
|
||
//Bounce the sprite if `bounce` is true
|
||
if (bounce) sprite.vx *= -1;
|
||
|
||
//If the sprite has `mass`, let the mass
|
||
//affect the sprite's velocity
|
||
if(sprite.mass) sprite.vx /= sprite.mass;
|
||
|
||
//Keep the sprite inside the container
|
||
sprite.x = container.x - sprite.parent.gx + compensateForAnchor(sprite, sprite.width, "x") - compensateForAnchor(container, container.width, "x");
|
||
|
||
//Add "left" to the collision set
|
||
collision.add("left");
|
||
}
|
||
|
||
//Top
|
||
if (sprite.y - compensateForAnchor(sprite, sprite.height, "y") < container.y - sprite.parent.gy - compensateForAnchor(container, container.height, "y")) {
|
||
if (bounce) sprite.vy *= -1;
|
||
if(sprite.mass) sprite.vy /= sprite.mass;
|
||
sprite.y = container.x - sprite.parent.gy + compensateForAnchor(sprite, sprite.height, "y") - compensateForAnchor(container, container.height, "y");
|
||
collision.add("top");
|
||
}
|
||
|
||
//Right
|
||
if (sprite.x - compensateForAnchor(sprite, sprite.width, "x") + sprite.width > container.width - compensateForAnchor(container, container.width, "x")) {
|
||
if (bounce) sprite.vx *= -1;
|
||
if(sprite.mass) sprite.vx /= sprite.mass;
|
||
sprite.x = container.width - sprite.width + compensateForAnchor(sprite, sprite.width, "x") - compensateForAnchor(container, container.width, "x");
|
||
collision.add("right");
|
||
}
|
||
|
||
//Bottom
|
||
if (sprite.y - compensateForAnchor(sprite, sprite.height, "y") + sprite.height > container.height - compensateForAnchor(container, container.height, "y")) {
|
||
if (bounce) sprite.vy *= -1;
|
||
if(sprite.mass) sprite.vy /= sprite.mass;
|
||
sprite.y = container.height - sprite.height + compensateForAnchor(sprite, sprite.height, "y") - compensateForAnchor(container, container.height, "y");
|
||
collision.add("bottom");
|
||
}
|
||
|
||
//If there were no collisions, set `collision` to `undefined`
|
||
if (collision.size === 0) collision = undefined;
|
||
|
||
//The `extra` function runs if there was a collision
|
||
//and `extra` has been defined
|
||
if (collision && extra) extra(collision);
|
||
|
||
//Return the `collision` value
|
||
return collision;
|
||
}
|
||
*/
|
||
contain(sprite, container, bounce = false, extra = undefined) {
|
||
|
||
//Add collision properties
|
||
if (!sprite._bumpPropertiesAdded) this.addCollisionProperties(sprite);
|
||
|
||
//Give the container x and y anchor offset values, if it doesn't
|
||
//have any
|
||
if (container.xAnchorOffset === undefined) container.xAnchorOffset = 0;
|
||
if (container.yAnchorOffset === undefined) container.yAnchorOffset = 0;
|
||
if (sprite.parent.gx === undefined) sprite.parent.gx = 0;
|
||
if (sprite.parent.gy === undefined) sprite.parent.gy = 0;
|
||
|
||
//Create a Set called `collision` to keep track of the
|
||
//boundaries with which the sprite is colliding
|
||
let collision = new Set();
|
||
|
||
//Left
|
||
if (sprite.x - sprite.xAnchorOffset < container.x - sprite.parent.gx - container.xAnchorOffset) {
|
||
|
||
//Bounce the sprite if `bounce` is true
|
||
if (bounce) sprite.vx *= -1;
|
||
|
||
//If the sprite has `mass`, let the mass
|
||
//affect the sprite's velocity
|
||
if (sprite.mass) sprite.vx /= sprite.mass;
|
||
|
||
//Reposition the sprite inside the container
|
||
sprite.x = container.x - sprite.parent.gx - container.xAnchorOffset + sprite.xAnchorOffset;
|
||
|
||
//Make a record of the side which the container hit
|
||
collision.add("left");
|
||
}
|
||
|
||
//Top
|
||
if (sprite.y - sprite.yAnchorOffset < container.y - sprite.parent.gy - container.yAnchorOffset) {
|
||
if (bounce) sprite.vy *= -1;
|
||
if (sprite.mass) sprite.vy /= sprite.mass;
|
||
sprite.y = container.y - sprite.parent.gy - container.yAnchorOffset + sprite.yAnchorOffset;;
|
||
collision.add("top");
|
||
}
|
||
|
||
//Right
|
||
if (sprite.x - sprite.xAnchorOffset + sprite.width > container.width - container.xAnchorOffset) {
|
||
if (bounce) sprite.vx *= -1;
|
||
if (sprite.mass) sprite.vx /= sprite.mass;
|
||
sprite.x = container.width - sprite.width - container.xAnchorOffset + sprite.xAnchorOffset;
|
||
collision.add("right");
|
||
}
|
||
|
||
//Bottom
|
||
if (sprite.y - sprite.yAnchorOffset + sprite.height > container.height - container.yAnchorOffset) {
|
||
if (bounce) sprite.vy *= -1;
|
||
if (sprite.mass) sprite.vy /= sprite.mass;
|
||
sprite.y = container.height - sprite.height - container.yAnchorOffset + sprite.yAnchorOffset;
|
||
collision.add("bottom");
|
||
}
|
||
|
||
//If there were no collisions, set `collision` to `undefined`
|
||
if (collision.size === 0) collision = undefined;
|
||
|
||
//The `extra` function runs if there was a collision
|
||
//and `extra` has been defined
|
||
if (collision && extra) extra(collision);
|
||
|
||
//Return the `collision` value
|
||
return collision;
|
||
}
|
||
|
||
//`outsideBounds` checks whether a sprite is outide the boundary of
|
||
//another object. It returns an object called `collision`. `collision` will be `undefined` if there's no
|
||
//collision. But if there is a collision, `collision` will be
|
||
//returned as a Set containg strings that tell you which boundary
|
||
//side was crossed: "left", "right", "top" or "bottom"
|
||
outsideBounds(s, bounds, extra) {
|
||
|
||
let x = bounds.x,
|
||
y = bounds.y,
|
||
width = bounds.width,
|
||
height = bounds.height;
|
||
|
||
//The `collision` object is used to store which
|
||
//side of the containing rectangle the sprite hits
|
||
let collision = new Set();
|
||
|
||
//Left
|
||
if (s.x < x - s.width) {
|
||
collision.add("left");
|
||
}
|
||
//Top
|
||
if (s.y < y - s.height) {
|
||
collision.add("top");
|
||
}
|
||
//Right
|
||
if (s.x > width + s.width) {
|
||
collision.add("right");
|
||
}
|
||
//Bottom
|
||
if (s.y > height + s.height) {
|
||
collision.add("bottom");
|
||
}
|
||
|
||
//If there were no collisions, set `collision` to `undefined`
|
||
if (collision.size === 0) collision = undefined;
|
||
|
||
//The `extra` function runs if there was a collision
|
||
//and `extra` has been defined
|
||
if (collision && extra) extra(collision);
|
||
|
||
//Return the `collision` object
|
||
return collision;
|
||
}
|
||
|
||
/*
|
||
_getCenter
|
||
----------
|
||
|
||
A utility that finds the center point of the sprite. If it's anchor point is the
|
||
sprite's top left corner, then the center is calculated from that point.
|
||
If the anchor point has been shifted, then the anchor x/y point is used as the sprite's center
|
||
*/
|
||
|
||
_getCenter(o, dimension, axis) {
|
||
if (o.anchor !== undefined) {
|
||
if (o.anchor[axis] !== 0) {
|
||
return 0;
|
||
} else {
|
||
//console.log(o.anchor[axis])
|
||
return dimension / 2;
|
||
}
|
||
} else {
|
||
return dimension;
|
||
}
|
||
}
|
||
|
||
|
||
/*
|
||
hit
|
||
---
|
||
A convenient universal collision function to test for collisions
|
||
between rectangles, circles, and points.
|
||
*/
|
||
|
||
hit(a, b, react = false, bounce = false, global, extra = undefined) {
|
||
|
||
//Local references to bump's collision methods
|
||
let hitTestPoint = this.hitTestPoint.bind(this),
|
||
hitTestRectangle = this.hitTestRectangle.bind(this),
|
||
hitTestCircle = this.hitTestCircle.bind(this),
|
||
movingCircleCollision = this.movingCircleCollision.bind(this),
|
||
circleCollision = this.circleCollision.bind(this),
|
||
hitTestCircleRectangle = this.hitTestCircleRectangle.bind(this),
|
||
rectangleCollision = this.rectangleCollision.bind(this),
|
||
circleRectangleCollision = this.circleRectangleCollision.bind(this);
|
||
|
||
let collision,
|
||
aIsASprite = a.parent !== undefined,
|
||
bIsASprite = b.parent !== undefined;
|
||
|
||
//Check to make sure one of the arguments isn't an array
|
||
if (aIsASprite && b instanceof Array || bIsASprite && a instanceof Array) {
|
||
//If it is, check for a collision between a sprite and an array
|
||
spriteVsArray();
|
||
} else {
|
||
//If one of the arguments isn't an array, find out what type of
|
||
//collision check to run
|
||
collision = findCollisionType(a, b);
|
||
if (collision && extra) extra(collision);
|
||
}
|
||
|
||
//Return the result of the collision.
|
||
//It will be `undefined` if there's no collision and `true` if
|
||
//there is a collision. `rectangleCollision` sets `collsision` to
|
||
//"top", "bottom", "left" or "right" depeneding on which side the
|
||
//collision is occuring on
|
||
return collision;
|
||
|
||
function findCollisionType(a, b) {
|
||
//Are `a` and `b` both sprites?
|
||
//(We have to check again if this function was called from
|
||
//`spriteVsArray`)
|
||
let aIsASprite = a.parent !== undefined;
|
||
let bIsASprite = b.parent !== undefined;
|
||
|
||
if (aIsASprite && bIsASprite) {
|
||
//Yes, but what kind of sprites?
|
||
if (a.diameter && b.diameter) {
|
||
//They're circles
|
||
return circleVsCircle(a, b);
|
||
} else if (a.diameter && !b.diameter) {
|
||
//The first one is a circle and the second is a rectangle
|
||
return circleVsRectangle(a, b);
|
||
} else {
|
||
//They're rectangles
|
||
return rectangleVsRectangle(a, b);
|
||
}
|
||
}
|
||
//They're not both sprites, so what are they?
|
||
//Is `a` not a sprite and does it have x and y properties?
|
||
else if (bIsASprite && !(a.x === undefined) && !(a.y === undefined)) {
|
||
//Yes, so this is a point vs. sprite collision test
|
||
return hitTestPoint(a, b);
|
||
} else {
|
||
//The user is trying to test some incompatible objects
|
||
throw new Error(`I'm sorry, ${a} and ${b} cannot be use together in a collision test.'`);
|
||
}
|
||
}
|
||
|
||
function spriteVsArray() {
|
||
//If `a` happens to be the array, flip it around so that it becomes `b`
|
||
if (a instanceof Array) {
|
||
let [a, b] = [b, a];
|
||
}
|
||
//Loop through the array in reverse
|
||
for (let i = b.length - 1; i >= 0; i--) {
|
||
let sprite = b[i];
|
||
collision = findCollisionType(a, sprite);
|
||
if (collision && extra) extra(collision, sprite);
|
||
}
|
||
}
|
||
|
||
function circleVsCircle(a, b) {
|
||
//If the circles shouldn't react to the collision,
|
||
//just test to see if they're touching
|
||
if (!react) {
|
||
return hitTestCircle(a, b);
|
||
}
|
||
//Yes, the circles should react to the collision
|
||
else {
|
||
//Are they both moving?
|
||
if (a.vx + a.vy !== 0 && b.vx + b.vy !== 0) {
|
||
//Yes, they are both moving
|
||
//(moving circle collisions always bounce apart so there's
|
||
//no need for the third, `bounce`, argument)
|
||
return movingCircleCollision(a, b, global);
|
||
} else {
|
||
//No, they're not both moving
|
||
return circleCollision(a, b, bounce, global);
|
||
}
|
||
}
|
||
}
|
||
|
||
function rectangleVsRectangle(a, b) {
|
||
//If the rectangles shouldn't react to the collision, just
|
||
//test to see if they're touching
|
||
if (!react) {
|
||
return hitTestRectangle(a, b, global);
|
||
} else {
|
||
return rectangleCollision(a, b, bounce, global);
|
||
}
|
||
}
|
||
|
||
function circleVsRectangle(a, b) {
|
||
//If the rectangles shouldn't react to the collision, just
|
||
//test to see if they're touching
|
||
if (!react) {
|
||
return hitTestCircleRectangle(a, b, global);
|
||
} else {
|
||
return circleRectangleCollision(a, b, bounce, global);
|
||
}
|
||
}
|
||
}
|
||
} |