/**
* Maths utility methods.
* @module mutils
*/
/**
* Return either 1.0 or -1.0 at random.
* @returns {number} randomNumber
*/
export function random1PlusMinus() {
return Math.random() > 0.5 ? -1.0 : 1.0;
}
/**
* Return a random number between -1.0 and 1.0.
* @returns {number} randomNumber
*/
export function randFloatNegOneToOne() {
return Math.random() * 2.0 - 1.0;
}
/**
* Rounds a number to nearest step, supporting optional starting offset.
* @param {number} number - The target number.
* @param {number} step - The step to round to.
* @param {number} [offset=0] - Optional starting offset.
* @returns {number} roundedResult
*/
export function roundToNearest(number, step, offset = 0) {
if (offset != 0) {
offset = offset-(Math.round(offset/step)*step);
return offset+Math.round((number-offset)/step)*step;
} else {
return (Math.round(number/step)*step);
}
};
/**
* Returns the intersection point between two infinite length lines.
* @param {vector} lineApoint0 - An object with `x` and `y` properties.
* @param {vector} lineApoint1 - An object with `x` and `y` properties.
* @param {vector} lineBpoint0 - An object with `x` and `y` properties.
* @param {vector} lineBpoint1 - An object with `x` and `y` properties.
* @returns {PIXI.Point|null} intersectionPoint - Will return null if lines don't intersect.
*/
export function intersectLineLine(a1, a2, b1, b2, applyToVector = null) {
let res = null;
const ua_t = (b2.x-b1.x)*(a1.y-b1.y)-(b2.y-b1.y)*(a1.x-b1.x);
const ub_t = (a2.x-a1.x)*(a1.y-b1.y)-(a2.y-a1.y)*(a1.x-b1.x);
const u_b = (b2.y-b1.y)*(a2.x-a1.x)-(b2.x-b1.x)*(a2.y-a1.y);
if (u_b != 0) {
const ua = ua_t/u_b;
const ub = ub_t/u_b;
if (applyToVector){
applyToVector.x = a1.x+ua*(a2.x-a1.x);
applyToVector.y = a1.y+ua*(a2.y-a1.y);
return null;
}
res = new Point(a1.x+ua*(a2.x-a1.x), a1.y+ua*(a2.y-a1.y));
}
return res;
}
/**
* Returns the intersection point between two closed lines.
* @param {vector} lineApoint0 - An object with `x` and `y` properties.
* @param {vector} lineApoint1 - An object with `x` and `y` properties.
* @param {vector} lineBpoint0 - An object with `x` and `y` properties.
* @param {vector} lineBpoint1 - An object with `x` and `y` properties.
* @returns {PIXI.Point|null} intersectionPoint - Will return null if segments don't intersect.
*/
export function intersectSegmentSegment(a1, a2, b1, b2) {
var res = null;
var ua_t = (b2.x-b1.x)*(a1.y-b1.y)-(b2.y-b1.y)*(a1.x-b1.x);
var ub_t = (a2.x-a1.x)*(a1.y-b1.y)-(a2.y-a1.y)*(a1.x-b1.x);
var u_b = (b2.y-b1.y)*(a2.x-a1.x)-(b2.x-b1.x)*(a2.y-a1.y);
if (u_b != 0) {
var ua = ua_t/u_b;
var ub = ub_t/u_b;
if (0<=ua && ua<=1 && 0<=ub && ub<=1) {
res = new Point(a1.x+ua*(a2.x-a1.x), a1.y+ua*(a2.y-a1.y));
}
}
return res;
};
/**
* Returns the intersection point between two rays.
* <br>A ray is an infinite line from origin point through the target point.
* @param {vector} lineAorigin - An object with `x` and `y` properties.
* @param {vector} lineAtarget - An object with `x` and `y` properties.
* @param {vector} lineBorigin - An object with `x` and `y` properties.
* @param {vector} lineBtarget - An object with `x` and `y` properties.
* @returns {PIXI.Point|null} intersectionPoint - Will return null if the rays don't intersect.
*/
export function intersectRayRay(a1, a2, b1, b2) {
var res = null;
var ua_t = (b2.x-b1.x)*(a1.y-b1.y)-(b2.y-b1.y)*(a1.x-b1.x);
var ub_t = (a2.x-a1.x)*(a1.y-b1.y)-(a2.y-a1.y)*(a1.x-b1.x);
var u_b = (b2.y-b1.y)*(a2.x-a1.x)-(b2.x-b1.x)*(a2.y-a1.y);
if (u_b != 0) {
var ua = ua_t/u_b;
var ub = ub_t/u_b;
if (ua>=0 && ub>=0) {
res = new Point(a1.x+ua*(a2.x-a1.x), a1.y+ua*(a2.y-a1.y));
}
}
return res;
};
/**
* Returns the intersection point between a line segment and the edges of a box.
* @param {vector} segmentPoint0 - An object with `x` and `y` properties.
* @param {vector} segmentPoint1 - An object with `x` and `y` properties.
* @param {number} boxLeftX - The leftmost position of the box.
* @param {number} boxTopY - The topmost position of the box.
* @param {number} boxWidth - The box width.
* @param {number} boxHeight - The box height.
* @returns {PIXI.Point|null} intersectionPoint - Will return null if the segment doesn't intersect the box edge.
* @private
*/
export function intersectSegmentBox(l1, l2, xb, yb, wb, hb) {
return intersectSegmentSegment(l1.x, l1.y, l2.x, l2.y, xb, yb, xb, yb + hb) ||
intersectSegmentSegment(l1.x, l1.y, l2.x, l2.y, xb + wb, yb, xb + wb, yb + hb) ||
intersectSegmentSegment(l1.x, l1.y, l2.x, l2.y, xb, yb + hb, xb + wb, yb + hb) ||
intersectSegmentSegment(l1.x, l1.y, l2.x, l2.y, xb, yb, xb + wb, yb)
}
/**
* Returns the intersection point between a line segment and the edges of a box.
* @param {vector} segmentPoint0 - An object with `x` and `y` properties.
* @param {vector} segmentPoint1 - An object with `x` and `y` properties.
* @param {rectangle} rectangle - An object with `x`, `y`, `width` and `height` properties.
* @returns {PIXI.Point|null} intersectionPoint - Will return null if the segment doesn't intersect the box edge.
* @private
*/
export function intersectSegmentRect(l1, l2, rect) {
return intersectSegmentBox(l1, l1, rect.x, rect.y, rect.width, rect.height)
}
/**
* Returns an array of intersection points (with length between 0 and 2) between a circle and line segment.
* @param {vector} segmentPoint0 - An object with `x` and `y` properties.
* @param {vector} segmentPoint1 - An object with `x` and `y` properties.
* @param {vector} circleCenter - An object with `x` and `y` properties.
* @param {number} circleRadius
* @returns {Array.<Vector>} intersectionPoints - Will return a list of intersection points, between 0 and 2.
*/
export function intersectionPtsBetweenCircleAndLineSeg(lineSegP0, lineSegP1, circleCenter, circleRadius){
// circle, line
let a, b, c, d, u1, u2, ret, retP1, retP2, v1, v2;
v1 = {};
v2 = {};
v1.x = lineSegP1.x - lineSegP0.x;
v1.y = lineSegP1.y - lineSegP0.y;
v2.x = lineSegP0.x - circleCenter.x;
v2.y = lineSegP0.y - circleCenter.y;
b = (v1.x * v2.x + v1.y * v2.y);
c = 2 * (v1.x * v1.x + v1.y * v1.y);
b *= -2;
d = Math.sqrt(b * b - 2 * c * (v2.x * v2.x + v2.y * v2.y - circleRadius * circleRadius));
if(isNaN(d)){ // no intercept
return [];
}
u1 = (b - d) / c; // these represent the unit distance of point one and two on the line
u2 = (b + d) / c;
retP1 = {}; // return points
retP2 = {}
ret = []; // return array
if(u1 <= 1 && u1 >= 0){ // add point if on the line segment
retP1.x = lineSegP0.x + v1.x * u1;
retP1.y = lineSegP0.y + v1.y * u1;
ret.push(retP1);
}
if(u2 <= 1 && u2 >= 0){ // second add point if on the line segment
retP2.x = lineSegP0.x + v1.x * u2;
retP2.y = lineSegP0.y + v1.y * u2;
ret.push(retP2);
}
return ret;
}
/**
* Returns an array of intersection points (with length between 0 and 2) between 2 circles.
* @param {vector} circleCenter0 - An object with `x` and `y` properties.
* @param {number} circleRadius0
* @param {vector} circleCenter1 - An object with `x` and `y` properties.
* @param {number} circleRadius1
* @returns {Array.<Vector>} intersectionPoints - Will return a list of intersection points, between 0 and 2.
*/
export function intersectionPtsBetweenCircles(pt_circle0_pos, int_circle0_radius, pt_circle1_pos, int_circle1_radius) {
var d = this.distanceBetweenPoints(pt_circle0_pos, pt_circle1_pos);
if (d>int_circle0_radius+int_circle1_radius || d<Math.abs(int_circle0_radius-int_circle1_radius)) {
return [];
} else {
var a = (Math.pow(int_circle0_radius, 2)-Math.pow(int_circle1_radius, 2)+Math.pow(d, 2))/(2*d);
var h = Math.sqrt(Math.pow(int_circle0_radius, 2)-Math.pow(a, 2));
var pt_p2 = new Object();
pt_p2["x"] = pt_circle0_pos["x"]+((a*(pt_circle1_pos["x"]-pt_circle0_pos["x"]))/d);
pt_p2["y"] = pt_circle0_pos["y"]+((a*(pt_circle1_pos["y"]-pt_circle0_pos["y"]))/d);
var pt_p3_a = {x:pt_p2["x"]+(h*(pt_circle1_pos["y"]-pt_circle0_pos["y"]))/d, y:pt_p2["y"]-(h*(pt_circle1_pos["x"]-pt_circle0_pos["x"]))/d};
var pt_p3_b = {x:-pt_p3_a["x"]+(2*pt_p2["x"]), y:-pt_p3_a["y"]+(2*pt_p2["y"])};
return [pt_p3_a, pt_p3_b];
}
};
/**
* Calculates the perimter (circumference) of an ellipse.
* @param {number} radiusX - The ellipse radius in the x axis.
* @param {number} radiusY - The ellipse radius in the y axis.
* @returns {number} perimeter - Will return the perimeter length of the ellipse.
*/
export function ellipsePerimeter(radX, radY){
return 2.0 * 3.14 * Math.sqrt((radX * radX + radY * radY) / (2.0 * 1.0));
}
/**
* Converts angle from degrees to radians.
* @param {number} angleDegrees - Angle in degrees.
* @returns {number} angleRadians - Angle in radians.
*/
export function degToRad(deg){
return deg / 180.0 * Math.PI;
}
/**
* Converts angle from radians to degrees.
* @param {number} angleRadians - Angle in radians.
* @returns {number} angleDegrees - Angle in degrees.
*/
export function radToDeg(rad){
return rad / Math.PI * 180.0;
}
/**
* Returns the distance between 2 vector points.
* @param {vector} pointA - An object with `x` and `y` properties.
* @param {vector} pointB - An object with `x` and `y` properties.
* @returns {number} distance
*/
export function distanceBetweenPoints(ptA, ptB) {
const dx = ptA.x - ptB.x;
const dy = ptA.y - ptB.y;
return Math.sqrt(dx * dx + dy * dy);
}
/**
* Restricts a given value between 0 and 1 (inclusive).
* @param {number} value
* @returns {number} restrictedValue
*/
export function clamp01(value){
return value < 0 ? 0 : (value > 1 ? 1 : value);
}
/**
* Restricts a given value between -1 and 1 (inclusive).
* @param {number} value
* @returns {number} restrictedValue
*/
export function clampNeg1Pos1(value){
return value < -1 ? -1 : (value > 1 ? 1 : value);
}
/**
* Returns the angle in radians between 2 points.
* @param {vector} originPoint - An object with `x` and `y` properties.
* @param {vector} destinationPoint - An object with `x` and `y` properties.
* @returns {number} angleRadians - The angle in radians.
*/
export function angleRadsBetweenPoints(ptA, ptB){
return Math.atan2(ptB.y - ptA.y, ptB.x - ptA.x);
}
/**
* Returns the angle in degrees between 2 points.
* @param {vector} originPoint - An object with `x` and `y` properties.
* @param {vector} destinationPoint - An object with `x` and `y` properties.
* @returns {number} angleDegrees - The angle in degrees.
*/
export function angleDegsBetweenPoints(ptA, ptB){
return Math.atan2(ptB.y - ptA.y, ptB.x - ptA.x) / Math.PI * 180.0
}
/**
* Projects from a point at a given radian angle and distance.
* @param {vector} originPoint - An object with `x` and `y` properties.
* @param {number} angleRadians - The rotation angle, in radians.
* @param {number} distance - The distance to project.
* @returns {PIXI.Point} projectedPoint - A new point object.
*/
export function projectFromPointRad(pt, angleRads, dist) {
return new Point(pt.x + dist * Math.cos(angleRads), pt.y + dist * Math.sin(angleRads));
}
/**
* Projects from a point at a given degree angle and distance.
* @param {vector} originPoint - An object with `x` and `y` properties.
* @param {number} angleDegrees - The rotation angle, in degrees.
* @param {number} distance - The distance to project.
* @returns {PIXI.Point} projectedPoint - A new point object.
*/
export function projectFromPointDeg(pt, angleDegs, dist) {
let angleRads = angleDegs / 180.0 * Math.PI;
return new Point(pt.x + dist * Math.cos(angleRads), pt.y + dist * Math.sin(angleRads));
}
/**
* Returns the point projecting from one point to another at a set distance.
* @param {vector} originPoint - An object with `x` and `y` properties.
* @param {vector} targetPoint - An object with `x` and `y` properties.
* @param {number} distance - The distance to project.
* @returns {PIXI.Point} projectedPoint - A new point object.
*/
export function projectDistance(ptA, ptB, dist){
const dx = ptA.x - ptB.x;
const dy = ptA.y - ptB.y;
const fullDist = Math.sqrt(dx * dx + dy * dy);
return new Point(ptA.x - dx*(dist/fullDist), ptA.y - dy*(dist/fullDist));
}
/**
* Rotates one point around another a given angle (in radians)
* @param {vector} centerPoint - An object with `x` and `y` properties.
* @param {vector} subjectPoint - An object with `x` and `y` properties.
* @param {number} angleRadians - The rotation angle, in radians.
* @param {boolean} overwrite - If true: `subjectPoint` will be updated with the result. If false: a new PIXI.Point object will be returned.
* @returns {PIXI.Point|null} result - The resulting coordinate.
*/
export function rotatePtAroundPtRad(centerPt, pt, angRads, overwrite = false){
if (overwrite){
pt.set(Math.cos(angRads) * (pt.x - centerPt.x) - Math.sin(angRads) * (pt.y-centerPt.y) + centerPt.x, Math.sin(angRads) * (pt.x - centerPt.x) + Math.cos(angRads) * (pt.y - centerPt.y) + centerPt.y);
} else {
return new Point(Math.cos(angRads) * (pt.x - centerPt.x) - Math.sin(angRads) * (pt.y-centerPt.y) + centerPt.x, Math.sin(angRads) * (pt.x - centerPt.x) + Math.cos(angRads) * (pt.y - centerPt.y) + centerPt.y);
}
}
/**
* Rotates one point around another a given angle (in degrees)
* @param {vector} centerPoint - An object with `x` and `y` properties.
* @param {vector} subjectPoint - An object with `x` and `y` properties.
* @param {number} angleDegrees - The rotation angle, in degrees.
* @param {boolean} overwrite - If true: `subjectPoint` will be updated with the result. If false: a new PIXI.Point object will be returned.
* @returns {PIXI.Point|null} result - The resulting coordinate.
*/
export function rotatePtAroundPtDeg(centerPt, pt, angDegs, overwrite = false){
return rotatePtAroundPtRad(centerPt, pt, degToRad(angDegs), overwrite);
}
// result is in radians, NOT degress
/**
* Return the shortest angular offset (in radians) from a source angle (in radians) to a target angle (in radians).
* <br>The result may be negative.
* @param {number} sourceAngleRadians - The source angle in radians.
* @param {number} targetAngleRadians - The target angle in radians.
* @returns {number} offsetAngleRadians - The offset in radians.
*/
export function angularDeltaFromAnglesRad(sourceAngRads, targetAngRads){
return Math.atan2(Math.sin(targetAngRads-sourceAngRads), Math.cos(targetAngRads-sourceAngRads));
}
/**
* Return the shortest angular offset (in degrees) from a source angle (in degrees) to a target angle (in degrees).
* <br>The result may be negative.
* @param {number} sourceAngleDegrees - The source angle in degrees.
* @param {number} targetAngleDegrees - The target angle in degrees.
* @returns {number} offsetAngleDegrees - The offset in degrees.
*/
export function angularDeltaFromAnglesDeg(sourceAngDegs, targetAngDegs){
return radToDeg(angularDeltaFromAnglesRad(degToRad(sourceAngDegs), degToRad(targetAngDegs)));
}
/**
* Return the shortest angular offset (in degrees) in the given direction of travel from a source angle (in degrees) to a target angle (in degrees).
* <br>The result may be negative.
* @param {number} sourceAngleDegrees - The source angle in degrees.
* @param {number} targetAngleDegrees - The target angle in degrees.
* @param {number} direction - The direction (under 0 for CCW, over 0 for CW)
* @returns {number} offsetAngleDegrees - The offset in degrees.
*/
export function angularDeltaFromAnglesForceDirDeg(sourceAngDegs, targetAngDegs, dir){
let delta = radToDeg(angularDeltaFromAnglesRad(degToRad(sourceAngDegs), degToRad(targetAngDegs)));
if (delta < 0.0 && dir > 0.0){
return 360 + delta;
} else if (delta > 0.0 && dir < 0.0){
return -360 + delta;
}
return delta;
}
/**
* Given source dimensions, returns the scale needed to completely cover the given bounds while maintaining aspect ratio.
* @param {number} sourceWidth - The source width.
* @param {number} sourceHeight - The source height.
* @param {number} boundsWidth - The bounds width.
* @param {number} boundsHeight - The bounds height.
* @returns {number} scale - The resulting scale.
*/
export function coverScale(srcW, srcH, boundsW, boundsH) {
var ratioSrc = srcW/srcH;
var ratioBounds = boundsW/boundsH;
if (ratioSrc<ratioBounds) {
return boundsW/srcW;
} else {
return boundsH/srcH;
}
};
/**
* Returns the scale needed to contain the source dimensions exactly within the given bounds while maintaining aspect ratio.
* @param {number} sourceWidth - The source width.
* @param {number} sourceHeight - The source height.
* @param {number} boundsWidth - The bounds width.
* @param {number} boundsHeight - The bounds height.
* @returns {number} scale - The resulting scale.
*/
export function containScale(srcW, srcH, boundsW, boundsH) {
var ratioSrc = srcW/srcH;
var ratioBounds = boundsW/boundsH;
if (ratioSrc>=ratioBounds) {
return boundsW/srcW;
} else {
return boundsH/srcH;
}
};
/**
* Given indexes that repeat from 0 to `totalCount`-1, return the offset from one to another in a set direction, negative or positive.
* @param {number} indexFrom - The starting index. Must be positive integer under `totalCount`.
* @param {number} indexTo - The target index. Must be positive integer under `totalCount`.
* @param {number} dir - A number over or under zero. If dir is over 0 the resulting offset will be over zero and visa-versa.
* @param {count} totalCount - The total number of indexes. Eg. The array length.
* @returns {int} indexOffset - The offset between indexes.
*/
export function indexOffsetInDirection(indexFrom, indexTo, dir, totalCount){
if (indexFrom === indexTo){
return 0.0;
}
let indexOffset = indexTo - indexFrom;
if (dir > 0.0 && indexOffset < 0.0){
indexOffset += totalCount
} else if (dir < 0.0 && indexOffset > 0.0){
indexOffset -= totalCount
}
return indexOffset;
}
/**
* Given indexes that repeat from 0 to `totalCount`-1, return the shortest offset from one to another in any direction.
* @param {number} indexFrom - The starting index. Must be positive integer under `totalCount`.
* @param {number} indexTo - The target index. Must be positive integer under `totalCount`.
* @param {count} totalCount - The total number of indexes. Eg. The array length.
* @returns {int} indexOffset - The offset between indexes. Result may be negative.
*/
export function shortestIndexOffset(indexFrom, indexTo, totalCount){
if (indexFrom === indexTo){
return 0.0;
}
let indexOffset = indexTo - (indexFrom + (indexTo > indexFrom ? totalCount : 0.0));
if (Math.abs(indexOffset) > totalCount*0.5){
indexOffset += totalCount;
}
return indexOffset;
}