export {VectorMixin as Mixin} from './mixin'; export const SQRT_2_2 = Math.sqrt(2) / 2; export const EIGHTH_PI = Math.PI * 0.125; export const QUARTER_PI = EIGHTH_PI * 2; export const HALF_PI = QUARTER_PI * 2; export const TWO_PI = Math.PI * 2; // export function {VectorMixin as Mixin} from './mixin' // Scale a vector. This multiplies *x* and *y* by **k**. // // avocado> Vector.scale [.5, 1.5], 2 // [1, 3] export function scale(v, k) { return [v[0] * k, v[1] * k]; } // Add two vectors. // // avocado> Vector.add [1, 2], [1, 1] // [2, 3] export function add(l, r) { return [l[0] + r[0], l[1] + r[1]]; } // Subtract two vectors. // // avocado> Vector.sub [9, 5], [5, 2] // [4, 3] export function sub(l, r) { return [l[0] - r[0], l[1] - r[1]]; } // Multiply two vectors. // // avocado> Vector.mul [3, 5], [5, 5] // [15, 25] export function mul(l, r) { return [l[0] * r[0], l[1] * r[1]]; } // Divide two vectors. // // avocado> Vector.div [15, 5], [5, 5] // [3, 1] export function div(l, r) { return [l[0] / r[0], l[1] / r[1]]; } // Modulo divide two vectors. // // avocado> Vector.mod [13, 6], [5, 5] // [3, 1] export function mod(l, r) { return [l[0] % r[0], l[1] % r[1]]; } // Get the magnitude between two point vectors. // // avocado> Vector.magnitude [0, 0], [1, 1] // 1.4142135623730951 export function magnitude (l, r) { const xd = l[0] - r[0]; const yd = l[1] - r[1]; return Math.sqrt(xd * xd + yd * yd); } // Get the minimum values from two vectors. // // avocado> Vector.min [-10, 10], [0, 0] // [-10, 0] export function min(l, r) { return [Math.min(l[0], r[0]), Math.min(l[1], r[1])]; } // Get the maximum values from two vectors. // // avocado> Vector.max [-10, 10], [0, 0] // [0, 10] export function max(l, r) { return [Math.max(l[0], r[0]), Math.max(l[1], r[1])]; } // Clamp a vector's axes using a min vector and a max vector. // // avocado> Vector.clamp [-10, 10], [0, 0], [5, 5] // [0, 5] export function clamp(v, min_, max_) { return min(max_, max(min_, v)); } // Returns a deep copy of the vector. // // avocado> vector = [0, 0] // avocado> otherVectory Vector.copy vector // avocado> vector is otherVector // false export function copy(v) { return [v[0], v[1]]; } // Check whether a vector equals another vector. // // avocado> Vector.equals [4, 4], [5, 4] // false // // avocado> Vector.equals [4, 4], [4, 4] // true export function equals(l, r) { return l[0] === r[0] && l[1] === r[1]; } // Checks whether a vector is [0, 0]. // // avocado> Vector.zero [0, 0] // true // // avocado> Vector.zero [0, 1] // false export function isZero(v) { return v[0] === 0 && v[1] === 0; } // Round both axes of a vector. // // avocado> Vector.round [3.14, 4.70] // [3, 5] export function round(v) { return [Math.round(v[0]), Math.round(v[1])]; } // Get the dot product of two vectors. // // avocado> Vector.dot [2, 3], [4, 5] // 23 export function dot(l, r) { return l[0] * r[0] + l[1] * r[1]; } // Get a hypotenuse unit vector. If an origin vector is passed in, the // hypotenuse is derived from the distance to the origin. // // avocado> Vector.hypotenuse [5, 5], [6, 7] // [-0.4472135954999579, -0.8944271909999159] // // avocado> Vector.hypotenuse [.5, .7] // [0.5812381937190965, 0.813733471206735] export function hypotenuse(unitOrDestination, origin) { let distanceOrUnit = origin ? sub(unitOrDestination, origin) : unitOrDestination; const dp = dot(distanceOrUnit, distanceOrUnit); if (0 === dp) { return [0, 0]; } const hypotenuse = scale(distanceOrUnit, 1 / Math.sqrt(dp)); // Don't let NaN poison our equations. return [ NaN === hypotenuse[0] ? 0 : hypotenuse[0], NaN === hypotenuse[1] ? 0 : hypotenuse[1], ]; } // Get the absolute values of the axes of a vector. // // avocado> Vector.abs [23, -5.20] // [23, 5.20] export function abs(v) { return [Math.abs(v[0]), Math.abs(v[1])]; } // Floor both axes of a vector. // // avocado> Vector.floor [3.14, 4.70] // [3, 4] export function floor(v) { return [Math.floor(v[0]), Math.floor(v[1])]; } // Ceiling both axes of a vector. // // avocado> Vector.floor [3.14, 4.70] // [3, 4] export function ceil(v) { return [Math.ceil(v[0]), Math.ceil(v[1])]; } // Get the area a vector. // // avocado> Vector.area [3, 6] // 18 export function area(v) { return v[0] * v[1]; } // Checks whether a vector is null. A vector is null if either axis is 0. // The algorithm prefers horizontal directions to vertical; if you move // up-right or down-right you'll face right. // // avocado> Vector.isNull [1, 0] // true // // avocado> Vector.isNull [1, 1] // false export function isNull(v) { if (!v) { return true; } if (2 !== v.length) { return true; } return 0 === v[0] || 0 === v[1]; } export function overshot(position, hypotenuse, destination) { const overshot = [false, false]; for (const i = 0; i < 2; ++i) { if (hypotenuse[i] < 0) { if (position[i] < destination[i]) { overshot[i] = true; } } else if (hypotenuse[i] > 0) { if (position[i] > destination[i]) { overshot[i] = true; } } } return overshot; } // Convert a vector to a 4-direction. A 4-direction is: // // * 0: Up // * 1: Right // * 2: Down // * 3: Left // // avocado> Vector.toDirection4 [0, 1] // 2 // // avocado> Vector.toDirection4 [1, 0] // 1 export function toDirection4(vector) { vector = hypotenuse(vector); const x = Math.abs(vector[0]) - SQRT_2_2; if (x > 0 && x < SQRT_2_2) { return vector[0] > 0 ? 1 : 3; } else { return vector[1] > 0 ? 2 : 0; } } export function angle(v) { return Math.atan2(v[1], v[0]); } // Convert a vector to an 8-direction. An 8-direction is: // // * 0: Up // * 1: Right // * 2: Down // * 3: Left // * 4: Up-Right // * 5: Down-Right // * 6: Down-Left // * 7: Up-Left // // avocado> Vector.toDirection8 [1, 1] // 5 // // avocado> Vector.toDirection8 [1, 0] // 1 export function toDirection8(v) { v = hypotenuse(v); // Orient radians. let rad = (TWO_PI + angle(v)) % TWO_PI; rad = (rad + HALF_PI) % TWO_PI; // Truncate. rad = Math.floor(rad * 100000) / 100000; const stepStart = TWO_PI - EIGHTH_PI; // Clockwise direction map. const directions = [0, 4, 1, 5, 2, 6, 3, 7]; for (const direction of directions) { let stepEnd = (stepStart + QUARTER_PI) % TWO_PI stepEnd = Math.floor(stepEnd * 100000) / 100000; if (rad >= stepStart && rad < stepEnd) { return direction; } stepStart = stepEnd; } return 0; } // Convert a vector to a *directionCount*-direction. // // avocado> Vector.toDirection [0, 1], 4 // 2 export function toDirection(vector, directionCount) { switch (directionCount) { case 1: return 0; case 4: return toDirection4(vector); case 8: return toDirection8(vector); default: throw new Error `Unsupported conversion of vector to ${ directionCount }-direction.`; } } export function fromDirection(direction) { switch (direction) { case 0: return [0, -1]; case 1: return [1, 0]; case 2: return [0, 1]; case 3: return [-1, 0]; case 4: return hypotenuse([1, -1]); case 5: return hypotenuse([1, 1]); case 6: return hypotenuse([-1, 1]); case 7: return hypotenuse([-1, -1]); } } export function interpolate(ultimate, actual, easing = 0) { if (0 === easing) { return ultimate; } const magnitude = magnitude(ultimate, actual); if (0 === magnitude) { return ultimate; } return add( actual, scale( hypotenuse(ultimate, actual), magnitude / easing ) ); } export function fromObject(object) { return [object.x, object.y]; } // Convert a vector to an object. // // avocado> Vector.toObject [3, 4] // {x: 3, y: 4} export function toObject(v) { return {x: v[0], y: v[1]}; }