scrawl-canvas
Version:
Responsive, interactive and more accessible HTML5 canvas elements. Scrawl-canvas is a JavaScript library designed to make using the HTML5 canvas element easier, and more fun
368 lines (234 loc) • 7.93 kB
JavaScript
// # Vector factory
// Scrawl-canvas uses vector objects for some of its calculations - in particular for calculating rotations. These objects are not stored in the library; rather, they are kept in a __vector pool__ and pulled from it when required.
// #### Imports
import { constructors } from '../core/library.js';
import { doCreate, isa_number, isa_obj, xt, xta, Ωempty } from '../helper/utilities.js';
// Shared constants
import { _atan2, _cos, _isArray, _sin, _sqrt, T_VECTOR } from '../helper/shared-vars.js';
// Local constants (none defined)
// #### Vector constructor
const Vector = function (x, y, z) {
this.x = 0;
this.y = 0;
this.z = 0;
if (xt(x)) this.set(x, y, z);
return this;
};
// #### Vector prototype
const P = Vector.prototype = doCreate();
P.type = T_VECTOR;
// #### Mixins
// Vector objects do not use mixins - they are regular Javascript objects. As such, they do not possess packet, clone or kill functionality.
// #### Vector attributes
// The __x__, __y__ and __z__ attributes are set in the Vector constructor - the Vector object does not have a `defs` object.
// #### Get, Set, deltaSet
// Vector attributes can be retrieved directly - `myvector.x`, etc. The following convenience getters are defined on the Vector prototype, and return an Array
P.getXYCoordinate = function () {
return [this.x, this.y];
};
P.getXYZCoordinate = function () {
return [this.x, this.y, this.z];
};
// Vector attributes can be set directly - `myvector.x = 0`, etc. The following convenience setters are defined on the Vector prototype
P.setX = function (x) {
if (xt(x) && isa_number(x)) this.x = x;
return this;
};
P.setY = function (y) {
if (xt(y) && isa_number(y)) this.y = y;
return this;
};
P.setZ = function (z) {
if (xt(z) && isa_number(z)) this.z = z;
return this;
};
P.setXY = function (x, y) {
if (xta(x, y) && isa_number(x) && isa_number(y)) {
this.x = x;
this.y = y;
}
return this;
};
// The Vector `set` function is overloaded. It can accept the following arguments:
// + `set(x-Number, y-Number [, z-Number])` - Number arguments
// + `set([x-Number, y-Number [, z-Number]])` - an Array of Numbers
// + `set({x:Number, y:Number, z:Number})` - a Javascript object containing x, y (and z) attributes
// + `set(Vector)` - a Vector object
P.set = function (x, y, z) {
if (isa_obj(x)) return this.setFromVector(x);
if (_isArray(x)) return this.setFromArray(x);
if (xta(x, y)) return this.setFromArray([x, y, z]);
return this;
};
P.setFromArray = function (args) {
if (_isArray(args)) {
const [x, y, z] = args;
if (isa_number(x)) this.x = x;
if (isa_number(y)) this.y = y;
if (isa_number(z)) this.z = z;
}
return this;
};
P.setFromVector = function (item) {
if (isa_obj(item)) {
const {x, y, z} = item;
if (isa_number(x)) this.x = x;
if (isa_number(y)) this.y = y;
if (isa_number(z)) this.z = z;
}
return this;
};
// #### Prototype functions
// Set the Vector attributes to their default values
P.zero = function () {
this.x = 0;
this.y = 0;
this.z = 0;
return this;
};
// Add a Vector, or an Array of Number values, to this Vector
P.vectorAdd = function (item = Ωempty) {
if (_isArray(item)) return this.vectorAddArray(item);
const {x, y, z} = item;
if (isa_number(x)) this.x += x;
if (isa_number(y)) this.y += y;
if (isa_number(z)) this.z += z;
return this;
};
P.vectorAddArray = function (item = []) {
const [x, y, z] = item;
if (isa_number(x)) this.x += x;
if (isa_number(y)) this.y += y;
if (isa_number(z)) this.z += z;
return this;
};
// Subtract a Vector, or an Array of Number values, from this Vector
P.vectorSubtract = function (item = Ωempty) {
if (_isArray(item)) return this.vectorSubtractArray(item);
const {x, y, z} = item;
if (isa_number(x)) this.x -= x;
if (isa_number(y)) this.y -= y;
if (isa_number(z)) this.z -= z;
return this;
};
P.vectorSubtractArray = function (item) {
const [x, y, z] = item;
if (isa_number(x)) this.x -= x;
if (isa_number(y)) this.y -= y;
if (isa_number(z)) this.z -= z;
return this;
};
// Multiply all Vector attributes by the argument Number
P.scalarMultiply = function (item) {
if (!isa_number(item)) item = 1;
this.x *= item;
this.y *= item;
this.z *= item;
return this;
};
P.vectorMultiply = function (item = Ωempty) {
if (_isArray(item)) return this.vectorMultiplyArray(item);
const {x, y, z} = item;
if (isa_number(x)) this.x *= x;
if (isa_number(y)) this.y *= y;
if (isa_number(z)) this.z *= z;
return this;
};
P.vectorMultiplyArray = function (item) {
const [x, y, z] = item;
if (isa_number(x)) this.x *= x;
if (isa_number(y)) this.y *= y;
if (isa_number(z)) this.z *= z;
return this;
};
// Divide all Vector attributes by the argument Number
P.scalarDivide = function (item) {
if (isa_number(item) && item) {
this.x /= item;
this.y /= item;
this.z /= item;
}
return this;
};
// Get the Vector's __magnitude__ value
P.getMagnitude = function () {
return _sqrt((this.x * this.x) + (this.y * this.y) + (this.z * this.z));
};
// Rotate a Vector by a given angle. Argument is a Number representing degrees, not radians.
P.rotate = function (angle) {
if (!isa_number(angle)) angle = 0;
let arg = _atan2(this.y, this.x);
arg += (angle * 0.01745329251);
const mag = this.getMagnitude();
this.x = mag * _cos(arg);
this.y = mag * _sin(arg);
return this;
};
// Change the numerical sign of all Vector attributes
P.reverse = function () {
this.x = -this.x;
this.y = -this.y;
this.z = -this.z;
return this;
};
// Normalize the Vector
P.normalize = function() {
const val = this.getMagnitude();
if (val > 0) {
this.x /= val;
this.y /= val;
this.z /= val;
}
return this;
};
// Dot product with Vector or [x,y,z]
P.getDot = function (item = Ωempty) {
if (_isArray(item)) {
const [x, y, z] = item;
return (isa_number(x) ? x : 0) * this.x
+ (isa_number(y) ? y : 0) * this.y
+ (isa_number(z) ? z : 0) * this.z;
}
const { x, y, z } = item;
return (isa_number(x) ? x : 0) * this.x
+ (isa_number(y) ? y : 0) * this.y
+ (isa_number(z) ? z : 0) * this.z;
};
// (Optional alias)
P.dot = P.getDot;
// this += scalar * other
P.vectorAddScaled = function (other = Ωempty, scalar = 1) {
if (_isArray(other)) {
const [x, y, z] = other;
if (isa_number(x)) this.x += scalar * x;
if (isa_number(y)) this.y += scalar * y;
if (isa_number(z)) this.z += scalar * z;
return this;
}
const { x, y, z } = other;
if (isa_number(x)) this.x += scalar * x;
if (isa_number(y)) this.y += scalar * y;
if (isa_number(z)) this.z += scalar * z;
return this;
};
// #### Vector pool
// An attempt to reuse vectors rather than constantly creating and deleting them
const vectorPool = [];
// `exported function` - retrieve a Vector from the vector pool
export const requestVector = function (x, y, z) {
if (!vectorPool.length) vectorPool.push(new Vector());
const v = vectorPool.pop();
v.set(x, y, z);
return v
};
// `exported function` - return a Vector to the vector pool. Failing to return Vectors to the pool may lead to more inefficient code and possible memory leaks.
export const releaseVector = function (...args) {
args.forEach(v => {
if (v && v.type === T_VECTOR) vectorPool.push(v.zero());
});
};
// #### Factory
export const makeVector = function (x, y, z) {
return new Vector(x, y, z);
};
constructors.Vector = Vector;