scrawl-canvas
Version:
Version 8.9.4 - 19 Nov 2022
252 lines (167 loc) • 6.37 kB
JavaScript
// # Coordinate factory
// Scrawl-canvas uses Coordinate Arrays extensively throughout the code base - in particular to hold artefact coordinate [x, y] and dimensional [w, h] data. The Coordinate factory does not care whether these values are presented as Numbers or Strings (or a mixture of both) - it leaves such functionality to other mixins (in particular the [position](../mixin/position.html), [dom](../mixin/dom.html) and [entity](../mixin/entity.html) mixins) and factories.
// #### Imports
import { constructors } from '../core/library.js';
import { xt, isa_number } from '../core/utilities.js';
// #### Coordinate constructor
const Coordinate = function (items, y) {
let coords = [0, 0];
Object.setPrototypeOf(coords, Coordinate.prototype);
if (items) coords.set(items, y);
return coords;
};
// #### Coordinate prototype
let P = Coordinate.prototype = Object.create(Array.prototype);
P.constructor = Coordinate;
P.type = 'Coordinate';
// #### Mixins
// Coordinate Arrays do not use mixins - they are regular Javascript Arrays. As such, they do not possess packet, clone or kill functionality.
// #### Coordinate attributes
// There are no attributes. The constructor returns an Array containing two members, whose prototype object includes functions for manipulating those members in various ways.
// #### Get, Set, deltaSet
// The Array members can be set using a `set` function, which is overloaded as follows:
// + `mycoordinate.set(Coordinate)` - use another Coordinate Array to set this Coordinate members' values
// + `mycoordinate.set(Array)` - - use a Javascript Array to set this Coordinate members' values
// + `mycoordinate.set(Quaternion)` - use a Quaternion object (specifically, its __v__ Vector object attribute) to set this Coordinate members' values
// + `mycoordinate.set(Vector)` - use a Vector object to set this Coordinate members' values
// + `mycoordinate.set(a:Number|String, b:Number|String)` - supply two arguments to the function
P.set = function (items, y) {
if (xt(items)) {
if (items.type === 'Coordinate') this.setFromArray(items);
else if (items.type === 'Vector') this.setFromVector(items);
else if (items.type === 'Quaternion') this.setFromVector(items.v);
else if (Array.isArray(items)) this.setFromArray(items);
else if (xt(y)) this.setFromArray([items, y]);
}
return this;
};
P.setFromArray = function (item) {
this[0] = item[0];
this[1] = item[1];
return this;
};
P.setFromVector = function (item) {
this[0] = item.x;
this[1] = item.y;
return this;
};
// #### Prototype functions
// Set the Coordinate array to `[0, 0]`
P.zero = function () {
this[0] = 0;
this[1] = 0;
return this;
};
// Add a Vector object's __x__ and __y__ attributes to the Coordinate members' values
P.vectorAdd = function (item) {
this[0] += item.x;
this[1] += item.y;
return this;
};
// Subtract a Vector object's __x__ and __y__ attributes from the Coordinate members' values
P.vectorSubtract = function (item) {
this[0] -= item.x;
this[1] -= item.y;
return this;
};
// Array addition - argument is a `[Number, Number]` Array
P.add = function (item) {
this[0] += item[0];
this[1] += item[1];
return this;
};
// Array subtraction - argument is a `[Number, Number]` Array
P.subtract = function (item) {
this[0] -= item[0];
this[1] -= item[1];
return this;
};
// Array multiplication - argument is a `[Number, Number]` Array
P.multiply = function (item) {
this[0] *= item[0];
this[1] *= item[1];
return this;
};
// Array division - argument is a `[Number, Number]` Array
P.divide = function (item) {
let [x, y] = item;
if (x && y) {
this[0] /= x;
this[1] /= y;
}
return this;
};
// Multiply both Coordinate Array members by the argument Number
P.scalarMultiply = function (item) {
this[0] *= item;
this[1] *= item;
return this;
};
// Divide both Coordinate Array members by the argument Number
P.scalarDivide = function (item) {
if (item && item.toFixed) {
this[0] /= item;
this[1] /= item;
}
return this;
};
// Get the Array's __magnitude__ value (treating the Coordinate as if it was a 2D vector)
P.getMagnitude = function () {
return Math.hypot(this[0], this[1]);
};
// Rotate the Coordinate by the argument number (treating the Coordinate as if it was a 2D vector) - the argument represents degrees, not radians
P.rotate = function (angle) {
let x = this[0],
y = this[1],
r0, r1;
r0 = Math.atan2(y, x);
r0 += (angle * 0.01745329251);
r1 = Math.hypot(x, y);
this[0] = r1 * Math.cos(r0);
this[1] = r1 * Math.sin(r0);
return this;
};
// Reverse the numerical sign on each of the Array's members
P.reverse = function () {
this[0] = -this[0];
this[1] = -this[1];
return this;
};
// Get the Array's __dot product__ value (treating the Coordinate as if it was a 2D vector)
P.getDotProduct = function (coord) {
return (this[0] * coord[0]) + (this[1] * coord[1]);
};
// __Normalize__ the Array's members (treating the Coordinate as if it was a 2D vector)
P.normalize = function() {
let val = this.getMagnitude();
if (val > 0) {
this[0] /= val;
this[1] /= val;
}
return this;
};
// #### Coordinate pool
// An attempt to reuse coordinates rather than constantly creating and deleting them
const coordinatePool = [];
// `exported function` - retrieve a Coordinate from the coordinate pool
const requestCoordinate = function (items, y) {
if (!coordinatePool.length) coordinatePool.push(new Coordinate());
let c = coordinatePool.shift();
c.set(items, y);
return c
};
// `exported function` - return a Coordinate to the coordinate pool. Failing to return Coordinates to the pool may lead to more inefficient code and possible memory leaks.
const releaseCoordinate = function (coordinate) {
if (coordinate && coordinate.type === 'Coordinate') coordinatePool.push(coordinate.zero());
};
// #### Factory
const makeCoordinate = function (items, y) {
return new Coordinate(items, y);
};
constructors.Coordinate = Coordinate;
// #### Exports
export {
makeCoordinate,
requestCoordinate,
releaseCoordinate,
};