2019-11-03 10:40:08 -06:00
|
|
|
import {Range as MathRange} from '../range';
|
|
|
|
import {randomNumber} from '../math';
|
|
|
|
|
2019-03-21 23:13:46 -05:00
|
|
|
export {VectorMixin as Mixin} from './mixin';
|
2019-03-17 23:45:48 -05:00
|
|
|
|
|
|
|
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) {
|
2019-03-19 18:04:51 -05:00
|
|
|
return [l[0] * r[0], l[1] * r[1]];
|
2019-03-17 23:45:48 -05:00
|
|
|
}
|
|
|
|
|
2019-07-23 00:18:59 -05:00
|
|
|
export function distance(l, r) {
|
|
|
|
const xd = l[0] - r[0];
|
|
|
|
const yd = l[1] - r[1];
|
|
|
|
return Math.sqrt(xd * xd + yd * yd);
|
|
|
|
}
|
|
|
|
|
2019-03-17 23:45:48 -05:00
|
|
|
// 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]];
|
|
|
|
}
|
|
|
|
|
2019-03-22 11:24:07 -05:00
|
|
|
// Get the magnitude between two point vectors.
|
2019-03-17 23:45:48 -05:00
|
|
|
//
|
2019-03-22 11:24:07 -05:00
|
|
|
// avocado> Vector.magnitude [0, 0], [1, 1]
|
2019-03-17 23:45:48 -05:00
|
|
|
// 1.4142135623730951
|
2019-03-27 18:06:55 -05:00
|
|
|
export function magnitude(l, r) {
|
|
|
|
return Math.sqrt(dot(sub(l, r)));
|
2019-03-17 23:45:48 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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];
|
|
|
|
}
|
|
|
|
|
2019-03-24 04:02:42 -05:00
|
|
|
export function equalsClose(l, r, epsilon = 0.000001) {
|
|
|
|
return (
|
|
|
|
(Math.abs(l[0] - r[0]) < epsilon)
|
|
|
|
&& (Math.abs(l[1] - r[1]) < epsilon)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-03-17 23:45:48 -05:00
|
|
|
// 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])];
|
|
|
|
}
|
|
|
|
|
2019-03-27 18:06:55 -05:00
|
|
|
// Get the dot product of two vectors. If only one gievn, dupe it.
|
2019-03-17 23:45:48 -05:00
|
|
|
//
|
|
|
|
// avocado> Vector.dot [2, 3], [4, 5]
|
|
|
|
// 23
|
|
|
|
export function dot(l, r) {
|
2019-03-27 18:06:55 -05:00
|
|
|
if (!r) {
|
|
|
|
r = l;
|
|
|
|
}
|
2019-03-17 23:45:48 -05:00
|
|
|
return l[0] * r[0] + l[1] * r[1];
|
|
|
|
}
|
|
|
|
|
2019-03-27 17:53:18 -05:00
|
|
|
// Get a unit vector.
|
2019-03-17 23:45:48 -05:00
|
|
|
//
|
2019-03-27 17:53:18 -05:00
|
|
|
// avocado> Vector.normalize [.5, .7]
|
2019-03-17 23:45:48 -05:00
|
|
|
// [0.5812381937190965, 0.813733471206735]
|
2019-03-27 17:53:18 -05:00
|
|
|
export function normalize(v) {
|
2019-03-27 18:06:55 -05:00
|
|
|
const dp = dot(v);
|
2019-03-17 23:45:48 -05:00
|
|
|
if (0 === dp) {
|
|
|
|
return [0, 0];
|
|
|
|
}
|
2019-03-27 17:53:18 -05:00
|
|
|
const normalized = scale(v, 1 / Math.sqrt(dp));
|
2019-03-17 23:45:48 -05:00
|
|
|
// Don't let NaN poison our equations.
|
|
|
|
return [
|
2019-03-27 17:53:18 -05:00
|
|
|
NaN === normalized[0] ? 0 : normalized[0],
|
|
|
|
NaN === normalized[1] ? 0 : normalized[1],
|
2019-03-17 23:45:48 -05:00
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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];
|
|
|
|
}
|
|
|
|
|
2019-03-27 17:53:18 -05:00
|
|
|
export function overshot(position, normal, destination) {
|
2019-03-17 23:45:48 -05:00
|
|
|
const overshot = [false, false];
|
2019-03-28 20:35:07 -05:00
|
|
|
for (let i = 0; i < 2; ++i) {
|
2019-03-27 17:53:18 -05:00
|
|
|
if (normal[i] < 0) {
|
2019-03-17 23:45:48 -05:00
|
|
|
if (position[i] < destination[i]) {
|
|
|
|
overshot[i] = true;
|
|
|
|
}
|
|
|
|
}
|
2019-03-27 17:53:18 -05:00
|
|
|
else if (normal[i] > 0) {
|
2019-03-17 23:45:48 -05:00
|
|
|
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) {
|
2019-03-27 17:53:18 -05:00
|
|
|
vector = normalize(vector);
|
2019-03-17 23:45:48 -05:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-09 16:44:13 -06:00
|
|
|
export function toAngle(v) {
|
|
|
|
return (Math.atan2(-v[1], v[0]) + Math.PI * 2) % (Math.PI * 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
export function fromAngle(angle) {
|
|
|
|
return [Math.cos(angle), -Math.sin(angle)];
|
2019-03-22 11:23:55 -05:00
|
|
|
}
|
|
|
|
|
2019-03-17 23:45:48 -05:00
|
|
|
// 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) {
|
2019-03-27 17:53:18 -05:00
|
|
|
v = normalize(v);
|
2019-03-17 23:45:48 -05:00
|
|
|
// Orient radians.
|
2019-03-22 11:23:55 -05:00
|
|
|
let rad = (TWO_PI + angle(v)) % TWO_PI;
|
2019-03-17 23:45:48 -05:00
|
|
|
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];
|
2019-03-27 17:53:18 -05:00
|
|
|
case 4: return normalize([1, -1]);
|
|
|
|
case 5: return normalize([1, 1]);
|
|
|
|
case 6: return normalize([-1, 1]);
|
|
|
|
case 7: return normalize([-1, -1]);
|
2019-03-17 23:45:48 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-27 16:54:43 -05:00
|
|
|
export function directionalProjection(direction, vector) {
|
|
|
|
switch (direction) {
|
|
|
|
case 0:
|
|
|
|
return [vector[0], -vector[1]];
|
|
|
|
case 1:
|
|
|
|
return [vector[1], vector[0]];
|
|
|
|
case 2:
|
|
|
|
return [vector[0], vector[1]];
|
|
|
|
case 3:
|
|
|
|
return [-vector[1], vector[0]];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-17 23:45:48 -05:00
|
|
|
export function interpolate(ultimate, actual, easing = 0) {
|
|
|
|
if (0 === easing) {
|
|
|
|
return ultimate;
|
|
|
|
}
|
2019-04-05 07:16:32 -05:00
|
|
|
const magnitude_ = magnitude(ultimate, actual);
|
|
|
|
if (0 === magnitude_) {
|
2019-03-17 23:45:48 -05:00
|
|
|
return ultimate;
|
|
|
|
}
|
|
|
|
return add(
|
|
|
|
actual,
|
|
|
|
scale(
|
2019-04-05 07:16:32 -05:00
|
|
|
normalize(sub(ultimate, actual)),
|
|
|
|
magnitude_ / easing
|
2019-03-17 23:45:48 -05:00
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
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]};
|
|
|
|
}
|
2019-09-23 19:39:01 -05:00
|
|
|
|
|
|
|
export function unpackFromUint32(uint32) {
|
|
|
|
return [
|
|
|
|
uint32 & 0xFFFF,
|
|
|
|
uint32 >> 16,
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
export function packToUint32(v) {
|
2019-10-10 23:50:02 -05:00
|
|
|
const x = Math.min(65535, Math.max(0, v[0]));
|
|
|
|
const y = Math.min(65535, Math.max(0, v[1]));
|
|
|
|
return (y << 16) | x;
|
2019-09-23 19:39:01 -05:00
|
|
|
}
|
2019-11-03 10:40:08 -06:00
|
|
|
|
|
|
|
export class Range extends MathRange {
|
|
|
|
|
|
|
|
value() {
|
|
|
|
if ('undefined' === typeof this.max) {
|
|
|
|
return this.min;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return [
|
|
|
|
randomNumber(this.min[0], this.max[0], false),
|
|
|
|
randomNumber(this.min[1], this.max[1], false),
|
|
|
|
];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2020-06-19 15:31:35 -05:00
|
|
|
|
2020-06-23 11:19:59 -05:00
|
|
|
export function behaviorTypes() {
|
2020-06-19 15:31:35 -05:00
|
|
|
return {
|
|
|
|
type: 'Vector',
|
|
|
|
children: {
|
|
|
|
fromDirection: {
|
|
|
|
type: 'vector',
|
|
|
|
label: 'Vector from direction $1.',
|
|
|
|
args: [
|
|
|
|
['direction', {
|
|
|
|
type: 'number',
|
|
|
|
}],
|
|
|
|
],
|
|
|
|
},
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|