Skip to content

Instantly share code, notes, and snippets.

@NaridaL
Created January 26, 2018 00:56
Show Gist options
  • Save NaridaL/3ed9878c5911bafb321bd9a9b88fa18a to your computer and use it in GitHub Desktop.
Save NaridaL/3ed9878c5911bafb321bd9a9b88fa18a to your computer and use it in GitHub Desktop.
This file has been truncated, but you can view the full file.
(function(l, i, v, e) { v = l.createElement(i); v.async = 1; v.src = '//' + (location.host || 'localhost').split(':')[0] + ':35729/livereload.js?snipver=1'; e = l.getElementsByTagName(i)[0]; e.parentNode.insertBefore(v, e)})(document, 'script');
var viewer = (function (exports,svgPathdata) {
'use strict';
/*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
MERCHANTABLITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
***************************************************************************** */
/* global Reflect, Promise */
function __awaiter(thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
function createCommonjsModule(fn, module) {
return module = { exports: {} }, fn(module, module.exports), module.exports;
}
var chroma = createCommonjsModule(function (module, exports) {
/**
* @license
*
* chroma.js - JavaScript library for color conversions
*
* Copyright (c) 2011-2017, Gregor Aisch
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. The name Gregor Aisch may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL GREGOR AISCH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
(function() {
var Color, DEG2RAD, LAB_CONSTANTS, PI, PITHIRD, RAD2DEG, TWOPI, _guess_formats, _guess_formats_sorted, _input, _interpolators, abs, atan2, bezier, blend, blend_f, brewer, burn, chroma, clip_rgb, cmyk2rgb, colors, cos, css2rgb, darken, dodge, each, floor, hcg2rgb, hex2rgb, hsi2rgb, hsl2css, hsl2rgb, hsv2rgb, interpolate, interpolate_hsx, interpolate_lab, interpolate_num, interpolate_rgb, lab2lch, lab2rgb, lab_xyz, lch2lab, lch2rgb, lighten, limit, log, luminance_x, m, max, multiply, normal, num2rgb, overlay, pow, rgb2cmyk, rgb2css, rgb2hcg, rgb2hex, rgb2hsi, rgb2hsl, rgb2hsv, rgb2lab, rgb2lch, rgb2luminance, rgb2num, rgb2temperature, rgb2xyz, rgb_xyz, rnd, root, round, screen, sin, sqrt, temperature2rgb, type, unpack, w3cx11, xyz_lab, xyz_rgb,
slice = [].slice;
type = (function() {
/*
for browser-safe type checking+
ported from jQuery's $.type
*/
var classToType, len, name, o, ref;
classToType = {};
ref = "Boolean Number String Function Array Date RegExp Undefined Null".split(" ");
for (o = 0, len = ref.length; o < len; o++) {
name = ref[o];
classToType["[object " + name + "]"] = name.toLowerCase();
}
return function(obj) {
var strType;
strType = Object.prototype.toString.call(obj);
return classToType[strType] || "object";
};
})();
limit = function(x, min, max) {
if (min == null) {
min = 0;
}
if (max == null) {
max = 1;
}
if (x < min) {
x = min;
}
if (x > max) {
x = max;
}
return x;
};
unpack = function(args) {
if (args.length >= 3) {
return [].slice.call(args);
} else {
return args[0];
}
};
clip_rgb = function(rgb) {
var i, o;
rgb._clipped = false;
rgb._unclipped = rgb.slice(0);
for (i = o = 0; o < 3; i = ++o) {
if (i < 3) {
if (rgb[i] < 0 || rgb[i] > 255) {
rgb._clipped = true;
}
if (rgb[i] < 0) {
rgb[i] = 0;
}
if (rgb[i] > 255) {
rgb[i] = 255;
}
} else if (i === 3) {
if (rgb[i] < 0) {
rgb[i] = 0;
}
if (rgb[i] > 1) {
rgb[i] = 1;
}
}
}
if (!rgb._clipped) {
delete rgb._unclipped;
}
return rgb;
};
PI = Math.PI, round = Math.round, cos = Math.cos, floor = Math.floor, pow = Math.pow, log = Math.log, sin = Math.sin, sqrt = Math.sqrt, atan2 = Math.atan2, max = Math.max, abs = Math.abs;
TWOPI = PI * 2;
PITHIRD = PI / 3;
DEG2RAD = PI / 180;
RAD2DEG = 180 / PI;
chroma = function() {
if (arguments[0] instanceof Color) {
return arguments[0];
}
return (function(func, args, ctor) {
ctor.prototype = func.prototype;
var child = new ctor, result = func.apply(child, args);
return Object(result) === result ? result : child;
})(Color, arguments, function(){});
};
_interpolators = [];
if (('object' !== "undefined" && module !== null) && (module.exports != null)) {
module.exports = chroma;
}
if (typeof undefined === 'function' && undefined.amd) {
undefined([], function() {
return chroma;
});
} else {
root = 'object' !== "undefined" && exports !== null ? exports : this;
root.chroma = chroma;
}
chroma.version = '1.3.4';
_input = {};
_guess_formats = [];
_guess_formats_sorted = false;
Color = (function() {
function Color() {
var arg, args, chk, len, len1, me, mode, o, w;
me = this;
args = [];
for (o = 0, len = arguments.length; o < len; o++) {
arg = arguments[o];
if (arg != null) {
args.push(arg);
}
}
mode = args[args.length - 1];
if (_input[mode] != null) {
me._rgb = clip_rgb(_input[mode](unpack(args.slice(0, -1))));
} else {
if (!_guess_formats_sorted) {
_guess_formats = _guess_formats.sort(function(a, b) {
return b.p - a.p;
});
_guess_formats_sorted = true;
}
for (w = 0, len1 = _guess_formats.length; w < len1; w++) {
chk = _guess_formats[w];
mode = chk.test.apply(chk, args);
if (mode) {
break;
}
}
if (mode) {
me._rgb = clip_rgb(_input[mode].apply(_input, args));
}
}
if (me._rgb == null) {
console.warn('unknown format: ' + args);
}
if (me._rgb == null) {
me._rgb = [0, 0, 0];
}
if (me._rgb.length === 3) {
me._rgb.push(1);
}
}
Color.prototype.toString = function() {
return this.hex();
};
Color.prototype.clone = function() {
return chroma(me._rgb);
};
return Color;
})();
chroma._input = _input;
/**
ColorBrewer colors for chroma.js
Copyright (c) 2002 Cynthia Brewer, Mark Harrower, and The
Pennsylvania State University.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
@preserve
*/
chroma.brewer = brewer = {
OrRd: ['#fff7ec', '#fee8c8', '#fdd49e', '#fdbb84', '#fc8d59', '#ef6548', '#d7301f', '#b30000', '#7f0000'],
PuBu: ['#fff7fb', '#ece7f2', '#d0d1e6', '#a6bddb', '#74a9cf', '#3690c0', '#0570b0', '#045a8d', '#023858'],
BuPu: ['#f7fcfd', '#e0ecf4', '#bfd3e6', '#9ebcda', '#8c96c6', '#8c6bb1', '#88419d', '#810f7c', '#4d004b'],
Oranges: ['#fff5eb', '#fee6ce', '#fdd0a2', '#fdae6b', '#fd8d3c', '#f16913', '#d94801', '#a63603', '#7f2704'],
BuGn: ['#f7fcfd', '#e5f5f9', '#ccece6', '#99d8c9', '#66c2a4', '#41ae76', '#238b45', '#006d2c', '#00441b'],
YlOrBr: ['#ffffe5', '#fff7bc', '#fee391', '#fec44f', '#fe9929', '#ec7014', '#cc4c02', '#993404', '#662506'],
YlGn: ['#ffffe5', '#f7fcb9', '#d9f0a3', '#addd8e', '#78c679', '#41ab5d', '#238443', '#006837', '#004529'],
Reds: ['#fff5f0', '#fee0d2', '#fcbba1', '#fc9272', '#fb6a4a', '#ef3b2c', '#cb181d', '#a50f15', '#67000d'],
RdPu: ['#fff7f3', '#fde0dd', '#fcc5c0', '#fa9fb5', '#f768a1', '#dd3497', '#ae017e', '#7a0177', '#49006a'],
Greens: ['#f7fcf5', '#e5f5e0', '#c7e9c0', '#a1d99b', '#74c476', '#41ab5d', '#238b45', '#006d2c', '#00441b'],
YlGnBu: ['#ffffd9', '#edf8b1', '#c7e9b4', '#7fcdbb', '#41b6c4', '#1d91c0', '#225ea8', '#253494', '#081d58'],
Purples: ['#fcfbfd', '#efedf5', '#dadaeb', '#bcbddc', '#9e9ac8', '#807dba', '#6a51a3', '#54278f', '#3f007d'],
GnBu: ['#f7fcf0', '#e0f3db', '#ccebc5', '#a8ddb5', '#7bccc4', '#4eb3d3', '#2b8cbe', '#0868ac', '#084081'],
Greys: ['#ffffff', '#f0f0f0', '#d9d9d9', '#bdbdbd', '#969696', '#737373', '#525252', '#252525', '#000000'],
YlOrRd: ['#ffffcc', '#ffeda0', '#fed976', '#feb24c', '#fd8d3c', '#fc4e2a', '#e31a1c', '#bd0026', '#800026'],
PuRd: ['#f7f4f9', '#e7e1ef', '#d4b9da', '#c994c7', '#df65b0', '#e7298a', '#ce1256', '#980043', '#67001f'],
Blues: ['#f7fbff', '#deebf7', '#c6dbef', '#9ecae1', '#6baed6', '#4292c6', '#2171b5', '#08519c', '#08306b'],
PuBuGn: ['#fff7fb', '#ece2f0', '#d0d1e6', '#a6bddb', '#67a9cf', '#3690c0', '#02818a', '#016c59', '#014636'],
Viridis: ['#440154', '#482777', '#3f4a8a', '#31678e', '#26838f', '#1f9d8a', '#6cce5a', '#b6de2b', '#fee825'],
Spectral: ['#9e0142', '#d53e4f', '#f46d43', '#fdae61', '#fee08b', '#ffffbf', '#e6f598', '#abdda4', '#66c2a5', '#3288bd', '#5e4fa2'],
RdYlGn: ['#a50026', '#d73027', '#f46d43', '#fdae61', '#fee08b', '#ffffbf', '#d9ef8b', '#a6d96a', '#66bd63', '#1a9850', '#006837'],
RdBu: ['#67001f', '#b2182b', '#d6604d', '#f4a582', '#fddbc7', '#f7f7f7', '#d1e5f0', '#92c5de', '#4393c3', '#2166ac', '#053061'],
PiYG: ['#8e0152', '#c51b7d', '#de77ae', '#f1b6da', '#fde0ef', '#f7f7f7', '#e6f5d0', '#b8e186', '#7fbc41', '#4d9221', '#276419'],
PRGn: ['#40004b', '#762a83', '#9970ab', '#c2a5cf', '#e7d4e8', '#f7f7f7', '#d9f0d3', '#a6dba0', '#5aae61', '#1b7837', '#00441b'],
RdYlBu: ['#a50026', '#d73027', '#f46d43', '#fdae61', '#fee090', '#ffffbf', '#e0f3f8', '#abd9e9', '#74add1', '#4575b4', '#313695'],
BrBG: ['#543005', '#8c510a', '#bf812d', '#dfc27d', '#f6e8c3', '#f5f5f5', '#c7eae5', '#80cdc1', '#35978f', '#01665e', '#003c30'],
RdGy: ['#67001f', '#b2182b', '#d6604d', '#f4a582', '#fddbc7', '#ffffff', '#e0e0e0', '#bababa', '#878787', '#4d4d4d', '#1a1a1a'],
PuOr: ['#7f3b08', '#b35806', '#e08214', '#fdb863', '#fee0b6', '#f7f7f7', '#d8daeb', '#b2abd2', '#8073ac', '#542788', '#2d004b'],
Set2: ['#66c2a5', '#fc8d62', '#8da0cb', '#e78ac3', '#a6d854', '#ffd92f', '#e5c494', '#b3b3b3'],
Accent: ['#7fc97f', '#beaed4', '#fdc086', '#ffff99', '#386cb0', '#f0027f', '#bf5b17', '#666666'],
Set1: ['#e41a1c', '#377eb8', '#4daf4a', '#984ea3', '#ff7f00', '#ffff33', '#a65628', '#f781bf', '#999999'],
Set3: ['#8dd3c7', '#ffffb3', '#bebada', '#fb8072', '#80b1d3', '#fdb462', '#b3de69', '#fccde5', '#d9d9d9', '#bc80bd', '#ccebc5', '#ffed6f'],
Dark2: ['#1b9e77', '#d95f02', '#7570b3', '#e7298a', '#66a61e', '#e6ab02', '#a6761d', '#666666'],
Paired: ['#a6cee3', '#1f78b4', '#b2df8a', '#33a02c', '#fb9a99', '#e31a1c', '#fdbf6f', '#ff7f00', '#cab2d6', '#6a3d9a', '#ffff99', '#b15928'],
Pastel2: ['#b3e2cd', '#fdcdac', '#cbd5e8', '#f4cae4', '#e6f5c9', '#fff2ae', '#f1e2cc', '#cccccc'],
Pastel1: ['#fbb4ae', '#b3cde3', '#ccebc5', '#decbe4', '#fed9a6', '#ffffcc', '#e5d8bd', '#fddaec', '#f2f2f2']
};
(function() {
var key, results;
results = [];
for (key in brewer) {
results.push(brewer[key.toLowerCase()] = brewer[key]);
}
return results;
})();
/**
X11 color names
http://www.w3.org/TR/css3-color/#svg-color
*/
w3cx11 = {
aliceblue: '#f0f8ff',
antiquewhite: '#faebd7',
aqua: '#00ffff',
aquamarine: '#7fffd4',
azure: '#f0ffff',
beige: '#f5f5dc',
bisque: '#ffe4c4',
black: '#000000',
blanchedalmond: '#ffebcd',
blue: '#0000ff',
blueviolet: '#8a2be2',
brown: '#a52a2a',
burlywood: '#deb887',
cadetblue: '#5f9ea0',
chartreuse: '#7fff00',
chocolate: '#d2691e',
coral: '#ff7f50',
cornflower: '#6495ed',
cornflowerblue: '#6495ed',
cornsilk: '#fff8dc',
crimson: '#dc143c',
cyan: '#00ffff',
darkblue: '#00008b',
darkcyan: '#008b8b',
darkgoldenrod: '#b8860b',
darkgray: '#a9a9a9',
darkgreen: '#006400',
darkgrey: '#a9a9a9',
darkkhaki: '#bdb76b',
darkmagenta: '#8b008b',
darkolivegreen: '#556b2f',
darkorange: '#ff8c00',
darkorchid: '#9932cc',
darkred: '#8b0000',
darksalmon: '#e9967a',
darkseagreen: '#8fbc8f',
darkslateblue: '#483d8b',
darkslategray: '#2f4f4f',
darkslategrey: '#2f4f4f',
darkturquoise: '#00ced1',
darkviolet: '#9400d3',
deeppink: '#ff1493',
deepskyblue: '#00bfff',
dimgray: '#696969',
dimgrey: '#696969',
dodgerblue: '#1e90ff',
firebrick: '#b22222',
floralwhite: '#fffaf0',
forestgreen: '#228b22',
fuchsia: '#ff00ff',
gainsboro: '#dcdcdc',
ghostwhite: '#f8f8ff',
gold: '#ffd700',
goldenrod: '#daa520',
gray: '#808080',
green: '#008000',
greenyellow: '#adff2f',
grey: '#808080',
honeydew: '#f0fff0',
hotpink: '#ff69b4',
indianred: '#cd5c5c',
indigo: '#4b0082',
ivory: '#fffff0',
khaki: '#f0e68c',
laserlemon: '#ffff54',
lavender: '#e6e6fa',
lavenderblush: '#fff0f5',
lawngreen: '#7cfc00',
lemonchiffon: '#fffacd',
lightblue: '#add8e6',
lightcoral: '#f08080',
lightcyan: '#e0ffff',
lightgoldenrod: '#fafad2',
lightgoldenrodyellow: '#fafad2',
lightgray: '#d3d3d3',
lightgreen: '#90ee90',
lightgrey: '#d3d3d3',
lightpink: '#ffb6c1',
lightsalmon: '#ffa07a',
lightseagreen: '#20b2aa',
lightskyblue: '#87cefa',
lightslategray: '#778899',
lightslategrey: '#778899',
lightsteelblue: '#b0c4de',
lightyellow: '#ffffe0',
lime: '#00ff00',
limegreen: '#32cd32',
linen: '#faf0e6',
magenta: '#ff00ff',
maroon: '#800000',
maroon2: '#7f0000',
maroon3: '#b03060',
mediumaquamarine: '#66cdaa',
mediumblue: '#0000cd',
mediumorchid: '#ba55d3',
mediumpurple: '#9370db',
mediumseagreen: '#3cb371',
mediumslateblue: '#7b68ee',
mediumspringgreen: '#00fa9a',
mediumturquoise: '#48d1cc',
mediumvioletred: '#c71585',
midnightblue: '#191970',
mintcream: '#f5fffa',
mistyrose: '#ffe4e1',
moccasin: '#ffe4b5',
navajowhite: '#ffdead',
navy: '#000080',
oldlace: '#fdf5e6',
olive: '#808000',
olivedrab: '#6b8e23',
orange: '#ffa500',
orangered: '#ff4500',
orchid: '#da70d6',
palegoldenrod: '#eee8aa',
palegreen: '#98fb98',
paleturquoise: '#afeeee',
palevioletred: '#db7093',
papayawhip: '#ffefd5',
peachpuff: '#ffdab9',
peru: '#cd853f',
pink: '#ffc0cb',
plum: '#dda0dd',
powderblue: '#b0e0e6',
purple: '#800080',
purple2: '#7f007f',
purple3: '#a020f0',
rebeccapurple: '#663399',
red: '#ff0000',
rosybrown: '#bc8f8f',
royalblue: '#4169e1',
saddlebrown: '#8b4513',
salmon: '#fa8072',
sandybrown: '#f4a460',
seagreen: '#2e8b57',
seashell: '#fff5ee',
sienna: '#a0522d',
silver: '#c0c0c0',
skyblue: '#87ceeb',
slateblue: '#6a5acd',
slategray: '#708090',
slategrey: '#708090',
snow: '#fffafa',
springgreen: '#00ff7f',
steelblue: '#4682b4',
tan: '#d2b48c',
teal: '#008080',
thistle: '#d8bfd8',
tomato: '#ff6347',
turquoise: '#40e0d0',
violet: '#ee82ee',
wheat: '#f5deb3',
white: '#ffffff',
whitesmoke: '#f5f5f5',
yellow: '#ffff00',
yellowgreen: '#9acd32'
};
chroma.colors = colors = w3cx11;
lab2rgb = function() {
var a, args, b, g, l, r, x, y, z;
args = unpack(arguments);
l = args[0], a = args[1], b = args[2];
y = (l + 16) / 116;
x = isNaN(a) ? y : y + a / 500;
z = isNaN(b) ? y : y - b / 200;
y = LAB_CONSTANTS.Yn * lab_xyz(y);
x = LAB_CONSTANTS.Xn * lab_xyz(x);
z = LAB_CONSTANTS.Zn * lab_xyz(z);
r = xyz_rgb(3.2404542 * x - 1.5371385 * y - 0.4985314 * z);
g = xyz_rgb(-0.9692660 * x + 1.8760108 * y + 0.0415560 * z);
b = xyz_rgb(0.0556434 * x - 0.2040259 * y + 1.0572252 * z);
return [r, g, b, args.length > 3 ? args[3] : 1];
};
xyz_rgb = function(r) {
return 255 * (r <= 0.00304 ? 12.92 * r : 1.055 * pow(r, 1 / 2.4) - 0.055);
};
lab_xyz = function(t) {
if (t > LAB_CONSTANTS.t1) {
return t * t * t;
} else {
return LAB_CONSTANTS.t2 * (t - LAB_CONSTANTS.t0);
}
};
LAB_CONSTANTS = {
Kn: 18,
Xn: 0.950470,
Yn: 1,
Zn: 1.088830,
t0: 0.137931034,
t1: 0.206896552,
t2: 0.12841855,
t3: 0.008856452
};
rgb2lab = function() {
var b, g, r, ref, ref1, x, y, z;
ref = unpack(arguments), r = ref[0], g = ref[1], b = ref[2];
ref1 = rgb2xyz(r, g, b), x = ref1[0], y = ref1[1], z = ref1[2];
return [116 * y - 16, 500 * (x - y), 200 * (y - z)];
};
rgb_xyz = function(r) {
if ((r /= 255) <= 0.04045) {
return r / 12.92;
} else {
return pow((r + 0.055) / 1.055, 2.4);
}
};
xyz_lab = function(t) {
if (t > LAB_CONSTANTS.t3) {
return pow(t, 1 / 3);
} else {
return t / LAB_CONSTANTS.t2 + LAB_CONSTANTS.t0;
}
};
rgb2xyz = function() {
var b, g, r, ref, x, y, z;
ref = unpack(arguments), r = ref[0], g = ref[1], b = ref[2];
r = rgb_xyz(r);
g = rgb_xyz(g);
b = rgb_xyz(b);
x = xyz_lab((0.4124564 * r + 0.3575761 * g + 0.1804375 * b) / LAB_CONSTANTS.Xn);
y = xyz_lab((0.2126729 * r + 0.7151522 * g + 0.0721750 * b) / LAB_CONSTANTS.Yn);
z = xyz_lab((0.0193339 * r + 0.1191920 * g + 0.9503041 * b) / LAB_CONSTANTS.Zn);
return [x, y, z];
};
chroma.lab = function() {
return (function(func, args, ctor) {
ctor.prototype = func.prototype;
var child = new ctor, result = func.apply(child, args);
return Object(result) === result ? result : child;
})(Color, slice.call(arguments).concat(['lab']), function(){});
};
_input.lab = lab2rgb;
Color.prototype.lab = function() {
return rgb2lab(this._rgb);
};
bezier = function(colors) {
var I, I0, I1, c, lab0, lab1, lab2, lab3, ref, ref1, ref2;
colors = (function() {
var len, o, results;
results = [];
for (o = 0, len = colors.length; o < len; o++) {
c = colors[o];
results.push(chroma(c));
}
return results;
})();
if (colors.length === 2) {
ref = (function() {
var len, o, results;
results = [];
for (o = 0, len = colors.length; o < len; o++) {
c = colors[o];
results.push(c.lab());
}
return results;
})(), lab0 = ref[0], lab1 = ref[1];
I = function(t) {
var i, lab;
lab = (function() {
var o, results;
results = [];
for (i = o = 0; o <= 2; i = ++o) {
results.push(lab0[i] + t * (lab1[i] - lab0[i]));
}
return results;
})();
return chroma.lab.apply(chroma, lab);
};
} else if (colors.length === 3) {
ref1 = (function() {
var len, o, results;
results = [];
for (o = 0, len = colors.length; o < len; o++) {
c = colors[o];
results.push(c.lab());
}
return results;
})(), lab0 = ref1[0], lab1 = ref1[1], lab2 = ref1[2];
I = function(t) {
var i, lab;
lab = (function() {
var o, results;
results = [];
for (i = o = 0; o <= 2; i = ++o) {
results.push((1 - t) * (1 - t) * lab0[i] + 2 * (1 - t) * t * lab1[i] + t * t * lab2[i]);
}
return results;
})();
return chroma.lab.apply(chroma, lab);
};
} else if (colors.length === 4) {
ref2 = (function() {
var len, o, results;
results = [];
for (o = 0, len = colors.length; o < len; o++) {
c = colors[o];
results.push(c.lab());
}
return results;
})(), lab0 = ref2[0], lab1 = ref2[1], lab2 = ref2[2], lab3 = ref2[3];
I = function(t) {
var i, lab;
lab = (function() {
var o, results;
results = [];
for (i = o = 0; o <= 2; i = ++o) {
results.push((1 - t) * (1 - t) * (1 - t) * lab0[i] + 3 * (1 - t) * (1 - t) * t * lab1[i] + 3 * (1 - t) * t * t * lab2[i] + t * t * t * lab3[i]);
}
return results;
})();
return chroma.lab.apply(chroma, lab);
};
} else if (colors.length === 5) {
I0 = bezier(colors.slice(0, 3));
I1 = bezier(colors.slice(2, 5));
I = function(t) {
if (t < 0.5) {
return I0(t * 2);
} else {
return I1((t - 0.5) * 2);
}
};
}
return I;
};
chroma.bezier = function(colors) {
var f;
f = bezier(colors);
f.scale = function() {
return chroma.scale(f);
};
return f;
};
/*
chroma.js
Copyright (c) 2011-2013, Gregor Aisch
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* The name Gregor Aisch may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL GREGOR AISCH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@source: https://github.com/gka/chroma.js
*/
chroma.cubehelix = function(start, rotations, hue, gamma, lightness) {
var dh, dl, f;
if (start == null) {
start = 300;
}
if (rotations == null) {
rotations = -1.5;
}
if (hue == null) {
hue = 1;
}
if (gamma == null) {
gamma = 1;
}
if (lightness == null) {
lightness = [0, 1];
}
dh = 0;
if (type(lightness) === 'array') {
dl = lightness[1] - lightness[0];
} else {
dl = 0;
lightness = [lightness, lightness];
}
f = function(fract) {
var a, amp, b, cos_a, g, h, l, r, sin_a;
a = TWOPI * ((start + 120) / 360 + rotations * fract);
l = pow(lightness[0] + dl * fract, gamma);
h = dh !== 0 ? hue[0] + fract * dh : hue;
amp = h * l * (1 - l) / 2;
cos_a = cos(a);
sin_a = sin(a);
r = l + amp * (-0.14861 * cos_a + 1.78277 * sin_a);
g = l + amp * (-0.29227 * cos_a - 0.90649 * sin_a);
b = l + amp * (+1.97294 * cos_a);
return chroma(clip_rgb([r * 255, g * 255, b * 255]));
};
f.start = function(s) {
if (s == null) {
return start;
}
start = s;
return f;
};
f.rotations = function(r) {
if (r == null) {
return rotations;
}
rotations = r;
return f;
};
f.gamma = function(g) {
if (g == null) {
return gamma;
}
gamma = g;
return f;
};
f.hue = function(h) {
if (h == null) {
return hue;
}
hue = h;
if (type(hue) === 'array') {
dh = hue[1] - hue[0];
if (dh === 0) {
hue = hue[1];
}
} else {
dh = 0;
}
return f;
};
f.lightness = function(h) {
if (h == null) {
return lightness;
}
if (type(h) === 'array') {
lightness = h;
dl = h[1] - h[0];
} else {
lightness = [h, h];
dl = 0;
}
return f;
};
f.scale = function() {
return chroma.scale(f);
};
f.hue(hue);
return f;
};
chroma.random = function() {
var code, digits, i, o;
digits = '0123456789abcdef';
code = '#';
for (i = o = 0; o < 6; i = ++o) {
code += digits.charAt(floor(Math.random() * 16));
}
return new Color(code);
};
chroma.average = function(colors, mode) {
var A, alpha, c, cnt, dx, dy, first, i, l, len, o, xyz, xyz2;
if (mode == null) {
mode = 'rgb';
}
l = colors.length;
colors = colors.map(function(c) {
return chroma(c);
});
first = colors.splice(0, 1)[0];
xyz = first.get(mode);
cnt = [];
dx = 0;
dy = 0;
for (i in xyz) {
xyz[i] = xyz[i] || 0;
cnt.push(!isNaN(xyz[i]) ? 1 : 0);
if (mode.charAt(i) === 'h' && !isNaN(xyz[i])) {
A = xyz[i] / 180 * PI;
dx += cos(A);
dy += sin(A);
}
}
alpha = first.alpha();
for (o = 0, len = colors.length; o < len; o++) {
c = colors[o];
xyz2 = c.get(mode);
alpha += c.alpha();
for (i in xyz) {
if (!isNaN(xyz2[i])) {
xyz[i] += xyz2[i];
cnt[i] += 1;
if (mode.charAt(i) === 'h') {
A = xyz[i] / 180 * PI;
dx += cos(A);
dy += sin(A);
}
}
}
}
for (i in xyz) {
xyz[i] = xyz[i] / cnt[i];
if (mode.charAt(i) === 'h') {
A = atan2(dy / cnt[i], dx / cnt[i]) / PI * 180;
while (A < 0) {
A += 360;
}
while (A >= 360) {
A -= 360;
}
xyz[i] = A;
}
}
return chroma(xyz, mode).alpha(alpha / l);
};
_input.rgb = function() {
var k, ref, results, v;
ref = unpack(arguments);
results = [];
for (k in ref) {
v = ref[k];
results.push(v);
}
return results;
};
chroma.rgb = function() {
return (function(func, args, ctor) {
ctor.prototype = func.prototype;
var child = new ctor, result = func.apply(child, args);
return Object(result) === result ? result : child;
})(Color, slice.call(arguments).concat(['rgb']), function(){});
};
Color.prototype.rgb = function(round) {
if (round == null) {
round = true;
}
if (round) {
return this._rgb.map(Math.round).slice(0, 3);
} else {
return this._rgb.slice(0, 3);
}
};
Color.prototype.rgba = function(round) {
if (round == null) {
round = true;
}
if (!round) {
return this._rgb.slice(0);
}
return [Math.round(this._rgb[0]), Math.round(this._rgb[1]), Math.round(this._rgb[2]), this._rgb[3]];
};
_guess_formats.push({
p: 3,
test: function(n) {
var a;
a = unpack(arguments);
if (type(a) === 'array' && a.length === 3) {
return 'rgb';
}
if (a.length === 4 && type(a[3]) === "number" && a[3] >= 0 && a[3] <= 1) {
return 'rgb';
}
}
});
hex2rgb = function(hex) {
var a, b, g, r, rgb, u;
if (hex.match(/^#?([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/)) {
if (hex.length === 4 || hex.length === 7) {
hex = hex.substr(1);
}
if (hex.length === 3) {
hex = hex.split("");
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
}
u = parseInt(hex, 16);
r = u >> 16;
g = u >> 8 & 0xFF;
b = u & 0xFF;
return [r, g, b, 1];
}
if (hex.match(/^#?([A-Fa-f0-9]{8})$/)) {
if (hex.length === 9) {
hex = hex.substr(1);
}
u = parseInt(hex, 16);
r = u >> 24 & 0xFF;
g = u >> 16 & 0xFF;
b = u >> 8 & 0xFF;
a = round((u & 0xFF) / 0xFF * 100) / 100;
return [r, g, b, a];
}
if ((_input.css != null) && (rgb = _input.css(hex))) {
return rgb;
}
throw "unknown color: " + hex;
};
rgb2hex = function(channels, mode) {
var a, b, g, hxa, r, str, u;
if (mode == null) {
mode = 'rgb';
}
r = channels[0], g = channels[1], b = channels[2], a = channels[3];
r = Math.round(r);
g = Math.round(g);
b = Math.round(b);
u = r << 16 | g << 8 | b;
str = "000000" + u.toString(16);
str = str.substr(str.length - 6);
hxa = '0' + round(a * 255).toString(16);
hxa = hxa.substr(hxa.length - 2);
return "#" + (function() {
switch (mode.toLowerCase()) {
case 'rgba':
return str + hxa;
case 'argb':
return hxa + str;
default:
return str;
}
})();
};
_input.hex = function(h) {
return hex2rgb(h);
};
chroma.hex = function() {
return (function(func, args, ctor) {
ctor.prototype = func.prototype;
var child = new ctor, result = func.apply(child, args);
return Object(result) === result ? result : child;
})(Color, slice.call(arguments).concat(['hex']), function(){});
};
Color.prototype.hex = function(mode) {
if (mode == null) {
mode = 'rgb';
}
return rgb2hex(this._rgb, mode);
};
_guess_formats.push({
p: 4,
test: function(n) {
if (arguments.length === 1 && type(n) === "string") {
return 'hex';
}
}
});
hsl2rgb = function() {
var args, b, c, g, h, i, l, o, r, ref, s, t1, t2, t3;
args = unpack(arguments);
h = args[0], s = args[1], l = args[2];
if (s === 0) {
r = g = b = l * 255;
} else {
t3 = [0, 0, 0];
c = [0, 0, 0];
t2 = l < 0.5 ? l * (1 + s) : l + s - l * s;
t1 = 2 * l - t2;
h /= 360;
t3[0] = h + 1 / 3;
t3[1] = h;
t3[2] = h - 1 / 3;
for (i = o = 0; o <= 2; i = ++o) {
if (t3[i] < 0) {
t3[i] += 1;
}
if (t3[i] > 1) {
t3[i] -= 1;
}
if (6 * t3[i] < 1) {
c[i] = t1 + (t2 - t1) * 6 * t3[i];
} else if (2 * t3[i] < 1) {
c[i] = t2;
} else if (3 * t3[i] < 2) {
c[i] = t1 + (t2 - t1) * ((2 / 3) - t3[i]) * 6;
} else {
c[i] = t1;
}
}
ref = [round(c[0] * 255), round(c[1] * 255), round(c[2] * 255)], r = ref[0], g = ref[1], b = ref[2];
}
if (args.length > 3) {
return [r, g, b, args[3]];
} else {
return [r, g, b];
}
};
rgb2hsl = function(r, g, b) {
var h, l, min, ref, s;
if (r !== void 0 && r.length >= 3) {
ref = r, r = ref[0], g = ref[1], b = ref[2];
}
r /= 255;
g /= 255;
b /= 255;
min = Math.min(r, g, b);
max = Math.max(r, g, b);
l = (max + min) / 2;
if (max === min) {
s = 0;
h = Number.NaN;
} else {
s = l < 0.5 ? (max - min) / (max + min) : (max - min) / (2 - max - min);
}
if (r === max) {
h = (g - b) / (max - min);
} else if (g === max) {
h = 2 + (b - r) / (max - min);
} else if (b === max) {
h = 4 + (r - g) / (max - min);
}
h *= 60;
if (h < 0) {
h += 360;
}
return [h, s, l];
};
chroma.hsl = function() {
return (function(func, args, ctor) {
ctor.prototype = func.prototype;
var child = new ctor, result = func.apply(child, args);
return Object(result) === result ? result : child;
})(Color, slice.call(arguments).concat(['hsl']), function(){});
};
_input.hsl = hsl2rgb;
Color.prototype.hsl = function() {
return rgb2hsl(this._rgb);
};
hsv2rgb = function() {
var args, b, f, g, h, i, p, q, r, ref, ref1, ref2, ref3, ref4, ref5, s, t, v;
args = unpack(arguments);
h = args[0], s = args[1], v = args[2];
v *= 255;
if (s === 0) {
r = g = b = v;
} else {
if (h === 360) {
h = 0;
}
if (h > 360) {
h -= 360;
}
if (h < 0) {
h += 360;
}
h /= 60;
i = floor(h);
f = h - i;
p = v * (1 - s);
q = v * (1 - s * f);
t = v * (1 - s * (1 - f));
switch (i) {
case 0:
ref = [v, t, p], r = ref[0], g = ref[1], b = ref[2];
break;
case 1:
ref1 = [q, v, p], r = ref1[0], g = ref1[1], b = ref1[2];
break;
case 2:
ref2 = [p, v, t], r = ref2[0], g = ref2[1], b = ref2[2];
break;
case 3:
ref3 = [p, q, v], r = ref3[0], g = ref3[1], b = ref3[2];
break;
case 4:
ref4 = [t, p, v], r = ref4[0], g = ref4[1], b = ref4[2];
break;
case 5:
ref5 = [v, p, q], r = ref5[0], g = ref5[1], b = ref5[2];
}
}
return [r, g, b, args.length > 3 ? args[3] : 1];
};
rgb2hsv = function() {
var b, delta, g, h, min, r, ref, s, v;
ref = unpack(arguments), r = ref[0], g = ref[1], b = ref[2];
min = Math.min(r, g, b);
max = Math.max(r, g, b);
delta = max - min;
v = max / 255.0;
if (max === 0) {
h = Number.NaN;
s = 0;
} else {
s = delta / max;
if (r === max) {
h = (g - b) / delta;
}
if (g === max) {
h = 2 + (b - r) / delta;
}
if (b === max) {
h = 4 + (r - g) / delta;
}
h *= 60;
if (h < 0) {
h += 360;
}
}
return [h, s, v];
};
chroma.hsv = function() {
return (function(func, args, ctor) {
ctor.prototype = func.prototype;
var child = new ctor, result = func.apply(child, args);
return Object(result) === result ? result : child;
})(Color, slice.call(arguments).concat(['hsv']), function(){});
};
_input.hsv = hsv2rgb;
Color.prototype.hsv = function() {
return rgb2hsv(this._rgb);
};
num2rgb = function(num) {
var b, g, r;
if (type(num) === "number" && num >= 0 && num <= 0xFFFFFF) {
r = num >> 16;
g = (num >> 8) & 0xFF;
b = num & 0xFF;
return [r, g, b, 1];
}
console.warn("unknown num color: " + num);
return [0, 0, 0, 1];
};
rgb2num = function() {
var b, g, r, ref;
ref = unpack(arguments), r = ref[0], g = ref[1], b = ref[2];
return (r << 16) + (g << 8) + b;
};
chroma.num = function(num) {
return new Color(num, 'num');
};
Color.prototype.num = function(mode) {
if (mode == null) {
mode = 'rgb';
}
return rgb2num(this._rgb, mode);
};
_input.num = num2rgb;
_guess_formats.push({
p: 1,
test: function(n) {
if (arguments.length === 1 && type(n) === "number" && n >= 0 && n <= 0xFFFFFF) {
return 'num';
}
}
});
hcg2rgb = function() {
var _c, _g, args, b, c, f, g, h, i, p, q, r, ref, ref1, ref2, ref3, ref4, ref5, t, v;
args = unpack(arguments);
h = args[0], c = args[1], _g = args[2];
c = c / 100;
g = g / 100 * 255;
_c = c * 255;
if (c === 0) {
r = g = b = _g;
} else {
if (h === 360) {
h = 0;
}
if (h > 360) {
h -= 360;
}
if (h < 0) {
h += 360;
}
h /= 60;
i = floor(h);
f = h - i;
p = _g * (1 - c);
q = p + _c * (1 - f);
t = p + _c * f;
v = p + _c;
switch (i) {
case 0:
ref = [v, t, p], r = ref[0], g = ref[1], b = ref[2];
break;
case 1:
ref1 = [q, v, p], r = ref1[0], g = ref1[1], b = ref1[2];
break;
case 2:
ref2 = [p, v, t], r = ref2[0], g = ref2[1], b = ref2[2];
break;
case 3:
ref3 = [p, q, v], r = ref3[0], g = ref3[1], b = ref3[2];
break;
case 4:
ref4 = [t, p, v], r = ref4[0], g = ref4[1], b = ref4[2];
break;
case 5:
ref5 = [v, p, q], r = ref5[0], g = ref5[1], b = ref5[2];
}
}
return [r, g, b, args.length > 3 ? args[3] : 1];
};
rgb2hcg = function() {
var _g, b, c, delta, g, h, min, r, ref;
ref = unpack(arguments), r = ref[0], g = ref[1], b = ref[2];
min = Math.min(r, g, b);
max = Math.max(r, g, b);
delta = max - min;
c = delta * 100 / 255;
_g = min / (255 - delta) * 100;
if (delta === 0) {
h = Number.NaN;
} else {
if (r === max) {
h = (g - b) / delta;
}
if (g === max) {
h = 2 + (b - r) / delta;
}
if (b === max) {
h = 4 + (r - g) / delta;
}
h *= 60;
if (h < 0) {
h += 360;
}
}
return [h, c, _g];
};
chroma.hcg = function() {
return (function(func, args, ctor) {
ctor.prototype = func.prototype;
var child = new ctor, result = func.apply(child, args);
return Object(result) === result ? result : child;
})(Color, slice.call(arguments).concat(['hcg']), function(){});
};
_input.hcg = hcg2rgb;
Color.prototype.hcg = function() {
return rgb2hcg(this._rgb);
};
css2rgb = function(css) {
var aa, ab, hsl, i, m, o, rgb, w;
css = css.toLowerCase();
if ((chroma.colors != null) && chroma.colors[css]) {
return hex2rgb(chroma.colors[css]);
}
if (m = css.match(/rgb\(\s*(\-?\d+),\s*(\-?\d+)\s*,\s*(\-?\d+)\s*\)/)) {
rgb = m.slice(1, 4);
for (i = o = 0; o <= 2; i = ++o) {
rgb[i] = +rgb[i];
}
rgb[3] = 1;
} else if (m = css.match(/rgba\(\s*(\-?\d+),\s*(\-?\d+)\s*,\s*(\-?\d+)\s*,\s*([01]|[01]?\.\d+)\)/)) {
rgb = m.slice(1, 5);
for (i = w = 0; w <= 3; i = ++w) {
rgb[i] = +rgb[i];
}
} else if (m = css.match(/rgb\(\s*(\-?\d+(?:\.\d+)?)%,\s*(\-?\d+(?:\.\d+)?)%\s*,\s*(\-?\d+(?:\.\d+)?)%\s*\)/)) {
rgb = m.slice(1, 4);
for (i = aa = 0; aa <= 2; i = ++aa) {
rgb[i] = round(rgb[i] * 2.55);
}
rgb[3] = 1;
} else if (m = css.match(/rgba\(\s*(\-?\d+(?:\.\d+)?)%,\s*(\-?\d+(?:\.\d+)?)%\s*,\s*(\-?\d+(?:\.\d+)?)%\s*,\s*([01]|[01]?\.\d+)\)/)) {
rgb = m.slice(1, 5);
for (i = ab = 0; ab <= 2; i = ++ab) {
rgb[i] = round(rgb[i] * 2.55);
}
rgb[3] = +rgb[3];
} else if (m = css.match(/hsl\(\s*(\-?\d+(?:\.\d+)?),\s*(\-?\d+(?:\.\d+)?)%\s*,\s*(\-?\d+(?:\.\d+)?)%\s*\)/)) {
hsl = m.slice(1, 4);
hsl[1] *= 0.01;
hsl[2] *= 0.01;
rgb = hsl2rgb(hsl);
rgb[3] = 1;
} else if (m = css.match(/hsla\(\s*(\-?\d+(?:\.\d+)?),\s*(\-?\d+(?:\.\d+)?)%\s*,\s*(\-?\d+(?:\.\d+)?)%\s*,\s*([01]|[01]?\.\d+)\)/)) {
hsl = m.slice(1, 4);
hsl[1] *= 0.01;
hsl[2] *= 0.01;
rgb = hsl2rgb(hsl);
rgb[3] = +m[4];
}
return rgb;
};
rgb2css = function(rgba) {
var mode;
mode = rgba[3] < 1 ? 'rgba' : 'rgb';
if (mode === 'rgb') {
return mode + '(' + rgba.slice(0, 3).map(round).join(',') + ')';
} else if (mode === 'rgba') {
return mode + '(' + rgba.slice(0, 3).map(round).join(',') + ',' + rgba[3] + ')';
} else {
}
};
rnd = function(a) {
return round(a * 100) / 100;
};
hsl2css = function(hsl, alpha) {
var mode;
mode = alpha < 1 ? 'hsla' : 'hsl';
hsl[0] = rnd(hsl[0] || 0);
hsl[1] = rnd(hsl[1] * 100) + '%';
hsl[2] = rnd(hsl[2] * 100) + '%';
if (mode === 'hsla') {
hsl[3] = alpha;
}
return mode + '(' + hsl.join(',') + ')';
};
_input.css = function(h) {
return css2rgb(h);
};
chroma.css = function() {
return (function(func, args, ctor) {
ctor.prototype = func.prototype;
var child = new ctor, result = func.apply(child, args);
return Object(result) === result ? result : child;
})(Color, slice.call(arguments).concat(['css']), function(){});
};
Color.prototype.css = function(mode) {
if (mode == null) {
mode = 'rgb';
}
if (mode.slice(0, 3) === 'rgb') {
return rgb2css(this._rgb);
} else if (mode.slice(0, 3) === 'hsl') {
return hsl2css(this.hsl(), this.alpha());
}
};
_input.named = function(name) {
return hex2rgb(w3cx11[name]);
};
_guess_formats.push({
p: 5,
test: function(n) {
if (arguments.length === 1 && (w3cx11[n] != null)) {
return 'named';
}
}
});
Color.prototype.name = function(n) {
var h, k;
if (arguments.length) {
if (w3cx11[n]) {
this._rgb = hex2rgb(w3cx11[n]);
}
this._rgb[3] = 1;
this;
}
h = this.hex();
for (k in w3cx11) {
if (h === w3cx11[k]) {
return k;
}
}
return h;
};
lch2lab = function() {
/*
Convert from a qualitative parameter h and a quantitative parameter l to a 24-bit pixel.
These formulas were invented by David Dalrymple to obtain maximum contrast without going
out of gamut if the parameters are in the range 0-1.
A saturation multiplier was added by Gregor Aisch
*/
var c, h, l, ref;
ref = unpack(arguments), l = ref[0], c = ref[1], h = ref[2];
h = h * DEG2RAD;
return [l, cos(h) * c, sin(h) * c];
};
lch2rgb = function() {
var L, a, args, b, c, g, h, l, r, ref, ref1;
args = unpack(arguments);
l = args[0], c = args[1], h = args[2];
ref = lch2lab(l, c, h), L = ref[0], a = ref[1], b = ref[2];
ref1 = lab2rgb(L, a, b), r = ref1[0], g = ref1[1], b = ref1[2];
return [r, g, b, args.length > 3 ? args[3] : 1];
};
lab2lch = function() {
var a, b, c, h, l, ref;
ref = unpack(arguments), l = ref[0], a = ref[1], b = ref[2];
c = sqrt(a * a + b * b);
h = (atan2(b, a) * RAD2DEG + 360) % 360;
if (round(c * 10000) === 0) {
h = Number.NaN;
}
return [l, c, h];
};
rgb2lch = function() {
var a, b, g, l, r, ref, ref1;
ref = unpack(arguments), r = ref[0], g = ref[1], b = ref[2];
ref1 = rgb2lab(r, g, b), l = ref1[0], a = ref1[1], b = ref1[2];
return lab2lch(l, a, b);
};
chroma.lch = function() {
var args;
args = unpack(arguments);
return new Color(args, 'lch');
};
chroma.hcl = function() {
var args;
args = unpack(arguments);
return new Color(args, 'hcl');
};
_input.lch = lch2rgb;
_input.hcl = function() {
var c, h, l, ref;
ref = unpack(arguments), h = ref[0], c = ref[1], l = ref[2];
return lch2rgb([l, c, h]);
};
Color.prototype.lch = function() {
return rgb2lch(this._rgb);
};
Color.prototype.hcl = function() {
return rgb2lch(this._rgb).reverse();
};
rgb2cmyk = function(mode) {
var b, c, f, g, k, m, r, ref, y;
if (mode == null) {
mode = 'rgb';
}
ref = unpack(arguments), r = ref[0], g = ref[1], b = ref[2];
r = r / 255;
g = g / 255;
b = b / 255;
k = 1 - Math.max(r, Math.max(g, b));
f = k < 1 ? 1 / (1 - k) : 0;
c = (1 - r - k) * f;
m = (1 - g - k) * f;
y = (1 - b - k) * f;
return [c, m, y, k];
};
cmyk2rgb = function() {
var alpha, args, b, c, g, k, m, r, y;
args = unpack(arguments);
c = args[0], m = args[1], y = args[2], k = args[3];
alpha = args.length > 4 ? args[4] : 1;
if (k === 1) {
return [0, 0, 0, alpha];
}
r = c >= 1 ? 0 : 255 * (1 - c) * (1 - k);
g = m >= 1 ? 0 : 255 * (1 - m) * (1 - k);
b = y >= 1 ? 0 : 255 * (1 - y) * (1 - k);
return [r, g, b, alpha];
};
_input.cmyk = function() {
return cmyk2rgb(unpack(arguments));
};
chroma.cmyk = function() {
return (function(func, args, ctor) {
ctor.prototype = func.prototype;
var child = new ctor, result = func.apply(child, args);
return Object(result) === result ? result : child;
})(Color, slice.call(arguments).concat(['cmyk']), function(){});
};
Color.prototype.cmyk = function() {
return rgb2cmyk(this._rgb);
};
_input.gl = function() {
var i, k, o, rgb, v;
rgb = (function() {
var ref, results;
ref = unpack(arguments);
results = [];
for (k in ref) {
v = ref[k];
results.push(v);
}
return results;
}).apply(this, arguments);
for (i = o = 0; o <= 2; i = ++o) {
rgb[i] *= 255;
}
return rgb;
};
chroma.gl = function() {
return (function(func, args, ctor) {
ctor.prototype = func.prototype;
var child = new ctor, result = func.apply(child, args);
return Object(result) === result ? result : child;
})(Color, slice.call(arguments).concat(['gl']), function(){});
};
Color.prototype.gl = function() {
var rgb;
rgb = this._rgb;
return [rgb[0] / 255, rgb[1] / 255, rgb[2] / 255, rgb[3]];
};
rgb2luminance = function(r, g, b) {
var ref;
ref = unpack(arguments), r = ref[0], g = ref[1], b = ref[2];
r = luminance_x(r);
g = luminance_x(g);
b = luminance_x(b);
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
};
luminance_x = function(x) {
x /= 255;
if (x <= 0.03928) {
return x / 12.92;
} else {
return pow((x + 0.055) / 1.055, 2.4);
}
};
_interpolators = [];
interpolate = function(col1, col2, f, m) {
var interpol, len, o, res;
if (f == null) {
f = 0.5;
}
if (m == null) {
m = 'rgb';
}
/*
interpolates between colors
f = 0 --> me
f = 1 --> col
*/
if (type(col1) !== 'object') {
col1 = chroma(col1);
}
if (type(col2) !== 'object') {
col2 = chroma(col2);
}
for (o = 0, len = _interpolators.length; o < len; o++) {
interpol = _interpolators[o];
if (m === interpol[0]) {
res = interpol[1](col1, col2, f, m);
break;
}
}
if (res == null) {
throw "color mode " + m + " is not supported";
}
return res.alpha(col1.alpha() + f * (col2.alpha() - col1.alpha()));
};
chroma.interpolate = interpolate;
Color.prototype.interpolate = function(col2, f, m) {
return interpolate(this, col2, f, m);
};
chroma.mix = interpolate;
Color.prototype.mix = Color.prototype.interpolate;
interpolate_rgb = function(col1, col2, f, m) {
var xyz0, xyz1;
xyz0 = col1._rgb;
xyz1 = col2._rgb;
return new Color(xyz0[0] + f * (xyz1[0] - xyz0[0]), xyz0[1] + f * (xyz1[1] - xyz0[1]), xyz0[2] + f * (xyz1[2] - xyz0[2]), m);
};
_interpolators.push(['rgb', interpolate_rgb]);
Color.prototype.luminance = function(lum, mode) {
var cur_lum, eps, max_iter, test;
if (mode == null) {
mode = 'rgb';
}
if (!arguments.length) {
return rgb2luminance(this._rgb);
}
if (lum === 0) {
this._rgb = [0, 0, 0, this._rgb[3]];
} else if (lum === 1) {
this._rgb = [255, 255, 255, this._rgb[3]];
} else {
eps = 1e-7;
max_iter = 20;
test = function(l, h) {
var lm, m;
m = l.interpolate(h, 0.5, mode);
lm = m.luminance();
if (Math.abs(lum - lm) < eps || !max_iter--) {
return m;
}
if (lm > lum) {
return test(l, m);
}
return test(m, h);
};
cur_lum = rgb2luminance(this._rgb);
this._rgb = (cur_lum > lum ? test(chroma('black'), this) : test(this, chroma('white'))).rgba();
}
return this;
};
temperature2rgb = function(kelvin) {
var b, g, r, temp;
temp = kelvin / 100;
if (temp < 66) {
r = 255;
g = -155.25485562709179 - 0.44596950469579133 * (g = temp - 2) + 104.49216199393888 * log(g);
b = temp < 20 ? 0 : -254.76935184120902 + 0.8274096064007395 * (b = temp - 10) + 115.67994401066147 * log(b);
} else {
r = 351.97690566805693 + 0.114206453784165 * (r = temp - 55) - 40.25366309332127 * log(r);
g = 325.4494125711974 + 0.07943456536662342 * (g = temp - 50) - 28.0852963507957 * log(g);
b = 255;
}
return [r, g, b];
};
rgb2temperature = function() {
var b, eps, g, maxTemp, minTemp, r, ref, rgb, temp;
ref = unpack(arguments), r = ref[0], g = ref[1], b = ref[2];
minTemp = 1000;
maxTemp = 40000;
eps = 0.4;
while (maxTemp - minTemp > eps) {
temp = (maxTemp + minTemp) * 0.5;
rgb = temperature2rgb(temp);
if ((rgb[2] / rgb[0]) >= (b / r)) {
maxTemp = temp;
} else {
minTemp = temp;
}
}
return round(temp);
};
chroma.temperature = chroma.kelvin = function() {
return (function(func, args, ctor) {
ctor.prototype = func.prototype;
var child = new ctor, result = func.apply(child, args);
return Object(result) === result ? result : child;
})(Color, slice.call(arguments).concat(['temperature']), function(){});
};
_input.temperature = _input.kelvin = _input.K = temperature2rgb;
Color.prototype.temperature = function() {
return rgb2temperature(this._rgb);
};
Color.prototype.kelvin = Color.prototype.temperature;
chroma.contrast = function(a, b) {
var l1, l2, ref, ref1;
if ((ref = type(a)) === 'string' || ref === 'number') {
a = new Color(a);
}
if ((ref1 = type(b)) === 'string' || ref1 === 'number') {
b = new Color(b);
}
l1 = a.luminance();
l2 = b.luminance();
if (l1 > l2) {
return (l1 + 0.05) / (l2 + 0.05);
} else {
return (l2 + 0.05) / (l1 + 0.05);
}
};
chroma.distance = function(a, b, mode) {
var d, i, l1, l2, ref, ref1, sum_sq;
if (mode == null) {
mode = 'lab';
}
if ((ref = type(a)) === 'string' || ref === 'number') {
a = new Color(a);
}
if ((ref1 = type(b)) === 'string' || ref1 === 'number') {
b = new Color(b);
}
l1 = a.get(mode);
l2 = b.get(mode);
sum_sq = 0;
for (i in l1) {
d = (l1[i] || 0) - (l2[i] || 0);
sum_sq += d * d;
}
return Math.sqrt(sum_sq);
};
chroma.deltaE = function(a, b, L, C) {
var L1, L2, a1, a2, b1, b2, c1, c2, c4, dH2, delA, delB, delC, delL, f, h1, ref, ref1, ref2, ref3, sc, sh, sl, t, v1, v2, v3;
if (L == null) {
L = 1;
}
if (C == null) {
C = 1;
}
if ((ref = type(a)) === 'string' || ref === 'number') {
a = new Color(a);
}
if ((ref1 = type(b)) === 'string' || ref1 === 'number') {
b = new Color(b);
}
ref2 = a.lab(), L1 = ref2[0], a1 = ref2[1], b1 = ref2[2];
ref3 = b.lab(), L2 = ref3[0], a2 = ref3[1], b2 = ref3[2];
c1 = sqrt(a1 * a1 + b1 * b1);
c2 = sqrt(a2 * a2 + b2 * b2);
sl = L1 < 16.0 ? 0.511 : (0.040975 * L1) / (1.0 + 0.01765 * L1);
sc = (0.0638 * c1) / (1.0 + 0.0131 * c1) + 0.638;
h1 = c1 < 0.000001 ? 0.0 : (atan2(b1, a1) * 180.0) / PI;
while (h1 < 0) {
h1 += 360;
}
while (h1 >= 360) {
h1 -= 360;
}
t = (h1 >= 164.0) && (h1 <= 345.0) ? 0.56 + abs(0.2 * cos((PI * (h1 + 168.0)) / 180.0)) : 0.36 + abs(0.4 * cos((PI * (h1 + 35.0)) / 180.0));
c4 = c1 * c1 * c1 * c1;
f = sqrt(c4 / (c4 + 1900.0));
sh = sc * (f * t + 1.0 - f);
delL = L1 - L2;
delC = c1 - c2;
delA = a1 - a2;
delB = b1 - b2;
dH2 = delA * delA + delB * delB - delC * delC;
v1 = delL / (L * sl);
v2 = delC / (C * sc);
v3 = sh;
return sqrt(v1 * v1 + v2 * v2 + (dH2 / (v3 * v3)));
};
Color.prototype.get = function(modechan) {
var channel, i, me, mode, ref, src;
me = this;
ref = modechan.split('.'), mode = ref[0], channel = ref[1];
src = me[mode]();
if (channel) {
i = mode.indexOf(channel);
if (i > -1) {
return src[i];
} else {
return console.warn('unknown channel ' + channel + ' in mode ' + mode);
}
} else {
return src;
}
};
Color.prototype.set = function(modechan, value) {
var channel, i, me, mode, ref, src;
me = this;
ref = modechan.split('.'), mode = ref[0], channel = ref[1];
if (channel) {
src = me[mode]();
i = mode.indexOf(channel);
if (i > -1) {
if (type(value) === 'string') {
switch (value.charAt(0)) {
case '+':
src[i] += +value;
break;
case '-':
src[i] += +value;
break;
case '*':
src[i] *= +(value.substr(1));
break;
case '/':
src[i] /= +(value.substr(1));
break;
default:
src[i] = +value;
}
} else {
src[i] = value;
}
} else {
console.warn('unknown channel ' + channel + ' in mode ' + mode);
}
} else {
src = value;
}
return chroma(src, mode).alpha(me.alpha());
};
Color.prototype.clipped = function() {
return this._rgb._clipped || false;
};
Color.prototype.alpha = function(a) {
if (arguments.length) {
return chroma.rgb([this._rgb[0], this._rgb[1], this._rgb[2], a]);
}
return this._rgb[3];
};
Color.prototype.darken = function(amount) {
var lab, me;
if (amount == null) {
amount = 1;
}
me = this;
lab = me.lab();
lab[0] -= LAB_CONSTANTS.Kn * amount;
return chroma.lab(lab).alpha(me.alpha());
};
Color.prototype.brighten = function(amount) {
if (amount == null) {
amount = 1;
}
return this.darken(-amount);
};
Color.prototype.darker = Color.prototype.darken;
Color.prototype.brighter = Color.prototype.brighten;
Color.prototype.saturate = function(amount) {
var lch, me;
if (amount == null) {
amount = 1;
}
me = this;
lch = me.lch();
lch[1] += amount * LAB_CONSTANTS.Kn;
if (lch[1] < 0) {
lch[1] = 0;
}
return chroma.lch(lch).alpha(me.alpha());
};
Color.prototype.desaturate = function(amount) {
if (amount == null) {
amount = 1;
}
return this.saturate(-amount);
};
Color.prototype.premultiply = function() {
var a, rgb;
rgb = this.rgb();
a = this.alpha();
return chroma(rgb[0] * a, rgb[1] * a, rgb[2] * a, a);
};
blend = function(bottom, top, mode) {
if (!blend[mode]) {
throw 'unknown blend mode ' + mode;
}
return blend[mode](bottom, top);
};
blend_f = function(f) {
return function(bottom, top) {
var c0, c1;
c0 = chroma(top).rgb();
c1 = chroma(bottom).rgb();
return chroma(f(c0, c1), 'rgb');
};
};
each = function(f) {
return function(c0, c1) {
var i, o, out;
out = [];
for (i = o = 0; o <= 3; i = ++o) {
out[i] = f(c0[i], c1[i]);
}
return out;
};
};
normal = function(a, b) {
return a;
};
multiply = function(a, b) {
return a * b / 255;
};
darken = function(a, b) {
if (a > b) {
return b;
} else {
return a;
}
};
lighten = function(a, b) {
if (a > b) {
return a;
} else {
return b;
}
};
screen = function(a, b) {
return 255 * (1 - (1 - a / 255) * (1 - b / 255));
};
overlay = function(a, b) {
if (b < 128) {
return 2 * a * b / 255;
} else {
return 255 * (1 - 2 * (1 - a / 255) * (1 - b / 255));
}
};
burn = function(a, b) {
return 255 * (1 - (1 - b / 255) / (a / 255));
};
dodge = function(a, b) {
if (a === 255) {
return 255;
}
a = 255 * (b / 255) / (1 - a / 255);
if (a > 255) {
return 255;
} else {
return a;
}
};
blend.normal = blend_f(each(normal));
blend.multiply = blend_f(each(multiply));
blend.screen = blend_f(each(screen));
blend.overlay = blend_f(each(overlay));
blend.darken = blend_f(each(darken));
blend.lighten = blend_f(each(lighten));
blend.dodge = blend_f(each(dodge));
blend.burn = blend_f(each(burn));
chroma.blend = blend;
chroma.analyze = function(data) {
var len, o, r, val;
r = {
min: Number.MAX_VALUE,
max: Number.MAX_VALUE * -1,
sum: 0,
values: [],
count: 0
};
for (o = 0, len = data.length; o < len; o++) {
val = data[o];
if ((val != null) && !isNaN(val)) {
r.values.push(val);
r.sum += val;
if (val < r.min) {
r.min = val;
}
if (val > r.max) {
r.max = val;
}
r.count += 1;
}
}
r.domain = [r.min, r.max];
r.limits = function(mode, num) {
return chroma.limits(r, mode, num);
};
return r;
};
chroma.scale = function(colors, positions) {
var _classes, _colorCache, _colors, _correctLightness, _domain, _fixed, _max, _min, _mode, _nacol, _out, _padding, _pos, _spread, _useCache, classifyValue, f, getClass, getColor, resetCache, setColors, tmap;
_mode = 'rgb';
_nacol = chroma('#ccc');
_spread = 0;
_domain = [0, 1];
_pos = [];
_padding = [0, 0];
_classes = false;
_colors = [];
_out = false;
_min = 0;
_max = 1;
_correctLightness = false;
_colorCache = {};
_useCache = true;
setColors = function(colors) {
var c, col, o, ref, ref1, w;
if (colors == null) {
colors = ['#fff', '#000'];
}
if ((colors != null) && type(colors) === 'string' && (chroma.brewer != null)) {
colors = chroma.brewer[colors] || chroma.brewer[colors.toLowerCase()] || colors;
}
if (type(colors) === 'array') {
colors = colors.slice(0);
for (c = o = 0, ref = colors.length - 1; 0 <= ref ? o <= ref : o >= ref; c = 0 <= ref ? ++o : --o) {
col = colors[c];
if (type(col) === "string") {
colors[c] = chroma(col);
}
}
_pos.length = 0;
for (c = w = 0, ref1 = colors.length - 1; 0 <= ref1 ? w <= ref1 : w >= ref1; c = 0 <= ref1 ? ++w : --w) {
_pos.push(c / (colors.length - 1));
}
}
resetCache();
return _colors = colors;
};
getClass = function(value) {
var i, n;
if (_classes != null) {
n = _classes.length - 1;
i = 0;
while (i < n && value >= _classes[i]) {
i++;
}
return i - 1;
}
return 0;
};
tmap = function(t) {
return t;
};
getColor = function(val, bypassMap) {
var c, col, i, k, o, p, ref, t;
if (bypassMap == null) {
bypassMap = false;
}
if (isNaN(val)) {
return _nacol;
}
if (!bypassMap) {
if (_classes && _classes.length > 2) {
c = getClass(val);
t = c / (_classes.length - 2);
t = _padding[0] + (t * (1 - _padding[0] - _padding[1]));
} else if (_max !== _min) {
t = (val - _min) / (_max - _min);
t = _padding[0] + (t * (1 - _padding[0] - _padding[1]));
t = Math.min(1, Math.max(0, t));
} else {
t = 1;
}
} else {
t = val;
}
if (!bypassMap) {
t = tmap(t);
}
k = Math.floor(t * 10000);
if (_useCache && _colorCache[k]) {
col = _colorCache[k];
} else {
if (type(_colors) === 'array') {
for (i = o = 0, ref = _pos.length - 1; 0 <= ref ? o <= ref : o >= ref; i = 0 <= ref ? ++o : --o) {
p = _pos[i];
if (t <= p) {
col = _colors[i];
break;
}
if (t >= p && i === _pos.length - 1) {
col = _colors[i];
break;
}
if (t > p && t < _pos[i + 1]) {
t = (t - p) / (_pos[i + 1] - p);
col = chroma.interpolate(_colors[i], _colors[i + 1], t, _mode);
break;
}
}
} else if (type(_colors) === 'function') {
col = _colors(t);
}
if (_useCache) {
_colorCache[k] = col;
}
}
return col;
};
resetCache = function() {
return _colorCache = {};
};
setColors(colors);
f = function(v) {
var c;
c = chroma(getColor(v));
if (_out && c[_out]) {
return c[_out]();
} else {
return c;
}
};
f.classes = function(classes) {
var d;
if (classes != null) {
if (type(classes) === 'array') {
_classes = classes;
_domain = [classes[0], classes[classes.length - 1]];
} else {
d = chroma.analyze(_domain);
if (classes === 0) {
_classes = [d.min, d.max];
} else {
_classes = chroma.limits(d, 'e', classes);
}
}
return f;
}
return _classes;
};
f.domain = function(domain) {
var c, d, k, len, o, ref, w;
if (!arguments.length) {
return _domain;
}
_min = domain[0];
_max = domain[domain.length - 1];
_pos = [];
k = _colors.length;
if (domain.length === k && _min !== _max) {
for (o = 0, len = domain.length; o < len; o++) {
d = domain[o];
_pos.push((d - _min) / (_max - _min));
}
} else {
for (c = w = 0, ref = k - 1; 0 <= ref ? w <= ref : w >= ref; c = 0 <= ref ? ++w : --w) {
_pos.push(c / (k - 1));
}
}
_domain = [_min, _max];
return f;
};
f.mode = function(_m) {
if (!arguments.length) {
return _mode;
}
_mode = _m;
resetCache();
return f;
};
f.range = function(colors, _pos) {
setColors(colors, _pos);
return f;
};
f.out = function(_o) {
_out = _o;
return f;
};
f.spread = function(val) {
if (!arguments.length) {
return _spread;
}
_spread = val;
return f;
};
f.correctLightness = function(v) {
if (v == null) {
v = true;
}
_correctLightness = v;
resetCache();
if (_correctLightness) {
tmap = function(t) {
var L0, L1, L_actual, L_diff, L_ideal, max_iter, pol, t0, t1;
L0 = getColor(0, true).lab()[0];
L1 = getColor(1, true).lab()[0];
pol = L0 > L1;
L_actual = getColor(t, true).lab()[0];
L_ideal = L0 + (L1 - L0) * t;
L_diff = L_actual - L_ideal;
t0 = 0;
t1 = 1;
max_iter = 20;
while (Math.abs(L_diff) > 1e-2 && max_iter-- > 0) {
(function() {
if (pol) {
L_diff *= -1;
}
if (L_diff < 0) {
t0 = t;
t += (t1 - t) * 0.5;
} else {
t1 = t;
t += (t0 - t) * 0.5;
}
L_actual = getColor(t, true).lab()[0];
return L_diff = L_actual - L_ideal;
})();
}
return t;
};
} else {
tmap = function(t) {
return t;
};
}
return f;
};
f.padding = function(p) {
if (p != null) {
if (type(p) === 'number') {
p = [p, p];
}
_padding = p;
return f;
} else {
return _padding;
}
};
f.colors = function(numColors, out) {
var dd, dm, i, o, ref, result, results, samples, w;
if (arguments.length < 2) {
out = 'hex';
}
result = [];
if (arguments.length === 0) {
result = _colors.slice(0);
} else if (numColors === 1) {
result = [f(0.5)];
} else if (numColors > 1) {
dm = _domain[0];
dd = _domain[1] - dm;
result = (function() {
results = [];
for (var o = 0; 0 <= numColors ? o < numColors : o > numColors; 0 <= numColors ? o++ : o--){ results.push(o); }
return results;
}).apply(this).map(function(i) {
return f(dm + i / (numColors - 1) * dd);
});
} else {
colors = [];
samples = [];
if (_classes && _classes.length > 2) {
for (i = w = 1, ref = _classes.length; 1 <= ref ? w < ref : w > ref; i = 1 <= ref ? ++w : --w) {
samples.push((_classes[i - 1] + _classes[i]) * 0.5);
}
} else {
samples = _domain;
}
result = samples.map(function(v) {
return f(v);
});
}
if (chroma[out]) {
result = result.map(function(c) {
return c[out]();
});
}
return result;
};
f.cache = function(c) {
if (c != null) {
return _useCache = c;
} else {
return _useCache;
}
};
return f;
};
if (chroma.scales == null) {
chroma.scales = {};
}
chroma.scales.cool = function() {
return chroma.scale([chroma.hsl(180, 1, .9), chroma.hsl(250, .7, .4)]);
};
chroma.scales.hot = function() {
return chroma.scale(['#000', '#f00', '#ff0', '#fff'], [0, .25, .75, 1]).mode('rgb');
};
chroma.analyze = function(data, key, filter) {
var add, k, len, o, r, val, visit;
r = {
min: Number.MAX_VALUE,
max: Number.MAX_VALUE * -1,
sum: 0,
values: [],
count: 0
};
if (filter == null) {
filter = function() {
return true;
};
}
add = function(val) {
if ((val != null) && !isNaN(val)) {
r.values.push(val);
r.sum += val;
if (val < r.min) {
r.min = val;
}
if (val > r.max) {
r.max = val;
}
r.count += 1;
}
};
visit = function(val, k) {
if (filter(val, k)) {
if ((key != null) && type(key) === 'function') {
return add(key(val));
} else if ((key != null) && type(key) === 'string' || type(key) === 'number') {
return add(val[key]);
} else {
return add(val);
}
}
};
if (type(data) === 'array') {
for (o = 0, len = data.length; o < len; o++) {
val = data[o];
visit(val);
}
} else {
for (k in data) {
val = data[k];
visit(val, k);
}
}
r.domain = [r.min, r.max];
r.limits = function(mode, num) {
return chroma.limits(r, mode, num);
};
return r;
};
chroma.limits = function(data, mode, num) {
var aa, ab, ac, ad, ae, af, ag, ah, ai, aj, ak, al, am, assignments, best, centroids, cluster, clusterSizes, dist, i, j, kClusters, limits, max_log, min, min_log, mindist, n, nb_iters, newCentroids, o, p, pb, pr, ref, ref1, ref10, ref11, ref12, ref13, ref14, ref2, ref3, ref4, ref5, ref6, ref7, ref8, ref9, repeat, sum, tmpKMeansBreaks, v, value, values, w;
if (mode == null) {
mode = 'equal';
}
if (num == null) {
num = 7;
}
if (type(data) === 'array') {
data = chroma.analyze(data);
}
min = data.min;
max = data.max;
values = data.values.sort(function(a, b) {
return a - b;
});
if (num === 1) {
return [min, max];
}
limits = [];
if (mode.substr(0, 1) === 'c') {
limits.push(min);
limits.push(max);
}
if (mode.substr(0, 1) === 'e') {
limits.push(min);
for (i = o = 1, ref = num - 1; 1 <= ref ? o <= ref : o >= ref; i = 1 <= ref ? ++o : --o) {
limits.push(min + (i / num) * (max - min));
}
limits.push(max);
} else if (mode.substr(0, 1) === 'l') {
if (min <= 0) {
throw 'Logarithmic scales are only possible for values > 0';
}
min_log = Math.LOG10E * log(min);
max_log = Math.LOG10E * log(max);
limits.push(min);
for (i = w = 1, ref1 = num - 1; 1 <= ref1 ? w <= ref1 : w >= ref1; i = 1 <= ref1 ? ++w : --w) {
limits.push(pow(10, min_log + (i / num) * (max_log - min_log)));
}
limits.push(max);
} else if (mode.substr(0, 1) === 'q') {
limits.push(min);
for (i = aa = 1, ref2 = num - 1; 1 <= ref2 ? aa <= ref2 : aa >= ref2; i = 1 <= ref2 ? ++aa : --aa) {
p = (values.length - 1) * i / num;
pb = floor(p);
if (pb === p) {
limits.push(values[pb]);
} else {
pr = p - pb;
limits.push(values[pb] * (1 - pr) + values[pb + 1] * pr);
}
}
limits.push(max);
} else if (mode.substr(0, 1) === 'k') {
/*
implementation based on
http://code.google.com/p/figue/source/browse/trunk/figue.js#336
simplified for 1-d input values
*/
n = values.length;
assignments = new Array(n);
clusterSizes = new Array(num);
repeat = true;
nb_iters = 0;
centroids = null;
centroids = [];
centroids.push(min);
for (i = ab = 1, ref3 = num - 1; 1 <= ref3 ? ab <= ref3 : ab >= ref3; i = 1 <= ref3 ? ++ab : --ab) {
centroids.push(min + (i / num) * (max - min));
}
centroids.push(max);
while (repeat) {
for (j = ac = 0, ref4 = num - 1; 0 <= ref4 ? ac <= ref4 : ac >= ref4; j = 0 <= ref4 ? ++ac : --ac) {
clusterSizes[j] = 0;
}
for (i = ad = 0, ref5 = n - 1; 0 <= ref5 ? ad <= ref5 : ad >= ref5; i = 0 <= ref5 ? ++ad : --ad) {
value = values[i];
mindist = Number.MAX_VALUE;
for (j = ae = 0, ref6 = num - 1; 0 <= ref6 ? ae <= ref6 : ae >= ref6; j = 0 <= ref6 ? ++ae : --ae) {
dist = abs(centroids[j] - value);
if (dist < mindist) {
mindist = dist;
best = j;
}
}
clusterSizes[best]++;
assignments[i] = best;
}
newCentroids = new Array(num);
for (j = af = 0, ref7 = num - 1; 0 <= ref7 ? af <= ref7 : af >= ref7; j = 0 <= ref7 ? ++af : --af) {
newCentroids[j] = null;
}
for (i = ag = 0, ref8 = n - 1; 0 <= ref8 ? ag <= ref8 : ag >= ref8; i = 0 <= ref8 ? ++ag : --ag) {
cluster = assignments[i];
if (newCentroids[cluster] === null) {
newCentroids[cluster] = values[i];
} else {
newCentroids[cluster] += values[i];
}
}
for (j = ah = 0, ref9 = num - 1; 0 <= ref9 ? ah <= ref9 : ah >= ref9; j = 0 <= ref9 ? ++ah : --ah) {
newCentroids[j] *= 1 / clusterSizes[j];
}
repeat = false;
for (j = ai = 0, ref10 = num - 1; 0 <= ref10 ? ai <= ref10 : ai >= ref10; j = 0 <= ref10 ? ++ai : --ai) {
if (newCentroids[j] !== centroids[i]) {
repeat = true;
break;
}
}
centroids = newCentroids;
nb_iters++;
if (nb_iters > 200) {
repeat = false;
}
}
kClusters = {};
for (j = aj = 0, ref11 = num - 1; 0 <= ref11 ? aj <= ref11 : aj >= ref11; j = 0 <= ref11 ? ++aj : --aj) {
kClusters[j] = [];
}
for (i = ak = 0, ref12 = n - 1; 0 <= ref12 ? ak <= ref12 : ak >= ref12; i = 0 <= ref12 ? ++ak : --ak) {
cluster = assignments[i];
kClusters[cluster].push(values[i]);
}
tmpKMeansBreaks = [];
for (j = al = 0, ref13 = num - 1; 0 <= ref13 ? al <= ref13 : al >= ref13; j = 0 <= ref13 ? ++al : --al) {
tmpKMeansBreaks.push(kClusters[j][0]);
tmpKMeansBreaks.push(kClusters[j][kClusters[j].length - 1]);
}
tmpKMeansBreaks = tmpKMeansBreaks.sort(function(a, b) {
return a - b;
});
limits.push(tmpKMeansBreaks[0]);
for (i = am = 1, ref14 = tmpKMeansBreaks.length - 1; am <= ref14; i = am += 2) {
v = tmpKMeansBreaks[i];
if (!isNaN(v) && limits.indexOf(v) === -1) {
limits.push(v);
}
}
}
return limits;
};
hsi2rgb = function(h, s, i) {
/*
borrowed from here:
http://hummer.stanford.edu/museinfo/doc/examples/humdrum/keyscape2/hsi2rgb.cpp
*/
var args, b, g, r;
args = unpack(arguments);
h = args[0], s = args[1], i = args[2];
if (isNaN(h)) {
h = 0;
}
h /= 360;
if (h < 1 / 3) {
b = (1 - s) / 3;
r = (1 + s * cos(TWOPI * h) / cos(PITHIRD - TWOPI * h)) / 3;
g = 1 - (b + r);
} else if (h < 2 / 3) {
h -= 1 / 3;
r = (1 - s) / 3;
g = (1 + s * cos(TWOPI * h) / cos(PITHIRD - TWOPI * h)) / 3;
b = 1 - (r + g);
} else {
h -= 2 / 3;
g = (1 - s) / 3;
b = (1 + s * cos(TWOPI * h) / cos(PITHIRD - TWOPI * h)) / 3;
r = 1 - (g + b);
}
r = limit(i * r * 3);
g = limit(i * g * 3);
b = limit(i * b * 3);
return [r * 255, g * 255, b * 255, args.length > 3 ? args[3] : 1];
};
rgb2hsi = function() {
/*
borrowed from here:
http://hummer.stanford.edu/museinfo/doc/examples/humdrum/keyscape2/rgb2hsi.cpp
*/
var b, g, h, i, min, r, ref, s;
ref = unpack(arguments), r = ref[0], g = ref[1], b = ref[2];
TWOPI = Math.PI * 2;
r /= 255;
g /= 255;
b /= 255;
min = Math.min(r, g, b);
i = (r + g + b) / 3;
s = 1 - min / i;
if (s === 0) {
h = 0;
} else {
h = ((r - g) + (r - b)) / 2;
h /= Math.sqrt((r - g) * (r - g) + (r - b) * (g - b));
h = Math.acos(h);
if (b > g) {
h = TWOPI - h;
}
h /= TWOPI;
}
return [h * 360, s, i];
};
chroma.hsi = function() {
return (function(func, args, ctor) {
ctor.prototype = func.prototype;
var child = new ctor, result = func.apply(child, args);
return Object(result) === result ? result : child;
})(Color, slice.call(arguments).concat(['hsi']), function(){});
};
_input.hsi = hsi2rgb;
Color.prototype.hsi = function() {
return rgb2hsi(this._rgb);
};
interpolate_hsx = function(col1, col2, f, m) {
var dh, hue, hue0, hue1, lbv, lbv0, lbv1, res, sat, sat0, sat1, xyz0, xyz1;
if (m === 'hsl') {
xyz0 = col1.hsl();
xyz1 = col2.hsl();
} else if (m === 'hsv') {
xyz0 = col1.hsv();
xyz1 = col2.hsv();
} else if (m === 'hcg') {
xyz0 = col1.hcg();
xyz1 = col2.hcg();
} else if (m === 'hsi') {
xyz0 = col1.hsi();
xyz1 = col2.hsi();
} else if (m === 'lch' || m === 'hcl') {
m = 'hcl';
xyz0 = col1.hcl();
xyz1 = col2.hcl();
}
if (m.substr(0, 1) === 'h') {
hue0 = xyz0[0], sat0 = xyz0[1], lbv0 = xyz0[2];
hue1 = xyz1[0], sat1 = xyz1[1], lbv1 = xyz1[2];
}
if (!isNaN(hue0) && !isNaN(hue1)) {
if (hue1 > hue0 && hue1 - hue0 > 180) {
dh = hue1 - (hue0 + 360);
} else if (hue1 < hue0 && hue0 - hue1 > 180) {
dh = hue1 + 360 - hue0;
} else {
dh = hue1 - hue0;
}
hue = hue0 + f * dh;
} else if (!isNaN(hue0)) {
hue = hue0;
if ((lbv1 === 1 || lbv1 === 0) && m !== 'hsv') {
sat = sat0;
}
} else if (!isNaN(hue1)) {
hue = hue1;
if ((lbv0 === 1 || lbv0 === 0) && m !== 'hsv') {
sat = sat1;
}
} else {
hue = Number.NaN;
}
if (sat == null) {
sat = sat0 + f * (sat1 - sat0);
}
lbv = lbv0 + f * (lbv1 - lbv0);
return res = chroma[m](hue, sat, lbv);
};
_interpolators = _interpolators.concat((function() {
var len, o, ref, results;
ref = ['hsv', 'hsl', 'hsi', 'hcl', 'lch', 'hcg'];
results = [];
for (o = 0, len = ref.length; o < len; o++) {
m = ref[o];
results.push([m, interpolate_hsx]);
}
return results;
})());
interpolate_num = function(col1, col2, f, m) {
var n1, n2;
n1 = col1.num();
n2 = col2.num();
return chroma.num(n1 + (n2 - n1) * f, 'num');
};
_interpolators.push(['num', interpolate_num]);
interpolate_lab = function(col1, col2, f, m) {
var res, xyz0, xyz1;
xyz0 = col1.lab();
xyz1 = col2.lab();
return res = new Color(xyz0[0] + f * (xyz1[0] - xyz0[0]), xyz0[1] + f * (xyz1[1] - xyz0[1]), xyz0[2] + f * (xyz1[2] - xyz0[2]), m);
};
_interpolators.push(['lab', interpolate_lab]);
}).call(commonjsGlobal);
});
var nerdamer_core = createCommonjsModule(function (module) {
/*
* Author : Martin Donk
* Website : http://www.nerdamer.com
* Email : martin.r.donk@gmail.com
* Source : https://github.com/jiggzson/nerdamer
*/
var nerdamer = (function(imports) {
"use strict";
var version = '0.7.11',
_ = new Parser(), //nerdamer's parser
//import bigInt
bigInt = imports.bigInt,
Groups = {},
//container of pregenerated primes
PRIMES = [],
//this is the class which holds the utilities which are exported to the core
//All utility functions which are to be made available to the core should be added to this object
Utils = {},
//Settings
Settings = {
//the max number up to which to cache primes. Making this too high causes performance issues
init_primes: 1000,
exclude: [],
//If you don't care about division by zero for example then this can be set to true.
//Has some nasty side effects so choose carefully.
suppress_errors: false,
//the global used to invoke the libary to parse to a number. Normally cos(9) for example returns
//cos(9) for convenience but parse to number will always try to return a number if set to true.
PARSE2NUMBER: false,
//this flag forces the a clone to be returned when add, subtract, etc... is called
SAFE: false,
//the symbol to use for imaginary symbols
IMAGINARY: 'i',
//the modules used to link numeric function holders
FUNCTION_MODULES: [Math],
//Allow certain characters
ALLOW_CHARS: ['Ï€'],
//Allow changing of power operator
POWER_OPERATOR: '^'
},
//Add the groups. These have been reorganized as of v0.5.1 to make CP the highest group
//The groups that help with organizing during parsing. Note that for FN is still a function even
//when it's raised to a symbol, which typically results in an EX
N = Groups.N = 1, // A number
P = Groups.P = 2, // A number with a rational power e.g. 2^(3/5).
S = Groups.S = 3, // A single variable e.g. x.
EX = Groups.EX = 4, // An exponential
FN = Groups.FN = 5, // A function
PL = Groups.PL = 6, // A symbol/expression having same name with different powers e.g. 1/x + x^2
CB = Groups.CB = 7, // A symbol/expression composed of one or more variables through multiplication e.g. x*y
CP = Groups.CP = 8, // A symbol/expression composed of one variable and any other symbol or number x+1 or x+y
CONST_HASH = Settings.CONST_HASH = '#',
//GLOBALS
PARENTHESIS = Settings.PARENTHESIS = 'parens',
//the function which represent vector
VECTOR = Settings.VECTOR = 'vector',
SQRT = Settings.SQRT = 'sqrt',
ABS = Settings.ABS = 'abs',
FACTORIAL = Settings.FACTORIAL = 'factorial',
DOUBLEFACTORIAL = Settings.DOUBLEFACTORIAL = 'dfactorial',
//the storage container "memory" for parsed expressions
EXPRESSIONS = [],
//variables
VARS = {},
//the container used to store all the reserved functions
RESERVED = ['__u__'],
WARNINGS = '',
/**
* Checks to see if value is one of nerdamer's reserved names
* @param {String} value
* @return boolean
*/
isReserved = Utils.isReserved = function(value) {
return RESERVED.indexOf(value) !== -1;
},
/**
* Use this when errors are suppressible
* @param {String} msg
*/
err = function(msg) {
if(!Settings.suppress_errors) throw new Error(msg);
},
/**
* Used to pass warnings or low severity errors about the library
* @param msg
*/
warn = function(msg) {
},
/**
* Enforces rule: "must start with a letter or underscore and
* can have any number of underscores, letters, and numbers thereafter."
* @param name The name of the symbol being checked
* @param {String} typ - The type of symbols that's being validated
* @throws {Exception} - Throws an exception on fail
*/
validateName = Utils.validateName = function(name, typ) {
typ = typ || 'variable';
if(Settings.ALLOW_CHARS.indexOf(name) !== -1)
return;
var regex = /^[a-z_][a-z\d\_]*$/gi;
if(!(regex.test( name)) ) {
throw new Error(name+' is not a valid '+typ+' name');
}
},
/**
* Finds intersection of two arrays
* @param {array} a
* @param {Array} b
* @param {Array} compare_fn
* @returns {Array}
*/
intersection = Utils.intersection = function(a, b, compare_fn) {
var c = [];
if(a.length > b.length) {
var t = a; a = b; b = t;
}
b = b.slice();
var l = a.length, l2 = b.length;
for(var i=0; i<l; i++) {
var item = a[i];
for(var j=0; j<l2; j++) {
var item2 = b[j];
if(item2 === undefined) continue;
var equals = compare_fn ? compare_fn(item, item2) : item === item2;
if(equals) {
b[j] = undefined;
c.push(item);
continue;
}
}
}
return c;
},
//convert number from scientific format to decimal format
scientificToDecimal = Utils.scientificToDecimal = function(num) {
//if the number is in scientific notation remove it
if(/\d+\.?\d*e[\+\-]*\d+/i.test(num)) {
var zero = '0',
parts = String(num).toLowerCase().split('e'), //split into coeff and exponent
e = parts.pop(),//store the exponential part
l = Math.abs(e), //get the number of zeros
sign = e/l,
coeff_array = parts[0].split('.');
if(sign === -1) {
num = zero + '.' + new Array(l).join(zero) + coeff_array.join('');
}
else {
var dec = coeff_array[1];
if(dec) l = l - dec.length;
num = coeff_array.join('') + new Array(l+1).join(zero);
}
}
return num;
},
/**
* Checks if number is a prime number
* @param {Number} n - the number to be checked
*/
isPrime = Utils.isPrime = function(n) {
var q = Math.floor(Math.sqrt(n));
for (var i = 2; i <= q; i++) {
if (n % i === 0) return false;
}
return true;
},
/**
* Checks to see if a number or Symbol is a fraction
* @param {Number|Symbol} num
* @returns {boolean}
*/
isFraction = Utils.isFraction = function(num) {
if(isSymbol(num)) return isFraction(num.multiplier.toDecimal());
return (num % 1 !== 0);
},
/**
* Checks to see if the object provided is a Symbol
* @param {Object} obj
*/
isSymbol = Utils.isSymbol = function(obj) {
return (obj instanceof Symbol);
},
/**
* Checks to see if the object provided is an Expression
* @param {Object} obj
*/
isExpression = Utils.isExpression = function(obj) {
return (obj instanceof Expression);
},
/**
*
* Checks to see if the object provided is a Vector
* @param {Object} obj
*/
isVector = Utils.isVector = function(obj) {
return (obj instanceof Vector);
},
/**
* Checks to see if the object provided is a Matrix
* @param {Object} obj
*/
isMatrix = Utils.isMatrix = function(obj) {
return (obj instanceof Matrix);
},
/**
* @param {Symbol} symbol
*/
isNumericSymbol = Utils.isNumericSymbol = function(symbol) {
return symbol.group === N;
},
/**
* Checks to see if the object provided is an Array
* @param {Object} arr
*/
isArray = Utils.isArray = function(arr) {
return arr instanceof Array;
},
/**
* Checks to see if a number is an integer
* @param {Number} num
*/
isInt = Utils.isInt = function(num) {
return num % 1 === 0;
},
/**
* @param {Number|Symbol} obj
* @returns {boolean}
*/
isNegative = Utils.isNegative = function(obj) {
if( isSymbol(obj) ) {
obj = obj.multiplier;
}
return obj.lessThan(0);
},
/**
* @param {String} str
* @returns {String} - returns a formatted string surrounded by brackets
*/
inBrackets = Utils.inBrackets = function(str) {
return '('+str+')';
},
/**
* A helper function to replace parts of string
* @param {String} str - The original string
* @param {Integer} from - The starting index
* @param {Integer} to - The ending index
* @param {String} with_str - The replacement string
* @returns {String} - A formatted string
*/
stringReplace = Utils.stringReplace = function(str, from, to, with_str) {
return str.substr(0, from)+with_str+str.substr(to, str.length);
},
/**
* the Parser uses this to check if it's allowed to convert the obj to type Symbol
* @param {Object} obj
* @returns {boolean}
*/
customType = Utils.customType = function(obj) {
return obj !== undefined && obj.custom;
},
/**
* Checks to see if numbers are both negative or are both positive
* @param {Number} a
* @param {Number} b
* @returns {boolean}
*/
sameSign = Utils.sameSign = function(a, b) {
return (a < 0) === (b < 0);
},
/**
* A helper function to replace multiple occurences in a string. Takes multiple arguments
* @example format('{0} nice, {0} sweet')
* //returns 'something nice, something sweet'
*/
format = Utils.format = function() {
var args = [].slice.call(arguments),
str = args.shift();
var new_str = str.replace(/{(\d+)}/g, function(match, index) {
var arg = args[index];
return typeof arg === 'function' ? arg() : arg;
});
return new_str;
},
/**
* Returns an array of all the keys in an array
* @param {Object} obj
* @returns {Array}
*/
keys = Utils.keys = Object.keys,
/**
* Returns the first encountered item in an object. Items do not have a fixed order in objects
* so only use if you need any first random or if there's only one item in the object
* @param {Object} obj
* @returns {*}
*/
firstObject = Utils.firstObject = function(obj) {
for( var x in obj ) break;
return obj[x];
},
/**
* Substitutes out variables for two symbols, parses them to a number and them compares them numerically
* @param {Symbol} sym1
* @param {Symbol} sym2
* @param {String[]} vars - an optional array of variables to use
* @returns {bool}
*/
compare = Utils.compare = function(sym1, sym2, vars) {
var n = 5; //a random number between 1 and 5 is good enough
var scope = {}; // scope object with random numbers generated using vars
var comparison;
for(var i=0; i<vars.length; i++)
scope[vars[i]] = new Symbol(Math.floor(Math.random()*n)+1);
block('PARSE2NUMBER', function() {
comparison = _.parse(sym1, scope).equals(_.parse(sym2, scope));
});
return comparison;
},
/**
* Returns the minimum number in an array
* @param {Array} arr
* @returns {Number}
*/
arrayMax = Utils.arrayMax = function(arr) {
return Math.max.apply(undefined, arr);
},
/**
* Returns the maximum number in an array
* @param {Array} arr
* @returns {Number}
*/
arrayMin = Utils.arrayMin = function(arr) {
return Math.min.apply(undefined, arr);
},
/**
* Clones array with clonable items
* @param {Array} arr
* @returns {Array}
*/
arrayClone = Utils.arrayClone = function(arr) {
var new_array = [], l = arr.length;
for(var i=0; i<l; i++) new_array[i] = arr[i].clone();
return new_array;
},
comboSort = Utils.comboSort = function(a, b) {
var l = a.length,
combined = []; //the linker
for(var i=0; i<a.length; i++) {
combined.push([a[i], b[i]]); //create the map
}
combined.sort(function(x, y) {
return x[0] - y[0];
});
var na = [], nb = [];
for(i=0; i<l; i++) {
na.push(combined[i][0]);
nb.push(combined[i][1]);
}
return [na, nb];
},
/**
* Rounds a number up to x decimal places
* @param {Number} x
* @param {Number} s
*/
round = Utils.round = function( x, s ) {
s = s || 14;
return Math.round( x*Math.pow( 10,s ) )/Math.pow( 10,s );
},
/**
* This method traverses the symbol structure and grabs all the variables in a symbol. The variable
* names are then returned in alphabetical order.
* @param {Symbol} obj
* @param {Boolean} poly
* @param {Object} vars - An object containing the variables. Do not pass this in as it generated
* automatically. In the future this will be a Collector object.
* @returns {String[]} - An array containing variable names
*/
variables = Utils.variables = function(obj, poly, vars) {
vars = vars || {
c: [],
add: function(value) {
if(this.c.indexOf(value) === -1 && isNaN(value)) this.c.push(value);
}
};
if(isSymbol(obj)) {
var group = obj.group,
prevgroup = obj.previousGroup;
if(group === EX) variables(obj.power, poly, vars);
if(group === CP || group === CB || prevgroup === CP || prevgroup === CB) {
for(var x in obj.symbols) variables(obj.symbols[x], poly, vars);
}
else if(group === S || prevgroup === S) {
//very crude needs fixing. TODO
if(!(obj.value === 'e' || obj.value === 'pi'))
vars.add(obj.value);
}
else if(group === PL || prevgroup === PL) {
variables(firstObject(obj.symbols), poly, vars);
}
else if(group === EX) {
if(!isNaN(obj.value)) vars.add(obj.value);
variables(obj.power, poly, vars);
}
else if(group === FN && !poly) {
for(var i=0; i<obj.args.length; i++) {
variables(obj.args[i], poly, vars);
}
}
}
return vars.c.sort();
},
/**
* Loops through each item in object and calls function with item as param
* @param {Object|Array} obj
* @param {Function} fn
*/
each = Utils.each = function(obj, fn) {
if(isArray(obj)) {
var l = obj.length;
for(var i=0; i<l; i++) fn.call(obj, i);
}
else {
for(var x in obj) if(obj.hasOwnProperty(x)) fn.call(obj, x);
}
},
/**
* Checks to see if a number is an even number
* @param {Number} num
* @returns {boolean}
*/
even = Utils.even = function(num) {
return num % 2 === 0;
},
/**
* Checks to see if a fraction is divisible by 2
* @param {Number} num
* @returns {boolean}
*/
evenFraction = Utils.evenFraction = function(num) {
return 1/( num % 1) % 2 === 0;
},
/**
* Strips duplicates out of an array
* @param {Array} arr
*/
arrayUnique = Utils.arrayUnique = function(arr) {
var l = arr.length, a = [];
for(var i=0; i<l; i++) {
var item = arr[i];
if(a.indexOf(item) === -1) a.push(item);
}
return a;
},
/**
* Reserves the names in an object so they cannot be used as function names
* @param {Object} obj
*/
reserveNames = Utils.reserveNames = function(obj) {
var add = function(item) {
if(RESERVED.indexOf(item) === -1) RESERVED.push(item);
};
if(typeof obj === 'string') add(obj);
else {
each(obj, function(x) {
add(x);
});
}
},
/**
* Removes an item from either an array or an object. If the object is an array, the index must be
* specified after the array. If it's an object then the key must be specified
* @param {Object|Array} obj
* @param {Integer} indexOrKey
*/
remove = Utils.remove = function( obj, indexOrKey ) {
var result;
if( isArray(obj) ) {
result = obj.splice(indexOrKey, 1)[0];
}
else {
result = obj[indexOrKey];
delete obj[indexOrKey];
}
return result;
},
/**
* Creates a temporary block in which one of the global settings is temporarily modified while
* the function is called. For instance if you want to parse directly to a number rather than have a symbolic
* answer for a period you would set PARSE2NUMBER to true in the block.
* @example block('PARSE2NUMBER', function(){//symbol being parsed to number}, true);
* @param {String} setting - The setting being accessed
* @param {Function} f
* @param {boolean} opt - The value of the setting in the block
* @param {String} obj - The obj of interest. Usually a Symbol but could be any object
*/
block = Utils.block = function(setting, f, opt, obj) {
var current_setting = Settings[setting];
Settings[setting] = opt === undefined ? true : !! opt;
var retval = f.call(obj);
Settings[setting] = current_setting;
return retval;
},
/**
* Converts function arguments to an array. I had hopes for this function :(
* @param {Object} obj - arguments obj
*/
arguments2Array = Utils.arguments2Array = function(obj) {
return [].slice.call(obj);
},
getCoeffs = Utils.getCoeffs = function(symbol, wrt) {
var coeffs = [];
//we loop through the symbols and stick them in their respective
//containers e.g. y*x^2 goes to index 2
symbol.each(function(term) {
if(term.contains(wrt)) {
//we want only the coefficient which in this case will be everything but the variable
//e.g. a*b*x -> a*b if the variable to solve for is x
var coeff = term.stripVar(wrt),
x = _.divide(term.clone(), coeff.clone()),
p = x.power.toDecimal();
}
else {
coeff = term;
p = 0;
}
var e = coeffs[p];
//if it exists just add it to it
coeffs[p] = e ? _.add(e, coeff) : coeff;
}, true);
for(var i=0; i<coeffs.length; i++)
if(!coeffs[i])
coeffs[i] = new Symbol(0);
//fill the holes
return coeffs;
},
/**
* Using a regex to get between brackets can be a bit tricky. This functions makes it more abstract
* to fetch between brackets within a string from any given index. If the starting index is a bracket
* then it will fail. returns [matched_string, first_bracket_index, end_bracket_index]
* @param {Char} ob - open bracket
* @param {Char} cb - close bracket
* @param {String} str - The string being read
* @param {Integer} start - Where in the string to start
* @returns {Array}
*/
betweenBrackets = function(ob, cb, str, start) {
},
/**
* A helper function to make substitutions
* @param {Object} subs
*/
format_subs = function(subs) {
},
generatePrimes = Utils.generatePrimes = function(upto) {
//get the last prime in the array
var last_prime = PRIMES[PRIMES.length-1] || 2;
//no need to check if we've already encountered the number. Just check the cache.
for(var i=last_prime; i<upto; i++) {
if(isPrime(i)) PRIMES.push(i);
}
},
evaluate = Utils.evaluate = function (symbol) {
return block('PARSE2NUMBER', function() {
return _.parse(symbol);
}, true);
},
//This object holds additional functions for nerdamer. Think of it as an extension of the Math object.
//I really don't like touching objects which aren't mine hence the reason for Math2. The names of the
//functions within are pretty self-explanatory.
Math2 = {
csc: function(x) { return 1/Math.sin(x); },
sec: function(x) { return 1/Math.cos(x); },
cot: function(x) { return 1/Math.tan(x); },
// https://gist.github.com/jiggzson/df0e9ae8b3b06ff3d8dc2aa062853bd8
erf: function(x) {
var t = 1/(1+0.5*Math.abs(x));
var result = 1-t*Math.exp( -x*x - 1.26551223 +
t * ( 1.00002368 +
t * ( 0.37409196 +
t * ( 0.09678418 +
t * (-0.18628806 +
t * ( 0.27886807 +
t * (-1.13520398 +
t * ( 1.48851587 +
t * (-0.82215223 +
t * ( 0.17087277)))))))))
);
return x >= 0 ? result : -result;
},
bigpow: function(n, p) {
n = Frac.simple(n);
var r = n.clone();
for(var i=0; i<p-1; i++) {
r = r.multiply(n);
}
return r;
},
//http://stackoverflow.com/questions/15454183/how-to-make-a-function-that-computes-the-factorial-for-numbers-with-decimals
gamma: function(z) {
var g = 7;
var C = [
0.99999999999980993,
676.5203681218851,
-1259.1392167224028,
771.32342877765313,
-176.61502916214059,
12.507343278686905,
-0.13857109526572012,
9.9843695780195716e-6,
1.5056327351493116e-7];
if (z < 0.5) return Math.PI / (Math.sin(Math.PI * z) * gamma(1 - z));
else {
z -= 1;
var x = C[0];
for (var i = 1; i < g + 2; i++)
x += C[i] / (z + i);
var t = z + g + 0.5;
return Math.sqrt(2 * Math.PI) * Math.pow(t, (z + 0.5)) * Math.exp(-t) * x;
}
},
//factorial
bigfactorial: function(x) {
var retval = new Frac(1);
for (var i = 2; i <= x; i++)
retval = retval.multiply(new Frac(i));
return retval;
},
//the factorial function but using the big library instead
factorial: function(x) {
if(x < 0)
throw new Error('factorial not defined for negative numbers');
var retval=1;
for (var i = 2; i <= x; i++) retval = retval * i;
return retval;
},
//double factorial
dfactorial: function(x) {
var even = x % 2 === 0;
// If x = even then n = x/2 else n = (x-1)/2
var n = even ? x/2 : (x+1)/2;
//the return value
var r = new Frac(1);
//start the loop
if(even)
for(var i=1; i<=n; i++)
r = r.multiply(new Frac(2).multiply(new Frac(i)));
else
for(var i=1; i<=n; i++)
r = r.multiply(new Frac(2).multiply(new Frac(i)).subtract(new Frac(1)));
//done
return r;
},
GCD: function() {
var args = arrayUnique([].slice.call(arguments)
.map(function(x){ return Math.abs(x); })).sort(),
a = Math.abs(args.shift()),
n = args.length;
while(n-- > 0) {
var b = Math.abs(args.shift());
while(true) {
a %= b;
if (a === 0) {
a = b;
break;
}
b %= a;
if (b === 0) break;
}
}
return a;
},
QGCD: function() {
var args = [].slice.call(arguments);
var a = args[0];
for(var i=1; i<args.length; i++) {
var b = args[i];
var sign = a.isNegative() && b.isNegative() ? -1 : 1;
a = b.gcd(a);
if(sign < 0) a.negate();
}
return a;
},
LCM: function(a, b) {
return (a * b) / Math2.GCD(a, b);
},
//pow but with the handling of negative numbers
//http://stackoverflow.com/questions/12810765/calculating-cubic-root-for-negative-number
pow: function(b, e) {
if (b < 0) {
if (Math.abs(e) < 1) {
//nth root of a negative number is imaginary when n is even
if (1 / e % 2 === 0) return NaN;
return -Math.pow(Math.abs(b), e);
}
}
return Math.pow(b, e);
},
factor: function(n) {
var ifactors = Math2.ifactor(n);
var factors = new Symbol();
factors.symbols = {};
factors.group = CB;
for(var x in ifactors) {
var factor = new Symbol(1);
factor.group = P; //cheat a little
factor.value = x;
factor.power = new Symbol(ifactors[x]);
factors.symbols[x] = factor;
}
factors.updateHash();
return factors;
},
//uses trial division to get factors
ifactor: function(n, factors) {
factors = factors || {};
var r = Math.floor(Math.sqrt(n));
var lcprime = PRIMES[PRIMES.length-1];
//a one-time cost... Hopefully ... And don't bother for more than a million
//takes too long
if(r > lcprime && n < 1e6) generatePrimes(r);
var l = PRIMES.length;
for(var i=0; i<l; i++) {
var prime = PRIMES[i];
//trial division
while(n%prime === 0) {
n = n/prime;
factors[prime] = (factors[prime] || 0)+1;
}
}
if(n > 1) factors[n] = 1;
return factors;
},
//factors a number into rectangular box. If sides are primes that this will be
//their prime factors. e.g. 21 -> (7)(3), 133 -> (7)(19)
boxfactor: function(n, max) {
max = max || 200; //stop after this number of iterations
var c, r,
d = Math.floor((5/12)*n), //the divisor
i = 0, //number of iterations
safety = false;
while(true) {
c = Math.floor(n/d);
r = n % d;
if(r === 0) break; //we're done
if(safety) return [n, 1];
d = Math.max(r, d-r);
i++;
safety = i > max;
}
return [c, d, i];
},
fib: function(n) {
var a = 0, b = 1, f = 1;
for(var i = 2; i <= n; i++) {
f = a + b;
a = b;
b = f;
}
return f;
},
mod: function(x, y) {
return x % y;
},
/**
*
* @param {Function} f - the function being integrated
* @param {Number} l - lower bound
* @param {Number} u - upper bound
* @param {Number} dx - step width
* @returns {Number}
*/
num_integrate: function(f, l, u, dx) {
dx = dx || 0.001; //default width of dx
var n, sum, x0, x1, a;
n = (u-l)/dx;// number of trapezoids
sum = 0; //area
a = 0;
for (var i=1; i<=n; i++) {
//the x locations of the left and right side of each trapezpoid
x0 = l + (i-1)*dx;
x1 = l + i*dx;
a = dx * (f(x0) + f(x1))/ 2; //the area
sum += a;
}
//avoid errors with numbers around -1e-14;
sum = round(sum, 13);
return sum;
},
//https://en.wikipedia.org/wiki/Trigonometric_integral
//CosineIntegral
Ci: function(x) {
var n =20,
g = 0.5772156649015329, //roughly Euler–Mascheroni
sum = 0;
for(var i=1; i<n; i++) {
var n2 = 2*i; //cache 2n
sum += (Math.pow(-1, i)*Math.pow(x, n2))/(n2*Math2.factorial(n2));
}
return Math.log(x) + g + sum;
},
//SineIntegral
Si: function(x) {
var n = 20,
sum = 0;
for(var i=0; i<n; i++) {
var n2 = 2*i;
sum += (Math.pow(-1, i)*Math.pow(x, n2+1))/((n2+1)*Math2.factorial(n2+1));
}
return sum;
},
//ExponentialIntegral
Ei: function(x) {
if(x.equals(0))
return -Infinity;
var n =30,
g = 0.5772156649015328606, //roughly Euler–Mascheroni
sum = 0;
for(var i=1; i<n; i++) {
sum += Math.pow(x, i)/(i*Math2.factorial(i));
}
return g+Math.abs(Math.log(x))+sum;
},
//Hyperbolic Sine Integral
//http://mathworld.wolfram.com/Shi.html
Shi: function(x) {
var n = 30,
sum = 0,
k, t;
for(var i=0; i<n; i++) {
k = 2*i;
t = k+1;
sum += Math.pow(x, t)/(t*t*Math2.factorial(k));
}
return sum;
},
//the cosine integral function
Chi: function(x) {
var dx, g, f;
dx = 0.001;
g = 0.5772156649015328606;
f = function(t) {
return (Math.cosh(t)-1)/t;
};
return Math.log(x)+g+Math2.num_integrate(f, 0.002, x, dx);
},
//the gamma incomplete function
gamma_incomplete: function(n, x) {
var t = n-1,
sum = 0,
x = x || 0;
for(var i=0; i<t; i++) {
sum += Math.pow(x, i)/Math2.factorial(i);
}
return Math2.factorial(t)*Math.exp(-x)*sum;
},
/*
* Heaviside step function - Moved from Special.js (originally contributed by Brosnan Yuen)
* Specification : http://mathworld.wolfram.com/HeavisideStepFunction.html
* if x > 0 then 1
* if x == 0 then 1/2
* if x < 0 then 0
*/
step: function(x) {
if(x > 0)
return 1;
if(x < 0)
return 0;
return 0.5;
},
/*
* Rectangle function - Moved from Special.js (originally contributed by Brosnan Yuen)
* Specification : http://mathworld.wolfram.com/RectangleFunction.html
* if |x| > 1/2 then 0
* if |x| == 1/2 then 1/2
* if |x| < 1/2 then 1
*/
rect: function(x) {
var x = Math.abs(x);
if(x === 0.5)
return x;
if(x > 0.5)
return 0;
return 1;
},
/*
* Sinc function - Moved from Special.js (originally contributed by Brosnan Yuen)
* Specification : http://mathworld.wolfram.com/SincFunction.html
* if x == 0 then 1
* otherwise sin(x)/x
*/
sinc: function(x) {
if(x.equals(0))
return 1;
return Math.sin(x)/x;
},
/*
* Triangle function - Moved from Special.js (originally contributed by Brosnan Yuen)
* Specification : http://mathworld.wolfram.com/TriangleFunction.html
* if |x| >= 1 then 0
* if |x| < then 1-|x|
*/
tri: function(x) {
x = Math.abs(x);
if(x >= 1)
return 0;
return 1-x;
}
};
//link the Math2 object to Settings.FUNCTION_MODULES
Settings.FUNCTION_MODULES.push(Math2);
//Make Math2 visible to the parser
Settings.FUNCTION_MODULES.push(Math2);
//polyfills
//https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/
Math.sign = Math.sign || function(x) {
x = +x; // convert to a number
if (x === 0 || isNaN(x)) {
return x;
}
return x > 0 ? 1 : -1;
};
Math.cosh = Math.cosh || function(x) {
var y = Math.exp(x);
return (y + 1 / y) / 2;
};
Math.sinh = Math.sinh || function(x) {
var y = Math.exp(x);
return (y - 1 / y) / 2;
};
Math.tanh = Math.tanh || function(x) {
if (x === Infinity) {
return 1;
} else if (x === -Infinity) {
return -1;
} else {
var y = Math.exp(2 * x);
return (y - 1) / (y + 1);
}
};
Math.asinh = Math.asinh || function(x) {
if (x === -Infinity) {
return x;
} else {
return Math.log(x + Math.sqrt(x * x + 1));
}
};
Math.acosh = Math.acosh || function(x) {
return Math.log(x + Math.sqrt(x * x - 1));
};
Math.atanh = Math.atanh || function(x) {
return Math.log((1+x)/(1-x)) / 2;
};
Math.log10 = Math.log10 || function(x) {
return Math.log(x) * Math.LOG10E;
};
reserveNames(Math2); //reserve the names in Math2
/* GLOBAL FUNCTIONS */
/**
* This method will return a hash or a text representation of a Symbol, Matrix, or Vector.
* If all else fails it *assumes* the object has a toString method and will call that.
*
* @param {Object} obj
* @param {String} option get is as a hash
* @returns {String}
*/
function text(obj, option, useGroup) {
var asHash = option === 'hash',
asDecimal = option === 'decimals' || option === 'decimal',
opt = asHash ? undefined : option;
//if the object is a symbol
if(isSymbol(obj)) {
var multiplier = '',
power = '',
sign = '',
group = obj.group || useGroup,
value = obj.value;
//if the value is to be used as a hash then the power and multiplier need to be suppressed
if(!asHash) {
//use asDecimal to get the object back as a decimal
var om = asDecimal ? obj.multiplier.valueOf() : obj.multiplier.toString();
if(om == '-1') {
sign = '-';
om = '1';
}
//only add the multiplier if it's not 1
if(om != '1') multiplier = om;
//use asDecimal to get the object back as a decimal
var p = obj.power ? (asDecimal ? obj.power.valueOf() : obj.power.toString()) : '';
//only add the multiplier
if(p != '1') {
//is it a symbol
if(isSymbol(p)) {
power = text(p, opt);
}
else {
power = p;
}
}
}
switch(group) {
case N:
multiplier = '';
//if it's numerical then all we need is the multiplier
value = obj.multiplier == '-1' ? '1' : asDecimal ? obj.valueOf() : obj.multiplier.toString();
power = '';
break;
case PL:
value = obj.collectSymbols(text, opt).join('+').replace(/\+\-/g, '-');
break;
case CP:
value = obj.collectSymbols(text, opt).join('+').replace(/\+\-/g, '-');
break;
case CB:
value = obj.collectSymbols(function(symbol){
var g = symbol.group;
//both groups will already be in brackets if their power is greater than 1
//so skip it.
if((g === PL || g === CP) && (symbol.power.equals(1) && symbol.multiplier.equals(1))) {
return inBrackets(text(symbol, opt));
}
return text(symbol, opt);
}).join('*');
break;
case EX:
var pg = obj.previousGroup,
pwg = obj.power.group;
//PL are the exception. It's simpler to just collect and set the value
if(pg === PL) value = obj.collectSymbols(text, opt).join('+').replace('+-', '-');
if(!(pg === N || pg === S || pg === FN) && !asHash) { value = inBrackets(value); }
if((pwg === CP || pwg === CB || pwg === PL || obj.power.multiplier.toString() != '1') && power) {
power = inBrackets(power);
}
break;
}
if(group === FN && asDecimal) {
value = obj.fname+inBrackets(obj.args.map(function(symbol) {
return text(symbol, opt);
}).join(','));
}
//wrap the power since / is less than ^
//TODO: introduce method call isSimple
if(power && !isInt(power) && group !== EX && !asDecimal) { power = inBrackets(power); }
//the following groups are held together by plus or minus. They can be raised to a power or multiplied
//by a multiplier and have to be in brackets to preserve the order of precedence
if(((group === CP || group === PL) && (multiplier && multiplier != '1' || sign === '-'))
|| ((group === CB || group === CP || group === PL) && (power && power != '1'))
|| !asHash && group === P && value == -1
|| obj.fname === PARENTHESIS) {
value = inBrackets(value);
}
var c = sign+multiplier;
if(multiplier && !isInt(multiplier) && !asDecimal) c = inBrackets(c);
if(power < 0) power = inBrackets(power);
if(multiplier) c = c + '*';
if(power) power = Settings.POWER_OPERATOR + power;
//this needs serious rethinking. Must fix
if(group === EX && value.charAt(0) === '-') value = inBrackets(value);
var cv = c+value;
if(obj.parens) cv = inBrackets(cv);
return cv+power;
}
else if(isVector(obj)) {
var l = obj.elements.length,
c = [];
for(var i=0; i<l; i++) c.push(obj.elements[i].text());
return '['+c.join(',')+']';
}
else {
try {
return obj.toString();
}
catch(e) { return ''; }
}
}
Utils.text = text;
/* END GLOBAL FUNCTIONS */
/**** CLASSES *****/
/**
* The Collector is used to find unique values within objects
* @param {Function} extra_conditions - A function which performs a check on the values and returns a boolean
* @returns {Collector}
*/
function Expression(symbol) {
//we don't want arrays wrapped
this.symbol = symbol;
}
/**
* Returns stored expression at index. For first index use 1 not 0.
* @param {bool} asType
* @param {Integer} expression_number
*/
Expression.getExpression = function(expression_number, asType) {
if(expression_number === 'last' || !expression_number) expression_number = EXPRESSIONS.length;
if(expression_number === 'first') expression_number = 1;
var index = expression_number -1,
expression = EXPRESSIONS[index],
retval = expression ? new Expression(expression) : expression;
return retval;
};
Expression.prototype = {
/**
* Returns the text representation of the expression
* @param {String} opt - option of formatting numbers
* @returns {String}
*/
text: function(opt) {
opt = opt || 'decimals';
if(this.symbol.text_)
return this.symbol.text_(opt);
return text(this.symbol, opt);
},
/**
* Returns the latex representation of the expression
* @param {String} option - option for formatting numbers
* @returns {String}
*/
latex: function(option) {
if(this.symbol.latex_)
return this.symbol.latex_(option);
return LaTeX.latex(this.symbol, option);
},
valueOf: function() {
return this.symbol.valueOf();
},
/**
* Evaluates the expression and tries to reduce it to a number if possible.
* If an argument is given in the form of %{integer} it will evaluate that expression.
* Other than that it will just use it's own text and reparse
* @returns {Expression}
*/
evaluate: function() {
var first_arg = arguments[0], expression, idx = 1;
if(typeof first_arg === 'string') {
expression = (first_arg.charAt(0) === '%') ? Expression.getExpression(first_arg.substr(1)).text() : first_arg;
}
else if(first_arg instanceof Expression || isSymbol(first_arg)) {
expression = first_arg.text();
}
else {
expression = this.symbol.text(); idx--;
}
var subs = arguments[idx] || {};
return new Expression(block('PARSE2NUMBER', function() {
return _.parse(expression, subs);
}, true));
},
/**
* Converts a symbol to a JS function. Pass in an array of variables to use that order instead of
* the default alphabetical order
* @param {Array}
*/
buildFunction: function(vars) {
return build(this.symbol, vars);
},
/**
* Checks to see if the expression is just a plain old number
* @returns {boolean}
*/
isNumber: function() {
return isNumericSymbol(this.symbol);
},
/**
* Checks to see if the expression is infinity
* @returns {boolean}
*/
isInfinity: function() {
return Math.abs(this.symbol.multiplier) === Infinity;
},
/**
* Returns all the variables in the expression
* @returns {Array}
*/
variables: function() {
return variables(this.symbol);
},
toString: function() {
try {
if(isArray(this.symbol)) return '['+this.symbol.toString()+']';
return this.symbol.toString();
}
catch(e) { return ''; }
},
//forces the symbol to be returned as a decimal
toDecimal: function() {
return this.symbol.toDecimal();
},
//checks to see if the expression is a fraction
isFraction: function() {
return isFraction(this.symbol);
},
//checks to see if the symbol is a multivariate polynomial
isPolynomial: function() {
return this.symbol.isPoly();
},
//performs a substitution
sub: function(symbol, for_symbol) {
return new Expression(this.symbol.sub(_.parse(symbol), _.parse(for_symbol)));
},
operation: function(otype, symbol) {
if(isExpression(symbol))
symbol = symbol.symbol;
else if(!isSymbol(symbol))
symbol = _.parse(symbol);
return new Expression(_[otype](this.symbol.clone(), symbol.clone()));
},
add: function(symbol) {
return this.operation('add', symbol);
},
subtract: function(symbol) {
return this.operation('subtract', symbol);
},
multiply: function(symbol) {
return this.operation('multiply', symbol);
},
divide: function(symbol) {
return this.operation('divide', symbol);
},
pow: function(symbol) {
return this.operation('pow', symbol);
},
expand: function() {
return new Expression(_.expand(this.symbol));
},
each: function(callback, i) {
if(this.symbol.each)
this.symbol.each(callback, i);
else if(isArray(this.symbol)) {
for(var i=0; i<this.symbol.length; i++)
callback.call(this.symbol, this.symbol[i], i);
}
else
callback.call(this.symbol);
},
eq: function(value) {
value = _.parse(value);
if(this.symbol.isConstant() && value.isConstant())
return this.symbol.equals(_.parse(value));
return false;
},
lt: function(value) {
value = _.parse(value);
if(this.symbol.isConstant() && value.isConstant())
return this.symbol.lessThan(_.parse(value));
return false;
},
gt: function(value) {
value = _.parse(value);
if(this.symbol.isConstant() && value.isConstant())
return this.symbol.greaterThan(_.parse(value));
return false;
},
gte: function(value) {
return this.greaterThan(value) || this.equals(value);
},
lte: function(value) {
return this.lessThan(value) || this.equals(value);
}
};
//Aliases
Expression.prototype.toTeX = Expression.prototype.latex;
function Frac(n) {
if(n instanceof Frac) return n;
if(n === undefined) return this;
if(isInt(n)) {
this.num = bigInt(n);
this.den = bigInt(1);
}
else {
var frac = Fraction.convert(n);
this.num = new bigInt(frac[0]);
this.den = new bigInt(frac[1]);
}
}
Frac.isFrac = function(o) {
return (o instanceof Frac);
};
Frac.quick = function(n, d) {
var frac = new Frac();
frac.num = new bigInt(n);
frac.den = new bigInt(d);
return frac;
};
Frac.simple = function(n) {
var nstr = String(n),
m_dc = nstr.split('.'),
num = m_dc.join(''),
den = 1,
l = (m_dc[1] || '').length;
for(var i=0; i<l; i++)
den += '0';
var frac = Frac.quick(num, den);
return frac.simplify();
};
Frac.prototype = {
multiply: function(m) {
if(this.isOne()) {
return m.clone();
}
if(m.isOne()) {
return this.clone();
}
var c = this.clone();
c.num = c.num.multiply(m.num);
c.den = c.den.multiply(m.den);
return c.simplify();
},
divide: function(m) {
if(m.equals(0)) throw new Error('Division by zero not allowed!');
return this.clone().multiply(m.clone().invert()).simplify();
},
subtract: function(m) {
return this.clone().add(m.clone().neg());
},
neg: function() {
this.num = this.num.multiply(-1);
return this;
},
add: function(m) {
var n1 = this.den, n2 = m.den, c = this.clone();
var a = c.num, b = m.num;
if(n1.equals(n2)) {
c.num = a.add(b);
}
else {
c.num = a.multiply(n2).add(b.multiply(n1));
c.den = n1.multiply(n2);
}
return c.simplify();
},
mod: function(m) {
var a = this.clone(),
b = m.clone();
//make their denominators even and return the mod of their numerators
a.num = a.num.multiply(b.den);
a.den = a.den.multiply(b.den);
b.num = b.num.multiply(this.den);
b.den = b.den.multiply(this.den);
a.num = a.num.mod(b.num);
return a.simplify();
},
simplify: function() {
var gcd = bigInt.gcd(this.num, this.den);
this.num = this.num.divide(gcd);
this.den = this.den.divide(gcd);
return this;
},
clone: function() {
var m = new Frac();
m.num = new bigInt(this.num);
m.den = new bigInt(this.den);
return m;
},
toDecimal: function() {
//toDecimal is primarily used for storing polynomials. If you need a bigNumber library for that
//you probably have bigger concerns
return this.num/this.den;
},
qcompare: function(n) {
return [this.num.multiply(n.den), n.num.multiply(this.den)];
},
equals: function(n) {
if(!isNaN(n)) n = new Frac(n);
var q = this.qcompare(n);
return q[0].equals(q[1]);
},
absEquals: function(n) {
if(!isNaN(n)) n = new Frac(n);
var q = this.qcompare(n);
return q[0].abs().equals(q[1]);
},
//lazy check to be fixed. Sufficient for now but will cause future problems
greaterThan: function(n) {
if(!isNaN(n)) n = new Frac(n);
var q = this.qcompare(n);
return q[0].gt(q[1]);
},
lessThan: function(n) {
if(!isNaN(n)) n = new Frac(n);
var q = this.qcompare(n);
return q[0].lt(q[1]);
},
isInteger: function() {
return this.den.equals(1);
},
negate: function() {
this.num = this.num.multiply(-1);
return this;
},
invert: function() {
var t = this.den;
var isnegative = this.num.isNegative();
this.den = this.num.abs();
this.num = t;
if(isnegative) this.num = this.num.multiply(-1);
return this;
},
isOne: function() {
return this.num.equals(1) && this.den.equals(1);
},
sign: function() {
return this.num.isNegative() ? -1 : 1;
},
abs: function() {
this.num = this.num.abs();
return this;
},
gcd: function(f) {
return Frac.quick(bigInt.gcd(f.num, this.num), bigInt.lcm(f.den, this.den));
},
toString: function() {
return !this.den.equals(1) ? this.num.toString()+'/'+this.den.toString() : this.num.toString();
},
valueOf: function() {
return this.num/this.den;
},
isNegative: function() {
return this.toDecimal() < 0;
}
};
/**
* All symbols e.g. x, y, z, etc or functions are wrapped in this class. All symbols have a multiplier and a group.
* All symbols except for "numbers (group N)" have a power.
* @class Primary data type for the Parser.
* @param {String} obj
* @returns {Symbol}
*/
function Symbol(obj) {
//this enables the class to be instantiated without the new operator
if(!(this instanceof Symbol)) { return new Symbol(obj); }
//define numeric symbols
if(!isNaN(obj) && obj !== 'Infinity') {
this.group = N;
this.value = CONST_HASH;
this.multiplier = new Frac(obj);
}
//define symbolic symbols
else {
this.group = S;
validateName(obj);
this.value = obj;
this.multiplier = new Frac(1);
this.imaginary = obj === Settings.IMAGINARY;
}
//As of 6.0.0 we switched to infinite precision so all objects have a power
//Although this is still redundant in constants, it simplifies the logic in
//other parts so we'll keep it
this.power = new Frac(1);
// Added to silence the strict warning.
return this;
}
Symbol.imaginary = function() {
var s = new Symbol(Settings.IMAGINARY);
s.isImaginary = true;
return s;
};
Symbol.shell = function(group, value) {
var symbol = new Symbol(value);
symbol.group = group;
symbol.symbols = {};
symbol.length = 0;
return symbol;
};
//sqrt(x) -> x^(1/2)
Symbol.unwrapSQRT = function(symbol, all) {
var p = symbol.power;
if(symbol.fname === SQRT && (symbol.isLinear() || all )) {
var t = symbol.args[0].clone();
t.power = t.power.multiply(new Frac(1/2));
t.multiplier = t.multiplier.multiply(symbol.multiplier);
symbol = t;
if(all)
symbol.power = p.multiply(new Frac(1/2));
}
return symbol;
};
Symbol.prototype = {
/**
* Checks to see if two functions are of equal value
*/
equals: function(symbol) {
if(!isSymbol(symbol))
symbol = new Symbol(symbol);
return this.value === symbol.value && this.power.equals(symbol.power) && this.multiplier.equals(symbol.multiplier);
},
// Greater than
gt: function(symbol) {
if(!isSymbol(symbol))
symbol = new Symbol(symbol);
return this.isConstant() && symbol.isConstant() && this.multiplier.greaterThan(symbol.multiplier);
},
// Greater than
gte: function(symbol) {
if(!isSymbol(symbol))
symbol = new Symbol(symbol);
return this.equals(symbol) ||
this.isConstant() && symbol.isConstant() && this.multiplier.greaterThan(symbol.multiplier);
},
// Less than
lt: function(symbol) {
if(!isSymbol(symbol))
symbol = new Symbol(symbol);
return this.isConstant() && symbol.isConstant() && this.multiplier.lessThan(symbol.multiplier);
},
// Less than
lte: function(symbol) {
if(!isSymbol(symbol))
symbol = new Symbol(symbol);
return this.equals(symbol) ||
this.isConstant() && symbol.isConstant() && this.multiplier.lessThan(symbol.multiplier);
},
/**
* Because nerdamer doesn't group symbols by polynomials but
* rather a custom grouping method, this has to be
* reinserted in order to make use of most algorithms. This function
* checks if the symbol meets the criteria of a polynomial.
* @returns {boolean}
*/
isPoly: function(multivariate) {
var g = this.group,
p = this.power;
//the power must be a integer so fail if it's not
if(!isInt(p) || p < 0) return false;
//constants and first orders
if(g === N || g === S) return true;
//PL groups. These only fail if a power is not an int
if(this.isComposite() || g === CB && multivariate) {
//fail if we're not checking for multivariate polynomials
if(!multivariate && variables(this) > 1) return false;
//loop though the symbols and check if they qualify
for(var x in this.symbols) {
//we've already the symbols if we're not checking for multivariates at this point
//so we check the sub-symbols
if(!this.symbols[x].isPoly(multivariate)) return false;
}
}
else return false;
//all tests must have passed so we must be dealing with a polynomial
return true;
},
//removes the requested variable from the symbol and returns the remainder
stripVar: function(x) {
var retval;
if((this.group === PL || this.group === S) && this.value === x)
retval = new Symbol(this.multiplier);
else if(this.group === CB && this.isLinear()) {
retval = new Symbol(1);
this.each(function(s) {
if(!s.contains(x, true))
retval = _.multiply(retval, s.clone());
});
retval.multiplier = retval.multiplier.multiply(this.multiplier);
}
else if(this.group === CP && !this.isLinear()) {
retval = new Symbol(this.multiplier);
}
else if(this.group === CP && this.isLinear()) {
retval = new Symbol(0);
this.each(function(s) {
if(!s.contains(x)) {
var t = s.clone();
t.multiplier = t.multiplier.multiply(this.multiplier);
retval = _.add(retval, t);
}
});
//BIG TODO!!! It doesn't make much sense
if(retval.equals(0))
retval = new Symbol(this.multiplier);
}
else if(this.group === EX && this.power.contains(x, true)) {
retval = new Symbol(this.multiplier);
}
else if(this.group === FN && this.contains(x)) {
retval = new Symbol(this.multiplier);
}
else retval = this.clone();
return retval;
},
//returns symbol in array form with x as base e.g. a*x^2+b*x+c = [c, b, a].
toArray: function(v, arr) {
arr = arr || {
arr: [],
add: function(x, idx) {
var e = this.arr[idx];
this.arr[idx] = e ? _.add(e, x) : x;
}
};
var g = this.group;
if(g === S && this.contains(v)) {
arr.add(new Symbol(this.multiplier), this.power);
}
else if(g === CB){
var a = this.stripVar(v),
x = _.divide(this.clone(), a.clone());
var p = x.isConstant() ? 0 : x.power;
arr.add(a, p);
}
else if(g === PL && this.value === v) {
this.each(function(x, p) {
arr.add(x.stripVar(v), p);
});
}
else if(g === CP) {
//the logic: they'll be broken into symbols so e.g. (x^2+x)+1 or (a*x^2+b*x+c)
//each case is handled above
this.each(function(x) {
x.toArray(v, arr);
});
}
else if(this.contains(v)){
throw new Error('Cannot convert to array! Exiting');
}
else {
arr.add(this.clone(), 0); //it's just a constant wrt to v
}
//fill the holes
arr = arr.arr; //keep only the array since we don't need the object anymore
for(var i=0; i<arr.length; i++)
if(!arr[i])
arr[i] = new Symbol(0);
return arr;
},
//checks to see if a symbol contans a function
hasFunc: function(v) {
var fn_group = this.group === FN || this.group === EX;
if( fn_group && !v || fn_group && this.contains(v) )
return true;
if(this.symbols) {
for(var x in this.symbols) {
if(this.symbols[x].hasFunc(v)) return true;
}
}
return false;
},
sub: function(a, b) {
a = !isSymbol(a) ? _.parse(a) : a.clone();
b = !isSymbol(b) ? _.parse(b) : b.clone();
if(a.group === N || a.group === P)
err('Cannot substitute a number. Must be a variable');
var same_pow = false,
a_is_unit_multiplier = a.multiplier.equals(1),
m = this.multiplier.clone(),
retval;
/*
* In order to make the substitution the bases have to first match take
* (x+1)^x -> (x+1)=y || x^2 -> x=y^6
* In both cases the first condition is that the bases match so we begin there
* Either both are PL or both are not PL but we cannot have PL and a non-PL group match
*/
if(this.value === a.value && (this.group !== PL && a.group !== PL || this.group === PL && a.group === PL)) {
//we cleared the first hurdle but a subsitution may not be possible just yet
if(a_is_unit_multiplier || a.multiplier.equals(this.multiplier)) {
if(a.isLinear()) {
retval = b;
}
else if(a.power.equals(this.power)) {
retval = b;
same_pow = true;
}
if(a.multiplier.equals(this.multiplier))
m = new Frac(1);
}
}
//the next thing is to handle CB
else if(this.group === CB || this.previousGroup === CB) {
retval = new Symbol(1);
this.each(function(x) {
retval = _.multiply(retval, x.sub(a, b));
});
}
else if(this.isComposite()) {
retval = new Symbol(0);
this.each(function(x) {
retval = _.add(retval, x.sub(a, b));
});
}
else if(this.group === EX) {
// the parsed value could be a function so parse and sub
retval = _.parse(this.value).sub(a, b);
}
else if(this.group === FN) {
var nargs = [];
for(var i=0; i<this.args.length; i++) {
var arg = this.args[i];
if(!isSymbol(arg))
arg = _.parse(arg);
nargs.push(arg.sub(a, b));
}
retval = _.symfunction(this.fname, nargs);
}
//if we did manage a substitution
if(retval) {
if(!same_pow) {
//substitute the power
var p = this.group === EX ? this.power.sub(a, b) : _.parse(this.power);
//now raise the symbol to that power
retval = _.pow(retval, p);
}
//transfer the multiplier
retval.multiplier = retval.multiplier.multiply(m);
//done
return retval;
}
//if all else fails
return this.clone();
},
isMonomial: function() {
if(this.group === S) return true;
if(this.group === CB) {
for(var x in this.symbols)
if(this.symbols[x].group !== S)
return false;
}
else return false;
return true;
},
isPi: function() {
return this.group === S && this.value === 'pi';
},
sign: function() {
return this.multiplier.sign();
},
isE: function() {
return this.value === 'e';
},
isSQRT: function() {
return this.fname === SQRT;
},
isConstant: function() {
return this.value === CONST_HASH;
},
isInteger: function() {
return this.isConstant() && this.multiplier.isInteger();
},
isLinear: function(wrt) {
if(wrt) {
if(this.isConstant())
return true;
if(this.group === S) {
if(this.value === wrt)return this.power.equals(1);
else return true;
}
if(this.isComposite() && this.power.equals(1)) {
for(var x in this.symbols) {
if(!this.symbols[x].isLinear(wrt))
return false;
}
return true;
}
if(this.group === CB && this.symbols[wrt])
return this.symbols[wrt].isLinear(wrt);
return false;
}
else return this.power.equals(1);
},
containsFunction: function(names) {
if(typeof names === 'string')
names = [names];
if(this.group === FN && names.indexOf(this.fname) !== -1)
return true;
if(this.symbols) {
for(var x in this.symbols) {
if(this.symbols[x].hasIntegral(names))
return true;
}
}
return false;
},
multiplyPower: function(p2) {
//leave out 1
if(this.group === N && this.multiplier.equals(1)) return this;
var p1 = this.power;
if(this.group !== EX && p2.group === N) {
var p = p2.multiplier;
if(this.group === N && !p.isInteger()) {
this.convert(P);
}
this.power = p1.equals(1) ? p.clone() : p1.multiply(p);
if(this.group === P && isInt(this.power)) {
//bring it back to an N
this.value = Math.pow(this.value, this.power);
this.toLinear();
this.convert(N);
}
}
else {
if(this.group !== EX) {
p1 = new Symbol(p1);
this.convert(EX);
}
this.power = _.multiply(p1, p2);
}
return this;
},
setPower: function(p, retainSign) {
//leave out 1
if(this.group === N && this.multiplier.equals(1)) return this;
if(this.group === EX && !isSymbol(p)) {
this.group = this.previousGroup;
delete this.previousGroup;
this.power = p;
}
else {
var isIntP = false,
isSymbolic = false;
if(isSymbol(p)) {
if(p.group === N) {
//p should be the multiplier instead
p = p.multiplier;
}
else {
isSymbolic = true;
}
}
var group = isSymbolic ? EX : !isIntP ? P : null;
this.power = p;
if(this.group === N && group) this.convert(group, retainSign);
}
return this;
},
/**
* Checks to see if symbol is located in the denominator
* @returns {boolean}
*/
isInverse: function() {
if(this.group === EX) return (this.power.multiplier.lessThan(0));
return this.power < 0;
},
/**
* Make a duplicate of a symbol by copying a predefined list of items
* to a new symbol
* @returns {Symbol}
*/
clone: function(c) {
var clone = c || new Symbol(0),
//list of properties excluding power as this may be a symbol and would also need to be a clone.
properties = [
'value', 'group', 'length', 'previousGroup', 'imaginary', 'fname', 'args'],
l = properties.length, i;
if(this.symbols) {
clone.symbols = {};
for(var x in this.symbols) {
clone.symbols[x] = this.symbols[x].clone();
}
}
for(i=0; i<l; i++) {
if(this[properties[i]] !== undefined) {
clone[properties[i]] = this[properties[i]];
}
}
clone.power = this.power.clone();
clone.multiplier = this.multiplier.clone();
return clone;
},
toUnitMultiplier: function(keepSign) {
this.multiplier.num = new bigInt(this.multiplier.num.isNegative() && keepSign ? -1 : 1);
this.multiplier.den = new bigInt(1);
return this;
},
toLinear: function() {
this.setPower(new Frac(1));
return this;
},
each: function(fn, deep) {
if(!this.symbols) {
fn.call(this, this, this.value);
}
else {
for(var x in this.symbols) {
var sym = this.symbols[x];
if(sym.group === PL && deep) {
for(var y in sym.symbols) {
fn.call(x, sym.symbols[y], y);
}
}
else
fn.call(this, sym, x);
}
}
},
/**
* A numeric value to be returned for Javascript. It will try to
* return a number as far a possible but in case of a pure symbolic
* symbol it will just return its text representation
* @returns {String|Number}
*/
valueOf: function() {
if(this.group === N)
return this.multiplier.valueOf();
else if(this.power === 0){ return 1; }
else if(this.multiplier === 0) { return 0; }
else { return text(this, 'decimals'); }
},
/**
* Checks to see if a symbols has a particular variable within it.
* Pass in true as second argument to include the power of exponentials
* which aren't check by default.
* @example var s = _.parse('x+y+z'); s.contains('y');
* //returns true
* @returns {boolean}
*/
contains: function(variable, all) {
var g = this.group;
if(this.value === variable)
return true;
if(this.symbols) {
for(var x in this.symbols) {
if(this.symbols[x].contains(variable, all)) return true;
}
}
if(g === FN || this.previousGroup === FN) {
for(var i=0; i<this.args.length; i++) {
if(this.args[i].contains(variable, all)) return true;
}
}
if(g === EX) {
//exit only if it does
if(all && this.power.contains(variable, all)) { return true; }
if(this.value === variable)
return true;
}
return this.value === variable;
},
/**
* Negates a symbols
* @returns {boolean}
*/
negate: function() {
this.multiplier.negate();
if(this.group === CP || this.group === PL) this.distributeMultiplier();
return this;
},
/**
* Inverts a symbol
* @returns {boolean}
*/
invert: function(power_only) {
//invert the multiplier
if(!power_only) this.multiplier = this.multiplier.invert();
//invert the rest
if(isSymbol(this.power)) {
this.power.negate();
}
else {
if(this.power && this.group !== N) this.power.negate();
}
return this;
},
/**
* Symbols of group CP or PL may have the multiplier being carried by
* the top level symbol at any given time e.g. 2*(x+y+z). This is
* convenient in many cases, however in some cases the multiplier needs
* to be carried individually e.g. 2*x+2*y+2*z.
* This method distributes the multiplier over the entire symbol
* @returns {Symbol}
*/
distributeMultiplier: function(all) {
var is_one = all ? this.power.absEquals(1) : this.power.equals(1);
if(this.symbols && is_one && this.group !== CB && !this.multiplier.equals(1)) {
for(var x in this.symbols) {
var s = this.symbols[x];
s.multiplier = s.multiplier.multiply(this.multiplier);
s.distributeMultiplier();
}
this.toUnitMultiplier();
}
return this;
},
/**
* This method expands the exponent over the entire symbol just like
* distributeMultiplier
* @returns {Symbol}
*/
distributeExponent: function() {
if(this.power !== 1) {
var p = this.power;
for(var x in this.symbols) {
var s = this.symbols[x];
if(s.group === EX) {
s.power = _.multiply(s.power, new Symbol(p));
}
else {
this.symbols[x].power = this.symbols[x].power.multiply(p);
}
}
this.toLinear();
}
return this;
},
/**
* This method will attempt to up-convert or down-convert one symbol
* from one group to another. Not all symbols are convertible from one
* group to another however. In that case the symbol will remain
* unchanged.
*/
convert: function(group, imaginary) {
if(group > FN) {
//make a clone of this symbol;
var cp = this.clone();
//attach a symbols object and upgrade the group
this.symbols = {};
if(group === CB) {
//symbol of group CB hold symbols bound together through multiplication
//because of commutativity this multiplier can technically be anywhere within the group
//to keep track of it however it's easier to always have the top level carry it
cp.toUnitMultiplier();
}
else {
//reset the symbol
this.toUnitMultiplier();
}
if(this.group === FN) {
cp.args = this.args;
delete this.args;
delete this.fname;
}
//the symbol may originate from the symbol i but this property no longer holds true
//after copying
if(this.isImgSymbol) delete this.isImgSymbol;
this.toLinear();
//attach a clone of this symbol to the symbols object using its proper key
this.symbols[cp.keyForGroup(group)] = cp;
this.group = group;
//objects by default don't have a length property. However, in order to keep track of the number
//of sub-symbols we have to impliment our own.
this.length = 1;
}
else if(group === EX) {
//1^x is just one so check and make sure
if(!(this.group === N && this.multiplier.equals(1))) {
if(this.group !== EX) this.previousGroup = this.group;
if(this.group === N) {
this.value = this.multiplier.num.toString();
this.toUnitMultiplier();
}
//update the hash to reflect the accurate hash
else this.value = text(this, 'hash');
this.group = EX;
}
}
else if(group === N) {
var m = this.multiplier.toDecimal();
if(this.symbols) this.symbols = undefined;
new Symbol(this.group === P ? m*Math.pow(this.value, this.power) : m).clone(this);
}
else if(group === P && this.group === N) {
this.value = imaginary ? this.multiplier.num.toString() : Math.abs(this.multiplier.num.toString());
this.toUnitMultiplier(!imaginary);
this.group = P;
}
return this;
},
/**
* This method is one of the principal methods to make it all possible.
* It performs cleanup and prep operations whenever a symbols is
* inserted. If the symbols results in a 1 in a CB (multiplication)
* group for instance it will remove the redundant symbol. Similarly
* in a symbol of group PL or CP (symbols glued by multiplication) it
* will remove any dangling zeroes from the symbol. It will also
* up-convert or down-convert a symbol if it detects that it's
* incorrectly grouped. It should be noted that this method is not
* called directly but rather by the 'attach' method for addition groups
* and the 'combine' method for multipiclation groups.
*/
insert: function(symbol, action) {
//this check can be removed but saves a lot of aggravation when trying to hunt down
//a bug. If left, you will instantly know that the error can only be between 2 symbols.
if(!isSymbol(symbol)) err('Object '+symbol+' is not of type Symbol!');
if(this.symbols) {
var group = this.group;
if(group > FN) {
var key = symbol.keyForGroup(group);
var existing = key in this.symbols ? this.symbols[key] : false; //check if there's already a symbol there
if(action === 'add') {
var hash = key;
if(existing) {
//add them together using the parser
this.symbols[hash] = _.add(existing, symbol);
//if the addition resulted in a zero multiplier remove it
if(this.symbols[hash].multiplier.equals(0)) {
delete this.symbols[hash];
this.length--;
if(this.length === 0) {
this.convert(N);
this.multiplier = new Frac(0);
}
}
}
else {
this.symbols[key] = symbol;
this.length++;
}
}
else {
//check if this is of group P and unwrap before inserting
if(symbol.group === P && isInt(symbol.power)) {
symbol.convert(N);
}
//transfer the multiplier to the upper symbol but only if the symbol numeric
if(symbol.group !== EX) {
this.multiplier = this.multiplier.multiply(symbol.multiplier);
symbol.toUnitMultiplier();
}
else {
symbol.parens = symbol.multiplier.lessThan(0);
this.multiplier = this.multiplier.multiply(symbol.multiplier.clone().abs());
symbol.toUnitMultiplier(true);
}
if(existing) {
//remove because the symbol may have changed
symbol = _.multiply(remove(this.symbols, key), symbol);
if(symbol.isConstant()) {
this.multiplier = this.multiplier.multiply(symbol.multiplier);
symbol = new Symbol(1); //the dirty work gets done down the line when it detects 1
}
this.length--;
//clean up
}
//don't insert the symbol if it's 1
if(!symbol.isOne(true)) {
this.symbols[key] = symbol;
this.length++;
}
else if(symbol.multiplier.lessThan(0)) {
this.negate(); //put back the sign
}
}
//clean up
if(this.length === 0) this.convert(N);
//update the hash
if(this.group === CP || this.group === CB) {
this.updateHash();
}
}
}
return this;
},
//the insert method for addition
attach: function(symbol) {
if(isArray(symbol)) {
for(var i=0; i<symbol.length; i++) this.insert(symbol[i], 'add');
return this;
}
return this.insert(symbol, 'add');
},
//the insert method for multiplication
combine: function(symbol) {
if(isArray(symbol)) {
for(var i=0; i<symbol.length; i++) this.insert(symbol[i], 'multiply');
return this;
}
return this.insert(symbol, 'multiply');
},
/**
* This method should be called after any major "surgery" on a symbol.
* It updates the hash of the symbol for example if the fname of a
* function has changed it will update the hash of the symbol.
*/
updateHash: function() {
if(this.group === N) return;
if(this.group === FN) {
var contents = '',
args = this.args,
is_parens = this.fname === PARENTHESIS;
for(var i=0; i<args.length; i++) contents += (i===0 ? '' : ',')+text(args[i]);
var fn_name = is_parens ? '' : this.fname;
this.value = fn_name+(is_parens ? contents : inBrackets(contents));
}
else if(!(this.group === S || this.group === PL)) {
this.value = text(this, 'hash');
}
},
/**
* this function defines how every group in stored within a group of
* higher order think of it as the switchboard for the library. It
* defines the hashes for symbols.
*/
keyForGroup: function(group) {
var g = this.group;
var key;
if(g === N) {
key = this.value;
}
else if(g === S || g === P) {
if(group === PL) key = this.power.toDecimal();
else key = this.value;
}
else if(g === FN) {
if(group === PL) key = this.power.toDecimal();
else key = text(this, 'hash');
}
else if(g === PL) {
//if the order is reversed then we'll assume multiplication
//TODO: possible future dilemma
if(group === CB) key = text(this, 'hash');
else if(group === CP) {
if(this.power.equals(1)) key = this.value;
else key = inBrackets(text(this, 'hash'))+Settings.POWER_OPERATOR+this.power.toDecimal();
}
else if(group === PL) key = this.power.toString();
else key = this.value;
return key;
}
else if(g === CP) {
if(group === CP) key = text(this, 'hash');
if(group === PL) key = this.power.toDecimal();
else key = this.value;
}
else if(g === CB) {
if(group === PL) key = this.power.toDecimal();
else key = text(this, 'hash');
}
else if(g === EX) {
if(group === PL) key = text(this.power);
else key = text(this, 'hash');
}
return key;
},
/**
* Symbols are typically stored in an object which works fine for most
* cases but presents a problem when the order of the symbols makes
* a difference. This function simply collects all the symbols and
* returns them as an array. If a function is supplied then that
* function is called on every symbol contained within the object.
* @returns {Array}
*/
collectSymbols: function(fn, opt, sort_fn, expand_symbol) {
var collected = [];
if(!this.symbols) collected.push(this);
else {
for(var x in this.symbols) {
var symbol = this.symbols[x];
if(expand_symbol && (symbol.group === PL || symbol.group === CP)) {
collected = collected.concat(symbol.collectSymbols());
}
else collected.push( fn ? fn(symbol, opt) : symbol );
}
}
if(sort_fn === null) sort_fn = undefined; //WTF Firefox? Seriously?
return collected.sort(sort_fn);//sort hopefully gives us some sort of consistency
},
/**
* Returns the latex representation of the symbol
* @returns {String}
*/
latex: function(option) {
return LaTeX.latex(this, option);
},
/**
* Returns the text representation of a symbol
* @returns {String}
*/
text: function(option) {
return text(this, option);
},
/**
* Checks if the function evaluates to 1. e.g. x^0 or 1 :)
*/
isOne: function(abs) {
var f = abs ? 'absEquals' : 'equals';
if(this.group === N) return this.multiplier[f](1);
else return this.power.equals(0);
},
isComposite: function() {
var g = this.group,
pg = this.previousGroup;
return g === CP || g === PL || pg === PL || pg === CP;
},
isCombination: function() {
var g = this.group,
pg = this.previousGroup;
return g === CB || pg === CB;
},
lessThan: function(n) {
return this.multiplier.lessThan(n);
},
greaterThan: function(n) {
return this.multiplier.greaterThan(n);
},
/**
* Get's the denominator of the symbol if the symbol is of class CB (multiplication)
* with other classes the symbol is either the denominator or not.
* Take x^-1+x^-2. If the symbol was to be mixed such as x+x^-2 then the symbol doesn't have have an exclusive
* denominator and has to be found by looking at the actual symbols themselves.
*/
getDenom: function() {
if(this.power.lessThan(0)) return this.clone();
if(this.group === CB)
var retval = new Symbol(1);
for(var x in this.symbols)
if(this.symbols[x].power < 0)
retval = _.multiply(retval, this.symbols[x].clone());
return retval;
return new Symbol(this.multiplier.den);
},
getNum: function() {
if(this.power.lessThan(0)) return new Symbol(this.multiplier.num);
if(this.group === CB) {
var newSymbol = new Symbol(1);
for(var x in this.symbols)
if(this.symbols[x].power > 0)
newSymbol = _.multiply(newSymbol, this.symbols[x].clone());
return newSymbol;
}
return this.clone();
},
toString: function() {
return this.text();
}
};
function primeFactors(num) {
if(isPrime(num)) return [num];
var l = num, i=1, factors = [],
epsilon = 2.2204460492503130808472633361816E-16;
while(i<l) {
var quotient = num/i;
var whole = Math.floor(quotient);
var remainder = quotient-whole;
if(remainder <= epsilon && i>1) {
if(PRIMES.indexOf(i) !== -1) factors.push(i);
l = whole;
}
i++;
}
return factors.sort(function(a, b){return a-b;});
}
/**
* This class defines the operators in nerdamer. The thinking is that with using these parameters
* we should be able to define more operators such as the modulus operator or a boolean operator.
* Although this initially works at the moment, it fails in some instances due to minor flaws in design which
* will be addressed in future releases.
* @param {char} val - The symbol of the operator
* @param {String} fn - The function it maps to
* @param {Integer} precedence - The precedence of the operator
* @param {boolean} left_assoc - Is the operator left or right associative
* @param {boolean} is_prefix - Is the operator a prefix operator
* @param {boolean} is_postfix - Is the operator a postfix operator
* @param {boolean} operation - The prefix or postfix operation the operator preforms if its either
* @returns {Operator}
*/
function Operator(val, fn, precedence, left_assoc, is_prefix, is_postfix, operation) {
this.val = val;
this.fn = fn;
this.precedence = precedence;
this.left_assoc = left_assoc;
this.is_prefix = is_prefix;
this.is_postfix = is_postfix || false;
this.operation = operation;
this.is_operator = true;
}
Operator.prototype.toString = function() {
return this.val;
};
function Bracket(val, bracket_id, is_open, fn, typ) {
this.val = val;
this.bracket_id = bracket_id;
this.open = !!is_open;
this.fn = fn;
this.type = typ;
}
Bracket.prototype.toString = function() {
return this.val;
};
function Prefix(operator) {
this.operation = operator.operation;
this.val = operator.val;
this.is_prefix_operator = true;
}
Prefix.prototype.toString = function() {
return '`'+this.val;
};
//custom errors
//thrown if trying to divide by zero
function DivisionByZero(msg){
this.message = msg || "";
}
DivisionByZero.prototype = new Error();
//thrown in parser
function ParseError(msg){
this.message = msg || "";
}
ParseError.prototype = new Error();
//Uses modified Shunting-yard algorithm. http://en.wikipedia.org/wiki/Shunting-yard_algorithm
function Parser(){
//we want the underscore to point to this parser not the global nerdamer parser.
var _ = this,
bin = {},
constants = this.constants = {
PI: Math.PI,
E: Math.E
},
subs = {
e: Math.E,
pi: Math.PI
};
//list all the supported operators
var operators = this.operators = {
'^' : new Operator('^', 'pow', 6, false, false),
'**' : new Operator('**', 'pow', 6, false, false),
'!!' : new Operator('!!', 'dfactorial', 5, false, false, true, function(e) {
return _.symfunction(DOUBLEFACTORIAL, [e]); //wrap it in a factorial function
}),
'!' : new Operator('!', 'factorial', 5, false, false, true, function(e) {
return _.symfunction(FACTORIAL, [e]); //wrap it in a factorial function
}),
//begin crazy fix ... :( TODO!!! revisit
'!+' : new Operator('!+', 'factadd', 3, true, true, false),
'!!+' : new Operator('!!+', 'dfactadd', 3, true, true, false),
'!-' : new Operator('!-', 'factsub', 3, true, true, false),
'!!-' : new Operator('!!-', 'dfactsub', 3, true, true, false),
//done with crazy fix
'*' : new Operator('*', 'multiply', 4, true, false),
'/' : new Operator('/', 'divide', 4, true, false),
'+' : new Operator('+', 'add', 3, true, true, false, function(e) {
return e;
}),
'-' : new Operator('-', 'subtract', 3, true, true, false, function(e) {
return e.negate();
}),
'=' : new Operator('=', 'equals', 2, false, false),
'==' : new Operator('==', 'eq', 1, false, false),
'<' : new Operator('<', 'lt', 1, false, false),
'<=' : new Operator('<=', 'lte', 1, false, false),
'>' : new Operator('>', 'gt', 1, false, false),
'>=' : new Operator('>=', 'gte', 1, false, false),
',' : new Operator(',', 'comma', 0, true, false)
},
//list of supported brackets
brackets = {
'(': new Bracket('(', 0, true, null, 'round'),
')': new Bracket(')', 0, false, null, 'round'),
'[': new Bracket('[', 1, true, function() {
var f = new Symbol('vector');
f.is_function = true;
return f;
}, 'square'),
']': new Bracket(']', 1, false, null, 'square')
},
// Supported functions.
// Format: function_name: [mapped_function, number_of_parameters]
functions = this.functions = {
'cos' : [ cos, 1],
'sin' : [ sin, 1],
'tan' : [ tan, 1],
'sec' : [ sec, 1],
'csc' : [ csc, 1],
'cot' : [ cot, 1],
'acos' : [ , 1],
'asin' : [ , 1],
'atan' : [ , 1],
'sinh' : [ , 1],
'cosh' : [ , 1],
'tanh' : [ , 1],
'asinh' : [ , 1],
'acosh' : [ , 1],
'atanh' : [ , 1],
'log10' : [ , 1],
'exp' : [ , 1],
'min' : [ ,-1],
'max' : [ ,-1],
'erf' : [ , 1],
'floor' : [ , 1],
'ceil' : [ , 1],
'Si' : [ , 1],
'step' : [ , 1],
'rect' : [ , 1],
'sinc' : [ , 1],
'tri' : [ , 1],
'sign' : [ , 1],
'Ci' : [ , 1],
'Ei' : [ , 1],
'Shi' : [ , 1],
'Chi' : [ , 1],
'fib' : [ , 1],
'fact' : [factorial, 1],
'factorial' : [factorial, 1],
'dfactorial' : [ , 1],
'gamma_incomplete' : [ , [1, 2]],
'round' : [ , 1],
'mod' : [ mod, 2],
'pfactor' : [ pfactor , 1],
'vector' : [ vector, -1],
'matrix' : [ matrix, -1],
'parens' : [ parens, -1],
'sqrt' : [ sqrt, 1],
'nthroot' : [ nthroot, 2],
'log' : [ log , 1],
'expand' : [ expand , 1],
'abs' : [ abs , 1],
'invert' : [ invert, 1],
'transpose' : [ transpose, 1],
'dot' : [ dot, 2],
'cross' : [ cross, 2],
'vecget' : [ vecget, 2],
'vecset' : [ vecset, 3],
'matget' : [ matget, 3],
'matset' : [ matset, 4],
'imatrix' : [ imatrix, 1],
'IF' : [ IF, 3]
};
this.error = err;
//this function is used to comb through the function modules and find a function given its name
var findFunction = function(fname) {
var fmodules = Settings.FUNCTION_MODULES,
l = fmodules.length;
for(var i=0; i<l; i++) {
var fmodule = fmodules[i];
if(fname in fmodule)
return fmodule[fname];
}
err('The function '+fname+' is undefined!');
};
var allNumbers = function(args) {
for(var i=0; i<args.length; i++)
if(args[i].group !== N)
return false;
return true;
};
/**
* This method gives the ability to override operators with new methods.
* @param {String} which
* @param {Function} with_what
*/
this.override = function(which, with_what) {
if(!bin[which]) bin[which] = [];
bin[which].push(this[which]);
this[which] = with_what;
};
/**
* Restores a previously overridden operator
* @param {String} what
*/
this.restore = function(what) {
if(this[what]) this[what] = bin[what].pop();
};
/**
* This method is supposed to behave similarly to the override method but it does not override
* the existing function rather it only extends it
* @param {String} what
* @param {Function} with_what
* @param {boolean} force_call
*/
this.extend = function(what, with_what, force_call) {
var _ = this,
extended = this[what];
if(typeof extended === 'function' && typeof with_what === 'function') {
var f = this[what];
this[what] = function(a, b) {
if(isSymbol(a) && isSymbol(b) && !force_call) return f.call(_, a, b);
else return with_what.call(_, a, b, f);
};
}
};
/**
* Generates library's representation of a function. It's a fancy way of saying a symbol with
* a few extras. The most important thing is that that it gives a fname and
* an args property to the symbols in addition to changing its group to FN
* @param {String} fn_name
* @param {Array} params
* @returns {Symbol}
*/
this.symfunction = function(fn_name, params) {
//call the proper function and return the result;
var f = new Symbol(fn_name);
f.group = FN;
if(typeof params === 'object')
params = [].slice.call(params);//ensure an array
f.args = params;
f.fname = fn_name === PARENTHESIS ? '' : fn_name;
f.updateHash();
return f;
};
/**
* An internal function call for the Parser. This will either trigger a real
* function call if it can do so or just return a symbolic representation of the
* function using symfunction.
* @param {String} fn_name
* @param {Array} args
* @returns {Symbol}
*/
this.callfunction = function(fn_name, args) {
var fn_settings = functions[fn_name];
if(!fn_settings)
err('Nerdamer currently does not support the function '+fn_name);
var num_allowed_args = fn_settings[1] || allowed_args, //get the number of allowed arguments
fn = fn_settings[0], //get the mapped function
retval;
//We want to be able to call apply on the arguments or create a symfunction. Both require
//an array so make sure to wrap the argument in an array.
if(!(args instanceof Array))
args = args !== undefined ? [args] : [];
if(num_allowed_args !== -1) {
var is_array = isArray(num_allowed_args),
min_args = is_array ? num_allowed_args[0] : num_allowed_args,
max_args = is_array ? num_allowed_args[1] : num_allowed_args,
num_args = args.length;
var error_msg = fn_name+' requires a {0} of {1} arguments. {2} provided!';
if(num_args < min_args) err(format(error_msg, 'minimum', min_args, num_args));
if(num_args > max_args) err(format(error_msg, 'maximum', max_args, num_args));
}
/*
* The following are very important to the how nerdamer constructs functions!
* Assumption 1 - if fn is undefined then handling of the function is purely numeric. This
* enables us to reuse Math, Math2, ..., any function from Settings.FUNCTIONS_MODULES entry
* Assumption 2 - if fn is defined then that function takes care of EVERYTHING including symbolics
* Assumption 3 - if the user calls symbolics on a function that returns a numeric value then
* they are expecting a symbolic output.
*/
if(!fn) {
//Remember assumption 1. No function defined so it MUST be numeric in nature
fn = findFunction(fn_name);
if(Settings.PARSE2NUMBER && allNumbers(args))
retval = bigConvert(fn.apply(fn, args));
else
retval = _.symfunction(fn_name, args);
}
else {
//Remember assumption 2. The function is defined so it MUST handle all aspects including numeric values
retval = fn.apply(fn_settings[2], args);
}
return retval;
};
/**
* Build a regex based on the operators currently loaded. These operators are to be ignored when
* substituting spaces for multiplication
*/
this.operator_filter_regex = (function() {
//we only want the operators which are singular since those are the ones
//that nerdamer uses anyway
var ostr = '^\\'+Object.keys(operators).filter(function(x) {
if(x.length === 1)
return x;
}).join('\\');
//create a regex which captures all spaces between characters except those
//have an operator on one end
return new RegExp('(['+ostr+'])\\s+(['+ostr+'])');
})();
/*
* This method parses the tree
* @param {String[]} rpn
* @returns {Symbol}
*/
this.parseTree = function(rpn) {
var q = []; // The container for parsed values
var l = rpn.length;
// begin parsing
for(var i=0; i<l; i++) {
var e = rpn[i];
if(e.is_prefix_operator || e.is_postfix) {
q.push(e.operation(q.pop()));
continue;
}
if(e.is_operator) {
var b = q.pop(),
a = q.pop();
if(isArray(b)) //misread function
_.error('Unrecognized function "'+a.value+'"');
q.push(this[e.fn](a, b));
}
else if(e.value in functions) {
q.push(_.callfunction(e.value, q.pop()));
}
else {
// Blank denotes a beginning of a scope with a prefix operator so all we have to do is
// convert it to a zero
if(e === '') {
q.push(new Symbol(0));
}
else {
var unsubbed = e;
// make substitutions
//constants take higher priority
if(e in constants)
e = new Symbol(constants[e]);
//next subs
else if(e in subs)
e = subs[e].clone();
else if(e in VARS) {
e = VARS[e].clone();
}
e.unsubbed = unsubbed;
q.push(e);
}
}
}
return q[0] || new Symbol(0);
};
/**
* This is the method that triggers the parsing of the string. It generates a parse tree but processes
* it right away. The operator functions are called when their respective operators are reached. For instance
* + with cause this.add to be called with the left and right hand values. It works by walking along each
* character of the string and placing the operators on the stack and values on the output. When an operator
* having a lower order than the last is reached then the stack is processed from the last operator on the
* stack.
* @param {String} expression_string
* @param {Object} substitutions
* @returns {Symbol}
*/
this.parse = function(expression_string, substitutions, tree) {
//prepare the substitutions
if(substitutions) {
for(var x in substitutions)
substitutions[x] = _.parse(substitutions[x]);
subs = substitutions;
}
else
subs = {};
//link e and pi
if(Settings.PARSE2NUMBER) {
subs.e = new Symbol(Math.E);
subs.pi = new Symbol(Math.PI);
}
/*
* Since variables cannot start with a number, the assumption is made that when this occurs the
* user intents for this to be a coefficient. The multiplication symbol in then added. The same goes for
* a side-by-side close and open parenthesis
*/
var e = String(expression_string), match;
//add support for spaces between variables
while(true) {
match = this.operator_filter_regex.exec(e);
if(!match)
break;
e = e.replace(match[0], match[1]+'*'+match[2]);
}
e = e.split(' ').join('')//strip empty spaces
//replace scientific numbers
.replace(/\d+\.*\d*e\+?\-?\d+/gi, function(x) {
return scientificToDecimal(x);
})
//allow omission of multiplication after coefficients
.replace(/([\+\-\/\*]*[0-9]+)([a-z_]+[\+\-\/\*]*)/gi, function() {
var str = arguments[4],
group1 = arguments[1],
group2 = arguments[2],
start = arguments[3],
first = str.charAt(start),
before = '',
d = '*';
if(!first.match(/[\+\-\/\*]/)) before = str.charAt(start-1);
if(before.match(/[a-z]/i)) d = '';
return group1+d+group2;
})
.replace(/([a-z0-9_]+)(\()|(\))([a-z0-9]+)/gi, function(match, a, b, c, d) {
var g1 = a || c,
g2 = b || d;
if(g1 in functions) //create a passthrough for functions
return g1+g2;
return g1+'*'+g2;
})
//allow omission of multiplication sign between brackets
.replace( /\)\(/g, ')*(' ) || '0';
var l = e.length, //the length of the string
output = [], //the output array. This is what's returned
stack = [], //the operator stack
last_pos = 0, //the location of last operator encountered
open_brackets = [0, 0], //a counter for the open brackets
prefix_cache = [],
new_scope = true; //signal if we're in a new scope or not
// This method gets and inserts the token on output as the name implies
var get_and_insert_token = function(to_pos) {
if(to_pos !== last_pos) {
token = new Symbol(e.substring(last_pos, to_pos));
output.push(token);
//once we find out first token we are no longer in a new scope so flip
//the flag
new_scope = false;
}
};
var verify_prefix_operator = function(operator) {
if(!operator.is_prefix)
err(operator+' is not a valid prefix operator');
};
var resolve_prefix = function(prefix1, prefix2) {
if(!prefix2)
return prefix1;
if(prefix1.val === prefix2.val)
return new Prefix(operators['+']);
return new Prefix(operators['-']);
};
var insert_prefix = function(prefix) {
var sl = stack.length;
if(sl && stack[sl-1].is_prefix_operator)
stack.push(resolve_prefix(prefix, stack.pop()));
stack.push(prefix);
};
var collapse_prefix_cache = function(to_output) {
if(prefix_cache.length) {
var prefix = prefix_cache.pop();
while(prefix_cache.length)
prefix = resolve_prefix(prefix, prefix_cache.pop());
if(to_output)
output.push(prefix);
else
stack.push(prefix);
}
};
/*
* We define the operator as anything that performs any form of operation. A bracket as any object that defines
* a scope and a token as anything in between two operators. This enables us to have variables of more than one letter.
* This function is a modified version of the Shunting-Yard algorithm to enable variable names, and compound operators.
* operators are defined in the operator object. We walk the string and check every character. If an operator is encountered
* then we mark it's location. We find the next operator and get the token between.
*/
var token, operator, start = 0, i=0;
// start the generation of the tree
for(var i=start; i<l; i++) {
//the character
var ch = e.charAt(i);
if(ch in operators) {
// We previously defined the token to be the anything between two operators and since we an operator
//we can grab the token
get_and_insert_token(i);
//mark the current position
var c = i;
/*
* In order to support compound operators we assume that the following might be operator as well. We keep walking the string
* until we encounter a character which is no longer an operator. We define that entire sub-string an operator
*/
while(e.charAt(i+1) in operators)
i++;
var end_operator = i+1;
//the probable operator will be the difference between c and i;
var pr_operator = e.substring(c, end_operator);
/*
* We now have to see if this operator is actually an operator or a combination of an operator and prefix operators
* e.g. 3*-+-8 or x^-3. To determine this we knock off an operator one at a time until we find the matching operator.
* For instance if we have an operator -= and we get -=-- we knock of a minus from the back until we reach -= which will
* register as a defined operator since we defined it as such
*/
while(!(pr_operator in operators)) {
var l2 = pr_operator.length,
end = l2-1,
prefix = operators[pr_operator.charAt(end)];
pr_operator = pr_operator.substring(0, end);
//make sure it's not a postfix operator that we're dealing with
try {
//verify that it's not a prefix operator
verify_prefix_operator(prefix);
//add the prefix to the stack
prefix_cache.push(new Prefix(prefix));
}
catch(e) {
//check if we're dealing with postfix operators.
//Rule: compound postfix operators must be a composition of postfix operators
var prl = pr_operator.length, o;
for(var j=0; j<prl; j++) {
o = operators[pr_operator.charAt(j)];
if(!o|| o && !o.is_postfix)
err(e.message);
}
//at this point we know that we have only postfix operators but they are parsed left to right
var rem = '';
do {
if(pr_operator === '')
break; //we're done since the entire operator has been consumed
if(pr_operator in operators) {
output.push(operators[pr_operator]);
pr_operator = rem;
rem = '';
}
else {
var end = pr_operator.length-1;
rem += pr_operator.charAt(end);
pr_operator = pr_operator.substring(0, end);
}
}
while(true)
//the actual operator is now the one we assumed to be a prefix earlier. I need to really
//pick better variable names :-/
pr_operator = prefix.val;
break;
}
}
// we now have the operator
operator = operators[pr_operator];
// we mark where we find the last operator so we know where the next token begins
last_pos = end_operator;
while(true) {
var sl = stack.length,
los = stack[sl-1];
//skip prefix
while(los !== undefined && los.is_prefix_operator) {
los = stack[--sl-1];
}
if(sl === 0 || !(operator.left_assoc && operator.precedence <= los.precedence
|| !operator.left_assoc && operator.precedence < los.precedence))
break; //nothing to do
output.push(stack.pop());
}
// If we're in a new scope then we're dealing with a prefix operator
if(new_scope) {
/*
* There is literally no way to differentiate between a malformed expression and a properly formed one if there is no gap left
* at the beginning of the scope. This is best illustrated. Take the expression 3+7- in RPN it becomes 3,7,+,-
* Take the expression -3+7 in RPN this become 3,7,+,- as well. The difference is that we tag the minus as
* a prefix in the properly formed expression. Problem solved! But wait. Imagine we have no gaps at the beginning
* of the scope let's say -(3+7). With no gaps this again becomes 3,7,+,- with no way to differentiate
* between -3+7 and -(3+7) unless the second one is written as 3,7,+, ,- where the gap denotes the end of the scope
*/
verify_prefix_operator(operator);
var prefix = new Prefix(operator);
//collapse the prefix cache
while(prefix_cache.length)
prefix = resolve_prefix(prefix, prefix_cache.pop());
insert_prefix(prefix);
}
else {
//if there's already a prefix on the stack then bring it down
var sl = stack.length;
if(sl && stack[sl-1].is_prefix_operator && operator.left_assoc)
//it's safe to move the prefix to output since it's at the beginning of a scope
output.push(stack.pop());
stack.push(operator);
//resolve the prefixes
collapse_prefix_cache();
}
}
else if(ch in brackets) {
var bracket = brackets[ch];
if(bracket.open) {
//mark a bracket as being opened
open_brackets[bracket.bracket_id]++;
//check if we're dealing with a function
if(last_pos !== i) {
var f = new Symbol(e.substring(last_pos, i));
// assume it's a function. Since a string is just an object, why not use it
f.is_function = true;
stack.push(f);
}
if(bracket.fn)
stack.push(bracket.fn());
// We're in a new scope so signal so
new_scope = true;
stack.push(bracket);
//get all the prefixes at the beginning of the scope
last_pos = i+1; //move past the bracket
}
else {
//close the open bracket
open_brackets[bracket.bracket_id]--;
// We proceed to pop the entire stack to output this this signals the end of a scope. The first thing is to get the
// the prefixes and then the token at the end of this scope.
// get the token
get_and_insert_token(i);
// And then keep popping the stack until we reach a bracket
while(true) {
var entry = stack.pop();
if(entry === undefined)
err("Unmatched open bracket for bracket '"+bracket+"'!");
//we found the matching bracket so our search is over
if(entry.bracket_id === bracket.bracket_id)
break; // We discard the closing bracket
else
output.push(entry);
}
var sl = stack.length;
//move the function to output
if(sl && stack[sl-1].is_function)
output.push(stack.pop());
last_pos = i+1; //move past the bracket
}
}
}
//get the last token at the end of the string
get_and_insert_token(l);
//collapse the stack to output
while(stack.length)
output.push(stack.pop());
//check parity
for(var i=0; i<open_brackets.length; i++)
if(open_brackets[i] > 0) {
var brkt;
for(bracket in brackets)
if(brackets[bracket].bracket_id === i && !brackets[bracket].open)
brkt = brackets[bracket];
err('Unmatched close bracket for bracket '+brkt+'!');
}
if(tree)
return output;
return this.parseTree(output);
};
/**
* Reads a string into an array of Symbols and operators
* @param {Symbol} symbol
* @returns {Array}
*/
this.toObject = function(expression_string) {
var output = [[]], //the first one is the parent
e = expression_string.split(' ').join(''), //remove spaces
func_stack = [],
lp = 0,
target = output[0],
token;
var push = function(token) {
if(token !== '')
target.push(new Symbol(token));
};
//start the conversion
for(var i=0, l=e.length; i<l; i++) {
var ch = e.charAt(i);
if(ch in operators) {
token = e.substring(lp, i);
push(token);
target.push(ch);
lp = i+1;
}
else if(ch in brackets) {
var bracket = brackets[ch];
if(bracket.open) {
//we may be dealing with a function so make
func_stack.push(e.substring(lp, i));
target = []; //start a new scope
output.push(target); //add it to the chain
lp = i+1;
}
else {
//we have a close bracket
token = e.substring(lp, i); //grab the token
push(token);
var o = output.pop(), //close the scope
f = func_stack.pop(), //grab the matching function
r;
//is it a function?
if(f in functions)
r = _.symfunction(f, o);
else if(f === '') {
r = o;
r.type = bracket.type;
}
else
r = f;
//point to the correct target
target = output[output.length-1];
target.push(r);
lp = i+1;
}
}
}
push(e.substring(lp, i)); //insert the last token
return output[0];
};
var chunkAtCommas = function(arr){
var j, k = 0, chunks = [[]];
for (var j = 0, l=arr.length; j<l; j++){
if (arr[j] === ',') {
k++;
chunks[k] = [];
} else {
chunks[k].push(arr[j]);
}
}
return chunks;
};
var rem_brackets = function(str) {
return str.replace(/^\\left\((.+)\\right\)$/g, function(str, a) {
if(a) return a;
return str;
});
};
this.toTeX = function(expression_or_obj) {
var obj = typeof expression_or_obj === 'string' ? this.toObject(expression_or_obj) : expression_or_obj,
TeX = [];
if(isArray(obj)) {
var nobj = [], a, b;
//first handle ^
for(var i=0; i<obj.length; i++) {
a = obj[i];
if(obj[i+1] === '^') {
b = obj[i+2];
nobj.push(LaTeX.braces(this.toTeX([a]))+'^'+LaTeX.braces(this.toTeX([b])));
i+=2;
}
else
nobj.push(a);
}
obj = nobj;
}
for(var i=0, l=obj.length; i<l; i++) {
var e = obj[i];
//convert * to cdot
if(e === '*')
e = '\\cdot';
if(isSymbol(e)) {
if(e.group === FN) {
var fname = e.fname, f;
if(fname === SQRT)
f = '\\sqrt'+LaTeX.braces(this.toTeX(e.args));
else if(fname === ABS)
f = LaTeX.brackets(this.toTeX(e.args), 'abs');
else if(fname === PARENTHESIS)
f = LaTeX.brackets(this.toTeX(e.args), 'parens');
else if (fname === 'log10')
f = '\\log_{10}\\left( ' + this.toTeX(e.args) + '\\right)';
else if(fname === 'integrate') {
/* Retrive [Expression, x] */
var chunks = chunkAtCommas(e.args);
/* Build TeX */
var expr = LaTeX.braces(this.toTeX(chunks[0])),
dx = this.toTeX(chunks[1]);
f = '\\int ' + expr + '\\, d' + dx;
}
else if (fname === 'defint') {
var chunks = chunkAtCommas(e.args),
expr = LaTeX.braces(this.toTeX(chunks[0])),
dx = this.toTeX(chunks[1]),
lb = this.toTeX(chunks[2]),
ub = this.toTeX(chunks[3]);
f = '\\int\\limits_{'+lb+'}^{'+ub+'} '+expr+'\\, d'+dx;
}
else if(fname === 'diff') {
var chunks = chunkAtCommas(e.args);
var dx = '', expr = LaTeX.braces(this.toTeX(chunks[0]));
/* Handle cases: one argument provided, we need to guess the variable, and assume n = 1 */
if (chunks.length == 1){
var vars = [];
for (j = 0; j < chunks[0].length; j++){
if (chunks[0][j].group === 3) {
vars.push(chunks[0][j].value);
}
}
vars = vars.sort();
dx = vars.length > 0 ? ('\\frac{d}{d ' + vars[0] + '}') : '\\frac{d}{d x}';
}
/* If two arguments, we have expression and variable, we assume n = 1 */
else if (chunks.length == 2){
dx = '\\frac{d}{d ' + chunks[1] + '}';
}
/* If we have more than 2 arguments, we assume we've got everything */
else {
dx = '\\frac{d^{' + chunks[2] + '}}{d ' + this.toTeX(chunks[1]) + '^{' + chunks[2] + '}}';
}
f = dx + '\\left(' + expr + '\\right)';
}
else if (fname === 'sum' || fname === 'product') {
// Split e.args into 4 parts based on locations of , symbols.
var argSplit = [[], [], [], []], j = 0, i;
for (i = 0; i < e.args.length; i++){
if (e.args[i] === ','){
j++;
continue;
}
argSplit[j].push(e.args[i]);
}
// Then build TeX string.
f = (fname==='sum'?'\\sum_':'\\prod_')+LaTeX.braces(this.toTeX(argSplit[1])+' = '+this.toTeX(argSplit[2]));
f += '^'+LaTeX.braces(this.toTeX(argSplit[3])) + LaTeX.braces(this.toTeX(argSplit[0]));
}
else if(fname === FACTORIAL || fname === DOUBLEFACTORIAL)
f = this.toTeX(e.args) + (fname === FACTORIAL ? '!' : '!!');
else {
f = '\\mathrm'+LaTeX.braces(fname) + LaTeX.brackets(this.toTeX(e.args), 'parens');
}
TeX.push(f);
}
else
TeX.push(LaTeX.latex(e));
}
else if(isArray(e)) {
TeX.push(LaTeX.brackets(this.toTeX(e)));
}
else {
if(e === '/')
TeX.push(LaTeX.frac(rem_brackets(TeX.pop()), rem_brackets(this.toTeX([obj[++i]]))));
else
TeX.push(e);
}
}
return TeX.join(' ');
};
/////////// ********** FUNCTIONS ********** ///////////
/* Although parens is not a "real" function it is important in some cases when the
* symbol must carry parenthesis. Once set you don't have to worry about it anymore
* as the parser will get rid of it at the first opportunity
*/
function parens(symbol) {
if(Settings.PARSE2NUMBER) {
return symbol;
}
return _.symfunction('parens', [symbol]);
}
function abs(symbol) {
if(symbol.multiplier.lessThan(0)) symbol.multiplier.negate();
if(isNumericSymbol(symbol) || even(symbol.power)) {
return symbol;
}
if(symbol.isComposite()) {
var ms = [];
symbol.each(function(x) {
ms.push(x.multiplier);
});
var gcd = Math2.QGCD.apply(null, ms);
if(gcd.lessThan(0)) {
symbol.multiplier = symbol.multiplier.multiply(new Frac(-1));
symbol.distributeMultiplier();
}
}
return _.symfunction(ABS, [symbol]);
}
/**
* The factorial functions
* @param {Symbol} symbol
* @return {Symbol)
*/
function factorial(symbol) {
var retval;
if(Settings.PARSE2NUMBER && symbol.isConstant()) {
if(isInt(symbol))
retval = Math2.bigfactorial(symbol);
else
retval = Math2.gamma(symbol.multiplier.toDecimal()+1);
return bigConvert(retval);
}
return _.symfunction(FACTORIAL, [symbol]);
}
/**
* The mod function
* @param {Symbol} symbol1
* @param {Symbol} symbol2
* @returns {Symbol}
*/
function mod(symbol1, symbol2) {
if(symbol1.isConstant() && symbol2.isConstant()) {
var retval = new Symbol(1);
retval.multiplier = retval.multiplier.multiply(symbol1.multiplier.mod(symbol2.multiplier));
return retval;
}
return _.symfunction('mod', [symbol1, symbol2]);
}
/**
* A branghing function
* @param {Boolean} condition
* @param {Symbol} a
* @param {Symbol} b
* @returns {Symbol}
*/
function IF(condition, a, b) {
if(typeof condition !== 'boolean')
if(isNumericSymbol(condition))
condition = !!Number(condition);
if(condition)
return a;
return b;
}
/**
* The square root function
* @param {Symbol} symbol
* @returns {Symbol}
*/
function sqrt(symbol) {
if(symbol.fname === '' && symbol.power.equals(1))
symbol = symbol.args[0];
if(Settings.PARSE2NUMBER && symbol.isConstant() && !symbol.multiplier.lessThan(0))
return new Symbol(Math.sqrt(symbol.multiplier.toDecimal()));
var img, retval,
isConstant = symbol.isConstant();
if(symbol.group === CB && symbol.isLinear()) {
var m = sqrt(Symbol(symbol.multiplier));
for(var s in symbol.symbols) {
var x = symbol.symbols[s];
m = _.multiply(m, sqrt(x));
}
retval = m;
}
//if the symbol is already sqrt then it's that symbol^(1/4) and we can unwrap it
else if(symbol.fname === SQRT) {
var s = symbol.args[0];
s.setPower(symbol.power.multiply(new Frac(0.25)));
retval = s;
}
//if the symbol is a fraction then we don't keep can unwrap it. For instance
//no need to keep sqrt(x^(1/3))
else if(!symbol.power.isInteger()) {
symbol.setPower(symbol.power.multiply(new Frac(0.5)));
retval = symbol;
}
else {
//if the symbols is imagary then we place in the imaginary part. We'll return it
//as a product
if(isConstant && symbol.multiplier.lessThan(0)) {
img = Symbol.imaginary();
symbol.multiplier = symbol.multiplier.abs();
}
var q = symbol.multiplier.toDecimal(),
qa = Math.abs(q),
t = Math.sqrt(qa);
var m;
//it's a perfect square so take the square
if(isInt(t)) {
m = new Symbol(t);
}
else if(isInt(q)) {
var factors = Math2.ifactor(q);
var tw = 1;
for(var x in factors) {
var n = factors[x],
nn = (n - (n%2)); //get out the whole numbers
if(nn) { //if there is a whole number ...
var w = Math.pow(x, nn);
tw *= Math.pow(x, nn/2); //add to total wholes
q /= w; //reduce the number by the wholes
}
}
m = _.multiply(_.symfunction(SQRT, [new Symbol(q)]), new Symbol(tw));
}
else {
var n = symbol.multiplier.num.toString(),
d = symbol.multiplier.den.toString(),
sqrtN = Math.sqrt(n),
sqrtD = Math.sqrt(d);
m = _.multiply(
n === '1' ? new Symbol(1) : isInt(sqrtN) ? new Symbol(sqrtN) : _.symfunction(SQRT, [new Symbol(n)]),
d === '1' ? new Symbol(1) : isInt(sqrtD) ? new Symbol(sqrtD).invert() : _.symfunction(SQRT, [new Symbol(d)]).invert()
);
}
//strip the multiplier since we already took the sqrt
symbol = symbol.toUnitMultiplier(true);
//if the symbol is one just return one and not the sqrt function
if(symbol.isOne()) {
retval = symbol;
}
else if(even(symbol.power.toString())) {
//just raise it to the 1/2
retval = _.pow(symbol.clone(), new Symbol(0.5));
}
else {
retval = _.symfunction(SQRT, [symbol]);
}
if(m) retval = _.multiply(m, retval);
if(img) retval = _.multiply(img, retval);
}
return retval;
}
function nthroot(symbol, n) {
if(isVector(n)) {
var v = new Vector();
n.each(function(e) {
v.elements.push(nthroot(symbol.clone(), e));
});
return v;
}
return block('PARSE2NUMBER', function() {
return _.pow(symbol, n.invert());
}, true);
}
function pfactor(symbol) {
var retval = new Symbol(1);
if(symbol.isConstant()) {
var m = symbol.multiplier.toDecimal();
if(isInt(m)) {
var factors = Math2.ifactor(m);
for(var factor in factors) {
var p = factors[factor];
retval = _.multiply(retval, _.symfunction('parens', [new Symbol(factor).setPower(new Frac(p))]));
}
}
else {
var n = pfactor(new Symbol(symbol.multiplier.num));
var d = pfactor(new Symbol(symbol.multiplier.den));
retval = _.multiply(_.symfunction('parens', [n]), _.symfunction('parens', [d]).invert());
}
}
return retval;
}
function log(symbol) {
var retval;
if(symbol.equals(0)) {
err('log(0) is undefined!');
}
if(symbol.group === EX && symbol.power.multiplier.lessThan(0) || symbol.power.toString() === '-1') {
symbol.power.negate();
//move the negative outside but keep the positive inside :)
retval = log(symbol).negate();
}
else if(symbol.value === 'e' && symbol.multiplier.equals(1)) {
var p = symbol.power;
retval = isSymbol(p) ? p : new Symbol(p);
}
else if(symbol.group === FN && symbol.fname === 'exp') {
var s = symbol.args[0];
if(symbol.multiplier.equals(1)) retval = _.multiply(s, new Symbol(symbol.power));
else retval = _.symfunction('log',[symbol]);
}
else if(Settings.PARSE2NUMBER && isNumericSymbol(symbol)) {
var img_part;
if(symbol.multiplier.lessThan(0)) {
symbol.negate();
img_part = _.multiply(new Symbol(Math.PI), new Symbol('i'));
}
retval = new Symbol(Math.log(symbol.multiplier.toDecimal()));
if(img_part) retval = _.add(retval, img_part);
}
else {
var s;
if(!symbol.power.equals(1)) {
s = symbol.group === EX ? symbol.power : new Symbol(symbol.power);
symbol.toLinear();
}
retval = _.symfunction('log', [symbol]);
if(s) retval = _.multiply(s, retval);
}
return retval;
}
function getQuadrant(m) {
var v = m % 2, quadrant;
if(v < 0) v = 2+v; //put it in terms of pi
if(v >= 0 && v <= 0.5) quadrant = 1;
else if(v > 0.5 && v <= 1) quadrant = 2;
else if(v > 1 && v <= 1.5) quadrant = 3;
else quadrant = 4;
return quadrant;
}
/*
* Serves as a bridge between numbers and bigNumbers
* @param {Frac|Number} n
* @returns {Symbol}
*/
function bigConvert(n) {
if(!isFinite(n)){
var sign = Math.sign(n);
var r = new Symbol(String(Math.abs(n)));
r.multiplier = r.multiplier.multiply(new Frac(sign));
return r;
}
if(isSymbol(n))
return n;
if(typeof n === 'number')
n = Frac.simple(n);
var symbol = new Symbol(0);
symbol.multiplier = n;
return symbol;
}
function cos(symbol) {
if(Settings.PARSE2NUMBER && symbol.isConstant()) {
return new Symbol(Math.cos(symbol.valueOf()));
}
var retval,
c = false,
q = getQuadrant(symbol.multiplier.toDecimal()),
m = symbol.multiplier.abs();
symbol.multiplier = m;
if(symbol.isPi() && symbol.isLinear()) {
//return for 1 or -1 for multiples of pi
if(isInt(m)) {
retval = new Symbol(even(m) ? 1 : -1);
}
else {
var n = Number(m.num), d = Number(m.den);
if(d === 2) retval = new Symbol(0);
else if(d === 3) {
retval = _.parse('1/2'); c = true;
}
else if(d === 4) {
retval = _.parse('1/sqrt(2)'); c = true;
}
else if(d === 6) {
retval = _.parse('sqrt(3)/2'); c = true;
}
else retval = _.symfunction('cos', [symbol]);
}
}
if(c && (q === 2 || q === 3)) retval.negate();
if(!retval) retval = _.symfunction('cos', [symbol]);
return retval;
}
function sin(symbol) {
if(Settings.PARSE2NUMBER && symbol.isConstant()) {
return new Symbol(Math.sin(symbol.valueOf()));
}
var retval,
c = false,
q = getQuadrant(symbol.multiplier.toDecimal()),
sign = symbol.multiplier.sign(),
m = symbol.multiplier.abs();
symbol.multiplier = m;
if(symbol.isPi() && symbol.isLinear()) {
//return for 0 for multiples of pi
if(isInt(m)) {
retval = new Symbol(0);
}
else {
var n = m.num, d = m.den;
if(d == 2) {
retval = new Symbol(1); c = true;
}
else if(d == 3) {
retval = _.parse('sqrt(3)/2'); c = true;
}
else if(d == 4) {
retval = _.parse('1/sqrt(2)'); c = true;
}
else if(d == 6) {
retval = _.parse('1/2'); c = true;
}
else retval = _.symfunction('sin', [symbol]);
}
}
if(!retval) retval = _.multiply(new Symbol(sign), _.symfunction('sin', [symbol]));
if(c && (q === 3 || q === 4)) retval.negate();
return retval;
}
function tan(symbol) {
if(Settings.PARSE2NUMBER && symbol.isConstant()) {
return new Symbol(Math.tan(symbol.valueOf()));
}
var retval,
c = false,
q = getQuadrant(symbol.multiplier.toDecimal()),
m = symbol.multiplier;
symbol.multiplier = m;
if(symbol.isPi() && symbol.isLinear()) {
//return 0 for all multiples of pi
if(isInt(m)) {
retval = new Symbol(0);
}
else {
var n = m.num, d = m.den;
if(d == 2) err('tan is undefined for '+symbol.toString());
else if(d == 3) {
retval = _.parse('sqrt(3)'); c = true;
}
else if(d == 4) {
retval = new Symbol(1); c = true;
}
else if(d == 6) {
retval = _.parse('1/sqrt(3)'); c = true;
}
else retval = _.symfunction('tan', [symbol]);
}
}
if(!retval) retval = _.symfunction('tan', [symbol]);
if(c && (q === 2 || q === 4)) retval.negate();
return retval;
}
function sec(symbol) {
//let's be lazy
if(Settings.PARSE2NUMBER && symbol.isConstant()) {
return new Symbol(Math2.sec(symbol.valueOf()));
}
var retval,
c = false,
q = getQuadrant(symbol.multiplier.toDecimal()),
m = symbol.multiplier.abs();
symbol.multiplier = m;
if(symbol.isPi() && symbol.isLinear()) {
//return for 1 or -1 for multiples of pi
if(isInt(m)) {
retval = new Symbol(even(m) ? 1 : -1);
}
else {
var n = m.num, d = m.den;
if(d == 2) err('sec is undefined for '+symbol.toString());
else if(d == 3) {
retval = new Symbol(2); c = true;
}
else if(d == 4) {
retval = _.parse('sqrt(2)'); c = true;
}
else if(d == 6) {
retval = _.parse('2/sqrt(3)'); c = true;
}
else retval = _.symfunction('sec', [symbol]);
}
}
if(c && (q === 2 || q === 3)) retval.negate();
if(!retval) retval = _.symfunction('sec', [symbol]);
return retval;
}
function csc(symbol) {
if(Settings.PARSE2NUMBER && symbol.isConstant()) {
return new Symbol(Math2.csc(symbol.valueOf()));
}
var retval,
c = false,
q = getQuadrant(symbol.multiplier.toDecimal()),
m = symbol.multiplier.abs();
symbol.multiplier = m;
if(symbol.isPi() && symbol.isLinear()) {
//return for 0 for multiples of pi
if(isInt(m)) {
err('csc is undefined for '+symbol.toString());
}
else {
var n = m.num, d = m.den;
if(d == 2) {
retval = new Symbol(1); c = true;
}
else if(d == 3) {
retval = _.parse('2/sqrt(3)'); c = true;
}
else if(d == 4) {
retval = _.parse('sqrt(2)'); c = true;
}
else if(d == 6) {
retval = new Symbol(2); c = true;
}
else retval = _.symfunction('csc', [symbol]);
}
}
if(!retval) retval = _.symfunction('csc', [symbol]);
if(c && (q === 3 || q === 4)) retval.negate();
return retval;
}
function cot(symbol) {
if(Settings.PARSE2NUMBER && symbol.isConstant()) {
return new Symbol(Math2.cot(symbol.valueOf()));
}
var retval,
c = false,
q = getQuadrant(symbol.multiplier.toDecimal()),
m = symbol.multiplier;
symbol.multiplier = m;
if(symbol.isPi() && symbol.isLinear()) {
//return 0 for all multiples of pi
if(isInt(m)) {
err('cot is undefined for '+symbol.toString());
}
else {
var n = m.num, d = m.den;
if(d == 2) retval = new Symbol(0);
else if(d == 3) {
retval = _.parse('1/sqrt(3)'); c = true;
}
else if(d == 4) {
retval = new Symbol(1); c = true;
}
else if(d == 6) {
retval = _.parse('sqrt(3)'); c = true;
}
else retval = _.symfunction('cot', [symbol]);
}
}
if(!retval) retval = _.symfunction('cot', [symbol]);
if(c && (q === 2 || q === 4)) retval.negate();
return retval;
}
function clean(symbol) {
// handle functions with numeric values
// handle denominator within denominator
// handle trig simplifications
var g = symbol.group, retval;
//Now let's get to work
if(g === CP) {
var num = symbol.getNum(),
den = symbol.getDenom() || new Symbol(1),
p = Number(symbol.power),
factor = new Symbol(1);
if(Math.abs(p) === 1) {
den.each(function(x) {
if(x.group === CB) {
factor = _.multiply(factor, clean(x.getDenom()));
}
else if(x.power.lessThan(0)) {
factor = _.multiply(factor, clean(x.clone().toUnitMultiplier()));
}
});
var new_den = new Symbol(0);
//now divide out the factor and add to new den
den.each(function(x) {
new_den = _.add(_.divide(x, factor.clone()), new_den);
});
factor.invert(); //invert so it can be added to the top
var new_num;
if(num.isComposite()) {
new_num = new Symbol(0);
num.each(function(x){
new_num = _.add(_.multiply(clean(x), factor.clone()), new_num);
});
}
else
new_num = _.multiply(factor, num);
retval = _.divide(new_num, new_den);
}
}
else if(g === CB) {
retval = new Symbol(1);
symbol.each(function(x) {
retval = _.multiply(retval, _.clean(x));
});
}
else if(g === FN) {
if(symbol.args.length === 1 && symbol.args[0].isConstant())
retval = block('PARSE2NUMBER', function() {
return _.parse(symbol);
}, true);
}
if(!retval)
retval = symbol;
return retval;
}
/**
* Expands a symbol
* @param symbol
*/
function expand(symbol) {
if(!symbol.symbols) return symbol; //nothing to do
var original = symbol.clone();
try {
var p = symbol.power,
m = symbol.multiplier,
pn = Number(p);
if(!symbol.symbols) return symbol;
//expand all the symbols
for(var s in symbol.symbols) {
var x = symbol.symbols[s];
symbol.symbols[s] = expand(x);
}
symbol = _.parse(symbol);
if(isInt(p) && pn > 0 && symbol.isComposite()) {
//leave original untouched
symbol = symbol.toLinear().toUnitMultiplier();
var result = symbol.clone();
for(var i=0; i<pn-1; i++) {
var t = new Symbol(0);
for(var s in symbol.symbols) {
var x = symbol.symbols[s];
for(var s2 in result.symbols) {
var y = result.symbols[s2];
var r = _.expand(_.multiply(x.clone(), y.clone())),
rp = Number(r.power);
if(r.group === CB && rp !== 1 || r.group === PL && rp !== 1) r = expand(r);
t = _.add(t, r);
}
}
result = t;
}
//put back the multiplier
if(!m.equals(1)) {
for(var s in result.symbols) {
var x = result.symbols[s];
x.multiplier = x.multiplier.multiply(m);
if(x.isComposite())
x.distributeMultiplier();
symbol.symbols[s] = x;
}
}
return result;
}
else if(symbol.group === CB) {
//check if the symbol has composites
var hascomposites = false, sp = symbol.power;
for(var x in symbol.symbols) {
var sub = symbol.symbols[x];
if(sub.isComposite()) {
hascomposites = true;
break;
}
if(isSymbol(sub.power) || isSymbol(sp)) {
sub.power = _.multiply(sub.power, Symbol(sp));
sub.group = EX;
}
else sub.power = sub.power.multiply(sp);
}
symbol.toLinear();
//I'm going to be super lazy here and take the easy way out. TODO: do this without re-parsing
symbol = _.parse(symbol.text());
if(!hascomposites) return symbol; //nothing to do here
var result = new Symbol(0);
var composites = [],
non_composites = new Symbol(symbol.multiplier);
//sort them out
for(var s in symbol.symbols) {
var x = symbol.symbols[s];
if(x.isComposite()) {
var p = x.power, isDenom = false;
if(isInt(p)) {
if(p < 0) {
x.power.negate();
isDenom = true;
}
}
if(isDenom) {
x.power.negate();
non_composites = _.multiply(non_composites, x);
}
else composites.push(x);
}
else non_composites = _.multiply(non_composites, x);
}
//multiply out the remainder
var l = composites.length;
//grab the first symbol since we'll loop over that one to begin
result = composites[0];
for(var i=1; i<l; i++) {
var t = new Symbol(0);
var s = composites[i];
for(var s1 in result.symbols) {
var x = result.symbols[s1];
for(var s2 in s.symbols) {
var y = s.symbols[s2];
var temp = _.multiply(x.clone(),y.clone());
t = _.add(t, temp);
}
}
result = t;
}
var finalResult = new Symbol(0);
//put back the multiplier
for(var s in result.symbols) {
var x = result.symbols[s];
finalResult = _.add(finalResult, expand(_.multiply(non_composites, x)));
}
symbol = finalResult;
}
}
catch(e){ return original; }
return symbol;
}
function imatrix(n) {
return Matrix.identity(n);
}
function vecget(vector, index) {
return vector.elements[index];
}
function vecset(vector, index, value) {
vector.elements[index] = value;
return vector;
}
function matget(matrix, i, j) {
return matrix.elements[i][j];
}
function matset(matrix, i, j, value) {
matrix.elements[i][j] = value;
return matrix;
}
//link this back to the parser
this.expand = expand;
this.clean = clean;
//the constructor for vectors
function vector() {
return new Vector([].slice.call(arguments));
}
//the constructor for matrices
function matrix() {
return Matrix.fromArray(arguments);
}
function determinant(symbol) {
if(isMatrix(symbol)) {
return symbol.determinant();
}
return symbol;
}
function dot(vec1, vec2) {
if(isVector(vec1) && isVector(vec2)) return vec1.dot(vec2);
err('function dot expects 2 vectors');
}
function cross(vec1, vec2) {
if(isVector(vec1) && isVector(vec2)) return vec1.cross(vec2);
err('function cross expects 2 vectors');
}
function transpose(mat) {
if(isMatrix(mat)) return mat.transpose();
err('function transpose expects a matrix');
}
function invert(mat) {
if(isMatrix(mat)) return mat.invert();
err('invert expects a matrix');
}
function testSQRT(symbol) {
//wrap the symbol in sqrt. This eliminates one more check down the line.
if(!isSymbol(symbol.power) && symbol.power.absEquals(0.5)) {
var sign = symbol.power.sign();
//don't devide the power directly. Notice the use of toString. This makes it possible
//to use a bigNumber library in the future
return sqrt(symbol.group === P ? new Symbol(symbol.value) : symbol.toLinear()).setPower(new Frac(sign));
}
return symbol;
}
//try to reduce a symbol by pulling its power
function testPow(symbol) {
if(symbol.group === P) {
var v = symbol.group === N ? symbol.multiplier.toDecimal() : symbol.value,
fct = primeFactors(v)[0],
n = new Frac(Math.log(v)/Math.log(fct)),
p = n.multiply(symbol.power);
//we don't want a more complex number than before
if(p.den > symbol.power.den) return symbol;
if(isInt(p)) symbol = Symbol(Math.pow(fct, p));
else symbol = new Symbol(fct).setPower(p);
}
return symbol;
}
//extended functions. Because functions like log aren't directly
//stored in an object, it's difficult to find out about them unless you know of them
//outside of the library. This serves as registry. That's all.
this.ext = {
log: log,
sqrt: sqrt,
abs: abs,
vector: vector,
matrix: matrix,
parens: parens,
determinant: determinant,
dot: dot,
invert: invert,
transpose: transpose
};
//The loader for functions which are not part of Math2
this.mapped_function = function() {
var subs = {},
params = this.params;
for(var i=0; i<params.length; i++) subs[params[i]] = arguments[i];
return _.parse(this.body, subs);
};
/**
* Adds two symbols
* @param {Symbol} a
* @param {Symbol} b
* @returns {Symbol}
*/
this.add = function(a, b) {
var aIsSymbol = isSymbol(a),
bIsSymbol = isSymbol(b);
//we're dealing with two symbols
if(aIsSymbol && bIsSymbol) {
if(a.isComposite() && a.isLinear() && b.isComposite() && b.isLinear()) {
a.distributeMultiplier();
b.distributeMultiplier();
}
//no need to waste time on zeroes
if(a.multiplier.equals(0)) return b;
if(b.multiplier.equals(0)) return a;
if(a.isConstant() && b.isConstant() && Settings.PARSE2NUMBER) {
return new Symbol(a.multiplier.add(b.multiplier).valueOf());
}
var g1 = a.group,
g2 = b.group,
ap = a.power.toString(),
bp = b.power.toString();
//always keep the greater group on the left.
if(g1 < g2 || (g1 === g2 && ap > bp && bp > 0)) return this.add(b, a);
/*note to self: Please don't forget about this dilemma ever again. In this model PL and CB goes crazy
* because it doesn't know which one to prioritize. */
//correction to PL dilemma
if(g1 === CB && g2 === PL && a.value === b.value) {
//swap
var t = a; a = b; b = t;
g1 = a.group; g2 = b.group; ap = a.power.toString(); bp = b.power.toString();
}
var powEQ = ap === bp,
v1 = a.value,
v2 = b.value,
aIsComposite = a.isComposite(),
bIsComposite = b.isComposite(),
h1, h2, result;
if(aIsComposite) h1 = text(a, 'hash');
if(bIsComposite) h2 = text(b, 'hash');
if(g1 === CP && g2 === CP && b.isLinear() && !a.isLinear() && h1 !== h2) {
return this.add(a, b);
}
//PL & PL should compare hashes and not values e.g. compare x+x^2 with x+x^3 and not x with x
if(g1 === PL && g2 === PL) {
v1 = h1; v2 = h2;
}
var PN = g1 === P && g2 === N,
PNEQ = a.value === b.multiplier.toString(),
valEQ = (v1 === v2 || h1 === h2 && !h1 === undefined || (PN && PNEQ));
//equal values, equal powers
if(valEQ && powEQ && g1 === g2) {
//make sure to convert N to something P can work with
if(PN) b = b.convert(P);//CL
//handle PL
if(g1 === PL && (g2 === S || g2 === P)) {
a.distributeMultiplier();
result = a.attach(b);
}
else {
result = a;//CL
if(a.multiplier.isOne() && b.multiplier.isOne() && g1 === CP) {
for(var s in b.symbols) {
var x = b.symbols[s];
result.attach(x);
}
}
else result.multiplier = result.multiplier.add(b.multiplier);
}
}
//equal values uneven powers
else if(valEQ && g1 !== PL) {
result = Symbol.shell(PL).attach([a, b]);
//update the hash
result.value = g1 === PL ? h1 : v1;
}
else if(aIsComposite && a.isLinear()) {
var canIterate = g1 === g2,
bothPL = g1 === PL && g2 === PL;
//we can only iterate group PL if they values match
if(bothPL) canIterate = a.value === b.value;
//distribute the multiplier over the entire symbol
a.distributeMultiplier();
if(b.isComposite() && b.isLinear() && canIterate) {
b.distributeMultiplier();
//CL
for(var s in b.symbols) {
var x = b.symbols[s];
a.attach(x);
}
result = a;
}
//handle cases like 2*(x+x^2)^2+2*(x+x^2)^3+4*(x+x^2)^2
else if(bothPL && a.value !== h2 || g1 === PL && !valEQ) {
result = Symbol.shell(CP).attach([a, b]);
result.updateHash();
}
else {
result = a.attach(b);
}
}
else {
if(g1 === FN && a.fname === SQRT && g2 !== EX && b.power.equals(0.5)) {
var m = b.multiplier.clone();
b = sqrt(b.toUnitMultiplier().toLinear());
b.multiplier = m;
}
result = Symbol.shell(CP).attach([a, b]);
result.updateHash();
}
if(result.multiplier.equals(0)) result = new Symbol(0);
//make sure to remove unnecessary wraps
if(result.length === 1) {
var m = result.multiplier;
result = firstObject(result.symbols);
result.multiplier = result.multiplier.multiply(m);
}
return result;
}
else {
//keep symbols to the right
if(bIsSymbol && !aIsSymbol) {
var t = a; a = b; b = t; //swap
t = bIsSymbol; bIsSymbol = aIsSymbol; aIsSymbol = t;
}
var bIsMatrix = isMatrix(b);
if(aIsSymbol && bIsMatrix) {
b.eachElement(function(e) {
return _.add(a.clone(), e);
});
}
else {
if(isMatrix(a) && bIsMatrix) {
b = a.add(b);
}
else if(aIsSymbol && isVector(b)) {
b.each(function(x, i) {
i--;
b.elements[i] = _.add(a.clone(), b.elements[i]);
});
}
else {
if(isVector(a) && isVector(b)) {
b.each(function(x, i) {
i--;
b.elements[i] = _.add(a.elements[i], b.elements[i]);
});
}
else if(isVector(a) && isMatrix(b)) {
//try to convert a to a matrix
return _.add(b, a);
}
else if(isMatrix(a) && isVector(b)) {
if(b.elements.length === a.rows()) {
var M = new Matrix(), l = a.cols();
b.each(function(e, i) {
var row = [];
for(var j=0; j<l; j++) {
row.push(_.add(a.elements[i-1][j].clone(), e.clone()));
}
M.elements.push(row);
});
return M;
}
else err('Dimensions must match!');
}
}
}
return b;
}
};
/**
* Gets called when the parser finds the - operator. Not the prefix operator. See this.add
* @param {Symbol} symbol1
* @param {Symbol} symbol2
* @returns {Symbol}
*/
this.subtract = function(a, b) {
var aIsSymbol = aIsSymbol = isSymbol(a),
bIsSymbol = isSymbol(b), t;
if(aIsSymbol && bIsSymbol) {
return this.add(a, b.negate());
}
else {
if(bIsSymbol) {
t = b; b = a; a = t;
aIsSymbol = bIsSymbol;
}
if(aIsSymbol && isVector(b)) {
b = b.map(function(x) {
return _.subtract(x, a.clone());
});
}
else if(isVector(a) && isVector(b)) {
if(a.dimensions() === b.dimensions()) b = a.subtract(b);
else _.error('Unable to subtract vectors. Dimensions do not match.');
}
else if(isMatrix(a) && isVector(b)) {
if(b.elements.length === a.rows()) {
var M = new Matrix(), l = a.cols();
b.each(function(e, i) {
var row = [];
for(var j=0; j<l; j++) {
row.push(_.subtract(a.elements[i-1][j].clone(), e.clone()));
}
M.elements.push(row);
});
return M;
}
else err('Dimensions must match!');
}
else if(isVector(a) && isMatrix(b)) {
var M = b.clone().negate();
return _.add(M, a);
}
else if(isMatrix(a) && isMatrix(b)) {
b = a.subtract(b);
}
return b;
}
};
/**
* Gets called when the parser finds the * operator. See this.add
* @param {Symbol} a
* @param {Symbol} b
* @returns {Symbol}
*/
this.multiply = function(a, b) {
var aIsSymbol = isSymbol(a),
bIsSymbol = isSymbol(b);
if(aIsSymbol && bIsSymbol) {
//the quickies
if(a.isConstant() && b.isConstant() && Settings.PARSE2NUMBER) {
return new Symbol(a.multiplier.multiply(b.multiplier).valueOf());
}
//don't waste time
if(a.isOne()) return b.clone();
if(b.isOne()) return a.clone();
if(a.multiplier.equals(0) || b.multiplier.equals(0)) return new Symbol(0);
if(b.group > a.group && !(b.group === CP)) return this.multiply(b, a);
//correction for PL/CB dilemma
if(a.group === CB && b.group === PL && a.value === b.value) {
var t = a; a = b; b = t;//swap
}
var g1 = a.group,
g2 = b.group,
bnum = b.multiplier.num,
bden = b.multiplier.den;
if(g1 === FN && a.fname === SQRT && !b.isConstant() && a.args[0].value === b.value) {
//unwrap sqrt
var a_pow = a.power;
a = a.args[0].clone();
a.setPower(new Frac(0.5).multiply(a_pow));
g1 = a.group;
}
var v1 = a.value,
v2 = b.value,
sign = new Frac(a.multiplier.lessThan(0) ? -1 : 1),
//since P is just a morphed version of N we need to see if they relate
ONN = (g1 === P && g2 === N && b.multiplier.equals(a.value)),
//don't multiply the multiplier of b since that's equal to the value of a
m = ONN ? new Frac(1).multiply(a.multiplier).abs() : a.multiplier.multiply(b.multiplier).abs(),
result = a.clone().toUnitMultiplier();
b = b.clone().toUnitMultiplier(true);
//if both are PL then their hashes have to match
if(v1 === v2 && g1 === PL && g1 === g2) {
v1 = a.text('hash');
v2 = b.text('hash');
}
//same issue with (x^2+1)^x*(x^2+1)
//EX needs an exception when multiplying because it needs to recognize
//that (x+x^2)^x has the same hash as (x+x^2). The latter is kept as x
if(g2 === EX && b.previousGroup === PL && g1 === PL) {
v1 = text(a, 'hash', EX);
}
if((v1 === v2 || ONN) && !(g1 === PL && (g2 === S || g2 === P || g2 === FN)) && !(g1 === PL && g2 === CB)) {
var p1 = a.power,
p2 = b.power,
isSymbolP1 = isSymbol(p1),
isSymbolP2 = isSymbol(p2),
toEX = (isSymbolP1 || isSymbolP2);
//TODO: this needs cleaning up
if(g1 === PL && g2 !== PL && b.previousGroup !== PL && p1.equals(1)) {
result = new Symbol(0);
a.each(function(x) {
result = _.add(result, _.multiply(x, b.clone()));
}, true);
}
else {
//add the powers
result.power = toEX ? _.add(
!(isSymbol(p1)) ? new Symbol(p1) : p1,
!(isSymbol(p2)) ? new Symbol(p2) : p2
): (g1 === N /*don't add powers for N*/? p1 : p1.add(p2));
//eliminate zero power values and convert them to numbers
if(result.power.equals(0)) result = result.convert(N);
//properly convert to EX
if(toEX) result.convert(EX);
//take care of imaginaries
if(a.imaginary && b.imaginary) {
var isEven = even(result.power % 2);
if(isEven) {
result = new Symbol(1);
m.negate();
}
}
//cleanup: this causes the LaTeX generator to get confused as to how to render the symbol
if(result.group !== EX && result.previousGroup) result.previousGroup = undefined;
//the sign for b is floating around. Remember we are assuming that the odd variable will carry
//the sign but this isn't true if they're equals symbols
result.multiplier = result.multiplier.multiply(b.multiplier);
}
}
else if(g1 === CB && a.isLinear()){
if(g2 === CB) b.distributeExponent();
if(g2 === CB && b.isLinear()) {
for(var s in b.symbols) {
var x = b.symbols[s];
result = result.combine(x);
}
result.multiplier = result.multiplier.multiply(b.multiplier);
}
else {
result.combine(b);
}
}
else {
//the multiplier was already handled so nothing left to do
if(g1 !== N) {
if(g1 === CB) {
result.distributeExponent();
result.combine(b);
}
else if(!b.isOne()) {
var bm = b.multiplier.clone();
b.toUnitMultiplier();
result = Symbol.shell(CB).combine([result, b]);
//transfer the multiplier to the outside
result.multiplier = result.multiplier.multiply(bm);
}
}
else {
result = b.clone().toUnitMultiplier();
}
}
if(result.group === P) {
var logV = Math.log(result.value),
n1 = Math.log(bnum)/logV,
n2 = Math.log(bden)/logV,
ndiv = m.num/bnum,
ddiv = m.den/bden;
//we don't want to divide by zero no do we? Strange things happen.
if(n1 !== 0 && isInt(n1) && isInt(ndiv)) {
result.power = result.power.add(new Frac(n1));
m.num /= bnum; //BigInt? Keep that in mind for the future.
}
if(n2 !== 0 && isInt(n2) && isInt(ddiv)) {
result.power = result.power.subtract(new Frac(n2));
m.den /= bden; //BigInt? Keep that in mind for the future.
}
}
//unpack CB if length is only one
if(result.length === 1) {
var t = result.multiplier;
//transfer the multiplier
result = firstObject(result.symbols);
result.multiplier = result.multiplier.multiply(t);
}
//reduce square root
var ps = result.power.toString();
if(even(ps) && result.fname === SQRT) {
var p = result.power;
result = result.args[0];
result = _.multiply(new Symbol(m), _.pow(result, new Symbol(p.divide(new Frac(2)))));
}
else {
result.multiplier = result.multiplier.multiply(m).multiply(sign);
}
//back convert group P to a simpler group N if possible
if(result.group === P && isInt(result.power.toDecimal())) result = result.convert(N);
return result;
}
else {
//****** Matrices & Vector *****//
if(bIsSymbol && !aIsSymbol) { //keep symbols to the right
t = a; a = b; b = t; //swap
t = bIsSymbol; bIsSymbol = aIsSymbol; aIsSymbol = t;
}
var isMatrixB = isMatrix(b), isMatrixA = isMatrix(a);
if(aIsSymbol && isMatrixB) {
b.eachElement(function(e) {
return _.multiply(a.clone(), e);
});
}
else {
if(isMatrixA && isMatrixB) {
b = a.multiply(b);
}
else if(aIsSymbol && isVector(b)) {
b.each(function(x, i) {
i--;
b.elements[i] = _.multiply(a.clone(), b.elements[i]);
});
}
else {
if(isVector(a) && isVector(b)) {
b.each(function(x, i) {
i--;
b.elements[i] = _.multiply(a.elements[i], b.elements[i]);
});
}
else if(isVector(a) && isMatrix(b)) {
//try to convert a to a matrix
return this.multiply(b, a);
}
else if(isMatrix(a) && isVector(b)) {
if(b.elements.length === a.rows()) {
var M = new Matrix(), l = a.cols();
b.each(function(e, i) {
var row = [];
for(var j=0; j<l; j++) {
row.push(_.multiply(a.elements[i-1][j].clone(), e.clone()));
}
M.elements.push(row);
});
return M;
}
else err('Dimensions must match!');
}
}
}
return b;
}
};
/**
* Gets called when the parser finds the / operator. See this.add
* @param {Symbol} symbol1
* @param {Symbol} symbol2
* @returns {Symbol}
*/
this.divide = function(a, b) {
var aIsSymbol = isSymbol(a),
bIsSymbol = isSymbol(b);
if(aIsSymbol && bIsSymbol) {
var result;
if(b.equals(0))
throw new DivisionByZero('Division by zero not allowed!');
if(a.isConstant() && b.isConstant()) {
result = a.clone();
result.multiplier = result.multiplier.divide(b.multiplier);
}
else {
b.invert();
result = _.multiply(a, b);
}
return result;
}
else {
//******* Vectors & Matrices *********//
var isVectorA = isVector(a), isVectorB = isVector(b);
if(aIsSymbol && isVectorB) {
b = b.map(function(x){
return _.divide(a.clone(),x);
});
}
else if(isVectorA && bIsSymbol) {
b = a.map(function(x) {
return _.divide(x, b.clone());
});
}
else if(isVectorA && isVectorB) {
if(a.dimensions() === b.dimensions()) {
b = b.map(function(x, i) {
return _.divide(a.elements[--i], x);
});
}
else _.error('Cannot divide vectors. Dimensions do not match!');
}
else {
var isMatrixA = isMatrix(a), isMatrixB = isMatrix(b);
if(isMatrixA && bIsSymbol) {
a.eachElement(function(x) {
return _.divide(x, b.clone());
});
b = a;
}
else if(isMatrixA && isMatrixB) {
if(a.rows() === b.rows() && a.cols() === b.cols()) {
a.eachElement(function(x, i, j) {
return _.divide(x, b.elements[i][j]);
});
}
else {
_.error('Dimensions do not match!');
}
}
else if(isMatrixA && isVectorB) {
if(a.cols() === b.dimensions()) {
a.eachElement(function(x, i, j) {
return _.divide(x, b.elements[i].clone());
});
b = a;
}
else {
_.error('Unable to divide matrix by vector.');
}
}
}
return b;
}
};
/**
* Gets called when the parser finds the ^ operator. See this.add
* @param {Symbol} a
* @param {Symbol} b
* @returns {Symbol}
*/
this.pow = function(a, b) {
var aIsSymbol = isSymbol(a),
bIsSymbol = isSymbol(b);
if(aIsSymbol && bIsSymbol) {
var aIsZero = a.equals(0);
if(aIsZero && b.equals(0)) err('0^0 is undefined!');
//return 0 right away if possible
if(aIsZero && b.isConstant() && b.multiplier.greaterThan(0))
return new Symbol(0);
var bIsConstant = b.isConstant(),
aIsConstant = a.isConstant(),
bIsInt = b.isInteger(),
m = a.multiplier,
result = a.clone();
//take care of the symbolic part
result.toUnitMultiplier();
//simpifly sqrt
if(result.group === FN && result.fname === SQRT && !bIsConstant) {
var s = result.args[0];
s.multiplyPower(new Symbol(0.5));
s.multiplier.multiply(result.multiplier);
s.multiplyPower(b);
result = s;
}
else {
var sign = m.sign();
//handle cases such as (-a^3)^(1/4)
if(evenFraction(b) && sign < 0) {
//swaperoo
//first put the sign back on the symbol
result.negate();
//wrap it in brackets
result = _.symfunction(PARENTHESIS, [result]);
//move the sign back the exterior and let nerdamer handle the rest
result.negate();
}
result.multiplyPower(b);
}
if(aIsConstant && bIsConstant && Settings.PARSE2NUMBER) {
var c;
//remove the sign
if(sign < 0) {
a.negate();
if(b.multiplier.den.equals(2))
//we know that the numerator has to be odd and therefore it's i
c = new Symbol(Settings.IMAGINARY);
else if(isInt(b.multiplier)) {
if(even(b.multiplier))
c = new Symbol(1);
else
c = new Symbol(-1);
}
else if(!even(b.multiplier.den)) {
sign = Math.pow(sign, b.multiplier.num);
c = new Symbol(Math.pow(a, b)*sign);
}
else {
c = _.pow(_.symfunction(PARENTHESIS, [new Symbol(sign)]), b.clone());
}
}
result = new Symbol(Math.pow(a.multiplier.toDecimal(), b.multiplier.toDecimal()));
//put the back sign
if(c)
result = _.multiply(result, c);
}
else if(bIsInt && !m.equals(1)) {
var p = b.multiplier.toDecimal(),
multiplier = new Frac(Math.pow(m.num, p) / Math.pow(m.den, p));
//multiplying is justified since after mulltiplyPower if it was of group P it will now be of group N
result.multiplier = result.multiplier.multiply(multiplier);
}
else {
var sign = a.sign();
if(b.isConstant() && a.isConstant() && !b.multiplier.den.equals(1) && sign < 0 ) {
//we know the sign is negative so if the denominator for b == 2 then it's i
if(b.multiplier.den.equals(2)) {
var i = new Symbol(Settings.IMAGINARY);
a.negate();//remove the sign
//if the power is negative then i is negative
if(b.lessThan(0)) {
i.negate();
b.negate();//remove the sign from the power
}
//pull the power normally and put back the imaginary
result = _.multiply(_.pow(a, b), i);
}
else {
var aa = a.clone();
aa.multiplier.negate();
result = _.pow(_.symfunction(PARENTHESIS, [new Symbol(-1)]), b.clone());
var _a = _.pow(new Symbol(aa.multiplier.num), b.clone());
var _b = _.pow(new Symbol(aa.multiplier.den), b.clone());
var r = _.divide(_a, _b);
result = _.multiply(result, r);
}
}
else {
//b is a symbol
var neg_num = a.group === N && sign < 0,
num = testSQRT(new Symbol(neg_num ? m.num : Math.abs(m.num)).setPower(b.clone())),
den = testSQRT(new Symbol(m.den).setPower(b.clone()).invert());
//eliminate imaginary if possible
if(a.imaginary) {
//assume i = sqrt(-1) -> (-1)^(1/2)
var nr = b.multiplier.multiply(Frac.quick(1, 2)),
//the denominator denotes the power so raise to it. It will turn positive it round
tn = Math.pow(-1, nr.num);
result = even(nr.den) ? new Symbol(-1).setPower(nr, true) : new Symbol(tn);
}
//ensure that the sign is carried by the symbol and not the multiplier
//this enables us to check down the line if the multiplier can indeed be transferred
if(sign < 0 && !neg_num) result.negate();
//retain the absolute value
if(bIsConstant && a.group !== EX) {
var evenr = even(b.multiplier.den),
evenp = even(a.power),
n = result.power.toDecimal(),
evennp = even(n);
if(evenr && evenp && !evennp) {
if(n === 1 ) result = _.symfunction(ABS, [result]);
else if(!isInt(n)) {
var p = result.power;
result = _.symfunction(ABS, [result.toLinear()]).setPower(p);
}
else {
result = _.multiply(_.symfunction(ABS, [result.clone().toLinear()]),
result.clone().setPower(new Frac(n-1)));
}
}
}
}
}
result = testSQRT(result);
//don't multiply until we've tested the remaining symbol
if(num && den)
result = _.multiply(result, testPow(_.multiply(num, den)));
//reduce square root
if(result.fname === SQRT) {
var isEX = result.group === EX;
var t = isEX ? result.power.multiplier.toString() : result.power.toString();
if(even(t)) {
var pt = isEX ? _.divide(result.power, new Symbol(2)) : new Symbol(result.power.divide(new Frac(2))),
m = result.multiplier;
result = _.pow(result.args[0], pt);
result.multiplier = result.multiplier.multiply(m);
}
}
//detect Euler's identity
else if(result.isE() && result.group === EX && result.power.contains('pi')
&& result.power.contains(Settings.IMAGINARY)) {
//we have a match
var m1 = result.multiplier,
m2 = result.power.multiplier;
result = new Symbol(even(m2) ? m1 : m1.negate());
}
return result;
}
else {
if(isVector(a) && bIsSymbol) {
a = a.map(function(x) {
return _.pow(x, b.clone());
});
}
else if(isMatrix(a) && bIsSymbol) {
a.eachElement(function(x) {
return _.pow(x, b.clone());
});
}
return a;
}
};
//gets called when the parser finds the , operator.
this.comma = function(a, b) {
var aIsArray = (a instanceof Array),
bIsArray = (b instanceof Array),
aHasSubArray = (aIsArray && a[0] instanceof Array);
if ( (aIsArray && aHasSubArray) || (aIsArray && !bIsArray) ) a.push(b);
else a = [a,b];
return a;
};
//the equality setter
this.equals = function(a, b) {
//equality can only be set for group S so complain it's not
if(a.group !== S && !a.isLinear())
err('Cannot set equality for '+a.toString());
VARS[a.value] = b.clone();
return b;
};
//check for equality
this.eq = function(a, b) {
return a.equals(b);
};
//checks for greater than
this.gt = function(a, b) {
return a.gt(b);
};
//checks for greater than equal
this.gte = function(a, b) {
return a.gte(b);
};
//checks for less than
this.lt = function(a, b) {
return a.lt(b);
};
//checks for less than equal
this.lte = function(a, b) {
return a.lte(b);
};
//wraps the factorial
this.factorial = function(a) {
return this.symfunction(FACTORIAL, [a]);
};
//wraps the double factorial
this.dfactorial = function(a) {
return this.symfunction(DOUBLEFACTORIAL, [a]);
};
//wacky fix for factorial with prefixes
this.factadd = function(a, b) {
return _.add(this.symfunction(FACTORIAL, [a]), b);
};
this.dfactadd = function(a, b) {
return _.add(this.symfunction(DOUBLEFACTORIAL, [a]), b);
};
this.factsub = function(a, b) {
return _.subtract(this.symfunction(FACTORIAL, [a]), b);
};
this.dfactsub = function(a, b) {
return _.subtract(this.symfunction(DOUBLEFACTORIAL, [a]), b);
};
}
/* "STATIC" */
//converts a number to a fraction.
var Fraction = {
/**
* Converts a decimal to a fraction
* @param {number} value
* @returns {Array} - an array containing the denominator and the numerator
*/
convert: function( value, opts ) {
var frac;
if( value === 0 ) {
frac = [ 0, 1];
}
else {
if( value < 1e-6 || value > 1e20) {
var qc = this.quickConversion( Number( value ) );
if( qc[1] <= 1e20 ) {
var abs = Math.abs( value );
var sign = value/abs;
frac = this.fullConversion( abs.toFixed( (qc[1]+'').length-1 ));
frac[0] = frac[0]*sign;
}
else {
frac = qc;
}
}
else {
frac = this.fullConversion( value );
}
}
return frac;
},
/**
* If the fraction is too small or too large this gets called instead of fullConversion method
* @param {number} dec
* @returns {Array} - an array containing the denominator and the numerator
*/
quickConversion: function( dec ) {
var x = (dec.toExponential()+'').split('e');
var d = x[0].split('.')[1];// get the number of places after the decimal
var l = d ? d.length : 0; // maybe the coefficient is an integer;
return [Math.pow(10,l)*x[0], Math.pow(10, Math.abs(x[1])+l)];
},
/**
* Returns a good approximation of a fraction. This method gets called by convert
* http://mathforum.org/library/drmath/view/61772.html
* Decimal To Fraction Conversion - A Simpler Version
* Dr Peterson
* @param {number} dec
* @returns {Array} - an array containing the denominator and the numerator
*/
fullConversion: function( dec ) {
var done = false;
//you can adjust the epsilon to a larger number if you don't need very high precision
var n1 = 0, d1 = 1, n2 = 1, d2 = 0, n = 0, q = dec, epsilon = 1e-16;
while(!done) {
n++;
if( n > 10000 ){
done = true;
}
var a = Math.floor(q);
var num = n1 + a * n2;
var den = d1 + a * d2;
var e = (q - a);
if( e < epsilon) {
done = true;
}
q = 1/e;
n1 = n2; d1 = d2; n2 = num; d2 = den;
if(Math.abs(num/den-dec) < epsilon || n > 30) {
done = true;
}
}
return [num, den];
}
};
//Depends on Fraction
//The latex generator
var LaTeX = {
space: '~',
dot: ' \\cdot ',
//grab a list of supported functions but remove the excluded ones found in exclFN
latex: function(symbol, option) {
if(isArray(symbol)) {
var LaTeXArray = [];
for(var i=0; i<symbol.length; i++) {
LaTeXArray.push(this.latex(symbol[i]));
}
return this.brackets(LaTeXArray.join(', '), 'square');
}
if(isMatrix(symbol)) {
var TeX = '\\begin{pmatrix}\n';
for(var i=0; i<symbol.elements.length; i++) {
var rowTeX = [],
e = symbol.elements[i];
for(var j=0; j<e.length; j++) {
rowTeX.push(this.latex(e[j]));
}
TeX += rowTeX.join(' & ')+'\\\\\n';
}
TeX += '\\end{pmatrix}';
return TeX;
}
if (isVector(symbol)) {
var TeX = '\\left[';
for (var i = 0; i < symbol.elements.length; i++){
TeX += this.latex(symbol.elements[i]) + ' ' + (i!==symbol.elements.length-1 ? ',\\,' : '');
}
TeX += '\\right]';
return TeX;
}
symbol = symbol.clone();
var decimal = option === 'decimal',
power = symbol.power,
invert = isNegative(power),
negative = symbol.multiplier.lessThan(0);
if(symbol.group === P && decimal) {
return String(symbol.multiplier.toDecimal()*Math.pow(symbol.value, symbol.power.toDecimal()));
}
else {
symbol.multiplier = symbol.multiplier.abs();
//if the user wants the result in decimal format then return it as such by placing it at the top part
var m_array;
if(decimal) {
var m = String(symbol.multiplier.toDecimal());
if(m == '1' && !decimal) m = '';
m_array = [m, ''];
}
else {
m_array = [symbol.multiplier.num, symbol.multiplier.den];
}
//get the value as a two part array
var v_array = this.value(symbol, invert, option, negative),
p;
//make it all positive since we know whether to push the power to the numerator or denominator already.
if(invert) power.negate();
//the power is simple since it requires no additional formatting. We can get it to a
//string right away. pass in true to neglect unit powers
if(decimal) {
p = isSymbol(power) ? LaTeX.latex(power, option) : String(power.toDecimal());
if(p == '1') p = '';
}
//get the latex representation
else if(isSymbol(power)) p = this.latex(power, option);
//get it as a fraction
else p = this.formatFrac(power, true);
//use this array to specify if the power is getting attached to the top or the bottom
var p_array = ['', ''],
//stick it to the top or the bottom. If it's negative then the power gets placed on the bottom
index = invert ? 1 : 0;
p_array[index] = p;
//special case group P and decimal
var retval = (negative ? '-': '')+this.set(m_array, v_array, p_array, symbol.group === CB);
return retval.replace(/\+\-/gi, '-');
}
},
//greek mapping
greek: {
alpha: '\\alpha',
beta: '\\beta',
gamma: '\\gamma',
delta: '\\delta',
epsilon: '\\epsilon',
zeta: '\\zeta',
eta: '\\eta',
theta: '\\theta',
iota: '\\iota',
kappa: '\\kappa',
lambda: '\\lambda',
mu: '\\mu',
nu: '\\nu',
xi: '\\xi',
omnikron: '\\omnikron',
pi: '\\pi',
rho: '\\rho',
sigma: '\\sigma',
tau: '\\tau',
upsilon: '\\upsilon',
phi: '\\phi',
chi: '\\chi',
psi: '\\psi',
omega: '\\omega',
Gamma: '\\Gamma',
Delta: '\\Delta',
Epsilon: '\\Epsilon',
Theta: '\\Theta',
Lambda: '\\Lambda',
Xi: '\\Xi',
Pi: '\\Pi',
Sigma: '\\Sigma',
Phi: '\\Phi',
Psi: '\\Psi',
Omega: '\\Omega'
},
//get the raw value of the symbol as an array
value: function(symbol, inverted, option, negative) {
var group = symbol.group,
previousGroup = symbol.previousGroup,
v = ['', ''],
index = inverted ? 1 : 0;
/*if(group === N) //do nothing since we want to return top & bottom blank; */
if(group === S || group === P || previousGroup === S || previousGroup === P || previousGroup === N) {
var value = symbol.value;
var greek = this.greek[value];
if(greek) value = greek;
v[index] = value;
}
else if(group === FN || previousGroup === FN) {
var name,
input = [],
fname = symbol.fname;
//collect the arguments
for(var i=0; i<symbol.args.length; i++) {
var arg = symbol.args[i], item;
if(typeof arg === 'string')
item = arg;
else
item = this.latex(arg, option);
input.push(item);
}
if(fname === SQRT) {
v[index] = '\\sqrt'+this.braces(input.join(','));
}
else if(fname === ABS) {
v[index] = this.brackets(input.join(','), 'abs');
}
else if(fname === PARENTHESIS) {
v[index] = this.brackets(input.join(','), 'parens');
}
else if(fname === 'integrate') {
v[index] = '\\int'+this.braces(input[0])+this.braces('d'+input[1]);
}
else if(fname === FACTORIAL || fname === DOUBLEFACTORIAL) {
var arg = symbol.args[0];
if(arg.power.equals(1) && (arg.isComposite() || arg.isCombination())) {
input[0] = this.brackets(input[0]);
}
v[index] = input[0]+(fname === FACTORIAL ? '!' : '!!');
}
else {
var name = fname!=='' ? '\\mathrm'+this.braces(fname) : '';
v[index] = name+this.brackets(input.join(','), 'parens');
}
}
else if(symbol.isComposite()) {
var collected = symbol.collectSymbols().sort(
group === CP || previousGroup === CP ?
function(a, b) { return b.group - a.group;}:
function(a, b) {
var x = isSymbol(a.power) ? -1 : a.power;
var y = isSymbol(b.power) ? -1 : b.power;
return y-x;
}
),
symbols = [],
l = collected.length;
for(var i=0; i<l; i++) {
symbols.push(LaTeX.latex(collected[i], option));
}
var value = symbols.join('+');
v[index] = !(symbol.isLinear() && symbol.multiplier.equals(1)) || negative ? this.brackets(value, 'parens') : value;
}
else if(group === CB || previousGroup === EX) {
//this almost feels a little like cheating but I need to know if I should be wrapping the symbol
//in brackets or not. We'll do this by checking the value of the numerator and then comparing it
//to whether the symbol value is "simple" or not.
var denominator = [],
numerator = [];
//generate a profile
var den_map = [], num_map = [], num_c = 0, den_c = 0;
var setBrackets = function(container, map, counter) {
if(counter > 1 && map.length > 0) {
var l = map.length;
for(var i=0; i<l; i++) {
var idx = map[i], item = container[idx];
if(!(/^\\left\(.+\\right\)\^\{.+\}$/g.test(item) || /^\\left\(.+\\right\)$/g.test(item))) {
container[idx] = LaTeX.brackets(item, 'parens');
}
}
}
return container;
};
//generate latex for each of them
symbol.each(function(x) {
var isDenom = isNegative(x.power),
laTex;
if(isDenom) {
laTex = LaTeX.latex(x.invert(), option);
den_c++;
if(x.isComposite()) {
if(symbol.multiplier.den != 1 && Math.abs(x.power) == 1) laTex = LaTeX.brackets(laTex, 'parens');
den_map.push(denominator.length); //make a note of where the composite was found
}
denominator.push(laTex);
}
else {
laTex = LaTeX.latex(x, option);
num_c++;
if(x.isComposite()) {
if(symbol.multiplier.num != 1 && Math.abs(x.power) == 1) laTex = LaTeX.brackets(laTex, 'parens');
num_map.push(numerator.length); //make a note of where the composite was found
}
numerator.push(laTex);
}
});
//apply brackets
setBrackets(numerator, num_map, num_c);
v[0] = numerator.join(this.dot); //collapse the numerator into one string
setBrackets(denominator, den_map, den_c);
v[1] = denominator.join(this.dot);
}
return v;
},
set: function(m, v, p, combine_power) {
var isBracketed = function(v) {
return /^\\left\(.+\\right\)$/.test(v);
};
//format the power if it exists
if(p) p = this.formatP(p);
//group CB will have to be wrapped since the power applies to both it's numerator and denominator
if(combine_power) {
//POSSIBLE BUG: If powers for group CB format wrong, investigate this since I might have overlooked something
//the assumption is that in every case the denonimator should be empty when dealing with CB. I can't think
//of a case where this isn't true
var tp = p[0];
p[0] = ''; //temporarily make p blank
}
//merge v and p. Not that v MUST be first since the order matters
v = this.merge(v, p);
var mn = m[0], md = m[1], vn = v[0], vd = v[1];
//filters
//if the top has a variable but the numerator is one drop it
if(vn && mn == 1) mn = '';
//if denominator is 1 drop it always
if(md == 1) md = '';
//prepare the top portion but check that it's not already bracketed. If it is then leave out the cdot
var top = this.join(mn, vn, !isBracketed(vn) ? this.dot : '');
//prepare the bottom portion but check that it's not already bracketed. If it is then leave out the cdot
var bottom = this.join(md, vd, !isBracketed(vd) ? this.dot : '');
//format the power if it exists
//make it a fraction if both top and bottom exists
if(top && bottom) {
var frac = this.frac(top, bottom);
if(combine_power && tp) frac = this.brackets(frac)+tp;
return frac;
}
//otherwise only the top exists so return that
else return top;
},
merge: function(a, b) {
var r = [];
for(var i=0; i<2; i++) r[i] = a[i]+b[i];
return r;
},
//joins together two strings if both exist
join: function(n, d, glue) {
if(!n && !d) return '';
if(n && !d) return n;
if(d && !n) return d;
return n+glue+d;
},
formatP: function(p_array) {
for(var i=0; i<2; i++) {
var p = p_array[i];
if(p) p_array[i] = '^'+this.braces(p);
}
return p_array;
},
/**
* formats the fractions accordingly.
* @param {Frac} f
* @param {bool} make_1_blank - let's the function know to return blank for denominators == 1
*/
formatFrac: function(f, is_pow) {
var n = f.num.toString(),
d = f.den.toString();
//no need to have x^1
if(is_pow && n === '1' && d === '1') return '';
//no need to have x/1
if(d === '1') return n;
return this.frac(n, d);
},
frac: function(n, d) {
return '\\frac'+this.braces(n)+this.braces(d);
},
braces: function(e) {
return '{'+e+'}';
},
brackets: function(e, typ) {
typ = typ || 'parens';
var bracketTypes = {
parens: ['(', ')'],
square: ['[', ']'],
brace: ['{', '}'],
abs: ['|', '|'],
angle: ['\\langle', '\\rangle']
};
var bracket = bracketTypes[typ];
return '\\left'+bracket[0]+e+'\\right'+bracket[1];
}
};
function Vector(v) {
if(isVector(v)) this.elements = v.items.slice(0);
else if(isArray(v)) this.elements = v.slice(0);
else this.elements = [].slice.call(arguments);
}
Vector.arrayPrefill = function(n, val) {
var a = [];
val = val || 0;
for(var i=0; i<n; i++) a[i] = val;
return a;
};
Vector.fromArray = function(a) {
var v = new Vector();
v.elements = a;
return v;
};
//Ported from Sylvester.js
Vector.prototype = {
custom: true,
// Returns element i of the vector
e: function(i) {
return (i < 1 || i > this.elements.length) ? null : this.elements[i-1];
},
set: function(i, val) {
this.elements[i] = new Symbol(val);
},
// Returns the number of elements the vector has
dimensions: function() {
return this.elements.length;
},
// Returns the modulus ('length') of the vector
modulus: function() {
return block('SAFE', function() {
return _.pow((this.dot(this.clone())), new Symbol(0.5));
}, undefined, this);
},
// Returns true iff the vector is equal to the argument
eql: function(vector) {
var n = this.elements.length;
var V = vector.elements || vector;
if (n !== V.length) { return false; }
do {
if (Math.abs(_.subtract(this.elements[n-1],V[n-1]).valueOf()) > PRECISION) { return false; }
} while (--n);
return true;
},
// Returns a clone of the vector
clone: function() {
var V = new Vector(),
l = this.elements.length;
for(var i=0; i<l; i++) {
//Rule: all items within the vector must have a clone method.
V.elements.push(this.elements[i].clone());
}
return V;
},
// Maps the vector to another vector according to the given function
map: function(fn) {
var elements = [];
this.each(function(x, i) {
elements.push(fn(x, i));
});
return new Vector(elements);
},
// Calls the iterator for each element of the vector in turn
each: function(fn) {
var n = this.elements.length, k=n, i;
do {
i = k-n;
fn(this.elements[i], i+1);
} while (--n);
},
// Returns a new vector created by normalizing the receiver
toUnitVector: function() {
return block('SAFE', function() {
var r = this.modulus();
if (r.valueOf() === 0) { return this.clone(); }
return this.map(function(x) { return _.divide(x, r); });
}, undefined, this);
},
// Returns the angle between the vector and the argument (also a vector)
angleFrom: function(vector) {
return block('SAFE', function() {
var V = vector.elements || vector;
var n = this.elements.length;
if (n !== V.length) { return null; }
var dot = new Symbol(0), mod1 = new Symbol(0), mod2 = new Symbol(0);
// Work things out in parallel to save time
this.each(function(x, i) {
dot = _.add(dot, _.multiply(x, V[i-1]));
mod1 = _.add(mod1, _.multiply(x, x));//will not conflict in safe block
mod2 = _.add(mod2, _.multiply(V[i-1], V[i-1]));//will not conflict in safe block
});
mod1 = _.pow(mod1, new Symbol(0.5)); mod2 = _.pow(mod2, new Symbol(0.5));
var product = _.multiply(mod1,mod2);
if(product.valueOf() === 0) { return null; }
var theta = _.divide(dot, product);
var theta_val = theta.valueOf();
if(theta_val < -1) { theta = -1; }
if (theta_val > 1) { theta = 1; }
return new Symbol(Math.acos(theta));
}, undefined, this);
},
// Returns true iff the vector is parallel to the argument
isParallelTo: function(vector) {
var angle = this.angleFrom(vector).valueOf();
return (angle === null) ? null : (angle <= PRECISION);
},
// Returns true iff the vector is antiparallel to the argument
isAntiparallelTo: function(vector) {
var angle = this.angleFrom(vector).valueOf();
return (angle === null) ? null : (Math.abs(angle - Math.PI) <= Sylvester.precision);
},
// Returns true iff the vector is perpendicular to the argument
isPerpendicularTo: function(vector) {
var dot = this.dot(vector);
return (dot === null) ? null : (Math.abs(dot) <= Sylvester.precision);
},
// Returns the result of adding the argument to the vector
add: function(vector) {
return block('SAFE', function(){
var V = vector.elements || vector;
if (this.elements.length !== V.length) { return null; }
return this.map(function(x, i) { return _.add(x, V[i-1]); });
}, undefined, this);
},
// Returns the result of subtracting the argument from the vector
subtract: function(vector) {
return block('SAFE', function(){
var V = vector.elements || vector;
if (this.elements.length !== V.length) { return null; }
return this.map(function(x, i) { return _.subtract(x, V[i-1]); });
}, undefined, this);
},
// Returns the result of multiplying the elements of the vector by the argument
multiply: function(k) {
return this.map(function(x) { return x.clone()*k.clone(); });
},
x: function(k) { return this.multiply(k); },
// Returns the scalar product of the vector with the argument
// Both vectors must have equal dimensionality
dot: function(vector) {
return block('SAFE', function() {
var V = vector.elements || vector;
var product = new Symbol(0), n = this.elements.length;
if (n !== V.length) { return null; }
do { product = _.add(product, _.multiply(this.elements[n-1], V[n-1])); } while (--n);
return product;
}, undefined, this);
},
// Returns the vector product of the vector with the argument
// Both vectors must have dimensionality 3
cross: function(vector) {
var B = vector.elements || vector;
if(this.elements.length !== 3 || B.length !== 3) { return null; }
var A = this.elements;
return block('SAFE', function() {
return new Vector([
_.subtract(_.multiply(A[1], B[2]), _.multiply(A[2], B[1])),
_.subtract(_.multiply(A[2], B[0]), _.multiply(A[0], B[2])),
_.subtract(_.multiply(A[0], B[1]), _.multiply(A[1], B[0]))
]);
}, undefined, this);
},
// Returns the (absolute) largest element of the vector
max: function() {
var m = 0, n = this.elements.length, k = n, i;
do { i = k - n;
if(Math.abs(this.elements[i].valueOf()) > Math.abs(m.valueOf())) { m = this.elements[i]; }
} while (--n);
return m;
},
// Returns the index of the first match found
indexOf: function(x) {
var index = null, n = this.elements.length, k = n, i;
do {
i = k-n;
if(index === null && this.elements[i].valueOf() === x.valueOf()) {
index = i+1;
}
} while (--n);
return index;
},
text: function(x) {
return text(this);
},
toString: function() {
return this.text();
},
latex: function(option) {
var tex = [];
for(var el in this.elements) {
tex.push(LaTeX.latex.call(LaTeX, this.elements[el], option));
}
return '['+tex.join(', ')+']';
}
};
function Matrix() {
var m = arguments,
l = m.length, i, el = [];
if(isMatrix(m)) { //if it's a matrix then make a clone
for(i=0; i<l; i++) {
el.push(m[i].slice(0));
}
}
else {
var row, lw, rl;
for(i=0; i<l; i++) {
row = m[i];
if(isVector(row)) row = row.elements;
if(!isArray(row)) row = [row];
rl = row.length;
if(lw && lw !== rl) err('Unable to create Matrix. Row dimensions do not match!');
el.push(row);
lw = rl;
}
}
this.elements = el;
}
Matrix.identity = function(n) {
var m = new Matrix();
for(var i=0; i<n; i++) {
m.elements.push([]);
for(var j=0; j<n; j++) {
m.set(i, j, i === j ? new Symbol(1) : new Symbol(0));
}
}
return m;
};
Matrix.fromArray = function(arr) {
function F(args) {
return Matrix.apply(this, args);
}
F.prototype = Matrix.prototype;
return new F(arr);
};
Matrix.zeroMatrix = function(rows, cols) {
var m = new Matrix();
for(var i=0; i<rows; i++) {
m.elements.push(Vector.arrayPrefill(cols, new Symbol(0)));
}
return m;
};
Matrix.prototype = {
//needs be true to let the parser know not to try to cast it to a symbol
custom: true,
get: function(row, column) {
if(!this.elements[row])
return undefined;
return this.elements[row][column];
},
set: function(row, column, value) {
if(!this.elements[row])
this.elements[row] = [];
this.elements[row][column] = isSymbol(value) ? value : new Symbol(value);
},
cols: function() {
return this.elements[0].length;
},
rows: function() {
return this.elements.length;
},
row: function(n) {
if(!n || n > this.cols()) return [];
return this.elements[n-1];
},
col: function(n) {
var nr = this.rows(),
col = [];
if(n > this.cols() || !n) return col;
for(var i=0; i<nr; i++) {
col.push(this.elements[i][n-1]);
}
return col;
},
eachElement: function(fn) {
var nr = this.rows(),
nc = this.cols(), i, j;
for(i=0; i<nr; i++) {
for(j=0; j<nc; j++) {
this.elements[i][j] = fn.call(this, this.elements[i][j], i, j);
}
}
},
//ported from Sylvester.js
determinant: function() {
if (!this.isSquare()) { return null; }
var M = this.toRightTriangular();
var det = M.elements[0][0], n = M.elements.length-1, k = n, i;
do {
i = k-n+1;
det = _.multiply(det,M.elements[i][i]);
} while (--n);
return det;
},
isSquare: function() {
return this.elements.length === this.elements[0].length;
},
isSingular: function() {
return this.isSquare() && this.determinant() === 0;
},
augment: function(m) {
var r = this.rows(), rr = m.rows();
if(r !== rr) err("Cannot augment matrix. Rows don't match.");
for(var i=0; i<r; i++) {
this.elements[i] = this.elements[i].concat(m.elements[i]);
}
return this;
},
clone: function() {
var r = this.rows(), c = this.cols(),
m = new Matrix();
for(var i=0; i<r; i++) {
m.elements[i] = [];
for(var j=0; j<c; j++) {
var symbol = this.elements[i][j];
m.elements[i][j] = isSymbol(symbol) ? symbol.clone() : symbol;
}
}
return m;
},
//ported from Sylvester.js
invert: function() {
if(!this.isSquare()) err('Matrix is not square!');
return block('SAFE', function() {
var ni = this.elements.length, ki = ni, i, j;
var imatrix = Matrix.identity(ni);
var M = this.augment(imatrix).toRightTriangular();
var np, kp = M.elements[0].length, p, els, divisor;
var inverse_elements = [], new_element;
// Matrix is non-singular so there will be no zeros on the diagonal
// Cycle through rows from last to first
do {
i = ni-1;
// First, normalise diagonal elements to 1
els = []; np = kp;
inverse_elements[i] = [];
divisor = M.elements[i][i];
do {
p = kp - np;
new_element = _.divide(M.elements[i][p], divisor.clone());
els.push(new_element);
// Shuffle of the current row of the right hand side into the results
// array as it will not be modified by later runs through this loop
if (p >= ki) { inverse_elements[i].push(new_element); }
} while (--np);
M.elements[i] = els;
// Then, subtract this row from those above it to
// give the identity matrix on the left hand side
for (j=0; j<i; j++) {
els = []; np = kp;
do { p = kp - np;
els.push(_.subtract(M.elements[j][p].clone(),_.multiply(M.elements[i][p].clone(), M.elements[j][i].clone())));
} while (--np);
M.elements[j] = els;
}
} while (--ni);
return Matrix.fromArray(inverse_elements);
}, undefined, this);
},
//ported from Sylvester.js
toRightTriangular: function() {
return block('SAFE', function(){
var M = this.clone(), els, fel, nel,
n = this.elements.length, k = n, i, np, kp = this.elements[0].length, p;
do {
i = k-n;
fel = M.elements[i][i];
if(fel.valueOf() === 0) {
for(var j=i+1; j<k; j++) {
nel = M.elements[j][i];
if (nel && nel.valueOf() !== 0) {
els = []; np = kp;
do {
p = kp-np;
els.push(_.add(M.elements[i][p].clone(), M.elements[j][p].clone()));
} while (--np);
M.elements[i] = els;
break;
}
}
}
var fel = M.elements[i][i];
if(fel.valueOf() !== 0) {
for (j=i+1; j<k; j++) {
var multiplier = _.divide(M.elements[j][i].clone(),M.elements[i][i].clone());
els = []; np = kp;
do { p = kp - np;
// Elements with column numbers up to an including the number
// of the row that we're subtracting can safely be set straight to
// zero, since that's the point of this routine and it avoids having
// to loop over and correct rounding errors later
els.push(p <= i ? new Symbol(0) :
_.subtract(M.elements[j][p].clone(), _.multiply(M.elements[i][p].clone(), multiplier.clone())));
} while (--np);
M.elements[j] = els;
}
}
} while (--n);
return M;
}, undefined, this);
},
transpose: function() {
var rows = this.elements.length, cols = this.elements[0].length;
var M = new Matrix(), ni = cols, i, nj, j;
do {
i = cols - ni;
M.elements[i] = [];
nj = rows;
do { j = rows - nj;
M.elements[i][j] = this.elements[j][i].clone();
} while (--nj);
} while (--ni);
return M;
},
// Returns true if the matrix can multiply the argument from the left
canMultiplyFromLeft: function(matrix) {
var l = isMatrix(matrix) ? matrix.elements.length : matrix.length;
// this.columns should equal matrix.rows
return (this.elements[0].length === l);
},
sameSize: function(matrix) {
return this.rows() === matrix.rows() && this.cols() === matrix.cols();
},
multiply: function(matrix) {
return block('SAFE', function(){
var M = matrix.elements || matrix;
if (!this.canMultiplyFromLeft(M)) {
if(this.sameSize(matrix)) {
var MM = new Matrix();
var rows = this.rows();
for(var i=0; i<rows; i++) {
var e = _.multiply(new Vector(this.elements[i]), new Vector(matrix.elements[i]));
MM.elements[i] = e.elements;
}
return MM;
}
return null;
}
var ni = this.elements.length, ki = ni, i, nj, kj = M[0].length, j;
var cols = this.elements[0].length, elements = [], sum, nc, c;
do {
i = ki-ni;
elements[i] = [];
nj = kj;
do {
j = kj - nj;
sum = new Symbol(0);
nc = cols;
do {
c = cols-nc;
sum = _.add(sum, _.multiply(this.elements[i][c], M[c][j])) ;
} while (--nc);
elements[i][j] = sum;
} while (--nj);
} while (--ni);
return Matrix.fromArray(elements);
}, undefined, this);
},
add: function(matrix) {
var M = new Matrix();
if(this.sameSize(matrix)) {
this.eachElement(function(e, i, j) {
M.set(i, j, _.add(e.clone(), matrix.elements[i][j]));
});
}
return M;
},
subtract: function(matrix) {
var M = new Matrix();
if(this.sameSize(matrix)) {
this.eachElement(function(e, i, j) {
M.set(i, j, _.subtract(e.clone(), matrix.elements[i][j]));
});
}
return M;
},
negate: function() {
this.each(function(e) {
return e.negate();
});
return this;
},
toVector: function() {
if(this.rows () === 1 || this.cols() === 1) {
var v = new Vector();
v.elements = this.elements;
return v;
}
return this;
},
toString: function(newline) {
var l = this.rows(),
s = [];
newline = newline === undefined ? '\n' : newline;
for(var i=0; i<l; i++) {
s.push('['+this.elements[i].map(function(x) {
return x !== undefined ? x.toString() : '';
}).join(',')+']');
}
return 'matrix'+inBrackets(s.join(','));
},
text: function() {
return 'matrix('+this.toString('')+')';
},
latex: function(option) {
var cols = this.cols(), elements = this.elements;
return format('\\begin{vmatrix}{0}\\end{vmatrix}', function() {
var tex = [];
for(var row in elements) {
var row_tex = [];
for(var i=0; i<cols; i++) {
row_tex.push(LaTeX.latex.call(LaTeX, elements[row][i], option));
}
tex.push(row_tex.join(' & '));
}
return tex.join(' \\cr ');
});
}
};
//aliases
Matrix.prototype.each = Matrix.prototype.eachElement;
/* END CLASSES */
/* FINALIZE */
var finalize = function() {
reserveNames(_.constants);
reserveNames(_.functions);
generatePrimes(Settings.init_primes);//generate the firs 100 primes
};
var build = Utils.build = function(symbol, arg_array) {
symbol = block('PARSE2NUMBER', function() {
return _.parse(symbol);
}, true);
var args = variables(symbol);
var supplements = [];
var ftext = function(symbol, xports) {
xports = xports || [];
var c = [],
group = symbol.group,
prefix = '';
var ftext_complex = function(group) {
var d = group === CB ? '*' : '+',
cc = [];
for(var x in symbol.symbols) {
var sym = symbol.symbols[x],
ft = ftext(sym, xports)[0];
//wrap it in brackets if it's group PL or CP
if(sym.isComposite())
ft = inBrackets(ft);
cc.push(ft);
}
var retval = cc.join(d);
retval = retval && !symbol.multiplier.equals(1) ? inBrackets(retval) : retval;
return retval;
},
ftext_function = function(bn) {
var retval;
if(bn in Math) retval = 'Math.'+bn;
else {
if(supplements.indexOf(bn) === -1) { //make sure you're not adding the function twice
//Math2 functions aren't part of the standard javascript
//Math library and must be exported.
xports.push('var '+bn+' = '+ Math2[bn].toString()+'; ');
supplements.push(bn);
}
retval = bn;
}
retval = retval+inBrackets(symbol.args.map(function(x) {
return ftext(x, xports)[0];
}).join(','));
return retval;
};
//the multiplier
if(group === N)
c.push(symbol.multiplier.toDecimal());
else if(symbol.multiplier.equals(-1))
prefix = '-';
else if(!symbol.multiplier.equals(1))
c.push(symbol.multiplier.toDecimal());
//the value
var value;
if(group === S || group === P) value = symbol.value;
else if(group === FN) {
value = ftext_function(symbol.fname);
}
else if(group === EX) {
var pg = symbol.previousGroup;
if(pg === N || pg === S) value = symbol.value;
else if(pg === FN) value = ftext_function(symbol.fname);
else value = ftext_complex(symbol.previousGroup);
}
else {
value = ftext_complex(symbol.group);
}
if(symbol.group !== N && !symbol.power.equals(1)) {
var pow = ftext(_.parse(symbol.power));
xports.push(pow[1]);
value = 'Math.pow'+inBrackets(value+','+pow[0]);
}
if(value) c.push(prefix+value);
return [c.join('*'), xports.join('').replace(/\n+\s+/g, ' ')];
};
if(arg_array) {
for(var i=0; i<args.length; i++) {
var arg = args[i];
if(arg_array.indexOf(arg) === -1) err(arg+' not found in argument array');
}
args = arg_array;
}
var f_array = ftext(symbol);
return new Function(args, f_array[1]+' return '+f_array[0]+';');
};
finalize(); //final preparations
/* END FINALIZE */
/* BUILD CORE */
//This contains all the parts of nerdamer and enables nerdamer's internal functions
//to be used.
var C = {};
C.exceptions = {};
C.Operator = Operator;
C.groups = Groups;
C.Symbol = Symbol;
C.Expression = Expression;
C.Frac = Frac;
C.Vector = Vector;
C.Matrix = Matrix;
C.Parser = Parser;
C.Fraction = Fraction;
C.Math2 = Math2;
C.LaTeX = LaTeX;
C.Utils = Utils;
C.PRIMES = PRIMES;
C.PARSER = _;
C.PARENTHESIS = PARENTHESIS;
C.Settings = Settings;
C.VARS = VARS;
C.err = err;
C.bigInt = bigInt;
//load the exceptions
C.exceptions.DivisionByZero = DivisionByZero;
C.exceptions.ParseError = ParseError;
//TODO: fix
if(!_.error)
_.error = err;
/* END BUILD CORE */
/* EXPORTS */
/**
*
* @param {String} expression the expression to be evaluated
* @param {Object} subs the object containing the variable values
* @param {Integer} location a specific location in the equation list to
* insert the evaluated expression
* @param {String} option additional options
* @returns {Expression}
*/
var libExports = function(expression, subs, option, location) {
var variable, fn, args;
//convert any expression passed in to a string
if(expression instanceof Expression) expression = expression.toString();
var multi_options = isArray(option),
expand = 'expand',
numer = multi_options ? option.indexOf('numer') !== -1 : option === 'numer';
if((multi_options ? option.indexOf(expand) !== -1 : option === expand)) {
expression = format('{0}({1})', expand, expression);
}
var e = block('PARSE2NUMBER', function(){
return _.parse(expression, subs);
}, numer || Settings.PARSE2NUMBER);
if(location) { EXPRESSIONS[location-1] = e; }
else { EXPRESSIONS.push(e);}
if(variable) libExports.setVar(variable, e);
if(fn) libExports.setFunction(fn, args, e);
return new Expression(e);
};
libExports.rpn = function(expression) {
return _.parse(expression, null, true);
};
libExports.convertToLaTeX = function(e) {
return _.toTeX(e);
};
/**
* Get the version of nerdamer or a loaded add-on
* @param {String} add_on - The add-on being checked
* @returns {String} returns the version of nerdamer
*/
libExports.version = function(add_on) {
if(add_on) {
try {
return C[add_on].version;
}
catch(e) {
return "No module named "+add_on+" found!";
}
}
return version;
};
/**
* Get nerdamer generated warnings
* @returns {String}
*/
libExports.getWarnings = function() {
return WARNINGS;
};
/**
*
* @param {String} constant The name of the constant to be set
* @param {mixed} value The value of the constant
* @returns {Object} Returns the nerdamer object
*/
libExports.setConstant = function(constant, value) {
validateName(constant);
if(!isReserved(constant)) {
if(value === 'delete') {
delete _.constants[constant];
}
else {
if(isNaN(value)) throw new Error('Constant must be a number!');
_.constants[constant] = value;
}
}
return this;
};
/**
*
* @param {String} name The name of the function
* @param {Array} params_array A list containing the parameter name of the functions
* @param {String} body The body of the function
* @returns {Boolean} returns true if succeeded and falls on fail
* @example nerdamer.setFunction('f',['x'], 'x^2+2');
*/
libExports.setFunction = function(name, params_array, body) {
validateName(name);
if(!isReserved(name)) {
params_array = params_array || variables(_.parse(body));
_.functions[name] = [_.mapped_function, params_array.length, {
name: name,
params: params_array,
body: body
}];
return true;
}
return false;
};
/**
*
* @returns {C} Exports the nerdamer core functions and objects
*/
libExports.getCore = function() {
return C;
};
libExports.getExpression = libExports.getEquation = Expression.getExpression;
/**
*
* @param {Boolean} asArray The returned names are returned as an array if this is set to true;
* @returns {String|Array}
*/
libExports.reserved = function(asArray) {
if(asArray){ return RESERVED; }
return RESERVED.join(', ');
};
/**
*
* @param {Integer} equation_number the number of the equation to clear.
* If 'all' is supplied then all equations are cleared
* @param {Boolean} keep_EXPRESSIONS_fixed use true if you don't want to keep EXPRESSIONS length fixed
* @returns {Object} Returns the nerdamer object
*/
libExports.clear = function( equation_number, keep_EXPRESSIONS_fixed ) {
if(equation_number === 'all') { EXPRESSIONS = []; }
else if(equation_number === 'last') { EXPRESSIONS.pop(); }
else if(equation_number === 'first') { EXPRESSIONS.shift(); }
else {
var index = !equation_number ? EXPRESSIONS.length : equation_number-1;
keep_EXPRESSIONS_fixed === true ? EXPRESSIONS[index] = undefined : remove(EXPRESSIONS, index);
}
return this;
};
/**
* Alias for nerdamer.clear('all')
*/
libExports.flush = function() {
this.clear('all');
return this;
};
/**
*
* @param {Boolean} asObject
* @param {Boolean} asLaTeX
* @returns {Array}
*/
libExports.expressions = function( asObject, asLaTeX, option ) {
var result = asObject ? {} : [];
for(var i=0; i<EXPRESSIONS.length; i++) {
var eq = asLaTeX ? LaTeX.latex(EXPRESSIONS[i], option) : text(EXPRESSIONS[i], option);
asObject ? result[i+1] = eq : result.push(eq);
}
return result;
};
//the method for registering modules
libExports.register = function(obj) {
var core = this.getCore();
if(isArray(obj)) {
for(var i=0; i<obj.length; i++) {
if(obj) this.register(obj[i]);
}
}
else if(obj && Settings.exclude.indexOf(obj.name) === -1) {
//make sure all the dependencies are available
if(obj.dependencies) {
for(var i=0; i<obj.dependencies.length; i++)
if(!core[obj.dependencies[i]])
throw new Error(format('{0} requires {1} to be loaded!', obj.name, obj.dependencies[i]));
}
//if no parent object is provided then the function does not have an address and cannot be called directly
var parent_obj = obj.parent,
fn = obj.build.call(core); //call constructor to get function
if(parent_obj) {
if(!core[parent_obj]) core[obj.parent] = {};
var ref_obj = parent_obj === 'nerdamer' ? this : core[parent_obj];
//attach the function to the core
ref_obj[obj.name] = fn;
}
if(obj.visible) _.functions[obj.name] = [fn, obj.numargs]; //make the function available
}
};
/**
* @param {String} name variable name
* @returns {boolean} validates if the profided string is a valid variable name
*/
libExports.validateName = validateName;
/**
* @param {String} varname variable name
* @returns {boolean} validates if the profided string is a valid variable name
*/
libExports.validVarName = function(varname) {
try {
validateName(varname);
return RESERVED.indexOf(varname) === -1;
}
catch(e){ return false; }
};
/**
*
* @returns {Array} Array of functions currently supported by nerdamer
*/
libExports.supported = function() {
return keys(_.functions);
};
/**
*
* @returns {Number} The number equations/expressions currently loaded
*/
libExports.numEquations = libExports.numExpressions = function() {
return EXPRESSIONS.length;
};
/* END EXPORTS */
/**
*
* @param {String} v variable to be set
* @param {String} val value of variable. This can be a variable expression or number
* @returns {Object} Returns the nerdamer object
*/
libExports.setVar = function(v, val) {
validateName(v);
//check if it's not already a constant
if(v in _.constants)
err('Cannot set value for constant '+v);
if(val === 'delete') delete VARS[v];
else {
VARS[v] = isSymbol(val) ? val : _.parse(val);
}
return this;
};
/**
* Clear the variables from the VARS object
* @returns {Object} Returns the nerdamer object
*/
libExports.clearVars = function() {
VARS = {};
return this;
};
/**
*
* @param {Function} loader
* @returns {nerdamer}
*/
libExports.load = function(loader) {
loader.call(this);
return this;
};
/**
* @param {String} Output format. Can be 'object' (just returns the VARS object), 'text' or 'latex'. Default: 'text'
* @returns {Object} Returns an object with the variables
*/
libExports.getVars = function(output, option) {
output = output || 'text';
var variables = {};
if (output === 'object') variables = VARS;
else {
for (var v in VARS) {
if (output === 'latex') {
variables[v] = VARS[v].latex(option);
} else if (output === 'text') {
variables[v] = VARS[v].text(option);
}
}
}
return variables;
};
/**
*
* @param {String} setting The setting to be changed
* @param {boolean} value
*/
libExports.set = function(setting, value) {
//current options:
//PARSE2NUMBER, suppress_errors
var disallowed = ['SAFE'];
if(disallowed.indexOf(setting) !== -1) err('Cannot modify setting: '+setting);
Settings[setting] = value;
};
/**
* This functions makes internal functions available externally
* @param {bool} override Override the functions when calling api if it exists
*/
libExports.api = function(override) {
//Map internal functions to external ones
var linker = function(fname) {
return function() {
var args = [].slice.call(arguments);
for(var i=0; i<args.length; i++)
args[i] = _.parse(args[i]);
return new Expression(block('PARSE2NUMBER', function() {
return _.callfunction(fname, args);
}));
};
};
//perform the mapping
for(var x in _.functions)
if(!(x in libExports) || override)
libExports[x] = linker(x);
};
libExports.convert = function(e) {
var raw = _.parse(e, null, true);
return raw;
};
libExports.api();
return libExports; //Done
})({
//https://github.com/peterolson/BigInteger.js
bigInt: (function (undefined) {
"use strict";
var BASE = 1e7,
LOG_BASE = 7,
MAX_INT = 9007199254740992,
MAX_INT_ARR = smallToArray(MAX_INT),
LOG_MAX_INT = Math.log(MAX_INT);
function BigInteger(value, sign) {
this.value = value;
this.sign = sign;
this.isSmall = false;
}
function SmallInteger(value) {
this.value = value;
this.sign = value < 0;
this.isSmall = true;
}
function isPrecise(n) {
return -MAX_INT < n && n < MAX_INT;
}
function smallToArray(n) { // For performance reasons doesn't reference BASE, need to change this function if BASE changes
if (n < 1e7)
return [n];
if (n < 1e14)
return [n % 1e7, Math.floor(n / 1e7)];
return [n % 1e7, Math.floor(n / 1e7) % 1e7, Math.floor(n / 1e14)];
}
function arrayToSmall(arr) { // If BASE changes this function may need to change
trim(arr);
var length = arr.length;
if (length < 4 && compareAbs(arr, MAX_INT_ARR) < 0) {
switch (length) {
case 0: return 0;
case 1: return arr[0];
case 2: return arr[0] + arr[1] * BASE;
default: return arr[0] + (arr[1] + arr[2] * BASE) * BASE;
}
}
return arr;
}
function trim(v) {
var i = v.length;
while (v[--i] === 0);
v.length = i + 1;
}
function createArray(length) { // function shamelessly stolen from Yaffle's library https://github.com/Yaffle/BigInteger
var x = new Array(length);
var i = -1;
while (++i < length) {
x[i] = 0;
}
return x;
}
function truncate(n) {
if (n > 0) return Math.floor(n);
return Math.ceil(n);
}
function add(a, b) { // assumes a and b are arrays with a.length >= b.length
var l_a = a.length,
l_b = b.length,
r = new Array(l_a),
carry = 0,
base = BASE,
sum, i;
for (i = 0; i < l_b; i++) {
sum = a[i] + b[i] + carry;
carry = sum >= base ? 1 : 0;
r[i] = sum - carry * base;
}
while (i < l_a) {
sum = a[i] + carry;
carry = sum === base ? 1 : 0;
r[i++] = sum - carry * base;
}
if (carry > 0) r.push(carry);
return r;
}
function addAny(a, b) {
if (a.length >= b.length) return add(a, b);
return add(b, a);
}
function addSmall(a, carry) { // assumes a is array, carry is number with 0 <= carry < MAX_INT
var l = a.length,
r = new Array(l),
base = BASE,
sum, i;
for (i = 0; i < l; i++) {
sum = a[i] - base + carry;
carry = Math.floor(sum / base);
r[i] = sum - carry * base;
carry += 1;
}
while (carry > 0) {
r[i++] = carry % base;
carry = Math.floor(carry / base);
}
return r;
}
BigInteger.prototype.add = function (v) {
var value, n = parseValue(v);
if (this.sign !== n.sign) {
return this.subtract(n.negate());
}
var a = this.value, b = n.value;
if (n.isSmall) {
return new BigInteger(addSmall(a, Math.abs(b)), this.sign);
}
return new BigInteger(addAny(a, b), this.sign);
};
BigInteger.prototype.plus = BigInteger.prototype.add;
SmallInteger.prototype.add = function (v) {
var n = parseValue(v);
var a = this.value;
if (a < 0 !== n.sign) {
return this.subtract(n.negate());
}
var b = n.value;
if (n.isSmall) {
if (isPrecise(a + b)) return new SmallInteger(a + b);
b = smallToArray(Math.abs(b));
}
return new BigInteger(addSmall(b, Math.abs(a)), a < 0);
};
SmallInteger.prototype.plus = SmallInteger.prototype.add;
function subtract(a, b) { // assumes a and b are arrays with a >= b
var a_l = a.length,
b_l = b.length,
r = new Array(a_l),
borrow = 0,
base = BASE,
i, difference;
for (i = 0; i < b_l; i++) {
difference = a[i] - borrow - b[i];
if (difference < 0) {
difference += base;
borrow = 1;
} else borrow = 0;
r[i] = difference;
}
for (i = b_l; i < a_l; i++) {
difference = a[i] - borrow;
if (difference < 0) difference += base;
else {
r[i++] = difference;
break;
}
r[i] = difference;
}
for (; i < a_l; i++) {
r[i] = a[i];
}
trim(r);
return r;
}
function subtractAny(a, b, sign) {
var value;
if (compareAbs(a, b) >= 0) {
value = subtract(a,b);
} else {
value = subtract(b, a);
sign = !sign;
}
value = arrayToSmall(value);
if (typeof value === "number") {
if (sign) value = -value;
return new SmallInteger(value);
}
return new BigInteger(value, sign);
}
function subtractSmall(a, b, sign) { // assumes a is array, b is number with 0 <= b < MAX_INT
var l = a.length,
r = new Array(l),
carry = -b,
base = BASE,
i, difference;
for (i = 0; i < l; i++) {
difference = a[i] + carry;
carry = Math.floor(difference / base);
difference %= base;
r[i] = difference < 0 ? difference + base : difference;
}
r = arrayToSmall(r);
if (typeof r === "number") {
if (sign) r = -r;
return new SmallInteger(r);
} return new BigInteger(r, sign);
}
BigInteger.prototype.subtract = function (v) {
var n = parseValue(v);
if (this.sign !== n.sign) {
return this.add(n.negate());
}
var a = this.value, b = n.value;
if (n.isSmall)
return subtractSmall(a, Math.abs(b), this.sign);
return subtractAny(a, b, this.sign);
};
BigInteger.prototype.minus = BigInteger.prototype.subtract;
SmallInteger.prototype.subtract = function (v) {
var n = parseValue(v);
var a = this.value;
if (a < 0 !== n.sign) {
return this.add(n.negate());
}
var b = n.value;
if (n.isSmall) {
return new SmallInteger(a - b);
}
return subtractSmall(b, Math.abs(a), a >= 0);
};
SmallInteger.prototype.minus = SmallInteger.prototype.subtract;
BigInteger.prototype.negate = function () {
return new BigInteger(this.value, !this.sign);
};
SmallInteger.prototype.negate = function () {
var sign = this.sign;
var small = new SmallInteger(-this.value);
small.sign = !sign;
return small;
};
BigInteger.prototype.abs = function () {
return new BigInteger(this.value, false);
};
SmallInteger.prototype.abs = function () {
return new SmallInteger(Math.abs(this.value));
};
function multiplyLong(a, b) {
var a_l = a.length,
b_l = b.length,
l = a_l + b_l,
r = createArray(l),
base = BASE,
product, carry, i, a_i, b_j;
for (i = 0; i < a_l; ++i) {
a_i = a[i];
for (var j = 0; j < b_l; ++j) {
b_j = b[j];
product = a_i * b_j + r[i + j];
carry = Math.floor(product / base);
r[i + j] = product - carry * base;
r[i + j + 1] += carry;
}
}
trim(r);
return r;
}
function multiplySmall(a, b) { // assumes a is array, b is number with |b| < BASE
var l = a.length,
r = new Array(l),
base = BASE,
carry = 0,
product, i;
for (i = 0; i < l; i++) {
product = a[i] * b + carry;
carry = Math.floor(product / base);
r[i] = product - carry * base;
}
while (carry > 0) {
r[i++] = carry % base;
carry = Math.floor(carry / base);
}
return r;
}
function shiftLeft(x, n) {
var r = [];
while (n-- > 0) r.push(0);
return r.concat(x);
}
function multiplyKaratsuba(x, y) {
var n = Math.max(x.length, y.length);
if (n <= 30) return multiplyLong(x, y);
n = Math.ceil(n / 2);
var b = x.slice(n),
a = x.slice(0, n),
d = y.slice(n),
c = y.slice(0, n);
var ac = multiplyKaratsuba(a, c),
bd = multiplyKaratsuba(b, d),
abcd = multiplyKaratsuba(addAny(a, b), addAny(c, d));
var product = addAny(addAny(ac, shiftLeft(subtract(subtract(abcd, ac), bd), n)), shiftLeft(bd, 2 * n));
trim(product);
return product;
}
// The following function is derived from a surface fit of a graph plotting the performance difference
// between long multiplication and karatsuba multiplication versus the lengths of the two arrays.
function useKaratsuba(l1, l2) {
return -0.012 * l1 - 0.012 * l2 + 0.000015 * l1 * l2 > 0;
}
BigInteger.prototype.multiply = function (v) {
var value, n = parseValue(v),
a = this.value, b = n.value,
sign = this.sign !== n.sign,
abs;
if (n.isSmall) {
if (b === 0) return CACHE[0];
if (b === 1) return this;
if (b === -1) return this.negate();
abs = Math.abs(b);
if (abs < BASE) {
return new BigInteger(multiplySmall(a, abs), sign);
}
b = smallToArray(abs);
}
if (useKaratsuba(a.length, b.length)) // Karatsuba is only faster for certain array sizes
return new BigInteger(multiplyKaratsuba(a, b), sign);
return new BigInteger(multiplyLong(a, b), sign);
};
BigInteger.prototype.times = BigInteger.prototype.multiply;
function multiplySmallAndArray(a, b, sign) { // a >= 0
if (a < BASE) {
return new BigInteger(multiplySmall(b, a), sign);
}
return new BigInteger(multiplyLong(b, smallToArray(a)), sign);
}
SmallInteger.prototype["_multiplyBySmall"] = function (a) {
if (isPrecise(a.value * this.value)) {
return new SmallInteger(a.value * this.value);
}
return multiplySmallAndArray(Math.abs(a.value), smallToArray(Math.abs(this.value)), this.sign !== a.sign);
};
BigInteger.prototype["_multiplyBySmall"] = function (a) {
if (a.value === 0) return CACHE[0];
if (a.value === 1) return this;
if (a.value === -1) return this.negate();
return multiplySmallAndArray(Math.abs(a.value), this.value, this.sign !== a.sign);
};
SmallInteger.prototype.multiply = function (v) {
return parseValue(v)["_multiplyBySmall"](this);
};
SmallInteger.prototype.times = SmallInteger.prototype.multiply;
function square(a) {
var l = a.length,
r = createArray(l + l),
base = BASE,
product, carry, i, a_i, a_j;
for (i = 0; i < l; i++) {
a_i = a[i];
for (var j = 0; j < l; j++) {
a_j = a[j];
product = a_i * a_j + r[i + j];
carry = Math.floor(product / base);
r[i + j] = product - carry * base;
r[i + j + 1] += carry;
}
}
trim(r);
return r;
}
BigInteger.prototype.square = function () {
return new BigInteger(square(this.value), false);
};
SmallInteger.prototype.square = function () {
var value = this.value * this.value;
if (isPrecise(value)) return new SmallInteger(value);
return new BigInteger(square(smallToArray(Math.abs(this.value))), false);
};
function divMod1(a, b) { // Left over from previous version. Performs faster than divMod2 on smaller input sizes.
var a_l = a.length,
b_l = b.length,
base = BASE,
result = createArray(b.length),
divisorMostSignificantDigit = b[b_l - 1],
// normalization
lambda = Math.ceil(base / (2 * divisorMostSignificantDigit)),
remainder = multiplySmall(a, lambda),
divisor = multiplySmall(b, lambda),
quotientDigit, shift, carry, borrow, i, l, q;
if (remainder.length <= a_l) remainder.push(0);
divisor.push(0);
divisorMostSignificantDigit = divisor[b_l - 1];
for (shift = a_l - b_l; shift >= 0; shift--) {
quotientDigit = base - 1;
if (remainder[shift + b_l] !== divisorMostSignificantDigit) {
quotientDigit = Math.floor((remainder[shift + b_l] * base + remainder[shift + b_l - 1]) / divisorMostSignificantDigit);
}
// quotientDigit <= base - 1
carry = 0;
borrow = 0;
l = divisor.length;
for (i = 0; i < l; i++) {
carry += quotientDigit * divisor[i];
q = Math.floor(carry / base);
borrow += remainder[shift + i] - (carry - q * base);
carry = q;
if (borrow < 0) {
remainder[shift + i] = borrow + base;
borrow = -1;
} else {
remainder[shift + i] = borrow;
borrow = 0;
}
}
while (borrow !== 0) {
quotientDigit -= 1;
carry = 0;
for (i = 0; i < l; i++) {
carry += remainder[shift + i] - base + divisor[i];
if (carry < 0) {
remainder[shift + i] = carry + base;
carry = 0;
} else {
remainder[shift + i] = carry;
carry = 1;
}
}
borrow += carry;
}
result[shift] = quotientDigit;
}
// denormalization
remainder = divModSmall(remainder, lambda)[0];
return [arrayToSmall(result), arrayToSmall(remainder)];
}
function divMod2(a, b) { // Implementation idea shamelessly stolen from Silent Matt's library http://silentmatt.com/biginteger/
// Performs faster than divMod1 on larger input sizes.
var a_l = a.length,
b_l = b.length,
result = [],
part = [],
base = BASE,
guess, xlen, highx, highy, check;
while (a_l) {
part.unshift(a[--a_l]);
if (compareAbs(part, b) < 0) {
result.push(0);
continue;
}
xlen = part.length;
highx = part[xlen - 1] * base + part[xlen - 2];
highy = b[b_l - 1] * base + b[b_l - 2];
if (xlen > b_l) {
highx = (highx + 1) * base;
}
guess = Math.ceil(highx / highy);
do {
check = multiplySmall(b, guess);
if (compareAbs(check, part) <= 0) break;
guess--;
} while (guess);
result.push(guess);
part = subtract(part, check);
}
result.reverse();
return [arrayToSmall(result), arrayToSmall(part)];
}
function divModSmall(value, lambda) {
var length = value.length,
quotient = createArray(length),
base = BASE,
i, q, remainder, divisor;
remainder = 0;
for (i = length - 1; i >= 0; --i) {
divisor = remainder * base + value[i];
q = truncate(divisor / lambda);
remainder = divisor - q * lambda;
quotient[i] = q | 0;
}
return [quotient, remainder | 0];
}
function divModAny(self, v) {
var value, n = parseValue(v);
var a = self.value, b = n.value;
var quotient;
if (b === 0) throw new Error("Cannot divide by zero");
if (self.isSmall) {
if (n.isSmall) {
return [new SmallInteger(truncate(a / b)), new SmallInteger(a % b)];
}
return [CACHE[0], self];
}
if (n.isSmall) {
if (b === 1) return [self, CACHE[0]];
if (b == -1) return [self.negate(), CACHE[0]];
var abs = Math.abs(b);
if (abs < BASE) {
value = divModSmall(a, abs);
quotient = arrayToSmall(value[0]);
var remainder = value[1];
if (self.sign) remainder = -remainder;
if (typeof quotient === "number") {
if (self.sign !== n.sign) quotient = -quotient;
return [new SmallInteger(quotient), new SmallInteger(remainder)];
}
return [new BigInteger(quotient, self.sign !== n.sign), new SmallInteger(remainder)];
}
b = smallToArray(abs);
}
var comparison = compareAbs(a, b);
if (comparison === -1) return [CACHE[0], self];
if (comparison === 0) return [CACHE[self.sign === n.sign ? 1 : -1], CACHE[0]];
// divMod1 is faster on smaller input sizes
if (a.length + b.length <= 200)
value = divMod1(a, b);
else value = divMod2(a, b);
quotient = value[0];
var qSign = self.sign !== n.sign,
mod = value[1],
mSign = self.sign;
if (typeof quotient === "number") {
if (qSign) quotient = -quotient;
quotient = new SmallInteger(quotient);
} else quotient = new BigInteger(quotient, qSign);
if (typeof mod === "number") {
if (mSign) mod = -mod;
mod = new SmallInteger(mod);
} else mod = new BigInteger(mod, mSign);
return [quotient, mod];
}
BigInteger.prototype.divmod = function (v) {
var result = divModAny(this, v);
return {
quotient: result[0],
remainder: result[1]
};
};
SmallInteger.prototype.divmod = BigInteger.prototype.divmod;
BigInteger.prototype.divide = function (v) {
return divModAny(this, v)[0];
};
SmallInteger.prototype.over = SmallInteger.prototype.divide = BigInteger.prototype.over = BigInteger.prototype.divide;
BigInteger.prototype.mod = function (v) {
return divModAny(this, v)[1];
};
SmallInteger.prototype.remainder = SmallInteger.prototype.mod = BigInteger.prototype.remainder = BigInteger.prototype.mod;
BigInteger.prototype.pow = function (v) {
var n = parseValue(v),
a = this.value,
b = n.value,
value, x, y;
if (b === 0) return CACHE[1];
if (a === 0) return CACHE[0];
if (a === 1) return CACHE[1];
if (a === -1) return n.isEven() ? CACHE[1] : CACHE[-1];
if (n.sign) {
return CACHE[0];
}
if (!n.isSmall) throw new Error("The exponent " + n.toString() + " is too large.");
if (this.isSmall) {
if (isPrecise(value = Math.pow(a, b)))
return new SmallInteger(truncate(value));
}
x = this;
y = CACHE[1];
while (true) {
if (b & 1 === 1) {
y = y.times(x);
--b;
}
if (b === 0) break;
b /= 2;
x = x.square();
}
return y;
};
SmallInteger.prototype.pow = BigInteger.prototype.pow;
BigInteger.prototype.modPow = function (exp, mod) {
exp = parseValue(exp);
mod = parseValue(mod);
if (mod.isZero()) throw new Error("Cannot take modPow with modulus 0");
var r = CACHE[1],
base = this.mod(mod);
while (exp.isPositive()) {
if (base.isZero()) return CACHE[0];
if (exp.isOdd()) r = r.multiply(base).mod(mod);
exp = exp.divide(2);
base = base.square().mod(mod);
}
return r;
};
SmallInteger.prototype.modPow = BigInteger.prototype.modPow;
function compareAbs(a, b) {
if (a.length !== b.length) {
return a.length > b.length ? 1 : -1;
}
for (var i = a.length - 1; i >= 0; i--) {
if (a[i] !== b[i]) return a[i] > b[i] ? 1 : -1;
}
return 0;
}
BigInteger.prototype.compareAbs = function (v) {
var n = parseValue(v),
a = this.value,
b = n.value;
if (n.isSmall) return 1;
return compareAbs(a, b);
};
SmallInteger.prototype.compareAbs = function (v) {
var n = parseValue(v),
a = Math.abs(this.value),
b = n.value;
if (n.isSmall) {
b = Math.abs(b);
return a === b ? 0 : a > b ? 1 : -1;
}
return -1;
};
BigInteger.prototype.compare = function (v) {
// See discussion about comparison with Infinity:
// https://github.com/peterolson/BigInteger.js/issues/61
if (v === Infinity) {
return -1;
}
if (v === -Infinity) {
return 1;
}
var n = parseValue(v),
a = this.value,
b = n.value;
if (this.sign !== n.sign) {
return n.sign ? 1 : -1;
}
if (n.isSmall) {
return this.sign ? -1 : 1;
}
return compareAbs(a, b) * (this.sign ? -1 : 1);
};
BigInteger.prototype.compareTo = BigInteger.prototype.compare;
SmallInteger.prototype.compare = function (v) {
if (v === Infinity) {
return -1;
}
if (v === -Infinity) {
return 1;
}
var n = parseValue(v),
a = this.value,
b = n.value;
if (n.isSmall) {
return a == b ? 0 : a > b ? 1 : -1;
}
if (a < 0 !== n.sign) {
return a < 0 ? -1 : 1;
}
return a < 0 ? 1 : -1;
};
SmallInteger.prototype.compareTo = SmallInteger.prototype.compare;
BigInteger.prototype.equals = function (v) {
return this.compare(v) === 0;
};
SmallInteger.prototype.eq = SmallInteger.prototype.equals = BigInteger.prototype.eq = BigInteger.prototype.equals;
BigInteger.prototype.notEquals = function (v) {
return this.compare(v) !== 0;
};
SmallInteger.prototype.neq = SmallInteger.prototype.notEquals = BigInteger.prototype.neq = BigInteger.prototype.notEquals;
BigInteger.prototype.greater = function (v) {
return this.compare(v) > 0;
};
SmallInteger.prototype.gt = SmallInteger.prototype.greater = BigInteger.prototype.gt = BigInteger.prototype.greater;
BigInteger.prototype.lesser = function (v) {
return this.compare(v) < 0;
};
SmallInteger.prototype.lt = SmallInteger.prototype.lesser = BigInteger.prototype.lt = BigInteger.prototype.lesser;
BigInteger.prototype.greaterOrEquals = function (v) {
return this.compare(v) >= 0;
};
SmallInteger.prototype.geq = SmallInteger.prototype.greaterOrEquals = BigInteger.prototype.geq = BigInteger.prototype.greaterOrEquals;
BigInteger.prototype.lesserOrEquals = function (v) {
return this.compare(v) <= 0;
};
SmallInteger.prototype.leq = SmallInteger.prototype.lesserOrEquals = BigInteger.prototype.leq = BigInteger.prototype.lesserOrEquals;
BigInteger.prototype.isEven = function () {
return (this.value[0] & 1) === 0;
};
SmallInteger.prototype.isEven = function () {
return (this.value & 1) === 0;
};
BigInteger.prototype.isOdd = function () {
return (this.value[0] & 1) === 1;
};
SmallInteger.prototype.isOdd = function () {
return (this.value & 1) === 1;
};
BigInteger.prototype.isPositive = function () {
return !this.sign;
};
SmallInteger.prototype.isPositive = function () {
return this.value > 0;
};
BigInteger.prototype.isNegative = function () {
return this.sign;
};
SmallInteger.prototype.isNegative = function () {
return this.value < 0;
};
BigInteger.prototype.isUnit = function () {
return false;
};
SmallInteger.prototype.isUnit = function () {
return Math.abs(this.value) === 1;
};
BigInteger.prototype.isZero = function () {
return false;
};
SmallInteger.prototype.isZero = function () {
return this.value === 0;
};
BigInteger.prototype.isDivisibleBy = function (v) {
var n = parseValue(v);
var value = n.value;
if (value === 0) return false;
if (value === 1) return true;
if (value === 2) return this.isEven();
return this.mod(n).equals(CACHE[0]);
};
SmallInteger.prototype.isDivisibleBy = BigInteger.prototype.isDivisibleBy;
function isBasicPrime(v) {
var n = v.abs();
if (n.isUnit()) return false;
if (n.equals(2) || n.equals(3) || n.equals(5)) return true;
if (n.isEven() || n.isDivisibleBy(3) || n.isDivisibleBy(5)) return false;
if (n.lesser(25)) return true;
// we don't know if it's prime: let the other functions figure it out
}
BigInteger.prototype.isPrime = function () {
var isPrime = isBasicPrime(this);
if (isPrime !== undefined) return isPrime;
var n = this.abs(),
nPrev = n.prev();
var a = [2, 3, 5, 7, 11, 13, 17, 19],
b = nPrev,
d, t, i, x;
while (b.isEven()) b = b.divide(2);
for (i = 0; i < a.length; i++) {
x = bigInt(a[i]).modPow(b, n);
if (x.equals(CACHE[1]) || x.equals(nPrev)) continue;
for (t = true, d = b; t && d.lesser(nPrev) ; d = d.multiply(2)) {
x = x.square().mod(n);
if (x.equals(nPrev)) t = false;
}
if (t) return false;
}
return true;
};
SmallInteger.prototype.isPrime = BigInteger.prototype.isPrime;
BigInteger.prototype.isProbablePrime = function (iterations) {
var isPrime = isBasicPrime(this);
if (isPrime !== undefined) return isPrime;
var n = this.abs();
var t = iterations === undefined ? 5 : iterations;
// use the Fermat primality test
for (var i = 0; i < t; i++) {
var a = bigInt.randBetween(2, n.minus(2));
if (!a.modPow(n.prev(), n).isUnit()) return false; // definitely composite
}
return true; // large chance of being prime
};
SmallInteger.prototype.isProbablePrime = BigInteger.prototype.isProbablePrime;
BigInteger.prototype.next = function () {
var value = this.value;
if (this.sign) {
return subtractSmall(value, 1, this.sign);
}
return new BigInteger(addSmall(value, 1), this.sign);
};
SmallInteger.prototype.next = function () {
var value = this.value;
if (value + 1 < MAX_INT) return new SmallInteger(value + 1);
return new BigInteger(MAX_INT_ARR, false);
};
BigInteger.prototype.prev = function () {
var value = this.value;
if (this.sign) {
return new BigInteger(addSmall(value, 1), true);
}
return subtractSmall(value, 1, this.sign);
};
SmallInteger.prototype.prev = function () {
var value = this.value;
if (value - 1 > -MAX_INT) return new SmallInteger(value - 1);
return new BigInteger(MAX_INT_ARR, true);
};
var powersOfTwo = [1];
while (powersOfTwo[powersOfTwo.length - 1] <= BASE) powersOfTwo.push(2 * powersOfTwo[powersOfTwo.length - 1]);
var powers2Length = powersOfTwo.length, highestPower2 = powersOfTwo[powers2Length - 1];
function shift_isSmall(n) {
return ((typeof n === "number" || typeof n === "string") && +Math.abs(n) <= BASE) ||
(n instanceof BigInteger && n.value.length <= 1);
}
BigInteger.prototype.shiftLeft = function (n) {
if (!shift_isSmall(n)) {
throw new Error(String(n) + " is too large for shifting.");
}
n = +n;
if (n < 0) return this.shiftRight(-n);
var result = this;
while (n >= powers2Length) {
result = result.multiply(highestPower2);
n -= powers2Length - 1;
}
return result.multiply(powersOfTwo[n]);
};
SmallInteger.prototype.shiftLeft = BigInteger.prototype.shiftLeft;
BigInteger.prototype.shiftRight = function (n) {
var remQuo;
if (!shift_isSmall(n)) {
throw new Error(String(n) + " is too large for shifting.");
}
n = +n;
if (n < 0) return this.shiftLeft(-n);
var result = this;
while (n >= powers2Length) {
if (result.isZero()) return result;
remQuo = divModAny(result, highestPower2);
result = remQuo[1].isNegative() ? remQuo[0].prev() : remQuo[0];
n -= powers2Length - 1;
}
remQuo = divModAny(result, powersOfTwo[n]);
return remQuo[1].isNegative() ? remQuo[0].prev() : remQuo[0];
};
SmallInteger.prototype.shiftRight = BigInteger.prototype.shiftRight;
function bitwise(x, y, fn) {
y = parseValue(y);
var xSign = x.isNegative(), ySign = y.isNegative();
var xRem = xSign ? x.not() : x,
yRem = ySign ? y.not() : y;
var xBits = [], yBits = [];
var xStop = false, yStop = false;
while (!xStop || !yStop) {
if (xRem.isZero()) { // virtual sign extension for simulating two's complement
xStop = true;
xBits.push(xSign ? 1 : 0);
}
else if (xSign) xBits.push(xRem.isEven() ? 1 : 0); // two's complement for negative numbers
else xBits.push(xRem.isEven() ? 0 : 1);
if (yRem.isZero()) {
yStop = true;
yBits.push(ySign ? 1 : 0);
}
else if (ySign) yBits.push(yRem.isEven() ? 1 : 0);
else yBits.push(yRem.isEven() ? 0 : 1);
xRem = xRem.over(2);
yRem = yRem.over(2);
}
var result = [];
for (var i = 0; i < xBits.length; i++) result.push(fn(xBits[i], yBits[i]));
var sum = bigInt(result.pop()).negate().times(bigInt(2).pow(result.length));
while (result.length) {
sum = sum.add(bigInt(result.pop()).times(bigInt(2).pow(result.length)));
}
return sum;
}
BigInteger.prototype.not = function () {
return this.negate().prev();
};
SmallInteger.prototype.not = BigInteger.prototype.not;
BigInteger.prototype.and = function (n) {
return bitwise(this, n, function (a, b) { return a & b; });
};
SmallInteger.prototype.and = BigInteger.prototype.and;
BigInteger.prototype.or = function (n) {
return bitwise(this, n, function (a, b) { return a | b; });
};
SmallInteger.prototype.or = BigInteger.prototype.or;
BigInteger.prototype.xor = function (n) {
return bitwise(this, n, function (a, b) { return a ^ b; });
};
SmallInteger.prototype.xor = BigInteger.prototype.xor;
var LOBMASK_I = 1 << 30, LOBMASK_BI = (BASE & -BASE) * (BASE & -BASE) | LOBMASK_I;
function roughLOB(n) { // get lowestOneBit (rough)
// SmallInteger: return Min(lowestOneBit(n), 1 << 30)
// BigInteger: return Min(lowestOneBit(n), 1 << 14) [BASE=1e7]
var v = n.value, x = typeof v === "number" ? v | LOBMASK_I : v[0] + v[1] * BASE | LOBMASK_BI;
return x & -x;
}
function max(a, b) {
a = parseValue(a);
b = parseValue(b);
return a.greater(b) ? a : b;
}
function min(a,b) {
a = parseValue(a);
b = parseValue(b);
return a.lesser(b) ? a : b;
}
function gcd(a, b) {
a = parseValue(a).abs();
b = parseValue(b).abs();
if (a.equals(b)) return a;
if (a.isZero()) return b;
if (b.isZero()) return a;
var c = CACHE[1], d, t;
while (a.isEven() && b.isEven()) {
d = Math.min(roughLOB(a), roughLOB(b));
a = a.divide(d);
b = b.divide(d);
c = c.multiply(d);
}
while (a.isEven()) {
a = a.divide(roughLOB(a));
}
do {
while (b.isEven()) {
b = b.divide(roughLOB(b));
}
if (a.greater(b)) {
t = b; b = a; a = t;
}
b = b.subtract(a);
} while (!b.isZero());
return c.isUnit() ? a : a.multiply(c);
}
function lcm(a, b) {
a = parseValue(a).abs();
b = parseValue(b).abs();
return a.divide(gcd(a, b)).multiply(b);
}
function randBetween(a, b) {
a = parseValue(a);
b = parseValue(b);
var low = min(a, b), high = max(a, b);
var range = high.subtract(low);
if (range.isSmall) return low.add(Math.round(Math.random() * range));
var length = range.value.length - 1;
var result = [], restricted = true;
for (var i = length; i >= 0; i--) {
var top = restricted ? range.value[i] : BASE;
var digit = truncate(Math.random() * top);
result.unshift(digit);
if (digit < top) restricted = false;
}
result = arrayToSmall(result);
return low.add(typeof result === "number" ? new SmallInteger(result) : new BigInteger(result, false));
}
var parseBase = function (text, base) {
var val = CACHE[0], pow = CACHE[1],
length = text.length;
if (2 <= base && base <= 36) {
if (length <= LOG_MAX_INT / Math.log(base)) {
return new SmallInteger(parseInt(text, base));
}
}
base = parseValue(base);
var digits = [];
var i;
var isNegative = text[0] === "-";
for (i = isNegative ? 1 : 0; i < text.length; i++) {
var c = text[i].toLowerCase(),
charCode = c.charCodeAt(0);
if (48 <= charCode && charCode <= 57) digits.push(parseValue(c));
else if (97 <= charCode && charCode <= 122) digits.push(parseValue(c.charCodeAt(0) - 87));
else if (c === "<") {
var start = i;
do { i++; } while (text[i] !== ">");
digits.push(parseValue(text.slice(start + 1, i)));
}
else throw new Error(c + " is not a valid character");
}
digits.reverse();
for (i = 0; i < digits.length; i++) {
val = val.add(digits[i].times(pow));
pow = pow.times(base);
}
return isNegative ? val.negate() : val;
};
function stringify(digit) {
var v = digit.value;
if (typeof v === "number") v = [v];
if (v.length === 1 && v[0] <= 35) {
return "0123456789abcdefghijklmnopqrstuvwxyz".charAt(v[0]);
}
return "<" + v + ">";
}
function toBase(n, base) {
base = bigInt(base);
if (base.isZero()) {
if (n.isZero()) return "0";
throw new Error("Cannot convert nonzero numbers to base 0.");
}
if (base.equals(-1)) {
if (n.isZero()) return "0";
if (n.isNegative()) return new Array(1 - n).join("10");
return "1" + new Array(+n).join("01");
}
var minusSign = "";
if (n.isNegative() && base.isPositive()) {
minusSign = "-";
n = n.abs();
}
if (base.equals(1)) {
if (n.isZero()) return "0";
return minusSign + new Array(+n + 1).join(1);
}
var out = [];
var left = n, divmod;
while (left.isNegative() || left.compareAbs(base) >= 0) {
divmod = left.divmod(base);
left = divmod.quotient;
var digit = divmod.remainder;
if (digit.isNegative()) {
digit = base.minus(digit).abs();
left = left.next();
}
out.push(stringify(digit));
}
out.push(stringify(left));
return minusSign + out.reverse().join("");
}
BigInteger.prototype.toString = function (radix) {
if (radix === undefined) radix = 10;
if (radix !== 10) return toBase(this, radix);
var v = this.value, l = v.length, str = String(v[--l]), zeros = "0000000", digit;
while (--l >= 0) {
digit = String(v[l]);
str += zeros.slice(digit.length) + digit;
}
var sign = this.sign ? "-" : "";
return sign + str;
};
SmallInteger.prototype.toString = function (radix) {
if (radix === undefined) radix = 10;
if (radix != 10) return toBase(this, radix);
return String(this.value);
};
BigInteger.prototype.valueOf = function () {
return +this.toString();
};
BigInteger.prototype.toJSNumber = BigInteger.prototype.valueOf;
SmallInteger.prototype.valueOf = function () {
return this.value;
};
SmallInteger.prototype.toJSNumber = SmallInteger.prototype.valueOf;
function parseStringValue(v) {
if (isPrecise(+v)) {
var x = +v;
if (x === truncate(x))
return new SmallInteger(x);
throw "Invalid integer: " + v;
}
var sign = v[0] === "-";
if (sign) v = v.slice(1);
var split = v.split(/e/i);
if (split.length > 2) throw new Error("Invalid integer: " + text.join("e"));
if (split.length === 2) {
var exp = split[1];
if (exp[0] === "+") exp = exp.slice(1);
exp = +exp;
if (exp !== truncate(exp) || !isPrecise(exp)) throw new Error("Invalid integer: " + exp + " is not a valid exponent.");
var text = split[0];
var decimalPlace = text.indexOf(".");
if (decimalPlace >= 0) {
exp -= text.length - decimalPlace - 1;
text = text.slice(0, decimalPlace) + text.slice(decimalPlace + 1);
}
if (exp < 0) throw new Error("Cannot include negative exponent part for integers");
text += (new Array(exp + 1)).join("0");
v = text;
}
var isValid = /^([0-9][0-9]*)$/.test(v);
if (!isValid) throw new Error("Invalid integer: " + v);
var r = [], max = v.length, l = LOG_BASE, min = max - l;
while (max > 0) {
r.push(+v.slice(min, max));
min -= l;
if (min < 0) min = 0;
max -= l;
}
trim(r);
return new BigInteger(r, sign);
}
function parseNumberValue(v) {
if (isPrecise(v)) return new SmallInteger(v);
return parseStringValue(v.toString());
}
function parseValue(v) {
if (typeof v === "number") {
return parseNumberValue(v);
}
if (typeof v === "string") {
return parseStringValue(v);
}
return v;
}
// Pre-define numbers in range [-999,999]
var CACHE = function (v, radix) {
if (typeof v === "undefined") return CACHE[0];
if (typeof radix !== "undefined") return +radix === 10 ? parseValue(v) : parseBase(v, radix);
return parseValue(v);
};
for (var i = 0; i < 1000; i++) {
CACHE[i] = new SmallInteger(i);
if (i > 0) CACHE[-i] = new SmallInteger(-i);
}
// Backwards compatibility
CACHE.one = CACHE[1];
CACHE.zero = CACHE[0];
CACHE.minusOne = CACHE[-1];
CACHE.max = max;
CACHE.min = min;
CACHE.gcd = gcd;
CACHE.lcm = lcm;
CACHE.isInstance = function (x) { return x instanceof BigInteger || x instanceof SmallInteger; };
CACHE.randBetween = randBetween;
return CACHE;
})()
});
{
module.exports = nerdamer;
}
});
/**
* Java style map.
*/
class JavaMap {
constructor() {
this[Symbol.toStringTag] = 'Map';
this._map = new Map();
this._size = 0;
}
toString() {
return '{' + Array.from(this.entries2()).map(({ key, value }) => key + ':' + value).join(', ') + '}';
}
forEach(callbackfn, thisArg) {
for (const bucket of this._map.values()) {
for (const { key, value } of bucket) {
callbackfn.call(thisArg, value, key, this);
}
}
}
*keys() {
for (const bucket of this._map.values()) {
for (const { key } of bucket) {
yield key;
}
}
}
*values() {
for (const bucket of this._map.values()) {
for (const { value } of bucket) {
yield value;
}
}
}
[Symbol.iterator]() {
return this.entries();
}
set(key, value) {
this.set2(key, value);
return this;
}
/**
* Like {@link #set} except it returns true if key was new and false if the value was only updated.
*
*/
set2(key, val) {
const hashCode = key.hashCode(), bucket = this._map.get(hashCode);
//assert(hashCode === (hashCode | 0))
if (bucket) {
const pairIndex = bucket.findIndex(pair => pair.key.equals(key));
if (-1 == pairIndex) {
bucket.push({ key: key, value: val });
}
else {
bucket[pairIndex].value = val;
return false;
}
}
else {
this._map.set(hashCode, [{ key: key, value: val }]);
}
this._size++;
return true;
}
has(key) {
const hashCode = key.hashCode(), bucket = this._map.get(hashCode);
//assert(hashCode === (hashCode | 0))
return undefined !== bucket && bucket.some(pair => pair.key.equals(key));
}
get(key) {
const hashCode = key.hashCode(), bucket = this._map.get(hashCode), pair = bucket && bucket.find(pair => pair.key.equals(key));
return pair && pair.value;
}
getLike(key) {
for (const hashCode of key.hashCodes()) {
const bucket = this._map.get(hashCode);
const canonVal = bucket && bucket.find(x => x.key.like(key));
if (canonVal)
return canonVal;
}
}
setLike(key, val) {
return !this.getLike(key) && this.set(key, val);
}
'delete'(key) {
const hashCode = key.hashCode(), bucket = this._map.get(hashCode);
if (bucket) {
const index = bucket.findIndex(x => x.key.equals(key));
if (-1 != index) {
if (1 == bucket.length) {
this._map.delete(hashCode);
}
else {
bucket.splice(index, 1);
}
this._size--;
return true;
}
}
return false;
}
deleteLike(key) {
for (const hashCode of key.hashCodes()) {
const bucket = this._map.get(hashCode);
if (bucket) {
const index = bucket.findIndex(x => x.key.like(key));
if (-1 != index) {
const deleted = bucket[index];
if (1 == bucket.length) {
this._map.delete(hashCode);
}
else {
bucket.splice(index, 1);
}
this._size--;
return deleted;
}
}
}
}
*entries2() {
for (const bucket of this._map.values()) {
yield* bucket;
}
}
*entries() {
for (const bucket of this._map.values()) {
for (const { key, value } of bucket) {
yield [key, value];
}
}
}
clear() {
this._map.clear();
this._size = 0;
}
get size() {
return this._size;
}
}
class JavaSet {
constructor(iterable) {
this[Symbol.toStringTag] = 'Set';
this[Symbol.iterator] = JavaSet.prototype.values;
this.keys = JavaSet.prototype.values;
this._map = new Map();
this._size = 0;
if (iterable) {
this.addAll(iterable);
}
}
forEach(callbackfn, thisArg) {
for (const value of this.entries()) {
callbackfn.call(thisArg, value, value, this);
}
}
add(val) {
this.add2(val);
return this;
}
add2(val) {
// you can't use this.canonicalize here, as there is no way to differentiate if val
// is new or if val was === the exisitng value (not only .equals)
const hashCode = val.hashCode(), bucket = this._map.get(hashCode);
if (bucket) {
if (bucket.some(x => x.equals(val))) {
return false;
}
bucket.push(val);
}
else {
this._map.set(hashCode, [val]);
}
this._size++;
return true;
}
addAll(iterable) {
for (const val of iterable) {
this.add(val);
}
return this;
}
canonicalize(val) {
const hashCode = val.hashCode(), bucket = this._map.get(hashCode);
if (bucket) {
const existing = bucket.find(x => x.equals(val));
if (existing) {
return existing;
}
bucket.push(val);
}
else {
this._map.set(hashCode, [val]);
}
this._size++;
return val;
}
has(val) {
const hashCode = val.hashCode(), bucket = this._map.get(hashCode);
return undefined !== bucket && bucket.some(x => x.equals(val));
}
getLike(val) {
for (const hashCode of val.hashCodes()) {
const bucket = this._map.get(hashCode);
const canonVal = bucket && bucket.find(x => x.like(val));
if (canonVal)
return canonVal;
}
}
canonicalizeLike(val) {
// if this.getLike(val) is defined, return it, otherwise add val and return val
return this.getLike(val) || this.canonicalize(val);
}
addLike(val) {
return !this.getLike(val) && this.add(val);
}
'delete'(val) {
const hashCode = val.hashCode(), bucket = this._map.get(hashCode);
if (bucket) {
const index = bucket.findIndex(x => x.equals(val));
if (-1 != index) {
if (1 == bucket.length) {
this._map.delete(hashCode);
}
else {
bucket.splice(index, 1);
}
this._size--;
return true;
}
}
return false;
}
deleteLike(val) {
for (const hashCode of val.hashCodes()) {
const bucket = this._map.get(hashCode);
if (bucket) {
const index = bucket.findIndex(x => x.like(val));
if (-1 != index) {
const deleted = bucket[index];
if (1 == bucket.length) {
this._map.delete(hashCode);
}
else {
bucket.splice(index, 1);
}
this._size--;
return deleted;
}
}
}
}
*values() {
for (const bucket of this._map.values()) {
yield* bucket;
}
}
*entries() {
for (const bucket of this._map.values()) {
for (const value of bucket) {
yield [value, value];
}
}
}
clear() {
this._map.clear();
this._size = 0;
}
get size() {
return this._size;
}
toString() {
return '{' + Array.from(this.values()).join(', ') + '}';
}
}
class Pair {
constructor(left, right) {
this.left = left;
this.right = right;
}
hashCode() {
return this.left.hashCode() * 31 + this.right.hashCode();
}
equals(other) {
return this == other || Object.getPrototypeOf(other) == Pair.prototype && this.left.equals(other.left) && this.right.equals(other.right);
}
toString() {
return '(' + this.left.toString() + ', ' + this.right.toString() + ')';
}
toSource() {
return 'new Pair(' + this.left.toSource() + ', ' + this.right.toSource() + ')';
}
}
//# sourceMappingURL=bundle.module.js.map
/* toSource by Marcello Bastea-Forte - zlib license */
var tosource = function (object, filter, indent, startingIndent) {
var seen = [];
return walk(object, filter, indent === undefined ? ' ' : (indent || ''), startingIndent || '', seen)
function walk (object, filter, indent, currentIndent, seen) {
var nextIndent = currentIndent + indent;
object = filter ? filter(object) : object;
switch (typeof object) {
case 'string':
return JSON.stringify(object)
case 'boolean':
case 'number':
case 'undefined':
return '' + object
case 'function':
return object.toString()
}
if (object === null) {
return 'null'
}
if (object instanceof RegExp) {
return stringifyRegExp(object)
}
if (object instanceof Date) {
return 'new Date(' + object.getTime() + ')'
}
var seenIndex = seen.indexOf(object) + 1;
if (seenIndex > 0) {
return '{$circularReference:' + seenIndex + '}'
}
seen.push(object);
function join (elements) {
return indent.slice(1) + elements.join(',' + (indent && '\n') + nextIndent) + (indent ? ' ' : '')
}
if (Array.isArray(object)) {
return '[' + join(object.map(function (element) {
return walk(element, filter, indent, nextIndent, seen.slice())
})) + ']'
}
var keys = Object.keys(object);
return keys.length ? '{' + join(keys.map(function (key) {
return (legalKey(key) ? key : JSON.stringify(key)) + ':' + walk(object[key], filter, indent, nextIndent, seen.slice())
})) + '}' : '{}'
}
};
var KEYWORD_REGEXP = /^(abstract|boolean|break|byte|case|catch|char|class|const|continue|debugger|default|delete|do|double|else|enum|export|extends|false|final|finally|float|for|function|goto|if|implements|import|in|instanceof|int|interface|long|native|new|null|package|private|protected|public|return|short|static|super|switch|synchronized|this|throw|throws|transient|true|try|typeof|undefined|var|void|volatile|while|with)$/;
function legalKey (string) {
return /^[a-z_$][0-9a-z_$]*$/gi.test(string) && !KEYWORD_REGEXP.test(string)
}
// Node.js 0.10 doesn't escape slashes in re.toString() or re.source
// when they were not escaped initially.
// Here we check if the workaround is needed once and for all,
// then apply it only for non-escaped slashes.
var isRegExpEscaped = (new RegExp('/')).source === '\\/';
function stringifyRegExp (re) {
if (isRegExpEscaped) {
return re.toString()
}
var source = re.source.replace(/\//g, function (found, offset, str) {
if (offset === 0 || str[offset - 1] !== '\\') {
return '\\/'
}
return '/'
});
var flags = (re.global && 'g' || '') + (re.ignoreCase && 'i' || '') + (re.multiline && 'm' || '');
return '/' + source + '/' + flags
}
class Vector {
constructor(v) {
this.v = v;
assertInst(Float64Array, v);
}
static fromFunction(dims, f) {
assertNumbers(dims);
const e = new Float64Array(dims);
let i = dims;
while (i--) {
e[i] = f(i);
}
return new Vector(e);
}
static random(dims) {
return Vector.fromFunction(dims, (i) => Math.random());
}
static from(...args) {
assert(args[0] instanceof Float64Array || args.every(a => 'number' == typeof a), 'args[0] instanceof Float64Array || args.every(a => "number" == typeof a)');
return new Vector(args[0] instanceof Float64Array ? args[0] : Float64Array.from(args));
}
static Zero(dims) {
assertNumbers(dims);
let i = 0;
const n = new Float64Array(dims);
while (i--) {
n[i] = 0;
}
return new Vector(n);
}
static Unit(dims, dir) {
assertNumbers(dims, dir);
let i = 0;
const n = new Float64Array(dims);
while (i--) {
n[i] = +(i == dir); // +true === 1, +false === 0
}
return new Vector(n);
}
[Symbol.iterator]() {
return this.v[Symbol.iterator]();
}
dim() {
return this.v.length;
}
e(index) {
if (0 > index || index >= this.v.length) {
throw new Error('array index out of bounds');
}
return this.v[index];
}
plus(vector) {
const u = this.v, v = vector.v;
const n = new Float64Array(u.length);
let i = u.length;
while (i--) {
n[i] = u[i] + v[i];
}
return new Vector(n);
}
minus(vector) {
const u = this.v, v = vector.v;
const n = new Float64Array(u.length);
let i = u.length;
while (i--) {
n[i] = u[i] - v[i];
}
return new Vector(n);
}
times(factor) {
const u = this.v;
const n = new Float64Array(u.length);
let i = u.length;
while (i--) {
n[i] = u[i] * factor;
}
return new Vector(n);
}
div(val) {
const u = this.v;
const n = new Float64Array(u.length);
let i = u.length;
while (i--) {
n[i] = u[i] / val;
}
return new Vector(n);
}
dot(vector) {
assert(this.dim == vector.dim, 'passed vector must have the same dim');
let result = 0;
const u = this.v, v = vector.v;
let i = u.length;
while (i--) {
result += u[i] * v[i];
}
return result;
}
cross(vector) {
assertInst(Vector, vector);
const n = new Float64Array(3);
n[0] = this.v[1] * vector.v[2] - this.v[2] * vector.v[1];
n[1] = this.v[2] * vector.v[0] - this.v[0] * vector.v[2];
n[2] = this.v[0] * vector.v[1] - this.v[1] * vector.v[0];
return new Vector(n);
}
schur(vector) {
assertInst(Vector, vector);
const u = this.v, v = vector.v;
const n = new Float64Array(u.length);
let i = u.length;
while (i--) {
n[i] = u[i] * v[i];
}
return new Vector(n);
}
equals(obj) {
if (obj === this)
return true;
if (obj.constructor !== Vector)
return false;
if (this.v.length != obj.v.length)
return false;
let i = this.v.length;
while (i--) {
if (!eq(this.v[i], obj.v[i]))
return false;
}
return true;
}
map(f) {
return new Vector(this.v.map(f));
}
toString(roundFunction) {
roundFunction = roundFunction || ((v) => +v.toFixed(6));
return 'Vector(' + this.v.map(roundFunction).join(', ') + ')';
}
/*
get x() {
return this.v[0]
},
get y() {
return this.v[1]
},
get z() {
return this.v[2]
},
get w() {
return this.v[3]
},
*/
angleTo(vector) {
assertInst(Vector, vector);
assert(!this.isZero(), '!this.likeO()');
assert(!vector.isZero(), '!vector.likeO()');
return Math.acos(this.dot(vector) / this.length() / vector.length());
}
/**
Returns true iff this is parallel to vector, using equals
Throw a DebugError
if vector is not a Vector or
if this has a length of 0 or
if vector has a length of 0
*/
isParallelTo(vector) {
assertInst(Vector, vector);
assert(!this.isZero(), '!this.likeO()');
assert(!vector.isZero(), '!vector.likeO()');
// a . b takes on values of +|a|*|b| (vectors same direction) to -|a|*|b| (opposite direction)
// in both cases the vectors are paralle, so check if abs(a . b) == |a|*|b|
return eq(Math.sqrt(this.lengthSquared() * vector.lengthSquared()), Math.abs(this.dot(vector)));
}
isPerpendicularTo(vector) {
assertInst(Vector, vector);
assert(!this.isZero(), '!this.likeO()');
assert(!vector.isZero(), '!vector.likeO()');
return eq0(this.dot(vector));
}
/**
Returns true iff the length of this vector is 0, as returned by NLA.isZero.
Definition: Vector.prototype.isZero = () => NLA.isZero(this.length())
*/
isZero() {
return eq0(this.length());
}
// Returns a new unit Vector (.length() === 1) with the same direction as this vector. Throws a
/*/ Returns the length of this Vector, i.e. the euclidian norm.*/
length() {
return Math.hypot.apply(undefined, this.v);
//return Math.sqrt(this.lengthSquared())
}
lengthSquared() {
let result = 0;
const u = this.v;
let i = u.length;
while (i--) {
result += u[i] * u[i];
}
return result;
}
// NLA_DEBUGError if this has a length of 0.
normalized() {
const length = this.length();
if (eq0(length)) {
throw new Error('cannot normalize zero vector');
}
return this.div(this.length());
}
asRowMatrix() {
return new Matrix(this.v.length, 1, this.v);
}
asColMatrix() {
return new Matrix(1, this.v.length, this.v);
}
/**
Returns a new Vector which is the projection of this vector onto the passed vector.
Examples
NLA.V(3, 4).projectedOn(NLA.V(1, 0)) // returns NLA.V(3, 0)
NLA.V(3, 4).projectedOn(NLA.V(2, 0)) // returns NLA.V(3, 0)
NLA.V(3, 4).projectedOn(NLA.V(-1, 0)) // returns NLA.V(-3, 0)
NLA.V(3, 4).projectedOn(NLA.V(0, 1)) // returns NLA.V(0, 4)
NLA.V(3, 4).projectedOn(NLA.V(1, 1)) // returns
*/
projectedOn(b) {
assertInst(Vector, b);
// https://en.wikipedia.org/wiki/Vector_projection#Vector_projection_2
return b.times(this.dot(b) / b.dot(b));
}
rejectedOn(b) {
assertInst(Vector, b);
// https://en.wikipedia.org/wiki/Vector_projection#Vector_projection_2
return this.minus(b.times(this.dot(b) / b.dot(b)));
}
/**
Returns true iff the length() of this vector is equal to 'length', using equals
E.g. NLA.V(3, 4).hasLength(5) === true
NLA.V(1, 1).hasLength(1) === false
*/
hasLength(length) {
assertNumbers(length);
return eq(length, this.length());
}
V3() {
//assert(this.dim() == 3)
return new V3(this.v[0], this.v[1], this.v[2]);
}
}
class Matrix {
constructor(width, height, m) {
assert(width * height == m.length, 'width * height == m.length', width, height, m.length);
this.m = m;
this.width = width;
this.height = height;
}
static random(width, height) {
assertNumbers(width, height);
return Matrix.fromFunction(width, height, (i, j) => Math.random());
}
static fromFunction(width, height, f) {
assertNumbers(width, height);
const m = new Float64Array(height * width);
let elIndex = height * width;
while (elIndex--) {
m[elIndex] = f(Math.floor(elIndex / width), elIndex % width, elIndex);
}
return new Matrix(width, height, m);
}
static identityN(dim) {
assertNumbers(dim);
const m = new Float64Array(dim * dim);
// Float64Arrays are init to 0
let elIndex = dim * (dim + 1);
while (elIndex) {
elIndex -= (dim + 1);
m[elIndex] = 1;
}
return new Matrix(dim, dim, m);
}
static permutation(dim, i, k) {
assertNumbers(dim, i, k);
const m = new Float64Array(dim * dim);
// Float64Array are init to 0
let elIndex = dim * (dim + 1);
while (elIndex) {
elIndex -= (dim + 1);
m[elIndex] = 1;
}
m[i * dim + i] = 0;
m[k * dim + k] = 0;
m[i * dim + k] = 1;
m[k * dim + i] = 1;
return new Matrix(dim, dim, m);
}
static fromRowArrays(...args) {
return Matrix.fromRowArrays2(args);
}
static fromRowArrays2(arrays) {
if (0 == arrays.length) {
throw new Error('cannot have 0 vector');
}
const height = arrays.length;
const width = arrays[0].length;
const m = new Float64Array(height * width);
arrayCopy(arrays[0], 0, m, 0, width);
for (let rowIndex = 1; rowIndex < height; rowIndex++) {
if (arrays[rowIndex].length != width) {
throw new Error('all row arrays must be the same length');
}
arrayCopy(arrays[rowIndex], 0, m, rowIndex * width, width);
}
return new Matrix(width, height, m);
}
static fromColVectors(colVectors) {
return Matrix.fromColArrays(colVectors.map((v) => v.v));
}
static forWidthHeight(width, height) {
return new Matrix(width, height, new Float64Array(width * height));
}
static fromColArrays(colArrays) {
if (0 == colArrays.length) {
throw new Error('cannot have 0 vector');
}
const width = colArrays.length;
const height = colArrays[0].length;
const m = new Float64Array(height * width);
arrayCopyStep(colArrays[0], 0, 1, m, 0, width, height);
for (let colIndex = 1; colIndex < width; colIndex++) {
if (colArrays[colIndex].length != height) {
throw new Error('all col arrays must be the same length');
}
arrayCopyStep(colArrays[colIndex], 0, 1, m, colIndex, width, height);
}
return new Matrix(width, height, m);
}
/**
* Numerically calculate all the partial derivatives of f at x0.
*
*
* @param f
* @param x0
* @param fx0 f(x0), pass it if you have it already
* @param EPSILON
*/
static jacobi(f, x0, fx0 = f(x0), EPSILON = 1e-6) {
const jacobi = Matrix.forWidthHeight(x0.length, fx0.length);
for (let colIndex = 0; colIndex < x0.length; colIndex++) {
x0[colIndex] += EPSILON;
const fx = f(x0);
for (let rowIndex = 0; rowIndex < fx0.length; rowIndex++) {
const value = (fx[rowIndex] - fx0[rowIndex]) / EPSILON;
jacobi.setEl(rowIndex, colIndex, value);
}
x0[colIndex] -= EPSILON;
}
return jacobi;
}
e(rowIndex, colIndex) {
assertNumbers(rowIndex, colIndex);
if (NLA_DEBUG && (rowIndex >= this.height || colIndex >= this.width)) {
throw new Error('index ' + rowIndex + ', ' + colIndex + ' is out of bounds (' + this.width + ' x ' + this.height + ')');
}
return this.m[rowIndex * this.width + colIndex];
}
setEl(rowIndex, colIndex, val) {
assertNumbers(rowIndex, colIndex, val);
assert(0 <= rowIndex && rowIndex < this.height, 'rowIndex out of bounds ' + rowIndex);
assert(0 <= colIndex && colIndex < this.width, 'colIndex out of bounds ' + colIndex);
this.m[rowIndex * this.width + colIndex] = val;
}
plus(m) {
assert(this.width == m.width);
assert(this.height == m.height);
const r = this.new();
let i = this.m.length;
while (i--)
r.m[i] = this.m[i] + m.m[i];
return r;
}
minus(m) {
assert(this.width == m.width);
assert(this.height == m.height);
const r = this.new();
let i = this.m.length;
while (i--)
r.m[i] = this.m[i] - m.m[i];
return r;
}
mulScalar(factor) {
const r = this.new();
let i = this.m.length;
while (i--)
r.m[i] = this.m[i] * factor;
return r;
}
divScalar(scalar) {
const r = this.new();
let i = this.m.length;
while (i--)
r.m[i] = this.m[i] / scalar;
return r;
}
new() {
return new Matrix(this.width, this.height, new Float64Array(this.width * this.height));
}
toString(f, colNames, rowNames) {
f = f || ((v) => v.toFixed(6));
assert(typeof f(0) == 'string', '' + typeof f(0));
assert(!colNames || colNames.length == this.width);
assert(!rowNames || rowNames.length == this.height);
const rounded = Array.from(this.m).map(f);
const rows = arrayFromFunction(this.height, (rowIndex) => rounded.slice(rowIndex * this.width, (rowIndex + 1) * this.width)); // select matrix row
if (colNames) {
rows.unshift(Array.from(colNames));
}
if (rowNames) {
rows.forEach((row, rowIndex) => row.unshift(rowNames[rowIndex - (colNames ? 1 : 0)] || ''));
}
const colWidths = arrayFromFunction(this.width, (colIndex) => rows.map(row => row[colIndex].length).max());
return rows.map((row, rowIndex) => row.map((x, colIndex) => {
// pad numbers with spaces to col width
const padder = rowIndex == 0 && colNames || colIndex == 0 && rowNames
? String.prototype.padEnd
: String.prototype.padStart;
return padder.call(x, colWidths[colIndex]);
}).join(' ')).map(x => x + '\n').join(''); // join rows
}
row(rowIndex) {
const v = new Float64Array(this.width);
arrayCopy(this.m, rowIndex * this.width, v, 0, this.width);
return new Vector(v);
}
col(colIndex) {
const v = new Float64Array(this.height);
arrayCopyStep(this.m, colIndex, this.width, v, 0, 1, this.height);
return new Vector(v);
}
dim() {
return { width: this.width, height: this.height };
}
dimString() {
return this.width + 'x' + this.height;
}
equals(obj) {
if (obj.constructor != this.constructor)
return false;
if (this.width != obj.width || this.height != obj.height)
return false;
let elIndex = this.m.length;
while (elIndex--) {
if (this.m[elIndex] != obj.m[elIndex])
return false;
}
return true;
}
equalsMatrix(matrix, precision) {
precision = precision || NLA_PRECISION;
if (!(matrix instanceof Matrix))
throw new Error('not a matrix');
if (this.width != matrix.width || this.height != matrix.height)
return false;
let elIndex = this.m.length;
while (elIndex--) {
if (Math.abs(this.m[elIndex] - matrix.m[elIndex]) >= precision)
return false;
}
return true;
}
hashCode() {
let result = 0;
let elIndex = this.m.length;
while (elIndex--) {
result = result * 31 + floatHashCode(this.m[elIndex]);
}
return result;
}
// todo rename
isZero() {
let elIndex = this.m.length;
while (elIndex--) {
if (!eq0(this.m[elIndex])) {
return false;
}
}
return true;
}
isOrthogonal() {
return this.isSquare() && this.transposed().times(this).equalsMatrix(Matrix.identityN(this.width));
}
/**
* Returns L, U, P such that L * U == P * this
*/
luDecomposition() {
assertf(() => this.isSquare(), this.dim().toSource());
const dim = this.width;
const uRowArrays = this.asRowArrays(Float64Array);
const lRowArrays = arrayFromFunction(dim, (row) => new Float64Array(dim));
const pRowArrays = Matrix.identityN(dim).asRowArrays(Float64Array);
let currentRowIndex = 0;
for (let colIndex = 0; colIndex < dim; colIndex++) {
// find largest value in colIndex
let maxAbsValue = 0, pivotRowIndex = -1, numberOfNonZeroRows = 0;
for (let rowIndex = currentRowIndex; rowIndex < dim; rowIndex++) {
const el = uRowArrays[rowIndex][colIndex];
numberOfNonZeroRows += +(0 != el);
if (Math.abs(el) > maxAbsValue) {
maxAbsValue = Math.abs(el);
pivotRowIndex = rowIndex;
}
}
// TODO: check with isZero
if (0 == maxAbsValue) {
// column contains only zeros
continue;
}
assert(-1 !== pivotRowIndex);
// swap rows
arraySwap(uRowArrays, currentRowIndex, pivotRowIndex);
arraySwap(lRowArrays, currentRowIndex, pivotRowIndex);
arraySwap(pRowArrays, currentRowIndex, pivotRowIndex);
lRowArrays[colIndex][colIndex] = 1;
if (1 < numberOfNonZeroRows) {
// subtract pivot (now current) row from all below it
for (let rowIndex = currentRowIndex + 1; rowIndex < dim; rowIndex++) {
const l = uRowArrays[rowIndex][colIndex] / uRowArrays[currentRowIndex][colIndex];
lRowArrays[rowIndex][colIndex] = l;
// subtract pivot row * l from row 'rowIndex'
for (let colIndex2 = colIndex; colIndex2 < dim; colIndex2++) {
uRowArrays[rowIndex][colIndex2] -= l * uRowArrays[currentRowIndex][colIndex2];
}
}
}
currentRowIndex++; // this doesn't increase if pivot was zero
}
return {
L: Matrix.fromRowArrays2(lRowArrays),
U: Matrix.fromRowArrays2(uRowArrays),
P: Matrix.fromRowArrays2(pRowArrays),
};
}
gauss() {
const width = this.width, height = this.height;
const uRowArrays = this.asRowArrays(Float64Array);
const lRowArrays = arrayFromFunction(height, (row) => new Float64Array(width));
const pRowArrays = Matrix.identityN(height).asRowArrays(Float64Array);
let currentRowIndex = 0;
for (let colIndex = 0; colIndex < width; colIndex++) {
// console.log('currentRowIndex', currentRowIndex) // find largest value in colIndex
let maxAbsValue = 0, pivotRowIndex = -1, numberOfNonZeroRows = 0;
for (let rowIndex = currentRowIndex; rowIndex < height; rowIndex++) {
const el = uRowArrays[rowIndex][colIndex];
numberOfNonZeroRows += +(0 != el);
if (Math.abs(el) > maxAbsValue) {
maxAbsValue = Math.abs(el);
pivotRowIndex = rowIndex;
}
}
// TODO: check with isZero
if (0 == maxAbsValue) {
// column contains only zeros
continue;
}
assert(-1 !== pivotRowIndex);
// swap rows
arraySwap(uRowArrays, currentRowIndex, pivotRowIndex);
arraySwap(lRowArrays, currentRowIndex, pivotRowIndex);
arraySwap(pRowArrays, currentRowIndex, pivotRowIndex);
lRowArrays[currentRowIndex][colIndex] = 1;
if (1 < numberOfNonZeroRows) {
// subtract pivot (now current) row from all below it
for (let rowIndex = currentRowIndex + 1; rowIndex < height; rowIndex++) {
const l = uRowArrays[rowIndex][colIndex] / uRowArrays[currentRowIndex][colIndex];
lRowArrays[rowIndex][colIndex] = l;
// subtract pivot row * l from row 'rowIndex'
for (let colIndex2 = colIndex; colIndex2 < width; colIndex2++) {
uRowArrays[rowIndex][colIndex2] -= l * uRowArrays[currentRowIndex][colIndex2];
}
}
}
currentRowIndex++; // this doesn't increase if pivot was zero
}
return {
L: Matrix.fromRowArrays2(lRowArrays),
U: Matrix.fromRowArrays2(uRowArrays),
P: Matrix.fromRowArrays2(pRowArrays),
};
}
qrDecompositionGivensRotation() {
function matrixForCS(dim, i, k, c, s) {
const m = Matrix.identityN(dim);
m.setEl(i, i, c);
m.setEl(k, k, c);
m.setEl(i, k, s);
m.setEl(k, i, -s);
return m;
}
let qTransposed = Matrix.identityN(this.height);
for (let colIndex = 0; colIndex < this.width; colIndex++) {
// find largest value in colIndex
for (let rowIndex = colIndex + 1; rowIndex < this.height; rowIndex++) {
//console.log('row ', rowIndex, 'col ', colIndex)
const xi = this.e(colIndex, colIndex);
const xk = this.e(rowIndex, colIndex);
if (xk == 0) {
continue;
}
const r = Math.sqrt(xi * xi + xk * xk);
const c = xi / r;
const s = xk / r;
// apply transformation on every column:
for (let col2 = colIndex; col2 < this.width; col2++) {
const x1 = this.e(colIndex, col2) * c + this.e(rowIndex, col2) * s;
const x2 = this.e(rowIndex, col2) * c - this.e(colIndex, col2) * s;
this.setEl(colIndex, col2, x1);
this.setEl(rowIndex, col2, x2);
}
//console.log('r ', r, 'c ', c, 's ', s, 'sigma', sigma(c, s))
//console.log(this.toString(),'cs\n', matrixForCS(this.height, colIndex, rowIndex, c, s).toString())
qTransposed = matrixForCS(this.height, colIndex, rowIndex, c, s).times(qTransposed);
}
}
//console.log(qTransposed.transposed().toString(), this.toString(),
// qTransposed.transposed().times(this).toString())
return { Q: qTransposed.transposed(), R: this };
}
isPermutation() {
if (!this.isSquare())
return false;
if (this.m.some((value) => !eq0(value) && !eq(1, value)))
return false;
const rows = this.asRowArrays(Array);
if (rows.some((row) => row.filter((value) => eq(1, value)).length != 1))
return false;
const cols = this.asColArrays(Array);
if (cols.some((col) => col.filter((value) => eq(1, value)).length != 1))
return false;
return true;
}
isDiagonal(precision) {
let i = this.m.length;
while (i--) {
if (0 !== i % (this.width + 1) && !eq0(this.m[i]))
return false;
}
return true;
}
isIdentity(precision) {
return this.isLowerUnitriangular(precision) && this.isUpperTriangular(precision);
}
isUpperTriangular(precision) {
precision = 'number' == typeof precision ? precision : NLA_PRECISION;
if (!this.isSquare())
return false;
for (let rowIndex = 1; rowIndex < this.height; rowIndex++) {
for (let colIndex = 0; colIndex < rowIndex; colIndex++) {
if (!eq0(this.m[rowIndex * this.width + colIndex], precision)) {
return false;
}
}
}
return true;
}
/**
* Returns x, so that this * x = b
* More efficient than calculating the inverse for few (~ <= this.height) values
*/
solveLinearSystem(b) {
const lup = this.luDecomposition();
// console.log(lup.L.toString())
// console.log(lup.U.toString())
// console.log(lup.P.toString())
const y = lup.L.solveForwards(lup.P.timesVector(b));
const x = lup.U.solveBackwards(y);
return x;
}
isLowerUnitriangular(precision) {
precision = 'number' == typeof precision ? precision : NLA_PRECISION;
if (!this.isSquare())
return false;
for (let rowIndex = 0; rowIndex < this.height - 1; rowIndex++) {
for (let colIndex = rowIndex; colIndex < this.width; colIndex++) {
const el = this.m[rowIndex * this.width + colIndex];
if (rowIndex == colIndex ? !eq(1, el, precision) : !eq0(el, precision)) {
return false;
}
}
}
return true;
}
isLowerTriangular() {
if (!this.isSquare())
return false;
for (let rowIndex = 0; rowIndex < this.height - 1; rowIndex++) {
for (let colIndex = rowIndex + 1; colIndex < this.width; colIndex++) {
if (!eq0(this.m[rowIndex * this.width + colIndex])) {
return false;
}
}
}
return true;
}
solveBackwards(x) {
assertVectors(x);
assert(this.height == x.dim(), 'this.height == x.dim()');
assert(this.isUpperTriangular(), 'this.isUpperTriangular()\n' + this.str);
const v = new Float64Array(this.width);
let rowIndex = this.height;
while (rowIndex--) {
let temp = x.v[rowIndex];
for (let colIndex = rowIndex + 1; colIndex < this.width; colIndex++) {
temp -= v[colIndex] * this.e(rowIndex, colIndex);
}
v[rowIndex] = temp / this.e(rowIndex, rowIndex);
}
return new Vector(v);
}
solveBackwardsMatrix(matrix) {
const colVectors = new Array(matrix.width);
let i = matrix.width;
while (i--) {
colVectors[i] = this.solveBackwards(matrix.col(i));
}
return Matrix.fromColVectors(colVectors);
}
solveForwardsMatrix(matrix) {
const colVectors = new Array(matrix.width);
let i = matrix.width;
while (i--) {
colVectors[i] = this.solveForwards(matrix.col(i));
}
return Matrix.fromColVectors(colVectors);
}
solveForwards(x) {
assertVectors(x);
assert(this.height == x.dim(), 'this.height == x.dim()');
assertf(() => this.isLowerTriangular(), this.toString());
const v = new Float64Array(this.width);
for (let rowIndex = 0; rowIndex < this.height; rowIndex++) {
let temp = x.v[rowIndex];
for (let colIndex = 0; colIndex < rowIndex; colIndex++) {
temp -= v[colIndex] * this.e(rowIndex, colIndex);
}
v[rowIndex] = temp / this.e(rowIndex, rowIndex);
}
return new Vector(v);
}
/**
* Calculates rank of matrix.
* Number of linearly independant row/column vectors.
* Is equal to the unmber of dimensions the image of the affine transformation represented this matrix has.
*/
rank() {
const U = this.gauss().U;
//console.log(R.toString())
let rowIndex = this.height;
while (rowIndex-- && U.row(rowIndex).isZero()) {
console.log('RANK' + U.row(rowIndex).toString() + U.row(rowIndex).isZero());
}
return rowIndex + 1;
}
rowsIndependent() {
return this.height == this.rank();
}
colsIndependent() {
return this.width == this.rank();
}
asRowArrays(arrayConstructor) {
arrayConstructor = arrayConstructor || Float64Array;
let rowIndex = this.height;
const result = new Array(this.height);
while (rowIndex--) {
result[rowIndex] = this.rowArray(rowIndex, arrayConstructor);
}
return result;
}
asColArrays(arrayConstructor) {
arrayConstructor = arrayConstructor || Float64Array;
let colIndex = this.width;
const result = new Array(this.width);
while (colIndex--) {
result[colIndex] = this.colArray(colIndex, arrayConstructor);
}
return result;
}
rowArray(rowIndex, arrayConstructor) {
arrayConstructor = arrayConstructor || Float64Array;
const result = new arrayConstructor(this.width);
arrayCopy(this.m, rowIndex * this.width, result, 0, this.width);
return result;
}
colArray(colIndex, arrayConstructor) {
arrayConstructor = arrayConstructor || Float64Array;
const result = new arrayConstructor(this.width);
arrayCopyStep(this.m, colIndex, this.height, result, 0, 1, this.height);
return result;
}
subMatrix(firstColIndex, subWidth, firstRowIndex, subHeight) {
assert(firstColIndex + subWidth > this.width || firstRowIndex + subHeight > this.height);
const m = new Float64Array(this.height);
arrayCopyBlocks(this.m, firstColIndex, this.width, m, 0, subWidth, subHeight, subWidth);
return new Matrix(subWidth, subHeight, m);
}
map(fn) {
return new Matrix(this.width, this.height, this.m.map(fn));
}
dimEquals(matrix) {
assertInst(Matrix, matrix);
return this.width == matrix.width && this.height == matrix.height;
}
inversed() {
const lup = this.luDecomposition();
const y = lup.L.solveForwardsMatrix(lup.P);
console.log(y);
const inverse = lup.U.solveBackwardsMatrix(y);
return inverse;
}
inversed3() {
assertf(() => 3 == this.width && 3 == this.height);
const result = Matrix.forWidthHeight(3, 3), m = this.m, r = result.m;
r[0] = m[4] * m[8] - m[5] * m[7];
r[1] = -m[1] * m[8] + m[2] * m[7];
r[2] = m[1] * m[5] - m[2] * m[4];
r[3] = -m[3] * m[8] + m[5] * m[6];
r[4] = m[0] * m[8] - m[2] * m[6];
r[5] = -m[0] * m[5] + m[2] * m[3];
r[6] = m[3] * m[7] - m[4] * m[6];
r[7] = -m[0] * m[7] + m[1] * m[6];
r[8] = m[0] * m[4] - m[1] * m[3];
const det = m[0] * r[0] + m[1] * r[3] + m[2] * r[6];
let i = 9;
while (i--) {
r[i] /= det;
}
return result;
}
inversed2() {
assertf(() => 2 == this.width && 2 == this.height);
const result = Matrix.forWidthHeight(2, 2), m = this.m, r = result.m;
const det = m[0] * m[3] - m[1] * r[2];
r[0] = m[3] / det;
r[1] = -m[2] / det;
r[2] = -m[1] / det;
r[3] = m[0] / det;
return result;
}
canMultiply(matrix) {
assertInst(Matrix, matrix);
return this.width == matrix.height;
}
times(matrix) {
assertInst(Matrix, matrix);
assert(this.canMultiply(matrix), `Cannot multiply this {this.dimString()} by matrix {matrix.dimString()}`);
const nWidth = matrix.width, nHeight = this.height, n = this.width;
const nM = new Float64Array(nWidth * nHeight);
let nRowIndex = nHeight;
while (nRowIndex--) {
let nColIndex = nWidth;
while (nColIndex--) {
let result = 0;
let i = n;
while (i--) {
result += this.m[nRowIndex * n + i] * matrix.m[i * nWidth + nColIndex];
}
nM[nRowIndex * nWidth + nColIndex] = result;
}
}
return new Matrix(nWidth, nHeight, nM);
}
timesVector(v) {
assertVectors(v);
assert(this.width == v.dim());
const nHeight = this.height, n = this.width;
const nM = new Float64Array(nHeight);
let nRowIndex = nHeight;
while (nRowIndex--) {
let result = 0;
let i = n;
while (i--) {
result += this.m[nRowIndex * n + i] * v.v[i];
}
nM[nRowIndex] = result;
}
return new Vector(nM);
}
transposed() {
const tWidth = this.height, tHeight = this.width;
const tM = new Float64Array(tWidth * tHeight);
let tRowIndex = tHeight;
while (tRowIndex--) {
let tColIndex = tWidth;
while (tColIndex--) {
tM[tRowIndex * tWidth + tColIndex] = this.m[tColIndex * tHeight + tRowIndex];
}
}
return new Matrix(tWidth, tHeight, tM);
}
/**
* In-place transpose.
*/
transpose() {
const h = this.height, w = this.width, tM = this.m;
let tRowIndex = h;
while (tRowIndex--) {
let tColIndex = Math.min(tRowIndex, w);
while (tColIndex--) {
console.log('col', tColIndex, 'row', tRowIndex);
const temp = tM[tRowIndex * w + tColIndex];
tM[tRowIndex * w + tColIndex] = tM[tColIndex * h + tRowIndex];
tM[tColIndex * h + tRowIndex] = temp;
}
}
this.width = h;
this.height = w;
}
isSquare() {
return this.height == this.width;
}
diagonal() {
if (!this.isSquare()) {
throw new Error('!!');
}
const v = new Float64Array(this.width);
let elIndex = this.width * (this.width + 1);
let vIndex = this.width;
while (vIndex--) {
elIndex -= this.width + 1;
v[vIndex] = this.m[elIndex];
}
return new Vector(v);
}
maxEl() {
return Math.max.apply(undefined, this.m);
}
minEl() {
return Math.min.apply(undefined, this.m);
}
maxAbsColSum() {
let result = 0;
let colIndex = this.width;
while (colIndex--) {
let absSum = 0;
let rowIndex = this.height;
while (rowIndex--) {
absSum += Math.abs(this.m[rowIndex * this.width + colIndex]);
}
result = Math.max(result, absSum);
}
return result;
}
maxAbsRowSum() {
let result = 0;
let rowIndex = this.height;
while (rowIndex--) {
let absSum = 0;
let colIndex = this.width;
while (colIndex--) {
absSum += Math.abs(this.m[rowIndex * this.width + colIndex]);
}
result = Math.max(result, absSum);
}
return result;
}
getTriangularDeterminant() {
assert(this.isUpperTriangular() || this.isLowerTriangular(), 'not a triangular matrix');
let product = 1;
let elIndex = this.width * (this.width + 1);
while (elIndex) {
elIndex -= this.width + 1;
product *= this.m[elIndex];
}
return product;
}
/**
* Calculates the determinant by first calculating the LU decomposition. If you already have that, use
* U.getTriangularDeterminant()
*/
getDeterminant() {
// PA = LU
// det(A) * det(B) = det(A * B)
// det(P) == 1 (permutation matrix)
// det(L) == 1 (main diagonal is 1s
// => det(A) == det(U)
return this.luDecomposition().U.getTriangularDeterminant();
}
hasFullRank() {
return Math.min(this.width, this.height) == this.rank();
}
permutationAsIndexMap() {
assertf(() => this.isPermutation());
const result = new Array(this.height);
let i = this.height;
while (i--) {
const searchIndexStart = i * this.width;
let searchIndex = searchIndexStart;
while (this.m[searchIndex] < 0.5)
searchIndex++;
result[i] = searchIndex - searchIndexStart;
}
return result;
}
getDependentRowIndexes(gauss = this.gauss()) {
const { L, U, P } = gauss;
// rows which end up as zero vectors in U are not linearly independent
const dependents = new Array(this.height);
let uRowIndex = this.height;
while (uRowIndex--) {
const uRow = U.row(uRowIndex);
if (uRow.length() < NLA_PRECISION) {
dependents[uRowIndex] = true;
}
else {
break;
}
}
// figure out from which other rows the rows which end up as zero vectors are created by
let lRowIndex = this.height;
while (lRowIndex--) {
if (dependents[lRowIndex]) {
let lColIndex = Math.min(lRowIndex, this.width);
while (lColIndex--) {
if (0 !== L.e(lRowIndex, lColIndex)) {
dependents[lColIndex] = true;
}
}
}
}
console.log('m\n', this.toString(x => '' + x));
console.log('L\n', L.toString(x => '' + x));
console.log('U\n', U.toString(x => '' + x));
console.log('P\n', P.toString(x => '' + x));
// gauss algorithm permutes the order of the rows, so map our results back to the original indices
const indexMap = P.permutationAsIndexMap();
const dependentRowIndexes = dependents.map((b, index) => b && indexMap[index]).filter(x => x != undefined);
return dependentRowIndexes;
}
}
// @ts-ignore
const { abs: abs$1, PI, sign: sign$1 } = Math;
const TAU = 2 * PI;
/** @define {boolean} */
const NLA_DEBUG = true;
const NLA_PRECISION = 1 / (1 << 26);
console.log('NLA_PRECISION', NLA_PRECISION);
console.log('NLA_DEBUG', NLA_DEBUG);
let oldConsole = undefined;
function disableConsole() {
oldConsole = console.log;
console.log = function () { };
}
function enableConsole() {
if (oldConsole) {
console.log = oldConsole;
}
}
function hasConstructor(instance, cons) {
return instance.constructor == cons;
}
function getIntervals(ts, min, max) {
ts.sort((a, b) => a - b);
if (!eq(ts[0], min)) {
ts.splice(0, 0, min);
}
if (!eq(ts.last, max)) {
ts.push(max);
}
return arrayFromFunction(ts.length - 1, i => [ts[i], ts[i + 1]]);
}
function assertVectors(...vectors) {
if (NLA_DEBUG) {
for (let i = 0; i < arguments.length; i++) {
if (!(arguments[i] instanceof V3 || arguments[i] instanceof Vector)) {
throw new Error('assertVectors arguments[' + (i) + '] is not a vector. ' + typeof arguments[i] + ' == typeof ' + arguments[i]);
}
}
}
return true;
}
function assertInst(what, ...objs) {
if (NLA_DEBUG) {
for (let i = 0; i < objs.length; i++) {
if (!(objs[i] instanceof what)) {
throw new Error('assertInst objs[' + (i) + '] is not a ' + what.prototype.name + '. ' + objs[i].constructor.name + objs[i]);
}
}
}
return true;
}
function assertNumbers(...numbers) {
if (NLA_DEBUG) {
for (let i = 0; i < numbers.length; i++) {
if ('number' !== typeof numbers[i]) {
throw new Error('assertNumbers arguments[' + (i) + '] is not a number. ' + typeof numbers[i] + ' == typeof ' + numbers[i]);
}
}
}
return true;
}
function assert(value, ...messages) {
if (NLA_DEBUG && !value) {
throw new Error('assert failed: '
+ messages.map(message => ('function' === typeof message ? message() : message || '')).join('\n'));
}
return true;
}
function assertNever(value) {
throw new Error();
}
function assertf(f, ...messages) {
if (!f()) {
throw new Error('assertf failed: ' + f.toString()
+ messages.map(message => ('function' === typeof message ? message() : message || '')).join('\n'));
}
}
function lerp(a, b, t) {
return a * (1 - t) + b * t;
}
const originalNumberToString = Number.prototype.toString;
Number.prototype.toString = function (radix) {
if (PI == this) {
return 'PI';
}
return originalNumberToString.call(this, radix);
};
const eq0 = (x, EPS = NLA_PRECISION) => Math.abs(x) <= EPS;
const eq = (x, y, EPS = NLA_PRECISION) => Math.abs(x - y) <= EPS;
const lt = (x, y, EPS = NLA_PRECISION) => x - y < -EPS;
const gt = (x, y, EPS = NLA_PRECISION) => y - x < -EPS;
const le = (x, y, EPS = NLA_PRECISION) => x - y <= EPS;
const ge = (x, y, EPS = NLA_PRECISION) => y - x <= EPS;
const eqAngle = (x, y) => zeroAngle(x - y);
const zeroAngle = (x) => ((x % (2 * Math.PI)) + 2 * Math.PI + NLA_PRECISION) % (2 * Math.PI) < 2 * NLA_PRECISION;
const snap = (x, to) => Math.abs(x - to) <= NLA_PRECISION ? to : x;
const snap2 = (x, ...to) => to.reduce((x, to) => Math.abs(x - to) <= NLA_PRECISION ? to : x, x);
const snapEPS = (x, EPS, ...to) => to.reduce((x, to) => Math.abs(x - to) <= EPS ? to : x, x);
const snap0 = (x, EPS = NLA_PRECISION) => Math.abs(x) <= EPS ? 0 : x;
const canonAngle = (x) => ((x % (2 * Math.PI)) + 2 * Math.PI) % (2 * Math.PI);
/** @deprecated */ const eq02 = eq0;
/** @deprecated */ const eq2 = eq;
/**
* Decimal adjustment of a number.
*
* @param f The type of adjustment.
* @param value The number.
* @param exp The exponent (the 10 logarithm of the adjustment base).
* @returns The adjusted value.
*/
function decimalAdjust(f, value, exp) {
// If the exp is undefined or zero...
if (typeof exp === 'undefined' || +exp === 0) {
return f(value);
}
value = +value;
exp = +exp;
// If the value is not a number or the exp is not an integer...
if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0)) {
return NaN;
}
// Shift
let vs = value.toString().split('e');
value = f(+(vs[0] + 'e' + (vs[1] ? (+vs[1] - exp) : -exp)));
// Shift back
vs = value.toString().split('e');
return +(vs[0] + 'e' + (vs[1] ? (+vs[1] + exp) : exp));
}
const round10 = decimalAdjust.bind(undefined, Math.round);
const floor10 = decimalAdjust.bind(undefined, Math.floor);
const ceil10 = decimalAdjust.bind(undefined, Math.ceil);
const GOLDEN_RATIO = (1 + Math.sqrt(5)) / 2;
function repeatString(count, str) {
if (count == 0) {
return '';
}
count *= str.length;
const halfCharLength = count / 2;
let result = str;
// double the input until it is long enough.
while (result.length <= halfCharLength) {
result += result;
}
// use substring to hit the precise length target without
// using extra memory
return result + result.substring(0, count - result.length);
}
function mod(a, b) {
return ((a % b) + b) % b;
}
function arraySwap(arr, i, j) {
const temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
function arrayCopy(src, sstart, dst, dstart, length) {
dstart += length;
length += sstart;
while (length-- > sstart) {
dst[--dstart] = src[length];
}
}
function clamp(val, min, max) {
assertNumbers(val, min, max);
return Math.max(min, Math.min(max, val));
}
function between(val, min, max) {
return min <= val && val <= max;
}
function fuzzyBetween(val, min, max) {
return le(min, val) && le(val, max);
}
function randomColor() {
return Math.floor(Math.random() * 0x1000000);
}
function mapPush(map, key, val) {
const array = map.get(key);
if (array) {
array.push(val);
}
else {
map.set(key, [val]);
}
}
function arrayCopyStep(src, sstart, sstep, dst, dstart, dstep, count) {
let srcIndex = sstart + count * sstep;
let dIndex = dstart + count * dstep;
while (srcIndex > sstart) {
dst[dIndex -= dstep] = src[srcIndex -= sstep];
}
}
function arrayCopyBlocks(src, sstart, sstep, dst, dstart, dstep, blockSize, blockCount) {
for (let i = 0; i < blockCount; i++) {
arrayCopy(src, sstart + sstep * i, dst, dstart + dstep * i, blockSize);
}
}
function arrayRange(startInclusive, endExclusive, step = 1) {
assertNumbers(startInclusive, step);
//console.log(Math.ceil((endExclusive - startInclusive) / step))
const arrLength = Math.ceil((endExclusive - startInclusive) / step);
const result = new Array(arrLength); // '- startInclusive' so that chunk in the last row will also be selected, even
// if the row is not complete
for (let i = startInclusive, index = 0; index < arrLength; i += step, index++) {
result[index] = i;
}
return result;
}
function arrayFromFunction(length, f) {
assertNumbers(length);
assert('function' == typeof f);
const a = new Array(length);
let elIndex = length;
while (elIndex--) {
a[elIndex] = f(elIndex);
}
return a;
}
function fuzzyUniques(vals) {
const round = (val) => Math.floor(val * (1 << 26)) / (1 << 26);
const map = new Map();
for (let i = 0; i < vals.length; i++) {
const val = vals[i], roundVal = round(val);
let key;
if (!map.has(roundVal)
&& !((key = map.get(roundVal - 1 / (1 << 26))) && eq(key, val))
&& !((key = map.get(roundVal + 1 / (1 << 26))) && eq(key, val))) {
map.set(roundVal, val);
}
}
return Array.from(map.values());
}
function fuzzyUniquesF(vals, f) {
const round = (val) => Math.floor(val * (1 << 26)) / (1 << 26);
const map = new Map();
for (let i = 0; i < vals.length; i++) {
const val = vals[i], roundVal = round(f(val));
let key;
if (!map.has(roundVal)
&& !((key = map.get(roundVal - 1 / (1 << 26))) && eq(key, f(val)))
&& !((key = map.get(roundVal + 1 / (1 << 26))) && eq(key, f(val)))) {
map.set(roundVal, val);
}
}
return Array.from(map.values());
}
function addOwnProperties(target, props, ...exclude) {
Object.getOwnPropertyNames(props).forEach(key => {
//console.log(props, key)
if (!exclude.includes(key)) {
if (target.hasOwnProperty(key)) {
console.warn('target ', target, ' already has property ', key, target[key]);
}
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(props, key));
}
});
}
//function defineClass(name, parent, constructor, props, statics) {
// assertf(() => 'function' == typeof constructor, 'function' == typeof constructor)
// constructor.prototype = defineObject(parent && parent.prototype, props)
// constructor.prototype.constructor = constructor
// Object.defineProperty(constructor.prototype, 'name', {value: name})
// statics && addOwnProperties(constructor, statics)
// return constructor
//}
let defaultRoundFunction = (x) => x; // Math.round10(x, -4)
function forceFinite(val) {
const valNum = parseFloat(val.replace(',', '.').replace(/^[^0-9,\.\-]/, ''));
return Number.isFinite(valNum) ? valNum : 0;
}
const MINUS = (a, b) => a - b;
function floatHashCode(f) {
return ~~(f * (1 << 28));
}
/**
* combinations(2) will generate
* [0,0] [0,1] [1,1] [0,2] [1,2] [2,2]
*/
function* combinations(n) {
for (let i = 0; i < n; i++) {
for (let j = i; j < n; j++) {
yield { i: i, j: j };
}
}
}
/* The arithmetic-geometric mean of two non-negative numbers */
function arithmeticGeometricMean(x, y) {
assertf(() => lt(0, x));
assertf(() => lt(0, y));
let a = x, g = y;
let i = 30;
while (i-- && a != g) {
[a, g] = [(a + g) / 2, Math.sqrt(a * g)];
}
assert(i != -1);
return a;
}
/**
* incomplete elliptic integral of the first kind
* EllipticF(phi, k2) = INT[0; phi] 1 / sqrt(1 - k2 * sin²(phi)) dphi
*/
function EllipticF(phi, k2) {
return gaussLegendreQuadrature24(phi => Math.pow(1 - k2 * Math.pow(Math.sin(phi), 2), -0.5), 0, phi);
}
/**
* incomplete elliptic integral of the second kind
* EllipticE(phi, k2) = INT[0; phi] sqrt(1 - k2 * sin²(phi)) dphi
*/
function EllipticE(phi, k2) {
return gaussLegendreQuadrature24(phi => Math.pow(1 - k2 * Math.pow(Math.sin(phi), 2), 0.5), 0, phi);
}
const DEG = .017453292519943295;
function rad2deg(rad) {
// discuss at: http://phpjs.org/functions/deg2rad/
// original by: Enrique Gonzalez
// improved by: Thomas Grainger (http://graingert.co.uk)
// example 1: deg2rad(45)
// returns 1: 0.7853981633974483
return rad / DEG;
}
/**
* numberToStr(2/3) == '0.6p'
* numberToStr(7/12) == '0.583p'
* numberToStr(2/7) == '0.285714pppppp'
* numberToStr(NLA_PRECISION) == '0+'
* numberToStr(-NLA_PRECISION) == '0-'
* numberToStr(2-NLA_PRECISION) == '2-'
* numberToStr(0) == '0='
*
*/
function numberToStr(value, length) {
let minAbsDiff = Infinity, closestValue = undefined, closestValueStr = undefined;
function test(testValue, testValueStr) {
const absDiff = Math.abs(testValue - value);
console.log(testValue, testValueStr, absDiff);
if (absDiff < minAbsDiff) {
minAbsDiff = absDiff;
closestValue = testValue;
closestValueStr = testValueStr;
}
return 0 == absDiff;
}
function overline(str) {
return str.split('').map(c => c + '\u0304').join('');
}
if (test(parseFloat(value.toFixed(length)), value.toFixed(length)))
return closestValueStr + '=';
const valueStr = '' + value;
const toDecimal = valueStr.substr(0, valueStr.indexOf('.') + 1);
const decimals = valueStr.substr(valueStr.indexOf('.') + 1);
for (let startPos = 0; startPos < length; startPos++) {
for (let endPos = startPos + 1; endPos <= length; endPos++) {
const prefixDecimals = decimals.substr(0, startPos);
const period = decimals.substr(startPos, endPos);
const testValue = parseFloat(toDecimal + prefixDecimals + repeatString(Math.ceil((17 - startPos) / period.length), period));
if (test(testValue, toDecimal + prefixDecimals + overline(period)))
return closestValueStr + '=';
}
}
return closestValueStr + (closestValue < value ? '-' : '+');
}
function time(f) {
const start = performance.now();
f();
return performance.now() - start;
}
Object.map = function (o, f, context = undefined) {
const result = {};
for (const key in o) {
result[key] = f.call(context, o[key], key, o);
}
return result;
};
Array.prototype.emod = function (i) {
return this[i % this.length];
};
Array.prototype.sliceStep = function (start, end, step, chunkSize = 1) {
assertNumbers(start, step);
start < 0 && (start = this.length + start);
end <= 0 && (end = this.length + end);
const resultLength = Math.ceil((end - start) / step);
const result = new Array(resultLength); // '- start' so that chunk in the last row
// will also be selected, even if the row is
// not complete
let index = 0;
for (let i = start; i < end; i += step) {
for (let j = i; j < Math.min(i + chunkSize, end); j++) {
result[index++] = this[j];
}
}
assert(resultLength == index);
return result;
};
Array.prototype.equals = function (obj) {
if (this === obj)
return true;
if (Object.getPrototypeOf(obj) !== Array.prototype)
return false;
if (this.length !== obj.length)
return false;
for (let i = 0; i < this.length; i++) {
if (!this[i].equals(obj[i]))
return false;
}
return true;
};
Array.prototype.hashCode = function () {
let hashCode = 0;
for (let i = 0; i < this.length; i++) {
hashCode = hashCode * 31 + this[i].hashCode() | 0;
}
return hashCode | 0;
};
Array.prototype.mapFilter = function (f) {
const length = this.length, result = [];
for (let i = 0; i < length; i++) {
if (i in this) {
const val = f(this[i], i, this);
if (val) {
result.push(val);
}
}
}
return result;
};
Array.prototype.flatMap = function (f) {
return Array.prototype.concat.apply([], this.map(f));
};
Array.prototype.clear = function (...newItems) {
return this.splice(0, this.length, ...newItems);
};
/**
*
* @returns Array.prototype.concat.apply([], this)
*/
Array.prototype.concatenated = function () {
return Array.prototype.concat.apply([], this);
};
Array.prototype.min = function () {
let i = this.length, max = Infinity;
while (i--) {
const val = this[i];
if (max > val)
max = val;
}
return max;
};
Array.prototype.max = function () {
// faster and no limit on array size, see https://jsperf.com/math-max-apply-vs-loop/2
let i = this.length, max = -Infinity;
while (i--) {
const val = this[i];
if (max < val)
max = val;
}
return max;
};
Array.prototype.indexWithMax = function (f) {
if (this.length == 0) {
return -1;
}
let i = this.length, result = -1, maxVal = -Infinity;
while (i--) {
const val = f(this[i], i, this);
if (val > maxVal) {
maxVal = val;
result = i;
}
}
return result;
};
Array.prototype.withMax = function (f) {
let i = this.length, result = undefined, maxVal = -Infinity;
while (i--) {
const el = this[i], val = f(el, i, this);
if (val > maxVal) {
maxVal = val;
result = el;
}
}
return result;
};
/**
Returns the sum of the absolute values of the components of this vector.
E.g. V(1, -2, 3) === abs(1) + abs(-2) + abs(3) === 1 + 2 + 3 === 6
*/
Array.prototype.absSum = function () {
let i = this.length;
let result = 0;
while (i--) {
result += Math.abs(this[i]);
}
return result;
};
Array.prototype.sum = function () {
let i = this.length;
let result = 0;
while (i--) {
result += this[i];
}
return result;
};
Array.prototype.sumInPlaceTree = function () {
if (0 == this.length)
return 0;
let l = this.length;
while (l != 1) {
const lHalfFloor = Math.floor(l / 2);
const lHalfCeil = Math.ceil(l / 2);
for (let i = 0; i < lHalfFloor; i++) {
this[i] += this[i + lHalfCeil];
}
l = lHalfCeil;
}
return this[0];
};
Array.prototype.isEmpty = function () {
return 0 == this.length;
};
Array.prototype.unique = function () {
const uniqueSet = new Set(this);
return Array.from(uniqueSet);
};
Array.prototype.remove = function (o) {
const index = this.indexOf(o);
if (index != -1) {
this.splice(index, 1);
return true;
}
return false;
};
Array.prototype.removeIndex = function (i) {
const result = this[i];
this.splice(i, 1);
return result;
};
Array.prototype.bagRemoveIndex = function (i) {
const result = this[i];
if (i == this.length - 1) {
this.pop();
}
else {
this[i] = this.pop();
}
return result;
};
Array.prototype.removeMatch = function (matcher) {
const index = this.findIndex(matcher);
if (-1 != index) {
return this.removeIndex(index);
}
};
Array.prototype.removeAll = function (o) {
let i = o.length;
while (i--) {
this.remove(o[i]);
}
};
Array.prototype.toggle = function (o) {
const index = this.indexOf(o);
if (index != -1) {
this.splice(index, 1);
return false;
}
else {
this.push(o);
return true;
}
};
Array.prototype.bagToggle = function (o) {
const index = this.indexOf(o);
if (index != -1) {
this.bagRemoveIndex(index);
return false;
}
else {
this.push(o);
return true;
}
};
Array.prototype.binaryIndexOf = function (searchElement, cmp = (a, b) => a - b) {
let minIndex = 0;
let maxIndex = this.length - 1;
let currentIndex;
let currentElement;
while (minIndex <= maxIndex) {
currentIndex = (minIndex + maxIndex) / 2 | 0;
currentElement = this[currentIndex];
if (cmp(currentElement, searchElement) < 0) {
minIndex = currentIndex + 1;
}
else if (cmp(currentElement, searchElement) > 0) {
maxIndex = currentIndex - 1;
}
else {
return currentIndex;
}
}
return -minIndex - 1;
};
Array.prototype.binaryInsert = function (el, cmp = MINUS) {
let minIndex = 0;
let maxIndex = this.length;
let currentIndex;
let currentElement;
while (minIndex < maxIndex) {
currentIndex = ~~((minIndex + maxIndex) / 2);
currentElement = this[currentIndex];
if (cmp(currentElement, el) < 0) {
minIndex = currentIndex + 1;
}
else {
maxIndex = currentIndex;
}
}
this.splice(minIndex, 0, el);
};
Object.defineProperty(Array.prototype, 'last', {
get() {
return this[this.length - 1];
},
set(val) {
this[this.length - 1] = val;
},
});
String.prototype.capitalizeFirstLetter = function () {
return this.charAt(0).toUpperCase() + this.slice(1);
};
String.prototype.equals = function (x) {
return this == x;
};
function SCE(o) {
switch (typeof o) {
case 'undefined':
return 'undefined';
case 'function':
return o.toString();
case 'number':
return '' + o;
case 'string':
return JSON.stringify(o);
case 'object':
if (null == o) {
return 'null';
}
else {
return o.sce;
}
default:
throw new Error();
}
}
function STR(o) {
return o.str;
}
Object.defineProperty(Object.prototype, 'sce', { get: function () { return this.toSource(); } });
Object.defineProperty(Object.prototype, 'str', { get: function () { return this.toString(); } });
if (!Object.prototype.toSource) {
Object.defineProperty(Object.prototype, 'toSource', { value: function () { return tosource(this); } });
}
//const NLA = {}
//for (let key in ARRAY_UTILITIES) {
// const nlaName = 'array' + key.capitalizeFirstLetter()
// assert(!NLA[nlaName])
// NLA[nlaName] = (arr, ...rest) => ARRAY_UTILITIES[key].apply(arr, rest)
//}
function isCCW(vertices, normal) {
const dsa = doubleSignedArea(vertices, normal);
assert(0 != dsa);
return dsa < 0;
}
function doubleSignedArea(vertices, normal) {
assert(!normal.likeO(), '!normal.likeO()');
const absMaxDim = normal.maxAbsDim();
// order is important, coord0 and coord1 must be set so that coord0, coord1 and maxDim span a right-hand coordinate
// system var [coord0, coord1] = [['y', 'z'], ['z', 'x'], ['x', 'y']][maxAbsDim]
const doubleSignedArea = vertices.map((v0, i, vertices) => {
const v1 = vertices[(i + 1) % vertices.length];
//return (v1[coord0] - v0[coord0]) * (v1[coord1] + v0[coord1])
switch (absMaxDim) {
case 0:
return (v1.y - v0.y) * (v1.z + v0.z);
case 1:
return (v1.z - v0.z) * (v1.x + v0.x);
case 2:
return (v1.x - v0.x) * (v1.y + v0.y);
}
}).reduce((a, b) => a + b);
return snap(doubleSignedArea * Math.sign(normal.e(absMaxDim)), 0);
}
/**
* solves x² + px + q = 0
*/
function pqFormula(p, q) {
// 4 times the discriminant:in
const discriminantX4 = p * p / 4 - q;
if (discriminantX4 < -NLA_PRECISION) {
return [];
}
else if (discriminantX4 <= NLA_PRECISION) {
return [-p / 2];
}
else {
const root = Math.sqrt(discriminantX4);
return [-p / 2 - root, -p / 2 + root];
}
}
/**
* from pomax' library
* solves ax³ + bx² + cx + d = 0
* This function from pomax' utils
* @returns 0-3 roots
*/
function solveCubicReal2(a, b, c, d) {
if (eq0(a)) {
if (eq0(b)) {
return [-d / c];
}
else {
return pqFormula(c / b, d / b);
}
}
const divisor = a;
a = b / divisor;
b = c / divisor;
c = d / divisor;
const p = (3 * b - a * a) / 3, pDiv3 = p / 3, pDiv3Pow3 = pDiv3 * pDiv3 * pDiv3, q = (2 * a * a * a - 9 * a * b + 27 * c) / 27, qDiv2 = q / 2, discriminant = qDiv2 * qDiv2 + pDiv3Pow3;
// 18abcd - 4b³d + b²c² - 4ac³ - 27a²d²
if (discriminant < -NLA_PRECISION / 8) {
const r = Math.sqrt(-pDiv3Pow3), t = -q / (2 * r), cosphi = t < -1 ? -1 : t > 1 ? 1 : t, // clamp t to [-1;1]
phi = Math.acos(cosphi), t1 = 2 * Math.cbrt(r);
const x1 = t1 * Math.cos(phi / 3) - a / 3;
const x2 = t1 * Math.cos((phi + 2 * Math.PI) / 3) - a / 3;
const x3 = t1 * Math.cos((phi + 4 * Math.PI) / 3) - a / 3;
return [x1, x2, x3];
}
else if (discriminant <= NLA_PRECISION / 8) {
if (0 == qDiv2) {
// TODO: compare with likeO?
return [-a / 3];
}
const u1 = qDiv2 < 0 ? Math.cbrt(-qDiv2) : -Math.cbrt(qDiv2);
const x1 = 2 * u1 - a / 3;
const x2 = -u1 - a / 3;
return [x1, x2];
}
else {
const sd = Math.sqrt(discriminant);
const u1 = Math.cbrt(-qDiv2 + sd);
const v1 = Math.cbrt(qDiv2 + sd);
return [u1 - v1 - a / 3];
}
}
function checkDerivate(f, df, a, b, maxFaults = 0) {
const eps = 1e-4;
let faults = 0;
for (let t = a; t < b; t += (b - a) / 100) {
const dfdt = df(t);
const df2 = (f(t + eps) - f(t)) / eps;
assert((faults += +!eq2(df2, dfdt, 0.1)) <= maxFaults, `df2 == ${df2} != ${df(t)} = df(t)`);
}
}
function getRoots(f, a, b, stepSize, df) {
const results = [];
for (let startT = a; startT <= b; startT += stepSize) {
const dt = stepSize * abs$1(df(startT));
if (abs$1(f(startT)) <= dt) {
//const t = newtonIterate1d(f, startT, 16)
let t = newtonIterateWithDerivative(f, startT, 16, df);
if (!eq0(f(t)) || eq0(df(t))) {
t = newtonIterate1d(df, startT, 16);
//if (f(a) * f(b) < 0) {
// t = bisect(f, a, b, 16)
//} else if (df(a) * df(b) < 0) {
// t = bisect(df, a, b, 16)
//}
}
if (eq0(f(t)) && !results.some(r => eq(r, t))) {
results.push(t);
}
}
}
return results;
}
//addOwnProperties(Array.prototype, ARRAY_UTILITIES)
function bisect(f, a, b, steps) {
assert(a < b);
let fA = f(a), fB = f(b);
while (steps--) {
const c = (b + a) / 2;
const fC = f(c);
if (sign$1(fA) == sign$1(fC)) {
a = c;
fA = fC;
}
else {
b = c;
fB = fC;
}
}
assert(a <= (b + a) / 2);
assert(b >= (b + a) / 2);
return (b - a) / 2;
}
function newtonIterate(f, x, steps = 4, EPSILON) {
EPSILON = EPSILON || 1e-8;
for (let i = 0; i < steps; i++) {
const fx = f(x);
const dfdx = Matrix.jacobi(f, x, fx, EPSILON);
assert(!dfdx.isZero());
const dx = dfdx.solveLinearSystem(new Vector(new Float64Array(fx))).v;
assert(!isNaN(dx[0]));
//console.log('fx / dfdx', fx / dfdx)
for (let j = 0; j < x.length; j++)
x[j] -= dx[j];
}
return x;
}
function newtonIterate1d(f, xStart, steps = 8, EPSILON = 1e-8) {
let x = xStart;
for (let i = 0; i < steps; i++) {
const fx = f(x);
const dfdx = (f(x + EPSILON) - fx) / EPSILON;
//console.log('fx / dfdx', fx / dfdx)
x = x - fx / dfdx;
}
return x;
}
function newtonIterateWithDerivative(f, xStart, steps = 4, df) {
let x = xStart;
for (let i = 0; i < steps; i++) {
const fx = f(x);
const dfdx = df(x);
if (isNaN(fx) || isNaN(dfdx)) {
console.log();
//console.log('fx / dfdx', fx / dfdx)
}
x = x - fx / dfdx;
if (isNaN(fx)) {
console.log();
//console.log('fx / dfdx', fx / dfdx)
}
}
return x;
}
function newtonIterateSmart(f, xStart, steps = 4, df, mindf = 1e-6) {
let x = xStart;
for (let i = 0; i < steps; i++) {
const fx = f(x);
const dfdx = df(x);
if (abs$1(dfdx) < mindf && abs$1(fx) < mindf) {
return newtonIterate1d(df, x);
}
if (isNaN(fx) || isNaN(dfdx)) {
console.log();
//console.log('fx / dfdx', fx / dfdx)
}
x = x - fx / dfdx;
if (isNaN(fx)) {
console.log();
//console.log('fx / dfdx', fx / dfdx)
}
}
return x;
}
function newtonIterate2d(f1, f2, sStart, tStart, steps) {
const EPSILON = 1e-6;
steps = steps || 4;
let s = sStart, t = tStart, f1ts, f2ts;
do {
/*
| a b |-1 | d -b |
| c d | = 1 / (ad - bc) * | -c a |
*/
f1ts = f1(s, t);
f2ts = f2(s, t);
/*
let df1s = (f1(s + EPSILON, t) - f1ts) / EPSILON, df1t = (f1(s, t + EPSILON) - f1ts) / EPSILON,
df2s = (f2(s + EPSILON, t) - f2ts) / EPSILON, df2t = (f2(s, t + EPSILON) - f2ts) / EPSILON
let det = df1s * df2t - df1t * df2s
s = s - ( df2t * f1ts - df1t * f2ts) / det
t = t - (-df2s * f1ts + df1s * f2ts) / det
*/
// TODO: is this even more accurate?
const df1s = (f1(s + EPSILON, t) - f1ts), df1t = (f1(s, t + EPSILON) - f1ts), df2s = (f2(s + EPSILON, t) - f2ts), df2t = (f2(s, t + EPSILON) - f2ts);
const det = (df1s * df2t - df1t * df2s) / EPSILON;
const ds = (df2t * f1ts - df1t * f2ts) / det;
const dt = (-df2s * f1ts + df1s * f2ts) / det;
s -= ds;
t -= dt;
} while (--steps && f1ts * f1ts + f2ts * f2ts > NLA_PRECISION);
if (!steps) {
//console.log(f1ts * f1ts + f2ts * f2ts)
return undefined;
}
return new V3(s, t, 0);
}
function newtonIterate2dWithDerivatives(f, g, sStart, tStart, steps, dfds, dfdt, dgds, dgdt) {
steps = steps || 4;
let s = sStart, t = tStart;
let f1ts, f2ts;
do {
/*
| a b |-1 | d -b |
| c d | = 1 / (ad - bc) * | -c a |
*/
f1ts = f(s, t);
f2ts = g(s, t);
const df1s = dfds(s, t), df1t = dfdt(s, t), df2s = dgds(s, t), df2t = dgdt(s, t);
// TODO: is this even more accurate?
const det = df1s * df2t - df1t * df2s;
const ds = (df2t * f1ts - df1t * f2ts) / det;
const dt = (-df2s * f1ts + df1s * f2ts) / det;
s -= ds;
t -= dt;
} while (--steps && f1ts * f1ts + f2ts * f2ts > NLA_PRECISION / 32);
if (!steps) {
//console.log(f1ts * f1ts + f2ts * f2ts)
return undefined;
}
return V(s, t, 0);
}
const gaussLegendre24Xs = [
-0.0640568928626056260850430826247450385909,
0.0640568928626056260850430826247450385909,
-0.1911188674736163091586398207570696318404,
0.1911188674736163091586398207570696318404,
-0.3150426796961633743867932913198102407864,
0.3150426796961633743867932913198102407864,
-0.4337935076260451384870842319133497124524,
0.4337935076260451384870842319133497124524,
-0.5454214713888395356583756172183723700107,
0.5454214713888395356583756172183723700107,
-0.6480936519369755692524957869107476266696,
0.6480936519369755692524957869107476266696,
-0.7401241915785543642438281030999784255232,
0.7401241915785543642438281030999784255232,
-0.8200019859739029219539498726697452080761,
0.8200019859739029219539498726697452080761,
-0.8864155270044010342131543419821967550873,
0.8864155270044010342131543419821967550873,
-0.9382745520027327585236490017087214496548,
0.9382745520027327585236490017087214496548,
-0.9747285559713094981983919930081690617411,
0.9747285559713094981983919930081690617411,
-0.9951872199970213601799974097007368118745,
0.9951872199970213601799974097007368118745,
];
const gaussLegendre24Weights = [
0.1279381953467521569740561652246953718517,
0.1279381953467521569740561652246953718517,
0.1258374563468282961213753825111836887264,
0.1258374563468282961213753825111836887264,
0.1216704729278033912044631534762624256070,
0.1216704729278033912044631534762624256070,
0.1155056680537256013533444839067835598622,
0.1155056680537256013533444839067835598622,
0.1074442701159656347825773424466062227946,
0.1074442701159656347825773424466062227946,
0.0976186521041138882698806644642471544279,
0.0976186521041138882698806644642471544279,
0.0861901615319532759171852029837426671850,
0.0861901615319532759171852029837426671850,
0.0733464814110803057340336152531165181193,
0.0733464814110803057340336152531165181193,
0.0592985849154367807463677585001085845412,
0.0592985849154367807463677585001085845412,
0.0442774388174198061686027482113382288593,
0.0442774388174198061686027482113382288593,
0.0285313886289336631813078159518782864491,
0.0285313886289336631813078159518782864491,
0.0123412297999871995468056670700372915759,
0.0123412297999871995468056670700372915759,
];
function gaussLegendreQuadrature24(f, startT, endT) {
//let result = 0
//for (let i = 0; i < gaussLegendre24Xs.length; i++) {
// // gauss-legendre goes from -1 to 1, so we need to scale
// let t = startT + (gaussLegendre24Xs[i] + 1) / 2 * (endT - startT)
// result += gaussLegendre24Weights[i] * f(t)
//}
//const result = NLA
// .arrayFromFunction(24, i => startT + (gaussLegendre24Xs[i] + 1) / 2 * (endT - startT))
// .map((t, i) => gaussLegendre24Weights[i] * f(t))
// .sumInPlaceTree()
//99.54182500782605
//99.54182500782602
// again, [-1,1], so div by 2
//return result // 2 * (endT - startT)
return glq24_11(t => f(startT + (t + 1) / 2 * (endT - startT))) / 2 * (endT - startT);
}
function glq24_11(f) {
return arrayFromFunction(24, i => gaussLegendre24Weights[i] * f(gaussLegendre24Xs[i])).sumInPlaceTree();
}
function glqInSteps(f, startT, endT, steps) {
const dt = (endT - startT) / steps;
return arrayFromFunction(steps, i => glq24_11(t => f(startT + dt * i + (t + 1) / 2 * dt))).sumInPlaceTree() / 2 * dt;
}
function midpointRuleQuadrature(f, startT, endT, steps = 32) {
const dt = (endT - startT) / steps;
return arrayFromFunction(steps, i => startT + dt / 2 + dt * i).map(f).sumInPlaceTree() * dt;
}
function callsce(name, ...params) {
return name + '(' + params.map(SCE).join(',') + ')';
}
/**
* Immutable 3d-vector/point.
*/
class V3 {
constructor(x, y, z) {
this.x = x;
this.y = y;
this.z = z;
assertNumbers(x, y, z);
}
static random() {
return new V3(Math.random(), Math.random(), Math.random());
}
static parallel(a, b) {
return a.dot(b) - a.length() * b.length();
}
/**
* See http://math.stackexchange.com/questions/44689/how-to-find-a-random-axis-or-unit-vector-in-3d
* @returns A random point on the unit sphere with uniform distribution across the surface.
*/
static randomUnit() {
const zRotation = Math.random() * 2 * Math.PI;
const z = Math.random() * 2 - 1;
const zRadius = Math.sqrt(1 - Math.pow(z, 2));
return new V3(zRadius * Math.cos(zRotation), zRadius * Math.sin(zRotation), z);
}
//noinspection JSUnusedLocalSymbols
/**
* Documentation stub. You want {@see V3#sphere}
*/
static fromAngles(theta, phi) {
throw new Error();
}
static fromFunction(f) {
return new V3(f(0), f(1), f(2));
}
static min(a, b) {
return new V3(Math.min(a.x, b.x), Math.min(a.y, b.y), Math.min(a.z, b.z));
}
static max(a, b) {
return new V3(Math.max(a.x, b.x), Math.max(a.y, b.y), Math.max(a.z, b.z));
}
static lerp(a, b, fraction) {
return b.minus(a).times(fraction).plus(a);
}
static fromArray(a) {
return new V3(a[0], a[1], a[2]);
}
static angleBetween(a, b) {
return a.angleTo(b);
}
static zip(f, ...args) {
assert(f instanceof Function);
return new V3(f.apply(undefined, args.map(x => x.x)), f.apply(undefined, args.map(x => x.y)), f.apply(undefined, args.map(x => x.z)));
}
static normalOnPoints(a, b, c) {
assertVectors(a, b, c);
return a.to(b).cross(a.to(c));
}
static add(...vs) {
assertVectors.apply(undefined, vs);
let x = 0, y = 0, z = 0;
let i = vs.length;
while (i--) {
x += vs[i].x;
y += vs[i].y;
z += vs[i].z;
}
return new V3(x, y, z);
}
static sub(...vs) {
assertVectors.apply(undefined, vs);
let x = vs[0].x, y = vs[0].y, z = vs[0].z;
let i = vs.length;
while (i--) {
x -= vs[i].x;
y -= vs[i].y;
z -= vs[i].z;
}
return new V3(x, y, z);
}
/**
* Pack an array of V3s into an array of numbers (Float32Array by default).
*
* @param v3arr source array
* @param dest destination array. If provided, must be large enough to fit v3count items.
* @param srcStart starting index in source array
* @param destStart starting index in destination array
* @param v3count Number of V3s to copy.
* @returns Packed array.
*/
static pack(v3arr, dest, srcStart = 0, destStart = 0, v3count = v3arr.length - srcStart) {
//assert (v3arr.every(v3 => v3 instanceof V3), 'v3arr.every(v3 => v3 instanceof V3)')
const result = dest || new Float32Array(3 * v3count); // TODO
assert(result.length - destStart >= v3count * 3, 'dest.length - destStart >= v3count * 3', result.length, destStart, v3count * 3);
let i = v3count, srcIndex = srcStart, destIndex = destStart;
while (i--) {
const v = v3arr[srcIndex++];
result[destIndex++] = v.x;
result[destIndex++] = v.y;
result[destIndex++] = v.z;
}
return result;
}
static unpack(packedArray, dest, srcStart = 0, destStart = 0, v3count = (packedArray.length - srcStart) / 3) {
//assert (v3arr.every(v3 => v3 instanceof V3), 'v3arr.every(v3 => v3 instanceof V3)')
const result = dest || new Array(v3count);
assert(result.length - destStart >= v3count, 'dest.length - destStart >= v3count');
let i = v3count, srcIndex = srcStart, destIndex = destStart;
while (i--) {
result[destIndex++] = new V3(packedArray[srcIndex++], packedArray[srcIndex++], packedArray[srcIndex++]);
}
return result;
}
static packXY(v3arr, dest, srcStart = 0, destStart = 0, v3count = v3arr.length - srcStart) {
//assert (v3arr.every(v3 => v3 instanceof V3), 'v3arr.every(v3 => v3 instanceof V3)')
const result = dest || new Float32Array(2 * v3count);
assert(result.length - destStart >= v3count, 'dest.length - destStart >= v3count');
let i = v3count, srcIndex = srcStart, destIndex = destStart;
while (i--) {
const v = v3arr[srcIndex++];
result[destIndex++] = v.x;
result[destIndex++] = v.y;
}
return result;
}
static unpackXY(src, dest, srcStart = 0, destStart = 0, v3count = Math.min(src.length / 2, dest && dest.length || Infinity) - destStart) {
//assert (v3arr.every(v3 => v3 instanceof V3), 'v3arr.every(v3 => v3 instanceof V3)')
dest = dest || new Array(v3count);
assert(dest.length - destStart >= v3count, 'dest.length - destStart >= v3count');
assert(src.length - srcStart >= v3count * 2, 'dest.length - destStart >= v3count');
let i = v3count, srcIndex = srcStart, destIndex = destStart;
while (i--) {
dest[destIndex++] = new V3(src[srcIndex++], src[srcIndex++], 0);
}
return dest;
}
static perturbed(v, delta) {
return v.perturbed(delta);
}
static polar(radius, phi, z = 0) {
return new V3(radius * Math.cos(phi), radius * Math.sin(phi), z);
}
/**
*
* @param longitude angle in XY plane
* @param latitude "height"/z dir angle
*/
static sphere(longitude, latitude, length = 1) {
return new V3(length * Math.cos(latitude) * Math.cos(longitude), length * Math.cos(latitude) * Math.sin(longitude), length * Math.sin(latitude));
}
static inverseLerp(a, b, x) {
const ab = a.to(b);
return a.to(x).dot(ab) / ab.squared();
}
perturbed(delta = NLA_PRECISION * 0.8) {
return this.map(x => x + (Math.random() - 0.5) * delta);
}
*[Symbol.iterator]() {
yield this.x;
yield this.y;
yield this.z;
}
e(index) {
assert(index >= 0 && index < 3);
return 0 == index ? this.x : (1 == index ? this.y : this.z);
}
negated() {
return new V3(-this.x, -this.y, -this.z);
}
abs() {
return new V3(Math.abs(this.x), Math.abs(this.y), Math.abs(this.z));
}
plus(a) {
assertVectors(a);
return new V3(this.x + a.x, this.y + a.y, this.z + a.z);
}
/**
* Hadarmard product (or Schur product)
* Element-wise multiplication of two vectors.
* @see https://en.wikipedia.org/wiki/Hadamard_product_(matrices)
*
*/
schur(a) {
return new V3(this.x * a.x, this.y * a.y, this.z * a.z);
}
/**
* Element-wise division.
*/
divv(a) {
return new V3(this.x / a.x, this.y / a.y, this.z / a.z);
}
/**
* See also {@link to} which is a.minus(this)
*/
minus(a) {
assertVectors(a);
return new V3(this.x - a.x, this.y - a.y, this.z - a.z);
}
to(a) {
assertVectors(a);
return a.minus(this);
}
times(factor) {
assertNumbers(factor);
return new V3(this.x * factor, this.y * factor, this.z * factor);
}
div(a) {
assertNumbers(a);
return new V3(this.x / a, this.y / a, this.z / a);
}
/**
* Dot product.
* @see https://en.wikipedia.org/wiki/Dot_product
*/
dot(a) {
assertInst(V3, a);
return this.x * a.x + this.y * a.y + this.z * a.z;
}
/**
* Linearly interpolate
*/
lerp(b, t) {
assertVectors(b);
assertNumbers(t);
return this.plus(b.minus(this).times(t));
}
squared() {
return this.dot(this);
}
distanceTo(a) {
assertVectors(a);
//return this.minus(a).length()
return Math.hypot(this.x - a.x, this.y - a.y, this.z - a.z);
}
distanceToSquared(a) {
assertVectors(a);
return this.minus(a).squared();
}
///**
// * See also {@see #setTo} for the individual
// *
// * @param v
// */
//assign(v) {
// assertVectors(v)
// this.x = v.x
// this.y = v.y
// this.z = v.z
//}
//
///**
// * See also {@see #assign} for the V3 version
// *
// * @param x
// * @param y
// * @param z
// */
//setTo(x, y, z = 0) {
// this.x = x
// this.y = y
// this.z = z
//}
toSource() {
return V3.NAMEMAP.get(this) || this.toString();
}
nonParallelVector() {
const abs = this.abs();
if ((abs.x <= abs.y) && (abs.x <= abs.z)) {
return V3.X;
}
else if ((abs.y <= abs.x) && (abs.y <= abs.z)) {
return V3.Y;
}
else {
return V3.Z;
}
}
slerp(b, t) {
assertVectors(b);
assertNumbers(t);
const sin = Math.sin;
const omega = this.angleTo(b);
return this.times(sin((1 - t) * omega) / sin(omega)).plus(b.times(sin(t * omega) / sin(omega)));
}
min(b) {
return new V3(Math.min(this.x, b.x), Math.min(this.y, b.y), Math.min(this.z, b.z));
}
max(b) {
return new V3(Math.max(this.x, b.x), Math.max(this.y, b.y), Math.max(this.z, b.z));
}
equals(v) {
return this == v || this.x == v.x && this.y == v.y && this.z == v.z;
}
/**
*
* The cross product is defined as:
* a x b = |a| * |b| * sin(phi) * n
* where |.| is the euclidean norm, phi is the angle between the vectors
* and n is a unit vector perpendicular to both a and b.
*
* The cross product is zero for parallel vectors.
* @see https://en.wikipedia.org/wiki/Cross_product
*/
cross(v) {
return new V3(this.y * v.z - this.z * v.y, this.z * v.x - this.x * v.z, this.x * v.y - this.y * v.x);
}
//noinspection JSMethodCanBeStatic
/**
* Documentation stub. You want {@link unit}
*/
normalized() { throw new Error('documentation stub. use .unit()'); }
minElement() {
return Math.min(this.x, this.y, this.z);
}
maxElement() {
return Math.max(this.x, this.y, this.z);
}
toArray(n = 3) {
return [this.x, this.y, this.z].slice(0, n);
}
/**
* Get a perpendicular vector.
* For vectors in the XY-Plane, returns vector rotated 90° CCW.
*/
getPerpendicular() {
if (eq0(this.x) && eq0(this.y)) {
if (eq0(this.z)) {
throw new Error('zero vector');
}
// v is Vector(0, 0, v.z)
return V3.Y;
}
return new V3(-this.y, this.x, 0);
}
//noinspection JSMethodCanBeStatic
dim() {
return 3;
}
els() {
return [this.x, this.y, this.z];
}
angleXY() {
return Math.atan2(this.y, this.x);
}
lengthXY() {
return Math.hypot(this.x, this.y);
//return Math.sqrt(this.x * this.x + this.y * this.y)
}
squaredXY() {
return this.x * this.x + this.y * this.y;
}
xy() {
return new V3(this.x, this.y, 0);
}
/**
* Transform this vector element-wise by way of function f. Returns V3(f(x), f(y), f(z))
* @param f function to apply to elements (number -> number)
*/
map(f) {
return new V3(f(this.x, 'x'), f(this.y, 'y'), f(this.z, 'z'));
}
toString(roundFunction) {
roundFunction = roundFunction || defaultRoundFunction;
return V3.NAMEMAP.get(this) ||
'V(' + [this.x, this.y, this.z].map(roundFunction).join(', ') + ')'; //+ this.id
}
angleTo(b) {
assert(1 == arguments.length);
assertVectors(b);
assert(!this.likeO());
assert(!b.likeO());
return Math.acos(Math.min(1, this.dot(b) / this.length() / b.length()));
}
/**
*
* phi = angle between A and B
* alpha = angle between n and normal1
*
* A . B = ||A|| * ||B|| * cos(phi)
* A x B = ||A|| * ||B|| * sin(phi) * n (n = unit vector perpendicular)
* (A x B) . normal1 = ||A|| * ||B|| * sin(phi) * cos(alpha)
*/
angleRelativeNormal(vector, normal1) {
assertf(() => 2 == arguments.length);
assertVectors(vector, normal1);
assertf(() => normal1.hasLength(1));
//assert(vector.isPerpendicularTo(normal1), 'vector.isPerpendicularTo(normal1)' + vector.sce + normal1.sce)
//assert(this.isPerpendicularTo(normal1), 'this.isPerpendicularTo(normal1)' + this.dot(vector)) //
// -0.000053600770598683675
return Math.atan2(this.cross(vector).dot(normal1), this.dot(vector));
}
/**
Returns true iff this is parallel to vector, i.e. this * s == vector, where s is a pos or neg number, using equals
Throw a DebugError
if vector is not a Vector or
if this has a length of 0 or
if vector has a length of 0
*/
isParallelTo(vector) {
assertVectors(vector);
assert(!this.likeO());
assert(!vector.likeO());
// a . b takes on values of +|a|*|b| (vectors same direction) to -|a|*|b| (opposite direction)
// in both cases the vectors are parallel, so check if abs(a . b) == |a|*|b|
const dot = this.dot(vector);
return eq(this.squared() * vector.squared(), dot * dot);
}
isPerpendicularTo(vector) {
assertVectors(vector);
assert(!this.likeO(), '!this.likeO()');
assert(!vector.likeO(), '!vector.likeO()');
return eq0(this.dot(vector));
}
isReverseDirTo(other) {
assertVectors(other);
assert(!this.likeO());
assert(!other.likeO());
// a . b takes on values of +|a|*|b| (vectors same direction) to -|a|*|b| (opposite direction)
// in both cases the vectors are parallel, so check if abs(a . b) == |a|*|b|
const dot = this.dot(other);
return eq(Math.sqrt(this.squared() * other.squared()), dot);
}
/**
* Returns the length of this Vector, i.e. the euclidean norm.
*
* Note that the partial derivatives of the euclidean norm at point x are equal to the
* components of the unit vector x.
*/
length() {
return Math.hypot(this.x, this.y, this.z);
//return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z)
}
/**
* Definition: V3.likeO == V3.like(V3.O)
*/
likeO() {
return this.like(V3.O);
}
like(obj) {
if (obj === this)
return true;
if (!(obj instanceof V3))
return false;
return eq(this.x, obj.x) && eq(this.y, obj.y) && eq(this.z, obj.z);
}
/**
* equivalent to this.like(v) || this.negated().like(v)
*/
likeOrReversed(v) {
return eq(Math.abs(this.dot(v)), Math.sqrt(this.squared() * v.squared()));
}
/**
* Returns a new unit Vector (.length() === 1) with the same direction as this vector. Throws a
* DebugError if this has a length of 0.
*/
unit() {
assert(!this.likeO(), 'cannot normalize zero vector');
return this.div(this.length());
}
/**
* Returns a new V3 equal to this scaled so that its length is equal to newLength.
*
* Passing a negative newLength will flip the vector.
*/
toLength(newLength) {
assertNumbers(newLength);
return this.times(newLength / this.length());
}
/**
Returns a new Vector which is the projection of this vector onto the passed vector.
Examples
V(3, 4).projectedOn(V(1, 0)) // returns V(3, 0)
V(3, 4).projectedOn(V(2, 0)) // returns V(3, 0)
V(3, 4).projectedOn(V(-1, 0)) // returns V(-3, 0)
V(3, 4).projectedOn(V(0, 1)) // returns V(0, 4)
V(3, 4).projectedOn(V(1, 1)) // returns
*/
projectedOn(b) {
assertVectors(b);
// https://en.wikipedia.org/wiki/Vector_projection#Vector_projection_2
return b.times(this.dot(b) / b.dot(b));
}
rejectedFrom(b) {
assertVectors(b);
// https://en.wikipedia.org/wiki/Vector_projection#Vector_projection_2
return this.minus(b.times(this.dot(b) / b.dot(b)));
}
rejectedFrom1(b1) {
assertVectors(b1);
assert(b1.hasLength(1));
// https://en.wikipedia.org/wiki/Vector_projection#Vector_projection_2
return this.minus(b1.times(this.dot(b1)));
}
/**
* Returns the length of this vector rejected from the unit vector b.
*
* /|
* this / | ^
* /__| | b
* r
* Returns length of r (r === this.rejectedFrom(b))
*/
rejectedLength(b) {
assertVectors(b);
return Math.sqrt(this.dot(this) - Math.pow(this.dot(b), 2) / b.dot(b));
}
/**
* Returns the length of this vector rejected from the unit vector b1.
*
* /|
* this / | ^
* /__| | b1
* r
* Returns length of r (r === this.rejectedFrom(b1))
*/
rejected1Length(b1) {
assertVectors(b1);
assert(b1.hasLength(1));
return Math.sqrt(this.dot(this) - Math.pow(this.dot(b1), 2));
}
/**
Returns true iff the length() of this vector is equal to 'length', using eq
E.g. V(3, 4).hasLength(5) === true
V(1, 1).hasLength(1) === false
*/
hasLength(length) {
assertNumbers(length);
return eq(length, this.length());
}
/**
Returns the sum of the absolute values of the components of this vector.
E.g. V(1, -2, 3) === abs(1) + abs(-2) + abs(3) === 1 + 2 + 3 === 6
*/
absSum() {
return Math.abs(this.x) + Math.abs(this.y) + Math.abs(this.z);
}
/**
* returns max(|x|, |y|, |z|)
*/
maxAbsElement() {
return Math.max(Math.abs(this.x), Math.abs(this.y), Math.abs(this.z));
}
/**
* returns min(|x|, |y|, |z|)
*/
minAbsElement() {
return Math.min(Math.abs(this.x), Math.abs(this.y), Math.min(this.z));
}
maxAbsDim() {
const xAbs = Math.abs(this.x), yAbs = Math.abs(this.y), zAbs = Math.abs(this.z);
return xAbs >= yAbs ? (xAbs >= zAbs ? 0 : 2) : (yAbs >= zAbs ? 1 : 2);
}
minAbsDim() {
const xAbs = Math.abs(this.x), yAbs = Math.abs(this.y), zAbs = Math.abs(this.z);
return xAbs < yAbs ? (xAbs < zAbs ? 0 : 2) : (yAbs < zAbs ? 1 : 2);
}
withElement(dim, el) {
assert(['x', 'y', 'z'].includes(dim), '' + dim);
assertNumbers(el);
if ('x' == dim) {
return new V3(el, this.y, this.z);
}
if ('y' == dim) {
return new V3(this.x, el, this.z);
}
return new V3(this.x, this.y, el);
}
hashCode() {
function floatHashCode$$1(f) {
return ~~(f * (1 << 28));
}
return ~~((floatHashCode$$1(this.x) * 31 + floatHashCode$$1(this.y)) * 31 + floatHashCode$$1(this.z));
}
hashCodes() {
//function floatHashCode(f) {
// return ~~(f * (1 << 28))
//}
// compare hashCode.floatHashCode
// the following ops are equivalent to
// floatHashCode((el - NLA_PRECISION) % (2 * NLA_PRECISION))
// this results in the hashCode for the (out of 8 possible) cube with the lowest hashCode
// the other 7 can be calculated by adding constants
const xHC = ~~(this.x * (1 << 28) - 0.5), yHC = ~~(this.y * (1 << 28) - 0.5), zHC = ~~(this.z * (1 << 28) - 0.5), hc = ~~((xHC * 31 + yHC) * 31 + zHC);
return [
~~(hc),
~~(hc + 961),
~~(hc + 31),
~~(hc + 31 + 961),
~~(hc + 1),
~~(hc + 1 + 961),
~~(hc + 1 + 31),
~~(hc + 1 + 31 + 961),
];
}
//static areDisjoint(it: Iterable<V3>): boolean {
// const vSet = new CustomSet
// for (const v of it) {
// if (!v.equals(vSet.canonicalizeLike(v))) {
// // like value already in set
// return false
// }
// }
// return true
//}
compareTo(other) {
if (this.x != other.x) {
return this.x - other.x;
}
else if (this.y != other.y) {
return this.y - other.y;
}
else {
return this.z - other.z;
}
}
compareTo2(other, eps = NLA_PRECISION) {
if (!eq2(this.x, other.x, eps)) {
return this.x - other.x;
}
else if (!eq2(this.y, other.y, eps)) {
return this.y - other.y;
}
else if (!eq2(this.z, other.z, eps)) {
return this.z - other.z;
}
else {
return 0;
}
}
toAngles() {
return {
theta: Math.atan2(this.y, this.x),
phi: Math.asin(this.z / this.length()),
};
}
}
V3.O = new V3(0, 0, 0);
V3.X = new V3(1, 0, 0);
V3.Y = new V3(0, 1, 0);
V3.Z = new V3(0, 0, 1);
V3.XY = new V3(1, 1, 0);
V3.XYZ = new V3(1, 1, 1);
V3.INF = new V3(Infinity, Infinity, Infinity);
V3.UNITS = [V3.X, V3.Y, V3.Z];
V3.NAMEMAP = new JavaMap()
.set(V3.O, 'V3.O')
.set(V3.X, 'V3.X')
.set(V3.Y, 'V3.Y')
.set(V3.Z, 'V3.Z')
.set(V3.XYZ, 'V3.XYZ')
.set(V3.INF, 'V3.INF');
/**
* Utility method for creating V3s
*
* Example usage:
*
* V(1, 2, 3)
* V([1, 2, 3])
* V({ x: 1, y: 2, z: 3 })
* V(1, 2) * assumes z=0
* V([1, 2]) // assumes z=0
*
*/
function V(a, b, c) {
if (arguments.length == 3) {
return new V3(parseFloat(a), parseFloat(b), parseFloat(c));
}
else if (arguments.length == 2) {
return new V3(parseFloat(a), parseFloat(b), 0);
}
else if (arguments.length == 1) {
if (typeof (a) == 'object') {
if (a instanceof V3) {
// immutable, so
return a;
}
else if (a instanceof Array || a instanceof Float32Array || a instanceof Float64Array) {
if (2 == a.length) {
return new V3(parseFloat(a[0]), parseFloat(a[1]), 0);
}
else if (3 == a.length) {
return new V3(parseFloat(a[0]), parseFloat(a[1]), parseFloat(a[2]));
}
}
else if (('x' in a) && ('y' in a)) {
return new V3(parseFloat(a.x), parseFloat(a.y), 'z' in a ? parseFloat(a.z) : 0);
}
}
}
throw new Error('invalid arguments' + arguments);
}
const P3YZ = { normal1: V3.X, w: 0 };
const P3ZX = { normal1: V3.Y, w: 0 };
const P3XY = { normal1: V3.Z, w: 0 };
class Transformable {
mirror(plane) {
return this.transform(M4.mirror(plane));
}
mirroredX() {
return this.mirror(P3YZ);
}
mirrorY() {
return this.mirror(P3ZX);
}
mirrorZ() {
return this.mirror(P3XY);
}
project(plane) {
return this.transform(M4.project(plane));
}
projectXY() {
return this.transform(M4.project(P3XY));
}
projectYZ() {
return this.transform(M4.project(P3YZ));
}
projectZX() {
return this.transform(M4.project(P3ZX));
}
translate(...args) {
return this.transform(M4.translate.apply(undefined, args), callsce.call(undefined, '.translate', ...args));
}
scale(...args) {
return this.transform(M4.scale.apply(undefined, args), callsce.call(undefined, '.scale', ...args));
}
rotateX(radians) {
return this.transform(M4.rotateX(radians), `.rotateX(${radians})`);
}
rotateY(radians) {
return this.transform(M4.rotateY(radians), `.rotateY(${radians})`);
}
rotateZ(radians) {
return this.transform(M4.rotateZ(radians), `.rotateZ(${radians})`);
}
rotate(rotationCenter, rotationAxis, radians) {
return this.transform(M4.rotateLine(rotationCenter, rotationAxis, radians), callsce('.rotate', rotationCenter, rotationAxis, radians));
}
rotateAB(from, to) {
return this.transform(M4.rotateAB(from, to), callsce('.rotateAB', from, to));
}
eulerZXZ(alpha, beta, gamma) {
throw new Error();
//return this.transform(M4.eulerZXZ(alpha, beta, gamma))
}
shearX(y, z) {
return this.transform(new M4([
1, y, z, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
]));
}
foo() {
return this.transform(M4.FOO);
}
bar() {
return this.transform(M4.BAR);
}
visit(visitor, ...args) {
let proto = Object.getPrototypeOf(this);
// walk up the prototype chain until we find a defined function in o
while (!visitor.hasOwnProperty(proto.constructor.name) && proto !== Transformable.prototype) {
proto = Object.getPrototypeOf(proto);
}
if (visitor.hasOwnProperty(proto.constructor.name)) {
return visitor[proto.constructor.name].apply(this, args);
}
else {
throw new Error('No implementation for ' + this.constructor.name);
}
}
}
const { PI: PI$1, abs: abs$1$1 } = Math;
class M4 extends Matrix {
/**
* Takes 16 arguments in row-major order, which can be passed individually, as a list, or even as
* four lists, one for each row. If the arguments are omitted then the identity matrix is constructed instead.
*/
constructor(...var_args) {
let m;
if (0 == arguments.length) {
m = new Float64Array(16);
}
else {
const flattened = Array.prototype.concat.apply([], arguments);
assert(flattened.length == 16, 'flattened.length == 16' + flattened.length);
m = new Float64Array(flattened);
}
super(4, 4, m);
}
get X() {
return this.transformVector(V3.X);
}
get Y() {
return this.transformVector(V3.Y);
}
get Z() {
return this.transformVector(V3.Z);
}
get O() {
return this.getTranslation();
}
/**
* Returns the matrix that when multiplied with `matrix` results in the
* identity matrix. You can optionally pass an existing matrix in `result`
* to avoid allocating a new matrix. This implementation is from the Mesa
* OpenGL function `__gluInvertMatrixd()` found in `project.c`.
*/
static inverse(matrix, result) {
assertInst(M4, matrix);
!result || assertInst(M4, result);
assert(matrix != result, 'matrix != result');
result = result || new M4();
const m = matrix.m, r = result.m;
// first compute transposed cofactor matrix:
// cofactor of an element is the determinant of the 3x3 matrix gained by removing the column and row belonging
// to the element
r[0] = m[5] * m[10] * m[15] - m[5] * m[14] * m[11] - m[6] * m[9] * m[15] + m[6] * m[13] * m[11] + m[7] * m[9] * m[14] - m[7] * m[13] * m[10];
r[1] = -m[1] * m[10] * m[15] + m[1] * m[14] * m[11] + m[2] * m[9] * m[15] - m[2] * m[13] * m[11] - m[3] * m[9] * m[14] + m[3] * m[13] * m[10];
r[2] = m[1] * m[6] * m[15] - m[1] * m[14] * m[7] - m[2] * m[5] * m[15] + m[2] * m[13] * m[7] + m[3] * m[5] * m[14] - m[3] * m[13] * m[6];
r[3] = -m[1] * m[6] * m[11] + m[1] * m[10] * m[7] + m[2] * m[5] * m[11] - m[2] * m[9] * m[7] - m[3] * m[5] * m[10] + m[3] * m[9] * m[6];
r[4] = -m[4] * m[10] * m[15] + m[4] * m[14] * m[11] + m[6] * m[8] * m[15] - m[6] * m[12] * m[11] - m[7] * m[8] * m[14] + m[7] * m[12] * m[10];
r[5] = m[0] * m[10] * m[15] - m[0] * m[14] * m[11] - m[2] * m[8] * m[15] + m[2] * m[12] * m[11] + m[3] * m[8] * m[14] - m[3] * m[12] * m[10];
r[6] = -m[0] * m[6] * m[15] + m[0] * m[14] * m[7] + m[2] * m[4] * m[15] - m[2] * m[12] * m[7] - m[3] * m[4] * m[14] + m[3] * m[12] * m[6];
r[7] = m[0] * m[6] * m[11] - m[0] * m[10] * m[7] - m[2] * m[4] * m[11] + m[2] * m[8] * m[7] + m[3] * m[4] * m[10] - m[3] * m[8] * m[6];
r[8] = m[4] * m[9] * m[15] - m[4] * m[13] * m[11] - m[5] * m[8] * m[15] + m[5] * m[12] * m[11] + m[7] * m[8] * m[13] - m[7] * m[12] * m[9];
r[9] = -m[0] * m[9] * m[15] + m[0] * m[13] * m[11] + m[1] * m[8] * m[15] - m[1] * m[12] * m[11] - m[3] * m[8] * m[13] + m[3] * m[12] * m[9];
r[10] = m[0] * m[5] * m[15] - m[0] * m[13] * m[7] - m[1] * m[4] * m[15] + m[1] * m[12] * m[7] + m[3] * m[4] * m[13] - m[3] * m[12] * m[5];
r[11] = -m[0] * m[5] * m[11] + m[0] * m[9] * m[7] + m[1] * m[4] * m[11] - m[1] * m[8] * m[7] - m[3] * m[4] * m[9] + m[3] * m[8] * m[5];
r[12] = -m[4] * m[9] * m[14] + m[4] * m[13] * m[10] + m[5] * m[8] * m[14] - m[5] * m[12] * m[10] - m[6] * m[8] * m[13] + m[6] * m[12] * m[9];
r[13] = m[0] * m[9] * m[14] - m[0] * m[13] * m[10] - m[1] * m[8] * m[14] + m[1] * m[12] * m[10] + m[2] * m[8] * m[13] - m[2] * m[12] * m[9];
r[14] = -m[0] * m[5] * m[14] + m[0] * m[13] * m[6] + m[1] * m[4] * m[14] - m[1] * m[12] * m[6] - m[2] * m[4] * m[13] + m[2] * m[12] * m[5];
r[15] = m[0] * m[5] * m[10] - m[0] * m[9] * m[6] - m[1] * m[4] * m[10] + m[1] * m[8] * m[6] + m[2] * m[4] * m[9] - m[2] * m[8] * m[5];
// calculate determinant using laplace expansion (cf https://en.wikipedia.org/wiki/Laplace_expansion),
// as we already have the cofactors. We multiply a column by a row as the cofactor matrix is transposed.
const det = m[0] * r[0] + m[1] * r[4] + m[2] * r[8] + m[3] * r[12];
// assert(!isZero(det), 'det may not be zero, i.e. the matrix is not invertible')
let i = 16;
while (i--) {
r[i] /= det;
}
return result;
}
/**
* Returns `matrix`, exchanging columns for rows. You can optionally pass an
* existing matrix in `result` to avoid allocating a new matrix.
*/
static transpose(matrix, result) {
assertInst(M4, matrix);
!result || assertInst(M4, result);
assert(matrix != result, 'matrix != result');
result = result || new M4();
const m = matrix.m, r = result.m;
r[0] = m[0];
r[1] = m[4];
r[2] = m[8];
r[3] = m[12];
r[4] = m[1];
r[5] = m[5];
r[6] = m[9];
r[7] = m[13];
r[8] = m[2];
r[9] = m[6];
r[10] = m[10];
r[11] = m[14];
r[12] = m[3];
r[13] = m[7];
r[14] = m[11];
r[15] = m[15];
return result;
}
/**
* Returns the concatenation of the transforms for `left` and `right`.
*/
static multiply(left, right, result) {
assertInst(M4, left, right);
!result || assertInst(M4, result);
assert(left != result, 'left != result');
assert(right != result, 'right != result');
result = result || new M4();
const a = left.m, b = right.m, r = result.m;
r[0] = a[0] * b[0] + a[1] * b[4] + (a[2] * b[8] + a[3] * b[12]);
r[1] = a[0] * b[1] + a[1] * b[5] + (a[2] * b[9] + a[3] * b[13]);
r[2] = a[0] * b[2] + a[1] * b[6] + (a[2] * b[10] + a[3] * b[14]);
r[3] = a[0] * b[3] + a[1] * b[7] + (a[2] * b[11] + a[3] * b[15]);
r[4] = a[4] * b[0] + a[5] * b[4] + (a[6] * b[8] + a[7] * b[12]);
r[5] = a[4] * b[1] + a[5] * b[5] + (a[6] * b[9] + a[7] * b[13]);
r[6] = a[4] * b[2] + a[5] * b[6] + (a[6] * b[10] + a[7] * b[14]);
r[7] = a[4] * b[3] + a[5] * b[7] + (a[6] * b[11] + a[7] * b[15]);
r[8] = a[8] * b[0] + a[9] * b[4] + (a[10] * b[8] + a[11] * b[12]);
r[9] = a[8] * b[1] + a[9] * b[5] + (a[10] * b[9] + a[11] * b[13]);
r[10] = a[8] * b[2] + a[9] * b[6] + (a[10] * b[10] + a[11] * b[14]);
r[11] = a[8] * b[3] + a[9] * b[7] + (a[10] * b[11] + a[11] * b[15]);
r[12] = a[12] * b[0] + a[13] * b[4] + (a[14] * b[8] + a[15] * b[12]);
r[13] = a[12] * b[1] + a[13] * b[5] + (a[14] * b[9] + a[15] * b[13]);
r[14] = a[12] * b[2] + a[13] * b[6] + (a[14] * b[10] + a[15] * b[14]);
r[15] = a[12] * b[3] + a[13] * b[7] + (a[14] * b[11] + a[15] * b[15]);
return result;
}
static copy(src, result = new M4()) {
assertInst(M4, src, result);
assert(result != src, 'result != src');
const s = src.m, d = result.m;
let i = 16;
while (i--) {
d[i] = s[i];
}
return result;
}
static forSys(e0, e1, e2 = e0.cross(e1), origin = V3.O) {
assertVectors(e0, e1, e2, origin);
return new M4(e0.x, e1.x, e2.x, origin.x, e0.y, e1.y, e2.y, origin.y, e0.z, e1.z, e2.z, origin.z, 0, 0, 0, 1);
}
static forRows(n0, n1, n2, n3 = V3.O) {
assertVectors(n0, n1, n2, n3);
return new M4(n0.x, n0.y, n0.z, 0, n1.x, n1.y, n1.z, 0, n2.x, n2.y, n2.z, 0, n3.x, n3.y, n3.z, 1);
}
/**
* Returns an identity matrix. You can optionally pass an existing matrix in `result` to avoid allocating a new
* matrix. This emulates the OpenGL function `glLoadIdentity()`
*
* Unless initializing a matrix to be modified, use M4.IDENTITY
*/
static identity(result = new M4()) {
assertInst(M4, result);
const m = result.m;
m[0] = m[5] = m[10] = m[15] = 1;
m[1] = m[2] = m[3] = m[4] = m[6] = m[7] = m[8] = m[9] = m[11] = m[12] = m[13] = m[14] = 0;
return result;
}
/**
* Creates a new M4 initialized by a user defined callback function
*
* @param f signature: (elRow, elCol, elIndex) =>
* el, where elIndex is the row-major index, i.e. eLindex == elRow * 4 + elCol
* @param result
*/
static fromFunction4(f, result = new M4()) {
assert(typeof f == 'function');
assertInst(M4, result);
const m = result.m;
let i = 16;
while (i--) {
m[i] = f(Math.floor(i / 4), i % 4, i);
}
return result;
}
/**
### GL.Matrix.perspective(fov, aspect, near, far[, result])
*/
/**
* ## hjghfhg jhg hjg jhkg jhg jkh jhg jh gjh {@see V3.O}
* {@see perspectiveRad}
* perspectiveRad
* ```
* test ```
* @param fovDegrees in degrees
* @param aspect aspect ratio = width/height of viewport
*/
static perspective(fovDegrees, aspect, near, far, result = new M4()) {
return M4.perspectiveRad(fovDegrees * DEG, aspect, near, far, result);
}
static perspectiveRad(fov, aspect, near, far, result = new M4()) {
assertInst(M4, result);
assertNumbers(fov, aspect, near, far);
const y = Math.tan(fov / 2) * near;
const x = y * aspect;
return M4.frustum(-x, x, -y, y, near, far, result);
}
// the OpenGL function `glFrustum()`.
static frustum(left, right, bottom, top, near, far, result) {
assertNumbers(left, right, bottom, top, near, far);
assert(0 < near, '0 < near');
assert(near < far, 'near < far');
!result || assertInst(M4, result);
result = result || new M4();
const m = result.m;
m[0] = 2 * near / (right - left);
m[1] = 0;
m[2] = (right + left) / (right - left);
m[3] = 0;
m[4] = 0;
m[5] = 2 * near / (top - bottom);
m[6] = (top + bottom) / (top - bottom);
m[7] = 0;
m[8] = 0;
m[9] = 0;
m[10] = -(far + near) / (far - near);
m[11] = -2 * far * near / (far - near);
m[12] = 0;
m[13] = 0;
m[14] = -1;
m[15] = 0;
return result;
}
/**
* Returns a new M4 representing the a projection through/towards a point onto a plane.
*/
static projectPlanePoint(p, plane, result = new M4()) {
assertVectors(p, plane.normal1);
assertInst(M4, result);
const m = result.m;
const n = plane.normal1, w = plane.w;
const np = n.dot(p);
m[0] = p.x * n.x + w - np;
m[1] = p.x * n.y;
m[2] = p.x * n.z;
m[3] = -w * p.x;
m[4] = p.y * n.x;
m[5] = p.y * n.y + w - np;
m[6] = p.y * n.z;
m[7] = -w * p.y;
m[8] = p.z * n.x;
m[9] = p.z * n.y;
m[10] = p.z * n.z + w - np;
m[11] = -w * p.z;
m[12] = n.x;
m[13] = n.y;
m[14] = n.z;
m[15] = -np;
return result;
}
/**
* Orthographic/orthogonal projection. Transforms the cuboid with the dimensions X: [left right] Y: [bottom, top]
* Z: [near far] to the cuboid X: [-1 1] Y [-1 1] Z [-1, 1]
*/
static ortho(left, right, bottom, top, near, far, result = new M4()) {
assertNumbers(left, right, bottom, top, near, far);
assertInst(M4, result);
const m = result.m;
m[0] = 2 / (right - left);
m[1] = 0;
m[2] = 0;
m[3] = -(right + left) / (right - left);
m[4] = 0;
m[5] = 2 / (top - bottom);
m[6] = 0;
m[7] = -(top + bottom) / (top - bottom);
m[8] = 0;
m[9] = 0;
m[10] = -2 / (far - near);
m[11] = -(far + near) / (far - near);
m[12] = 0;
m[13] = 0;
m[14] = 0;
m[15] = 1;
return result;
}
static scale(...args) {
let x, y, z, result;
if (args[0] instanceof V3) {
assert(args.length <= 2);
({ x, y, z } = args[0]);
result = args[1];
}
else if ('number' != typeof args[1]) {
x = y = z = args[0];
result = args[1];
}
else {
assert(args.length <= 4);
x = args[0];
y = args[1];
z = undefined != args[2] ? args[2] : 1;
result = args[3];
}
undefined == result && (result = new M4());
assertInst(M4, result);
assertNumbers(x, y, z);
const m = result.m;
m[0] = x;
m[1] = 0;
m[2] = 0;
m[3] = 0;
m[4] = 0;
m[5] = y;
m[6] = 0;
m[7] = 0;
m[8] = 0;
m[9] = 0;
m[10] = z;
m[11] = 0;
m[12] = 0;
m[13] = 0;
m[14] = 0;
m[15] = 1;
return result;
}
static translate(...args) {
let x, y, z, result;
if (args[0] instanceof V3) {
assert(args.length <= 2);
({ x, y, z } = args[0]);
result = args[1];
}
else {
assert(args.length <= 4);
x = args[0];
y = undefined != args[1] ? args[1] : 0;
z = undefined != args[2] ? args[2] : 0;
result = args[3];
}
undefined == result && (result = new M4());
assertInst(M4, result);
assertNumbers(x, y, z);
const m = result.m;
m[0] = 1;
m[1] = 0;
m[2] = 0;
m[3] = x;
m[4] = 0;
m[5] = 1;
m[6] = 0;
m[7] = y;
m[8] = 0;
m[9] = 0;
m[10] = 1;
m[11] = z;
m[12] = 0;
m[13] = 0;
m[14] = 0;
m[15] = 1;
return result;
}
/**
* Returns a matrix that rotates by `a` degrees around the vector (x, y, z). You can optionally pass an existing
* matrix in `result` to avoid allocating a new matrix. This emulates the OpenGL function `glRotate()`.
*/
//static rotation(radians: raddd, x: number, y: number, z: number, result?: M4): M4
static rotate(radians, v, result) {
undefined == result && (result = new M4());
assertInst(M4, result);
let { x, y, z } = v;
assert(!new V3(x, y, z).likeO(), '!V(x, y, z).likeO()');
const m = result.m;
const d = Math.sqrt(x * x + y * y + z * z);
x /= d;
y /= d;
z /= d;
const cos = Math.cos(radians), sin = Math.sin(radians), t = 1 - cos;
m[0] = x * x * t + cos;
m[1] = x * y * t - z * sin;
m[2] = x * z * t + y * sin;
m[3] = 0;
m[4] = y * x * t + z * sin;
m[5] = y * y * t + cos;
m[6] = y * z * t - x * sin;
m[7] = 0;
m[8] = z * x * t - y * sin;
m[9] = z * y * t + x * sin;
m[10] = z * z * t + cos;
m[11] = 0;
m[12] = 0;
m[13] = 0;
m[14] = 0;
m[15] = 1;
return result;
}
/**
* Returns a matrix that puts the camera at the eye point `ex, ey, ez` looking
* toward the center point `cx, cy, cz` with an up direction of `ux, uy, uz`.
* You can optionally pass an existing matrix in `result` to avoid allocating
* a new matrix. This emulates the OpenGL function `gluLookAt()`.
*/
static lookAt(eye, focus, up, result) {
assert(3 == arguments.length || 4 == arguments.length, '3 == arguments.length || 4 == arguments.length');
assertVectors(eye, focus, up);
!result || assertInst(M4, result);
result = result || new M4();
const m = result.m;
const f = eye.minus(focus).unit();
const s = up.cross(f).unit();
const t = f.cross(s).unit();
m[0] = s.x;
m[1] = s.y;
m[2] = s.z;
m[3] = -s.dot(eye);
m[4] = t.x;
m[5] = t.y;
m[6] = t.z;
m[7] = -t.dot(eye);
m[8] = f.x;
m[9] = f.y;
m[10] = f.z;
m[11] = -f.dot(eye);
m[12] = 0;
m[13] = 0;
m[14] = 0;
m[15] = 1;
return result;
}
/**
* Create a rotation matrix for rotating around the X axis
*/
static rotateX(radians) {
assertNumbers(radians);
const sin = Math.sin(radians), cos = Math.cos(radians);
const els = [
1, 0, 0, 0, 0, cos, -sin, 0, 0, sin, cos, 0, 0, 0, 0, 1,
];
return new M4(els);
}
/**
* Create a rotation matrix for rotating around the Y axis
*/
static rotateY(radians) {
const sin = Math.sin(radians), cos = Math.cos(radians);
const els = [
cos, 0, sin, 0, 0, 1, 0, 0, -sin, 0, cos, 0, 0, 0, 0, 1,
];
return new M4(els);
}
/**
* Create a rotation matrix for rotating around the Z axis
*/
static rotateZ(radians) {
const sin = Math.sin(radians), cos = Math.cos(radians);
const els = [
cos, -sin, 0, 0, sin, cos, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1,
];
return new M4(els);
}
/**
* New rotation matrix such that result.transformVector(a).isParallelTo(b) through smallest rotation.
* Performs no scaling.
*/
static rotateAB(a, b, result) {
// see http://inside.mines.edu/fs_home/gmurray/ArbitraryAxisRotation/
assertVectors(a, b);
!result || assertInst(M4, result);
const rotationAxis = a.cross(b), rotationAxisLength = rotationAxis.length();
if (eq0(rotationAxisLength)) {
return M4.identity(result);
}
const radians = Math.atan2(rotationAxisLength, a.dot(b));
return M4.rotateLine(V3.O, rotationAxis, radians, result);
}
/**
* Matrix for rotation about arbitrary line defined by an anchor point and direction.
* rotationAxis does not need to be unit
*/
static rotateLine(rotationAnchor, rotationAxis, radians, result) {
// see http://inside.mines.edu/fs_home/gmurray/ArbitraryAxisRotation/
assertVectors(rotationAnchor, rotationAxis);
assertNumbers(radians);
!result || assertInst(M4, result);
result = result || new M4();
rotationAxis = rotationAxis.unit();
const ax = rotationAnchor.x, ay = rotationAnchor.y, az = rotationAnchor.z, dx = rotationAxis.x, dy = rotationAxis.y, dz = rotationAxis.z;
const m = result.m, cos = Math.cos(radians), sin = Math.sin(radians);
m[0] = dx * dx + (dy * dy + dz * dz) * cos;
m[1] = dx * dy * (1 - cos) - dz * sin;
m[2] = dx * dz * (1 - cos) + dy * sin;
m[3] = (ax * (dy * dy + dz * dz) - dx * (ay * dy + az * dz)) * (1 - cos) + (ay * dz - az * dy) * sin;
m[4] = dx * dy * (1 - cos) + dz * sin;
m[5] = dy * dy + (dx * dx + dz * dz) * cos;
m[6] = dy * dz * (1 - cos) - dx * sin;
m[7] = (ay * (dx * dx + dz * dz) - dy * (ax * dx + az * dz)) * (1 - cos) + (az * dx - ax * dz) * sin;
m[8] = dx * dz * (1 - cos) - dy * sin;
m[9] = dy * dz * (1 - cos) + dx * sin;
m[10] = dz * dz + (dx * dx + dy * dy) * cos;
m[11] = (az * (dx * dx + dy * dy) - dz * (ax * dx + ay * dy)) * (1 - cos) + (ax * dy - ay * dx) * sin;
m[12] = 0;
m[13] = 0;
m[14] = 0;
m[15] = 1;
return result;
}
/**
* Create an affine matrix for mirroring into an arbitrary plane:
*/
static mirror(plane, result = new M4()) {
assertVectors(plane.normal1);
assertInst(M4, result);
const [nx, ny, nz] = plane.normal1;
const w = plane.w;
const m = result.m;
m[0] = 1.0 - 2.0 * nx * nx;
m[1] = -2.0 * ny * nx;
m[2] = -2.0 * nz * nx;
m[3] = 2.0 * nx * w;
m[4] = -2.0 * nx * ny;
m[5] = 1.0 - 2.0 * ny * ny;
m[6] = -2.0 * nz * ny;
m[7] = 2.0 * ny * w;
m[8] = -2.0 * nx * nz;
m[9] = -2.0 * ny * nz;
m[10] = 1.0 - 2.0 * nz * nz;
m[11] = 2.0 * nz * w;
m[12] = 0;
m[13] = 0;
m[14] = 0;
m[15] = 1;
return result;
}
/**
*
* @param plane
* @param dir Projection direction. Optional, if not specified plane normal1 will be used.
* @param result {@see M4}
*/
static project(plane, dir = plane.normal1, result = new M4()) {
// TODO: doc
/**
* plane.normal1 DOT (p + lambda * dir) = w (1)
* extract lambda:
* plane.normal1 DOT p + lambda * plane.normal1 DOT dir = w
* lambda = (w - plane.normal1 DOT p) / plane.normal1 DOT dir
* result = p + lambda * dir
* result = p + dir * (w - plane.normal1 DOT p) / plane.normal1 DOT dir
* result = w * dir / (plane.normal1 DOT dir) + p - plane.normal1 DOT p * dir / (plane.normal1 DOT dir) *
*
a + d * (w - n . a) / (nd)
a + dw - d * na
*/
assertVectors(dir, plane.normal1);
assertInst(M4, result);
const w = plane.w;
const m = result.m;
const nd = plane.normal1.dot(dir);
const { x: nx, y: ny, z: nz } = plane.normal1;
const { x: dx, y: dy, z: dz } = dir.div(nd);
/*
rejectedFrom: return this.minus(b.times(this.dot(b) / b.dot(b)))
return M4.forSys(
V3.X.rejectedFrom(plane.normal1),
V3.Y.rejectedFrom(plane.normal1),
V3.Z.rejectedFrom(plane.normal1),
plane.anchor,
result
)
*/
m[0] = 1.0 - nx * dx;
m[1] = -ny * dx;
m[2] = -nz * dx;
m[3] = dx * w;
m[4] = -nx * dy;
m[5] = 1.0 - ny * dy;
m[6] = -nz * dy;
m[7] = dy * w;
m[8] = -nx * dz;
m[9] = -ny * dz;
m[10] = 1.0 - nz * dz;
m[11] = dz * w;
m[12] = 0;
m[13] = 0;
m[14] = 0;
m[15] = 1;
return result;
}
static lineProjection(line, result = new M4()) {
assertVectors(line.anchor, line.dir1);
assertInst(M4, result);
const ax = line.anchor.x, ay = line.anchor.y, az = line.anchor.z;
const dx = line.dir1.x, dy = line.dir1.y, dz = line.dir1.z;
const m = result.m;
/*
projectedOn: return b.times(this.dot(b) / b.dot(b))
*/
m[0] = dx * dx;
m[1] = dx * dy;
m[2] = dx * dz;
m[3] = ax;
m[4] = dy * dx;
m[5] = dy * dy;
m[6] = dy * dz;
m[7] = ay;
m[8] = dz * dx;
m[9] = dz * dy;
m[10] = dz * dz;
m[11] = az;
m[12] = 0;
m[13] = 0;
m[14] = 0;
m[15] = 1;
return result;
}
/**
Returns a perspective transform matrix, which makes far away objects appear smaller than nearby objects. The `aspect` argument should be the width divided by the height of your viewport and `fov` is the top-to-bottom angle of the field of view in degrees. You can optionally pass an existing matrix in `result` to avoid allocating a new matrix. This emulates the OpenGL function `gluPerspective()`.
*/
static multiplyMultiple(...m4s) {
if (0 == m4s.length)
return M4.identity();
let temp = M4.identity(), result = m4s[0].copy();
for (let i = 1; i < m4s.length; i++) {
M4.multiply(result, m4s[i], temp);
{
[temp, result] = [result, temp];
}
}
return result;
}
static pointInversion(p, result = new M4()) {
assertVectors(p);
assertInst(M4, result);
const m = result.m;
m[0] = -1;
m[1] = 0;
m[2] = 0;
m[3] = 2 * p.x;
m[4] = 0;
m[5] = -1;
m[6] = 0;
m[7] = 2 * p.y;
m[8] = 0;
m[9] = 0;
m[10] = -1;
m[11] = 2 * p.z;
m[12] = 0;
m[13] = 0;
m[14] = 0;
m[15] = 1;
return result;
}
// ### GL.Matrix.frustum(left, right, bottom, top, near, far[, result])
//
// Sets up a viewing frustum, which is shaped like a truncated pyramid with the
// camera where the point of the pyramid would be. You can optionally pass an
// existing matrix in `result` to avoid allocating a new matrix. This emulates
/**
* Returns a new M4 which is equal to the inverse of this.
*/
inversed() {
return M4.inverse(this);
}
/**
* Matrix trace is defined as the sum of the elements of the main diagonal.
*/
trace() {
return this.m[0] + this.m[5] + this.m[10] + this.m[15];
}
as3x3() {
const result = M4.copy(this), m = result.m;
m[3] = m[7] = m[11] = m[12] = m[13] = m[14] = 0;
m[15] = 1;
return result;
}
transform(m4) {
return m4.times(this);
}
realEigenValues3() {
const m = this.m;
assert(0 == m[12] && 0 == m[13] && 0 == m[14]);
// determinant of (this - λI):
// | a-λ b c |
// | d e-λ f | = -λ^3 + λ^2 (a+e+i) + λ (-a e-a i+b d+c g-e i+f h) + a(ei - fh) - b(di - fg) + c(dh - eg)
// | g h i-λ |
const [a, b, c, , d, e, f, , g, h, i] = m;
// det(this - λI) = -λ^3 +λ^2 (a+e+i) + λ (-a e-a i-b d+c g-e i+f h)+ (a e i-a f h-b d i+b f g+c d h-c e g)
const s = -1;
const t = a + e + i; // equivalent to trace of matrix
const u = -a * e - a * i + b * d + c * g - e * i + f * h; // equivalent to 1/2 (trace(this²) - trace²(A))
const w = a * (e * i - f * h) - b * (d * i - f * g) + c * (d * h - e * g); // equivalent to matrix determinant
console.log(s, t, u, w);
return solveCubicReal2(s, t, u, w);
}
realEigenVectors3() {
const eigenValues = this.realEigenValues3();
const this3x3 = this.times(M4.IDENTITY3);
console.log(this.toString());
console.log(this3x3.toString());
let mats = eigenValues.map(ev => M4.IDENTITY3.scale(-ev).plus(this3x3));
console.log(mats.map(m => m.determinant3()));
console.log(mats.map(m => '' + m.toString(v => '' + v)).join('\n\n'));
console.log(mats.map(m => '' + m.gauss().U.toString(v => '' + v)).join('\n\n'));
console.log('mats.map(m=>m.rank())', mats.map(m => m.rank()));
if (1 == eigenValues.length) {
console.log(mats[0].toString());
assertf(() => 0 == mats[0].rank());
// col vectors
return arrayFromFunction(3, col => new V3(this.m[col], this.m[4 + col], this.m[8 + col]));
}
if (2 == eigenValues.length) {
// one matrix should have rank 1, the other rank 2
if (1 == mats[0].rank()) {
mats = [mats[1], mats[0]];
}
assertf(() => 2 == mats[0].rank());
assertf(() => 1 == mats[1].rank());
// mat[0] has rank 2, mat[1] has rank 1
const gauss0 = mats[0].gauss().U;
const eigenVector0 = gauss0.row(0).cross(gauss0.row(1)).V3().unit();
const planeNormal = mats[1].gauss().U.row(0).V3();
const eigenVector1 = planeNormal.getPerpendicular().unit();
const eigenVector2 = eigenVector0.cross(eigenVector1).rejectedFrom(planeNormal);
return [eigenVector0, eigenVector1, eigenVector2];
}
if (3 == eigenValues.length) {
mats.forEach((mat, i) => assert(2 == mat.rank(), i + ': ' + mat.rank()));
// the (A - lambda I) matrices map to a plane. This means, that there is an entire line in R³ which maps to
// the point V3.O
return mats.map(mat => {
const gauss = mat.gauss().U;
return gauss.row(0).cross(gauss.row(1)).V3().unit();
});
}
throw new Error('there cannot be more than 3 eigen values');
}
/**
* U * SIGMA * VSTAR = this
* U and VSTAR are orthogonal matrices
* SIGMA is a diagonal matrix
*/
svd3() {
function matrixForCS(i, k, c, s) {
const m = M4.identity();
m.setEl(i, i, c);
m.setEl(k, k, c);
m.setEl(i, k, s);
m.setEl(k, i, -s);
return m;
}
const A = this.as3x3();
let S = A.transposed().times(A), V$$1 = M4.identity();
console.log(S.str);
for (let it = 0; it < 16; it++) {
console.log('blahg\n', V$$1.times(S).times(V$$1.transposed()).str);
assert(V$$1.times(S).times(V$$1.transposed()).likeM4(A.transposed().times(A)), V$$1.times(S).times(V$$1.transposed()).str, A.transposed().times(A).str);
let maxOffDiagonal = 0, maxOffDiagonalIndex = 1, j = 10;
while (j--) {
const val = Math.abs(S.m[j]);
if (j % 4 != Math.floor(j / 4) && val > maxOffDiagonal) {
maxOffDiagonal = val;
maxOffDiagonalIndex = j;
}
}
const i = Math.floor(maxOffDiagonalIndex / 4), k = maxOffDiagonalIndex % 4;
const a_ii = S.m[5 * i], a_kk = S.m[5 * k], a_ik = S.m[maxOffDiagonalIndex];
const phi = a_ii === a_kk ? PI$1 / 4 : Math.atan(2 * a_ik / (a_ii - a_kk)) / 2;
console.log(maxOffDiagonalIndex, i, k, 'phi', phi);
const cos = Math.cos(phi), sin = Math.sin(phi);
const givensRotation = matrixForCS(i, k, cos, -sin);
assert(givensRotation.transposed().times(givensRotation).likeIdentity());
console.log(givensRotation.str);
V$$1 = V$$1.times(givensRotation);
S = M4.multiplyMultiple(givensRotation.transposed(), S, givensRotation);
console.log(S.str);
}
const sigma = S.map((el, elIndex) => elIndex % 5 == 0 ? Math.sqrt(el) : 0);
return {
U: M4.multiplyMultiple(A, V$$1, sigma.map((el, elIndex) => elIndex % 5 == 0 ? 1 / el : 0)),
SIGMA: sigma,
VSTAR: V$$1.transposed(),
};
}
map(fn) {
return M4.fromFunction4((x, y, i) => fn(this.m[i], i, this.m));
}
likeM4(m4) {
assertInst(M4, m4);
return this.m.every((el, index) => eq(el, m4.m[index]));
}
/**
* Returns a new M4 equal to the transpose of this.
*/
transposed() {
return M4.transpose(this);
}
/**
* Returns a new M4 which equal to (this * matrix) (in that order)
*/
times(matrix) {
return M4.multiply(this, matrix);
}
/**
* Transforms the vector as a point with a w coordinate of 1. This means translations will have an effect, for
* example.
*/
transformPoint(v) {
assertVectors(v);
const m = this.m;
const vx = v.x, vy = v.y, vz = v.z, vw = 1;
const x = vx * m[0] + vy * m[1] + vz * m[2] + vw * m[3];
const y = vx * m[4] + vy * m[5] + vz * m[6] + vw * m[7];
const z = vx * m[8] + vy * m[9] + vz * m[10] + vw * m[11];
const w = vx * m[12] + vy * m[13] + vz * m[14] + vw * m[15];
// scale such that fourth element becomes 1:
return new V3(x / w, y / w, z / w);
}
/**
* Transforms the vector as a vector with a w coordinate of 0. This means translations will have no effect, for
* example. Will throw an exception if the calculated w component != 0. This occurs for example when attempting
* to transform a vector with a perspective matrix.
*/
transformVector(v) {
assertVectors(v);
const m = this.m;
const w = v.x * m[12] + v.y * m[13] + v.z * m[14];
assert(w === 0, () => 'w != 0 needs to be true for this to make sense (w =' + w + this.str);
return new V3(m[0] * v.x + m[1] * v.y + m[2] * v.z, m[4] * v.x + m[5] * v.y + m[6] * v.z, m[8] * v.x + m[9] * v.y + m[10] * v.z);
}
transformedPoints(vs) {
return vs.map(v => this.transformPoint(v));
}
transformedVectors(vs) {
return vs.map(v => this.transformVector(v));
}
new() {
return new M4();
}
copy() {
return M4.copy(this);
}
isRegular() {
return !eq0(this.determinant());
}
isAxisAligned() {
const m = this.m;
return (1 >= +!eq0(m[0]) + +!eq0(m[1]) + +!eq0(m[2]))
&& (1 >= +!eq0(m[4]) + +!eq0(m[5]) + +!eq0(m[6]))
&& (1 >= +!eq0(m[8]) + +!eq0(m[9]) + +!eq0(m[10]));
}
/**
* A matrix M is orthogonal iff M * M^T = I
* I being the identity matrix.
*
* @returns If this matrix is orthogonal or very close to it. Comparison of the identity matrix and
* this * this^T is done with {@link #likeM4}
*/
isOrthogonal() {
// return this.transposed().times(this).likeM4(M4.IDENTITY)
M4.transpose(this, M4.temp0);
M4.multiply(this, M4.temp0, M4.temp1);
return M4.IDENTITY.likeM4(M4.temp1);
}
/**
* A matrix M is symmetric iff M == M^T
* I being the identity matrix.
*
* @returns If this matrix is symmetric or very close to it. Comparison of the identity matrix and
* this * this^T is done with {@link #likeM4}
*/
isSymmetric() {
M4.transpose(this, M4.temp0);
return this.likeM4(M4.temp0);
}
/**
* A matrix M is normal1 iff M * M^-T == M^T * M TODO: ^-T?
* I being the identity matrix.
*
* @returns If this matrix is symmetric or very close to it. Comparison of the identity matrix and
* this * this^T is done with {@link #likeM4}
*/
isNormal() {
M4.transpose(this, M4.temp0); // temp0 = this^-T
M4.multiply(this, M4.temp0, M4.temp1); // temp1 = this * this^-T
M4.multiply(M4.temp0, this, M4.temp2); // temp2 = this^-T * this
return M4.temp1.likeM4(M4.temp2);
}
/**
* Determinant of matrix.
*
* Notes:
* For matrices A and B
* det(A * B) = det(A) * det(B)
* det(A^-1) = 1 / det(A)
*/
determinant() {
/*
| a b c d |
| e f g h |
| i j k l |
| m n o p |
*/
const $ = this.m, a = $[0], b = $[1], c = $[2], d = $[3], e = $[4], f = $[5], g = $[6], h = $[7], i = $[8], j = $[9], k = $[10], l = $[11], m = $[12], n = $[13], o = $[14], p = $[15], klop = k * p - l * o, jlnp = j * p - l * n, jkno = j * o - k * n, ilmp = i * p - l * m, ikmo = i * o - k * m, ijmn = i * n - j * m;
return (a * (f * klop - g * jlnp + h * jkno)
- b * (e * klop - g * ilmp + h * ikmo)
+ c * (e * jlnp - f * ilmp + h * ijmn)
- d * (e * jkno - f * ikmo + g * ijmn));
}
determinant3() {
const [a, b, c, , d, e, f, , g, h, i] = this.m;
const det = a * (e * i - f * h) - b * (d * i - f * g) + c * (d * h - e * g);
return det;
}
/**
* determine whether this matrix is a mirroring transformation
*/
isMirroring() {
/*
var u = V(this.m[0], this.m[4], this.m[8])
var v = V(this.m[1], this.m[5], this.m[9])
var w = V(this.m[2], this.m[6], this.m[10])
// for a true orthogonal, non-mirrored base, u.cross(v) == w
// If they have an opposite direction then we are mirroring
var mirrorvalue = u.cross(v).dot(w)
var ismirror = (mirrorvalue < 0)
return ismirror
*/
return this.determinant() < 0; // TODO: also valid for 4x4?
}
/**
* Get the translation part of this matrix, i.e. the result of this.transformPoint(V3.O)
*/
getTranslation() {
const m = this.m, w = m[15];
return new V3(m[3] / w, m[7] / w, m[11] / w);
}
/**
* Returns this matrix scaled so that the determinant is 1.
* det(c * A) = (c ** n) * det(A) for n x n matrices,
* so we need to divide by the 4th root of the determinant
*/
normalized() {
const detAbs = abs$1$1(this.determinant());
return 1 == detAbs ? this : this.divScalar(Math.pow(detAbs, 0.25));
}
/**
* Returns this matrix scaled so that the determinant is 1.
* det(c * A) = (c ** n) * det(A) for n x n matrices,
* so we need to divide by the 4th root of the determinant
*/
normalized2() {
const div = this.m[15];
return 1 == div ? this : this.divScalar(Math.pow(div, 0.25));
}
/**
* Returns if the matrix has the following form (within NLA_PRECISION):
* a b c 0
* c d e 0
* f g h 0
* 0 0 0 1
*/
like3x3() {
const m = this.m;
return eq(1, m[15])
&& eq0(m[12]) && eq0(m[13]) && eq0(m[14])
&& eq0(m[3]) && eq0(m[7]) && eq0(m[11]);
}
isNoProj() {
const m = this.m;
return 0 == m[12] && 0 == m[13] && 0 == m[14] && 1 == m[15];
}
likeIdentity() {
return this.m.every((val, i) => (i / 4 | 0) == (i % 4) ? eq(1, val) : eq0(val));
}
isIdentity() {
return this.m.every((val, i) => (i / 4 | 0) == (i % 4) ? 1 == val : 0 == val);
}
toString(f) {
f = f || ((v) => v.toFixed(6).replace(/([0.])(?=0*$)/g, ' ').toString());
assert(typeof f(0) == 'string', '' + typeof f(0));
// slice this.m to convert it to an Array (from TypeArray)
const rounded = Array.prototype.slice.call(this.m).map(f);
const colWidths = [0, 1, 2, 3].map((colIndex) => rounded.sliceStep(colIndex, 0, 4).map((x) => x.length).max());
return [0, 1, 2, 3].map((rowIndex) => rounded
.slice(rowIndex * 4, rowIndex * 4 + 4) // select matrix row
.map((x, colIndex) => repeatString(colWidths[colIndex] - x.length, ' ') + x) // pad numbers with
.join(' ')).join('\n'); // join rows
}
isTranslation() {
// 2: any value, otherwise same value
const mask = [
1, 0, 0, 2,
0, 1, 0, 2,
0, 0, 1, 2,
0, 0, 0, 1
];
return mask.every((expected, index) => expected == 2 || expected == this.m[index]);
}
isScaling() {
const mask = [
2, 0, 0, 0,
0, 2, 0, 0,
0, 0, 2, 0,
0, 0, 0, 1
];
return mask.every((expected, index) => expected == 2 || expected == this.m[index]);
}
toSource() {
if (this.isIdentity()) {
return 'M4.IDENTITY';
}
else if (this.isTranslation()) {
return callsce('M4.translate', this.O);
}
else if (this.isScaling()) {
return callsce('M4.scale', this.m[0], this.m[5], this.m[10]);
}
else if (this.isNoProj()) {
return !this.O.equals(V3.O)
? callsce('M4.forSys', this.X, this.Y, this.Z, this.O)
: callsce('M4.forSys', this.X, this.Y, this.Z);
}
throw new Error();
}
xyAreaFactor() {
return this.transformVector(V3.X).cross(this.transformVector(V3.Y)).length();
}
}
/**
* A simple (consists of integers), regular, non-orthogonal matrix, useful mainly for testing.
* M4.BAR = M4.FOO.inverse()
*/
M4.FOO = new M4(0, 1, 1, 2, 0.3, 0.4, 0.8, 13, 2.1, 3.4, 5.5, 8.9, 0, 0, 0, 1);
M4.BAR = M4.FOO.inversed();
M4.IDENTITY = M4.identity();
M4.YZX = M4.forSys(V3.Y, V3.Z, V3.X);
M4.ZXY = M4.forSys(V3.Z, V3.X, V3.Y);
M4.IDENTITY3 = new M4(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0);
M4.temp0 = new M4();
M4.temp1 = new M4();
M4.temp2 = new M4();
M4.NAMEMAP = new JavaMap()
.set(M4.IDENTITY3, 'M4.IDENTITY3')
.set(M4.FOO, 'M4.FOO')
.set(M4.BAR, 'M4.BAR')
.set(M4.IDENTITY, 'M4.IDENTITY')
.set(M4.ZXY, 'M4.ZXY')
.set(M4.YZX, 'M4.YZX');
M4.prototype.height = 4;
M4.prototype.width = 4;
addOwnProperties(M4.prototype, Transformable.prototype, 'constructor');
class AABB extends Transformable {
constructor(min = V3.INF, max = V3.INF.negated()) {
super();
this.min = min;
this.max = max;
assertVectors(min, max);
}
static forXYZ(x, y, z) {
return new AABB(V3.O, new V3(x, y, z));
}
static forAABBs(aabbs) {
const result = new AABB();
for (const aabb of aabbs) {
result.addAABB(aabb);
}
return result;
}
addPoint(p) {
assertVectors(p);
this.min = this.min.min(p);
this.max = this.max.max(p);
return this;
}
addPoints(ps) {
ps.forEach(p => this.addPoint(p));
return this;
}
addAABB(aabb) {
assertInst(AABB, aabb);
this.addPoint(aabb.min);
this.addPoint(aabb.max);
return this;
}
/**
* Returns the largest AABB contained in this which doesn't overlap with aabb
* @param aabb
*/
withoutAABB(aabb) {
assertInst(AABB, aabb);
let min, max;
const volume = this.volume(), size = this.size();
let remainingVolume = -Infinity;
for (let i = 0; i < 3; i++) {
const dim = ['x', 'y', 'z'][i];
const cond = aabb.min[dim] - this.min[dim] > this.max[dim] - aabb.max[dim];
const dimMin = cond ? this.min[dim] : Math.max(this.min[dim], aabb.max[dim]);
const dimMax = !cond ? this.max[dim] : Math.min(this.max[dim], aabb.min[dim]);
const newRemainingVolume = (dimMax - dimMin) * volume / size[dim];
if (newRemainingVolume > remainingVolume) {
remainingVolume = newRemainingVolume;
min = this.min.withElement(dim, dimMin);
max = this.max.withElement(dim, dimMax);
}
}
return new AABB(min, max);
}
getIntersectionAABB(aabb) {
assertInst(AABB, aabb);
return new AABB(this.min.max(aabb.min), this.max.min(aabb.max));
}
touchesAABB(aabb) {
assertInst(AABB, aabb);
return !(this.min.x > aabb.max.x || this.max.x < aabb.min.x
|| this.min.y > aabb.max.y || this.max.y < aabb.min.y
|| this.min.z > aabb.max.z || this.max.z < aabb.min.z);
}
fuzzyTouchesAABB(aabb) {
assertInst(AABB, aabb);
return !(lt(aabb.max.x, this.min.x) || lt(this.max.x, aabb.min.x)
|| lt(aabb.max.y, this.min.y) || lt(this.max.y, aabb.min.y)
|| lt(aabb.max.z, this.min.z) || lt(this.max.z, aabb.min.z));
}
intersectsAABB(aabb) {
assertInst(AABB, aabb);
return !(this.min.x >= aabb.max.x || this.max.x <= aabb.min.x
|| this.min.y >= aabb.max.y || this.max.y <= aabb.min.y
|| this.min.z >= aabb.max.z || this.max.z <= aabb.min.z);
}
intersectsAABB2d(aabb) {
assertInst(AABB, aabb);
return !(this.min.x >= aabb.max.x || this.max.x <= aabb.min.x
|| this.min.y >= aabb.max.y || this.max.y <= aabb.min.y);
}
containsPoint(p) {
assertVectors(p);
return this.min.x <= p.x && this.min.y <= p.y && this.min.z <= p.z
&& this.max.x >= p.x && this.max.y >= p.y && this.max.z >= p.z;
}
containsSphere(center, radius) {
assertVectors(center);
assertNumbers(radius);
return this.distanceToPoint(center) > radius;
}
intersectsSphere(center, radius) {
assertVectors(center);
assertNumbers(radius);
return this.distanceToPoint(center) <= radius;
}
distanceToPoint(p) {
assertVectors(p);
const x = p.x, y = p.y, z = p.z;
const min = this.min, max = this.max;
if (this.containsPoint(p)) {
return Math.max(min.x - x, x - max.x, min.y - y, y - max.y, min.z - z, z - max.z);
}
return p.distanceTo(new V3(clamp(x, min.x, max.x), clamp(y, min.y, max.y), clamp(z, min.z, max.z)));
}
containsAABB(aabb) {
assertInst(AABB, aabb);
return this.containsPoint(aabb.min) && this.containsPoint(aabb.max);
}
likeAABB(aabb) {
assertInst(AABB, aabb);
return this.min.like(aabb.min) && this.max.like(aabb.max);
}
intersectsLine(line) {
assertVectors(line.anchor, line.dir1);
const dir = line.dir1.map(el => el || Number.MIN_VALUE);
const minTs = (this.min.minus(line.anchor)).divv(dir);
const maxTs = (this.max.minus(line.anchor)).divv(dir);
const tMin = minTs.min(maxTs).maxElement(), tMax = minTs.max(maxTs).minElement();
return tMin <= tMax && !(tMax < line.tMin || line.tMax < tMin);
}
hasVolume() {
return this.min.x <= this.max.x && this.min.y <= this.max.y && this.min.z <= this.max.z;
}
volume() {
if (!this.hasVolume()) {
return -1;
}
const v = this.max.minus(this.min);
return v.x * v.y * v.z;
}
size() {
return this.max.minus(this.min);
}
getCenter() {
return this.min.plus(this.max).div(2);
}
transform(m4) {
assertInst(M4, m4);
assert(m4.isAxisAligned());
const aabb = new AABB();
aabb.addPoint(m4.transformPoint(this.min));
aabb.addPoint(m4.transformPoint(this.max));
return aabb;
}
ofTransformed(m4) {
assertInst(M4, m4);
const aabb = new AABB();
aabb.addPoints(m4.transformedPoints(this.corners()));
return aabb;
}
corners() {
const min = this.min, max = this.max;
return [
min,
new V3(min.x, min.y, max.z),
new V3(min.x, max.y, min.z),
new V3(min.x, max.y, max.z),
new V3(max.x, min.y, min.z),
new V3(max.x, min.y, max.z),
new V3(max.x, max.y, min.z),
max,
];
}
toString() {
return callsce('new AABB', this.min, this.max);
}
toSource() {
return this.toString();
}
}
//# sourceMappingURL=bundle.module.js.map
var ts3dutils = Object.freeze({
V3: V3,
V: V,
M4: M4,
Matrix: Matrix,
Vector: Vector,
P3YZ: P3YZ,
P3ZX: P3ZX,
P3XY: P3XY,
Transformable: Transformable,
TAU: TAU,
NLA_DEBUG: NLA_DEBUG,
NLA_PRECISION: NLA_PRECISION,
disableConsole: disableConsole,
enableConsole: enableConsole,
hasConstructor: hasConstructor,
getIntervals: getIntervals,
assertVectors: assertVectors,
assertInst: assertInst,
assertNumbers: assertNumbers,
assert: assert,
assertNever: assertNever,
assertf: assertf,
lerp: lerp,
eq0: eq0,
eq: eq,
lt: lt,
gt: gt,
le: le,
ge: ge,
eqAngle: eqAngle,
zeroAngle: zeroAngle,
snap: snap,
snap2: snap2,
snapEPS: snapEPS,
snap0: snap0,
canonAngle: canonAngle,
eq02: eq02,
eq2: eq2,
round10: round10,
floor10: floor10,
ceil10: ceil10,
GOLDEN_RATIO: GOLDEN_RATIO,
repeatString: repeatString,
mod: mod,
arraySwap: arraySwap,
arrayCopy: arrayCopy,
clamp: clamp,
between: between,
fuzzyBetween: fuzzyBetween,
randomColor: randomColor,
mapPush: mapPush,
arrayCopyStep: arrayCopyStep,
arrayCopyBlocks: arrayCopyBlocks,
arrayRange: arrayRange,
arrayFromFunction: arrayFromFunction,
fuzzyUniques: fuzzyUniques,
fuzzyUniquesF: fuzzyUniquesF,
addOwnProperties: addOwnProperties,
defaultRoundFunction: defaultRoundFunction,
forceFinite: forceFinite,
MINUS: MINUS,
floatHashCode: floatHashCode,
combinations: combinations,
arithmeticGeometricMean: arithmeticGeometricMean,
EllipticF: EllipticF,
EllipticE: EllipticE,
DEG: DEG,
rad2deg: rad2deg,
numberToStr: numberToStr,
time: time,
SCE: SCE,
STR: STR,
isCCW: isCCW,
doubleSignedArea: doubleSignedArea,
pqFormula: pqFormula,
solveCubicReal2: solveCubicReal2,
checkDerivate: checkDerivate,
getRoots: getRoots,
bisect: bisect,
newtonIterate: newtonIterate,
newtonIterate1d: newtonIterate1d,
newtonIterateWithDerivative: newtonIterateWithDerivative,
newtonIterateSmart: newtonIterateSmart,
newtonIterate2d: newtonIterate2d,
newtonIterate2dWithDerivatives: newtonIterate2dWithDerivatives,
gaussLegendre24Xs: gaussLegendre24Xs,
gaussLegendre24Weights: gaussLegendre24Weights,
gaussLegendreQuadrature24: gaussLegendreQuadrature24,
glq24_11: glq24_11,
glqInSteps: glqInSteps,
midpointRuleQuadrature: midpointRuleQuadrature,
callsce: callsce,
AABB: AABB
});
var chroma$2 = createCommonjsModule(function (module, exports) {
/**
* @license
*
* chroma.js - JavaScript library for color conversions
*
* Copyright (c) 2011-2017, Gregor Aisch
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. The name Gregor Aisch may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL GREGOR AISCH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
(function() {
var Color, DEG2RAD, LAB_CONSTANTS, PI, PITHIRD, RAD2DEG, TWOPI, _average_lrgb, _guess_formats, _guess_formats_sorted, _input, _interpolators, abs, atan2, bezier, blend, blend_f, brewer, burn, chroma, clip_rgb, cmyk2rgb, colors, cos, css2rgb, darken, dodge, each, floor, hcg2rgb, hex2rgb, hsi2rgb, hsl2css, hsl2rgb, hsv2rgb, interpolate, interpolate_hsx, interpolate_lab, interpolate_lrgb, interpolate_num, interpolate_rgb, lab2lch, lab2rgb, lab_xyz, lch2lab, lch2rgb, lighten, limit, log, luminance_x, m, max, multiply, normal, num2rgb, overlay, pow, rgb2cmyk, rgb2css, rgb2hcg, rgb2hex, rgb2hsi, rgb2hsl, rgb2hsv, rgb2lab, rgb2lch, rgb2luminance, rgb2num, rgb2temperature, rgb2xyz, rgb_xyz, rnd, root, round, screen, sin, sqrt, temperature2rgb, type, unpack, w3cx11, xyz_lab, xyz_rgb,
slice = [].slice;
type = (function() {
/*
for browser-safe type checking+
ported from jQuery's $.type
*/
var classToType, len, name, o, ref;
classToType = {};
ref = "Boolean Number String Function Array Date RegExp Undefined Null".split(" ");
for (o = 0, len = ref.length; o < len; o++) {
name = ref[o];
classToType["[object " + name + "]"] = name.toLowerCase();
}
return function(obj) {
var strType;
strType = Object.prototype.toString.call(obj);
return classToType[strType] || "object";
};
})();
limit = function(x, min, max) {
if (min == null) {
min = 0;
}
if (max == null) {
max = 1;
}
if (x < min) {
x = min;
}
if (x > max) {
x = max;
}
return x;
};
unpack = function(args) {
if (args.length >= 3) {
return [].slice.call(args);
} else {
return args[0];
}
};
clip_rgb = function(rgb) {
var i, o;
rgb._clipped = false;
rgb._unclipped = rgb.slice(0);
for (i = o = 0; o < 3; i = ++o) {
if (i < 3) {
if (rgb[i] < 0 || rgb[i] > 255) {
rgb._clipped = true;
}
if (rgb[i] < 0) {
rgb[i] = 0;
}
if (rgb[i] > 255) {
rgb[i] = 255;
}
} else if (i === 3) {
if (rgb[i] < 0) {
rgb[i] = 0;
}
if (rgb[i] > 1) {
rgb[i] = 1;
}
}
}
if (!rgb._clipped) {
delete rgb._unclipped;
}
return rgb;
};
PI = Math.PI, round = Math.round, cos = Math.cos, floor = Math.floor, pow = Math.pow, log = Math.log, sin = Math.sin, sqrt = Math.sqrt, atan2 = Math.atan2, max = Math.max, abs = Math.abs;
TWOPI = PI * 2;
PITHIRD = PI / 3;
DEG2RAD = PI / 180;
RAD2DEG = 180 / PI;
chroma = function() {
if (arguments[0] instanceof Color) {
return arguments[0];
}
return (function(func, args, ctor) {
ctor.prototype = func.prototype;
var child = new ctor, result = func.apply(child, args);
return Object(result) === result ? result : child;
})(Color, arguments, function(){});
};
chroma["default"] = chroma;
_interpolators = [];
if (('object' !== "undefined" && module !== null) && (module.exports != null)) {
module.exports = chroma;
}
if (typeof undefined === 'function' && undefined.amd) {
undefined([], function() {
return chroma;
});
} else {
root = 'object' !== "undefined" && exports !== null ? exports : this;
root.chroma = chroma;
}
chroma.version = '1.3.4';
_input = {};
_guess_formats = [];
_guess_formats_sorted = false;
Color = (function() {
function Color() {
var arg, args, chk, len, len1, me, mode, o, w;
me = this;
args = [];
for (o = 0, len = arguments.length; o < len; o++) {
arg = arguments[o];
if (arg != null) {
args.push(arg);
}
}
if (args.length > 1) {
mode = args[args.length - 1];
}
if (_input[mode] != null) {
me._rgb = clip_rgb(_input[mode](unpack(args.slice(0, -1))));
} else {
if (!_guess_formats_sorted) {
_guess_formats = _guess_formats.sort(function(a, b) {
return b.p - a.p;
});
_guess_formats_sorted = true;
}
for (w = 0, len1 = _guess_formats.length; w < len1; w++) {
chk = _guess_formats[w];
mode = chk.test.apply(chk, args);
if (mode) {
break;
}
}
if (mode) {
me._rgb = clip_rgb(_input[mode].apply(_input, args));
}
}
if (me._rgb == null) {
console.warn('unknown format: ' + args);
}
if (me._rgb == null) {
me._rgb = [0, 0, 0];
}
if (me._rgb.length === 3) {
me._rgb.push(1);
}
}
Color.prototype.toString = function() {
return this.hex();
};
Color.prototype.clone = function() {
return chroma(me._rgb);
};
return Color;
})();
chroma._input = _input;
/**
ColorBrewer colors for chroma.js
Copyright (c) 2002 Cynthia Brewer, Mark Harrower, and The
Pennsylvania State University.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
@preserve
*/
chroma.brewer = brewer = {
OrRd: ['#fff7ec', '#fee8c8', '#fdd49e', '#fdbb84', '#fc8d59', '#ef6548', '#d7301f', '#b30000', '#7f0000'],
PuBu: ['#fff7fb', '#ece7f2', '#d0d1e6', '#a6bddb', '#74a9cf', '#3690c0', '#0570b0', '#045a8d', '#023858'],
BuPu: ['#f7fcfd', '#e0ecf4', '#bfd3e6', '#9ebcda', '#8c96c6', '#8c6bb1', '#88419d', '#810f7c', '#4d004b'],
Oranges: ['#fff5eb', '#fee6ce', '#fdd0a2', '#fdae6b', '#fd8d3c', '#f16913', '#d94801', '#a63603', '#7f2704'],
BuGn: ['#f7fcfd', '#e5f5f9', '#ccece6', '#99d8c9', '#66c2a4', '#41ae76', '#238b45', '#006d2c', '#00441b'],
YlOrBr: ['#ffffe5', '#fff7bc', '#fee391', '#fec44f', '#fe9929', '#ec7014', '#cc4c02', '#993404', '#662506'],
YlGn: ['#ffffe5', '#f7fcb9', '#d9f0a3', '#addd8e', '#78c679', '#41ab5d', '#238443', '#006837', '#004529'],
Reds: ['#fff5f0', '#fee0d2', '#fcbba1', '#fc9272', '#fb6a4a', '#ef3b2c', '#cb181d', '#a50f15', '#67000d'],
RdPu: ['#fff7f3', '#fde0dd', '#fcc5c0', '#fa9fb5', '#f768a1', '#dd3497', '#ae017e', '#7a0177', '#49006a'],
Greens: ['#f7fcf5', '#e5f5e0', '#c7e9c0', '#a1d99b', '#74c476', '#41ab5d', '#238b45', '#006d2c', '#00441b'],
YlGnBu: ['#ffffd9', '#edf8b1', '#c7e9b4', '#7fcdbb', '#41b6c4', '#1d91c0', '#225ea8', '#253494', '#081d58'],
Purples: ['#fcfbfd', '#efedf5', '#dadaeb', '#bcbddc', '#9e9ac8', '#807dba', '#6a51a3', '#54278f', '#3f007d'],
GnBu: ['#f7fcf0', '#e0f3db', '#ccebc5', '#a8ddb5', '#7bccc4', '#4eb3d3', '#2b8cbe', '#0868ac', '#084081'],
Greys: ['#ffffff', '#f0f0f0', '#d9d9d9', '#bdbdbd', '#969696', '#737373', '#525252', '#252525', '#000000'],
YlOrRd: ['#ffffcc', '#ffeda0', '#fed976', '#feb24c', '#fd8d3c', '#fc4e2a', '#e31a1c', '#bd0026', '#800026'],
PuRd: ['#f7f4f9', '#e7e1ef', '#d4b9da', '#c994c7', '#df65b0', '#e7298a', '#ce1256', '#980043', '#67001f'],
Blues: ['#f7fbff', '#deebf7', '#c6dbef', '#9ecae1', '#6baed6', '#4292c6', '#2171b5', '#08519c', '#08306b'],
PuBuGn: ['#fff7fb', '#ece2f0', '#d0d1e6', '#a6bddb', '#67a9cf', '#3690c0', '#02818a', '#016c59', '#014636'],
Viridis: ['#440154', '#482777', '#3f4a8a', '#31678e', '#26838f', '#1f9d8a', '#6cce5a', '#b6de2b', '#fee825'],
Spectral: ['#9e0142', '#d53e4f', '#f46d43', '#fdae61', '#fee08b', '#ffffbf', '#e6f598', '#abdda4', '#66c2a5', '#3288bd', '#5e4fa2'],
RdYlGn: ['#a50026', '#d73027', '#f46d43', '#fdae61', '#fee08b', '#ffffbf', '#d9ef8b', '#a6d96a', '#66bd63', '#1a9850', '#006837'],
RdBu: ['#67001f', '#b2182b', '#d6604d', '#f4a582', '#fddbc7', '#f7f7f7', '#d1e5f0', '#92c5de', '#4393c3', '#2166ac', '#053061'],
PiYG: ['#8e0152', '#c51b7d', '#de77ae', '#f1b6da', '#fde0ef', '#f7f7f7', '#e6f5d0', '#b8e186', '#7fbc41', '#4d9221', '#276419'],
PRGn: ['#40004b', '#762a83', '#9970ab', '#c2a5cf', '#e7d4e8', '#f7f7f7', '#d9f0d3', '#a6dba0', '#5aae61', '#1b7837', '#00441b'],
RdYlBu: ['#a50026', '#d73027', '#f46d43', '#fdae61', '#fee090', '#ffffbf', '#e0f3f8', '#abd9e9', '#74add1', '#4575b4', '#313695'],
BrBG: ['#543005', '#8c510a', '#bf812d', '#dfc27d', '#f6e8c3', '#f5f5f5', '#c7eae5', '#80cdc1', '#35978f', '#01665e', '#003c30'],
RdGy: ['#67001f', '#b2182b', '#d6604d', '#f4a582', '#fddbc7', '#ffffff', '#e0e0e0', '#bababa', '#878787', '#4d4d4d', '#1a1a1a'],
PuOr: ['#7f3b08', '#b35806', '#e08214', '#fdb863', '#fee0b6', '#f7f7f7', '#d8daeb', '#b2abd2', '#8073ac', '#542788', '#2d004b'],
Set2: ['#66c2a5', '#fc8d62', '#8da0cb', '#e78ac3', '#a6d854', '#ffd92f', '#e5c494', '#b3b3b3'],
Accent: ['#7fc97f', '#beaed4', '#fdc086', '#ffff99', '#386cb0', '#f0027f', '#bf5b17', '#666666'],
Set1: ['#e41a1c', '#377eb8', '#4daf4a', '#984ea3', '#ff7f00', '#ffff33', '#a65628', '#f781bf', '#999999'],
Set3: ['#8dd3c7', '#ffffb3', '#bebada', '#fb8072', '#80b1d3', '#fdb462', '#b3de69', '#fccde5', '#d9d9d9', '#bc80bd', '#ccebc5', '#ffed6f'],
Dark2: ['#1b9e77', '#d95f02', '#7570b3', '#e7298a', '#66a61e', '#e6ab02', '#a6761d', '#666666'],
Paired: ['#a6cee3', '#1f78b4', '#b2df8a', '#33a02c', '#fb9a99', '#e31a1c', '#fdbf6f', '#ff7f00', '#cab2d6', '#6a3d9a', '#ffff99', '#b15928'],
Pastel2: ['#b3e2cd', '#fdcdac', '#cbd5e8', '#f4cae4', '#e6f5c9', '#fff2ae', '#f1e2cc', '#cccccc'],
Pastel1: ['#fbb4ae', '#b3cde3', '#ccebc5', '#decbe4', '#fed9a6', '#ffffcc', '#e5d8bd', '#fddaec', '#f2f2f2']
};
(function() {
var key, results;
results = [];
for (key in brewer) {
results.push(brewer[key.toLowerCase()] = brewer[key]);
}
return results;
})();
/**
X11 color names
http://www.w3.org/TR/css3-color/#svg-color
*/
w3cx11 = {
aliceblue: '#f0f8ff',
antiquewhite: '#faebd7',
aqua: '#00ffff',
aquamarine: '#7fffd4',
azure: '#f0ffff',
beige: '#f5f5dc',
bisque: '#ffe4c4',
black: '#000000',
blanchedalmond: '#ffebcd',
blue: '#0000ff',
blueviolet: '#8a2be2',
brown: '#a52a2a',
burlywood: '#deb887',
cadetblue: '#5f9ea0',
chartreuse: '#7fff00',
chocolate: '#d2691e',
coral: '#ff7f50',
cornflower: '#6495ed',
cornflowerblue: '#6495ed',
cornsilk: '#fff8dc',
crimson: '#dc143c',
cyan: '#00ffff',
darkblue: '#00008b',
darkcyan: '#008b8b',
darkgoldenrod: '#b8860b',
darkgray: '#a9a9a9',
darkgreen: '#006400',
darkgrey: '#a9a9a9',
darkkhaki: '#bdb76b',
darkmagenta: '#8b008b',
darkolivegreen: '#556b2f',
darkorange: '#ff8c00',
darkorchid: '#9932cc',
darkred: '#8b0000',
darksalmon: '#e9967a',
darkseagreen: '#8fbc8f',
darkslateblue: '#483d8b',
darkslategray: '#2f4f4f',
darkslategrey: '#2f4f4f',
darkturquoise: '#00ced1',
darkviolet: '#9400d3',
deeppink: '#ff1493',
deepskyblue: '#00bfff',
dimgray: '#696969',
dimgrey: '#696969',
dodgerblue: '#1e90ff',
firebrick: '#b22222',
floralwhite: '#fffaf0',
forestgreen: '#228b22',
fuchsia: '#ff00ff',
gainsboro: '#dcdcdc',
ghostwhite: '#f8f8ff',
gold: '#ffd700',
goldenrod: '#daa520',
gray: '#808080',
green: '#008000',
greenyellow: '#adff2f',
grey: '#808080',
honeydew: '#f0fff0',
hotpink: '#ff69b4',
indianred: '#cd5c5c',
indigo: '#4b0082',
ivory: '#fffff0',
khaki: '#f0e68c',
laserlemon: '#ffff54',
lavender: '#e6e6fa',
lavenderblush: '#fff0f5',
lawngreen: '#7cfc00',
lemonchiffon: '#fffacd',
lightblue: '#add8e6',
lightcoral: '#f08080',
lightcyan: '#e0ffff',
lightgoldenrod: '#fafad2',
lightgoldenrodyellow: '#fafad2',
lightgray: '#d3d3d3',
lightgreen: '#90ee90',
lightgrey: '#d3d3d3',
lightpink: '#ffb6c1',
lightsalmon: '#ffa07a',
lightseagreen: '#20b2aa',
lightskyblue: '#87cefa',
lightslategray: '#778899',
lightslategrey: '#778899',
lightsteelblue: '#b0c4de',
lightyellow: '#ffffe0',
lime: '#00ff00',
limegreen: '#32cd32',
linen: '#faf0e6',
magenta: '#ff00ff',
maroon: '#800000',
maroon2: '#7f0000',
maroon3: '#b03060',
mediumaquamarine: '#66cdaa',
mediumblue: '#0000cd',
mediumorchid: '#ba55d3',
mediumpurple: '#9370db',
mediumseagreen: '#3cb371',
mediumslateblue: '#7b68ee',
mediumspringgreen: '#00fa9a',
mediumturquoise: '#48d1cc',
mediumvioletred: '#c71585',
midnightblue: '#191970',
mintcream: '#f5fffa',
mistyrose: '#ffe4e1',
moccasin: '#ffe4b5',
navajowhite: '#ffdead',
navy: '#000080',
oldlace: '#fdf5e6',
olive: '#808000',
olivedrab: '#6b8e23',
orange: '#ffa500',
orangered: '#ff4500',
orchid: '#da70d6',
palegoldenrod: '#eee8aa',
palegreen: '#98fb98',
paleturquoise: '#afeeee',
palevioletred: '#db7093',
papayawhip: '#ffefd5',
peachpuff: '#ffdab9',
peru: '#cd853f',
pink: '#ffc0cb',
plum: '#dda0dd',
powderblue: '#b0e0e6',
purple: '#800080',
purple2: '#7f007f',
purple3: '#a020f0',
rebeccapurple: '#663399',
red: '#ff0000',
rosybrown: '#bc8f8f',
royalblue: '#4169e1',
saddlebrown: '#8b4513',
salmon: '#fa8072',
sandybrown: '#f4a460',
seagreen: '#2e8b57',
seashell: '#fff5ee',
sienna: '#a0522d',
silver: '#c0c0c0',
skyblue: '#87ceeb',
slateblue: '#6a5acd',
slategray: '#708090',
slategrey: '#708090',
snow: '#fffafa',
springgreen: '#00ff7f',
steelblue: '#4682b4',
tan: '#d2b48c',
teal: '#008080',
thistle: '#d8bfd8',
tomato: '#ff6347',
turquoise: '#40e0d0',
violet: '#ee82ee',
wheat: '#f5deb3',
white: '#ffffff',
whitesmoke: '#f5f5f5',
yellow: '#ffff00',
yellowgreen: '#9acd32'
};
chroma.colors = colors = w3cx11;
lab2rgb = function() {
var a, args, b, g, l, r, x, y, z;
args = unpack(arguments);
l = args[0], a = args[1], b = args[2];
y = (l + 16) / 116;
x = isNaN(a) ? y : y + a / 500;
z = isNaN(b) ? y : y - b / 200;
y = LAB_CONSTANTS.Yn * lab_xyz(y);
x = LAB_CONSTANTS.Xn * lab_xyz(x);
z = LAB_CONSTANTS.Zn * lab_xyz(z);
r = xyz_rgb(3.2404542 * x - 1.5371385 * y - 0.4985314 * z);
g = xyz_rgb(-0.9692660 * x + 1.8760108 * y + 0.0415560 * z);
b = xyz_rgb(0.0556434 * x - 0.2040259 * y + 1.0572252 * z);
return [r, g, b, args.length > 3 ? args[3] : 1];
};
xyz_rgb = function(r) {
return 255 * (r <= 0.00304 ? 12.92 * r : 1.055 * pow(r, 1 / 2.4) - 0.055);
};
lab_xyz = function(t) {
if (t > LAB_CONSTANTS.t1) {
return t * t * t;
} else {
return LAB_CONSTANTS.t2 * (t - LAB_CONSTANTS.t0);
}
};
LAB_CONSTANTS = {
Kn: 18,
Xn: 0.950470,
Yn: 1,
Zn: 1.088830,
t0: 0.137931034,
t1: 0.206896552,
t2: 0.12841855,
t3: 0.008856452
};
rgb2lab = function() {
var b, g, r, ref, ref1, x, y, z;
ref = unpack(arguments), r = ref[0], g = ref[1], b = ref[2];
ref1 = rgb2xyz(r, g, b), x = ref1[0], y = ref1[1], z = ref1[2];
return [116 * y - 16, 500 * (x - y), 200 * (y - z)];
};
rgb_xyz = function(r) {
if ((r /= 255) <= 0.04045) {
return r / 12.92;
} else {
return pow((r + 0.055) / 1.055, 2.4);
}
};
xyz_lab = function(t) {
if (t > LAB_CONSTANTS.t3) {
return pow(t, 1 / 3);
} else {
return t / LAB_CONSTANTS.t2 + LAB_CONSTANTS.t0;
}
};
rgb2xyz = function() {
var b, g, r, ref, x, y, z;
ref = unpack(arguments), r = ref[0], g = ref[1], b = ref[2];
r = rgb_xyz(r);
g = rgb_xyz(g);
b = rgb_xyz(b);
x = xyz_lab((0.4124564 * r + 0.3575761 * g + 0.1804375 * b) / LAB_CONSTANTS.Xn);
y = xyz_lab((0.2126729 * r + 0.7151522 * g + 0.0721750 * b) / LAB_CONSTANTS.Yn);
z = xyz_lab((0.0193339 * r + 0.1191920 * g + 0.9503041 * b) / LAB_CONSTANTS.Zn);
return [x, y, z];
};
chroma.lab = function() {
return (function(func, args, ctor) {
ctor.prototype = func.prototype;
var child = new ctor, result = func.apply(child, args);
return Object(result) === result ? result : child;
})(Color, slice.call(arguments).concat(['lab']), function(){});
};
_input.lab = lab2rgb;
Color.prototype.lab = function() {
return rgb2lab(this._rgb);
};
bezier = function(colors) {
var I, I0, I1, c, lab0, lab1, lab2, lab3, ref, ref1, ref2;
colors = (function() {
var len, o, results;
results = [];
for (o = 0, len = colors.length; o < len; o++) {
c = colors[o];
results.push(chroma(c));
}
return results;
})();
if (colors.length === 2) {
ref = (function() {
var len, o, results;
results = [];
for (o = 0, len = colors.length; o < len; o++) {
c = colors[o];
results.push(c.lab());
}
return results;
})(), lab0 = ref[0], lab1 = ref[1];
I = function(t) {
var i, lab;
lab = (function() {
var o, results;
results = [];
for (i = o = 0; o <= 2; i = ++o) {
results.push(lab0[i] + t * (lab1[i] - lab0[i]));
}
return results;
})();
return chroma.lab.apply(chroma, lab);
};
} else if (colors.length === 3) {
ref1 = (function() {
var len, o, results;
results = [];
for (o = 0, len = colors.length; o < len; o++) {
c = colors[o];
results.push(c.lab());
}
return results;
})(), lab0 = ref1[0], lab1 = ref1[1], lab2 = ref1[2];
I = function(t) {
var i, lab;
lab = (function() {
var o, results;
results = [];
for (i = o = 0; o <= 2; i = ++o) {
results.push((1 - t) * (1 - t) * lab0[i] + 2 * (1 - t) * t * lab1[i] + t * t * lab2[i]);
}
return results;
})();
return chroma.lab.apply(chroma, lab);
};
} else if (colors.length === 4) {
ref2 = (function() {
var len, o, results;
results = [];
for (o = 0, len = colors.length; o < len; o++) {
c = colors[o];
results.push(c.lab());
}
return results;
})(), lab0 = ref2[0], lab1 = ref2[1], lab2 = ref2[2], lab3 = ref2[3];
I = function(t) {
var i, lab;
lab = (function() {
var o, results;
results = [];
for (i = o = 0; o <= 2; i = ++o) {
results.push((1 - t) * (1 - t) * (1 - t) * lab0[i] + 3 * (1 - t) * (1 - t) * t * lab1[i] + 3 * (1 - t) * t * t * lab2[i] + t * t * t * lab3[i]);
}
return results;
})();
return chroma.lab.apply(chroma, lab);
};
} else if (colors.length === 5) {
I0 = bezier(colors.slice(0, 3));
I1 = bezier(colors.slice(2, 5));
I = function(t) {
if (t < 0.5) {
return I0(t * 2);
} else {
return I1((t - 0.5) * 2);
}
};
}
return I;
};
chroma.bezier = function(colors) {
var f;
f = bezier(colors);
f.scale = function() {
return chroma.scale(f);
};
return f;
};
/*
chroma.js
Copyright (c) 2011-2013, Gregor Aisch
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* The name Gregor Aisch may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL GREGOR AISCH OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@source: https://github.com/gka/chroma.js
*/
chroma.cubehelix = function(start, rotations, hue, gamma, lightness) {
var dh, dl, f;
if (start == null) {
start = 300;
}
if (rotations == null) {
rotations = -1.5;
}
if (hue == null) {
hue = 1;
}
if (gamma == null) {
gamma = 1;
}
if (lightness == null) {
lightness = [0, 1];
}
dh = 0;
if (type(lightness) === 'array') {
dl = lightness[1] - lightness[0];
} else {
dl = 0;
lightness = [lightness, lightness];
}
f = function(fract) {
var a, amp, b, cos_a, g, h, l, r, sin_a;
a = TWOPI * ((start + 120) / 360 + rotations * fract);
l = pow(lightness[0] + dl * fract, gamma);
h = dh !== 0 ? hue[0] + fract * dh : hue;
amp = h * l * (1 - l) / 2;
cos_a = cos(a);
sin_a = sin(a);
r = l + amp * (-0.14861 * cos_a + 1.78277 * sin_a);
g = l + amp * (-0.29227 * cos_a - 0.90649 * sin_a);
b = l + amp * (+1.97294 * cos_a);
return chroma(clip_rgb([r * 255, g * 255, b * 255]));
};
f.start = function(s) {
if (s == null) {
return start;
}
start = s;
return f;
};
f.rotations = function(r) {
if (r == null) {
return rotations;
}
rotations = r;
return f;
};
f.gamma = function(g) {
if (g == null) {
return gamma;
}
gamma = g;
return f;
};
f.hue = function(h) {
if (h == null) {
return hue;
}
hue = h;
if (type(hue) === 'array') {
dh = hue[1] - hue[0];
if (dh === 0) {
hue = hue[1];
}
} else {
dh = 0;
}
return f;
};
f.lightness = function(h) {
if (h == null) {
return lightness;
}
if (type(h) === 'array') {
lightness = h;
dl = h[1] - h[0];
} else {
lightness = [h, h];
dl = 0;
}
return f;
};
f.scale = function() {
return chroma.scale(f);
};
f.hue(hue);
return f;
};
chroma.random = function() {
var code, digits, i, o;
digits = '0123456789abcdef';
code = '#';
for (i = o = 0; o < 6; i = ++o) {
code += digits.charAt(floor(Math.random() * 16));
}
return new Color(code);
};
_interpolators = [];
interpolate = function(col1, col2, f, m) {
var interpol, len, o, res;
if (f == null) {
f = 0.5;
}
if (m == null) {
m = 'rgb';
}
/*
interpolates between colors
f = 0 --> me
f = 1 --> col
*/
if (type(col1) !== 'object') {
col1 = chroma(col1);
}
if (type(col2) !== 'object') {
col2 = chroma(col2);
}
for (o = 0, len = _interpolators.length; o < len; o++) {
interpol = _interpolators[o];
if (m === interpol[0]) {
res = interpol[1](col1, col2, f, m);
break;
}
}
if (res == null) {
throw "color mode " + m + " is not supported";
}
return res.alpha(col1.alpha() + f * (col2.alpha() - col1.alpha()));
};
chroma.interpolate = interpolate;
Color.prototype.interpolate = function(col2, f, m) {
return interpolate(this, col2, f, m);
};
chroma.mix = interpolate;
Color.prototype.mix = Color.prototype.interpolate;
_input.rgb = function() {
var k, ref, results, v;
ref = unpack(arguments);
results = [];
for (k in ref) {
v = ref[k];
results.push(v);
}
return results;
};
chroma.rgb = function() {
return (function(func, args, ctor) {
ctor.prototype = func.prototype;
var child = new ctor, result = func.apply(child, args);
return Object(result) === result ? result : child;
})(Color, slice.call(arguments).concat(['rgb']), function(){});
};
Color.prototype.rgb = function(round) {
if (round == null) {
round = true;
}
if (round) {
return this._rgb.map(Math.round).slice(0, 3);
} else {
return this._rgb.slice(0, 3);
}
};
Color.prototype.rgba = function(round) {
if (round == null) {
round = true;
}
if (!round) {
return this._rgb.slice(0);
}
return [Math.round(this._rgb[0]), Math.round(this._rgb[1]), Math.round(this._rgb[2]), this._rgb[3]];
};
_guess_formats.push({
p: 3,
test: function(n) {
var a;
a = unpack(arguments);
if (type(a) === 'array' && a.length === 3) {
return 'rgb';
}
if (a.length === 4 && type(a[3]) === "number" && a[3] >= 0 && a[3] <= 1) {
return 'rgb';
}
}
});
_input.lrgb = _input.rgb;
interpolate_lrgb = function(col1, col2, f, m) {
var xyz0, xyz1;
xyz0 = col1._rgb;
xyz1 = col2._rgb;
return new Color(sqrt(pow(xyz0[0], 2) * (1 - f) + pow(xyz1[0], 2) * f), sqrt(pow(xyz0[1], 2) * (1 - f) + pow(xyz1[1], 2) * f), sqrt(pow(xyz0[2], 2) * (1 - f) + pow(xyz1[2], 2) * f), m);
};
_average_lrgb = function(colors) {
var col, f, len, o, rgb, xyz;
f = 1 / colors.length;
xyz = [0, 0, 0, 0];
for (o = 0, len = colors.length; o < len; o++) {
col = colors[o];
rgb = col._rgb;
xyz[0] += pow(rgb[0], 2) * f;
xyz[1] += pow(rgb[1], 2) * f;
xyz[2] += pow(rgb[2], 2) * f;
xyz[3] += rgb[3] * f;
}
xyz[0] = sqrt(xyz[0]);
xyz[1] = sqrt(xyz[1]);
xyz[2] = sqrt(xyz[2]);
return new Color(xyz);
};
_interpolators.push(['lrgb', interpolate_lrgb]);
chroma.average = function(colors, mode) {
var A, alpha, c, cnt, dx, dy, first, i, l, len, o, xyz, xyz2;
if (mode == null) {
mode = 'rgb';
}
l = colors.length;
colors = colors.map(function(c) {
return chroma(c);
});
first = colors.splice(0, 1)[0];
if (mode === 'lrgb') {
return _average_lrgb(colors);
}
xyz = first.get(mode);
cnt = [];
dx = 0;
dy = 0;
for (i in xyz) {
xyz[i] = xyz[i] || 0;
cnt.push(!isNaN(xyz[i]) ? 1 : 0);
if (mode.charAt(i) === 'h' && !isNaN(xyz[i])) {
A = xyz[i] / 180 * PI;
dx += cos(A);
dy += sin(A);
}
}
alpha = first.alpha();
for (o = 0, len = colors.length; o < len; o++) {
c = colors[o];
xyz2 = c.get(mode);
alpha += c.alpha();
for (i in xyz) {
if (!isNaN(xyz2[i])) {
xyz[i] += xyz2[i];
cnt[i] += 1;
if (mode.charAt(i) === 'h') {
A = xyz[i] / 180 * PI;
dx += cos(A);
dy += sin(A);
}
}
}
}
for (i in xyz) {
xyz[i] = xyz[i] / cnt[i];
if (mode.charAt(i) === 'h') {
A = atan2(dy / cnt[i], dx / cnt[i]) / PI * 180;
while (A < 0) {
A += 360;
}
while (A >= 360) {
A -= 360;
}
xyz[i] = A;
}
}
return chroma(xyz, mode).alpha(alpha / l);
};
hex2rgb = function(hex) {
var a, b, g, r, rgb, u;
if (hex.match(/^#?([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/)) {
if (hex.length === 4 || hex.length === 7) {
hex = hex.substr(1);
}
if (hex.length === 3) {
hex = hex.split("");
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
}
u = parseInt(hex, 16);
r = u >> 16;
g = u >> 8 & 0xFF;
b = u & 0xFF;
return [r, g, b, 1];
}
if (hex.match(/^#?([A-Fa-f0-9]{8})$/)) {
if (hex.length === 9) {
hex = hex.substr(1);
}
u = parseInt(hex, 16);
r = u >> 24 & 0xFF;
g = u >> 16 & 0xFF;
b = u >> 8 & 0xFF;
a = round((u & 0xFF) / 0xFF * 100) / 100;
return [r, g, b, a];
}
if ((_input.css != null) && (rgb = _input.css(hex))) {
return rgb;
}
throw "unknown color: " + hex;
};
rgb2hex = function(channels, mode) {
var a, b, g, hxa, r, str, u;
if (mode == null) {
mode = 'rgb';
}
r = channels[0], g = channels[1], b = channels[2], a = channels[3];
r = Math.round(r);
g = Math.round(g);
b = Math.round(b);
u = r << 16 | g << 8 | b;
str = "000000" + u.toString(16);
str = str.substr(str.length - 6);
hxa = '0' + round(a * 255).toString(16);
hxa = hxa.substr(hxa.length - 2);
return "#" + (function() {
switch (mode.toLowerCase()) {
case 'rgba':
return str + hxa;
case 'argb':
return hxa + str;
default:
return str;
}
})();
};
_input.hex = function(h) {
return hex2rgb(h);
};
chroma.hex = function() {
return (function(func, args, ctor) {
ctor.prototype = func.prototype;
var child = new ctor, result = func.apply(child, args);
return Object(result) === result ? result : child;
})(Color, slice.call(arguments).concat(['hex']), function(){});
};
Color.prototype.hex = function(mode) {
if (mode == null) {
mode = 'rgb';
}
return rgb2hex(this._rgb, mode);
};
_guess_formats.push({
p: 4,
test: function(n) {
if (arguments.length === 1 && type(n) === "string") {
return 'hex';
}
}
});
hsl2rgb = function() {
var args, b, c, g, h, i, l, o, r, ref, s, t1, t2, t3;
args = unpack(arguments);
h = args[0], s = args[1], l = args[2];
if (s === 0) {
r = g = b = l * 255;
} else {
t3 = [0, 0, 0];
c = [0, 0, 0];
t2 = l < 0.5 ? l * (1 + s) : l + s - l * s;
t1 = 2 * l - t2;
h /= 360;
t3[0] = h + 1 / 3;
t3[1] = h;
t3[2] = h - 1 / 3;
for (i = o = 0; o <= 2; i = ++o) {
if (t3[i] < 0) {
t3[i] += 1;
}
if (t3[i] > 1) {
t3[i] -= 1;
}
if (6 * t3[i] < 1) {
c[i] = t1 + (t2 - t1) * 6 * t3[i];
} else if (2 * t3[i] < 1) {
c[i] = t2;
} else if (3 * t3[i] < 2) {
c[i] = t1 + (t2 - t1) * ((2 / 3) - t3[i]) * 6;
} else {
c[i] = t1;
}
}
ref = [round(c[0] * 255), round(c[1] * 255), round(c[2] * 255)], r = ref[0], g = ref[1], b = ref[2];
}
if (args.length > 3) {
return [r, g, b, args[3]];
} else {
return [r, g, b];
}
};
rgb2hsl = function(r, g, b) {
var h, l, min, ref, s;
if (r !== void 0 && r.length >= 3) {
ref = r, r = ref[0], g = ref[1], b = ref[2];
}
r /= 255;
g /= 255;
b /= 255;
min = Math.min(r, g, b);
max = Math.max(r, g, b);
l = (max + min) / 2;
if (max === min) {
s = 0;
h = Number.NaN;
} else {
s = l < 0.5 ? (max - min) / (max + min) : (max - min) / (2 - max - min);
}
if (r === max) {
h = (g - b) / (max - min);
} else if (g === max) {
h = 2 + (b - r) / (max - min);
} else if (b === max) {
h = 4 + (r - g) / (max - min);
}
h *= 60;
if (h < 0) {
h += 360;
}
return [h, s, l];
};
chroma.hsl = function() {
return (function(func, args, ctor) {
ctor.prototype = func.prototype;
var child = new ctor, result = func.apply(child, args);
return Object(result) === result ? result : child;
})(Color, slice.call(arguments).concat(['hsl']), function(){});
};
_input.hsl = hsl2rgb;
Color.prototype.hsl = function() {
return rgb2hsl(this._rgb);
};
hsv2rgb = function() {
var args, b, f, g, h, i, p, q, r, ref, ref1, ref2, ref3, ref4, ref5, s, t, v;
args = unpack(arguments);
h = args[0], s = args[1], v = args[2];
v *= 255;
if (s === 0) {
r = g = b = v;
} else {
if (h === 360) {
h = 0;
}
if (h > 360) {
h -= 360;
}
if (h < 0) {
h += 360;
}
h /= 60;
i = floor(h);
f = h - i;
p = v * (1 - s);
q = v * (1 - s * f);
t = v * (1 - s * (1 - f));
switch (i) {
case 0:
ref = [v, t, p], r = ref[0], g = ref[1], b = ref[2];
break;
case 1:
ref1 = [q, v, p], r = ref1[0], g = ref1[1], b = ref1[2];
break;
case 2:
ref2 = [p, v, t], r = ref2[0], g = ref2[1], b = ref2[2];
break;
case 3:
ref3 = [p, q, v], r = ref3[0], g = ref3[1], b = ref3[2];
break;
case 4:
ref4 = [t, p, v], r = ref4[0], g = ref4[1], b = ref4[2];
break;
case 5:
ref5 = [v, p, q], r = ref5[0], g = ref5[1], b = ref5[2];
}
}
return [r, g, b, args.length > 3 ? args[3] : 1];
};
rgb2hsv = function() {
var b, delta, g, h, min, r, ref, s, v;
ref = unpack(arguments), r = ref[0], g = ref[1], b = ref[2];
min = Math.min(r, g, b);
max = Math.max(r, g, b);
delta = max - min;
v = max / 255.0;
if (max === 0) {
h = Number.NaN;
s = 0;
} else {
s = delta / max;
if (r === max) {
h = (g - b) / delta;
}
if (g === max) {
h = 2 + (b - r) / delta;
}
if (b === max) {
h = 4 + (r - g) / delta;
}
h *= 60;
if (h < 0) {
h += 360;
}
}
return [h, s, v];
};
chroma.hsv = function() {
return (function(func, args, ctor) {
ctor.prototype = func.prototype;
var child = new ctor, result = func.apply(child, args);
return Object(result) === result ? result : child;
})(Color, slice.call(arguments).concat(['hsv']), function(){});
};
_input.hsv = hsv2rgb;
Color.prototype.hsv = function() {
return rgb2hsv(this._rgb);
};
num2rgb = function(num) {
var b, g, r;
if (type(num) === "number" && num >= 0 && num <= 0xFFFFFF) {
r = num >> 16;
g = (num >> 8) & 0xFF;
b = num & 0xFF;
return [r, g, b, 1];
}
console.warn("unknown num color: " + num);
return [0, 0, 0, 1];
};
rgb2num = function() {
var b, g, r, ref;
ref = unpack(arguments), r = ref[0], g = ref[1], b = ref[2];
return (r << 16) + (g << 8) + b;
};
chroma.num = function(num) {
return new Color(num, 'num');
};
Color.prototype.num = function(mode) {
if (mode == null) {
mode = 'rgb';
}
return rgb2num(this._rgb, mode);
};
_input.num = num2rgb;
_guess_formats.push({
p: 1,
test: function(n) {
if (arguments.length === 1 && type(n) === "number" && n >= 0 && n <= 0xFFFFFF) {
return 'num';
}
}
});
hcg2rgb = function() {
var _c, _g, args, b, c, f, g, h, i, p, q, r, ref, ref1, ref2, ref3, ref4, ref5, t, v;
args = unpack(arguments);
h = args[0], c = args[1], _g = args[2];
c = c / 100;
g = g / 100 * 255;
_c = c * 255;
if (c === 0) {
r = g = b = _g;
} else {
if (h === 360) {
h = 0;
}
if (h > 360) {
h -= 360;
}
if (h < 0) {
h += 360;
}
h /= 60;
i = floor(h);
f = h - i;
p = _g * (1 - c);
q = p + _c * (1 - f);
t = p + _c * f;
v = p + _c;
switch (i) {
case 0:
ref = [v, t, p], r = ref[0], g = ref[1], b = ref[2];
break;
case 1:
ref1 = [q, v, p], r = ref1[0], g = ref1[1], b = ref1[2];
break;
case 2:
ref2 = [p, v, t], r = ref2[0], g = ref2[1], b = ref2[2];
break;
case 3:
ref3 = [p, q, v], r = ref3[0], g = ref3[1], b = ref3[2];
break;
case 4:
ref4 = [t, p, v], r = ref4[0], g = ref4[1], b = ref4[2];
break;
case 5:
ref5 = [v, p, q], r = ref5[0], g = ref5[1], b = ref5[2];
}
}
return [r, g, b, args.length > 3 ? args[3] : 1];
};
rgb2hcg = function() {
var _g, b, c, delta, g, h, min, r, ref;
ref = unpack(arguments), r = ref[0], g = ref[1], b = ref[2];
min = Math.min(r, g, b);
max = Math.max(r, g, b);
delta = max - min;
c = delta * 100 / 255;
_g = min / (255 - delta) * 100;
if (delta === 0) {
h = Number.NaN;
} else {
if (r === max) {
h = (g - b) / delta;
}
if (g === max) {
h = 2 + (b - r) / delta;
}
if (b === max) {
h = 4 + (r - g) / delta;
}
h *= 60;
if (h < 0) {
h += 360;
}
}
return [h, c, _g];
};
chroma.hcg = function() {
return (function(func, args, ctor) {
ctor.prototype = func.prototype;
var child = new ctor, result = func.apply(child, args);
return Object(result) === result ? result : child;
})(Color, slice.call(arguments).concat(['hcg']), function(){});
};
_input.hcg = hcg2rgb;
Color.prototype.hcg = function() {
return rgb2hcg(this._rgb);
};
css2rgb = function(css) {
var aa, ab, hsl, i, m, o, rgb, w;
css = css.toLowerCase();
if ((chroma.colors != null) && chroma.colors[css]) {
return hex2rgb(chroma.colors[css]);
}
if (m = css.match(/rgb\(\s*(\-?\d+),\s*(\-?\d+)\s*,\s*(\-?\d+)\s*\)/)) {
rgb = m.slice(1, 4);
for (i = o = 0; o <= 2; i = ++o) {
rgb[i] = +rgb[i];
}
rgb[3] = 1;
} else if (m = css.match(/rgba\(\s*(\-?\d+),\s*(\-?\d+)\s*,\s*(\-?\d+)\s*,\s*([01]|[01]?\.\d+)\)/)) {
rgb = m.slice(1, 5);
for (i = w = 0; w <= 3; i = ++w) {
rgb[i] = +rgb[i];
}
} else if (m = css.match(/rgb\(\s*(\-?\d+(?:\.\d+)?)%,\s*(\-?\d+(?:\.\d+)?)%\s*,\s*(\-?\d+(?:\.\d+)?)%\s*\)/)) {
rgb = m.slice(1, 4);
for (i = aa = 0; aa <= 2; i = ++aa) {
rgb[i] = round(rgb[i] * 2.55);
}
rgb[3] = 1;
} else if (m = css.match(/rgba\(\s*(\-?\d+(?:\.\d+)?)%,\s*(\-?\d+(?:\.\d+)?)%\s*,\s*(\-?\d+(?:\.\d+)?)%\s*,\s*([01]|[01]?\.\d+)\)/)) {
rgb = m.slice(1, 5);
for (i = ab = 0; ab <= 2; i = ++ab) {
rgb[i] = round(rgb[i] * 2.55);
}
rgb[3] = +rgb[3];
} else if (m = css.match(/hsl\(\s*(\-?\d+(?:\.\d+)?),\s*(\-?\d+(?:\.\d+)?)%\s*,\s*(\-?\d+(?:\.\d+)?)%\s*\)/)) {
hsl = m.slice(1, 4);
hsl[1] *= 0.01;
hsl[2] *= 0.01;
rgb = hsl2rgb(hsl);
rgb[3] = 1;
} else if (m = css.match(/hsla\(\s*(\-?\d+(?:\.\d+)?),\s*(\-?\d+(?:\.\d+)?)%\s*,\s*(\-?\d+(?:\.\d+)?)%\s*,\s*([01]|[01]?\.\d+)\)/)) {
hsl = m.slice(1, 4);
hsl[1] *= 0.01;
hsl[2] *= 0.01;
rgb = hsl2rgb(hsl);
rgb[3] = +m[4];
}
return rgb;
};
rgb2css = function(rgba) {
var mode;
mode = rgba[3] < 1 ? 'rgba' : 'rgb';
if (mode === 'rgb') {
return mode + '(' + rgba.slice(0, 3).map(round).join(',') + ')';
} else if (mode === 'rgba') {
return mode + '(' + rgba.slice(0, 3).map(round).join(',') + ',' + rgba[3] + ')';
} else {
}
};
rnd = function(a) {
return round(a * 100) / 100;
};
hsl2css = function(hsl, alpha) {
var mode;
mode = alpha < 1 ? 'hsla' : 'hsl';
hsl[0] = rnd(hsl[0] || 0);
hsl[1] = rnd(hsl[1] * 100) + '%';
hsl[2] = rnd(hsl[2] * 100) + '%';
if (mode === 'hsla') {
hsl[3] = alpha;
}
return mode + '(' + hsl.join(',') + ')';
};
_input.css = function(h) {
return css2rgb(h);
};
chroma.css = function() {
return (function(func, args, ctor) {
ctor.prototype = func.prototype;
var child = new ctor, result = func.apply(child, args);
return Object(result) === result ? result : child;
})(Color, slice.call(arguments).concat(['css']), function(){});
};
Color.prototype.css = function(mode) {
if (mode == null) {
mode = 'rgb';
}
if (mode.slice(0, 3) === 'rgb') {
return rgb2css(this._rgb);
} else if (mode.slice(0, 3) === 'hsl') {
return hsl2css(this.hsl(), this.alpha());
}
};
_input.named = function(name) {
return hex2rgb(w3cx11[name]);
};
_guess_formats.push({
p: 5,
test: function(n) {
if (arguments.length === 1 && (w3cx11[n] != null)) {
return 'named';
}
}
});
Color.prototype.name = function(n) {
var h, k;
if (arguments.length) {
if (w3cx11[n]) {
this._rgb = hex2rgb(w3cx11[n]);
}
this._rgb[3] = 1;
this;
}
h = this.hex();
for (k in w3cx11) {
if (h === w3cx11[k]) {
return k;
}
}
return h;
};
lch2lab = function() {
/*
Convert from a qualitative parameter h and a quantitative parameter l to a 24-bit pixel.
These formulas were invented by David Dalrymple to obtain maximum contrast without going
out of gamut if the parameters are in the range 0-1.
A saturation multiplier was added by Gregor Aisch
*/
var c, h, l, ref;
ref = unpack(arguments), l = ref[0], c = ref[1], h = ref[2];
h = h * DEG2RAD;
return [l, cos(h) * c, sin(h) * c];
};
lch2rgb = function() {
var L, a, args, b, c, g, h, l, r, ref, ref1;
args = unpack(arguments);
l = args[0], c = args[1], h = args[2];
ref = lch2lab(l, c, h), L = ref[0], a = ref[1], b = ref[2];
ref1 = lab2rgb(L, a, b), r = ref1[0], g = ref1[1], b = ref1[2];
return [r, g, b, args.length > 3 ? args[3] : 1];
};
lab2lch = function() {
var a, b, c, h, l, ref;
ref = unpack(arguments), l = ref[0], a = ref[1], b = ref[2];
c = sqrt(a * a + b * b);
h = (atan2(b, a) * RAD2DEG + 360) % 360;
if (round(c * 10000) === 0) {
h = Number.NaN;
}
return [l, c, h];
};
rgb2lch = function() {
var a, b, g, l, r, ref, ref1;
ref = unpack(arguments), r = ref[0], g = ref[1], b = ref[2];
ref1 = rgb2lab(r, g, b), l = ref1[0], a = ref1[1], b = ref1[2];
return lab2lch(l, a, b);
};
chroma.lch = function() {
var args;
args = unpack(arguments);
return new Color(args, 'lch');
};
chroma.hcl = function() {
var args;
args = unpack(arguments);
return new Color(args, 'hcl');
};
_input.lch = lch2rgb;
_input.hcl = function() {
var c, h, l, ref;
ref = unpack(arguments), h = ref[0], c = ref[1], l = ref[2];
return lch2rgb([l, c, h]);
};
Color.prototype.lch = function() {
return rgb2lch(this._rgb);
};
Color.prototype.hcl = function() {
return rgb2lch(this._rgb).reverse();
};
rgb2cmyk = function(mode) {
var b, c, f, g, k, m, r, ref, y;
if (mode == null) {
mode = 'rgb';
}
ref = unpack(arguments), r = ref[0], g = ref[1], b = ref[2];
r = r / 255;
g = g / 255;
b = b / 255;
k = 1 - Math.max(r, Math.max(g, b));
f = k < 1 ? 1 / (1 - k) : 0;
c = (1 - r - k) * f;
m = (1 - g - k) * f;
y = (1 - b - k) * f;
return [c, m, y, k];
};
cmyk2rgb = function() {
var alpha, args, b, c, g, k, m, r, y;
args = unpack(arguments);
c = args[0], m = args[1], y = args[2], k = args[3];
alpha = args.length > 4 ? args[4] : 1;
if (k === 1) {
return [0, 0, 0, alpha];
}
r = c >= 1 ? 0 : 255 * (1 - c) * (1 - k);
g = m >= 1 ? 0 : 255 * (1 - m) * (1 - k);
b = y >= 1 ? 0 : 255 * (1 - y) * (1 - k);
return [r, g, b, alpha];
};
_input.cmyk = function() {
return cmyk2rgb(unpack(arguments));
};
chroma.cmyk = function() {
return (function(func, args, ctor) {
ctor.prototype = func.prototype;
var child = new ctor, result = func.apply(child, args);
return Object(result) === result ? result : child;
})(Color, slice.call(arguments).concat(['cmyk']), function(){});
};
Color.prototype.cmyk = function() {
return rgb2cmyk(this._rgb);
};
_input.gl = function() {
var i, k, o, rgb, v;
rgb = (function() {
var ref, results;
ref = unpack(arguments);
results = [];
for (k in ref) {
v = ref[k];
results.push(v);
}
return results;
}).apply(this, arguments);
for (i = o = 0; o <= 2; i = ++o) {
rgb[i] *= 255;
}
return rgb;
};
chroma.gl = function() {
return (function(func, args, ctor) {
ctor.prototype = func.prototype;
var child = new ctor, result = func.apply(child, args);
return Object(result) === result ? result : child;
})(Color, slice.call(arguments).concat(['gl']), function(){});
};
Color.prototype.gl = function() {
var rgb;
rgb = this._rgb;
return [rgb[0] / 255, rgb[1] / 255, rgb[2] / 255, rgb[3]];
};
rgb2luminance = function(r, g, b) {
var ref;
ref = unpack(arguments), r = ref[0], g = ref[1], b = ref[2];
r = luminance_x(r);
g = luminance_x(g);
b = luminance_x(b);
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
};
luminance_x = function(x) {
x /= 255;
if (x <= 0.03928) {
return x / 12.92;
} else {
return pow((x + 0.055) / 1.055, 2.4);
}
};
interpolate_rgb = function(col1, col2, f, m) {
var xyz0, xyz1;
xyz0 = col1._rgb;
xyz1 = col2._rgb;
return new Color(xyz0[0] + f * (xyz1[0] - xyz0[0]), xyz0[1] + f * (xyz1[1] - xyz0[1]), xyz0[2] + f * (xyz1[2] - xyz0[2]), m);
};
_interpolators.push(['rgb', interpolate_rgb]);
Color.prototype.luminance = function(lum, mode) {
var cur_lum, eps, max_iter, test;
if (mode == null) {
mode = 'rgb';
}
if (!arguments.length) {
return rgb2luminance(this._rgb);
}
if (lum === 0) {
this._rgb = [0, 0, 0, this._rgb[3]];
} else if (lum === 1) {
this._rgb = [255, 255, 255, this._rgb[3]];
} else {
eps = 1e-7;
max_iter = 20;
test = function(l, h) {
var lm, m;
m = l.interpolate(h, 0.5, mode);
lm = m.luminance();
if (Math.abs(lum - lm) < eps || !max_iter--) {
return m;
}
if (lm > lum) {
return test(l, m);
}
return test(m, h);
};
cur_lum = rgb2luminance(this._rgb);
this._rgb = (cur_lum > lum ? test(chroma('black'), this) : test(this, chroma('white'))).rgba();
}
return this;
};
temperature2rgb = function(kelvin) {
var b, g, r, temp;
temp = kelvin / 100;
if (temp < 66) {
r = 255;
g = -155.25485562709179 - 0.44596950469579133 * (g = temp - 2) + 104.49216199393888 * log(g);
b = temp < 20 ? 0 : -254.76935184120902 + 0.8274096064007395 * (b = temp - 10) + 115.67994401066147 * log(b);
} else {
r = 351.97690566805693 + 0.114206453784165 * (r = temp - 55) - 40.25366309332127 * log(r);
g = 325.4494125711974 + 0.07943456536662342 * (g = temp - 50) - 28.0852963507957 * log(g);
b = 255;
}
return [r, g, b];
};
rgb2temperature = function() {
var b, eps, g, maxTemp, minTemp, r, ref, rgb, temp;
ref = unpack(arguments), r = ref[0], g = ref[1], b = ref[2];
minTemp = 1000;
maxTemp = 40000;
eps = 0.4;
while (maxTemp - minTemp > eps) {
temp = (maxTemp + minTemp) * 0.5;
rgb = temperature2rgb(temp);
if ((rgb[2] / rgb[0]) >= (b / r)) {
maxTemp = temp;
} else {
minTemp = temp;
}
}
return round(temp);
};
chroma.temperature = chroma.kelvin = function() {
return (function(func, args, ctor) {
ctor.prototype = func.prototype;
var child = new ctor, result = func.apply(child, args);
return Object(result) === result ? result : child;
})(Color, slice.call(arguments).concat(['temperature']), function(){});
};
_input.temperature = _input.kelvin = _input.K = temperature2rgb;
Color.prototype.temperature = function() {
return rgb2temperature(this._rgb);
};
Color.prototype.kelvin = Color.prototype.temperature;
chroma.contrast = function(a, b) {
var l1, l2, ref, ref1;
if ((ref = type(a)) === 'string' || ref === 'number') {
a = new Color(a);
}
if ((ref1 = type(b)) === 'string' || ref1 === 'number') {
b = new Color(b);
}
l1 = a.luminance();
l2 = b.luminance();
if (l1 > l2) {
return (l1 + 0.05) / (l2 + 0.05);
} else {
return (l2 + 0.05) / (l1 + 0.05);
}
};
chroma.distance = function(a, b, mode) {
var d, i, l1, l2, ref, ref1, sum_sq;
if (mode == null) {
mode = 'lab';
}
if ((ref = type(a)) === 'string' || ref === 'number') {
a = new Color(a);
}
if ((ref1 = type(b)) === 'string' || ref1 === 'number') {
b = new Color(b);
}
l1 = a.get(mode);
l2 = b.get(mode);
sum_sq = 0;
for (i in l1) {
d = (l1[i] || 0) - (l2[i] || 0);
sum_sq += d * d;
}
return Math.sqrt(sum_sq);
};
chroma.deltaE = function(a, b, L, C) {
var L1, L2, a1, a2, b1, b2, c1, c2, c4, dH2, delA, delB, delC, delL, f, h1, ref, ref1, ref2, ref3, sc, sh, sl, t, v1, v2, v3;
if (L == null) {
L = 1;
}
if (C == null) {
C = 1;
}
if ((ref = type(a)) === 'string' || ref === 'number') {
a = new Color(a);
}
if ((ref1 = type(b)) === 'string' || ref1 === 'number') {
b = new Color(b);
}
ref2 = a.lab(), L1 = ref2[0], a1 = ref2[1], b1 = ref2[2];
ref3 = b.lab(), L2 = ref3[0], a2 = ref3[1], b2 = ref3[2];
c1 = sqrt(a1 * a1 + b1 * b1);
c2 = sqrt(a2 * a2 + b2 * b2);
sl = L1 < 16.0 ? 0.511 : (0.040975 * L1) / (1.0 + 0.01765 * L1);
sc = (0.0638 * c1) / (1.0 + 0.0131 * c1) + 0.638;
h1 = c1 < 0.000001 ? 0.0 : (atan2(b1, a1) * 180.0) / PI;
while (h1 < 0) {
h1 += 360;
}
while (h1 >= 360) {
h1 -= 360;
}
t = (h1 >= 164.0) && (h1 <= 345.0) ? 0.56 + abs(0.2 * cos((PI * (h1 + 168.0)) / 180.0)) : 0.36 + abs(0.4 * cos((PI * (h1 + 35.0)) / 180.0));
c4 = c1 * c1 * c1 * c1;
f = sqrt(c4 / (c4 + 1900.0));
sh = sc * (f * t + 1.0 - f);
delL = L1 - L2;
delC = c1 - c2;
delA = a1 - a2;
delB = b1 - b2;
dH2 = delA * delA + delB * delB - delC * delC;
v1 = delL / (L * sl);
v2 = delC / (C * sc);
v3 = sh;
return sqrt(v1 * v1 + v2 * v2 + (dH2 / (v3 * v3)));
};
Color.prototype.get = function(modechan) {
var channel, i, me, mode, ref, src;
me = this;
ref = modechan.split('.'), mode = ref[0], channel = ref[1];
src = me[mode]();
if (channel) {
i = mode.indexOf(channel);
if (i > -1) {
return src[i];
} else {
return console.warn('unknown channel ' + channel + ' in mode ' + mode);
}
} else {
return src;
}
};
Color.prototype.set = function(modechan, value) {
var channel, i, me, mode, ref, src;
me = this;
ref = modechan.split('.'), mode = ref[0], channel = ref[1];
if (channel) {
src = me[mode]();
i = mode.indexOf(channel);
if (i > -1) {
if (type(value) === 'string') {
switch (value.charAt(0)) {
case '+':
src[i] += +value;
break;
case '-':
src[i] += +value;
break;
case '*':
src[i] *= +(value.substr(1));
break;
case '/':
src[i] /= +(value.substr(1));
break;
default:
src[i] = +value;
}
} else {
src[i] = value;
}
} else {
console.warn('unknown channel ' + channel + ' in mode ' + mode);
}
} else {
src = value;
}
return chroma(src, mode).alpha(me.alpha());
};
Color.prototype.clipped = function() {
return this._rgb._clipped || false;
};
Color.prototype.alpha = function(a) {
if (arguments.length) {
return chroma.rgb([this._rgb[0], this._rgb[1], this._rgb[2], a]);
}
return this._rgb[3];
};
Color.prototype.darken = function(amount) {
var lab, me;
if (amount == null) {
amount = 1;
}
me = this;
lab = me.lab();
lab[0] -= LAB_CONSTANTS.Kn * amount;
return chroma.lab(lab).alpha(me.alpha());
};
Color.prototype.brighten = function(amount) {
if (amount == null) {
amount = 1;
}
return this.darken(-amount);
};
Color.prototype.darker = Color.prototype.darken;
Color.prototype.brighter = Color.prototype.brighten;
Color.prototype.saturate = function(amount) {
var lch, me;
if (amount == null) {
amount = 1;
}
me = this;
lch = me.lch();
lch[1] += amount * LAB_CONSTANTS.Kn;
if (lch[1] < 0) {
lch[1] = 0;
}
return chroma.lch(lch).alpha(me.alpha());
};
Color.prototype.desaturate = function(amount) {
if (amount == null) {
amount = 1;
}
return this.saturate(-amount);
};
Color.prototype.premultiply = function() {
var a, rgb;
rgb = this.rgb();
a = this.alpha();
return chroma(rgb[0] * a, rgb[1] * a, rgb[2] * a, a);
};
blend = function(bottom, top, mode) {
if (!blend[mode]) {
throw 'unknown blend mode ' + mode;
}
return blend[mode](bottom, top);
};
blend_f = function(f) {
return function(bottom, top) {
var c0, c1;
c0 = chroma(top).rgb();
c1 = chroma(bottom).rgb();
return chroma(f(c0, c1), 'rgb');
};
};
each = function(f) {
return function(c0, c1) {
var i, o, out;
out = [];
for (i = o = 0; o <= 3; i = ++o) {
out[i] = f(c0[i], c1[i]);
}
return out;
};
};
normal = function(a, b) {
return a;
};
multiply = function(a, b) {
return a * b / 255;
};
darken = function(a, b) {
if (a > b) {
return b;
} else {
return a;
}
};
lighten = function(a, b) {
if (a > b) {
return a;
} else {
return b;
}
};
screen = function(a, b) {
return 255 * (1 - (1 - a / 255) * (1 - b / 255));
};
overlay = function(a, b) {
if (b < 128) {
return 2 * a * b / 255;
} else {
return 255 * (1 - 2 * (1 - a / 255) * (1 - b / 255));
}
};
burn = function(a, b) {
return 255 * (1 - (1 - b / 255) / (a / 255));
};
dodge = function(a, b) {
if (a === 255) {
return 255;
}
a = 255 * (b / 255) / (1 - a / 255);
if (a > 255) {
return 255;
} else {
return a;
}
};
blend.normal = blend_f(each(normal));
blend.multiply = blend_f(each(multiply));
blend.screen = blend_f(each(screen));
blend.overlay = blend_f(each(overlay));
blend.darken = blend_f(each(darken));
blend.lighten = blend_f(each(lighten));
blend.dodge = blend_f(each(dodge));
blend.burn = blend_f(each(burn));
chroma.blend = blend;
chroma.analyze = function(data) {
var len, o, r, val;
r = {
min: Number.MAX_VALUE,
max: Number.MAX_VALUE * -1,
sum: 0,
values: [],
count: 0
};
for (o = 0, len = data.length; o < len; o++) {
val = data[o];
if ((val != null) && !isNaN(val)) {
r.values.push(val);
r.sum += val;
if (val < r.min) {
r.min = val;
}
if (val > r.max) {
r.max = val;
}
r.count += 1;
}
}
r.domain = [r.min, r.max];
r.limits = function(mode, num) {
return chroma.limits(r, mode, num);
};
return r;
};
chroma.scale = function(colors, positions) {
var _classes, _colorCache, _colors, _correctLightness, _domain, _fixed, _max, _min, _mode, _nacol, _out, _padding, _pos, _spread, _useCache, classifyValue, f, getClass, getColor, resetCache, setColors, tmap;
_mode = 'rgb';
_nacol = chroma('#ccc');
_spread = 0;
_domain = [0, 1];
_pos = [];
_padding = [0, 0];
_classes = false;
_colors = [];
_out = false;
_min = 0;
_max = 1;
_correctLightness = false;
_colorCache = {};
_useCache = true;
setColors = function(colors) {
var c, col, o, ref, ref1, w;
if (colors == null) {
colors = ['#fff', '#000'];
}
if ((colors != null) && type(colors) === 'string' && (chroma.brewer != null)) {
colors = chroma.brewer[colors] || chroma.brewer[colors.toLowerCase()] || colors;
}
if (type(colors) === 'array') {
colors = colors.slice(0);
for (c = o = 0, ref = colors.length - 1; 0 <= ref ? o <= ref : o >= ref; c = 0 <= ref ? ++o : --o) {
col = colors[c];
if (type(col) === "string") {
colors[c] = chroma(col);
}
}
_pos.length = 0;
for (c = w = 0, ref1 = colors.length - 1; 0 <= ref1 ? w <= ref1 : w >= ref1; c = 0 <= ref1 ? ++w : --w) {
_pos.push(c / (colors.length - 1));
}
}
resetCache();
return _colors = colors;
};
getClass = function(value) {
var i, n;
if (_classes != null) {
n = _classes.length - 1;
i = 0;
while (i < n && value >= _classes[i]) {
i++;
}
return i - 1;
}
return 0;
};
tmap = function(t) {
return t;
};
getColor = function(val, bypassMap) {
var c, col, i, k, o, p, ref, t;
if (bypassMap == null) {
bypassMap = false;
}
if (isNaN(val)) {
return _nacol;
}
if (!bypassMap) {
if (_classes && _classes.length > 2) {
c = getClass(val);
t = c / (_classes.length - 2);
t = _padding[0] + (t * (1 - _padding[0] - _padding[1]));
} else if (_max !== _min) {
t = (val - _min) / (_max - _min);
t = _padding[0] + (t * (1 - _padding[0] - _padding[1]));
t = Math.min(1, Math.max(0, t));
} else {
t = 1;
}
} else {
t = val;
}
if (!bypassMap) {
t = tmap(t);
}
k = Math.floor(t * 10000);
if (_useCache && _colorCache[k]) {
col = _colorCache[k];
} else {
if (type(_colors) === 'array') {
for (i = o = 0, ref = _pos.length - 1; 0 <= ref ? o <= ref : o >= ref; i = 0 <= ref ? ++o : --o) {
p = _pos[i];
if (t <= p) {
col = _colors[i];
break;
}
if (t >= p && i === _pos.length - 1) {
col = _colors[i];
break;
}
if (t > p && t < _pos[i + 1]) {
t = (t - p) / (_pos[i + 1] - p);
col = chroma.interpolate(_colors[i], _colors[i + 1], t, _mode);
break;
}
}
} else if (type(_colors) === 'function') {
col = _colors(t);
}
if (_useCache) {
_colorCache[k] = col;
}
}
return col;
};
resetCache = function() {
return _colorCache = {};
};
setColors(colors);
f = function(v) {
var c;
c = chroma(getColor(v));
if (_out && c[_out]) {
return c[_out]();
} else {
return c;
}
};
f.classes = function(classes) {
var d;
if (classes != null) {
if (type(classes) === 'array') {
_classes = classes;
_domain = [classes[0], classes[classes.length - 1]];
} else {
d = chroma.analyze(_domain);
if (classes === 0) {
_classes = [d.min, d.max];
} else {
_classes = chroma.limits(d, 'e', classes);
}
}
return f;
}
return _classes;
};
f.domain = function(domain) {
var c, d, k, len, o, ref, w;
if (!arguments.length) {
return _domain;
}
_min = domain[0];
_max = domain[domain.length - 1];
_pos = [];
k = _colors.length;
if (domain.length === k && _min !== _max) {
for (o = 0, len = domain.length; o < len; o++) {
d = domain[o];
_pos.push((d - _min) / (_max - _min));
}
} else {
for (c = w = 0, ref = k - 1; 0 <= ref ? w <= ref : w >= ref; c = 0 <= ref ? ++w : --w) {
_pos.push(c / (k - 1));
}
}
_domain = [_min, _max];
return f;
};
f.mode = function(_m) {
if (!arguments.length) {
return _mode;
}
_mode = _m;
resetCache();
return f;
};
f.range = function(colors, _pos) {
setColors(colors, _pos);
return f;
};
f.out = function(_o) {
_out = _o;
return f;
};
f.spread = function(val) {
if (!arguments.length) {
return _spread;
}
_spread = val;
return f;
};
f.correctLightness = function(v) {
if (v == null) {
v = true;
}
_correctLightness = v;
resetCache();
if (_correctLightness) {
tmap = function(t) {
var L0, L1, L_actual, L_diff, L_ideal, max_iter, pol, t0, t1;
L0 = getColor(0, true).lab()[0];
L1 = getColor(1, true).lab()[0];
pol = L0 > L1;
L_actual = getColor(t, true).lab()[0];
L_ideal = L0 + (L1 - L0) * t;
L_diff = L_actual - L_ideal;
t0 = 0;
t1 = 1;
max_iter = 20;
while (Math.abs(L_diff) > 1e-2 && max_iter-- > 0) {
(function() {
if (pol) {
L_diff *= -1;
}
if (L_diff < 0) {
t0 = t;
t += (t1 - t) * 0.5;
} else {
t1 = t;
t += (t0 - t) * 0.5;
}
L_actual = getColor(t, true).lab()[0];
return L_diff = L_actual - L_ideal;
})();
}
return t;
};
} else {
tmap = function(t) {
return t;
};
}
return f;
};
f.padding = function(p) {
if (p != null) {
if (type(p) === 'number') {
p = [p, p];
}
_padding = p;
return f;
} else {
return _padding;
}
};
f.colors = function(numColors, out) {
var dd, dm, i, o, ref, result, results, samples, w;
if (arguments.length < 2) {
out = 'hex';
}
result = [];
if (arguments.length === 0) {
result = _colors.slice(0);
} else if (numColors === 1) {
result = [f(0.5)];
} else if (numColors > 1) {
dm = _domain[0];
dd = _domain[1] - dm;
result = (function() {
results = [];
for (var o = 0; 0 <= numColors ? o < numColors : o > numColors; 0 <= numColors ? o++ : o--){ results.push(o); }
return results;
}).apply(this).map(function(i) {
return f(dm + i / (numColors - 1) * dd);
});
} else {
colors = [];
samples = [];
if (_classes && _classes.length > 2) {
for (i = w = 1, ref = _classes.length; 1 <= ref ? w < ref : w > ref; i = 1 <= ref ? ++w : --w) {
samples.push((_classes[i - 1] + _classes[i]) * 0.5);
}
} else {
samples = _domain;
}
result = samples.map(function(v) {
return f(v);
});
}
if (chroma[out]) {
result = result.map(function(c) {
return c[out]();
});
}
return result;
};
f.cache = function(c) {
if (c != null) {
return _useCache = c;
} else {
return _useCache;
}
};
return f;
};
if (chroma.scales == null) {
chroma.scales = {};
}
chroma.scales.cool = function() {
return chroma.scale([chroma.hsl(180, 1, .9), chroma.hsl(250, .7, .4)]);
};
chroma.scales.hot = function() {
return chroma.scale(['#000', '#f00', '#ff0', '#fff'], [0, .25, .75, 1]).mode('rgb');
};
chroma.analyze = function(data, key, filter) {
var add, k, len, o, r, val, visit;
r = {
min: Number.MAX_VALUE,
max: Number.MAX_VALUE * -1,
sum: 0,
values: [],
count: 0
};
if (filter == null) {
filter = function() {
return true;
};
}
add = function(val) {
if ((val != null) && !isNaN(val)) {
r.values.push(val);
r.sum += val;
if (val < r.min) {
r.min = val;
}
if (val > r.max) {
r.max = val;
}
r.count += 1;
}
};
visit = function(val, k) {
if (filter(val, k)) {
if ((key != null) && type(key) === 'function') {
return add(key(val));
} else if ((key != null) && type(key) === 'string' || type(key) === 'number') {
return add(val[key]);
} else {
return add(val);
}
}
};
if (type(data) === 'array') {
for (o = 0, len = data.length; o < len; o++) {
val = data[o];
visit(val);
}
} else {
for (k in data) {
val = data[k];
visit(val, k);
}
}
r.domain = [r.min, r.max];
r.limits = function(mode, num) {
return chroma.limits(r, mode, num);
};
return r;
};
chroma.limits = function(data, mode, num) {
var aa, ab, ac, ad, ae, af, ag, ah, ai, aj, ak, al, am, assignments, best, centroids, cluster, clusterSizes, dist, i, j, kClusters, limits, max_log, min, min_log, mindist, n, nb_iters, newCentroids, o, p, pb, pr, ref, ref1, ref10, ref11, ref12, ref13, ref14, ref2, ref3, ref4, ref5, ref6, ref7, ref8, ref9, repeat, sum, tmpKMeansBreaks, v, value, values, w;
if (mode == null) {
mode = 'equal';
}
if (num == null) {
num = 7;
}
if (type(data) === 'array') {
data = chroma.analyze(data);
}
min = data.min;
max = data.max;
values = data.values.sort(function(a, b) {
return a - b;
});
if (num === 1) {
return [min, max];
}
limits = [];
if (mode.substr(0, 1) === 'c') {
limits.push(min);
limits.push(max);
}
if (mode.substr(0, 1) === 'e') {
limits.push(min);
for (i = o = 1, ref = num - 1; 1 <= ref ? o <= ref : o >= ref; i = 1 <= ref ? ++o : --o) {
limits.push(min + (i / num) * (max - min));
}
limits.push(max);
} else if (mode.substr(0, 1) === 'l') {
if (min <= 0) {
throw 'Logarithmic scales are only possible for values > 0';
}
min_log = Math.LOG10E * log(min);
max_log = Math.LOG10E * log(max);
limits.push(min);
for (i = w = 1, ref1 = num - 1; 1 <= ref1 ? w <= ref1 : w >= ref1; i = 1 <= ref1 ? ++w : --w) {
limits.push(pow(10, min_log + (i / num) * (max_log - min_log)));
}
limits.push(max);
} else if (mode.substr(0, 1) === 'q') {
limits.push(min);
for (i = aa = 1, ref2 = num - 1; 1 <= ref2 ? aa <= ref2 : aa >= ref2; i = 1 <= ref2 ? ++aa : --aa) {
p = (values.length - 1) * i / num;
pb = floor(p);
if (pb === p) {
limits.push(values[pb]);
} else {
pr = p - pb;
limits.push(values[pb] * (1 - pr) + values[pb + 1] * pr);
}
}
limits.push(max);
} else if (mode.substr(0, 1) === 'k') {
/*
implementation based on
http://code.google.com/p/figue/source/browse/trunk/figue.js#336
simplified for 1-d input values
*/
n = values.length;
assignments = new Array(n);
clusterSizes = new Array(num);
repeat = true;
nb_iters = 0;
centroids = null;
centroids = [];
centroids.push(min);
for (i = ab = 1, ref3 = num - 1; 1 <= ref3 ? ab <= ref3 : ab >= ref3; i = 1 <= ref3 ? ++ab : --ab) {
centroids.push(min + (i / num) * (max - min));
}
centroids.push(max);
while (repeat) {
for (j = ac = 0, ref4 = num - 1; 0 <= ref4 ? ac <= ref4 : ac >= ref4; j = 0 <= ref4 ? ++ac : --ac) {
clusterSizes[j] = 0;
}
for (i = ad = 0, ref5 = n - 1; 0 <= ref5 ? ad <= ref5 : ad >= ref5; i = 0 <= ref5 ? ++ad : --ad) {
value = values[i];
mindist = Number.MAX_VALUE;
for (j = ae = 0, ref6 = num - 1; 0 <= ref6 ? ae <= ref6 : ae >= ref6; j = 0 <= ref6 ? ++ae : --ae) {
dist = abs(centroids[j] - value);
if (dist < mindist) {
mindist = dist;
best = j;
}
}
clusterSizes[best]++;
assignments[i] = best;
}
newCentroids = new Array(num);
for (j = af = 0, ref7 = num - 1; 0 <= ref7 ? af <= ref7 : af >= ref7; j = 0 <= ref7 ? ++af : --af) {
newCentroids[j] = null;
}
for (i = ag = 0, ref8 = n - 1; 0 <= ref8 ? ag <= ref8 : ag >= ref8; i = 0 <= ref8 ? ++ag : --ag) {
cluster = assignments[i];
if (newCentroids[cluster] === null) {
newCentroids[cluster] = values[i];
} else {
newCentroids[cluster] += values[i];
}
}
for (j = ah = 0, ref9 = num - 1; 0 <= ref9 ? ah <= ref9 : ah >= ref9; j = 0 <= ref9 ? ++ah : --ah) {
newCentroids[j] *= 1 / clusterSizes[j];
}
repeat = false;
for (j = ai = 0, ref10 = num - 1; 0 <= ref10 ? ai <= ref10 : ai >= ref10; j = 0 <= ref10 ? ++ai : --ai) {
if (newCentroids[j] !== centroids[i]) {
repeat = true;
break;
}
}
centroids = newCentroids;
nb_iters++;
if (nb_iters > 200) {
repeat = false;
}
}
kClusters = {};
for (j = aj = 0, ref11 = num - 1; 0 <= ref11 ? aj <= ref11 : aj >= ref11; j = 0 <= ref11 ? ++aj : --aj) {
kClusters[j] = [];
}
for (i = ak = 0, ref12 = n - 1; 0 <= ref12 ? ak <= ref12 : ak >= ref12; i = 0 <= ref12 ? ++ak : --ak) {
cluster = assignments[i];
kClusters[cluster].push(values[i]);
}
tmpKMeansBreaks = [];
for (j = al = 0, ref13 = num - 1; 0 <= ref13 ? al <= ref13 : al >= ref13; j = 0 <= ref13 ? ++al : --al) {
tmpKMeansBreaks.push(kClusters[j][0]);
tmpKMeansBreaks.push(kClusters[j][kClusters[j].length - 1]);
}
tmpKMeansBreaks = tmpKMeansBreaks.sort(function(a, b) {
return a - b;
});
limits.push(tmpKMeansBreaks[0]);
for (i = am = 1, ref14 = tmpKMeansBreaks.length - 1; am <= ref14; i = am += 2) {
v = tmpKMeansBreaks[i];
if (!isNaN(v) && limits.indexOf(v) === -1) {
limits.push(v);
}
}
}
return limits;
};
hsi2rgb = function(h, s, i) {
/*
borrowed from here:
http://hummer.stanford.edu/museinfo/doc/examples/humdrum/keyscape2/hsi2rgb.cpp
*/
var args, b, g, r;
args = unpack(arguments);
h = args[0], s = args[1], i = args[2];
if (isNaN(h)) {
h = 0;
}
h /= 360;
if (h < 1 / 3) {
b = (1 - s) / 3;
r = (1 + s * cos(TWOPI * h) / cos(PITHIRD - TWOPI * h)) / 3;
g = 1 - (b + r);
} else if (h < 2 / 3) {
h -= 1 / 3;
r = (1 - s) / 3;
g = (1 + s * cos(TWOPI * h) / cos(PITHIRD - TWOPI * h)) / 3;
b = 1 - (r + g);
} else {
h -= 2 / 3;
g = (1 - s) / 3;
b = (1 + s * cos(TWOPI * h) / cos(PITHIRD - TWOPI * h)) / 3;
r = 1 - (g + b);
}
r = limit(i * r * 3);
g = limit(i * g * 3);
b = limit(i * b * 3);
return [r * 255, g * 255, b * 255, args.length > 3 ? args[3] : 1];
};
rgb2hsi = function() {
/*
borrowed from here:
http://hummer.stanford.edu/museinfo/doc/examples/humdrum/keyscape2/rgb2hsi.cpp
*/
var b, g, h, i, min, r, ref, s;
ref = unpack(arguments), r = ref[0], g = ref[1], b = ref[2];
TWOPI = Math.PI * 2;
r /= 255;
g /= 255;
b /= 255;
min = Math.min(r, g, b);
i = (r + g + b) / 3;
s = 1 - min / i;
if (s === 0) {
h = 0;
} else {
h = ((r - g) + (r - b)) / 2;
h /= Math.sqrt((r - g) * (r - g) + (r - b) * (g - b));
h = Math.acos(h);
if (b > g) {
h = TWOPI - h;
}
h /= TWOPI;
}
return [h * 360, s, i];
};
chroma.hsi = function() {
return (function(func, args, ctor) {
ctor.prototype = func.prototype;
var child = new ctor, result = func.apply(child, args);
return Object(result) === result ? result : child;
})(Color, slice.call(arguments).concat(['hsi']), function(){});
};
_input.hsi = hsi2rgb;
Color.prototype.hsi = function() {
return rgb2hsi(this._rgb);
};
interpolate_hsx = function(col1, col2, f, m) {
var dh, hue, hue0, hue1, lbv, lbv0, lbv1, res, sat, sat0, sat1, xyz0, xyz1;
if (m === 'hsl') {
xyz0 = col1.hsl();
xyz1 = col2.hsl();
} else if (m === 'hsv') {
xyz0 = col1.hsv();
xyz1 = col2.hsv();
} else if (m === 'hcg') {
xyz0 = col1.hcg();
xyz1 = col2.hcg();
} else if (m === 'hsi') {
xyz0 = col1.hsi();
xyz1 = col2.hsi();
} else if (m === 'lch' || m === 'hcl') {
m = 'hcl';
xyz0 = col1.hcl();
xyz1 = col2.hcl();
}
if (m.substr(0, 1) === 'h') {
hue0 = xyz0[0], sat0 = xyz0[1], lbv0 = xyz0[2];
hue1 = xyz1[0], sat1 = xyz1[1], lbv1 = xyz1[2];
}
if (!isNaN(hue0) && !isNaN(hue1)) {
if (hue1 > hue0 && hue1 - hue0 > 180) {
dh = hue1 - (hue0 + 360);
} else if (hue1 < hue0 && hue0 - hue1 > 180) {
dh = hue1 + 360 - hue0;
} else {
dh = hue1 - hue0;
}
hue = hue0 + f * dh;
} else if (!isNaN(hue0)) {
hue = hue0;
if ((lbv1 === 1 || lbv1 === 0) && m !== 'hsv') {
sat = sat0;
}
} else if (!isNaN(hue1)) {
hue = hue1;
if ((lbv0 === 1 || lbv0 === 0) && m !== 'hsv') {
sat = sat1;
}
} else {
hue = Number.NaN;
}
if (sat == null) {
sat = sat0 + f * (sat1 - sat0);
}
lbv = lbv0 + f * (lbv1 - lbv0);
return res = chroma[m](hue, sat, lbv);
};
_interpolators = _interpolators.concat((function() {
var len, o, ref, results;
ref = ['hsv', 'hsl', 'hsi', 'hcl', 'lch', 'hcg'];
results = [];
for (o = 0, len = ref.length; o < len; o++) {
m = ref[o];
results.push([m, interpolate_hsx]);
}
return results;
})());
interpolate_num = function(col1, col2, f, m) {
var n1, n2;
n1 = col1.num();
n2 = col2.num();
return chroma.num(n1 + (n2 - n1) * f, 'num');
};
_interpolators.push(['num', interpolate_num]);
interpolate_lab = function(col1, col2, f, m) {
var res, xyz0, xyz1;
xyz0 = col1.lab();
xyz1 = col2.lab();
return res = new Color(xyz0[0] + f * (xyz1[0] - xyz0[0]), xyz0[1] + f * (xyz1[1] - xyz0[1]), xyz0[2] + f * (xyz1[2] - xyz0[2]), m);
};
_interpolators.push(['lab', interpolate_lab]);
}).call(commonjsGlobal);
});
var __awaiter$1 = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
const { cos, sin, PI: PI$1$1, min, max } = Math;
const WGL = WebGLRenderingContext;
/**
* @example new Mesh()
* .addIndexBuffer('TRIANGLES')
* .addIndexBuffer('LINES')
* .addVertexBuffer('normals', 'LGL_Normal')
*/
class Mesh extends Transformable {
constructor() {
super();
this.hasBeenCompiled = false;
this.vertexBuffers = {};
this.indexBuffers = {};
this.addVertexBuffer('vertices', 'LGL_Vertex');
//if (options.coords) this.addVertexBuffer('coords', 'LGL_TexCoord')
//if (options.normals) this.addVertexBuffer('normals', 'LGL_Normal')
//if (options.colors) this.addVertexBuffer('colors', 'LGL_Color')
}
calcVolume() {
let totalVolume = 0, totalCentroid = V3.O, totalAreaX2 = 0;
const triangles = this.TRIANGLES;
const vertices = this.vertices;
for (let i = 0; i < triangles.length; i += 3) {
const i0 = triangles[i + 0], i1 = triangles[i + 1], i2 = triangles[i + 2];
const v0 = vertices[i0], v1 = vertices[i1], v2 = vertices[i2];
const v01 = v1.minus(v0), v02 = v2.minus(v0);
const normal = v01.cross(v02);
//const centroidZ = (v0.z + v1.z + v2.z) / 3
//totalVolume += centroidZ * (area === v01.cross(v02).length() / 2) * v01.cross(v02).unit().z
const faceCentroid = v0.plus(v1.plus(v2)).div(3);
totalVolume += faceCentroid.z * normal.z / 2;
const faceAreaX2 = normal.length();
totalAreaX2 += faceAreaX2;
totalCentroid = totalCentroid.plus(new V3(faceCentroid.x, faceCentroid.y, faceCentroid.z / 2).times(faceCentroid.z * normal.z / 2));
}
// sumInPlaceTree adds negligible additional accuracy for XY sphere
return { volume: totalVolume, centroid: totalCentroid.div(triangles.length / 3), area: totalAreaX2 / 2 };
}
/**
* Add a new vertex buffer with a list as a property called `name` on this object and map it to
* the attribute called `attribute` in all shaders that draw this mesh.
* @example new Mesh().addVertexBuffer('coords', 'LGL_TexCoord')
*/
addVertexBuffer(name, attribute) {
assert(!this.vertexBuffers[attribute], 'Buffer ' + attribute + ' already exists.');
//assert(!this[name])
this.hasBeenCompiled = false;
assert('string' == typeof name);
assert('string' == typeof attribute);
const buffer = this.vertexBuffers[attribute] = new Buffer$1(WGL.ARRAY_BUFFER, Float32Array);
buffer.name = name;
this[name] = [];
return this;
}
/**
* Add a new index buffer.
* @example new Mesh().addIndexBuffer('TRIANGLES')
* @example new Mesh().addIndexBuffer('LINES')
*/
addIndexBuffer(name) {
this.hasBeenCompiled = false;
const buffer = this.indexBuffers[name] = new Buffer$1(WGL.ELEMENT_ARRAY_BUFFER, Uint16Array);
buffer.name = name;
this[name] = [];
return this;
}
concat(...others) {
const mesh = new Mesh();
[this].concat(others).forEach((oldMesh) => {
const startIndex = mesh.vertices ? mesh.vertices.length : 0;
Object.getOwnPropertyNames(oldMesh.vertexBuffers).forEach(attribute => {
const bufferName = this.vertexBuffers[attribute].name;
if (!mesh.vertexBuffers[attribute]) {
mesh.addVertexBuffer(bufferName, attribute);
}
mesh[bufferName].push(...oldMesh[bufferName]);
});
Object.getOwnPropertyNames(oldMesh.indexBuffers).forEach(name => {
if (!mesh.indexBuffers[name]) {
mesh.addIndexBuffer(name);
}
mesh[name].push(...oldMesh[name].map(index => index + startIndex));
});
});
return mesh;
}
/**
* Upload all attached buffers to the GPU in preparation for rendering. This doesn't need to be called every
* frame, only needs to be done when the data changes.
*
* Sets `this.hasBeenCompiled` to true.
*/
compile(gl = currentGL()) {
// figure out shortest vertex buffer to make sure indexBuffers are in bounds
Object.getOwnPropertyNames(this.vertexBuffers).forEach(attribute => {
const buffer = this.vertexBuffers[attribute];
buffer.data = this[buffer.name];
buffer.compile(undefined, gl);
});
for (const name in this.indexBuffers) {
const buffer = this.indexBuffers[name];
buffer.data = this[buffer.name];
buffer.compile(undefined, gl);
// if (NLA_DEBUG && buffer.maxValue >= minVertexBufferLength) {
// throw new Error(`max index value for buffer ${name}
// is too large ${buffer.maxValue} min Vbuffer size: ${minVertexBufferLength} ${minBufferName}`)
// }
}
this.hasBeenCompiled = true;
return this;
}
static fromBinarySTL(stl) {
return __awaiter$1(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => {
const mesh = new Mesh()
.addVertexBuffer('normals', 'LGL_Normal');
const fileReader = new FileReader();
fileReader.onerror = reject;
fileReader.onload = function (_progressEvent) {
const dataView = new DataView(this.result);
const HEADER_BYTE_SIZE = 80;
const triangleCount = dataView.getUint32(HEADER_BYTE_SIZE, true);
mesh.normals.length = triangleCount * 3;
mesh.vertices.length = triangleCount * 3;
let i = triangleCount * 3, bufferPtr = HEADER_BYTE_SIZE + 4;
function readV3() {
const x = dataView.getFloat32(bufferPtr, true);
bufferPtr += 4;
const y = dataView.getFloat32(bufferPtr, true);
bufferPtr += 4;
const z = dataView.getFloat32(bufferPtr, true);
bufferPtr += 4;
return new V3(x, y, z);
}
while (i) {
i -= 3;
const normal = readV3();
mesh.normals[i + 0] = normal;
mesh.normals[i + 1] = normal;
mesh.normals[i + 2] = normal;
mesh.vertices[i + 0] = readV3();
mesh.vertices[i + 1] = readV3();
mesh.vertices[i + 2] = readV3();
bufferPtr += 2;
}
resolve(mesh);
};
fileReader.readAsArrayBuffer(stl);
});
});
}
toBinarySTL() {
if (!this.TRIANGLES)
throw new Error('TRIANGLES must be defined.');
const HEADER_BYTE_SIZE = 80, FLOAT_BYTE_SIZE = 4;
const triangles = this.TRIANGLES;
const triangleCount = triangles.length / 3;
const buffer = new ArrayBuffer(HEADER_BYTE_SIZE + 4 + triangleCount * (4 * 3 * FLOAT_BYTE_SIZE + 2));
const dataView = new DataView(buffer);
dataView.setUint32(HEADER_BYTE_SIZE, triangleCount, true);
let bufferPtr = HEADER_BYTE_SIZE + 4;
let i = triangles.length;
while (i) {
i -= 3;
const a = this.vertices[triangles[i]], b = this.vertices[triangles[i + 1]], c = this.vertices[triangles[i + 2]];
const normal = V3.normalOnPoints(a, b, c);
[normal, a, b, c].forEach(v => {
dataView.setFloat32(bufferPtr, v.x, true);
bufferPtr += 4;
dataView.setFloat32(bufferPtr, v.y, true);
bufferPtr += 4;
dataView.setFloat32(bufferPtr, v.z, true);
bufferPtr += 4;
});
// skip 2 bytes, already initalized to zero
bufferPtr += 2;
}
assert(bufferPtr == buffer.byteLength, bufferPtr + ' ' + buffer.byteLength);
return new Blob([buffer], { type: 'application/octet-stream' });
}
/**
* Transform all vertices by `matrix` and all normals by the inverse transpose of `matrix`.
*
* Index buffer data is referenced.
*/
transform(m4) {
const mesh = new Mesh();
mesh.vertices = m4.transformedPoints(this.vertices);
if (this.normals) {
mesh.addVertexBuffer('normals', 'LGL_Normal');
const invTrans = m4.as3x3().inversed().transposed().normalized();
mesh.normals = this.normals.map(n => invTrans.transformVector(n).unit());
// mesh.normals.forEach(n => assert(n.hasLength(1)))
}
for (const name in this.indexBuffers) {
mesh.addIndexBuffer(name);
mesh[name] = this[name];
}
for (const attribute in this.vertexBuffers) {
if ('LGL_Vertex' !== attribute && 'LGL_Normal' !== attribute) {
const name = this.vertexBuffers[attribute].name;
mesh.addVertexBuffer(name, attribute);
mesh[name] = this[name];
}
}
mesh.compile();
return mesh;
}
/**
* Computes a new normal1 for each vertex from the average normal1 of the neighboring triangles. This means
* adjacent triangles must share vertices for the resulting normals to be smooth.
*/
computeNormalsFromFlatTriangles() {
if (!this.normals)
this.addVertexBuffer('normals', 'LGL_Normal');
// tslint:disable:no-string-literal
//this.vertexBuffers['LGL_Normal'].data = arrayFromFunction(this.vertices.length, i => V3.O)
const TRIANGLES = this.TRIANGLES, vertices = this.vertices, normals = this.normals;
normals.length = vertices.length;
for (let i = 0; i < TRIANGLES.length; i += 3) {
const ai = TRIANGLES[i], bi = TRIANGLES[i + 1], ci = TRIANGLES[i + 2];
const a = vertices[ai];
const b = vertices[bi];
const c = vertices[ci];
const normal = b.minus(a).cross(c.minus(a)).unit();
normals[ai] = normals[ai].plus(normal);
normals[bi] = normals[bi].plus(normal);
normals[ci] = normals[ci].plus(normal);
}
for (let i = 0; i < vertices.length; i++) {
normals[i] = normals[i].unit();
}
this.hasBeenCompiled = false;
return this;
}
computeWireframeFromFlatTriangles(indexBufferName = 'LINES') {
if (!this.TRIANGLES)
throw new Error('TRIANGLES must be defined.');
const canonEdges = new Set();
function canonEdge(i0, i1) {
const iMin = min(i0, i1), iMax = max(i0, i1);
return (iMin << 16) | iMax;
}
// function uncanonEdge(key) {
// return [key >> 16, key & 0xffff]
// }
const t = this.TRIANGLES;
for (let i = 0; i < t.length; i += 3) {
canonEdges.add(canonEdge(t[i + 0], t[i + 1]));
canonEdges.add(canonEdge(t[i + 1], t[i + 2]));
canonEdges.add(canonEdge(t[i + 2], t[i + 0]));
}
const data = indexBufferName;
if (!this[data])
this.addIndexBuffer(indexBufferName);
//this.LINES = new Array(canonEdges.size)
canonEdges.forEach(val => this[data].push(val >> 16, val & 0xffff));
this.hasBeenCompiled = false;
return this;
}
computeWireframeFromFlatTrianglesClosedMesh(indexBufferName = 'LINES') {
if (!this.TRIANGLES)
throw new Error('TRIANGLES must be defined.');
if (!this.LINES)
this.addIndexBuffer('LINES');
const tris = this.TRIANGLES;
if (!this[indexBufferName])
this.addIndexBuffer(indexBufferName);
const lines = this[indexBufferName];
for (let i = 0; i < tris.length; i += 3) {
if (tris[i + 0] < tris[i + 1])
lines.push(tris[i + 0], tris[i + 1]);
if (tris[i + 1] < tris[i + 2])
lines.push(tris[i + 1], tris[i + 2]);
if (tris[i + 2] < tris[i + 0])
lines.push(tris[i + 2], tris[i + 0]);
}
this.hasBeenCompiled = false;
return this;
}
computeNormalLines(length = 1, indexBufferName = 'LINES') {
if (!this.normals) {
throw new Error('normals must be defined.');
}
const vs = this.vertices, si = this.vertices.length;
if (!this[indexBufferName])
this.addIndexBuffer(indexBufferName);
for (let i = 0; i < this.normals.length; i++) {
vs[si + i] = vs[i].plus(this.normals[i].toLength(length));
this[indexBufferName].push(si + i, i);
}
this.hasBeenCompiled = false;
return this;
}
getAABB() {
return new AABB().addPoints(this.vertices);
}
getBoundingSphere() {
const sphere = { center: this.getAABB().getCenter(), radius: 0 };
for (let i = 0; i < this.vertices.length; i++) {
sphere.radius = Math.max(sphere.radius, this.vertices[i].minus(sphere.center).length());
}
return sphere;
}
// ### Mesh.plane([options])
//
// Generates a square 2x2 mesh the xy plane centered at the origin. The
// `options` argument specifies options to pass to the mesh constructor.
// Additional options include `detailX` and `detailY`, which set the tesselation
// in x and y, and `detail`, which sets both `detailX` and `detailY` at once.
// Two triangles are generated by default.
// Example usage:
//
// var mesh1 = Mesh.plane();
// var mesh2 = Mesh.plane({ detail: 5 });
// var mesh3 = Mesh.plane({ detailX: 20, detailY: 40 });
//
/**
* Generates a square mesh in the XY plane.
* Texture coordinates (buffer "coords") are set to go from 0 to 1 in either direction.
*
*
* @param {Object=} options
* @param {number=} options.detail Defaults to 1
* @param {number=} options.detailX Defaults to options.detail. Number of subdivisions in X direction.
* @param {number=} options.detailY Defaults to options.detail. Number of subdivisions in Y direction.j
* @param {number=} options.width defaults to 1
* @param {number=} options.height defaults to 1
* @param {number=} options.startX defaults to 0
* @param {number=} options.startY defaults to 0
*/
static plane(options = {}) {
const detailX = options.detailX || options.detail || 1;
const detailY = options.detailY || options.detail || 1;
const startX = options.startX || 0;
const startY = options.startY || 0;
const width = options.width || 1;
const height = options.height || 1;
const mesh = new Mesh()
.addIndexBuffer('LINES')
.addIndexBuffer('TRIANGLES')
.addVertexBuffer('normals', 'LGL_Normal')
.addVertexBuffer('coords', 'LGL_TexCoord');
for (let j = 0; j <= detailY; j++) {
const t = j / detailY;
for (let i = 0; i <= detailX; i++) {
const s = i / detailX;
mesh.vertices.push(new V3(startX + s * width, startY + t * height, 0));
mesh.coords.push([s, t]);
mesh.normals.push(V3.Z);
if (i < detailX && j < detailY) {
const offset = i + j * (detailX + 1);
mesh.TRIANGLES.push(offset, offset + detailX + 1, offset + 1, offset + detailX + 1, offset + detailX + 2, offset + 1);
}
}
}
for (let i = 0; i < detailX; i++) {
mesh.LINES.push(i, i + 1);
mesh.LINES.push((detailX + 1) * detailY + i, (detailX + 1) * detailY + i + 1);
}
for (let j = 0; j < detailY; j++) {
mesh.LINES.push(detailX * j, detailX * (j + 1) + 1);
mesh.LINES.push(detailX * (j + 1), detailX * (j + 2) + 1);
}
mesh.compile();
return mesh;
}
/**
* Generates a unit cube (1x1x1) starting at the origin and extending into the (+ + +) octant.
* I.e. box from V3.O to V3(1,1,1)
* Creates line, triangle, vertex and normal1 buffers.
*/
static cube() {
const mesh = new Mesh()
.addVertexBuffer('normals', 'LGL_Normal')
.addIndexBuffer('TRIANGLES')
.addIndexBuffer('LINES');
// basically indexes for faces of the cube. vertices each need to be added 3 times,
// as they have different normals depending on the face being rendered
const VERTEX_CORNERS = [
0, 1, 2, 3,
4, 5, 6, 7,
0, 4, 1, 5,
2, 6, 3, 7,
2, 6, 0, 4,
3, 7, 1, 5,
];
mesh.vertices = VERTEX_CORNERS.map(i => Mesh.UNIT_CUBE_CORNERS[i]);
mesh.normals = [V3.X.negated(), V3.X, V3.Y.negated(), V3.Y, V3.Z.negated(), V3.Z].map(v => [v, v, v, v]).concatenated();
for (let i = 0; i < 6 * 4; i += 4) {
pushQuad(mesh.TRIANGLES, 0 != i % 8, VERTEX_CORNERS[i], VERTEX_CORNERS[i + 1], VERTEX_CORNERS[i + 2], VERTEX_CORNERS[i + 3]);
}
// indexes of LINES relative to UNIT_CUBE_CORNERS. Mapped to VERTEX_CORNERS.indexOf
// so they make sense in the context of the mesh
mesh.LINES = [
0, 1,
0, 2,
1, 3,
2, 3,
0, 4,
1, 5,
2, 6,
3, 7,
4, 5,
4, 6,
5, 7,
6, 7,
].map(i => VERTEX_CORNERS.indexOf(i));
mesh.compile();
return mesh;
}
static isocahedron() {
return Mesh.sphere(0);
}
static sphere2(las, longs) {
const baseVertices = arrayFromFunction(las, i => {
const angle = i / (las - 1) * PI$1$1 - PI$1$1 / 2;
return new V3(0, cos(angle), sin(angle));
});
return Mesh.rotation(baseVertices, { anchor: V3.O, dir1: V3.Z }, 2 * PI$1$1, longs, true, baseVertices);
}
/**
* Returns a sphere mesh with radius 1 created by subdividing the faces of a isocahedron (20-sided) recursively
* The sphere is positioned at the origin
* @param subdivisions
* How many recursive divisions to do. A subdivision divides a triangle into 4,
* so the total number of triangles is 20 * 4^subdivisions
* @returns
* Contains vertex and normal1 buffers and index buffers for triangles and LINES
*/
static sphere(subdivisions = 3) {
const golden = (1 + Math.sqrt(5)) / 2, u = new V3(1, golden, 0).unit(), s = u.x, t = u.y;
// base vertices of isocahedron
const vertices = [
new V3(-s, t, 0),
new V3(s, t, 0),
new V3(-s, -t, 0),
new V3(s, -t, 0),
new V3(0, -s, t),
new V3(0, s, t),
new V3(0, -s, -t),
new V3(0, s, -t),
new V3(t, 0, -s),
new V3(t, 0, s),
new V3(-t, 0, -s),
new V3(-t, 0, s)
];
// base triangles of isocahedron
const triangles = [
// 5 faces around point 0
0, 11, 5,
0, 5, 1,
0, 1, 7,
0, 7, 10,
0, 10, 11,
// 5 adjacent faces
1, 5, 9,
5, 11, 4,
11, 10, 2,
10, 7, 6,
7, 1, 8,
// 5 faces around point 3
3, 9, 4,
3, 4, 2,
3, 2, 6,
3, 6, 8,
3, 8, 9,
// 5 adjacent faces
4, 9, 5,
2, 4, 11,
6, 2, 10,
8, 6, 7,
9, 8, 1,
];
/**
* Tesselates triangle a b c
* a b c must already be in vertices with the indexes ia ib ic
* res is the number of subdivisions to do. 0 just results in triangle and line indexes being added to the
* respective buffers.
*/
function tesselateRecursively(a, b, c, res, vertices, triangles, ia, ib, ic, lines) {
if (0 == res) {
triangles.push(ia, ib, ic);
if (ia < ib)
lines.push(ia, ib);
if (ib < ic)
lines.push(ib, ic);
if (ic < ia)
lines.push(ic, ia);
}
else {
// subdivide the triangle abc into 4 by adding a vertex (with the correct distance from the origin)
// between each segment ab, bc and cd, then calling the function recursively
const abMid1 = a.plus(b).toLength(1), bcMid1 = b.plus(c).toLength(1), caMid1 = c.plus(a).toLength(1);
// indexes of new vertices:
const iabm = vertices.length, ibcm = iabm + 1, icam = iabm + 2;
vertices.push(abMid1, bcMid1, caMid1);
tesselateRecursively(abMid1, bcMid1, caMid1, res - 1, vertices, triangles, iabm, ibcm, icam, lines);
tesselateRecursively(a, abMid1, caMid1, res - 1, vertices, triangles, ia, iabm, icam, lines);
tesselateRecursively(b, bcMid1, abMid1, res - 1, vertices, triangles, ib, ibcm, iabm, lines);
tesselateRecursively(c, caMid1, bcMid1, res - 1, vertices, triangles, ic, icam, ibcm, lines);
}
}
const mesh = new Mesh()
.addVertexBuffer('normals', 'LGL_Normal')
.addIndexBuffer('TRIANGLES')
.addIndexBuffer('LINES');
mesh.vertices.push(...vertices);
subdivisions = undefined == subdivisions ? 4 : subdivisions;
for (let i = 0; i < 20; i++) {
const [ia, ic, ib] = triangles.slice(i * 3, i * 3 + 3);
tesselateRecursively(vertices[ia], vertices[ic], vertices[ib], subdivisions, mesh.vertices, mesh.TRIANGLES, ia, ic, ib, mesh.LINES);
}
mesh.normals = mesh.vertices;
mesh.compile();
return mesh;
}
static aabb(aabb) {
const matrix = M4.multiplyMultiple(M4.translate(aabb.min), M4.scale(aabb.size().max(new V3(NLA_PRECISION, NLA_PRECISION, NLA_PRECISION))));
const mesh = Mesh.cube().transform(matrix);
// mesh.vertices = aabb.corners()
mesh.computeNormalLines(20);
mesh.compile();
return mesh;
}
static offsetVertices(vertices, offset, close, normals) {
assertVectors.apply(undefined, vertices);
assertVectors(offset);
const mesh = new Mesh()
.addIndexBuffer('TRIANGLES')
.addVertexBuffer('coords', 'LGL_TexCoord');
normals && mesh.addVertexBuffer('normals', 'LGL_Normal');
mesh.vertices = vertices.concat(vertices.map(v => v.plus(offset)));
const vl = vertices.length;
mesh.coords = arrayFromFunction(vl * 2, (i) => [(i % vl) / vl, (i / vl) | 0]);
const triangles = mesh.TRIANGLES;
for (let i = 0; i < vertices.length - 1; i++) {
pushQuad(triangles, false, i, i + 1, vertices.length + i, vertices.length + i + 1);
}
if (close) {
pushQuad(triangles, false, 0, vertices.length - 1, vertices.length, vertices.length * 2 - 1);
}
if (normals) {
mesh.normals = normals.concat(normals);
}
mesh.compile();
return mesh;
}
// Creates a new $Mesh by rotating $vertices by $totalRads around $lineAxis (according to the right-hand
// rule). $steps is the number of steps to take. $close is whether the vertices of the first and last step
// should be connected by triangles. If $normals is set (pass an array of V3s of the same length as $vertices),
// these will also be rotated and correctly added to the mesh.
// @example const precious = Mesh.rotation([V(10, 0, -2), V(10, 0, 2), V(11, 0, 2), V(11, 0, -2)], , L3.Z, 512)
static rotation(vertices, lineAxis, totalRads, steps, close = true, normals) {
const mesh = new Mesh().addIndexBuffer('TRIANGLES');
normals && mesh.addVertexBuffer('normals', 'LGL_Normal');
const vc = vertices.length, vTotal = vc * steps;
const rotMat = new M4();
const triangles = mesh.TRIANGLES;
for (let i = 0; i < steps; i++) {
// add triangles
const rads = totalRads / steps * i;
M4.rotateLine(lineAxis.anchor, lineAxis.dir1, rads, rotMat);
mesh.vertices.push(...rotMat.transformedPoints(vertices));
normals && mesh.normals.push(...rotMat.transformedVectors(normals));
if (close || i !== steps - 1) {
for (let j = 0; j < vc - 1; j++) {
pushQuad(triangles, false, i * vc + j + 1, i * vc + j, ((i + 1) * vc + j + 1) % vTotal, ((i + 1) * vc + j) % vTotal);
}
}
}
mesh.compile();
return mesh;
}
static parametric(pF, pN, sMin, sMax, tMin, tMax, sRes, tRes) {
const mesh = new Mesh()
.addVertexBuffer('normals', 'LGL_Normal')
.addIndexBuffer('TRIANGLES');
for (let si = 0; si <= sRes; si++) {
const s = lerp(sMin, sMax, si / sRes);
for (let ti = 0; ti <= tRes; ti++) {
const t = lerp(tMin, tMax, ti / tRes);
mesh.vertices.push(pF(s, t));
mesh.normals.push(pN(s, t));
if (ti < tRes && si < sRes) {
const offset = ti + si * (tRes + 1);
pushQuad(mesh.TRIANGLES, false, offset, offset + tRes + 1, offset + 1, offset + tRes + 2);
}
}
}
mesh.compile();
return mesh;
}
static load(json) {
const mesh = new Mesh();
if (Array.isArray(json.vertices[0])) {
mesh.vertices = json.vertices.map(x => V(x));
}
else {
throw new Error();
}
if (json.triangles) {
mesh.addIndexBuffer('TRIANGLES');
mesh.TRIANGLES = json.triangles;
}
if (json.normals) {
mesh.addVertexBuffer('normals', 'LGL_Normal');
mesh.normals = json.normals;
}
mesh.compile();
return mesh;
}
}
// unique corners of a unit cube. Used by Mesh.cube to generate a cube mesh.
Mesh.UNIT_CUBE_CORNERS = [
V3.O,
new V3(0, 0, 1),
new V3(0, 1, 0),
new V3(0, 1, 1),
new V3(1, 0, 0),
new V3(1, 0, 1),
new V3(1, 1, 0),
V3.XYZ,
];
/* tslint:disable:no-string-literal */
const WGL$1 = WebGLRenderingContext;
/**
* These are all the draw modes usable in OpenGL ES
*/
var DRAW_MODES;
(function (DRAW_MODES) {
DRAW_MODES[DRAW_MODES["POINTS"] = WGL$1.POINTS] = "POINTS";
DRAW_MODES[DRAW_MODES["LINES"] = WGL$1.LINES] = "LINES";
DRAW_MODES[DRAW_MODES["LINE_STRIP"] = WGL$1.LINE_STRIP] = "LINE_STRIP";
DRAW_MODES[DRAW_MODES["LINE_LOOP"] = WGL$1.LINE_LOOP] = "LINE_LOOP";
DRAW_MODES[DRAW_MODES["TRIANGLES"] = WGL$1.TRIANGLES] = "TRIANGLES";
DRAW_MODES[DRAW_MODES["TRIANGLE_STRIP"] = WGL$1.TRIANGLE_STRIP] = "TRIANGLE_STRIP";
DRAW_MODES[DRAW_MODES["TRIANGLE_FAN"] = WGL$1.TRIANGLE_FAN] = "TRIANGLE_FAN";
})(DRAW_MODES || (DRAW_MODES = {}));
const SHADER_VAR_TYPES = ['FLOAT', 'FLOAT_MAT2', 'FLOAT_MAT3', 'FLOAT_MAT4', 'FLOAT_VEC2', 'FLOAT_VEC3', 'FLOAT_VEC4', 'INT', 'INT_VEC2', 'INT_VEC3', 'INT_VEC4', 'UNSIGNED_INT'];
const DRAW_MODE_CHECKS = {
[DRAW_MODES.POINTS]: _ => true,
[DRAW_MODES.LINES]: x => 0 == x % 2,
[DRAW_MODES.LINE_STRIP]: x => x > 2,
[DRAW_MODES.LINE_LOOP]: x => x > 2,
[DRAW_MODES.TRIANGLES]: x => 0 == x % 3,
[DRAW_MODES.TRIANGLE_STRIP]: x => x > 3,
[DRAW_MODES.TRIANGLE_FAN]: x => x > 3,
};
function isArray(obj) {
return Array == obj.constructor || Float32Array == obj.constructor || Float64Array == obj.constructor;
}
function isFloatArray(obj) {
return Float32Array == obj.constructor || Float64Array == obj.constructor ||
Array.isArray(obj) && obj.every(x => 'number' == typeof x);
}
function isIntArray(x) {
if ([Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array, Int32Array, Uint32Array]
.some(y => x instanceof y)) {
return true;
}
return (x instanceof Float32Array || x instanceof Float64Array || Array.isArray(x)) &&
x.every(x => Number.isInteger(x));
}
//const x:keyof UniformTypesMap = undefined as 'FLOAT_VEC4' | 'FLOAT_VEC3'
class Shader {
/**
* Provides a convenient wrapper for WebGL shaders. A few uniforms and attributes,
* prefixed with `gl_`, are automatically added to all shader sources to make
* simple shaders easier to write.
* Headers for the following variables are automatically prepended to the passed source. The correct variables
* are also automatically passed to the shader when drawing.
*
* For vertex and fragment shaders:
uniform mat3 LGL_NormalMatrix;
uniform mat4 LGL_ModelViewMatrix;
uniform mat4 LGL_ProjectionMatrix;
uniform mat4 LGL_ModelViewProjectionMatrix;
uniform mat4 LGL_ModelViewMatrixInverse;
uniform mat4 LGL_ProjectionMatrixInverse;
uniform mat4 LGL_ModelViewProjectionMatrixInverse;
*
*
* Example usage:
*
* const shader = new GL.Shader(
* `void main() { gl_Position = LGL_ModelViewProjectionMatrix * LGL_Vertex; }`,
* `uniform vec4 color; void main() { gl_FragColor = color; }`)
*
* shader.uniforms({ color: [1, 0, 0, 1] }).draw(mesh)
*
* Compiles a shader program using the provided vertex and fragment shaders.
*/
constructor(vertexSource, fragmentSource, gl = currentGL()) {
this.projectionMatrixVersion = -1;
this.modelViewMatrixVersion = -1;
// const versionRegex = /^(?:\s+|\/\/[\s\S]*?[\r\n]+|\/\*[\s\S]*?\*\/)+(#version\s+(\d+)\s+es)/
// Headers are prepended to the sources to provide some automatic functionality.
const header = `
uniform mat3 LGL_NormalMatrix;
uniform mat4 LGL_ModelViewMatrix;
uniform mat4 LGL_ProjectionMatrix;
uniform mat4 LGL_ModelViewProjectionMatrix;
uniform mat4 LGL_ModelViewMatrixInverse;
uniform mat4 LGL_ProjectionMatrixInverse;
uniform mat4 LGL_ModelViewProjectionMatrixInverse;
`;
const matrixNames = header.match(/\bLGL_\w+/g);
// Compile and link errors are thrown as strings.
function compileSource(type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, WGL$1.COMPILE_STATUS)) {
throw new Error('compile error: ' + gl.getShaderInfoLog(shader));
}
return shader;
}
this.gl = gl;
const program = gl.createProgram();
if (!program) {
gl.handleError();
}
this.program = program;
gl.attachShader(this.program, compileSource(WGL$1.VERTEX_SHADER, vertexSource));
gl.attachShader(this.program, compileSource(WGL$1.FRAGMENT_SHADER, fragmentSource));
gl.linkProgram(this.program);
if (!gl.getProgramParameter(this.program, WGL$1.LINK_STATUS)) {
throw new Error('link error: ' + gl.getProgramInfoLog(this.program));
}
this.attributes = {};
this.uniformLocations = {};
// Check for the use of built-in matrices that require expensive matrix
// multiplications to compute, and record these in `activeMatrices`.
this.activeMatrices = {};
matrixNames && matrixNames.forEach(name => {
if (gl.getUniformLocation(this.program, name)) {
this.activeMatrices[name] = true;
}
});
this.uniformInfos = {};
for (let i = gl.getProgramParameter(this.program, WGL$1.ACTIVE_UNIFORMS); i-- > 0;) {
// see https://www.khronos.org/registry/OpenGL-Refpages/es2.0/xhtml/glGetActiveUniform.xml
// this.program has already been checked
// i is in bounds
const info = gl.getActiveUniform(this.program, i);
this.uniformInfos[info.name] = info;
}
gl.handleError();
}
static create(vertexSource, fragmentSource, gl) {
return new Shader(vertexSource, fragmentSource, gl);
}
/**
* Set a uniform for each property of `uniforms`. The correct `viewerGL.uniform*()` method is inferred from the
* value types and from the stored uniform sampler flags.
*/
uniforms(uniforms) {
const gl = this.gl;
gl.useProgram(this.program);
gl.handleError();
for (const name in uniforms) {
const location = this.uniformLocations[name] || gl.getUniformLocation(this.program, name);
// !location && console.warn(name + ' uniform is not used in shader')
if (!location)
continue;
this.uniformLocations[name] = location;
let value = uniforms[name];
const info = this.uniformInfos[name];
if (NLA_DEBUG) {
// TODO: better errors
if (gl.SAMPLER_2D == info.type || gl.SAMPLER_CUBE == info.type || gl.INT == info.type) {
if (1 == info.size) {
assert(Number.isInteger(value));
}
else {
assert(isIntArray(value) && value.length == info.size, 'value must be int array if info.size != 1');
}
}
assert(gl.FLOAT != info.type ||
(1 == info.size && 'number' === typeof value || isFloatArray(value) && info.size == value.length));
assert(gl.FLOAT_VEC3 != info.type ||
(1 == info.size && value instanceof V3 ||
Array.isArray(value) && info.size == value.length && assertVectors(...value)));
assert(gl.FLOAT_VEC4 != info.type || 1 != info.size || isFloatArray(value) && value.length == 4);
assert(gl.FLOAT_MAT4 != info.type || value instanceof M4, () => value.toSource());
assert(gl.FLOAT_MAT3 != info.type || value.length == 9 || value instanceof M4);
}
if (value instanceof V3) {
value = value.toArray();
}
if (gl.FLOAT_VEC4 == info.type && info.size != 1) {
gl.uniform4fv(location, value.concatenated());
}
else if (gl.FLOAT == info.type && info.size != 1) {
gl.uniform1fv(location, value);
}
else if (gl.FLOAT_VEC3 == info.type && info.size != 1) {
gl.uniform3fv(location, V3.pack(value));
}
else if (value.length) {
switch (value.length) {
case 1:
gl.uniform1fv(location, value);
break;
case 2:
gl.uniform2fv(location, value);
break;
case 3:
gl.uniform3fv(location, value);
break;
case 4:
gl.uniform4fv(location, value);
break;
// Matrices are automatically transposed, since WebGL uses column-major
// indices instead of row-major indices.
case 9:
gl.uniformMatrix3fv(location, false, new Float32Array([
value[0], value[3], value[6],
value[1], value[4], value[7],
value[2], value[5], value[8],
]));
break;
case 16:
gl.uniformMatrix4fv(location, false, new Float32Array([
value[0], value[4], value[8], value[12],
value[1], value[5], value[9], value[13],
value[2], value[6], value[10], value[14],
value[3], value[7], value[11], value[15],
]));
break;
default:
throw new Error('don\'t know how to load uniform "' + name + '" of length ' + value.length);
}
}
else if ('number' == typeof value) {
if (gl.SAMPLER_2D == info.type || gl.SAMPLER_CUBE == info.type || gl.INT == info.type) {
gl.uniform1i(location, value);
}
else {
gl.uniform1f(location, value);
}
}
else if ('boolean' == typeof value) {
gl.uniform1i(location, +value);
}
else if (value instanceof M4) {
const m = value.m;
if (gl.FLOAT_MAT4 == info.type) {
gl.uniformMatrix4fv(location, false, [
m[0], m[4], m[8], m[12],
m[1], m[5], m[9], m[13],
m[2], m[6], m[10], m[14],
m[3], m[7], m[11], m[15]
]);
}
else if (gl.FLOAT_MAT3 == info.type) {
gl.uniformMatrix3fv(location, false, [
m[0], m[4], m[8],
m[1], m[5], m[9],
m[2], m[6], m[10]
]);
}
else if (gl.FLOAT_MAT2 == info.type) {
gl.uniformMatrix2fv(location, false, new Float32Array([
m[0], m[4],
m[1], m[5]
]));
}
else {
throw new Error(`Can't assign M4 to ${info.type}`);
}
}
else {
throw new Error('attempted to set uniform "' + name + '" to invalid value ' + value);
}
gl.handleError();
}
return this;
}
/**
* Sets all uniform matrix attributes, binds all relevant buffers, and draws the mesh geometry as indexed
* triangles or indexed LINES. Set `mode` to `WGL.LINES` (and either add indices to `LINES` or call
* `computeWireframe()`) to draw the mesh in wireframe.
*
* @param mesh
* @param mode Defaults to 'TRIANGLES'. Must be passed as string so the correct index buffer can be
* automatically drawn.
* @param start int
* @param count int
*/
draw(mesh, mode = DRAW_MODES.TRIANGLES, start, count) {
assert(mesh.hasBeenCompiled, 'mesh.hasBeenCompiled');
assert(undefined != DRAW_MODES[mode]);
const modeStr = DRAW_MODES[mode];
// assert(mesh.indexBuffers[modeStr], `mesh.indexBuffers[${modeStr}] undefined`)
return this.drawBuffers(mesh.vertexBuffers, mesh.indexBuffers[modeStr], mode, start, count);
}
/**
* Sets all uniform matrix attributes, binds all relevant buffers, and draws the
* indexed mesh geometry. The `vertexBuffers` argument is a map from attribute
* names to `Buffer` objects of type `WGL.ARRAY_BUFFER`, `indexBuffer` is a `Buffer`
* object of type `WGL.ELEMENT_ARRAY_BUFFER`, and `mode` is a WebGL primitive mode
* like `WGL.TRIANGLES` or `WGL.LINES`. This method automatically creates and caches
* vertex attribute pointers for attributes as needed.
*/
drawBuffers(vertexBuffers, indexBuffer, mode = DRAW_MODES.TRIANGLES, start = 0, count) {
const gl = this.gl;
gl.handleError();
assert(undefined != DRAW_MODES[mode]);
assertf(() => 1 <= Object.keys(vertexBuffers).length);
Object.keys(vertexBuffers).forEach(key => assertInst(Buffer$1, vertexBuffers[key]));
// Only varruct up the built-in matrices that are active in the shader
const on = this.activeMatrices;
const modelViewMatrixInverse = (on['LGL_ModelViewMatrixInverse'] || on['LGL_NormalMatrix'])
//&& this.modelViewMatrixVersion != gl.modelViewMatrixVersion
&& gl.modelViewMatrix.inversed();
const projectionMatrixInverse = on['LGL_ProjectionMatrixInverse']
//&& this.projectionMatrixVersion != gl.projectionMatrixVersion
&& gl.projectionMatrix.inversed();
const modelViewProjectionMatrix = (on['LGL_ModelViewProjectionMatrix'] || on['LGL_ModelViewProjectionMatrixInverse'])
//&& (this.projectionMatrixVersion != gl.projectionMatrixVersion || this.modelViewMatrixVersion !=
// gl.modelViewMatrixVersion)
&& gl.projectionMatrix.times(gl.modelViewMatrix);
const uni = {}; // Uniform Matrices
on['LGL_ModelViewMatrix']
&& this.modelViewMatrixVersion != gl.modelViewMatrixVersion
&& (uni['LGL_ModelViewMatrix'] = gl.modelViewMatrix);
on['LGL_ModelViewMatrixInverse'] && (uni['LGL_ModelViewMatrixInverse'] = modelViewMatrixInverse);
on['LGL_ProjectionMatrix']
&& this.projectionMatrixVersion != gl.projectionMatrixVersion
&& (uni['LGL_ProjectionMatrix'] = gl.projectionMatrix);
projectionMatrixInverse && (uni['LGL_ProjectionMatrixInverse'] = projectionMatrixInverse);
modelViewProjectionMatrix && (uni['LGL_ModelViewProjectionMatrix'] = modelViewProjectionMatrix);
modelViewProjectionMatrix && on['LGL_ModelViewProjectionMatrixInverse']
&& (uni['LGL_ModelViewProjectionMatrixInverse'] = modelViewProjectionMatrix.inversed());
on['LGL_NormalMatrix']
&& this.modelViewMatrixVersion != gl.modelViewMatrixVersion
&& (uni['LGL_NormalMatrix'] = modelViewMatrixInverse.transposed());
this.uniforms(uni);
this.projectionMatrixVersion = gl.projectionMatrixVersion;
this.modelViewMatrixVersion = gl.modelViewMatrixVersion;
// Create and enable attribute pointers as necessary.
let minVertexBufferLength = Infinity;
for (const attribute in vertexBuffers) {
const buffer = vertexBuffers[attribute];
assert(buffer.hasBeenCompiled);
const location = this.attributes[attribute] || gl.getAttribLocation(this.program, attribute);
gl.handleError();
if (location == -1 || !buffer.buffer) {
if (!attribute.startsWith('LGL_')) {
console.warn(`Vertex buffer ${attribute} was not bound because the attribute is not active.`);
}
continue;
}
this.attributes[attribute] = location;
gl.bindBuffer(WGL$1.ARRAY_BUFFER, buffer.buffer);
gl.handleError();
gl.enableVertexAttribArray(location);
gl.handleError();
gl.vertexAttribPointer(location, buffer.spacing, WGL$1.FLOAT, false, 0, 0);
gl.handleError();
minVertexBufferLength = Math.min(minVertexBufferLength, buffer.count);
}
// Disable unused attribute pointers.
for (const attribute in this.attributes) {
if (!(attribute in vertexBuffers)) {
gl.disableVertexAttribArray(this.attributes[attribute]);
gl.handleError();
}
}
if (NLA_DEBUG) {
const numAttribs = gl.getProgramParameter(this.program, gl.ACTIVE_ATTRIBUTES);
for (let i = 0; i < numAttribs; ++i) {
const buffer = gl.getVertexAttrib(i, gl.VERTEX_ATTRIB_ARRAY_BUFFER_BINDING);
if (!buffer) {
const info = gl.getActiveAttrib(this.program, i);
throw new Error('No buffer is bound to attribute ' + info.name);
}
// console.log('name:', info.name, 'type:', info.type, 'size:', info.size)
}
}
// Draw the geometry.
if (minVertexBufferLength) {
count = count || (indexBuffer ? indexBuffer.count : minVertexBufferLength);
assert(DRAW_MODE_CHECKS[mode](count), 'count ' + count + ' doesn\'t fulfill requirement '
+ DRAW_MODE_CHECKS[mode].toString() + ' for mode ' + DRAW_MODES[mode]);
if (indexBuffer) {
assert(indexBuffer.hasBeenCompiled);
assert(minVertexBufferLength > indexBuffer.maxValue);
assert(count % indexBuffer.spacing == 0);
assert(start % indexBuffer.spacing == 0);
if (start + count > indexBuffer.count) {
throw new Error('Buffer not long enough for passed parameters start/length/buffer length' + ' ' + start + ' ' + count + ' ' + indexBuffer.count);
}
gl.bindBuffer(WGL$1.ELEMENT_ARRAY_BUFFER, indexBuffer.buffer);
gl.handleError();
// start parameter has to be multiple of sizeof(WGL.UNSIGNED_SHORT)
gl.drawElements(mode, count, WGL$1.UNSIGNED_SHORT, 2 * start);
gl.handleError();
}
else {
if (start + count > minVertexBufferLength) {
throw new Error('invalid');
}
gl.drawArrays(mode, start, count);
gl.handleError();
}
gl.drawCallCount++;
}
return this;
}
}
/**
* There's only one constant, use it for default values. Use chroma-js or similar for actual colors.
*/
const GL_COLOR_BLACK = [0, 0, 0, 1];
function currentGL() {
return TSGLContext.gl;
}
const WGL$2 = WebGLRenderingContext;
function isNumber(obj) {
const str = Object.prototype.toString.call(obj);
return str == '[object Number]' || str == '[object Boolean]';
}
class TSGLContext {
constructor(gl, immediate = {
mesh: new Mesh()
.addVertexBuffer('coords', 'LGL_TexCoord')
.addVertexBuffer('colors', 'LGL_Color'),
mode: -1,
coord: [0, 0],
color: [1, 1, 1, 1],
pointSize: 1,
shader: Shader.create(`
attribute vec4 LGL_Color;
attribute vec4 LGL_Vertex;
uniform mat4 LGL_ModelViewProjectionMatrix;
attribute vec2 LGL_TexCoord;
uniform float pointSize;
varying vec4 color;
varying vec2 coord;
void main() {
color = LGL_Color;
coord = LGL_TexCoord;
gl_Position = LGL_ModelViewProjectionMatrix * LGL_Vertex;
gl_PointSize = pointSize;
}
`, `
precision highp float;
uniform sampler2D texture;
uniform float pointSize;
// uniform bool useTexture;
varying vec4 color;
varying vec2 coord;
void main() {
gl_FragColor = color;
// if (useTexture) gl_FragColor *= texture2D(texture, coord.xy);
}
`, gl),
}) {
this.immediate = immediate;
this.modelViewMatrix = M4.identity();
this.projectionMatrix = M4.identity();
this.tempMatrix = new M4();
this.resultMatrix = new M4();
this.modelViewStack = [];
this.projectionStack = [];
this.drawCallCount = 0;
this.projectionMatrixVersion = 0;
this.modelViewMatrixVersion = 0;
this.matrixMode(TSGLContext.MODELVIEW);
}
/// Implement the OpenGL modelview and projection matrix stacks, along with some other useful GLU matrix functions.
matrixMode(mode) {
switch (mode) {
case this.MODELVIEW:
this.currentMatrixName = 'modelViewMatrix';
this.stack = this.modelViewStack;
break;
case this.PROJECTION:
this.currentMatrixName = 'projectionMatrix';
this.stack = this.projectionStack;
break;
default:
throw new Error('invalid matrix mode ' + mode);
}
}
loadIdentity() {
M4.identity(this[this.currentMatrixName]);
this.currentMatrixName == 'projectionMatrix' ? this.projectionMatrixVersion++ : this.modelViewMatrixVersion++;
}
loadMatrix(m4) {
M4.copy(m4, this[this.currentMatrixName]);
this.currentMatrixName == 'projectionMatrix' ? this.projectionMatrixVersion++ : this.modelViewMatrixVersion++;
}
multMatrix(m4) {
M4.multiply(this[this.currentMatrixName], m4, this.resultMatrix);
const temp = this.resultMatrix;
this.resultMatrix = this[this.currentMatrixName];
this[this.currentMatrixName] = temp;
this.currentMatrixName == 'projectionMatrix' ? this.projectionMatrixVersion++ : this.modelViewMatrixVersion++;
}
mirror(plane) {
this.multMatrix(M4.mirror(plane));
}
perspective(fovDegrees, aspect, near, far) {
this.multMatrix(M4.perspectiveRad(fovDegrees * DEG, aspect, near, far, this.tempMatrix));
}
frustum(left, right, bottom, top, near, far) {
this.multMatrix(M4.frustum(left, right, bottom, top, near, far, this.tempMatrix));
}
ortho(left, right, bottom, top, near, far) {
this.multMatrix(M4.ortho(left, right, bottom, top, near, far, this.tempMatrix));
}
scale(...args) {
this.multMatrix(M4.scale(...args, this.tempMatrix));
}
mirroredX() {
this.multMatrix(M4.mirror(P3ZX));
}
translate(x, y, z) {
if (undefined !== y) {
this.multMatrix(M4.translate(x, y, z, this.tempMatrix));
}
else {
this.multMatrix(M4.translate(x, this.tempMatrix));
}
}
rotate(angleDegrees, x, y, z) {
this.multMatrix(M4.rotate(angleDegrees * DEG, { x, y, z }, this.tempMatrix));
}
lookAt(eye, center, up) {
this.multMatrix(M4.lookAt(eye, center, up, this.tempMatrix));
}
pushMatrix() {
this.stack.push(M4.copy(this[this.currentMatrixName]));
}
popMatrix() {
const pop = this.stack.pop();
assert(undefined !== pop);
this[this.currentMatrixName] = pop;
this.currentMatrixName == 'projectionMatrix' ? this.projectionMatrixVersion++ : this.modelViewMatrixVersion++;
}
/**
* World coordinates (WC) to screen/window coordinates matrix
*/
wcToWindowMatrix() {
const viewport = this.getParameter(this.VIEWPORT);
const [x, y, w, h] = viewport;
const viewportToScreenMatrix = new M4([
w / 2, 0, 0, x + w / 2,
h / 2, 0, 0, y + h / 2,
0, 0, 1, 0,
0, 0, 0, 1,
]);
return M4.multiplyMultiple(viewportToScreenMatrix, this.projectionMatrix, this.modelViewMatrix);
}
/////////// IMMEDIATE MODE
// ### Immediate mode
//
// Provide an implementation of OpenGL's deprecated immediate mode. This is
// deprecated for a reason: constantly re-specifying the geometry is a bad
// idea for performance. You should use a `GL.Mesh` instead, which specifies
// the geometry once and caches it on the graphics card. Still, nothing
// beats a quick `viewerGL.begin(WGL.POINTS); viewerGL.vertex(1, 2, 3); viewerGL.end();` for
// debugging. This intentionally doesn't implement fixed-function lighting
// because it's only meant for quick debugging tasks.
pointSize(pointSize) {
this.immediate.shader.uniforms({ pointSize: pointSize });
}
begin(mode) {
if (this.immediate.mode != -1)
throw new Error('mismatched viewerGL.begin() and viewerGL.end() calls');
this.immediate.mode = mode;
this.immediate.mesh.colors = [];
this.immediate.mesh.coords = [];
this.immediate.mesh.vertices = [];
}
color(...args) {
this.immediate.color =
(1 == args.length && Array.isArray(args[0]))
? args[0]
: (1 == args.length && 'number' == typeof args[0])
? hexIntToGLColor(args[0])
: (1 == args.length && 'string' == typeof args[0])
? chroma$2(args[0]).gl()
: [args[0], args[1], args[2], args[3] || 0];
}
texCoord(...args) {
this.immediate.coord = V.apply(undefined, args).toArray(2);
}
vertex(...args) {
this.immediate.mesh.colors.push(this.immediate.color);
this.immediate.mesh.coords.push(this.immediate.coord);
this.immediate.mesh.vertices.push(V.apply(undefined, args));
}
end() {
if (this.immediate.mode == -1)
throw new Error('mismatched viewerGL.begin() and viewerGL.end() calls');
this.immediate.mesh.compile();
this.immediate.shader.uniforms({
useTexture: !!TSGLContext.gl.getParameter(WGL$2.TEXTURE_BINDING_2D),
}).drawBuffers(this.immediate.mesh.vertexBuffers, undefined, this.immediate.mode);
this.immediate.mode = -1;
}
makeCurrent() {
TSGLContext.gl = this;
}
/**
* Starts an animation loop.
*/
animate(callback) {
const requestAnimationFrame = window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
function (callback) {
setTimeout(() => callback(performance.now()), 1000 / 60);
};
let time$$1 = performance.now(), keepUpdating = true;
const update = (now) => {
if (keepUpdating) {
callback.call(this, now, now - time$$1);
time$$1 = now;
requestAnimationFrame(update);
}
};
requestAnimationFrame(update);
return () => { keepUpdating = false; };
}
/**
* Provide an easy way to get a fullscreen app running, including an
* automatic 3D perspective projection matrix by default. This should be
* called once.
*
* Just fullscreen, no automatic camera:
*
* viewerGL.fullscreen({ camera: false })
*
* Adjusting field of view, near plane distance, and far plane distance:
*
* viewerGL.fullscreen({ fov: 45, near: 0.1, far: 1000 })
*
* Adding padding from the edge of the window:
*
* viewerGL.fullscreen({ paddingLeft: 250, paddingBottom: 60 })
*/
fullscreen(options = {}) {
const top = options.paddingTop || 0;
const left = options.paddingLeft || 0;
const right = options.paddingRight || 0;
const bottom = options.paddingBottom || 0;
if (!document.body) {
throw new Error('document.body doesn\'t exist yet (call viewerGL.fullscreen() from ' +
'window.onload() or from inside the <body> tag)');
}
document.body.appendChild(this.canvas);
document.body.style.overflow = 'hidden';
this.canvas.style.position = 'absolute';
this.canvas.style.left = left + 'px';
this.canvas.style.top = top + 'px';
this.canvas.style.width = window.innerWidth - left - right + 'px';
this.canvas.style.bottom = window.innerHeight - top - bottom + 'px';
const gl = this;
function windowOnResize() {
gl.canvas.width = (window.innerWidth - left - right) * window.devicePixelRatio;
gl.canvas.height = (window.innerHeight - top - bottom) * window.devicePixelRatio;
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
if (options.camera) {
gl.matrixMode(TSGLContext.PROJECTION);
gl.loadIdentity();
gl.perspective(options.fov || 45, gl.canvas.width / gl.canvas.height, options.near || 0.1, options.far || 1000);
gl.matrixMode(TSGLContext.MODELVIEW);
}
}
window.addEventListener('resize', windowOnResize);
windowOnResize();
return this;
}
viewportFill() {
this.viewport(0, 0, this.canvas.width, this.canvas.height);
}
handleError() {
// const errorCode = this.getError()
// if (0 !== errorCode) {
// throw new Error('' + errorCode + WGL_ERROR[errorCode])
// }
}
/**
* `create()` creates a new WebGL context and augments it with more methods. The alpha channel is disabled
* by default because it usually causes unintended transparencies in the canvas.
*/
static create(options = {}) {
const canvas = options.canvas || document.createElement('canvas');
if (!options.canvas) {
canvas.width = 800;
canvas.height = 600;
}
if (!('alpha' in options))
options.alpha = false;
let newGL = undefined;
try {
newGL = canvas.getContext('webgl2', options);
newGL && (newGL.version = 2);
if (!newGL) {
newGL = (canvas.getContext('webgl', options) || canvas.getContext('experimental-webgl', options));
newGL && (newGL.version = 1);
}
console.log('getting context');
}
catch (e) {
console.log(e, 'Failed to get context');
}
if (!newGL)
throw new Error('WebGL not supported');
TSGLContext.gl = newGL;
addOwnProperties(newGL, TSGLContext.prototype);
addOwnProperties(newGL, new TSGLContext(newGL));
//addEventListeners(newGL)
return newGL;
}
}
TSGLContext.MODELVIEW = 0;
TSGLContext.PROJECTION = 1;
TSGLContext.HALF_FLOAT_OES = 0x8D61;
// enum WGL_ERROR {
// NO_ERROR = WGL.NO_ERROR,
// INVALID_ENUM = WGL.INVALID_ENUM,
// INVALID_VALUE = WGL.INVALID_VALUE,
// INVALID_OPERATION = WGL.INVALID_OPERATION,
// INVALID_FRAMEBUFFER_OPERATION = WGL.INVALID_FRAMEBUFFER_OPERATION,
// OUT_OF_MEMORY = WGL.OUT_OF_MEMORY,
// CONTEXT_LOST_WEBGL = WGL.CONTEXT_LOST_WEBGL,
// }
TSGLContext.prototype.MODELVIEW = TSGLContext.MODELVIEW;
TSGLContext.prototype.PROJECTION = TSGLContext.PROJECTION;
TSGLContext.prototype.HALF_FLOAT_OES = TSGLContext.HALF_FLOAT_OES;
/**
*
* Push two triangles:
* c - d
* | \ |
* a - b
*/
function pushQuad(triangles, flipped, a, b, c, d) {
if (flipped) {
triangles.push(a, c, b, b, c, d);
}
else {
triangles.push(a, b, c, b, d, c);
}
}
function hexIntToGLColor(color) {
return [(color >> 16) / 255.0, ((color >> 8) & 0xff) / 255.0, (color & 0xff) / 255.0, 1.0];
}
const WGL$3 = WebGLRenderingContext;
class Buffer$1 {
/**
* Provides a simple method of uploading data to a GPU buffer. Example usage:
*
* const vertices = new Buffer(WGL.ARRAY_BUFFER, Float32Array)
* vertices.data = [[0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0]]
* vertices.compile()
*
* const indices = new Buffer(WGL.ELEMENT_ARRAY_BUFFER, Uint16Array)
* indices.data = [[0, 1, 2], [2, 1, 3]]
* indices.compile()
*
* Specifies the target to which the buffer object is bound.
* The symbolic constant must be GL_ARRAY_BUFFER or GL_ELEMENT_ARRAY_BUFFER.
*/
constructor(target, type) {
this.target = target;
this.type = type;
assert(target == WGL$3.ARRAY_BUFFER || target == WGL$3.ELEMENT_ARRAY_BUFFER, 'target == WGL.ARRAY_BUFFER || target == WGL.ELEMENT_ARRAY_BUFFER');
assert(type == Float32Array || type == Uint16Array, 'type == Float32Array || type == Uint16Array');
this.buffer = undefined;
this.type = type;
this.data = [];
this.count = 0;
this.spacing = 0;
this.hasBeenCompiled = false;
}
/**
* Upload the contents of `data` to the GPU in preparation for rendering. The data must be a list of lists
* where each inner list has the same length. For example, each element of data for vertex normals would be a
* list of length three. This will remember the data length and element length for later use by shaders.
*
* This could have used `[].concat.apply([], this.data)` to flatten the array but Google
* Chrome has a maximum number of arguments so the concatenations are chunked to avoid that limit.
*
* @param type Either `WGL.STATIC_DRAW` or `WGL.DYNAMIC_DRAW`. Defaults to `WGL.STATIC_DRAW`
*/
compile(type = WGL$3.STATIC_DRAW, gl = currentGL()) {
assert(WGL$3.STATIC_DRAW == type || WGL$3.DYNAMIC_DRAW == type, 'WGL.STATIC_DRAW == type || WGL.DYNAMIC_DRAW == type');
gl.handleError();
this.buffer = this.buffer || gl.createBuffer();
gl.handleError();
let buffer;
if (this.data.length == 0) {
console.warn('empty buffer ' + this.name);
//console.trace()
}
if (this.data.length == 0 || this.data[0] instanceof V3) {
assert(!(this.data[0] instanceof V3) || this.type == Float32Array);
V3.pack(this.data, buffer = new this.type(this.data.length * 3)); // asserts that all
// elements are V3s
this.spacing = 3;
this.count = this.data.length;
this.maxValue = 0;
}
else {
//assert(Array != this.data[0].constructor, this.name + this.data[0])
if (Array.isArray(this.data[0])) {
const bufferLength = this.data.length * this.data[0].length;
buffer = new this.type(bufferLength);
let i = this.data.length, destPtr = bufferLength;
while (i--) {
const subArray = this.data[i];
let j = subArray.length;
while (j--) {
buffer[--destPtr] = subArray[j];
}
}
assert(0 == destPtr);
}
else {
buffer = new this.type(this.data);
}
const spacing = this.data.length ? buffer.length / this.data.length : 0;
assert(spacing % 1 == 0, `buffer ${this.name} elements not of consistent size, average size is ` + spacing);
if (NLA_DEBUG) {
if (10000 <= buffer.length) {
this.maxValue = 0;
}
else {
this.maxValue = Math.max.apply(undefined, buffer);
}
}
assert(spacing !== 0);
this.spacing = spacing;
this.count = this.data.length;
}
gl.bindBuffer(this.target, this.buffer);
gl.handleError();
gl.bufferData(this.target, buffer, type);
gl.handleError();
this.hasBeenCompiled = true;
}
}
class Texture {
/**
* Provides a simple wrapper around WebGL textures that supports render-to-texture.
*
* The arguments `width` and `height` give the size of the texture in texels.
* WebGL texture dimensions must be powers of two unless `filter` is set to
* either `WGL.NEAREST` or `WGL.LINEAR` and `wrap` is set to `WGL.CLAMP_TO_EDGE`
* (which they are by default).
*
* Texture parameters can be passed in via the `options` argument.
* Example usage:
*
* let tex = new GL.Texture(256, 256, {
* magFilter: WGL.NEAREST,
* minFilter: WGL.LINEAR,
*
* wrapS: WGL.REPEAT,
* wrapT: WGL.REPEAT,
*
* format: WGL.RGB, // Defaults to WGL.RGBA
* type: WGL.FLOAT // Defaults to WGL.UNSIGNED_BYTE
* })
*
*/
constructor(width, height, options = {}, gl = currentGL()) {
this.gl = gl;
this.texture = gl.createTexture();
gl.handleError(); // in case createTexture returns null & fails
this.width = width;
this.height = height;
this.format = options.format || gl.RGBA;
this.internalFormat = options.internalFormat || gl.RGBA;
this.type = options.type || gl.UNSIGNED_BYTE;
const magFilter = options.filter || options.magFilter || gl.LINEAR;
const minFilter = options.filter || options.minFilter || gl.LINEAR;
if (this.type === gl.FLOAT) {
if (gl.version != 2 && !gl.getExtension('OES_texture_float')) {
throw new Error('OES_texture_float is required but not supported');
}
if ((minFilter !== gl.NEAREST || magFilter !== gl.NEAREST) && !gl.getExtension('OES_texture_float_linear')) {
throw new Error('OES_texture_float_linear is required but not supported');
}
}
else if (this.type === TSGLContext.HALF_FLOAT_OES) {
if (!gl.getExtension('OES_texture_half_float')) {
throw new Error('OES_texture_half_float is required but not supported');
}
if ((minFilter !== gl.NEAREST || magFilter !== gl.NEAREST) && !gl.getExtension('OES_texture_half_float_linear')) {
throw new Error('OES_texture_half_float_linear is required but not supported');
}
}
gl.bindTexture(gl.TEXTURE_2D, this.texture);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, magFilter);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, minFilter);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, options.wrap || options.wrapS || gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, options.wrap || options.wrapT || gl.CLAMP_TO_EDGE);
gl.texImage2D(gl.TEXTURE_2D, 0, this.internalFormat, width, height, 0, this.format, this.type, options.data);
}
setData(data) {
this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture);
this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.format, this.width, this.height, 0, this.format, this.type, data);
}
bind(unit) {
this.gl.activeTexture(this.gl.TEXTURE0 + unit);
this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture);
}
unbind(unit) {
this.gl.activeTexture(this.gl.TEXTURE0 + unit);
this.gl.bindTexture(this.gl.TEXTURE_2D, null);
}
canDrawTo() {
const gl = this.gl;
this.framebuffer = this.framebuffer || gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.texture, 0);
const result = gl.checkFramebufferStatus(gl.FRAMEBUFFER) == gl.FRAMEBUFFER_COMPLETE;
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
return result;
}
drawTo(callback) {
const gl = this.gl;
this.framebuffer = this.framebuffer || gl.createFramebuffer();
this.renderbuffer = this.renderbuffer || gl.createRenderbuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
gl.bindRenderbuffer(gl.RENDERBUFFER, this.renderbuffer);
if (this.width != this.renderbuffer.width || this.height != this.renderbuffer.height) {
this.renderbuffer.width = this.width;
this.renderbuffer.height = this.height;
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, this.width, this.height);
}
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.texture, 0);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, this.renderbuffer);
// if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) {
// throw new Error('Rendering to this texture is not supported (incomplete this.framebuffer)')
// }
const viewport = gl.getParameter(gl.VIEWPORT);
gl.viewport(0, 0, this.width, this.height);
callback(gl);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.bindRenderbuffer(gl.RENDERBUFFER, null);
gl.viewport(viewport[0], viewport[1], viewport[2], viewport[3]);
}
swapWith(other) {
assert(this.gl == other.gl);
let temp;
temp = other.texture;
other.texture = this.texture;
this.texture = temp;
temp = other.width;
other.width = this.width;
this.width = temp;
temp = other.height;
other.height = this.height;
this.height = temp;
}
/**
* Return a new texture created from `imgElement`, an `<img>` tag.
*/
static fromImage(imgElement, options, gl = currentGL()) {
options = options || {};
const texture = new Texture(imgElement.width, imgElement.height, options, gl);
try {
gl.texImage2D(gl.TEXTURE_2D, 0, texture.format, texture.format, texture.type, imgElement);
}
catch (e) {
if (location.protocol == 'file:') {
throw new Error('imgElement not loaded for security reasons (serve this page over "http://" instead)');
}
else {
throw new Error('imgElement not loaded for security reasons (imgElement must originate from the same ' +
'domain as this page or use Cross-Origin Resource Sharing)');
}
}
if (options.minFilter && options.minFilter != gl.NEAREST && options.minFilter != gl.LINEAR) {
gl.generateMipmap(gl.TEXTURE_2D);
}
return texture;
}
/**
* Returns a checkerboard texture that will switch to the correct texture when it loads.
*/
static fromURL(url, options = {}, gl = currentGL()) {
Texture.checkerBoardCanvas = Texture.checkerBoardCanvas || (function () {
const c = document.createElement('canvas').getContext('2d');
if (!c)
throw new Error('Could not create 2d canvas.');
c.canvas.width = c.canvas.height = 128;
for (let y = 0; y < c.canvas.height; y += 16) {
for (let x = 0; x < c.canvas.width; x += 16) {
//noinspection JSBitwiseOperatorUsage
c.fillStyle = (x ^ y) & 16 ? '#FFF' : '#DDD';
c.fillRect(x, y, 16, 16);
}
}
return c.canvas;
})();
const texture = Texture.fromImage(Texture.checkerBoardCanvas, options);
const image = new Image();
image.onload = () => Texture.fromImage(image, options, gl).swapWith(texture);
image.src = url;
return texture;
}
}
//# sourceMappingURL=bundle.module.js.map
var tsgl = Object.freeze({
Buffer: Buffer$1,
Mesh: Mesh,
get DRAW_MODES () { return DRAW_MODES; },
SHADER_VAR_TYPES: SHADER_VAR_TYPES,
isArray: isArray,
Shader: Shader,
Texture: Texture,
GL_COLOR_BLACK: GL_COLOR_BLACK,
currentGL: currentGL,
isNumber: isNumber,
TSGLContext: TSGLContext,
pushQuad: pushQuad
});
const { ceil, floor, abs: abs$2 } = Math;
class Curve extends Transformable {
constructor(tMin, tMax) {
super();
this.tMin = tMin;
this.tMax = tMax;
assertNumbers(tMin, tMax);
assert('number' == typeof tMin && !isNaN(tMin));
assert('number' == typeof tMax && !isNaN(tMax));
assert(tMin < tMax);
}
static integrate(curve, startT, endT, steps) {
const step = (endT - startT) / steps;
let length = 0;
let p = curve.at(startT);
let i = 0, t = startT + step;
for (; i < steps; i++, t += step) {
const next = curve.at(t);
length += p.distanceTo(next);
p = next;
}
return length;
}
static ispsRecursive(curve1, tMin, tMax, curve2, sMin, sMax) {
// the recursive function finds good approximates for the intersection points
// curve1 function uses newton iteration to improve the result as much as possible
function handleStartTS(startT, startS) {
if (!result.some(info => eq(info.tThis, startT) && eq(info.tOther, startS))) {
const f1 = (t, s) => curve1.tangentAt(t).dot(curve1.at(t).minus(curve2.at(s)));
const f2 = (t, s) => curve2.tangentAt(s).dot(curve1.at(t).minus(curve2.at(s)));
// f = (b1, b2, t1, t2) = b1.tangentAt(t1).dot(b1.at(t1).minus(b2.at(t2)))
const dfdt1 = (b1, b2, t1, t2) => b1.ddt(t1).dot(b1.at(t1).minus(b2.at(t2))) + (b1.tangentAt(t1).squared());
const dfdt2 = (b1, b2, t1, t2) => -b1.tangentAt(t1).dot(b2.tangentAt(t2));
const ni = newtonIterate2dWithDerivatives(f1, f2, startT, startS, 16, dfdt1.bind(undefined, curve1, curve2), dfdt2.bind(undefined, curve1, curve2), (t, s) => -dfdt2(curve2, curve1, s, t), (t, s) => -dfdt1(curve2, curve1, s, t));
assert(isFinite(ni.x));
assert(isFinite(ni.y));
if (ni == undefined)
console.log(startT, startS, curve1.sce, curve2.sce);
result.push({ tThis: ni.x, tOther: ni.y, p: curve1.at(ni.x) });
}
}
// returns whether an intersection was immediately found (i.e. without further recursion)
function findRecursive(tMin, tMax, sMin, sMax, curve1AABB, curve2AABB, depth = 0) {
const EPS = NLA_PRECISION;
if (curve1AABB.fuzzyTouchesAABB(curve2AABB)) {
const tMid = (tMin + tMax) / 2;
const sMid = (sMin + sMax) / 2;
if (Math.abs(tMax - tMin) < EPS || Math.abs(sMax - sMin) < EPS) {
handleStartTS(tMid, sMid);
return true;
}
else {
const curve1AABBleft = curve1.getAABB(tMin, tMid);
const curve2AABBleft = curve2.getAABB(sMin, sMid);
let curve1AABBright, curve2AABBright;
// if one of the following calls immediately finds an intersection, we don't want to call the others
// as that will lead to the same intersection being output multiple times
findRecursive(tMin, tMid, sMin, sMid, curve1AABBleft, curve2AABBleft, depth + 1)
|| findRecursive(tMin, tMid, sMid, sMax, curve1AABBleft, curve2AABBright = curve2.getAABB(sMid, sMax), depth + 1)
|| findRecursive(tMid, tMax, sMin, sMid, curve1AABBright = curve1.getAABB(tMid, tMax), curve2AABBleft, depth + 1)
|| findRecursive(tMid, tMax, sMid, sMax, curve1AABBright, curve2AABBright, depth + 1);
}
}
return false;
}
const result = [];
findRecursive(tMin, tMax, sMin, sMax, curve1.getAABB(tMin, tMax), curve2.getAABB(sMin, sMax));
return fuzzyUniquesF(result, info => info.tThis);
}
static breakDownIC(implicitCurve, { sMin, sMax, tMin, tMax }, sStep, tStep, stepSize, dids, didt) {
const bounds = (s, t) => sMin <= s && s <= sMax && tMin <= t && t <= tMax;
const deltaS = sMax - sMin, deltaT = tMax - tMin;
const sRes = ceil(deltaS / sStep), tRes = ceil(deltaT / tStep);
const grid = new Array(sRes * tRes).fill(0);
arrayFromFunction(tRes, i => grid.slice(sRes * i, sRes * (i + 1)).map(v => v ? 'X' : '_').join('')).join('\n');
const at = (i, j) => grid[j * sRes + i];
const set = (i, j) => 0 <= i && i < sRes && 0 <= j && j < tRes && (grid[j * sRes + i] = 1);
const result = [];
const logTable = [];
for (let i = 0; i < sRes; i++) {
search: for (let j = 0; j < tRes; j++) {
if (at(i, j))
continue;
set(i, j);
let s = sMin + (i + 0.5) * sStep, t = tMin + (j + 0.5) * tStep;
const startS = s, startT = t;
// basically curvePoint
for (let k = 0; k < 8; k++) {
const fp = implicitCurve(s, t);
const dfpdx = implicitCurve.x(s, t), dfpdy = implicitCurve.y(s, t);
if (0 == dfpdx * dfpdx + dfpdy * dfpdy) {
// top of a hill, keep looking
continue search;
}
const scale = fp / (dfpdx * dfpdx + dfpdy * dfpdy);
s -= scale * dfpdx;
t -= scale * dfpdy;
}
const li = floor((s - sMin) / sStep), lj = floor((t - tMin) / tStep);
logTable.push({
i,
j,
li,
lj,
startS,
startT,
s,
t,
'bounds(s, t)': bounds(s, t),
'ic(s,t)': implicitCurve(s, t),
});
if (!(i == li && j == lj) && at(li, lj)) {
continue search;
}
set(li, lj);
// s, t are now good starting coordinates to use follow algo
if (bounds(s, t) && eq0(implicitCurve(s, t))) {
console.log(V(s, t).sce);
const subresult = mkcurves(implicitCurve, s, t, stepSize, implicitCurve.x, implicitCurve.y, bounds);
for (const curvedata of subresult) {
assert(curvedata.points.length > 2);
for (const { x, y } of curvedata.points) {
const lif = (x - sMin) / sStep, ljf = (y - tMin) / tStep;
set((lif - 0.5) | 0, (ljf - 0.5) | 0);
set((lif - 0.5) | 0, (ljf + 0.5) | 0);
set((lif + 0.5) | 0, (ljf - 0.5) | 0);
set((lif + 0.5) | 0, (ljf + 0.5) | 0);
}
}
result.push(...subresult);
}
}
}
//console.table(logTable)
for (const { points } of result) {
for (let i = 0; i < points.length - 1; i++) {
assert(!points[i].equals(points[i + 1]));
}
}
return result;
}
toString() {
return this.toSource();
}
toSource(rounder = x => x) {
return callsce.call(undefined, 'new ' + this.constructor.name, ...this.getConstructorParameters());
}
withBounds(tMin = this.tMin, tMax = this.tMax) {
assert(this.tMin <= tMin && tMin <= this.tMax);
assert(this.tMin <= tMax && tMax <= this.tMax);
assert(this.tMin <= tMax && tMax <= this.tMax);
return new this.constructor(...this.getConstructorParameters().slice(0, -2), tMin, tMax);
}
/**
* The point on the line that is closest to the given point.
*/
closestPointToPoint(p) {
return this.at(this.closestTToPoint(p));
}
isValidT(t) {
return le(this.tMin, t) && le(t, this.tMax);
}
diff(t, eps) {
return this.at(t).to(this.at(t + eps));
}
closestTToPoint(p, tStart) {
// this.at(t) has minimal distance to p when this.tangentAt(t) is perpendicular to
// the vector between this.at(t) and p. This is the case iff the dot product of the two is 0.
// f = (this.at(t) - p) . (this.tangentAt(t)
// df = this.tangentAt(t) . this.tangentAt(t) + (this.at(t) - p) . this.ddt(t)
// = this.tangentAt(t)² + (this.at(t) - p) . this.ddt(t)
const f = (t) => this.at(t).minus(p).dot(this.tangentAt(t)); // 5th degree polynomial
const df = (t) => this.tangentAt(t).squared() + (this.at(t).minus(p).dot(this.ddt(t)));
const STEPS = 32;
const startT = undefined !== tStart
? tStart
: arrayFromFunction(STEPS, i => this.tMin + (this.tMax - this.tMin) * i / STEPS)
.withMax(t => -this.at(t).distanceTo(p));
return newtonIterateWithDerivative(f, startT, 16, df);
}
/**
* So different edges on the same curve do not have different vertices, they are always generated
* on fixed points this.at(k * this.tIncrement), with k taking integer values
*
*/
calcSegmentPoints(aT, bT, a, b, reversed, includeFirst) {
assert(this.tIncrement, 'tIncrement not defined on ' + this);
const inc = this.tIncrement;
const points = [];
if (includeFirst)
points.push(a);
assert(reversed != aT < bT);
if (aT < bT) {
const start = Math.ceil((aT + NLA_PRECISION) / inc);
const end = Math.floor((bT - NLA_PRECISION) / inc);
for (let i = start; i <= end; i++) {
points.push(this.at(i * inc));
}
}
else {
const start = Math.floor((aT - NLA_PRECISION) / inc);
const end = Math.ceil((bT + NLA_PRECISION) / inc);
for (let i = start; i >= end; i--) {
points.push(this.at(i * inc));
}
}
points.push(b);
return points;
}
/**
*
* @param p
* @param tStart Defines interval with tEnd in which a start value for t will be searched.
* Result is not necessarily in this interval.
* @param tEnd
*/
distanceToPoint(p, tStart, tEnd) {
const closestT = this.closestTToPoint(p, tStart, tEnd);
return this.at(closestT).distanceTo(p);
}
asSegmentDistanceToPoint(p, tStart, tEnd) {
let t = this.closestTToPoint(p, tStart, tEnd);
t = clamp(t, tStart, tEnd);
return this.at(t).distanceTo(p);
}
/**
* Behavior when curves are colinear: self intersections
*/
isInfosWithCurve(curve) {
return Curve.ispsRecursive(this, this.tMin, this.tMax, curve, curve.tMin, curve.tMax);
}
arcLength(startT, endT, steps = 1) {
assert(startT < endT, 'startT < endT');
return glqInSteps(t => this.tangentAt(t).length(), startT, endT, steps);
}
getAABB(tMin = this.tMin, tMax = this.tMax) {
tMin = isFinite(tMin) ? tMin : this.tMin;
tMax = isFinite(tMax) ? tMax : this.tMax;
const tMinAt = this.at(tMin), tMaxAt = this.at(tMax);
const roots = this.roots();
const mins = new Array(3), maxs = new Array(3);
for (let dim = 0; dim < 3; dim++) {
const tRoots = roots[dim];
mins[dim] = Math.min(tMinAt.e(dim), tMaxAt.e(dim));
maxs[dim] = Math.max(tMinAt.e(dim), tMaxAt.e(dim));
for (const tRoot of tRoots) {
if (tMin < tRoot && tRoot < tMax) {
mins[dim] = Math.min(mins[dim], this.at(tRoot).e(dim));
maxs[dim] = Math.max(maxs[dim], this.at(tRoot).e(dim));
}
}
}
return new AABB(V3.fromArray(mins), V3.fromArray(maxs));
}
reversed() {
throw new Error();
}
clipPlane(plane) {
const ists = this.isTsWithPlane(plane).filter(ist => this.tMin <= ist && ist <= this.tMax);
return getIntervals(ists, this.tMin, this.tMax).mapFilter(([a, b]) => {
const midT = (a + b) / 2;
return !eq(a, b) && plane.distanceToPointSigned(this.at(midT)) < 0 && this.withBounds(a, b);
});
}
}
Curve.hlol = 0;
function mkcurves(implicitCurve, sStart, tStart, stepSize, dids, didt, bounds) {
const start = V(sStart, tStart);
// checkDerivate(s => implicitCurve(s, 0), s => dids(s, 0), -1, 1, 0)
// checkDerivate(t => implicitCurve(0, t), t => didt(0, t), -1, 1, 0)
const { points, tangents } = followAlgorithm2d(implicitCurve, start, stepSize, bounds);
if (points[0].distanceTo(points.last) < stepSize && points.length > 2) {
// this is a loop: split it
for (let i = 0; i < points.length - 1; i++) {
assert(!points[i].equals(points[i + 1]));
}
const half = floor(points.length / 2);
const points1 = points.slice(0, half), points2 = points.slice(half - 1, points.length);
const tangents1 = tangents.slice(0, half), tangents2 = tangents.slice(half - 1, tangents.length);
tangents2[tangents2.length - 1] = tangents1[0];
points2[tangents2.length - 1] = points1[0];
for (let i = 0; i < points1.length - 1; i++) {
assert(!points1[i].equals(points1[i + 1]));
}
for (let i = 0; i < points2.length - 1; i++) {
assert(!points2[i].equals(points2[i + 1]));
}
return [{ points: points1, tangents: tangents1 }, { points: points2, tangents: tangents2 }];
}
else {
// not a loop: check in the other direction
const { points: reversePoints, tangents: reverseTangents } = followAlgorithm2d(implicitCurve, start, -stepSize, bounds);
const result = followAlgorithm2d(implicitCurve, reversePoints.last, stepSize, bounds, undefined, reverseTangents.last.negated());
assert(result.points.length > 2);
return [result];
}
}
function curvePoint(implicitCurve, startPoint, dids, didt) {
let p = startPoint;
for (let i = 0; i < 8; i++) {
const fp = implicitCurve(p.x, p.y);
const dfpdx = dids(p.x, p.y), dfpdy = didt(p.x, p.y);
const scale = fp / (dfpdx * dfpdx + dfpdy * dfpdy);
//console.log(p.$)
p = p.minus(new V3(scale * dfpdx, scale * dfpdy, 0));
}
return p;
}
function curvePointMF(mf, startPoint, steps = 8, eps = 1 / (1 << 30)) {
let p = startPoint;
for (let i = 0; i < steps; i++) {
const fp = mf(p.x, p.y);
const dfpdx = mf.x(p.x, p.y), dfpdy = mf.y(p.x, p.y);
const scale = fp / (dfpdx * dfpdx + dfpdy * dfpdy);
//console.log(p.$)
p = p.minus(new V3(scale * dfpdx, scale * dfpdy, 0));
if (abs$2(fp) <= eps)
break;
}
return p;
}
const { PI: PI$2 } = Math;
class XiEtaCurve extends Curve {
constructor(center, f1, f2, tMin = -PI$2, tMax = PI$2) {
super(tMin, tMax);
this.center = center;
this.f1 = f1;
this.f2 = f2;
this.tMin = tMin;
this.tMax = tMax;
assertVectors(center, f1, f2);
this.normal = f1.cross(f2);
if (!this.normal.likeO()) {
this.normal = this.normal.unit();
this.matrix = M4.forSys(f1, f2, this.normal, center);
this.inverseMatrix = this.matrix.inversed();
}
else {
this.matrix = M4.forSys(f1, f2, f1.unit(), center);
const f1p = f1.getPerpendicular();
this.inverseMatrix = new M4(1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1).times(M4.forSys(f1, f1p, f1.cross(f1p), center).inversed());
}
}
static magic(a, b, c) {
throw new Error('abstract');
}
/**
* Returns a new EllipseCurve representing an ellipse parallel to the XY-plane
* with semi-major/minor axes parallel t the X and Y axes and of length a and b.
*
* @param a length of the axis parallel to X axis
* @param b length of the axis parallel to Y axis
* @param center Defaults to V3.O
*/
static forAB(a, b, center = V3.O) {
return new this(center, V(a, 0, 0), V(0, b, 0));
}
static XYLCValid(pLC) {
throw new Error('abstract');
}
static XYLCPointT(pLC) {
throw new Error('abstract');
}
static unitIsInfosWithLine(anchorLC, dirLC, anchorWC, dirWC) {
throw new Error('abstract');
}
addToMesh(mesh, res = 4, radius = 0, pointStep = 1) {
const baseNormals = arrayFromFunction(res, i => V3.polar(1, TAU * i / res));
const baseVertices = arrayFromFunction(res, i => V3.polar(radius, TAU * i / res));
const inc = this.tIncrement;
const start = Math.ceil((this.tMin + NLA_PRECISION) / inc);
const end = Math.floor((this.tMax - NLA_PRECISION) / inc);
for (let i = start; i <= end; i += pointStep) {
const t = i * inc;
const start = mesh.vertices.length;
if (0 !== i) {
for (let j = 0; j < res; j++) {
pushQuad(mesh.TRIANGLES, true, start - res + j, start + j, start - res + (j + 1) % res, start + (j + 1) % res);
}
}
const point = this.at(t), tangent = this.tangentAt(t);
const matrix = M4.forSys(this.normal, tangent.cross(this.normal), tangent, point);
mesh.normals.push(...matrix.transformedVectors(baseNormals));
mesh.vertices.push(...matrix.transformedPoints(baseVertices));
}
}
getConstructorParameters() {
return [this.center, this.f1, this.f2, this.tMin, this.tMax];
}
isInfosWithCurve(curve) {
if (curve instanceof L3) {
return this.isInfosWithLine(curve.anchor, curve.dir1, this.tMin, this.tMax, curve.tMin, curve.tMax);
}
if (curve instanceof BezierCurve) {
return this.isInfosWithBezier(curve);
}
if (curve instanceof XiEtaCurve) {
if (!this.normal.isParallelTo(curve.normal)) {
return this.isTsWithPlane(curve.getPlane()).mapFilter(tThis => {
const p = this.at(tThis);
if (curve.containsPoint(p)) {
return { tThis, tOther: curve.pointT(p), p };
}
});
}
}
return super.isInfosWithCurve(curve);
}
transform(m4) {
return new this.constructor(m4.transformPoint(this.center), m4.transformVector(this.f1), m4.transformVector(this.f2), this.tMin, this.tMax);
}
equals(obj) {
return this == obj ||
obj.constructor == this.constructor
&& this.center.equals(obj.center)
&& this.f1.equals(obj.f1)
&& this.f2.equals(obj.f2);
}
hashCode() {
let hashCode = 0;
hashCode = hashCode * 31 + this.center.hashCode();
hashCode = hashCode * 31 + this.f1.hashCode();
hashCode = hashCode * 31 + this.f2.hashCode();
return hashCode | 0;
}
likeCurve(curve) {
return hasConstructor(curve, this.constructor)
&& this.center.like(curve.center)
&& this.f1.like(curve.f1)
&& this.f2.like(curve.f2);
}
normalP(t) {
return this.tangentAt(t).cross(this.normal);
}
getPlane() {
return P3.normalOnAnchor(this.normal, this.center);
}
isTsWithPlane(plane) {
assertInst(P3, plane);
/*
this: x = center + f1 * cos t + f2 * sin t (1)
plane:
n := plane.normal1
n DOT x == plane.w (2)
plane defined by f1/f2
x = center + f1 * xi + f2 * eta (3)
intersection plane and planef1/f2:
insert (3) into (2):
n DOT center + n DOT f1 * xi + n DOT f2 * eta = plane.w | -n DOT center
n DOT f1 * xi + n DOT f2 * eta = plane.w - n DOT center (4)
points on ellipse have additional condition
eta * eta + xi * xi = 1 (5)
g1 := n DOT f1
g2 := n DOT f2
g3 := w - n DOT center
solve system (5)/(6)
g1 * xi + g2 * eta = g3 (6)
*/
if (plane.normal1.isParallelTo(this.normal)) {
return [];
}
const n = plane.normal1, w = plane.w, center = this.center, f1 = this.f1, f2 = this.f2, g1 = n.dot(f1), g2 = n.dot(f2), g3 = w - n.dot(center);
return this.constructor.magic(g1, g2, g3);
}
pointT(p) {
assertVectors(p);
const pLC = this.inverseMatrix.transformPoint(p);
return this.constructor.XYLCPointT(pLC);
}
containsPoint(p) {
const pLC = this.inverseMatrix.transformPoint(p);
return eq0(pLC.z) && this.constructor.XYLCValid(pLC);
}
isInfosWithLine(anchorWC, dirWC, tMin, tMax, lineMin = -100000, lineMax = 100000) {
const anchorLC = this.inverseMatrix.transformPoint(anchorWC);
const dirLC = this.inverseMatrix.transformVector(dirWC);
if (eq0(dirLC.z)) {
// local line parallel to XY-plane
if (eq0(anchorLC.z)) {
// local line lies in XY-plane
return this.constructor.unitIsInfosWithLine(anchorLC, dirLC, anchorWC, dirWC);
}
}
else {
// if the line intersects the XY-plane in a single point, there can be an intersection there
// find point, then check if distance from circle = 1
const otherTAtZ0 = anchorLC.z / dirLC.z;
const isp = dirLC.times(otherTAtZ0).plus(anchorLC);
if (this.constructor.XYLCValid(isp)) {
// point lies on unit circle
return [{
tThis: this.constructor.XYLCPointT(isp),
tOther: otherTAtZ0,
p: anchorWC.plus(dirWC.times(otherTAtZ0)),
}];
}
}
return [];
}
isTsWithSurface(surface) {
if (surface instanceof PlaneSurface$1) {
return this.isTsWithPlane(surface.plane);
}
else if (surface instanceof SemiEllipsoidSurface) {
const isEllipse = surface.asEllipsoidSurface().isCurvesWithSurface(new PlaneSurface$1(this.getPlane()));
if (isEllipse.length < 1)
return [];
const possibleInfos = this.isInfosWithCurve(isEllipse[0]);
return possibleInfos.filter(info => surface.containsPoint(info.p)).map(info => info.tThis);
}
else if (surface instanceof ProjectedCurveSurface ||
surface instanceof EllipsoidSurface ||
surface instanceof ConicSurface) {
return surface.isCurvesWithPlane(this.getPlane())
.flatMap(curve => this.isInfosWithCurve(curve))
.map(info => info.tThis);
}
else {
throw new Error();
}
}
isInfosWithBezier(bezierWC) {
const bezierLC = bezierWC.transform(this.inverseMatrix);
if (new PlaneSurface$1(P3.XY).containsCurve(bezierLC)) {
return this.isInfosWithBezier2D(bezierWC);
}
else {
const infos = bezierLC.isTsWithPlane(P3.XY).mapFilter(tOther => {
const pLC = bezierLC.at(tOther);
if (this.constructor.XYLCValid(pLC)) {
return { tOther: tOther, p: bezierWC.at(tOther), tThis: this.constructor.XYLCPointT(pLC) };
}
});
return infos;
}
}
isInfosWithBezier2D(bezierWC, sMin, sMax) {
sMin = isFinite(sMin) ? sMin : bezierWC.tMin;
sMax = isFinite(sMax) ? sMax : bezierWC.tMax;
assertf(() => 0 < Math.PI);
assertf(() => sMin < sMax);
return Curve.ispsRecursive(this, this.tMin, this.tMax, bezierWC, sMin, sMax);
}
isOrthogonal() {
return this.f1.isPerpendicularTo(this.f2);
}
at2(xi, eta) {
assertNumbers(xi, eta);
// center + f1 xi + f2 eta
return this.center.plus(this.f1.times(xi)).plus(this.f2.times(eta));
}
debugToMesh(mesh, bufferName) {
mesh[bufferName] || mesh.addVertexBuffer(bufferName, bufferName);
for (let t = 0; t < Math.PI; t += 0.1) {
const p = this.at(t);
mesh[bufferName].push(p, p.plus(this.tangentAt(t).toLength(1)));
mesh[bufferName].push(p, p.plus(this.normalP(t).toLength(1)));
}
mesh[bufferName].push(this.center, this.center.plus(this.f1.times(1.2)));
mesh[bufferName].push(this.center, this.center.plus(this.f2));
mesh[bufferName].push(this.center, this.center.plus(this.normal));
}
}
const { ceil: ceil$1, floor: floor$1 } = Math;
class ImplicitCurve extends Curve {
constructor(points, tangents, dir = 1, generator, tMin = (1 == dir ? 0 : -(points.length - 1)), tMax = (1 == dir ? points.length - 1 : 0)) {
super(tMin, tMax);
this.points = points;
this.tangents = tangents;
this.dir = dir;
this.generator = generator;
assert(points.length > 2);
assert(0 <= tMin && tMin <= points.length - 1);
assert(0 <= tMax && tMax <= points.length - 1);
}
likeCurve(curve) {
throw new Error('Method not implemented.');
}
toSource(rounder = x => x) {
return this.generator || super.toSource(rounder);
}
containsPoint(p) {
assertVectors(p);
return !isNaN(this.pointT(p));
}
equals(obj) {
return this == obj ||
Object.getPrototypeOf(obj) == PICurve.prototype
&& this.points[0].equals(obj.points[0])
&& this.tangents[0].equals(obj.tangents[0]);
}
hashCode() {
return [this.points[0], this.tangents[0]].hashCode();
}
tangentP(pWC) {
assertVectors(pWC);
assert(this.containsPoint(pWC), 'this.containsPoint(pWC)' + this.containsPoint(pWC));
const t = this.pointT(pWC);
return this.tangentAt(t);
}
tangentAt(t) {
t = clamp(t, this.tMin, this.tMax);
return V3.lerp(this.tangents[floor$1(t)], this.tangents[ceil$1(t)], t % 1);
}
at(t) {
assert(!isNaN(t));
return V3.lerp(this.points[floor$1(t)], this.points[ceil$1(t)], t % 1);
}
getConstructorParameters() {
return [];
}
transform(m4) {
return new ImplicitCurve(m4.transformedPoints(this.points), m4.transformedVectors(this.tangents));
}
roots() {
const allTs = arrayRange(0, this.points.length);
return [allTs, allTs, allTs];
}
addToMesh(mesh, res = 4, radius = 0, pointStep = 1) {
const baseNormals = arrayFromFunction(res, i => V3.polar(1, TAU * i / res));
const baseVertices = arrayFromFunction(res, i => V3.polar(radius, TAU * i / res));
let prevTangent = V3.Z, prevMatrix = M4.IDENTITY;
for (let i = ceil$1(this.tMin); i < floor$1(this.tMax); i += pointStep) {
const start = mesh.vertices.length;
if (ceil$1(this.tMin) !== i) {
for (let j = 0; j < res; j++) {
pushQuad(mesh.TRIANGLES, true, start - res + j, start + j, start - res + (j + 1) % res, start + (j + 1) % res);
}
}
const point = this.points[i], tangent = this.tangents[i];
const tangentMatrix = M4.rotateAB(prevTangent, tangent).times(prevMatrix);
mesh.normals.push(...tangentMatrix.transformedVectors(baseNormals));
const baseMatrix = M4.translate(point).times(tangentMatrix);
mesh.vertices.push(...baseMatrix.transformedPoints(baseVertices));
prevTangent = tangent;
prevMatrix = tangentMatrix;
}
}
}
ImplicitCurve.prototype.tIncrement = 1;
const { PI: PI$3, abs: abs$3, sin: sin$1, cos: cos$1 } = Math;
class BezierCurve extends Curve {
constructor(p0, p1, p2, p3, tMin = -0.1, tMax = 1.1) {
super(tMin, tMax);
assertVectors(p0, p1, p2, p3);
assert(isFinite(tMin) && isFinite(tMax));
//assert(!L3.throughPoints(p0, p3).containsPoint(p1) || !L3.throughPoints(p0, p3).containsPoint(p2))
this.p0 = p0;
this.p1 = p1;
this.p2 = p2;
this.p3 = p3;
}
get points() {
return [this.p0, this.p1, this.p2, this.p3];
}
/**
* Returns a curve with curve.at(x) == V(x, ax³ + bx² + cx + d, 0)
*/
static graphXY(a, b, c, d, tMin, tMax) {
// d = p0y
// c = -3 p0y + 3 p1y => p1y = c/3 + p0y
// b = 3 p0y - 6 p1y + 3 p2y => p2y = b/3 - p0y + 2 p1y
// a = -p0y + 3 p1y -3 p2y + p3y => p3y = a + p0y - 3 p1y + 3 p2y
const p0y = d;
const p1y = c / 3 + p0y;
const p2y = b / 3 - p0y + 2 * p1y;
const p3y = a + p0y - 3 * p1y + 3 * p2y;
return new BezierCurve(V(0, p0y), V(1 / 3, p1y), V(2 / 3, p2y), V(1, p3y), tMin, tMax);
}
static quadratic(a, b, c, tMin = 0, tMax = 1) {
const line = L3.throughPoints(a, c);
if (line.containsPoint(b)) {
return line;
}
else {
// p1 = 1/3 a + 2/3 b
// p2 = 1/3 c + 2/3 b
return new BezierCurve(a, b.times(2).plus(a).div(3), b.times(2).plus(c).div(3), c, tMin, tMax);
}
}
/**
* Returns a bezier curve which approximates a CCW unit circle arc starting at V3.X of angle phi
* phi <= PI / 2 is recommended
*
* Formula from here: https://pomax.github.io/bezierinfo/#circles_cubic
*/
static approximateUnitArc(phi) {
const f = 4 / 3 * Math.tan(phi / 4);
return new BezierCurve(V3.X, new V3(1, f, 0), new V3(cos$1(phi) + f * sin$1(phi), sin$1(phi) - f * cos$1(phi), 0), V3.sphere(phi, 0), 0, 1);
}
static testEdges() {
const curve2 = BezierCurve.graphXY(2, -3, -3, 2, 0.6, 2);
const items = curve2.magic().map(c => Edge.forCurveAndTs(c).translate(3));
console.log(items.length);
return [Edge.forCurveAndTs(curve2)].concat(items);
}
getConstructorParameters() {
return [this.p0, this.p1, this.p2, this.p3, this.tMin, this.tMax];
}
at(t) {
// = s^3 p0 + 3 s^2 t p1 + 3 s t^2 p2 + t^3 p3
assertNumbers(t);
const p0 = this.p0, p1 = this.p1, p2 = this.p2, p3 = this.p3;
const s = 1 - t, c0 = s * s * s, c1 = 3 * s * s * t, c2 = 3 * s * t * t, c3 = t * t * t;
return new V3(p0.x * c0 + p1.x * c1 + p2.x * c2 + p3.x * c3, p0.y * c0 + p1.y * c1 + p2.y * c2 + p3.y * c3, p0.z * c0 + p1.z * c1 + p2.z * c2 + p3.z * c3);
}
/**
* s := (1 - t)
* at(t) := s³ p0 + 3 s² t p1 + 3 s t² p2 + t³ p3
* tangent(t) := 3 s² (p1 - p0) + 6 s t (p2 - p1) + 3 t² (p3 - p2)
* := 3 (1 - t)² (p1 - p0) + 6 (1 - t) t (p2 - p1) + 3 t² (p3 - p2)
* := 3 (1 - 2 t + t²) (p1 - p0) + 6 (t - t²) (p2 - p1) + 3 t² (p3 - p2)
* := (3 (p3 - p2) - 6 (p2 - p1) + 3 (p1 - p0)) t²*
* + (-6 (p1 - p0) + (p2 - p1)) t
* + 3 (p1 - p0)
*/
tangentAt(t) {
assertNumbers(t);
const p0 = this.p0, p1 = this.p1, p2 = this.p2, p3 = this.p3;
const s = 1 - t, c01 = 3 * s * s, c12 = 6 * s * t, c23 = 3 * t * t;
return new V3((p1.x - p0.x) * c01 + (p2.x - p1.x) * c12 + (p3.x - p2.x) * c23, (p1.y - p0.y) * c01 + (p2.y - p1.y) * c12 + (p3.y - p2.y) * c23, (p1.z - p0.z) * c01 + (p2.z - p1.z) * c12 + (p3.z - p2.z) * c23);
}
ddt(t) {
assertNumbers(t);
const p0 = this.p0, p1 = this.p1, p2 = this.p2, p3 = this.p3;
const c012 = 6 * (1 - t), c123 = 6 * t;
return new V3((p2.x - 2 * p1.x + p0.x) * c012 + (p3.x - 2 * p2.x + p1.x) * c123, (p2.y - 2 * p1.y + p0.y) * c012 + (p3.y - 2 * p2.y + p1.y) * c123, (p2.z - 2 * p1.z + p0.z) * c012 + (p3.z - 2 * p2.z + p1.z) * c123);
}
normalP(t) {
const tangent = this.tangentAt(t);
const rot = tangent.cross(this.ddt(t));
return rot.cross(tangent);
}
isTsWithPlane(plane) {
assertInst(P3, plane);
/*
We are solving for t:
n := plane.normal1
this.at(t) DOT n == plane.w // according to plane definition
(a t³ + b t² + c t + d) DOT n == plane.w // bezier curve as cubic equation
(a DOT n) t³ + (b DOT n) t³ + (c DOT n) t + d DOT n - plane.w == 0 // multiply out DOT n, minus plane.w
*/
const { p0, p1, p2, p3 } = this;
const n = plane.normal1;
const a = p1.minus(p2).times(3).minus(p0).plus(p3);
const b = p0.plus(p2).times(3).minus(p1.times(6));
const c = p1.minus(p0).times(3);
const d = p0;
return solveCubicReal2(a.dot(n), b.dot(n), c.dot(n), d.dot(n) - plane.w)
.filter(t => between(t, this.tMin, this.tMax));
}
isTsWithSurface(surface) {
if (surface instanceof PlaneSurface$1) {
return this.isTsWithPlane(surface.plane);
}
if (surface instanceof SemiCylinderSurface) {
const projPlane = new P3(surface.dir.unit(), 0);
const projThis = this.project(projPlane);
const projEllipse = surface.baseCurve.project(projPlane);
return projEllipse.isInfosWithBezier2D(projThis).map(info => info.tOther);
}
if (surface instanceof ProjectedCurveSurface) {
const projPlane = new P3(surface.dir.unit(), 0);
const projThis = this.project(projPlane);
const projEllipse = surface.baseCurve.project(projPlane);
return projEllipse.isInfosWithCurve(projThis).map(info => info.tOther);
}
if (surface instanceof EllipsoidSurface) {
const thisOC = this.transform(surface.inverseMatrix);
const f = (t) => thisOC.at(t).length() - 1;
const df = (t) => thisOC.at(t).unit().dot(thisOC.tangentAt(t));
const stepSize = 1 / (1 << 11);
const result = [];
for (let startT = this.tMin; startT <= this.tMax; startT += stepSize) {
const dt = stepSize * thisOC.tangentAt(startT).length();
if (abs$3(f(startT)) <= dt) {
//const t = newtonIterate1d(f, startT, 16)
let t = newtonIterateWithDerivative(f, startT, 16, df);
if (!eq0(f(t)) || eq0(df(t))) {
t = newtonIterate1d(df, startT, 16);
//if (f(a) * f(b) < 0) {
// t = bisect(f, a, b, 16)
//} else if (df(a) * df(b) < 0) {
// t = bisect(df, a, b, 16)
//}
}
if (eq0(f(t)) && !result.some(r => eq(r, t))) {
result.push(t);
}
}
}
return result;
}
if (surface instanceof SemiEllipsoidSurface) {
return this.isTsWithSurface(surface.asEllipsoidSurface()).filter(t => surface.containsPoint(this.at(t)));
}
throw new Error();
}
likeCurve(curve) {
return this == curve ||
hasConstructor(curve, BezierCurve)
&& this.p0.like(curve.p0)
&& this.p1.like(curve.p1)
&& this.p2.like(curve.p2)
&& this.p3.like(curve.p3);
}
equals(obj) {
return this == obj ||
hasConstructor(obj, BezierCurve)
&& this.p0.equals(obj.p0)
&& this.p1.equals(obj.p1)
&& this.p2.equals(obj.p2)
&& this.p3.equals(obj.p3);
}
hashCode() {
let hashCode = 0;
hashCode = hashCode * 31 + this.p0.hashCode();
hashCode = hashCode * 31 + this.p1.hashCode();
hashCode = hashCode * 31 + this.p2.hashCode();
hashCode = hashCode * 31 + this.p3.hashCode();
return hashCode | 0;
}
/**
* Checks if this curve is colinear to the passed curve, i.e.
* for every t:number there exists a s:number with this.at(t) = curve.at(s)
*/
isColinearTo(curve) {
if (this === curve || this.likeCurve(curve))
return true;
if (!(curve instanceof BezierCurve))
return false;
// first, find out where/if curve.p0 and curve.p3 are on this
// then split this at curve.p0 --> curve.p3 to compare points p1 and p2
let curveP0T, curveP3T;
// assign in if condition to exploit short-circuit
if (isNaN(curveP0T = this.pointT(curve.p0)) || isNaN(curveP3T = this.pointT(curve.p3))) {
return false;
}
let thisSplit;
if (eq(1, curveP0T)) {
// this.split(curveP0T).right is degenerate in this case, so we need to handle it separately
// this.split(curveP3T): 0 --> curveP3T --> 1
// .right: curveP3T --> 1
// .reversed(): 1 --> curveP3T
thisSplit = this.split(curveP3T)[1].reversed();
}
else {
// curveP3T describes the point on this
// adjust it so it describes the same point on this.split(curveP0T).right
// this: 0 p0t p3t 1
// | | | |
// this.split(curveP0T).right: 0 p3tad 1
const curveP3Tadjusted = (curveP3T - curveP0T) / (1 - curveP0T);
thisSplit = this.split(curveP0T)[1].split(curveP3Tadjusted)[0];
}
return curve.likeCurve(thisSplit);
}
reversed() {
return new BezierCurve(this.p3, this.p2, this.p1, this.p0, 1 - this.tMax, 1 - this.tMin);
}
getCoefficients() {
const { p0, p1, p2, p3 } = this;
// calculate cubic equation coefficients
// a t³ + b t² + c t + d = 0
// multiplying out the cubic Bézier curve equation gives:
// a = -p0 + 3 p1 - 3 p2 + p3
// b = 3 p0 - 6 p1 + 3 p2
// c = -3 p0 + 3 p1
// d = p0 - p
const a = p1.minus(p2).times(3).minus(p0).plus(p3);
const b = p0.plus(p2).times(3).minus(p1.times(6));
const c = p1.minus(p0).times(3);
const d = p0;
return [a, b, c, d];
}
tangentCoefficients() {
const { p0, p1, p2, p3 } = this;
const p01 = p1.minus(p0), p12 = p2.minus(p1), p23 = p3.minus(p2);
const a = p01.plus(p23).times(3).minus(p12.times(6));
const b = p12.minus(p01).times(6);
const c = p01.times(3);
return [V3.O, a, b, c];
}
pointT(p) {
return this.closestTToPoint(p);
}
pointT3(p) {
const { p0, p1, p2, p3 } = this;
// calculate cubic equation coefficients
// a t³ + b t² + c t + d = 0
// multiplying out the cubic Bézier curve equation gives:
// a = -p0 + 3 p1 - 3 p2 + p3
// b = 3 p0 - 6 p1 + 3 p2
// c = -3 p0 + 3 p1
// d = p0 - p
const a = p1.minus(p2).times(3).minus(p0).plus(p3);
const b = p0.plus(p2).times(3).minus(p1.times(6));
const c = p1.minus(p0).times(3);
const d = p0.minus(p);
// a t³ + b t² + c t + d = 0 is 3 cubic equations, some of which can be degenerate
const maxDim = NLA_PRECISION < a.maxAbsElement() ? a.maxAbsDim()
: NLA_PRECISION < b.maxAbsElement() ? b.maxAbsDim()
: NLA_PRECISION < c.maxAbsElement() ? c.maxAbsDim()
: assertNever();
const results = solveCubicReal2(a.e(maxDim), b.e(maxDim), c.e(maxDim), d.e(maxDim)).filter(t => this.at(t).like(p));
if (0 == results.length)
return NaN;
if (1 == results.length)
return results[0];
assert(false, 'multiple intersection ' + this.toString() + p.sce);
}
pointT2(p) {
const { p0, p1, p2, p3 } = this;
// calculate cubic equation coefficients
// a t³ + b t² + c t + d = 0
// multiplying out the cubic Bézier curve equation gives:
// a = -p0 + 3 p1 - 3 p2 + p3
// b = 3 p0 - 6 p1 + 3 p2
// c = -3 p0 + 3 p1
// d = p0 - p
const a = p1.minus(p2).times(3).minus(p0).plus(p3).els();
const b = p0.plus(p2).times(3).minus(p1.times(6)).els();
const c = p1.minus(p0).times(3).els();
const d = p0.minus(p).els();
let results = undefined;
// assume passed point is on curve and that curve does not self-intersect,
// i.e. there is exactly one correct result for t
// try to find a single result in the x-dimension, if multiple are found,
// filter them by checking the other dimensions
for (let dim = 0; dim < 3; dim++) {
if (eq0(a[dim]) && eq0(b[dim]) && eq0(c[dim])) {
// for case x:
// ax == bx == cx == 0 => x(t) = dx
// x value is constant
// if x == 0 for all t, this does not limit the result, otherwise, there is no result, i.e
// the passed point is not on the curve
if (!eq0(d[dim]))
return NaN;
}
else {
const newResults = solveCubicReal2(a[dim], b[dim], c[dim], d[dim]);
if (0 == newResults.length)
return NaN;
if (1 == newResults.length)
return newResults[0];
if (results) {
results = results.filter(t => newResults.some(t2 => eq(t, t2)));
if (0 == results.length)
return NaN;
if (1 == results.length)
return results[0];
}
else {
results = newResults;
}
}
}
assert(false, 'multiple intersection ' + results + this.toString() + p.sce);
}
transform(m4) {
return new BezierCurve(m4.transformPoint(this.p0), m4.transformPoint(this.p1), m4.transformPoint(this.p2), m4.transformPoint(this.p3), this.tMin, this.tMax);
}
isClosed() {
return this.p0.like(this.p3);
}
isQuadratic() {
return this.p1.like(this.p2);
}
debugToMesh(mesh, bufferName) {
const result = mesh.addVertexBuffer(bufferName, bufferName);
for (let t = -2; t <= 2; t += 0.01) {
const p = this.at(t);
result[bufferName].push(p, p.plus(this.tangentAt(t).toLength(1)));
result[bufferName].push(p, p.plus(this.normalP(t).toLength(1)));
}
result[bufferName].push(this.p0, this.p1);
result[bufferName].push(this.p1, this.p2);
result[bufferName].push(this.p2, this.p3);
}
split(t) {
// do de Casteljau's algorithm at t, the resulting points are the points needed to create 2 new curves
const s = (1 - t);
const { p0, p1, p2, p3 } = this;
/*
p3 // n3
b01 = s p0 + t p1
b11 = s p1 + t p2
b21 = s p2 + t p3 // n2
b02 = s b01 + t b11
b12 = s b11 + t b21 // n1
b03 = s b02 + t b12 // n0
c01 =
*/
const b01 = p0.times(s).plus(p1.times(t)), b11 = p1.times(s).plus(p2.times(t)), b21 = p2.times(s).plus(p3.times(t));
const b02 = b01.times(s).plus(b11.times(t)), b12 = b11.times(s).plus(b21.times(t));
const b03 = b02.times(s).plus(b12.times(t));
return [new BezierCurve(p0, b01, b02, b03), new BezierCurve(b03, b12, b21, p3)];
}
containsPoint(p) {
return isFinite(this.pointT(p));
}
roots() {
/**
* := (3 (p3 - p2) - 6 (p2 - p1) + 3 (p1 - p0)) t²*
* + (-6 (p1 - p0) + 6 (p2 - p1)) t
* + 3 (p1 - p0)
* */
const { p0, p1, p2, p3 } = this;
const p01 = p1.minus(p0), p12 = p2.minus(p1), p23 = p3.minus(p2);
const a = p01.plus(p23).times(3).minus(p12.times(6));
const b = p12.minus(p01).times(6);
const c = p01.times(3);
return arrayFromFunction(3, dim => solveCubicReal2(0, a.e(dim), b.e(dim), c.e(dim)));
}
isInfosWithLine(anchorWC, dirWC, tMin, tMax, lineMin = -100000, lineMax = 100000) {
const dirLength = dirWC.length();
// TODO: no:
let result = Curve.ispsRecursive(this, this.tMin, this.tMax, new L3(anchorWC, dirWC.unit()), lineMin, lineMax);
result = fuzzyUniquesF(result, info => info.tOther);
result.forEach(info => (info.tOther /= dirLength));
return result;
// looking for this.at(t) == line.at(s)
// this.at(t).x == anchorWC.x + dirWC.x * s
// (this.at(t).x - anchorWC.x) / dirWC.x == s (analogue for y and z) (1x, 1y, 1z)
// (1x) - (1y):
// (this.at(t).x - anchorWC.x) / dirWC.x - (this.at(t).y - anchorWC.y) / dirWC.y == 0
// (this.at(t).x - anchorWC.x) * dirWC.y - (this.at(t).y - anchorWC.y) * dirWC.x == 0 (2)
// cubic equation params (see #pointT):
const { p0, p1, p2, p3 } = this;
const a = p1.minus(p2).times(3).minus(p0).plus(p3);
const b = p0.plus(p2).times(3).minus(p1.times(6));
const c = p1.minus(p0).times(3);
const d = p0;
// modifier cubic equation stP to get (1)
// const w = a.x * dirWC.y - a.y * dirWC.x
// const x = b.x * dirWC.y - b.y * dirWC.x
// const y = c.x * dirWC.y - c.y * dirWC.x
// const z = (d.x - anchorWC.x) * dirWC.y - (d.y - anchorWC.y) * dirWC.x
// the above version doesn't work for dirWC.x == dirWC.y == 0, so:
const absMinDim = dirWC.minAbsDim();
const [coord0, coord1] = [[1, 2], [2, 0], [0, 1]][absMinDim];
const w = a.e(coord0) * dirWC.e(coord1) - a.e(coord1) * dirWC.e(coord0);
const x = b.e(coord0) * dirWC.e(coord1) - b.e(coord1) * dirWC.e(coord0);
const y = c.e(coord0) * dirWC.e(coord1) - c.e(coord1) * dirWC.e(coord0);
const z = (d.e(coord0) - anchorWC.e(coord0)) * dirWC.e(coord1) - (d.e(coord1) - anchorWC.e(coord1)) * dirWC.e(coord0);
tMin = isFinite(tMin) ? tMin : this.tMin;
tMax = isFinite(tMax) ? tMax : this.tMax;
// we ignored a dimension in the previous step, so we need to check it too
return solveCubicReal2(w, x, y, z).mapFilter(tThis => {
if (tMin <= tThis && tThis <= tMax) {
const p = this.at(tThis);
// console.log(t*t*t*w+t*t*x+t*y+z, dirWC.length())
const s = p.minus(anchorWC).dot(dirWC) / dirWC.dot(dirWC);
const lineAtS = dirWC.times(s).plus(anchorWC);
if (lineAtS.like(p))
return { tThis: tThis, tOther: s, p: p };
}
});
}
closestPointToLine(line, tMin, tMax) {
// (this(t)-line(s)) * line.dir == 0 (1)
// (this(t)-line(s)) * this.tangentAt(t) == 0 (2)
// this(t) * line.dir - line(s) * line.dir == 0
// this(t) * line.dir - line.anchor * line.dir - s line.dir * line.dir == 0
// this(t) * line.dir - line.anchor * line.dir == s (3)
// insert (3) in (2)
// (this(t)-line(this(t) * line.dir - line.anchor * line.dir)) * this.tangentAt(t) == 0 (4)
// (4) is a 5th degree polynomial, solve numerically
tMin = isFinite(tMin) ? tMin : this.tMin;
tMax = isFinite(tMax) ? tMax : this.tMax;
const anchorDotDir1 = line.anchor.dot(line.dir1);
const f = (t) => {
const atT = this.at(t);
return (atT.minus(line.at(atT.dot(line.dir1) - anchorDotDir1))).dot(this.tangentAt(t));
};
const STEPS = 32;
const startT = arrayFromFunction(STEPS, i => tMin + (tMax - tMin) * i / STEPS).withMax(t => -f(t));
return newtonIterate1d(f, startT, 8);
}
/**
*
* @param bezier
* @param tMin
* @param tMax
* @param sMin
* @param {number=} sMax
* @returns
*/
isInfosWithBezie3(bezier, tMin, tMax, sMin, sMax) {
const handleStartTS = (startT, startS) => {
if (!result.some(info => eq(info.tThis, startT) && eq(info.tOther, startS))) {
const f1 = (t, s) => this.tangentAt(t).dot(this.at(t).minus(bezier.at(s)));
const f2 = (t, s) => bezier.tangentAt(s).dot(this.at(t).minus(bezier.at(s)));
// f = (b1, b2, t1, t2) = b1.tangentAt(t1).dot(b1.at(t1).minus(b2.at(t2)))
const fdt1 = (b1, b2, t1, t2) => b1.ddt(t1).dot(b1.at(t1).minus(b2.at(t2))) + (b1.tangentAt(t1).squared());
const fdt2 = (b1, b2, t1, t2) => -b1.tangentAt(t1).dot(b2.tangentAt(t2));
const ni = newtonIterate2dWithDerivatives(f1, f2, startT, startS, 16, fdt1.bind(undefined, this, bezier), fdt2.bind(undefined, this, bezier), (t, s) => -fdt2(bezier, this, s, t), (t, s) => -fdt1(bezier, this, s, t));
result.push({ tThis: ni.x, tOther: ni.y, p: this.at(ni.x) });
}
};
tMin = 'number' == typeof tMin && isFinite(tMin) ? tMin : this.tMin;
tMax = 'number' == typeof tMax && isFinite(tMax) ? tMax : this.tMax;
sMin = 'number' == typeof sMin && isFinite(sMin) ? sMin : bezier.tMin;
sMax = 'number' == typeof sMax && isFinite(sMax) ? sMax : bezier.tMax;
// stack of indices:
const indices = [tMin, tMax, sMin, sMax];
const tMid = (tMin + tMax) / 2;
const sMid = (sMin + sMax) / 2;
const aabbs = [this.getAABB(tMin, tMid), this.getAABB(tMid, tMax), bezier.getAABB(sMin, sMin), bezier.getAABB(sMid, sMax)];
const result = [];
while (indices.length) {
const i = indices.length - 4;
const tMin = indices[i], tMax = indices[i + 1], sMin = indices[i + 2], sMax = indices[i + 3];
indices.length -= 4;
const thisAABB = this.getAABB(tMin, tMax);
const otherAABB = bezier.getAABB(sMin, sMax);
// console.log(tMin, tMax, sMin, sMax, thisAABB.sce, otherAABB.sce)
if (thisAABB && otherAABB && thisAABB.intersectsAABB2d(otherAABB)) {
const tMid = (tMin + tMax) / 2;
const sMid = (sMin + sMax) / 2;
const EPS = 0.00001;
if (tMax - tMin < EPS || sMax - sMin < EPS) {
console.log(tMin, tMax, sMin, sMax);
console.log(thisAABB.sce);
console.log(otherAABB.sce);
console.log(tMid, sMid);
handleStartTS(tMid, sMid);
}
else {
Array.prototype.push.call(indices, tMin, tMid, sMin, sMid, tMin, tMid, sMid, sMax, tMid, tMax, sMin, sMid, tMid, tMax, sMid, sMax);
}
}
}
return result;
}
isInfosWithBezier(bezier, tMin, tMax, sMin, sMax) {
tMin = 'number' == typeof tMin && isFinite(tMin) ? tMin : this.tMin;
tMax = 'number' == typeof tMax && isFinite(tMax) ? tMax : this.tMax;
sMin = 'number' == typeof sMin && isFinite(sMin) ? sMin : bezier.tMin;
sMax = 'number' == typeof sMax && isFinite(sMax) ? sMax : bezier.tMax;
assertf(() => tMin < tMax);
assertf(() => sMin < sMax);
const result = [];
const likeCurves = this.likeCurve(bezier), colinearCurves = this.isColinearTo(bezier);
if (likeCurves || colinearCurves) {
if (!likeCurves) {
// only colinear
// recalculate sMin and sMax so they are valid on this, from then on we can ignore bezier
sMin = this.pointT(bezier.at(sMin));
sMax = this.pointT(bezier.at(sMax));
}
tMin = Math.min(tMin, sMin);
tMax = Math.max(tMax, sMax);
const splits = fuzzyUniques(this.roots().concatenated().filter(isFinite).concat([tMin, tMax])).sort(MINUS);
//const aabbs = arrayFromFunction(splits.length - 1, i => this.getAABB(splits[i], splits[i + 1]))
Array.from(combinations(splits.length - 1)).forEach(({ i, j }) => {
// adjacent curves can't intersect
if (Math.abs(i - j) > 2) {
// console.log(splits[i], splits[i + 1], splits[j], splits[j + 1], aabbs[i], aabbs[j])
//findRecursive(splits[i], splits[i + 1], splits[j], splits[j + 1], aabbs[i], aabbs[j])
result.push(...Curve.ispsRecursive(this, splits[i], splits[i + 1], bezier, splits[j], splits[j + 1]));
}
});
}
else {
return Curve.ispsRecursive(this, tMin, tMax, bezier, sMin, sMax);
}
return result;
}
selfIntersectionsInfo() {
return this.isInfosWithBezier(this);
}
isInfosWithCurve(curve) {
if (curve instanceof L3) {
return this.isInfosWithLine(curve.anchor, curve.dir1, curve.tMin, curve.tMax);
}
if (curve instanceof BezierCurve) {
return this.isInfosWithBezier(curve);
}
return curve.isInfosWithCurve(this).map(({ tThis, tOther, p }) => ({ tThis: tOther, tOther: tThis, p }));
}
getAreaInDirSurface(dir1, surface, aT, bT) {
assertf(() => dir1.hasLength(1));
// INT[aT; bT] at(t) * dir1 * tangentAt(t).rejectedFrom(dir1) dt
const f = (t) => {
const tangent = this.tangentAt(t);
const at = this.at(t);
const outsideVector = tangent.cross(surface.normalP(at));
const sign = Math.sign(outsideVector.dot(dir1));
return at.dot(dir1) * tangent.rejected1Length(dir1) * sign;
//return this.at(t).dot(dir1) * tangent.minus(dir1.times(tangent.dot(dir1))).length()
};
const cx = (t) => {
const height = this.at(t).dot(dir1);
//console.log(t, this.at(t).minus(dir1.times(height / 2)).sce, f(t))
return this.at(t).minus(dir1.times(height / 2));
};
const area = gaussLegendreQuadrature24(f, aT, bT);
const x = V3.add.apply(undefined, arrayFromFunction(24, i => {
const t = aT + (gaussLegendre24Xs[i] + 1) / 2 * (bT - aT);
return cx(t).times(gaussLegendre24Weights[i] * f(t));
})).div(2 * (bT - aT) * area);
return { area: area, centroid: x };
}
magic(t0 = this.tMin, t1 = this.tMax, result = []) {
const splits = 20;
const ts = arrayFromFunction(splits, i => lerp(t0, t1, i / (splits - 1)));
const ps = ts.map(t => this.at(t));
const ns = ts.map(t => this.normalP(t).unit());
const f = (ns) => {
const ls = ts.map((t, i) => new L3(ps[i], ns[i].unit()));
const isInfos = arrayFromFunction(splits - 1, i => {
const j = i + 1;
const li = ls[i], lj = ls[j];
return li.infoClosestToLine(lj);
});
const a = isInfos.map(isInfo => isInfo.s - isInfo.t);
const centers = isInfos.map(isInfo => V3.lerp(isInfo.closest, isInfo.closest2, 0.5));
const b = arrayFromFunction(splits - 1, i => {
const tMid = lerp(ts[i], ts[i + 1], 0.5);
const pMid = this.at(tMid);
return Math.pow(pMid.distanceTo(centers[i]), 0.5);
});
return a.concat(b);
};
const startX = V3.packXY(ns);
const ff = (xs) => {
return f(V3.unpackXY(xs));
};
const x = new Vector(new Float64Array(startX));
for (let i = 0; i < 2; i++) {
const Fx = new Vector(new Float64Array(ff(x.v)));
console.log(Fx.v);
const jacobi = Matrix.jacobi(ff, x.v);
console.log('jacobi\n', jacobi.toString(x => '' + x));
const jacobiDependentRowIndexes = jacobi.getDependentRowIndexes();
//if (0 != jacobiDependentRowIndexes.length) {
// const error:any = new Error()
// error.jacobiDependentRowIndexes = jacobiDependentRowIndexes
// throw error
//}
const jacobiTranspose = jacobi.transposed();
console.log((jacobi.times(jacobiTranspose)).str);
console.log((jacobi.times(jacobiTranspose)).inversed().str);
const matrix = jacobiTranspose.times((jacobi.times(jacobiTranspose)).inversed());
const xDiff = matrix.timesVector(Fx);
x = x.minus(xDiff);
}
const ns2 = V3.unpackXY(x.v);
const ls2 = arrayFromFunction(splits, i => new L3(ps[i], ns2[i].unit()));
const curves = arrayFromFunction(splits - 1, i => {
const j = i + 1;
const li = ls2[i], lj = ls2[j];
const isInfo = li.infoClosestToLine(lj);
return EllipseCurve.circleForCenter2P(isInfo.closest, ps[i], ps[j], isInfo.s);
});
return curves;
}
magic2(t0 = this.tMin, t1 = this.tMax, result = []) {
const max3d = 0.01, eps = 0.01;
const a = this.at(t0), b = this.at(t1);
const aN = this.normalP(t0).unit(), bN = this.normalP(t1).unit();
const aL = new L3(a, aN), bL = new L3(b, bN);
const isInfo = aL.infoClosestToLine(bL);
if (isInfo.s < 0 || isInfo.t < 0
|| isInfo.distance > max3d
|| !eq(isInfo.s, isInfo.t, eps)) {
}
else {
const centerPoint = V3.lerp(isInfo.closest, isInfo.closest2, 0.5);
const testT1 = lerp(t0, t1, 1 / 2), testP1 = this.at(testT1);
const testT2 = lerp(t0, t1, 2 / 3), testP2 = this.at(testT2);
const radius = (isInfo.s + isInfo.t) / 2;
if (eq(centerPoint.distanceTo(testP1), radius, eps)) {
const newCurve = EllipseCurve.circleForCenter2P(centerPoint, a, b, radius);
result.push(newCurve);
return result;
}
}
const tMid = (t0 + t1) / 2;
this.magic(t0, tMid, result);
this.magic(tMid, t1, result);
return result;
}
}
/**
* https://en.wikipedia.org/wiki/Cubic_function#/media/File:Graph_of_cubic_polynomial.svg
*/
BezierCurve.EX2D = BezierCurve.graphXY(2, -3, -3, 2);
BezierCurve.EX3D = new BezierCurve(V3.O, V(-0.1, -1, 1), V(1.1, 1, 1), V3.X);
BezierCurve.QUARTER_CIRCLE = BezierCurve.approximateUnitArc(PI$3 / 2);
BezierCurve.prototype.hlol = Curve.hlol++;
BezierCurve.prototype.tIncrement = 1 / 80;
const { PI: PI$4, cos: cos$2, sin: sin$2, min: min$1, max: max$1, tan, sign: sign$2, ceil: ceil$2, floor: floor$2, abs: abs$4, sqrt, pow: pow$1, atan2, round } = Math;
class EllipseCurve extends XiEtaCurve {
constructor(center, f1, f2, tMin = -PI$4, tMax = PI$4) {
super(center, f1, f2, tMin, tMax);
assert(EllipseCurve.isValidT(tMin));
assert(EllipseCurve.isValidT(tMax));
}
static isValidT(t) {
return -Math.PI <= t && t <= Math.PI;
}
static XYLCValid(pLC) {
return eq(1, pLC.lengthXY());
}
/**
* @param hint +-PI, whichever is correct
*/
static XYLCPointT(pLC, hint) {
const angle = pLC.angleXY();
if (angle < -Math.PI + NLA_PRECISION || angle > Math.PI - NLA_PRECISION) {
assert(isFinite(hint));
return Math.sign(hint) * Math.PI;
}
return angle;
}
static magic(a, b, c) {
const isLC = intersectionUnitCircleLine2(a, b, c);
return isLC.map(([xi, eta]) => Math.atan2(eta, xi));
}
static unitIsInfosWithLine(anchorLC, dirLC, anchorWC, dirWC) {
// ell: x² + y² = 1 = p²
// line(t) = anchor + t dir
// anchor² - 1 + 2 t dir anchor + t² dir² = 0
const pqDiv = dirLC.dot(dirLC);
const lineTs = pqFormula(2 * dirLC.dot(anchorLC) / pqDiv, (anchorLC.dot(anchorLC) - 1) / pqDiv);
return lineTs.map(tOther => ({
tThis: Math.atan2(anchorLC.y + tOther * dirLC.y, anchorLC.x + tOther * dirLC.x),
tOther: tOther,
p: L3.at(anchorWC, dirWC, tOther),
}));
}
/**
* Returns a new EllipseCurve representing a circle parallel to the XY-plane.`
*/
static circle(radius, center = V3.O) {
return new EllipseCurve(center, new V3(radius, 0, 0), new V3(0, radius, 0));
}
static circleForCenter2P(center, a, b, radius) {
const f1 = center.to(a);
const normal = f1.cross(center.to(b));
const f2 = normal.cross(f1).toLength(f1.length());
const tMax = f1.angleTo(center.to(b));
return new EllipseCurve(center, f1, f2, 0, tMax);
}
// TODO: there'S alsoa commented out test
getVolZAnd(dir1, tStart, tEnd) {
// let p = at(t)
// integrate area [p -> plane.projectPoint(p)] to x axis...
// INTEGRATE[tStart, tEnd] fp(this.at(t)) dt
function fp(p) {
const p0ToP = dir1.times(dir1.dot(p));
const area = p0ToP.lengthXY() * (p.z - p0ToP.z / 2);
return area;
}
const f = (t) => fp(this.at(t)) * this.tangentAt(t).cross(this.normal).unit().z;
return { volume: glqInSteps(f, tStart, tEnd, 4), centroid: undefined };
}
getAreaInDir(right, up, tStart, tEnd) {
//assertf(() => tStart < tEnd)
assertf(() => right.isPerpendicularTo(this.normal));
assertf(() => up.isPerpendicularTo(this.normal));
//assertf(() => EllipseCurve.isValidT(tStart), tStart)
//assertf(() => EllipseCurve.isValidT(tEnd), tEnd)
const upLC = this.inverseMatrix.transformVector(up);
const rightLC = upLC.cross(V3.Z);
const normTStart = tStart - rightLC.angleXY();
const normTEnd = tEnd - rightLC.angleXY();
const transformedOriginY = this.inverseMatrix.getTranslation().dot(upLC.unit());
//console.log(upLC.str, rightLC.str, normTStart, normTEnd, 'upLC.length()', upLC.length())
//console.log('transformedOriginY', transformedOriginY)
//assertf(() => upLC.hasLength(1), upLC.length())
function fArea(t) { return (t - Math.sin(t) * Math.cos(t)) / 2; }
// for the centroid, we want
// cx = 1 / area * INTEGRAL[cos(t); PI/2] x * f(x) dx
// cx = 1 / area * INTEGRAL[cos(t); PI/2] x * sqrt(1 - x²) dx
// cx = 1 / area * INTEGRAL[cos(0); cos(t)] x * -sqrt(1 - x²) dx
// ...
// cx = 1 / area * INTEGRAL[0; t] cos(t) * sin²(t) dt // WA
// cx = 1 / area * (sin^3(t) / 3)[0; t]
function cxTimesArea(t) { return Math.pow(Math.sin(t), 3) / 3; }
// cy = 1 / area * INTEGRAL[cos(t); PI/2] f²(x) / 2 dx
// cy = 1 / area * INTEGRAL[cos(0); cos(t)] -(1 - x²) / 2 dx
// cy = 1 / area * INTEGRAL[0; t] (cos²(t) - 1) * -sin(t) / 2 dt
// cy = 1 / area * (cos (3 * t) - 9 * cos(t)) / 24 )[0; t]
function cyTimesArea(t) { return (Math.cos(3 * t) - 9 * Math.cos(t)) / 24; }
const restArea = -transformedOriginY * (-Math.cos(normTEnd) + Math.cos(normTStart));
const area = fArea(normTEnd) - fArea(normTStart) + restArea;
const cxt = (cxTimesArea(normTEnd) - cxTimesArea(normTStart) + -transformedOriginY * (-Math.cos(normTEnd) - Math.cos(normTStart)) / 2 * restArea) / area;
const cyt = (cyTimesArea(normTEnd) - cyTimesArea(normTStart) - -transformedOriginY / 2 * restArea) / area;
const factor = this.matrix.xyAreaFactor(); // * upLC.length()
//console.log('fctor', factor, 'area', area, 'resultarea', area* factor)
assert(!eq0(factor));
return {
area: area * factor,
centroid: this.matrix.transformPoint(M4.rotateZ(rightLC.angleXY()).transformPoint(new V3(cxt, cyt, 0))),
};
}
at(t) {
// = center + f1 cos t + f2 sin t
return this.center.plus(this.f1.times(Math.cos(t))).plus(this.f2.times(Math.sin(t)));
}
tangentAt(t) {
assertNumbers(t);
// f2 cos(t) - f1 sin(t)
return this.f2.times(Math.cos(t)).minus(this.f1.times(Math.sin(t)));
}
ddt(t) {
assertNumbers(t);
return this.f2.times(-Math.sin(t)).minus(this.f1.times(Math.cos(t)));
}
tangentAt2(xi, eta) {
return this.f2.times(xi).minus(this.f1.times(eta));
}
isCircular() {
return eq(this.f1.length(), this.f2.length()) && this.f1.isPerpendicularTo(this.f2);
}
reversed() {
return new this.constructor(this.center, this.f1, this.f2.negated(), -this.tMax, -this.tMin);
}
isColinearTo(curve) {
if (!hasConstructor(curve, EllipseCurve))
return false;
if (!this.center.like(curve.center)) {
return false;
}
if (this == curve) {
return true;
}
if (this.isCircular()) {
return curve.isCircular() && eq(this.f1.length(), curve.f1.length()) && this.normal.isParallelTo(curve.normal);
}
else {
let { f1: f1, f2: f2 } = this.rightAngled(), { f1: c1, f2: c2 } = curve.rightAngled();
if (f1.length() > f2.length()) {
[f1, f2] = [f2, f1];
}
if (c1.length() > c2.length()) {
[c1, c2] = [c2, c1];
}
return eq(f1.squared(), Math.abs(f1.dot(c1)))
&& eq(f2.squared(), Math.abs(f2.dot(c2)));
}
}
eccentricity() {
const mainAxes = this.rightAngled();
const f1length = mainAxes.f1.length(), f2length = mainAxes.f1.length();
const [a, b] = f1length > f2length ? [f1length, f2length] : [f2length, f1length];
return Math.sqrt(1 - b * b / a / a);
}
circumference() {
return this.arcLength(-Math.PI, Math.PI);
}
arcLength(startT, endT, steps) {
assert(startT < endT, 'startT < endT');
if (this.isCircular()) {
return this.f1.length() * (endT - startT);
}
return super.arcLength(startT, endT, steps);
}
circumferenceApproximate() {
// approximate circumference by Ramanujan
// https://en.wikipedia.org/wiki/Ellipse#Circumference
const { f1, f2 } = this.rightAngled(), a = f1.length(), b = f2.length();
const h = Math.pow((a - b), 2) / Math.pow((a + b), 2);
return Math.PI * (a + b) * (1 + 3 * h / (10 + Math.sqrt(4 - 3 * h)));
}
rightAngled() {
const f1 = this.f1, f2 = this.f2, a = f1.dot(f2), b = f2.squared() - f1.squared();
if (eq0(a)) {
return this;
}
const g1 = 2 * a, g2 = b + Math.sqrt(b * b + 4 * a * a);
const { x1: xi, y1: eta } = intersectionUnitCircleLine(g1, g2, 0);
return new EllipseCurve(this.center, f1.times(xi).plus(f2.times(eta)), f1.times(-eta).plus(f2.times(xi)));
}
isInfosWithEllipse(ellipse) {
if (this.normal.isParallelTo(ellipse.normal) && eq0(this.center.minus(ellipse.center).dot(ellipse.normal))) {
// ellipses are coplanar
const ellipseLCRA = ellipse.transform(this.inverseMatrix).rightAngled();
const r1 = ellipseLCRA.f1.lengthXY(), r2 = ellipseLCRA.f2.lengthXY(), centerDist = ellipseLCRA.center.lengthXY();
const rMin = min$1(r1, r2), rMax = max$1(r1, r2);
if (lt(centerDist + rMax, 1) || // entirely inside unit circle
lt(1, centerDist - rMax) || // entirely outside unit circle
lt(1, rMin - centerDist) || // contains unit circle
eq(1, r1) && eq(1, r2) && eq0(centerDist) // also unit circle, return no IS
) {
return [];
}
const f = (t) => ellipseLCRA.at(t).lengthXY() - 1;
const df = (t) => ellipseLCRA.at(t).xy().dot(ellipseLCRA.tangentAt(t)) / ellipseLCRA.at(t).lengthXY();
checkDerivate(f, df, -PI$4, PI$4, 1);
const ts = [];
const tsvs = arrayRange(-4 / 5 * PI$4, PI$4, PI$4 / 4).map(startT => [startT, df(startT), newtonIterateSmart(f, startT, 16, df, 1e-4), f(newtonIterateSmart(f, startT, 16, df, 1e-4))]);
for (let startT = -4 / 5 * PI$4; startT < PI$4; startT += PI$4 / 4) {
let t = newtonIterateSmart(f, startT, 16, df, 1e-4);
le(t, -PI$4) && (t += TAU);
assert(!isNaN(t));
if (ellipseLCRA.isValidT(t) && eq0(f(t)) && !ts.some(r => eq(t, r))) {
ts.push(t);
}
}
return ts.map(raT => {
const p = this.matrix.transformPoint(ellipseLCRA.at(raT));
return { tThis: this.pointT(p), tOther: ellipse.pointT(p, PI$4), p };
});
//const angle = ellipseLCRA.f1.angleXY()
//const aSqr = ellipseLCRA.f1.squared(), bSqr = ellipseLCRA.f2.squared()
//const a = Math.sqrt(aSqr), b = Math.sqrt(bSqr)
//const {x: centerX, y: centerY} = ellipseLCRA.center
//const rotCenterX = centerX * Math.cos(-angle) + centerY * -Math.sin(-angle)
//const rotCenterY = centerX * Math.sin(-angle) + centerY * Math.cos(-angle)
//const rotCenter = V(rotCenterX, rotCenterY)
//const f = t => {
// const lex = Math.cos(t) - rotCenterX, ley = Math.sin(t) - rotCenterY
// return lex * lex / aSqr + ley * ley / bSqr - 1
//}
//const f2 = (x, y) => (x * x + y * y - 1)
//const f3 = (x, y) => ((x - rotCenterX) * (x - rotCenterX) / aSqr + (y - rotCenterY) * (y - rotCenterY) /
// bSqr - 1) const results = [] const resetMatrix = this.matrix.times(M4.rotateZ(angle)) for (let startT =
// Math.PI / 4; startT < 2 * Math.PI; startT += Math.PI / 2) { const startP = EllipseCurve.XY.at(startT)
// const p = newtonIterate2d(f3, f2, startP.x, startP.y, 10) if (p && !results.some(r => r.like(p))) {
// results.push(p) } } const rotEl = new EllipseCurve(rotCenter, V(a, 0, 0), V(0, b, 0)) return
// results.map(pLC => { const p = resetMatrix.transformPoint(pLC) return {tThis: this.pointT(p, PI),
// tOther: ellipse.pointT(p, PI), p} })
}
else {
return this.isTsWithPlane(ellipse.getPlane()).mapFilter(t => {
const p = this.at(t);
if (ellipse.containsPoint(p)) {
return { tThis: t, tOther: ellipse.pointT(p), p };
}
});
}
}
isInfosWithCurve(curve) {
if (curve instanceof EllipseCurve) {
return this.isInfosWithEllipse(curve);
}
return super.isInfosWithCurve(curve);
}
roots() {
// tangent(t) = f2 cos t - f1 sin t
// solve for each dimension separately
// tangent(eta, xi) = f2 eta - f1 xi
return arrayFromFunction(3, dim => {
const a = this.f2.e(dim), b = -this.f1.e(dim);
const { x1, y1, x2, y2 } = intersectionUnitCircleLine(a, b, 0);
return [Math.atan2(y1, x1), Math.atan2(y2, x2)];
});
}
closestTToPoint(p, tStart) {
// (at(t) - p) * tangentAt(t) = 0
// (xi f1 + eta f2 + q) * (xi f2 - eta f1) = 0
// xi eta (f2^2-f1^2) + xi f2 q - eta² f1 f2 + xi² f1 f2 - eta f1 q = 0
// (xi² - eta²) f1 f2 + xi eta (f2^2-f1^2) + xi f2 q - eta f1 q = 0
// atan2 of p is a good first approximation for the searched t
const startT = this.inverseMatrix.transformPoint(p).angleXY();
const pRelCenter = p.minus(this.center);
const f = (t) => this.tangentAt(t).dot(this.f1.times(Math.cos(t)).plus(this.f2.times(Math.sin(t))).minus(pRelCenter));
return newtonIterate1d(f, startT);
}
area() {
// see
// https://upload.wikimedia.org/wikipedia/commons/thumb/4/4e/Cross_product_parallelogram.svg/220px-Cross_product_parallelogram.svg.png
return Math.PI * this.f1.cross(this.f2).length();
}
angleToT(phi) {
// atan2(y, x) = phi
const phiDir = this.f1.unit().times(Math.cos(phi)).plus(this.f2.rejectedFrom(this.f1).unit().times(Math.sin(phi)));
const dirLC = this.inverseMatrix.transformVector(phiDir);
return dirLC.angleXY();
}
}
EllipseCurve.XY = new EllipseCurve(V3.O, V3.X, V3.Y);
EllipseCurve.prototype.hlol = Curve.hlol++;
EllipseCurve.prototype.tIncrement = 2 * Math.PI / (4 * 800);
const { PI: PI$5, cos: cos$3, sin: sin$3, min: min$2, max: max$2, tan: tan$1, sign: sign$3, ceil: ceil$3, floor: floor$3, abs: abs$5, sqrt: sqrt$1, pow: pow$2, atan2: atan2$1, round: round$1 } = Math;
/**
* x² - y² = 1
*
*/
class HyperbolaCurve extends XiEtaCurve {
constructor(center, f1, f2, tMin = -7, tMax = 7) {
super(center, f1, f2, tMin, tMax);
}
static XYLCValid(pLC) {
return pLC.x > 0 && eq(1, pLC.x * pLC.x - pLC.y * pLC.y);
}
static XYLCPointT(pLC) {
return Math.asinh(pLC.y);
}
/**
* http://www.wolframalpha.com/input/?i=x%C2%B2-y%C2%B2%3D1,ax%2Bby%3Dc
* Minor empiric test shows asinh(eta) consistently gets more accurate results than atanh(eta/xi)
*/
static magic(a, b, c) {
if (eq0(b)) {
const sqrtVal = snap0(Math.pow(c, 2) / Math.pow(a, 2) - 1);
if (sqrtVal < 0 || c * a < 0) {
return [];
}
else if (sqrtVal == 0) {
return [0];
}
const eta1 = Math.sqrt(sqrtVal);
return [-Math.asinh(eta1), Math.asinh(eta1)];
}
else if (eq(abs$5(a), abs$5(b))) {
if (le(c * a, 0)) {
return [];
}
const eta = sign$3(a * b) * (Math.pow(c, 2) - Math.pow(a, 2)) / 2 / a / c;
return [Math.asinh(eta)];
}
else {
const sqrtVal = snap0(Math.pow(b, 2) * (-(Math.pow(a, 2)) + Math.pow(b, 2) + Math.pow(c, 2)));
if (sqrtVal < 0) {
return [];
}
const xi1 = (a * c - Math.sqrt(sqrtVal)) / (Math.pow(a, 2) - Math.pow(b, 2));
const xi2 = (a * c + Math.sqrt(sqrtVal)) / (Math.pow(a, 2) - Math.pow(b, 2));
const eta1 = (Math.pow(b, 2) * c - a * Math.sqrt(sqrtVal)) / (b * (Math.pow(b, 2) - Math.pow(a, 2)));
const eta2 = (Math.pow(b, 2) * c + a * Math.sqrt(sqrtVal)) / (b * (Math.pow(b, 2) - Math.pow(a, 2)));
return [xi1 > 0 && Math.asinh(eta1), xi2 > 0 && Math.asinh(eta2)].filter((x) => x !== false);
}
}
at(t) {
assertNumbers(t);
// = center + f1 cosh t + f2 sinh t
return this.center.plus(this.f1.times(Math.cosh(t))).plus(this.f2.times(Math.sinh(t)));
}
tangentAt(t) {
assertNumbers(t);
// = f1 sinh t + f2 cosh t
return this.f1.times(Math.sinh(t)).plus(this.f2.times(Math.cosh(t)));
}
tangentAt2(xi, eta) {
assertNumbers(xi, eta);
// = f1 eta + f2 xi
return this.f1.times(eta).plus(this.f2.times(xi));
}
ddt(t) {
assertNumbers(t);
return this.f1.times(Math.cosh(t)).plus(this.f2.times(Math.sinh(t)));
}
isColinearTo(curve) {
if (!hasConstructor(curve, HyperbolaCurve))
return false;
if (!curve.center || !this.center.like(curve.center)) {
return false;
}
if (this === curve) {
return true;
}
const { f1: f1, f2: f2 } = this.rightAngled(), { f1: c1, f2: c2 } = curve.rightAngled();
return eq(f1.squared(), Math.abs(f1.dot(c1)))
&& eq(f2.squared(), Math.abs(f2.dot(c2)));
}
reversed() {
return new HyperbolaCurve(this.center, this.f1, this.f2.negated(), -this.tMax, -this.tMin);
}
rightAngled() {
const f1 = this.f1, f2 = this.f2, a = f1.dot(f2), b = f2.squared() + f1.squared();
if (eq0(a)) {
return this;
}
const g1 = 2 * a, g2 = b + Math.sqrt(b * b - 4 * a * a);
const { x1: xi, y1: eta } = intersectionUnitHyperbolaLine(g1, g2, 0);
return new HyperbolaCurve(this.center, f1.times(xi).plus(f2.times(eta)), f1.times(eta).plus(f2.times(xi)));
}
eccentricity() {
const mainAxes = this.rightAngled();
const f1length = mainAxes.f1.length(), f2length = mainAxes.f1.length();
const [a, b] = f1length > f2length ? [f1length, f2length] : [f2length, f1length];
return Math.sqrt(1 + b * b / a / a);
}
roots() {
// tangent(t) = f1 sinh t + f2 cosh t = 0
// tangentAt2(xi, eta) = f1 eta + f2 xi = V3.O
// xi² - eta² = 1 (by def for hyperbola)
return arrayFromFunction(3, dim => {
const a = this.f2.e(dim), b = this.f1.e(dim);
return HyperbolaCurve.magic(a, b, 0);
});
}
}
HyperbolaCurve.XY = new HyperbolaCurve(V3.O, V3.X, V3.Y);
HyperbolaCurve.prototype.tIncrement = PI$5 / 16;
class L3 extends Curve {
constructor(anchor, // line anchor
dir1, // normalized line dir
tMin = -4096, tMax = 4096) {
super(tMin, tMax);
this.anchor = anchor;
this.dir1 = dir1;
assertVectors(anchor, dir1);
assert(dir1.hasLength(1), 'dir must be unit' + dir1);
assertf(() => !Number.isNaN(anchor.x));
}
static throughPoints(anchor, b, tMin, tMax) {
return new L3(anchor, b.minus(anchor).unit(), tMin, tMax);
}
static pointT(anchor, dir, x) {
assertVectors(anchor, dir, x);
return x.minus(anchor).dot(dir) / dir.squared();
}
static at(anchor, dir, t) {
return anchor.plus(dir.times(t));
}
static fromPlanes(p1, p2) {
assertInst(P3, p1, p2);
const dir = p1.normal1.cross(p2.normal1);
const length = dir.length();
if (length < 1e-10) {
throw new Error('Parallel planes');
}
return p1.intersectionWithPlane(p2);
}
static containsPoint(anchor, dir, p) {
const closestT = L3.pointT(anchor, dir, p);
const distance = L3.at(anchor, dir, closestT).distanceTo(p);
return eq0(distance);
}
addToMesh(mesh, res = 4, radius = 0, pointStep = 1, tMin = this.tMin, tMax = this.tMax) {
const baseNormals = arrayFromFunction(res, i => V3.polar(1, TAU * i / res));
const baseVertices = arrayFromFunction(res, i => V3.polar(radius, TAU * i / res));
for (let i = 0; i <= 1; i += 1) {
const start = mesh.vertices.length;
if (0 !== i) {
for (let j = 0; j < res; j++) {
pushQuad(mesh.TRIANGLES, true, start - res + j, start + j, start - res + (j + 1) % res, start + (j + 1) % res);
}
}
const t = 0 == i ? tMin : tMax;
const point = this.at(t), tangent = this.dir1, x = this.dir1.getPerpendicular();
const matrix = M4.forSys(x, this.dir1.cross(x), this.dir1, point);
mesh.normals.push(...matrix.transformedVectors(baseNormals));
mesh.vertices.push(...matrix.transformedPoints(baseVertices));
}
}
roots() {
return [[], [], []];
}
containsPoint(p) {
assertVectors(p);
const dist = this.distanceToPoint(p);
assertNumbers(dist);
return eq0(dist);
}
likeCurve(curve) {
return this == curve ||
hasConstructor(curve, L3)
&& this.anchor.like(curve.anchor)
&& this.dir1.like(curve.dir1);
}
equals(obj) {
return this == obj ||
Object.getPrototypeOf(obj) == L3.prototype
&& this.anchor.equals(obj.anchor)
&& this.dir1.equals(obj.dir1);
}
isColinearTo(obj) {
return obj instanceof L3
&& this.containsPoint(obj.anchor)
&& eq(1, Math.abs(this.dir1.dot(obj.dir1)));
}
distanceToLine(line) {
assertInst(L3, line);
if (this.isParallelToLine(line)) {
return this.distanceToPoint(line.anchor);
}
const dirCross1 = this.dir1.cross(line.dir1).unit();
const anchorDiff = this.anchor.minus(line.anchor);
return Math.abs(anchorDiff.dot(dirCross1));
}
distanceToPoint(x) {
assertVectors(x);
// See http://mathworld.wolfram.com/Point-LineDistance3-Dimensional.html
const t = x.minus(this.anchor).dot(this.dir1);
return this.at(t).distanceTo(x);
//return x.minus(this.anchor).cross(x.minus(this.anchor.plus(this.dir1))).length()
}
asSegmentDistanceToPoint(x, sStart, sEnd) {
let t = x.minus(this.anchor).dot(this.dir1);
t = clamp(t, sStart, sEnd);
return this.at(t).minus(x).length();
}
asSegmentDistanceToLine(line, sStart, sEnd) {
assertInst(L3, line);
const dirCross = this.dir1.cross(line.dir1);
const div = dirCross.squared();
if (eq0(div)) {
return undefined;
} // lines parallel
const anchorDiff = line.anchor.minus(this.anchor);
// check if distance is zero (see also L3.distanceToLine)
if (!eq0(anchorDiff.dot(dirCross.unit()))) {
return undefined;
}
let t = this.infoClosestToLine(line).t;
t = clamp(t, sStart, sEnd);
return this.at(clamp(t, sStart, sEnd));
}
at(t) {
assertNumbers(t);
return this.anchor.plus(this.dir1.times(t));
}
/**
* This function returns lambda for a given point x
*
* Every point x on this line is described by the equation
* x = this.anchor + lambda * this.dir1 | - this.anchor
* x - this.anchor = lambda * this.dir1 | DOT this.dir1
* (x - this.anchor) DOT this.dir1 = lambda (dir1² is 1 as |dir1| == 1)
*
* @param x
* @returns
*/
pointT(x) {
assertVectors(x);
const t = x.minus(this.anchor).dot(this.dir1);
return t;
}
/**
* Returns true if the line is parallel (this.dir = line.dir || this.dir = -line.dir) to the argument.
*/
isParallelToLine(line) {
assertInst(L3, line);
// we know that 1 == this.dir1.length() == line.dir1.length(), we can check for parallelity simpler than
// isParallelTo()
return eq(1, Math.abs(this.dir1.dot(line.dir1)));
}
angleToLine(line) {
assertInst(L3, line);
return this.dir1.angleTo(line.dir1);
}
/**
*
* @param line
* @returns {boolean} If the distance between the lines is zero
*/
intersectsLine(line) {
return eq0(this.distanceToLine(line));
}
isInfosWithCurve(curve) {
if (curve instanceof L3) {
const dirCross = this.dir1.cross(curve.dir1);
const div = dirCross.squared();
if (eq0(div)) {
// lines are parallel
return [];
}
const anchorDiff = curve.anchor.minus(this.anchor);
if (eq0(anchorDiff.dot(dirCross))) {
const tThis = anchorDiff.cross(curve.dir1).dot(dirCross) / div;
const tOther = anchorDiff.cross(this.dir1).dot(dirCross) / div;
const p = this.at(tThis);
return [{ tThis: tThis, tOther: tOther, p: p }];
}
return [];
}
throw new Error();
}
isInfoWithLine(line) {
// todo infos?
assertInst(L3, line);
const dirCross = this.dir1.cross(line.dir1);
const div = dirCross.squared();
if (eq0(div)) {
return undefined;
} // lines parallel
const anchorDiff = line.anchor.minus(this.anchor);
// check if distance is zero (see also L3.distanceToLine)
if (!eq0(anchorDiff.dot(dirCross.unit()))) {
return undefined;
}
const t = anchorDiff.cross(line.dir1).dot(dirCross) / div;
return this.at(t);
}
/**
* returns s and t with this.at(s) == line.at(t)
*/
intersectionLineST(line) {
// the two points on two lines the closest two each other are the ones whose
// connecting
// TODO Where does this come from?
// TODO: return value when no IS?
assertInst(L3, line);
const dirCross = this.dir1.cross(line.dir1);
const div = dirCross.squared();
const anchorDiff = line.anchor.minus(this.anchor);
const s = anchorDiff.cross(this.dir1).dot(dirCross) / div;
const t = anchorDiff.cross(line.dir1).dot(dirCross) / div;
return { s: s, t: t };
//console.log(segmentIntersectsRay, a, b, "ab", ab, "p", p, "dir", dir, s > 0 && t / div >= 0 && t / div <= 1,
// "s", s, "t", t, "div", div)
}
ddt(t) {
return V3.O;
}
getConstructorParameters() {
return [this.anchor, this.dir1];
}
closestTToPoint(p) {
// similar logic as pointT; we project the vector (anchor -> p) onto dir1, then add anchor back to it
const nearestT = p.minus(this.anchor).dot(this.dir1);
return nearestT;
}
infoClosestToLine(line) {
/*
line = a + s*b
this = c + t*d
(this - line) * b = 0
(this - line) * d = 0
(a + s*b - c - t*d) * b = 0
(a + s*b - c - t*d) * d = 0
(a - c + s*b - t*d) * b = 0
(a - c + s*b - t*d) * d = 0
(a - c)*b + (s*b - t*d)*b = 0
(a - c)*d + (s*b - t*d)*d = 0
(a - c)*b + s*(b*b) - t*(d*b) = 0
(a - c)*d + s*(b*d) - t*(d*d) = 0
s = (t*(d*b) - (a - c)*b) / (b*b)
=>
(a - c)*d + (t*(d*b) - (a - c)*b) / (b*b)*(b*d) - t*(d*d) = 0 | * (b*b)
(a - c)*d * (b*b) + (t*(d*b) - (a - c)*b)*(b*d) - t*(d*d) * (b*b) = 0
(a - c)*d * (b*b) + t*(d*b)*(b*d) - (a - c)*b*(b*d) - t*(d*d) * (b*b) = 0
t = ((a - c)*b*(b*d) - (a - c)*d * (b*b)) / ((d*b)*(b*d) - (d*d) * (b*b))
*/
if (this.isParallelToLine(line)) {
return { t: NaN, s: NaN, distance: this.distanceToLine(line) };
}
const a = line.anchor, b = line.dir1, c = this.anchor, d = this.dir1;
const bd = b.dot(d), bb = b.squared(), dd = d.squared(), amc = a.minus(c), divisor = bd * bd - dd * bb;
const t = (amc.dot(b) * bd - amc.dot(d) * bb) / divisor;
const s = (amc.dot(b) * dd - amc.dot(d) * bd) / divisor;
return {
t: t,
s: s,
closest: this.at(t),
closest2: line.at(s),
distance: this.at(t).distanceTo(line.at(s)),
};
}
intersectionWithPlane(plane) {
// plane: plane.normal1 * p = plane.w
// line: p=line.point + lambda * line.dir1
const lambda = (plane.w - plane.normal1.dot(this.anchor)) / plane.normal1.dot(this.dir1);
const point = this.anchor.plus(this.dir1.times(lambda));
return point;
}
tangentAt(t) {
return this.dir1;
}
isTWithPlane(plane) {
// plane: plane.normal1 * p = plane.w
// line: p=line.point + lambda * line.dir1
const div = plane.normal1.dot(this.dir1);
if (eq0(div))
return NaN;
const lambda = (plane.w - plane.normal1.dot(this.anchor)) / div;
return lambda;
}
reversed() {
return new L3(this.anchor, this.dir1.negated(), -this.tMax, -this.tMin);
}
isTsWithPlane(plane) {
return [this.isTWithPlane(plane)];
}
flipped() {
return new L3(this.anchor, this.dir1.negated());
}
transform(m4) {
const newAnchor = m4.transformPoint(this.anchor);
const newDir = m4.transformVector(this.dir1);
return new L3(newAnchor, newDir.unit(), this.tMin * newDir.length(), this.tMax * newDir.length());
}
hashCode() {
return this.anchor.hashCode() * 31 + this.dir1.hashCode();
}
}
L3.anchorDirection = (anchor, dir) => new L3(anchor, dir.unit());
L3.X = new L3(V3.O, V3.X);
L3.Y = new L3(V3.O, V3.Y);
L3.Z = new L3(V3.O, V3.Z);
L3.prototype.hlol = Curve.hlol++;
const { floor: floor$5, abs: abs$7, ceil: ceil$5, min: min$4, max: max$4 } = Math;
class PICurve$1 extends ImplicitCurve {
constructor(points, tangents, parametricSurface, implicitSurface, pmPoints, pmTangents, stepSize, dir = 1, generator, tMin, tMax) {
super(points, tangents, dir, generator, tMin, tMax);
this.parametricSurface = parametricSurface;
this.implicitSurface = implicitSurface;
this.pmPoints = pmPoints;
this.pmTangents = pmTangents;
this.stepSize = stepSize;
assert(Array.isArray(pmPoints));
assert(dir == 1);
assert(stepSize <= 1);
const pf = parametricSurface.pSTFunc();
const dpds = parametricSurface.dpds();
const dpdt = parametricSurface.dpdt();
const didp = implicitSurface.didp.bind(implicitSurface);
this.dids = (s, t) => didp(pf(s, t)).dot(dpds(s, t));
this.didt = (s, t) => didp(pf(s, t)).dot(dpdt(s, t));
for (let i = 0; i < points.length - 1; i++) {
assert(!points[i].equals(points[i + 1]));
//assert(parametricSurface.pST(pmPoints[i].x, pmPoints[i].y).equals(points[i]))
}
}
static forParametricStartEnd(ps, is, pmStart, pmEnd, stepSize = 0.02, startPMTangent, tMin, tMax) {
const pFunc = ps.pSTFunc(), iFunc = is.implicitFunction();
const dpds = ps.dpds();
const dpdt = ps.dpdt();
const didp = is.didp.bind(is);
const mf = MathFunctionR2R.forFFxFy((x, y) => iFunc(pFunc(x, y)), (s, t) => didp(pFunc(s, t)).dot(dpds(s, t)), (s, t) => didp(pFunc(s, t)).dot(dpdt(s, t)));
const { points, tangents } = followAlgorithm2d(mf, pmStart, stepSize, ps.bounds.bind(ps), pmEnd, startPMTangent);
return PICurve$1.forParametricPointsTangents(ps, is, points, tangents, stepSize, 1, tMin, tMax);
}
// assert(!startPoint.like(endPoint))
// assert(ParametricSurface.is(parametricSurface))
// assert(ImplicitSurface.is(implicitSurface))
// this.parametricSurface = parametricSurface
// this.implicitSurface = implicitSurface
// if (!startPoint) {
// const pmPoint = curvePoint(this.implicitCurve(), V(1, 1, 0))
// this.startPoint = this.parametricSurface.pSTFunc()(pmPoint.x, pmPoint.y)
//} else {
// this.startPoint = startPoint
//}
//this.endPoint = endPoint
//this.dir = dir
//this.isLoop = false
//try {
// this.calcPoints(startPoint, endPoint)
// this.startPoint = startPoint
// this.endPoint = endPoint
//} catch (e) {
// this.calcPoints(this.endPoint, this.startPoint)
// this.startPoint = endPoint
// this.endPoint = startPoint
//}
//this.tMin = 0
//this.tMax = this.points.length - 1
static forStartEnd(ps, is, start, end, stepSize = 0.02, startTangent, min, max) {
const startPM = ps.stP(start);
const dpds = ps.dpds()(startPM.x, startPM.y), dpdt = ps.dpdt()(startPM.x, startPM.y);
const startPMTangent = startTangent && M4.forSys(dpds, dpdt).inversed().transformVector(startTangent);
// assert(dpds.times(startPMTangent.x).plus(dpdt.times(startPMTangent.y)).like(startTangent))
const curve = PICurve$1.forParametricStartEnd(ps, is, startPM, ps.stP(end), stepSize, startPMTangent);
return curve.withBounds(min && curve.pointT(min), max && curve.pointT(max));
}
static forParametricPointsTangents(ps, is, pmPoints, pmTangents, stepSize, dir = 1, tMin, tMax) {
const pFunc = ps.pSTFunc(), iFunc = is.implicitFunction();
const dpds = ps.dpds();
const dpdt = ps.dpdt();
const points = pmPoints.map(({ x, y }) => pFunc(x, y));
const tangents = pmPoints.map(({ x: s, y: t }, i) => {
const ds = dpds(s, t);
const dt = dpdt(s, t);
return ds.times(pmTangents[i].x).plus(dt.times(pmTangents[i].y));
//const p = points[i]
//return cs.normalP(p).cross(ses.normalP(p))
// .toLength(ds.times(pmTangents[i].x).plus(dt.times(pmTangents[i].y)).length())
});
return new PICurve$1(points, tangents, ps, is, pmPoints, pmTangents, stepSize, dir, undefined, tMin, tMax);
}
getConstructorParameters() {
return [this.points, this.tangents,
this.parametricSurface, this.implicitSurface,
this.pmPoints, this.pmTangents,
this.stepSize, this.dir,
this.generator, this.tMin, this.tMax];
}
reversed() {
assertNever();
return new PICurve$1(this.parametricSurface, this.implicitSurface, this.endPoint, this.startPoint, -this.dir);
}
implicitCurve() {
const pF = this.parametricSurface.pSTFunc();
const iF = this.implicitSurface.implicitFunction();
return function (s, t) {
return iF(pF(s, t));
};
}
isColinearTo(curve) {
if (curve instanceof PICurve$1) {
if (this.equals(curve)) {
return true;
}
if (this.parametricSurface.isCoplanarTo(curve.parametricSurface) && this.implicitSurface.isCoplanarTo(curve.implicitSurface)) {
}
return false;
assertNever();
}
else {
return false;
}
}
//getVerticesNo0() {
//
// // TODO
// let start, end, arr
// if (!this.canon) {
// start = Math.floor(this.aT + 1)
// end = ceil(this.bT)
// arr = sliceCyclic(this.curve.points, start, end)
// } else {
// start = Math.floor(this.bT + 1)
// end = ceil(this.aT)
// arr = sliceCyclic(this.curve.points, start, end)
// console.log("this.canon", !!this.canon, arr.length, start, end, this.aT)
// arr.reverse()
// }
// arr.push(this.b)
// return arr
//}
containsPoint(p) {
assertVectors(p);
const t = this.pointT(p);
return !isNaN(t) && this.isValidT(t);
}
equals(obj) {
return Object.getPrototypeOf(obj) == PICurve$1.prototype
&& this.parametricSurface.equals(obj.parametricSurface)
&& this.implicitSurface.equals(obj.implicitSurface)
&& this.points[0].equals(obj.points[0])
&& this.tangents[0].equals(obj.tangents[0])
&& this.dir === obj.dir;
}
hashCode() {
let hashCode = 0;
hashCode = hashCode * 31 + this.parametricSurface.hashCode();
hashCode = hashCode * 31 + this.implicitSurface.hashCode();
hashCode = hashCode * 31 + this.points[0].hashCode();
hashCode = hashCode * 31 + this.tangents[0].hashCode();
return hashCode | 0;
}
tangentP(point) {
assertVectors(point);
assert(this.containsPoint(point), 'this.containsPoint(point)' + this.containsPoint(point));
const t = this.pointT(point);
return this.tangentAt(t);
}
tangentAt(t) {
return V3.lerp(this.tangents[floor$5(t)], this.tangents[ceil$5(t)], t % 1);
}
at(t) {
// assert(!isNaN(t))
// const pointParams = this.stT(t)
// const result = this.parametricSurface.pSTFunc()(pointParams.x, pointParams.y)
// // assert(eq(t, this.pointT(result)))
// return result
assert(!isNaN(t));
if (t % 1 == 0)
return this.points[t];
const startParams = V3.lerp(this.pmPoints[floor$5(t)], this.pmPoints[ceil$5(t)], t % 1);
return this.closestPointToParams(startParams);
}
stT(t) {
assert(!isNaN(t));
if (t % 1 == 0)
return this.points[t];
const startParams = V3.lerp(this.pmPoints[floor$5(t)], this.pmPoints[ceil$5(t)], t % 1);
return curvePoint(this.implicitCurve(), startParams, this.dids, this.didt);
}
closestTToPoint(p, tStart) {
return 0;
}
closestPointToParams(startParams) {
const pointParams = curvePoint(this.implicitCurve(), startParams, this.dids, this.didt);
return this.parametricSurface.pSTFunc()(pointParams.x, pointParams.y);
}
isTsWithSurface(surface) {
if (surface instanceof PlaneSurface$1) {
return this.isTsWithPlane(surface.plane);
}
else if (surface instanceof EllipsoidSurface || surface instanceof SemiEllipsoidSurface) {
const ps = this.parametricSurface, is = this.implicitSurface;
if (ps instanceof ProjectedCurveSurface && is instanceof SemiEllipsoidSurface) {
const iscs = is.isCurvesWithSurface(surface);
const points = iscs.flatMap(isc => isc.isTsWithSurface(ps).map(t => isc.at(t)));
const ts = fuzzyUniques(points.map(p => this.pointT(p)));
return ts.filter(t => !isNaN(t) && this.isValidT(t));
}
}
throw new Error();
}
isTsWithPlane(plane) {
assertInst(P3, plane);
const ps = this.parametricSurface, is = this.implicitSurface;
const pscs = ps.isCurvesWithPlane(plane);
const iscs = is.isCurvesWithPlane(plane);
const infos = iscs.flatMap(isc => pscs.flatMap(psc => isc.isInfosWithCurve(psc)));
const ts = fuzzyUniques(infos.map(info => this.pointT(info.p)));
return ts.filter(t => !isNaN(t) && this.isValidT(t));
}
pointT(p) {
assertVectors(p);
if (!this.parametricSurface.containsPoint(p) || !this.implicitSurface.containsPoint(p)) {
return NaN;
}
const pmPoint = this.parametricSurface.stPFunc()(p);
const ps = this.points, pmps = this.pmPoints;
let t = 0, prevDistance, pmDistance = pmPoint.distanceTo(pmps[0]);
while (pmDistance > abs$7(this.stepSize) && t < ps.length - 1) {
//console.log(t, pmps[t].$, pmDistance)
t = min$4(pmps.length - 1, t + max$4(1, Math.round(pmDistance / abs$7(this.stepSize) / 2 / 2)));
pmDistance = pmPoint.distanceTo(pmps[t]);
}
// if (t < this.pmPoints.length - 1 && pmDistance > pmPoint.distanceTo(pmps[t + 1])) {
// t++
// }
if (pmDistance > abs$7(this.stepSize) * 1.1) {
// p is not on this curve
return NaN;
}
if (t == ps.length - 1) {
t--;
}
if (ps[t].like(p))
return t;
if (ps[t + 1].like(p))
return t + 1;
const startT = t + V3.inverseLerp(ps[t], ps[t + 1], p);
if (startT)
return newtonIterate1d(t => this.at(t).distanceTo(p), startT, 2);
}
transform(m4) {
const dirFactor = m4.isMirroring() ? -1 : 1;
return PICurve$1.forStartEnd(this.parametricSurface.transform(m4), this.implicitSurface.transform(m4), m4.transformPoint(this.points[0]), m4.transformPoint(this.points.last), this.stepSize * dirFactor, m4.transformVector(this.tangents[0]), m4.transformPoint(this.at(this.tMin)), m4.transformPoint(this.at(this.tMax)));
//return PICurve.forParametricStartEnd(
// this.parametricSurface.transform(m4),
// this.implicitSurface.transform(m4),
// this.pmPoints[0],
// this.pmPoints.last,
// this.stepSize,
// this.dir,
// this.tMin,
// this.tMax)
// TODO: pass transformed points?
//return new PICurve(
// m4.transformedPoints(this.points),
// m4.transformedVectors(this.tangents),
// this.parametricSurface.transform(m4),
// this.implicitSurface.transform(m4),
// this.pmPoints,
// this.pmTangents,
//this.stepSize,
// this.dir,
//this.generator,
//this.tMin, this.tMax)
}
roots() {
const allTs = arrayRange(0, this.points.length);
return [allTs, allTs, allTs];
}
toSource(rounder = x => x) {
const result = callsce('PICurve.forParametricStartEnd', this.parametricSurface, this.implicitSurface, this.pmPoints[0], this.pmPoints.last, this.stepSize, this.pmTangents[0], this.tMin, this.tMax);
return result;
}
}
PICurve$1.prototype.tIncrement = 1;
class ParabolaCurve extends XiEtaCurve {
constructor(center, f1, f2, tMin = -10, tMax = 10) {
super(center, f1, f2, tMin, tMax);
}
static eccentricity() {
return 1;
}
static unitIsInfosWithLine(anchorLC, dirLC, anchorWC, dirWC) {
// para: x² = y
// line(t) = anchor + t dir
// (ax + t dx)² = ay + t dy
// ax² + t ax dx + t² dx² = ay + t dy
// t² dx² + t (ax dx + dy) + ay² + ay = 0
const pqDiv = Math.pow(dirLC.x, 2);
const lineTs = pqFormula((anchorLC.x * dirLC.x + dirLC.y) / pqDiv, (Math.pow(anchorLC.x, 2) + anchorLC.y) / pqDiv);
return lineTs.filter(tOther => le(0, anchorLC.y + tOther * dirLC.y))
.map(tOther => ({
tThis: dirLC.x * tOther + anchorLC.x,
tOther: tOther,
p: L3.at(anchorWC, dirWC, tOther),
}));
}
static magic(a, b, c) {
/*
solve system (5)/(6)
g1 * xi + g2 * eta = g3 (6)
g1 * xi + g2 * xi * xi = g3
xi² + xi * g1/g2 - g3/g2 = 0
*/
return pqFormula(a / b, -c / b);
}
static XYLCValid(pLC) {
return eq(Math.pow(pLC.x, 2), pLC.y);
}
static XYLCPointT(pLC) {
return pLC.x;
}
static quadratic(a, b, c) {
// (1 - t)² a + 2 * t * (1 - t) b + t² c
// (1 -2t +t²)a + (2t -2t²) b + t² c
// = t²(a - 2b + c) + t (-2a + 2b) + a
// (2t - 2) a + (1 - 2t) b + 2t c = t(2a + 2b - 2c) - 2a + b
// 2 a + -2 b + 2 c
const f2 = a.plus(c).minus(b.times(2));
const f1 = b.minus(a).times(2);
const center = a;
return new ParabolaCurve(center, f1, f2, 0, 1);
}
at(t) {
// center + f1 t + f2 t²
return this.center.plus(this.f1.times(t)).plus(this.f2.times(t * t));
}
tangentAt(t) {
assertNumbers(t);
// f1 + f2 2 t
return this.f1.plus(this.f2.times(2 * t));
}
ddt(t) {
assertNumbers(t);
return this.f2.times(2);
}
tangentAt2(xi, eta) {
assertNumbers(xi, eta);
return this.f1.plus(this.f2.times(2 * eta));
}
reversed() {
return new this.constructor(this.center, this.f1.negated(), this.f2, -this.tMax, -this.tMin);
}
/**
* tangent: f1 + 2 * t * f2 = 0
* t = -f1 / 2 / f2 (for individual dimensions)
*/
roots() {
const dimRoots = (dim) => eq0(this.f2.e(dim)) ? [] : [-this.f1.e(dim) / 2 / this.f2.e(dim)];
return arrayFromFunction(3, dimRoots);
}
isColinearTo(curve) {
if (!hasConstructor(curve, ParabolaCurve))
return false;
const thisRA = this.rightAngled(), curveRA = curve.rightAngled();
return thisRA.center.like(curveRA.center)
&& thisRA.f2.like(curveRA.f2)
&& thisRA.f1.likeOrReversed(curveRA.f1);
}
rightAngled() {
// looking for vertex of parabola
// this is the point where the tangent is perpendicular to the main axis (f2)
// tangent = f1 + f2 * 2 * t0
// f2 DOT (f1 + f2 * 2 * t0) == 0
// f1 DOT f2 + f2 DOT f2 * 2 * t0 == 0
// t0 == -(f1 DOT f2) / (f2 DOT f2 * 2)
const f1 = this.f1, f2 = this.f2;
const f1DOTf2 = f1.dot(f2);
if (eq0(f1DOTf2) && f1.hasLength(1)) {
return this;
}
const t0 = -f1DOTf2 / f2.squared() / 2;
// we need to rearange tMin/tMax
// tMin' = pointT(at(tMin)) =
const raCenter = this.at(t0);
const raF1 = this.tangentAt(t0), raF1Length = raF1.length(), raF11 = raF1.unit();
const repos = (t) => this.at(t).minus(raCenter).dot(raF11);
return new ParabolaCurve(raCenter, raF11, f2.div(Math.pow(raF1Length, 2)), repos(this.tMin), repos(this.tMax));
}
arcLength(startT, endT) {
let f1 = this.f1;
const f2 = this.f2;
const f1DOTf2 = f1.dot(f2);
let t0 = 0;
if (!eq0(f1DOTf2)) {
t0 = -f1DOTf2 / f2.squared() / 2;
f1 = f1.plus(f2.times(2 * t0));
}
const f1Length = f1.length();
const a = f2.length() / f1Length;
function F(x) {
return Math.asinh(a * 2 * x) / 4 / a + x * Math.sqrt(1 + a * a * 4 * x * x) / 2;
}
return f1Length * (F(endT - t0) - F(startT - t0));
}
asBezier() {
return BezierCurve.quadratic(this.at(-1), new L3(this.at(-1), this.tangentAt(-1).unit()).isInfoWithLine(new L3(this.at(1), this.tangentAt(1).unit())), this.at(1));
}
}
ParabolaCurve.XY = new ParabolaCurve(V3.O, V3.X, V3.Y);
ParabolaCurve.YZ = new ParabolaCurve(V3.O, V3.Y, V3.Z);
ParabolaCurve.ZX = new ParabolaCurve(V3.O, V3.Z, V3.X);
ParabolaCurve.prototype.tIncrement = 1 / 32;
const { PI: PI$7, cos: cos$5, sin: sin$5, min: min$5, max: max$5, tan: tan$3, sign: sign$5, ceil: ceil$6, floor: floor$6, abs: abs$8, sqrt: sqrt$3, pow: pow$4, atan2: atan2$3, round: round$3 } = Math;
class SemiEllipseCurve extends XiEtaCurve {
constructor(center, f1, f2, tMin = 0, tMax = PI$7) {
super(center, f1, f2, tMin, tMax);
assert(0 <= this.tMin && this.tMin < PI$7);
assert(0 < this.tMax && this.tMax <= PI$7);
}
static XYLCValid(pLC) {
const { x, y } = pLC;
return le(0, y) && eq0(Math.pow(x, 2) + Math.pow(y, 2) - 1);
}
static XYLCPointT(pLC) {
// assert(le(0, pLC.y))
const angle = Math.atan2(pLC.y, pLC.x);
return angle < -PI$7 / 2 ? angle + TAU : angle; // 0 ? (assert(eq0(angle) || eq(PI, abs(angle))), abs(angle)) :
// angle
}
static magic(a, b, c) {
const isLC = intersectionUnitCircleLine2(a, b, c);
const result = [];
for (const [xi, eta] of isLC) {
le(0, eta) && result.push(SemiEllipseCurve.XYLCPointT(new V3(xi, eta, 0)));
}
return result;
}
static unitIsInfosWithLine(anchorLC, dirLC, anchorWC, dirWC) {
// ell: x² + y² = 1 = p²
// line(t) = anchor + t dir
// anchor² - 1 + 2 t dir anchor + t² dir² = 0
const pqDiv = dirLC.squared();
const lineTs = pqFormula(2 * dirLC.dot(anchorLC) / pqDiv, (anchorLC.squared() - 1) / pqDiv);
return lineTs.filter(tOther => le(0, anchorLC.y + tOther * dirLC.y))
.map(tOther => ({
tThis: SemiEllipseCurve.XYLCPointT(dirLC.times(tOther).plus(anchorLC)),
tOther: tOther,
p: L3.at(anchorWC, dirWC, tOther),
}));
}
/**
* Returns a new SemiEllipseCurve representing a circle parallel to the XY-plane.`
*/
static semicircle(radius, center = V3.O) {
return new SemiEllipseCurve(center, new V3(radius, 0, 0), new V3(0, radius, 0));
}
static fromEllipse(curve, tMin, tMax) {
return [
tMin < 0 && new SemiEllipseCurve(curve.center, curve.f1.negated(), curve.f2.negated(), tMin + PI$7, min$5(0, tMax) + PI$7),
tMax > 0 && new SemiEllipseCurve(curve.center, curve.f1, curve.f2, max$5(0, tMin), tMax),
].filter(x => x);
}
getAreaInDir(right, up, tStart, tEnd) {
return EllipseCurve.prototype.getAreaInDir.call(this, right, up, tStart, tEnd);
}
at(t) {
assertNumbers(t);
//assert(this.isValidT(t))
// center + f1 cos t + f2 sin t
return this.center.plus(this.f1.times(Math.cos(t))).plus(this.f2.times(Math.sin(t)));
}
tangentAt(t) {
assertNumbers(t);
//assert(this.isValidT(t))
// f2 cos(t) - f1 sin(t)
return this.f2.times(Math.cos(t)).minus(this.f1.times(Math.sin(t)));
}
ddt(t) {
assertNumbers(t);
assert(this.isValidT(t));
return this.f2.times(-Math.sin(t)).minus(this.f1.times(Math.cos(t)));
}
isCircular() {
return eq(this.f1.length(), this.f2.length()) && this.f1.isPerpendicularTo(this.f2);
}
isColinearTo(curve) {
if (!((x) => x.constructor == this.constructor)(curve)) {
return false;
}
if (!hasConstructor(curve, SemiEllipseCurve))
return false;
if (!this.center.like(curve.center)) {
return false;
}
if (this == curve) {
return true;
}
if (this.isCircular()) {
return curve.isCircular() && eq(this.f1.length(), curve.f1.length()) && this.normal.isParallelTo(curve.normal);
}
else {
let { f1: f1, f2: f2 } = this.rightAngled(), { f1: c1, f2: c2 } = curve.rightAngled();
if (f1.length() > f2.length()) {
[f1, f2] = [f2, f1];
}
if (c1.length() > c2.length()) {
[c1, c2] = [c2, c1];
}
return eq(f1.squared(), Math.abs(f1.dot(c1)))
&& eq(f2.squared(), Math.abs(f2.dot(c2)));
}
}
isValidT(t) {
return le(0, t) && le(t, PI$7);
}
pointT(p) {
assertVectors(p);
assert(this.containsPoint(p));
const pLC = this.inverseMatrix.transformPoint(p);
const t = SemiEllipseCurve.XYLCPointT(pLC);
assert(this.isValidT(t));
return t;
}
reversed() {
return new SemiEllipseCurve(this.center, this.f1.negated(), this.f2, PI$7 - this.tMax, PI$7 - this.tMin);
}
eccentricity() {
const mainAxes = this.rightAngled();
const f1length = mainAxes.f1.length(), f2length = mainAxes.f1.length();
const [a, b] = f1length > f2length ? [f1length, f2length] : [f2length, f1length];
return Math.sqrt(1 - b * b / a / a);
}
circumference() {
return this.arcLength(-Math.PI, Math.PI);
}
arcLength(startT, endT, steps = 2) {
assert(startT < endT, 'startT < endT');
const f1Length = this.f1.length();
if (eq(f1Length, this.f2.length())) {
return f1Length * (endT - startT);
}
return super.arcLength(startT, endT, steps);
}
circumferenceApproximate() {
// approximate circumference by Ramanujan
// https://en.wikipedia.org/wiki/Ellipse#Circumference
const { f1, f2 } = this.rightAngled(), a = f1.length(), b = f2.length();
const h = (a - b) * (a - b) / (a + b) / (a + b); // (a - b)² / (a + b)²
return Math.PI * (a + b) * (1 + 3 * h / (10 + Math.sqrt(4 - 3 * h)));
}
/**
* Radii of the ellipse are described by
* q(phi) = f1 * cos(phi) + f2 * sin(phi)
* or q(xi, eta) = f1 * xi + f2 * eta (1) with the added condition
* xi² + eta² = 1 (2)
* we want to find the radius where the corresponding tangent is perpendicular
* tangent: q'(phi) = f1 * -sin(phi) + f2 * cos(phi)
* tangent: q'(xi, eta) = f1 * -eta + f2 * xi
* perpendicular when: q'(xi, eta) DOT q(xi, eta) = 0
* (f1 * -eta + f2 * xi) DOT (f1 * xi + f2 * eta) = 0
* DOT is distributive:
* f1² * (-eta * xi) + f1 * f2 * (-eta² + xi²) + f2² * (xi * eta) = 0
* (f2² - f1²) * (eta * xi) + f1 * f2 * (-eta² + xi²) = 0
* a * (xi² - eta²) + b * xi * eta = 0 (2)
* with a = f1 * f2, b = f2² - f1²
* => (xi/eta)² + xi/eta * b/a + 1 = 0 (divide by a * eta²)
* xi/eta = b/a/2 +- sqrt(b²/a²/4 - 1) | * 2*a*eta
* 2 * a * xi = eta * (b +- sqrt(b² - 4 * a²))
* g1 * xi - g2 * eta = 0 (3)
* with g1 = 2 * a, g2 = b +- sqrt(b² - 4 * a²)
* Solve (3), (2) with intersectionUnitCircleLine
*/
rightAngled() {
const f1 = this.f1, f2 = this.f2, a = f1.dot(f2), b = f2.squared() - f1.squared();
if (eq0(a)) {
return this;
}
const g1 = 2 * a, g2 = b + Math.sqrt(b * b + 4 * a * a);
const { x1: xi, y1: eta } = intersectionUnitCircleLine(g1, g2, 0);
const f1RA = f1.times(xi).plus(f2.times(eta));
const f2RA = f1.times(-eta).plus(f2.times(xi));
return new SemiEllipseCurve(this.center, f1RA, f2RA);
}
asEllipse() {
return new EllipseCurve(this.center, this.f1, this.f2, this.tMin, this.tMax);
}
isInfosWithEllipse(ellipse) {
if (this.normal.isParallelTo(ellipse.normal) && eq0(this.center.minus(ellipse.center).dot(ellipse.normal))) {
ellipse instanceof SemiEllipseCurve && (ellipse = ellipse.asEllipse());
return this.asEllipse().isInfosWithCurve(ellipse).filter(info => this.isValidT(info.tThis) && ellipse.isValidT(info.tOther));
}
else {
return this.isTsWithPlane(P3.normalOnAnchor(ellipse.normal.unit(), ellipse.center)).mapFilter(t => {
const p = this.at(t);
if (ellipse.containsPoint(p)) {
return { tThis: t, tOther: ellipse.pointT(p), p };
}
});
}
}
isInfosWithCurve(curve) {
if (curve instanceof SemiEllipseCurve || curve instanceof EllipseCurve) {
return this.isInfosWithEllipse(curve);
}
return super.isInfosWithCurve(curve);
}
roots() {
// tangent(t) = f2 cos t - f1 sin t
// solve for each dimension separately
// tangent(eta, xi) = f2 eta - f1 xi
return arrayFromFunction(3, dim => {
const a = this.f2.e(dim), b = -this.f1.e(dim);
const { x1, y1, x2, y2 } = intersectionUnitCircleLine(a, b, 0);
return [Math.atan2(y1, x1), Math.atan2(y2, x2)];
});
}
closestTToPoint(p) {
// (at(t) - p) * tangentAt(t) = 0
// (xi f1 + eta f2 + q) * (xi f2 - eta f1) = 0
// xi eta (f2^2-f1^2) + xi f2 q - eta² f1 f2 + xi² f1 f2 - eta f1 q = 0
// (xi² - eta²) f1 f2 + xi eta (f2^2-f1^2) + xi f2 q - eta f1 q = 0
// atan2 of p is a good first approximation for the searched t
const startT = this.inverseMatrix.transformPoint(p).angleXY();
const pRelCenter = p.minus(this.center);
const f = (t) => this.tangentAt(t).dot(this.f1.times(Math.cos(t)).plus(this.f2.times(Math.sin(t))).minus(pRelCenter));
return newtonIterate1d(f, startT);
}
area() {
// see
// https://upload.wikimedia.org/wikipedia/commons/thumb/4/4e/Cross_product_parallelogram.svg/220px-Cross_product_parallelogram.svg.png
return Math.PI * this.f1.cross(this.f2).length();
}
angleToT(phi) {
// atan2(y, x) = phi
const phiDir = this.f1.unit().times(Math.cos(phi)).plus(this.f2.rejectedFrom(this.f1).unit().times(Math.sin(phi)));
const localDir = this.inverseMatrix.transformVector(phiDir);
return localDir.angleXY();
}
}
SemiEllipseCurve.UNIT = new SemiEllipseCurve(V3.O, V3.X, V3.Y);
SemiEllipseCurve.prototype.hlol = Curve.hlol++;
SemiEllipseCurve.prototype.tIncrement = 2 * Math.PI / (4 * 32);
class P3 extends Transformable {
/**
* Oriented plane, i.e. splits R^3 in half, with one half being "in front" of the plane.
* Leads to multiple comparisons: isCoplanarToPlane returns if the plane occupies the same space,
* like returns if the plane occupies the same space and has the same orientation
*
* Points x on the plane fulfill the equation: normal1 DOT x = w
*
* @param normal1 unit plane normal1
* @param w signed (rel to normal1) distance from the origin
*/
constructor(normal1, w = 0) {
super();
this.normal1 = normal1;
this.w = w;
assertVectors(normal1);
assertNumbers(w);
assert(normal1.hasLength(1), 'normal1.hasLength(1)' + normal1);
}
get anchor() {
return this.normal1.times(this.w);
}
static throughPoints(a, b, c) {
assertVectors(a, b, c);
const n1 = b.minus(a).cross(c.minus(a)).unit();
return new P3(n1, n1.dot(a));
}
static normalOnAnchor(normal, anchor) {
assertVectors(normal, anchor);
const n1 = normal.unit();
return new P3(n1, n1.dot(anchor));
}
/**
* x/x0 + y/y0 + y/y0 = 1
*
*/
static forAxisIntercepts(x0, y0, z0) {
assertNumbers(x0, y0, z0);
const normal = new V3(1 / x0, 1 / y0, 1 / z0);
return new P3(normal.unit(), normal.length());
}
static forAnchorAndPlaneVectors(anchor, v0, v1) {
assertVectors(anchor, v0, v1);
return P3.normalOnAnchor(v0.cross(v1), anchor);
}
axisIntercepts() {
const w = this.w, n = this.normal1;
return new V3(w / n.x, w / n.y, w / n.z);
}
isCoplanarToPlane(plane) {
assertInst(P3, plane);
return this.like(plane) || this.likeFlipped(plane);
}
like(plane) {
assertInst(P3, plane);
return eq(this.w, plane.w) && this.normal1.like(plane.normal1);
}
likeFlipped(plane) {
assertInst(P3, plane);
return eq(this.w, -plane.w) && this.normal1.like(plane.normal1.negated());
}
/**
* True iff plane.normal1 is equal to this.normal1 or it's negation.
*
*/
isParallelToPlane(plane) {
assertInst(P3, plane);
return eq(1, Math.abs(this.normal1.dot(plane.normal1)));
}
isParallelToLine(line) {
assertInst(L3, line);
return eq0(this.normal1.dot(line.dir1));
}
isPerpendicularToLine(line) {
assertInst(L3, line);
// this.normal1 || line.dir1
return eq(1, Math.abs(this.normal1.dot(line.dir1)));
}
isPerpendicularToPlane(plane) {
assertInst(P3, plane);
return eq0(this.normal1.dot(plane.normal1));
}
toSource(rounder) {
return callsce('new P3', this.normal1, this.w);
}
translated(offset) {
return new P3(this.normal1, this.w + offset.dot(this.normal1));
}
transform(m4) {
const mirror = m4.isMirroring();
// get two vectors in the plane:
const u = this.normal1.getPerpendicular();
const v = u.cross(this.normal1);
// get 3 points in the plane:
const p1 = m4.transformPoint(this.anchor), p2 = m4.transformPoint(this.anchor.plus(v)), p3 = m4.transformPoint(this.anchor.plus(u));
// and create a new plane from the transformed points:
return P3.throughPoints(p1, !mirror ? p2 : p3, !mirror ? p3 : p2);
}
distanceToLine(line) {
assertInst(L3, line);
if (!this.isParallelToLine(line)) {
return this.distanceToPoint(line.anchor);
}
else {
return 0;
}
}
containsPoint(x) {
assertVectors(x);
return eq(this.w, this.normal1.dot(x));
}
containsLine(line) {
assertInst(L3, line);
return this.containsPoint(line.anchor) && this.isParallelToLine(line);
}
distanceToPointSigned(point) {
assertInst(V3, point);
return this.normal1.dot(point) - this.w;
}
distanceToPoint(point) {
assertInst(V3, point);
return Math.abs(this.normal1.dot(point) - this.w);
}
intersectionWithLine(line) {
return line.intersectionWithPlane(this);
}
intersectionWithPlane(plane) {
/*
this: n0 * x = w0
plane: n1 * x = w1
plane perpendicular to both which goes through origin:
n2 := n0 X x1
n2 * x = 0
*/
assertInst(P3, plane);
assert(!this.isParallelToPlane(plane), '!this.isParallelToPlane(plane)');
/*
var n0 = this.normal1, n1 = plane.normal1, n2 = n0.cross(n1).unit(), m = M4.forSys(n0, n1, n2)
var x0 = this.anchor, x1 = plane.anchor, x2 = V3.O
var p = n2.times(x2.dot(n2))
.plus(n1.cross(n2).times(x0.dot(n0)))
.plus(n2.cross(n0).times(x1.dot(n1)))
.div(m.determinant())
*/
const n0 = this.normal1, n1 = plane.normal1, n2 = n0.cross(n1).unit();
const p = M4.forRows(n0, n1, n2).inversed().transformVector(new V3(this.w, plane.w, 0));
return new L3(p, n2);
}
/**
* Returns the point in the plane closest to the given point
*
*/
projectedPoint(x) {
// See http://math.stackexchange.com/questions/444968/project-a-point-in-3d-on-a-given-plane
// p = x - ((x - planeAnchor) * normal1) * normal1
return x.minus(this.normal1.times(x.minus(this.anchor).dot(this.normal1)));
}
projectedVector(x) {
// See V3.rejectedFrom. Simplified, as this.normal1.length() == 1
return x.minus(this.normal1.times(x.dot(this.normal1)));
}
flipped() {
return new P3(this.normal1.negated(), -this.w);
}
containsCurve(curve) {
if (curve instanceof L3) {
return this.containsLine(curve);
}
else if (curve instanceof SemiEllipseCurve ||
curve instanceof EllipseCurve ||
curve instanceof HyperbolaCurve ||
curve instanceof ParabolaCurve) {
return this.containsPoint(curve.center) && this.normal1.isParallelTo(curve.normal);
}
else if (curve instanceof BezierCurve) {
return curve.points.every(p => this.containsPoint(p));
}
else {
throw new Error('' + curve);
}
}
hashCode() {
return this.normal1.hashCode() * 31 | 0 + floatHashCode(this.w);
}
}
P3.YZ = new P3(V3.X, 0);
P3.ZX = new P3(V3.Y, 0);
P3.XY = new P3(V3.Z, 0);
const { PI: PI$8, cos: cos$6, sin: sin$6, min: min$6, max: max$6, tan: tan$4, sign: sign$6, ceil: ceil$7, floor: floor$7, abs: abs$9, sqrt: sqrt$4, pow: pow$5, atan2: atan2$4, round: round$4 } = Math;
class Surface extends Transformable {
static loopContainsPointGeneral(loop, p, testLine, lineOut) {
const testPlane = P3.normalOnAnchor(lineOut, p);
// edges colinear to the testing line; these will always be counted as "inside" relative to the testing line
const colinearEdges = loop.map((edge) => edge.colinearToLine(testLine));
let inside = false;
function logIS(isP) {
const isT = testLine.pointT(isP);
if (eq0(isT)) {
return true;
}
else if (isT > 0) {
inside = !inside;
}
}
for (let edgeIndex = 0; edgeIndex < loop.length; edgeIndex++) {
const edge = loop[edgeIndex];
const nextEdgeIndex = (edgeIndex + 1) % loop.length, nextEdge = loop[nextEdgeIndex];
//console.log(edge.toSource()) {p:V(2, -2.102, 0),
if (colinearEdges[edgeIndex]) {
const lineAT = testLine.pointT(edge.a), lineBT = testLine.pointT(edge.b);
if (Math.min(lineAT, lineBT) <= NLA_PRECISION && -NLA_PRECISION <= Math.max(lineAT, lineBT)) {
return PointVsFace.ON_EDGE;
}
// edge colinear to intersection
const nextInside = colinearEdges[nextEdgeIndex] || dotCurve(lineOut, nextEdge.aDir, nextEdge.aDDT) < 0;
if (!nextInside) {
if (logIS(edge.b))
return PointVsFace.ON_EDGE;
}
}
else {
for (const edgeT of edge.edgeISTsWithPlane(testPlane)) {
if (edgeT == edge.bT) {
if (!testLine.containsPoint(edge.b))
continue;
// endpoint lies on intersection line
if (edge.b.like(p)) {
// TODO: refactor, dont check for different sides, just logIs everything
return PointVsFace.ON_EDGE;
}
const edgeInside = dotCurve(lineOut, edge.bDir, edge.bDDT) > 0;
const nextInside = colinearEdges[nextEdgeIndex] || dotCurve(lineOut, nextEdge.aDir, nextEdge.aDDT) < 0;
if (edgeInside != nextInside) {
if (logIS(edge.b))
return PointVsFace.ON_EDGE;
}
}
else if (edgeT != edge.aT) {
const p = edge.curve.at(edgeT);
if (!testLine.containsPoint(p))
continue;
// edge crosses line, neither starts nor ends on it
if (logIS(p))
return PointVsFace.ON_EDGE;
// TODO: tangents?
}
}
}
}
return inside ? PointVsFace.INSIDE : PointVsFace.OUTSIDE;
}
toString() {
return this.toSource();
}
toSource(rounder = x => x) {
return callsce.call(undefined, 'new ' + this.constructor.name, ...this.getConstructorParameters());
}
isCurvesWithSurface(surface) {
return surface.isCurvesWithSurface(this).map(curve => curve.reversed());
}
containsCurve(curve) {
if (curve instanceof ImplicitCurve) {
for (let i = ceil$7(curve.tMin); i <= floor$7(curve.tMax); i++) {
if (!this.containsPoint(curve.points[i])) {
return false;
}
}
return true;
}
else {
return false;
}
}
flipped2(doFlip) {
return doFlip ? this.flipped() : this;
}
clipCurves(curves) {
return curves;
}
hashCode() {
return this.getConstructorParameters().hashCode();
}
zDirVolume(allEdges) {
return this.visit(ZDirVolumeVisitor, allEdges);
}
calculateArea(allEdges) {
return this.visit(CalculateAreaVisitor, allEdges);
}
}
var PointVsFace;
(function (PointVsFace) {
PointVsFace[PointVsFace["INSIDE"] = 0] = "INSIDE";
PointVsFace[PointVsFace["OUTSIDE"] = 1] = "OUTSIDE";
PointVsFace[PointVsFace["ON_EDGE"] = 2] = "ON_EDGE";
})(PointVsFace || (PointVsFace = {}));
const { ceil: ceil$8, min: min$7 } = Math;
class ParametricSurface extends Surface {
static isCurvesParametricImplicitSurface(ps, is, sStep, tStep = sStep, curveStepSize) {
const pf = ps.pSTFunc(), icc = is.implicitFunction();
const dpds = ps.dpds();
const dpdt = ps.dpdt();
const didp = is.didp.bind(is);
const ist = (x, y) => icc(pf(x, y));
const dids = (s, t) => didp(pf(s, t)).dot(dpds(s, t));
const didt = (s, t) => didp(pf(s, t)).dot(dpdt(s, t));
const mf = MathFunctionR2R.forFFxFy(ist, dids, didt);
const curves = Curve.breakDownIC(mf, ps, sStep, tStep, curveStepSize, dids, didt)
.map(({ points, tangents }, i) => PICurve$1.forParametricPointsTangents(ps, is, points, tangents, curveStepSize));
return curves;
}
static is(obj) {
return obj.pSTFunc;
}
pST(s, t) {
return this.pSTFunc()(s, t);
}
pSTFunc() {
return this.pST.bind(this);
}
stP(pWC) {
return this.stPFunc()(pWC);
}
stPFunc() {
return this.stP.bind(this);
}
bounds(s, t) {
return this.sMin <= s && s <= this.sMax && this.tMin <= t && t <= this.tMax;
}
/**
* Positive values are inside bounds.
*/
boundsSigned(s, t) {
return min$7(s - this.sMin, this.sMax - s, t - this.tMin, this.tMax - t);
}
normalP(p) {
const pmPoint = this.stPFunc()(p);
return this.normalST(pmPoint.x, pmPoint.y);
}
normalSTFunc() {
return this.normalST.bind(this);
}
normalST(s, t) {
return this.normalSTFunc()(s, t);
}
parametersValid(s, t) {
return between(s, this.sMin, this.sMax) && between(t, this.tMin, this.tMax);
}
pointFoot(pWC, ss, st) {
throw new Error();
}
toMesh() {
assert(isFinite(this.tMin) && isFinite(this.tMax) && isFinite(this.sMin) && isFinite(this.sMax));
return Mesh.parametric(this.pSTFunc(), this.normalSTFunc(), this.sMin, this.sMax, this.tMin, this.tMax, ceil$8((this.sMax - this.sMin) / this.uStep), ceil$8((this.tMax - this.tMin) / this.vStep));
}
isCurvesWithImplicitSurface(is, sStep, tStep, stepSize) {
return ParametricSurface.isCurvesParametricImplicitSurface(this, is, sStep, tStep, stepSize);
}
}
class ImplicitSurface extends Surface {
static is(obj) {
return obj.implicitFunction;
}
}
const { PI: PI$9, cos: cos$7, sin: sin$7, min: min$8, max: max$7, tan: tan$5, ceil: ceil$9, floor: floor$8, abs: abs$10, sqrt: sqrt$5, pow: pow$6, atan2: atan2$5, round: round$5, sign: sign$7 } = Math;
class ConicSurface extends ParametricSurface {
/**
* returns new cone C = {apex + f1 * z * cos(d) + f2 * z * sin(d) + f3 * z | -PI <= d <= PI, 0 <= z}
* @param f1
* @param f2
* @param dir Direction in which the cone opens. The ellipse spanned by f1, f2 is contained at (apex + f1).
*/
constructor(center, f1, f2, dir) {
super();
this.center = center;
this.f1 = f1;
this.f2 = f2;
this.dir = dir;
assertVectors(center, f1, f2, dir);
this.matrix = M4.forSys(f1, f2, dir, center);
this.inverseMatrix = this.matrix.inversed();
this.normalDir = sign$7(this.f1.cross(this.f2).dot(this.dir));
this.normalMatrix = this.matrix.as3x3().inversed().transposed().scale(this.normalDir);
}
get apex() {
return this.center;
}
static atApexThroughEllipse(apex, ellipse) {
assertVectors(apex);
assertInst(SemiEllipseCurve, ellipse);
return new ConicSurface(apex, ellipse.f1, ellipse.f2, apex.to(ellipse.center));
}
static unitISLineTs(anchor, dir) {
const { x: ax, y: ay, z: az } = anchor;
const { x: dx, y: dy, z: dz } = dir;
// this cone: x² + y² = z²
// line: p = anchor + t * dir1
// split line equation into 3 component equations, insert into cone equation
// transform to form (a t² + b t + c = 0) and solve with pqFormula
const a = dx * dx + dy * dy - dz * dz;
const b = 2 * (ax * dx + ay * dy - az * dz);
const c = ax * ax + ay * ay - az * az;
// cone only defined for 0 <= z, so filter invalid values
return pqFormula(b / a, c / a).filter(t => 0 < az + t * dz);
}
// calculate intersection of plane ax + cz = d and cone x² + y² = z²
static unitISPlane(a, c, d) {
if (eq0(c)) {
// plane is "vertical", i.e. parallel to Y and Z axes
assert(!eq0(a)); // normal would be zero, which is invalid
// z² - y² = d²/a²
if (eq0(d)) {
// d = 0 => z² - y² = 0 => z² = y² => z = y
// plane goes through origin/V3.O
return [new L3(V3.O, new V3(0, -sqrt$5(2) / 2, -sqrt$5(2) / 2), undefined, 0),
new L3(V3.O, new V3(0, -sqrt$5(2) / 2, sqrt$5(2) / 2), 0)];
}
else {
// hyperbola
const center = new V3(d / a, 0, 0);
const f1 = new V3(0, 0, abs$10(d / a)); // abs, because we always want the hyperbola to be pointing up
const f2 = new V3(0, d / a, 0);
return [new HyperbolaCurve(center, f1, f2)];
}
}
else {
// c != 0
const aa = a * a, cc = c * c;
if (eq0(d)) {
// ax + cz = d => x = d - cz / a => x² = d² - 2cdz/a + c²z²/a²
// x² + y² = z²
// => d² - 2cdz/a + c²z²/a² + y² = z²
if (eq(aa, cc)) {
return [new L3(V3.O, new V3(c, 0, -a).unit())];
}
else if (aa < cc) {
assert(false, 'intersection is single point V3.O');
}
else if (aa > cc) {
return [new L3(V3.O, new V3(c, sqrt$5(aa - cc), -a).unit()),
new L3(V3.O, new V3(c, -sqrt$5(aa - cc), -a).unit())];
}
}
else {
if (eq(aa, cc)) {
// parabola
const parabolaVertex = new V3(d / 2 / a, 0, d / 2 / c);
const parabolaVertexTangentPoint = new V3(d / 2 / a, d / c, d / 2 / c);
const p2 = new V3(0, 0, d / c);
const f2 = p2.minus(parabolaVertex);
return [new ParabolaCurve(parabolaVertex, parabolaVertexTangentPoint.minus(parabolaVertex), f2.z < 0
? f2.negated()
: f2)];
}
else if (aa < cc) {
// ellipse
const center = new V3(-a * d / (cc - aa), 0, d * c / (cc - aa));
if (center.z < 0) {
return [];
}
const p1 = new V3(d / (a - c), 0, -d / (a - c));
const p2 = new V3(-a * d / (cc - aa), d / sqrt$5(cc - aa), d * c / (cc - aa));
return [new EllipseCurve(center, center.to(p1), center.to(p2))];
}
else if (aa > cc) {
// hyperbola
const center = new V3(-a * d / (cc - aa), 0, d * c / (cc - aa));
const p1 = new V3(d / (a - c), 0, -d / (a - c));
const p2 = new V3(-a * d / (cc - aa), d / sqrt$5(aa - cc), d * c / (cc - aa));
const f1 = center.to(p1);
return [new HyperbolaCurve(center, f1.z > 0 ? f1 : f1.negated(), center.to(p2))];
}
}
}
}
equals(obj) {
return this == obj ||
Object.getPrototypeOf(this) == Object.getPrototypeOf(obj)
&& this.center.equals(obj.center)
&& this.f1.equals(obj.f1)
&& this.f2.equals(obj.f2)
&& this.dir.equals(obj.dir);
}
like(object) {
if (!this.isCoplanarTo(object))
return false;
// normals need to point in the same direction (outwards or inwards) for both
return this.normalDir == object.normalDir;
}
getVectors() {
return [{ anchor: this.center, dir1: this.dir },
{ anchor: this.center.plus(this.dir), dir1: this.f1 },
{ anchor: this.center.plus(this.dir), dir1: this.f2 }];
}
getSeamPlane() {
return P3.forAnchorAndPlaneVectors(this.center, this.f1, this.dir);
}
loopContainsPoint(contour, p) {
assertVectors(p);
const line = this.center.like(p)
? new L3(p, this.matrix.transformVector(new V3(0, 1, 1)).unit())
: L3.throughPoints(p, this.apex);
const lineOut = line.dir1.cross(this.dir);
return Surface.loopContainsPointGeneral(contour, p, line, lineOut);
}
getConstructorParameters() {
return [this.center, this.f1, this.f2, this.dir];
}
isTsForLine(line) {
// transforming line manually has advantage that dir1 will not be renormalized,
// meaning that calculated values t for lineLC are directly transferable to line
const anchorLC = this.inverseMatrix.transformPoint(line.anchor);
const dirLC = this.inverseMatrix.transformVector(line.dir1);
return ConicSurface.unitISLineTs(anchorLC, dirLC);
}
/**
* Interestingly, two cones don't need to have parallel dirs to be coplanar.
*/
isCoplanarTo(surface) {
if (this === surface)
return true;
if (!(surface instanceof ConicSurface) || !this.apex.like(surface.apex))
return false;
// at this point apexes are equal
return this.containsEllipse(new SemiEllipseCurve(surface.center.plus(surface.dir), surface.f1, surface.f2));
}
containsEllipse(ellipse) {
const ellipseLC = ellipse.transform(this.inverseMatrix);
if (ellipseLC.center.z < 0) {
return false;
}
const { f1, f2 } = ellipseLC.rightAngled();
const p1 = ellipseLC.center.plus(f1), p2 = ellipseLC.center.plus(f2);
// check if both endpoints are on the cone's surface
// and that one main axis is perpendicular to the Z-axis
return eq(Math.pow(p1.x, 2) + Math.pow(p1.y, 2), Math.pow(p1.z, 2))
&& eq(Math.pow(p2.x, 2) + Math.pow(p2.y, 2), Math.pow(p2.z, 2))
&& (eq0(f1.z) || eq0(f2.z));
}
containsLine(line) {
const lineLC = line.transform(this.inverseMatrix);
const d = lineLC.dir1;
return lineLC.containsPoint(V3.O) && eq(d.x * d.x + d.y * d.y, d.z * d.z);
}
containsParabola(curve) {
assertInst(ParabolaCurve, curve);
const curveLC = curve.transform(this.inverseMatrix);
if (curveLC.center.z < 0 || curveLC.f2.z < 0) {
return false;
}
const { center, f1, f2 } = curveLC.rightAngled();
// check if center is on the surface,
// that tangent is perpendicular to the Z-axis
// and that "y" axis is parallel to surface
return eq(center.x * center.x + center.y * center.y, center.z * center.z)
&& eq0(f1.z)
&& eq(f2.x * f2.x + f2.y * f2.y, f2.z * f2.z);
}
containsHyperbola(curve) {
assertInst(HyperbolaCurve, curve);
return true;
const curveLC = curve.transform(this.inverseMatrix);
if (curveLC.center.z < 0 || curveLC.f2.z < 0) {
return false;
}
const { center, f1, f2 } = curveLC.rightAngled();
// check if center is on the surface,
// that tangent is perpendicular to the Z-axis
return true;
return eq(center.x * center.x + center.y * center.y, center.z * center.z)
&& eq0(f1.z);
}
containsCurve(curve) {
if (curve instanceof SemiEllipseCurve) {
return this.containsEllipse(curve);
}
else if (curve instanceof L3) {
return this.containsLine(curve);
}
else if (curve instanceof HyperbolaCurve) {
return this.containsHyperbola(curve);
}
else if (curve instanceof ParabolaCurve) {
return this.containsParabola(curve);
}
else {
return super.containsCurve(curve);
}
}
transform(m4) {
return new ConicSurface(m4.transformPoint(this.center), m4.transformVector(this.f1).times(m4.isMirroring() ? -1 : 1), m4.transformVector(this.f2), m4.transformVector(this.dir));
}
rightAngled() {
// TODO
}
flipped() {
return new ConicSurface(this.center, this.f1.negated(), this.f2, this.dir);
}
normalSTFunc() {
const { f1, f2 } = this, f3 = this.dir;
return (d, z) => {
return f2.cross(f1).plus(f2.cross(f3.times(Math.cos(d)))).plus(f3.cross(f1.times(Math.sin(d)))).unit();
};
}
normalP(p) {
//TODO assert(!p.like(this.center))
const pLC = this.inverseMatrix.transformPoint(p);
return this.normalSTFunc()(pLC.angleXY(), pLC.z);
}
pSTFunc() {
return (s, t) => {
// center + f1 t cos s + f2 t sin s + t dir
return this.matrix.transformPoint(new V3(t * cos$7(s), t * sin$7(s), t));
};
}
dpds() {
return (s, t) => {
const resultLC = new V3(t * -sin$7(s), t * cos$7(s), 0);
return this.matrix.transformVector(resultLC);
};
}
dpdt() {
return (s, t) => {
const resultLC = new V3(cos$7(s), sin$7(s), 1);
return this.matrix.transformVector(resultLC);
};
}
implicitFunction() {
return pWC => {
const pLC = this.inverseMatrix.transformPoint(pWC);
const radiusLC = pLC.lengthXY();
return this.normalDir * (radiusLC - pLC.z);
};
}
containsPoint(p) {
return eq0(this.implicitFunction()(p));
}
boundsFunction() {
assert(false);
}
stP(pWC) {
const pLC = this.inverseMatrix.transformPoint(pWC);
const angle = pLC.angleXY();
return new V3(angle < -PI$9 / 2 ? angle + TAU : angle, pLC.z, 0);
}
isCurvesWithSurface(surface) {
if (surface instanceof PlaneSurface$1) {
return this.isCurvesWithPlane(surface.plane);
}
else if (ImplicitSurface.is(surface)) {
return ParametricSurface.isCurvesParametricImplicitSurface(this, surface, 0.1, 0.1 / this.dir.length(), 0.02);
}
return super.isCurvesWithSurface(surface);
}
getCenterLine() {
return new L3(this.center, this.dir);
}
isCurvesWithPlane(plane) {
assertInst(P3, plane);
const planeLC = plane.transform(this.inverseMatrix);
const planeNormal = planeLC.normal1;
const c = planeNormal.z;
/** "rotate" plane normal1 when passing to {@link ConicSurface.unitISPlane} so that
* y-component of normal1 is 0 */
const a = planeNormal.lengthXY();
const d = planeLC.w;
// generated curves need to be rotated back before transforming to world coordinates
const rotationMatrix = M4.rotateZ(planeNormal.angleXY());
const wcMatrix = eq0(planeNormal.lengthXY())
? this.matrix
: this.matrix.times(rotationMatrix);
return ConicSurface.unitISPlane(a, c, d).flatMap(curve => {
const curveWC = curve.transform(wcMatrix);
if (curve instanceof EllipseCurve) {
const curveLC = curve.transform(rotationMatrix);
const ts = curveLC.isTsWithPlane(P3.ZX);
const intervals = getIntervals(ts, -PI$9, PI$9).filter(([a, b]) => curveLC.at((a + b) / 2).y > 0);
return intervals.flatMap(([a, b]) => SemiEllipseCurve.fromEllipse(curveWC, a, b));
}
const p = curveWC.at(0.2);
return this.normalP(p).cross(plane.normal1).dot(curveWC.tangentAt(0.2)) > 0
? curveWC : curveWC.reversed();
});
}
edgeLoopCCW(contour) {
const ptpF = this.stPFunc();
return isCCW(contour.flatMap(e => e.getVerticesNo0()).map(v => ptpF(v)), V3.Z);
}
}
/**
* Unit cone. x² + y² = z², 0 <= z
*/
ConicSurface.UNIT = new ConicSurface(V3.O, V3.X, V3.Y, V3.Z);
ConicSurface.prototype.uStep = PI$9 / 16;
ConicSurface.prototype.vStep = 256;
ConicSurface.prototype.sMin = 0;
ConicSurface.prototype.sMax = PI$9;
ConicSurface.prototype.tMin = 0;
ConicSurface.prototype.tMax = 16;
const { PI: PI$10, cos: cos$8, sin: sin$8, abs: abs$11, sign: sign$8 } = Math;
class EllipsoidSurface extends ParametricSurface {
constructor(center, f1, f2, f3) {
super();
this.center = center;
this.f1 = f1;
this.f2 = f2;
this.f3 = f3;
assertVectors(center, f1, f2, f3);
this.matrix = M4.forSys(f1, f2, f3, center);
this.inverseMatrix = this.matrix.inversed();
this.normalDir = sign$8(this.f1.cross(this.f2).dot(this.f3));
this.pLCNormalWCMatrix = this.matrix.as3x3().inversed().transposed().scale(this.normalDir);
this.pWCNormalWCMatrix = this.pLCNormalWCMatrix.times(this.inverseMatrix);
}
/**
* unit sphere: x² + y² + z² = 1
* line: p = anchor + t * dir |^2
* p² = (anchor + t * dir)^2
* 1 == (anchor + t * dir)^2
* 1 == anchor DOT anchor + 2 * anchor * t * dir + t² * dir DOT dir
*/
static unitISTsWithLine(anchor, dir) {
// for 0 = a t² + b t + c
const a = dir.dot(dir);
const b = 2 * anchor.dot(dir);
const c = anchor.dot(anchor) - 1;
return pqFormula(b / a, c / a);
}
/**
* unit sphere: x² + y² + z² = 1
* plane: normal1 DOT p = w
*/
static unitISCurvesWithPlane(plane) {
assertInst(P3, plane);
let distPlaneCenter = Math.abs(plane.w);
if (lt(distPlaneCenter, 1)) {
// result is a circle
// radius of circle: imagine right angled triangle (origin -> center of intersection circle -> point on
// intersection circle) pythagoras: 1² == distPlaneCenter² + isCircleRadius² => isCircleRadius == sqrt(1 -
// distPlaneCenter²)
const isCircleRadius = Math.sqrt(1 - distPlaneCenter * distPlaneCenter);
const f1 = plane.normal1.getPerpendicular().toLength(isCircleRadius);
const f2 = plane.normal1.cross(f1);
return [new EllipseCurve(plane.anchor, f1, f2)];
}
else {
return [];
}
}
static sphere(radius, center) {
assertNumbers(radius);
center && assertVectors(center);
return new EllipsoidSurface(center || V3.O, new V3(radius, 0, 0), new V3(0, radius, 0), new V3(0, 0, radius));
}
/**
* x²/a² + y²/b² + z²/c² = 1
*/
static forABC(a, b, c, center) {
return new EllipsoidSurface(center || V3.O, new V3(a, 0, 0), new V3(0, b, 0), new V3(0, 0, c));
}
static calculateAreaSpheroid(a, b, c, edges) {
assertf(() => a.isPerpendicularTo(b));
assertf(() => b.isPerpendicularTo(c));
assertf(() => c.isPerpendicularTo(a));
// handling discontinuities:
// option 1: check for intersections with baseline, if there are any integrate parts separetely
// "rotate" the edge so that there are no overlaps
const matrix = M4.forSys(a, b, c), inverseMatrix = matrix.inversed();
const circleRadius = a.length();
const c1 = c.unit();
const totalArea = edges.map(edge => {
if (edge.curve instanceof EllipseCurve) {
const f = (t) => {
const at = edge.curve.at(t), tangent = edge.tangentAt(t);
const localAt = inverseMatrix.transformPoint(at);
const angleXY = localAt.angleXY();
const arcLength = angleXY * circleRadius * Math.sqrt(1 + Math.pow(localAt.z, 2));
const scaling = Math.sqrt(1 + Math.pow(c1.dot(tangent), 2));
return arcLength * scaling;
};
const val = glqInSteps(f, edge.aT, edge.bT, 1);
console.log('edge', edge, val);
return val;
}
else {
assertNever();
}
}).sum();
return totalArea;
}
like(obj) {
return this.isCoplanarTo(obj) && this.isInsideOut() == obj.isInsideOut();
}
edgeLoopCCW(loop) {
throw new Error();
}
rootPoints() {
}
getConstructorParameters() {
return [this.center, this.f1, this.f2, this.f3];
}
equals(obj) {
return this == obj ||
Object.getPrototypeOf(obj) == this.constructor.prototype
&& this.matrix.equals(obj.matrix);
}
isCurvesWithPlane(plane) {
const planeLC = plane.transform(this.inverseMatrix);
return EllipsoidSurface.unitISCurvesWithPlane(planeLC).map(c => c.transform(this.matrix));
}
isCurvesWithSurface(surface) {
if (surface instanceof PlaneSurface$1) {
return this.isCurvesWithPlane(surface.plane);
}
else if (surface instanceof CylinderSurface) {
if (surface.dir.isParallelTo(this.dir1)) {
const ellipseProjected = surface.baseCurve.transform(M4.project(this.baseEllipse.getPlane(), this.dir1));
return this.baseEllipse.isInfosWithEllipse(ellipseProjected).map(info => new L3(info.p, this.dir1));
}
else if (eq0(this.getCenterLine().distanceToLine(surface.getCenterLine()))) {
assert(false);
}
else {
assert(false);
}
}
else if (surface instanceof ProjectedCurveSurface) {
const surfaceLC = surface.transform(this.inverseMatrix);
const baseCurveLC = surfaceLC.baseCurve.project(new P3(surfaceLC.dir, 0));
const ists = baseCurveLC.isTsWithSurface(EllipsoidSurface.UNIT);
const insideIntervals = iii(ists, EllipsoidSurface.UNIT, baseCurveLC);
const curves = insideIntervals.flatMap(ii => {
const aLine = new L3(baseCurveLC.at(ii[0]), surfaceLC.dir);
const a = EllipsoidSurface.UNIT.isTsForLine(aLine).map(t => aLine.at(t));
const bLine = new L3(baseCurveLC.at(ii[1]), surfaceLC.dir);
const b = EllipsoidSurface.UNIT.isTsForLine(bLine).map(t => bLine.at(t));
return [0, 1].map(i => {
let aP = a[i] || a[0], bP = b[i] || b[0];
0 !== i && ([aP, bP] = [bP, aP]);
assert(EllipsoidSurface.UNIT.containsPoint(aP));
assert(EllipsoidSurface.UNIT.containsPoint(bP));
return PICurve.forStartEnd(surface, this.asEllipsoidSurface(), aP, bP);
});
});
return curves;
}
}
isTsForLine(line) {
assertInst(L3, line);
// transforming line manually has advantage that dir1 will not be renormalized,
// meaning that calculated values t for localLine are directly transferable to line
const anchorLC = this.inverseMatrix.transformPoint(line.anchor);
const dirLC = this.inverseMatrix.transformVector(line.dir1);
return EllipsoidSurface.unitISTsWithLine(anchorLC, dirLC);
}
isCoplanarTo(surface) {
if (this === surface)
return true;
if (surface.constructor !== EllipsoidSurface)
return false;
if (!this.center.like(surface.center))
return false;
if (this.isSphere())
return surface.isSphere() && eq(this.f1.length(), this.f2.length());
const localOtherMatrix = this.inverseMatrix.times(surface.matrix);
// Ellipsoid with matrix localOtherMatrix is unit sphere iff localOtherMatrix is orthogonal
return localOtherMatrix.is3x3() && localOtherMatrix.isOrthogonal();
}
containsEllipse(ellipse) {
const localEllipse = ellipse.transform(this.inverseMatrix);
const distLocalEllipseCenter = localEllipse.center.length();
const correctRadius = Math.sqrt(1 - distLocalEllipseCenter * distLocalEllipseCenter);
return lt(distLocalEllipseCenter, 1) && localEllipse.isCircular() && localEllipse.f1.hasLength(correctRadius);
}
containsCurve(curve) {
if (curve instanceof EllipseCurve) {
return this.containsEllipse(curve);
}
else {
return super.containsCurve(curve);
}
}
transform(m4) {
return new EllipsoidSurface(m4.transformPoint(this.center), m4.transformVector(this.f1), m4.transformVector(this.f2), m4.transformVector(this.f3));
}
isInsideOut() {
return this.f1.cross(this.f2).dot(this.f3) < 0;
}
flipped() {
return new EllipsoidSurface(this.center, this.f1, this.f2, this.f3.negated());
}
toMesh(subdivisions = 3) {
return Mesh.sphere(subdivisions).transform(this.matrix);
// let mesh = new Mesh({triangles: true, lines: false, normals: true})
// let pf = this.pSTFunc()
// let pn = this.normalSTFunc()
// let aCount = 32, bCount = 16, vTotal = aCount * bCount
// for (let i = 0, a = -PI; i < aCount; i++, a += 2 * PI / aCount) {
// for (let j = 0, b = -Math.PI / 2; j < bCount; j++, b += Math.PI / (bCount - 1)) {
// mesh.vertices.push(pf(a, b))
// mesh.normals.push(pn(a, b))
// j != (bCount - 1) && pushQuad(mesh.triangles, true,
// i * bCount + j, i * bCount + j + 1,
// ((i + 1) * bCount + j) % vTotal, ((i + 1) * bCount + j + 1) % vTotal)
// }
// }
// mesh.compile()
// return mesh
}
normalSTFunc() {
// ugh
// paramtric ellipsoid point q(a, b)
// normal1 == (dq(a, b) / da) X (dq(a, b) / db) (Cross product of partial derivatives
// normal1 == cos b * (f2 X f3 * cos b * cos a + f3 X f1 * cos b * sin a + f1 X f2 * sin b)
return (a, b) => {
let { f1, f2, f3 } = this;
let normal = f2.cross(f3).times(Math.cos(b) * Math.cos(a))
.plus(f3.cross(f1).times(Math.cos(b) * Math.sin(a)))
.plus(f1.cross(f2).times(Math.sin(b)))
.unit();
return normal;
};
}
normalP(p) {
return this.normalMatrix.transformVector(this.inverseMatrix.transformPoint(p)).unit();
}
normalST(s, t) {
return this.normalMatrix.transformVector(V3.sphere(s, t));
}
pST(s, t) {
return this.matrix.transformPoint(V3.sphere(s, t));
}
// d/dp (this.implicitFunction(p)) =
// = d/dp (this.inverseMatrix.transformPoint(p).length() - 1)
// = d/dp (this.inverseMatrix.transformPoint(p) * this.inverseMatrix.transformPoint(pWC).unit()
dpds() {
return (s, t) => this.matrix.transformVector(new V3(sin$8(s) * -cos$8(t), cos$8(s) * cos$8(t), 0));
}
dpdt() {
return (s, t) => this.matrix.transformVector(new V3(sin$8(t) * -cos$8(s), -sin$8(s) * sin$8(t), cos$8(t)));
}
stPFunc() {
return (pWC, hint) => {
const pLC = this.inverseMatrix.transformPoint(pWC);
let alpha = pLC.angleXY();
if (abs$11(alpha) > Math.PI - NLA_PRECISION) {
assert(hint == -PI$10 || hint == PI$10);
alpha = hint;
}
let beta = Math.asin(pLC.z);
return new V3(alpha, beta, 0);
};
}
isSphere() {
return eq(this.f1.length(), this.f2.length())
&& eq(this.f2.length(), this.f3.length())
&& eq(this.f3.length(), this.f1.length())
&& this.f1.isPerpendicularTo(this.f2)
&& this.f2.isPerpendicularTo(this.f3)
&& this.f3.isPerpendicularTo(this.f1);
}
isVerticalSpheroid() {
return eq(this.f1.length(), this.f2.length())
&& this.f1.isPerpendicularTo(this.f2)
&& this.f2.isPerpendicularTo(this.f3)
&& this.f3.isPerpendicularTo(this.f1);
}
implicitFunction() {
return (pWC) => {
const pLC = this.inverseMatrix.transformPoint(pWC);
return pLC.length() - 1;
};
}
// = this.inverseMatrix.transformPoint(this.inverseMatrix.transformPoint(pWC).unit())
didp(pWC) {
const pLC = this.inverseMatrix.transformPoint(pWC);
return this.inverseMatrix.transformVector(pLC.unit());
}
mainAxes() {
// q(a, b) = f1 cos a cos b + f2 sin a cos b + f3 sin b
// q(s, t, u) = s * f1 + t * f2 + u * f3 with s² + t² + u² = 1
// (del q(a, b) / del a) = f1 (-sin a) cos b + f2 cos a cos b
// (del q(a, b) / del b) = f1 cos a (-sin b) + f2 sin a (-sin b) + f2 cos b
// del q(s, t, u) / del a = -t f1 + s f2
// (del q(a, b) / del a) DOT q(a, b) == 0
// (f1 (-sin a) cos b + f2 cos a cos b) DOT (f1 cos a cos b + f2 sin a cos b + f2 sin b) == 0
// (del q(a, b) / del b) DOT q(a, b) == 0
// (f1 cos a (-sin b) + f2 sin a (-sin b) + f2 cos b) DOT (f1 cos a cos b + f2 sin a cos b + f2 sin b) == 0
// Solve[
// (f1 (-sin a) cos b + f2 cos a cos b) * (f1 cos a cos b + f2 sin a cos b + f2 sin b) = 0,
// (f1 cos a (-sin b) + f2 sin a (-sin b) + f2 cos b) * (f1 cos a cos b + f2 sin a cos b + f2 sin b) = 0}, a, b]
const { f1, f2, f3 } = this;
if (eq0(f1.dot(f2)) && eq0(f2.dot(f3)) && eq0(f3.dot(f1))) {
return this;
}
//const f = ([a, b], x?) => {
// const sinA = Math.sin(a), cosA = Math.cos(a), sinB = Math.sin(b), cosB = Math.cos(b)
// const centerToP = V3.add(f1.times(cosA * cosB), f2.times(sinA * cosB), f3.times(sinB))
// const centerToPdelA = f1.times(-sinA * cosB).plus(f2.times(cosA * cosB))
// const centerToPdelB = V3.add(f1.times(cosA * -sinB), f2.times(sinA * -sinB), f3.times(cosB))
// x && console.log(centerToP.sce, centerToPdelA.sce, centerToPdelB.sce)
// return [centerToP.dot(centerToPdelA), centerToP.dot(centerToPdelB)]
//}
//const mainF1Params = newtonIterate(f, [0, 0], 8), mainF1 = this.pSTFunc()(mainF1Params[0], mainF1Params[1])
//console.log(f(mainF1Params, 1).sce)
//const mainF2Params = newtonIterate(f, this.stPFunc()(f2.rejectedFrom(mainF1)).toArray(2), 8),
// mainF2 = this.pSTFunc()(mainF2Params[0], mainF2Params[1])
//console.log(this.normalSTFunc()(mainF2Params[0], mainF2Params[1]).sce)
//assert(mainF1.isPerpendicularTo(mainF2), mainF1, mainF2, mainF1.dot(mainF2), mainF1Params)
//const mainF3Params = this.stPFunc()(mainF1.cross(mainF2)), mainF3 = this.pSTFunc()(mainF3Params[0],
// mainF3Params[1]) return new EllipsoidSurface(this.center, mainF1, mainF2, mainF3)
const { U, SIGMA } = this.matrix.svd3();
assert(SIGMA.isDiagonal());
assert(U.isOrthogonal());
const U_SIGMA = U.times(SIGMA);
// column vectors of U_SIGMA
const [mainF1, mainF2, mainF3] = arrayFromFunction(3, i => new V3(U_SIGMA.m[i], U_SIGMA.m[i + 4], U_SIGMA.m[i + 8]));
return new EllipsoidSurface(this.center, mainF1, mainF2, mainF3);
}
containsPoint(p) {
return eq0(this.implicitFunction()(p));
}
boundsFunction() {
return (a, b) => between(b, -PI$10, PI$10);
}
volume() {
return 4 / 3 * Math.PI * this.f1.dot(this.f2.cross(this.f3));
}
// TODO: also a commented out test
//static splitOnPlaneLoop(loop: Edge[], ccw: boolean): [Edge[], Edge[]] {
//const seamPlane = P3.ZX, seamSurface = new PlaneSurface(seamPlane)
//const frontParts = [], backParts = [], iss = []
//const colinearEdges = loop.map((edge) => seamSurface.containsCurve(edge.curve))
//// a colinear edge is in front when
//// ccw is true
//// the edge curve is CCW on the seamPlane
//// the edge is the same dir as the curve (bT > aT)
//const colinearEdgesSide = loop.map((edge, i) => colinearEdges[i] &&
// (ccw ? 1 : -1) * seamPlane.normal1.dot(edge.curve.normal1) * (edge.bT - edge.aT))
//
//for (let edgeIndex = 0; edgeIndex < loop.length; edgeIndex++) {
// const edge = loop[edgeIndex]
// const nextEdgeIndex = (edgeIndex + 1) % loop.length, nextEdge = loop[nextEdgeIndex]
// //console.log(edge.toSource()) {p:V(2, -2.102, 0),
// if (colinearEdges[edgeIndex]) {
// const nextSide = colinearEdges[nextEdgeIndex] ? colinearEdgesSide[nextEdgeIndex]
// : dotCurve2(nextEdge.curve, nextEdge.aT, seamPlane.normal1, nextEdge.bT - nextEdge.aT)
// if (nextSide * colinearEdgesSide[edgeIndex] < 0) {
// iss.push({p: edge.b, t: 0, out: nextSide > 0})
// }
// (colinearEdgesSide[edgeIndex] > 0 ? frontParts : backParts).push(edge)
// } else {
// const f = sign(edge.bT - edge.aT)
// const ists = edge.edgeISTsWithPlane(seamPlane).sort((a, b) => f * (a - b))
// let prevT = edge.aT,
// prevP = edge.a,
// prevDir = edge.aDir,
// prevSide = snap0(seamPlane.distanceToPointSigned(edge.a)) || dotCurve2(edge.curve, edge.aT, V3.Y,
// f) for (let i = 0; i < ists.length; i++) { const t = ists[i] if (edge.aT == t || edge.bT == t) { edge.bT ==
// t && iss.push({p: edge.b, t: 0, out: true}) continue } const nextSide = dotCurve2(edge.curve, t, V3.Y, 1) if
// (prevSide * nextSide < 0) { // switches sides, so: const newP = edge.curve.at(t) const newDir =
// edge.tangentAt(t) const newEdge = Edge.create(edge.curve, prevP, newP, prevT, t, undefined, prevDir, newDir)
// ;(prevSide > 0 ? frontParts : backParts).push(newEdge) iss.push({p: newP, t: 0, out: nextSide > 0}) prevP =
// newP prevDir = newDir prevT = t prevSide = nextSide } } const lastEdge = Edge.create(edge.curve, prevP,
// edge.b, prevT, edge.bT, undefined, prevDir, edge.bDir) ;(prevSide > 0 ? frontParts :
// backParts).push(lastEdge) } } iss.forEach(is => is.t = V3.X.negated().angleRelativeNormal(is.p, V3.Y))
// iss.sort((a, b) => a.t - b.t) let i = ccw == iss[0].out ? 1 : 0 const curve = new EllipseCurve(V3.O,
// V3.X.negated(), V3.Z) //if (1 == i) {
// //frontParts.push(
// // Edge.create(curve, V3.Y.negated(), iss[0].p, -PI, iss[0].t, undefined, V3.Z.negated(),
// curve.tangentAt(iss[0].t)),
//// Edge.create(curve, iss.last.p, V3.Y.negated(), iss.last.t, PI, undefined,
// curve.tangentAt(iss.last.t), V3.Z.negated())) //} for (let i = ccw == iss[0].out ? 1 : 0; i < iss.length; i
// += 2) {
// let is0 = iss[i], is1 = iss[(i + 1) % iss.length]
// if (lt(is0.t, -PI) && lt(-PI, is1.t)) {
// iss.splice(i + 1, 0, is1 = {p: V3.Y.negated(), t: -PI, out: true}, {p: V3.Y.negated(), t: -PI, out:
// true})
// } else if (lt(is0.t, PI) && lt(PI, is1.t)) {
// iss.splice(i + 1, 0, is1 = {p: V3.Y, t: -PI, out: true}, {p: V3.Y, t: PI, out: true})
// }
// const edge = Edge.create(curve, is0.p, is1.p, is0.t, is1.t, undefined,
// curve.tangentAt(is0.t).times(sign(is1.t - is0.t)),
// curve.tangentAt(is1.t).times(sign(is1.t - is0.t)))
// frontParts.push(edge)
// backParts.push(edge.flipped())
//}
//return [frontParts, backParts]
//}
loopContainsPoint(loop, p) {
assertVectors(p);
const testLine = new EllipseCurve(this.center, this.matrix.transformVector(this.inverseMatrix.transformPoint(p).withElement('z', 0).unit()), this.f3);
const pT = testLine.pointT(p);
const lineOut = testLine.normal;
const testPlane = P3.normalOnAnchor(testLine.normal, p);
const colinearEdges = loop.map((edge) => edge.curve.isColinearTo(testLine));
let inside = false;
function logIS(isP) {
const isT = testLine.pointT(isP);
if (eq(pT, isT)) {
return true;
}
else if (pT < isT && le(isT, PI$10)) {
inside = !inside;
}
}
for (let edgeIndex = 0; edgeIndex < loop.length; edgeIndex++) {
const edge = loop[edgeIndex];
const nextEdgeIndex = (edgeIndex + 1) % loop.length, nextEdge = loop[nextEdgeIndex];
//console.log(edge.toSource()) {p:V(2, -2.102, 0),
if (colinearEdges[edgeIndex]) {
const lineAT = testLine.pointT(edge.a), lineBT = testLine.pointT(edge.b);
if (le(Math.min(lineAT, lineBT), pT) && ge(pT, Math.max(lineAT, lineBT))) {
return PointVsFace.ON_EDGE;
}
// edge colinear to intersection
const nextInside = colinearEdges[nextEdgeIndex] || dotCurve(lineOut, nextEdge.aDir, nextEdge.aDDT) < 0;
if (nextInside) {
if (logIS(edge.b))
return PointVsFace.ON_EDGE;
}
}
else {
for (const edgeT of edge.edgeISTsWithPlane(testPlane)) {
if (edgeT == edge.bT) {
if (!testLine.containsPoint(edge.b))
continue;
// endpoint lies on intersection testLine
const edgeInside = dotCurve(lineOut, edge.bDir, edge.bDDT) < 0;
const nextInside = colinearEdges[nextEdgeIndex] || dotCurve(lineOut, nextEdge.aDir, nextEdge.aDDT) < 0;
if (edgeInside != nextInside) {
if (logIS(edge.b))
return PointVsFace.ON_EDGE;
}
}
else if (edgeT != edge.aT) {
const p = edge.curve.at(edgeT);
if (!testLine.containsPoint(p))
continue;
// edge crosses testLine, neither starts nor ends on it
if (logIS(p))
return PointVsFace.ON_EDGE;
// TODO: tangents?
}
}
}
}
return inside ? PointVsFace.INSIDE : PointVsFace.OUTSIDE;
}
surfaceAreaApprox() {
// See https://en.wikipedia.org/wiki/Ellipsoid#Surface_area
const mainAxes = this.mainAxes(), a = mainAxes.f1.length(), b = mainAxes.f2.length(), c = mainAxes.f3.length();
const p = 1.6075;
return 4 * PI$10 * Math.pow((Math.pow(a * b, p) + Math.pow(b * c, p) + Math.pow(c * a, p)) / 3, 1 / p);
}
surfaceArea() {
// See https://en.wikipedia.org/wiki/Ellipsoid#Surface_area
const mainAxes = this.mainAxes(), f1l = mainAxes.f1.length(), f2l = mainAxes.f2.length(), f3l = mainAxes.f3.length(), [c, b, a] = [f1l, f2l, f3l].sort(MINUS);
// https://en.wikipedia.org/w/index.php?title=Spheroid&oldid=761246800#Area
function spheroidArea(a, c) {
if (c < a) {
const eccentricity2 = 1 - Math.pow(c, 2) / Math.pow(a, 2);
const eccentricity = Math.sqrt(eccentricity2);
return 2 * PI$10 * Math.pow(a, 2) * (1 + (1 - eccentricity2) / Math.sqrt(eccentricity) * Math.atanh(eccentricity));
}
else {
const eccentricity = Math.sqrt(1 - Math.pow(a, 2) / Math.pow(c, 2));
return 2 * PI$10 * Math.pow(a, 2) * (1 + c / a / eccentricity * Math.asin(eccentricity));
}
}
if (eq(a, b)) {
return spheroidArea(a, c);
}
else if (eq(b, c)) {
return spheroidArea(b, a);
}
else if (eq(c, a)) {
return spheroidArea(c, b);
}
const phi = Math.acos(c / a);
const k2 = Math.pow(a, 2) * (Math.pow(b, 2) - Math.pow(c, 2)) / (Math.pow(b, 2) * (Math.pow(a, 2) - Math.pow(c, 2)));
const incompleteEllipticInt1 = gaussLegendreQuadrature24(phi => Math.pow(1 - k2 * Math.pow(Math.sin(phi), 2), -0.5), 0, phi);
const incompleteEllipticInt2 = gaussLegendreQuadrature24(phi => Math.pow(1 - k2 * Math.pow(Math.sin(phi), 2), 0.5), 0, phi);
return 2 * PI$10 * Math.pow(c, 2) + 2 * PI$10 * a * b / Math.sin(phi) * (incompleteEllipticInt2 * Math.pow(Math.sin(phi), 2) + incompleteEllipticInt1 * Math.pow(Math.cos(phi), 2));
}
getSeamPlane() {
return P3.forAnchorAndPlaneVectors(this.center, this.f1, this.f3);
}
}
EllipsoidSurface.UNIT = new EllipsoidSurface(V3.O, V3.X, V3.Y, V3.Z);
EllipsoidSurface.prototype.uStep = PI$10 / 32;
EllipsoidSurface.prototype.vStep = PI$10 / 32;
const { PI: PI$11, cos: cos$9, sin: sin$9, min: min$9, max: max$8, tan: tan$6, sign: sign$9, ceil: ceil$10, floor: floor$9, abs: abs$12, sqrt: sqrt$6, pow: pow$7, atan2: atan2$6, round: round$6 } = Math;
/**
* Surface normal1 is (t, z) => this.baseCurve.tangentAt(t) X this.dir
* Choose dir appropriately to select surface orientation.
*/
class ProjectedCurveSurface extends ParametricSurface {
constructor(baseCurve, dir, sMin = baseCurve.tMin, sMax = baseCurve.tMax, tMin = -100, tMax = 100) {
super();
this.baseCurve = baseCurve;
this.dir = dir;
this.sMin = sMin;
this.sMax = sMax;
this.tMin = tMin;
this.tMax = tMax;
assertInst(Curve, baseCurve);
assertInst(V3, dir);
assertNumbers(sMin, sMax, tMin, tMax);
assert(sMin < sMax);
assert(tMin < tMax);
}
getConstructorParameters() {
return [this.baseCurve, this.dir, this.sMin, this.sMax, this.tMin, this.tMax];
}
equals(obj) {
return this == obj ||
Object.getPrototypeOf(this) == Object.getPrototypeOf(obj)
&& this.dir.equals(obj.dir)
&& this.baseCurve.equals(obj.baseCurve);
}
hashCode() {
return [this.dir, this.baseCurve].hashCode();
}
containsLine(line) {
return this.dir.isParallelTo(line.dir1) && this.containsPoint(line.anchor);
}
dpds() {
return (s, t) => this.baseCurve.tangentAt(s);
}
dpdt() {
return (s, t) => this.dir;
}
normalST(s, t) {
return this.baseCurve.tangentAt(s).cross(this.dir).unit();
}
pST(s, t) {
return this.baseCurve.at(s).plus(this.dir.times(t));
}
pointFoot(pWC, ss, st) {
const basePlane = new P3(this.dir, 0);
const projCurve = this.baseCurve.project(basePlane);
const projPoint = basePlane.projectedPoint(pWC);
const t = projCurve.closestTToPoint(projPoint, ss);
const z = pWC.minus(this.baseCurve.at(t)).dot(this.dir);
return new V3(t, z, 0);
}
stPFunc() {
const projPlane = new P3(this.dir.unit(), 0);
const projBaseCurve = this.baseCurve.project(projPlane);
return (pWC) => {
const projPoint = projPlane.projectedPoint(pWC);
const t = projBaseCurve.pointT(projPoint);
const z = L3.pointT(this.baseCurve.at(t), this.dir, pWC);
return new V3(t, z, 0);
};
}
isCurvesWithPlane(plane) {
assertInst(P3, plane);
if (this.dir.isPerpendicularTo(plane.normal1)) {
const ts = this.baseCurve.isTsWithPlane(plane);
return ts.map(t => {
const l3dir = 0 < this.baseCurve.tangentAt(t).dot(plane.normal1)
? this.dir
: this.dir.negated();
return new L3(this.baseCurve.at(t), l3dir.unit());
});
}
else {
let projCurve = this.baseCurve.transform(M4.project(plane, this.dir));
if (this.dir.dot(plane.normal1) > 0) {
// we need to flip the ellipse so the tangent is correct
projCurve = projCurve.reversed();
}
return [projCurve];
}
}
isCurvesWithSurface(surface) {
if (surface instanceof PlaneSurface$1) {
return this.isCurvesWithPlane(surface.plane);
}
if (surface instanceof ProjectedCurveSurface) {
const dir1 = surface.dir;
if (this.dir.isParallelTo(dir1)) {
const otherCurve = surface.baseCurve;
const infos = this.baseCurve.isInfosWithCurve(otherCurve);
return infos.map(info => {
const correctDir = this.normalP(info.p).cross(surface.normalP(info.p));
return new L3(info.p, dir1.times(sign$9(correctDir.dot(dir1))));
});
}
if (surface instanceof ProjectedCurveSurface) {
const line = new L3(this.baseCurve.at(0.5), this.dir);
const startPoint = line.at(surface.isTsForLine(line)[0]);
console.log(startPoint);
return [new PPCurve(this, surface, startPoint)];
// const testVector = this.dir.cross(surface.dir).unit()
// // look for points on surface.baseCurve where tangent DOT testVector == 0
// const abcd1 = surface.baseCurve.tangentCoefficients().map(c => c.dot(testVector))
// const ts1 = solveCubicReal2.apply(undefined, abcd1).concat(surface.sMin, surface.sMax)
// const abcd2 = this.baseCurve.tangentCoefficients().map(c => c.dot(testVector))
// const ts2 = solveCubicReal2.apply(undefined, abcd2)
// const tt1 = ts1.map(t => surface.baseCurve.at(t).dot(testVector))
// const tt2 = ts1.map(t => surface.baseCurve.at(t).dot(testVector))
// console.log(ts1, ts2, tt1, tt2)
// ts1.forEach(t => drPs.push(surface.baseCurve.at(t)))
// ts2.forEach(t => drPs.push(this.baseCurve.at(t)))
// return
}
}
if (surface instanceof SemiEllipsoidSurface) {
return surface.isCurvesWithSurface(this);
}
assertNever();
}
containsPoint(pWC) {
const uv = this.stPFunc()(pWC);
return this.pSTFunc()(uv.x, uv.y).like(pWC);
}
containsCurve(curve) {
if (curve instanceof L3) {
return this.dir.isParallelTo(curve.dir1) && this.containsPoint(curve.anchor);
}
if (curve instanceof PICurve$1) {
return super.containsCurve(curve);
}
// project baseCurve and test curve onto a common plane and check if the curves are alike
const projPlane = new P3(this.dir.unit(), 0);
const projBaseCurve = this.baseCurve.project(projPlane);
const projCurve = curve.project(projPlane);
return projBaseCurve.isColinearTo(projCurve);
}
isCoplanarTo(surface) {
return this == surface ||
hasConstructor(surface, ProjectedCurveSurface)
&& this.dir.isParallelTo(surface.dir)
&& this.containsCurve(surface.baseCurve);
}
like(object) {
if (!this.isCoplanarTo(object))
return false;
// normals need to point in the same direction (outwards or inwards) for both
const p00 = this.pSTFunc()(0, 0);
const thisNormal = this.normalSTFunc()(0, 0);
const otherNormal = object.normalP(p00);
return 0 < thisNormal.dot(otherNormal);
}
loopContainsPoint(loop, p) {
assertVectors(p);
assert(isFinite(p.x), p.y, p.z);
const line = new L3(p, this.dir.unit());
const ptpf = this.stPFunc();
const pp = ptpf(p);
if (isNaN(pp.x)) {
console.log(this.sce, p.sce);
assert(false);
}
const lineOut = this.baseCurve.tangentAt(pp.x).rejectedFrom(this.dir);
return Surface.loopContainsPointGeneral(loop, p, line, lineOut);
}
edgeLoopCCW(loop) {
if (loop.length < 56) {
let totalAngle = 0;
for (let i = 0; i < loop.length; i++) {
const ipp = (i + 1) % loop.length;
const edge = loop[i], nextEdge = loop[ipp];
totalAngle += edge.bDir.angleRelativeNormal(nextEdge.aDir, this.normalP(edge.b));
}
return totalAngle > 0;
}
else {
const ptpF = this.stPFunc();
return isCCW(loop.map(e => ptpF(e.a)), V3.Z);
}
}
transform(m4) {
const f = m4.isMirroring() ? -1 : 1;
return new this.constructor(this.baseCurve.transform(m4), m4.transformVector(this.dir).times(f), this.sMin, this.sMax, 1 == f ? this.tMin : -this.tMax, 1 == f ? this.tMax : -this.tMin);
}
isTsForLine(line) {
assertInst(L3, line);
const projPlane = new P3(this.dir.unit(), 0);
const projDir = projPlane.projectedVector(line.dir1);
if (projDir.likeO()) {
// line is parallel to this.dir
return [];
}
const projAnchor = projPlane.projectedPoint(line.anchor);
const projBaseCurve = this.baseCurve.project(projPlane);
return projBaseCurve
.isInfosWithLine(projAnchor, projDir, this.sMin, this.sMax, line.tMin, line.tMax)
.map(info => info.tOther);
}
flipped() {
return new this.constructor(this.baseCurve, this.dir.negated(), this.sMin, this.sMax, -this.tMax, -this.tMin);
}
}
ProjectedCurveSurface.prototype.uStep = 1 / 40;
ProjectedCurveSurface.prototype.vStep = 256;
const { PI: PI$12 } = Math;
class CylinderSurface extends ProjectedCurveSurface {
constructor(baseEllipse, dir1, zMin = -Infinity, zMax = Infinity) {
super(baseEllipse, dir1, undefined, undefined, zMin, zMax);
assert(2 == arguments.length);
assertVectors(dir1);
assertInst(EllipseCurve, baseEllipse);
//assert(!baseCurve.normal1.isPerpendicularTo(dir1), !baseCurve.normal1.isPerpendicularTo(dir1))
assert(dir1.hasLength(1));
this.matrix = M4.forSys(baseEllipse.f1, baseEllipse.f2, dir1, baseEllipse.center);
this.inverseMatrix = this.matrix.inversed();
}
static cylinder(radius) {
return new CylinderSurface(new EllipseCurve(V3.O, new V3(radius, 0, 0), new V3(0, radius, 0)), V3.Z);
}
/**
*
* @param anchor
* @param dir not necessarily unit
*/
static unitISLineTs(anchor, dir) {
const { x: ax, y: ay } = anchor;
const { x: dx, y: dy } = dir;
// this cylinder: x² + y² = 1
// line: p = anchor + t * dir
// split line equation into 3 component equations, insert into cylinder equation
// x = ax + t * dx
// y = ay + t * dy
// (ax² + 2 ax t dx + t²dx²) + (ay² + 2 ay t dy + t²dy²) = 1
// transform to form (a t² + b t + c = 0) and solve with pqFormula
const a = Math.pow(dx, 2) + Math.pow(dy, 2);
const b = 2 * (ax * dx + ay * dy);
const c = Math.pow(ax, 2) + Math.pow(ay, 2) - 1;
return pqFormula(b / a, c / a);
}
getConstructorParameters() {
return [this.baseCurve, this.dir];
}
loopContainsPoint(loop, p) {
assertVectors(p);
// create plane that goes through cylinder seam
const line = new L3(p, this.dir);
const seamBase = this.baseCurve.at(PI$12);
const lineOut = this.dir.cross(this.normalP(p));
return Surface.loopContainsPointGeneral(loop, p, line, lineOut);
}
isTsForLine(line) {
assertInst(L3, line);
// transforming line manually has advantage that dir1 will not be renormalized,
// meaning that calculated values t for localLine are directly transferable to line
const localDir = this.inverseMatrix.transformVector(line.dir1);
if (localDir.isParallelTo(V3.Z)) {
// line is parallel to this.dir
return [];
}
const localAnchor = this.inverseMatrix.transformPoint(line.anchor);
assert(!CylinderSurface.unitISLineTs(localAnchor, localDir).length || !isNaN(CylinderSurface.unitISLineTs(localAnchor, localDir)[0]), 'sad ' + localDir);
return CylinderSurface.unitISLineTs(localAnchor, localDir);
}
isCoplanarTo(surface) {
return this == surface ||
surface instanceof CylinderSurface
&& this.dir.isParallelTo(surface.dir)
&& this.containsEllipse(surface.baseCurve);
}
like(object) {
if (!this.isCoplanarTo(object))
return false;
// normals need to point in the same direction (outwards or inwards) for both
const thisFacesOut = 0 < this.baseCurve.normal.dot(this.dir);
const objectFacesOut = 0 < object.baseCurve.normal.dot(object.dir);
return thisFacesOut == objectFacesOut;
}
containsEllipse(ellipse) {
const ellipseProjected = ellipse.transform(M4.project(this.baseCurve.getPlane(), this.dir));
return this.baseCurve == ellipse || this.baseCurve.isColinearTo(ellipseProjected);
}
containsCurve(curve) {
if (curve instanceof EllipseCurve) {
return this.containsEllipse(curve);
}
else if (curve instanceof L3) {
return this.containsLine(curve);
}
else if (curve instanceof SemiEllipseCurve) {
return this.containsEllipse(curve);
}
else {
assert(false);
}
}
normalP(p) {
const pLC = this.inverseMatrix.transformPoint(p);
return this.normalSTFunc()(pLC.angleXY(), pLC.z);
}
implicitFunction() {
return (pWC) => {
const p = this.inverseMatrix.transformPoint(pWC);
const radiusLC = p.lengthXY();
const normalDir = Math.sign(this.baseCurve.normal.dot(this.dir));
return normalDir * (1 - radiusLC);
};
}
containsPoint(p) {
return eq0(this.implicitFunction()(p));
}
pointToParameterFunction() {
return (pWC, hint) => {
const pLC = this.inverseMatrix.transformPoint(pWC);
let angle = pLC.angleXY();
if (abs(angle) > Math.PI - NLA_PRECISION) {
assert(hint == -PI$12 || hint == PI$12);
angle = hint;
}
return new V3(angle, pLC.z, 0);
};
}
isCurvesWithSurface(surface) {
if (surface instanceof PlaneSurface) {
return this.isCurvesWithPlane(surface.plane);
}
else if (surface instanceof CylinderSurface) {
if (surface.dir.isParallelTo(this.dir)) {
const projEllipse = surface.baseCurve.transform(M4.project(this.baseCurve.getPlane(), this.dir));
return this.baseCurve.isInfosWithEllipse(projEllipse).map(info => new L3(info.p, this.dir));
}
else if (eq0(this.getCenterLine().distanceToLine(surface.getCenterLine()))) {
assert(false);
}
else {
assert(false);
}
}
}
getCenterLine() {
return new L3(this.baseCurve.center, this.dir);
}
edgeLoopCCW(loop) {
if (loop.length < 56) {
let totalAngle = 0;
for (let i = 0; i < loop.length; i++) {
const ipp = (i + 1) % loop.length;
const edge = loop[i], nextEdge = loop[ipp];
totalAngle += edge.bDir.angleRelativeNormal(nextEdge.aDir, this.normalP(edge.b));
}
return totalAngle > 0;
}
else {
const ptpF = this.stPFunc();
return isCCW(loop.map(e => ptpF(e.a)), V3.Z);
}
}
facesOutwards() {
return this.baseCurve.normal.dot(this.dir) > 0;
}
getSeamPlane() {
return P3.forAnchorAndPlaneVectors(this.baseCurve.center, this.baseCurve.f1, this.dir);
}
}
CylinderSurface.UNIT = new CylinderSurface(EllipseCurve.XY, V3.Z);
CylinderSurface.prototype.uStep = TAU / 128;
CylinderSurface.prototype.vStep = 256;
const { PI: PI$13, cos: cos$10, sin: sin$10, min: min$10, max: max$9, sign: sign$10, tan: tan$7, ceil: ceil$11, floor: floor$10, abs: abs$13, sqrt: sqrt$7, pow: pow$8, atan2: atan2$7, round: round$7 } = Math;
/**
* Rotation surface with r = f(z)
*/
class RotationREqFOfZ extends ParametricSurface {
constructor(matrix, rt, // r(z)
tMin, tMax, normalDir, drdz = z => (rt(z + EPS) - rt(z)) / EPS) {
super();
this.matrix = matrix;
this.rt = rt;
this.tMin = tMin;
this.tMax = tMax;
this.normalDir = normalDir;
this.drdz = drdz;
assertInst(M4, matrix);
assert(matrix.isNoProj());
assert(1 == normalDir || -1 == normalDir);
this.matrixInverse = matrix.inversed();
}
getConstructorParameters() {
return [this.matrix, this.rt, this.tMin, this.tMax, this.normalDir, this.drdz];
}
flipped() {
return new RotationREqFOfZ(this.matrix, this.rt, this.tMin, this.tMax, -this.normalDir, this.drdz);
}
transform(m4) {
return new RotationREqFOfZ(m4.times(this.matrix), this.rt, this.tMin, this.tMax, this.normalDir, this.drdz);
}
containsPoint(p) {
return eq0(this.implicitFunction()(p));
}
pSTFunc() {
return (d, z) => {
const radius = this.rt(z);
return this.matrix.transformPoint(V3.polar(radius, d, z));
};
}
dpds() {
return (s, t) => {
const radius = this.rt(t);
return this.matrix.transformVector(new V3(radius * -sin$10(s), radius * cos$10(s), 0));
};
}
/**
* new V3(f(z) * cos d, f(z) * sin d, z)
*/
dpdt() {
return (s, t) => {
const drdt = this.drdz(t);
return this.matrix.transformVector(new V3(drdt * cos$10(s), drdt * sin$10(s), 1));
};
}
normalSTFunc() {
/**
* (radius * -sin(s), radius * cos(s), 0) X (drds * cos(s), drds * sin(s), 1)
* =(radius * cos(s)*1,
* -radius * -sin(s)*1,
* radius * -sin(s)* drds * sin(s)- radius * cos(s)*drds * cos(s))
* div by radius
* => (cos s, sin s, -drds * (sin² + cos²))
*/
const matrix = this.matrix.inversed().transposed();
return (d, z) => {
const drdz = this.drdz(z);
return matrix.transformVector(V3.polar(1, d, -drdz)).toLength(this.normalDir);
};
}
implicitFunction() {
return (pWC) => {
const pLC = this.matrixInverse.transformPoint(pWC);
const radiusLC = pLC.lengthXY();
return this.rt(pLC.z) - radiusLC;
};
}
stPFunc() {
return (pWC) => {
const pLC = this.matrixInverse.transformPoint(pWC);
return new V3(atan2$7(pLC.y, pLC.x), pLC.z, 0);
};
}
}
Object.assign(RotationREqFOfZ.prototype, ImplicitSurface.prototype);
RotationREqFOfZ.prototype.sMin = 0;
RotationREqFOfZ.prototype.sMax = PI$13;
const { PI: PI$14, cos: cos$11, sin: sin$11, min: min$11, max: max$10, tan: tan$8, sign: sign$11, ceil: ceil$12, floor: floor$11, abs: abs$14, sqrt: sqrt$8, pow: pow$9, atan2: atan2$8, round: round$8 } = Math;
class SemiCylinderSurface extends ProjectedCurveSurface {
constructor(baseCurve, dir1, sMin, sMax, zMin = -Infinity, zMax = Infinity) {
super(baseCurve, dir1, sMin, sMax, zMin, zMax);
assertInst(SemiEllipseCurve, baseCurve);
//assert(!baseCurve.normal1.isPerpendicularTo(dir1), !baseCurve.normal1.isPerpendicularTo(dir1))
this.matrix = M4.forSys(baseCurve.f1, baseCurve.f2, dir1, baseCurve.center);
this.inverseMatrix = this.matrix.inversed();
this.normalDir = sign$11(this.baseCurve.normal.dot(this.dir));
this.normalMatrix = this.matrix.as3x3().inversed().transposed().scale(this.normalDir);
}
static semicylinder(radius) {
return new SemiCylinderSurface(new SemiEllipseCurve(V3.O, new V3(radius, 0, 0), new V3(0, radius, 0)), V3.Z, undefined, undefined);
}
/**
*
* @param anchorLC
* @param dirLC not necessarily unit
*/
static unitISLineTs(anchorLC, dirLC) {
const { x: ax, y: ay } = anchorLC;
const { x: dx, y: dy } = dirLC;
// this cylinder: x² + y² = 1
// line: p = anchorLC + t * dirLC
// split line equation into 3 component equations, insert into cylinder equation
// x = ax + t * dx
// y = ay + t * dy
// (ax² + 2 ax t dx + t²dx²) + (ay² + 2 ay t dy + t²dy²) = 1
// transform to form (a t² + b t + c = 0) and solve with pqFormula
const a = Math.pow(dx, 2) + Math.pow(dy, 2);
const b = 2 * (ax * dx + ay * dy);
const c = Math.pow(ax, 2) + Math.pow(ay, 2) - 1;
return pqFormula(b / a, c / a).filter(t => SemiEllipseCurve.XYLCValid(new V3(ax + dx * t, ay + dy * t, 0)));
}
getConstructorParameters() {
return [this.baseCurve, this.dir, this.sMin, this.sMax, this.tMin, this.tMax];
}
normalP(p) {
return this.normalMatrix.transformVector(this.inverseMatrix.transformPoint(p).xy()).unit();
}
loopContainsPoint(loop, p) {
assertVectors(p);
if (!this.containsPoint(p))
return OUTSIDE;
// create plane that goes through cylinder seam
const line = new L3(p, this.dir.unit());
const seamBase = this.baseCurve.at(PI$14);
const lineOut = this.dir.cross(this.normalP(p));
return Surface.loopContainsPointGeneral(loop, p, line, lineOut);
}
isTsForLine(line) {
assertInst(L3, line);
// transforming line manually has advantage that dir1 will not be renormalized,
// meaning that calculated values t for localLine are directly transferable to line
const dirLC = this.inverseMatrix.transformVector(line.dir1);
if (dirLC.isParallelTo(V3.Z)) {
// line is parallel to this.dir
return [];
}
const anchorLC = this.inverseMatrix.transformPoint(line.anchor);
assert(!SemiCylinderSurface.unitISLineTs(anchorLC, dirLC).length || !isNaN(SemiCylinderSurface.unitISLineTs(anchorLC, dirLC)[0]), 'sad ' + dirLC);
return SemiCylinderSurface.unitISLineTs(anchorLC, dirLC);
}
isCoplanarTo(surface) {
return this == surface ||
hasConstructor(surface, SemiCylinderSurface)
&& this.dir.isParallelTo(surface.dir)
&& this.containsSemiEllipse(surface.baseCurve, false);
}
like(surface) {
if (!this.isCoplanarTo(surface))
return false;
// normals need to point in the same direction (outwards or inwards) for both
const thisFacesOut = 0 < this.baseCurve.normal.dot(this.dir);
const objectFacesOut = 0 < surface.baseCurve.normal.dot(surface.dir);
return thisFacesOut == objectFacesOut;
}
containsSemiEllipse(ellipse, checkAABB = true) {
const projEllipse = ellipse.transform(M4.project(this.baseCurve.getPlane(), this.dir));
return this.baseCurve == ellipse || this.baseCurve.isColinearTo(projEllipse) &&
(!checkAABB || le(0, ellipse.transform(this.inverseMatrix).getAABB().min.y));
}
containsCurve(curve) {
if (curve instanceof L3) {
return this.containsLine(curve);
}
else if (curve instanceof SemiEllipseCurve) {
return this.containsSemiEllipse(curve);
}
else if (curve instanceof BezierCurve) {
return false;
}
else {
return super.containsCurve(curve);
}
}
implicitFunction() {
return (pWC) => {
const pLC = this.inverseMatrix.transformPoint(pWC);
const radiusLC = pLC.lengthXY();
const normalDir = Math.sign(this.baseCurve.normal.dot(this.dir));
return normalDir * (1 - radiusLC);
};
}
containsPoint(pWC) {
const pLC = this.inverseMatrix.transformPoint(pWC);
return SemiEllipseCurve.XYLCValid(pLC);
}
stP(pWC) {
assert(arguments.length == 1);
const pLC = this.inverseMatrix.transformPoint(pWC);
const u = SemiEllipseCurve.XYLCPointT(pLC);
return new V3(u, pLC.z, 0);
}
isCurvesWithSurface(surface2) {
if (surface2 instanceof PlaneSurface$1) {
return this.isCurvesWithPlane(surface2.plane);
}
else if (surface2 instanceof SemiCylinderSurface) {
if (surface2.dir.isParallelTo(this.dir)) {
const projEllipse = surface2.baseCurve.transform(M4.project(this.baseCurve.getPlane(), this.dir));
return this.baseCurve.isInfosWithEllipse(projEllipse).map(info => {
const lineDir = sign$11(this.normalP(info.p).cross(surface2.normalP(info.p)).dot(this.dir)) || 1;
return new L3(info.p, this.dir.times(lineDir));
});
}
else if (eq0(this.getCenterLine().distanceToLine(surface2.getCenterLine()))) {
assert(false);
}
else {
assert(false);
}
}
}
getCenterLine() {
return new L3(this.baseCurve.center, this.dir);
}
facesOutwards() {
return this.baseCurve.normal.dot(this.dir) > 0;
}
getSeamPlane() {
let normal = this.baseCurve.f1.cross(this.dir);
normal = normal.times(-sign$11(normal.dot(this.baseCurve.f2)));
return P3.normalOnAnchor(normal, this.baseCurve.center);
}
clipCurves(curves) {
return curves.flatMap(curve => curve.clipPlane(this.getSeamPlane()));
}
}
SemiCylinderSurface.UNIT = new SemiCylinderSurface(SemiEllipseCurve.UNIT, V3.Z, undefined, undefined, 0, 1);
SemiCylinderSurface.prototype.uStep = TAU / 32;
SemiCylinderSurface.prototype.vStep = 256;
const { PI: PI$15, cos: cos$12, sin: sin$12, min: min$12, max: max$11, tan: tan$9, sign: sign$12, ceil: ceil$13, floor: floor$12, abs: abs$15, sqrt: sqrt$9, pow: pow$10, atan2: atan2$9, round: round$9 } = Math;
class SemiEllipsoidSurface extends EllipsoidSurface {
constructor(center, f1, f2, f3) {
super(center, f1, f2, f3);
this.center = center;
this.f1 = f1;
this.f2 = f2;
this.f3 = f3;
assertVectors(center, f1, f2, f3);
this.matrix = M4.forSys(f1, f2, f3, center);
this.inverseMatrix = this.matrix.inversed();
this.normalDir = sign$12(this.f1.cross(this.f2).dot(this.f3));
this.pLCNormalWCMatrix = this.matrix.as3x3().inversed().transposed().scale(this.normalDir);
this.pWCNormalWCMatrix = this.pLCNormalWCMatrix.times(this.inverseMatrix);
}
static unitArea(contour) {
const totalArea = contour.map(edge => {
if (edge.curve instanceof PICurve$1) {
const points = edge.curve.calcSegmentPoints(edge.aT, edge.bT, edge.a, edge.b, edge.aT > edge.bT, true);
let sum = 0;
for (let i = 0; i < points.length - 1; i++) {
const p = points[i], ppp = points[i + 1];
sum += (abs$15(p.angleXY()) + abs$15(ppp.angleXY())) / 2 * (ppp.z - p.z);
}
return sum;
}
else if (edge.curve instanceof SemiEllipseCurve) {
const f = (t) => {
const at = edge.curve.at(t), tangent = edge.curve.tangentAt(t);
const angleXY = abs$15(at.angleXY());
//const arcLength = angleXY * Math.sqrt(1 - at.z ** 2) ( == at.lengthXY())
//const scaling = tangent.z / at.lengthXY()
return angleXY * tangent.z;
};
const val = glqInSteps(f, edge.aT, edge.bT, 1);
return val;
}
else {
assertNever();
}
}).sum();
return totalArea;
}
/**
* unit sphere: x² + y² + z² = 1
* line: p = anchor + t * dir |^2
* p² = (anchor + t * dir)^2
* 1 == (anchor + t * dir)^2
* 1 == anchor DOT anchor + 2 * anchor * t * dir + t² * dir DOT dir
*/
static unitISTsWithLine(anchor, dir) {
// for 0 = a t² + b t + c
const a = dir.dot(dir);
const b = 2 * anchor.dot(dir);
const c = anchor.dot(anchor) - 1;
return pqFormula(b / a, c / a).filter(t => le(0, anchor.y + t * dir.y));
}
/**
* unit sphere: x² + y² + z² = 1
* plane: normal1 DOT p = w
*/
static unitISCurvesWithPlane(plane) {
const distPlaneCenter = Math.abs(plane.w);
if (lt(distPlaneCenter, 1)) {
// result is a circle
// radius of circle: imagine right angled triangle (origin -> center of intersection circle -> point on
// intersection circle) pythagoras: 1² == distPlaneCenter² + isCircleRadius² => isCircleRadius == sqrt(1 -
// distPlaneCenter²)
const isCircleRadius = Math.sqrt(1 - Math.pow(distPlaneCenter, 2));
const anchorY = plane.normal1.y * plane.w;
const d = abs$15(distPlaneCenter * isCircleRadius);
if (le(anchorY, -d) && !eq0(distPlaneCenter)) {
return [];
}
else if (le(anchorY, 0) && !plane.normal1.isParallelTo(V3.Y)) {
let f1 = plane.normal1.isParallelTo(V3.Y) ? V3.Z : plane.normal1.cross(V3.Y).toLength(isCircleRadius);
const f2 = f1.cross(plane.normal1);
const minEta = -anchorY / f2.y, minT = max$11(0, Math.asin(minEta));
return [new SemiEllipseCurve(plane.anchor, f1, f2, minT, PI$15 - minT)];
}
else {
const f2 = (plane.normal1.isParallelTo(V3.Y)
? V3.X
: plane.normal1.cross(V3.Y)).toLength(isCircleRadius);
const f1 = f2.cross(plane.normal1);
const minXi = eq0(f1.y) ? -1 : -anchorY / f1.y, maxT = Math.acos(max$11(-1, minXi - NLA_PRECISION));
return [new SemiEllipseCurve(plane.anchor, f1.negated(), f2, PI$15 - maxT, PI$15),
new SemiEllipseCurve(plane.anchor, f1, f2.negated(), 0, maxT)];
}
}
else {
return [];
}
}
static unitISCurvesWithEllipsoidSurface(surface) {
if (surface.isSphere()) {
const surfaceRadius = surface.f1.length();
const surfaceCenterDist = surface.center.length();
if (le(1, surfaceCenterDist - surfaceRadius) || le(surfaceCenterDist + surfaceRadius, 1) || le(surfaceCenterDist - surfaceRadius, -1)) {
return [];
}
else {
// origin, surface.center and points on the intersection curves form a triangle.
// the height on the segment origin - surface.center is the radius of the is curves
// the distance from the origin to the lot point is the distance to the intersection plane
function heron(a, b, c) {
const p = (a + b + c) / 2;
return sqrt$9(p * (p - a) * (p - b) * (p - c));
}
const triangleArea = heron(1, surfaceRadius, surfaceCenterDist);
const radius = triangleArea * 2 / surfaceCenterDist;
const isCurvesCenterDist = sign$12(1 + Math.pow(surfaceCenterDist, 2) - Math.pow(surfaceRadius, 2)) * sqrt$9(1 - Math.pow(radius, 2));
const plane = new P3(surface.center.unit(), isCurvesCenterDist);
return SemiEllipsoidSurface.unitISCurvesWithPlane(plane.flipped());
}
}
assertNever();
}
static unitISCurvesWithSemiCylinderSurface(surface) {
if (new L3(surface.baseCurve.center, surface.dir).containsPoint(V3.O)) {
const projEllipse = surface.baseCurve.transform(M4.project(new P3(surface.dir, 0)));
const f1Length = projEllipse.f1.length(), f2Length = projEllipse.f2.length();
if (lt(1, min$12(f1Length, f2Length)))
return [];
if (projEllipse.isCircular()) {
const distISCurveCenter = Math.sqrt(1 - Math.pow(min$12(1, f1Length), 2));
const isCurveCenter = (surface.dir.y < 0 ? surface.dir.negated() : surface.dir).times(distISCurveCenter);
// isCurve.at(t).y = isCurveCenter.y + projEllipse.f1.y * cos(t) + projEllipse.f2.y * sin(t) = 0
return [new SemiEllipseCurve(isCurveCenter, projEllipse.f1, projEllipse.f2)];
}
}
assert(false);
}
static sphere(radius, center = V3.O) {
assertNumbers(radius);
return new SemiEllipsoidSurface(center, new V3(radius, 0, 0), new V3(0, radius, 0), new V3(0, 0, radius));
}
/**
* x²/a² + y²/b² + z²/c² = 1
*/
static forABC(a, b, c, center = V3.O) {
return new SemiEllipsoidSurface(center, new V3(a, 0, 0), new V3(0, b, 0), new V3(0, 0, c));
}
static calculateAreaSpheroid(a, b, c, edges) {
assertf(() => a.isPerpendicularTo(b));
assertf(() => b.isPerpendicularTo(c));
assertf(() => c.isPerpendicularTo(a));
// handling discontinuities:
// option 1: check for intersections with baseline, if there are any integrate parts separetely
// "rotate" the edge so that there are no overlaps
const matrix = M4.forSys(a, b, c), inverseMatrix = matrix.inversed();
const circleRadius = a.length();
const c1 = c.unit();
const totalArea = edges.map(edge => {
if (edge.curve instanceof SemiEllipseCurve) {
const f = (t) => {
const at = edge.curve.at(t), tangent = edge.tangentAt(t);
const localAt = inverseMatrix.transformPoint(at);
const angleXY = localAt.angleXY();
const arcLength = angleXY * circleRadius * Math.sqrt(1 + Math.pow(localAt.z, 2));
const scaling = Math.sqrt(1 + Math.pow(c1.dot(tangent), 2));
return arcLength * scaling;
};
const val = glqInSteps(f, edge.aT, edge.bT, 1);
return val;
}
else {
assertNever();
}
}).sum();
return totalArea;
}
equals(obj) {
return this == obj ||
Object.getPrototypeOf(obj) == this.constructor.prototype
&& this.matrix.equals(obj.matrix);
}
edgeLoopCCW(loop) {
return SemiEllipsoidSurface.unitArea(loop.map(edge => edge.transform(this.inverseMatrix))) > 0;
//let totalAngle = 0
//for (let i = 0; i < contour.length; i++) {
// const ipp = (i + 1) % contour.length
// const edge = contour[i], nextEdge = contour[ipp]
// totalAngle += edge.bDir.angleRelativeNormal(nextEdge.aDir, this.normalP(edge.b))
//}
//return le(0, totalAngle)
}
like(object) {
if (!this.isCoplanarTo(object))
return false;
// normals need to point in the same direction (outwards or inwards) for both
return this.matrix.determinant3() * object.matrix.determinant3() > 0;
}
rootPoints() {
}
toMesh() {
return ParametricSurface.prototype.toMesh.call(this);
}
getConstructorParameters() {
return [this.center, this.f1, this.f2, this.f3];
}
clipCurves(curves) {
return curves.flatMap(curve => curve.clipPlane(this.getSeamPlane()));
}
isCurvesWithPCS(surface) {
let curves2 = ParametricSurface.isCurvesParametricImplicitSurface(surface, this, 0.1, 0.1 / surface.dir.length(), 0.05);
curves2 = this.clipCurves(curves2);
curves2 = surface.clipCurves(curves2);
return curves2;
const surfaceLC = surface.transform(this.inverseMatrix);
//const lcMinZ0RelO =
const baseCurveLC = surfaceLC.baseCurve.project(new P3(surfaceLC.dir, 0));
const ists = baseCurveLC.isTsWithSurface(EllipsoidSurface.UNIT);
const insideIntervals = getIntervals(ists, baseCurveLC.tMin, baseCurveLC.tMax)
.filter(([a, b]) => baseCurveLC.at((a + b) / 2).length() < 1);
const projectedCurves = [0, 1].map(id => {
return (t) => {
const atSqr = snap(baseCurveLC.at(t).squared(), 1);
const lineISTs = sqrt$9(1 - atSqr);
//assert(!isNaN(lineISTs))
return eq0(lineISTs)
? baseCurveLC.at(t)
: baseCurveLC.at(t).plus(surfaceLC.dir.times(sign$12(id - 0.5) * lineISTs));
};
});
const dProjectedCurves = [0, 1].map(id => {
return (t) => {
// d/dt sqrt(1 - baseCurveLC.at(t).squared())
// = -1/2 * 1/sqrt(1 - baseCurveLC.at(t).squared()) * -2*baseCurveLC.at(t) * baseCurveLC.tangentAt(t)
const atSqr = snap(baseCurveLC.at(t).squared(), 1);
const lineISTs = baseCurveLC.at(t).times(-1 / sqrt$9(1 - atSqr)).dot(baseCurveLC.tangentAt(t));
//assert(!isNaN(lineISTs))
return baseCurveLC.tangentAt(t).plus(surfaceLC.dir.times(sign$12(id - 0.5) * lineISTs));
};
});
//const f2 = t => sqrt(1 - baseCurveLC.at(t).squared())
//const df2 = t => baseCurveLC.at(t).times(-1 / sqrt(1 -
// baseCurveLC.at(t).squared())).dot(baseCurveLC.tangentAt(t)) checkDerivate(f2, df2, 0.31, 0.60)
const curves = [];
for (const [aT, bT] of insideIntervals) {
//const aLine = new L3(baseCurveLC.at(aT), surfaceLC.dir1)
//const a = EllipsoidSurface.UNIT.isTsForLine(aLine).map(t => aLine.at(t))
//const bLine = new L3(baseCurveLC.at(bT), surfaceLC.dir1)
//const b = EllipsoidSurface.UNIT.isTsForLine(bLine).map(t => bLine.at(t))
for (const i of [0, 1]) {
const f = (t) => projectedCurves[i](t).y;
const df = (t) => dProjectedCurves[i](t).y;
checkDerivate(f, df, aT + 0.1, bT - 0.1);
const tsAtY0 = getRoots(f, aT + NLA_PRECISION, bT - NLA_PRECISION, 1 / (1 << 11), df);
const ii2 = getIntervals(tsAtY0, aT, bT).filter(([a, b]) => f((a + b) / 2) > 0);
for (const [aT2, bT2] of ii2) {
let aP = projectedCurves[i](aT2), bP = projectedCurves[i](bT2);
0 === i && ([aP, bP] = [bP, aP]);
assert(EllipsoidSurface.UNIT.containsPoint(aP));
assert(EllipsoidSurface.UNIT.containsPoint(bP));
curves.push(PICurve$1.forStartEnd(surface, this, this.matrix.transformPoint(bP), this.matrix.transformPoint(aP), undefined, 1));
}
}
}
return surface.clipCurves(curves);
}
isCurvesWithSurface(surface) {
if (surface instanceof PlaneSurface$1) {
return this.isCurvesWithPlane(surface.plane);
}
else if (surface instanceof SemiCylinderSurface) {
return this.isCurvesWithSemiCylinderSurface(surface);
}
else if (surface instanceof SemiEllipsoidSurface) {
const surfaceLC = surface.transform(this.inverseMatrix);
const curves = SemiEllipsoidSurface.unitISCurvesWithEllipsoidSurface(surfaceLC)
.map(c => c.transform(this.matrix));
return surface.clipCurves(curves);
}
else if (surface instanceof ProjectedCurveSurface) {
return this.isCurvesWithPCS(surface);
}
else if (surface instanceof ParametricSurface) {
let curves2 = ParametricSurface.isCurvesParametricImplicitSurface(surface, this, 0.1, 0.1, 0.05);
curves2 = this.clipCurves(curves2);
curves2 = surface.clipCurves(curves2);
return curves2;
}
else {
assert(false);
}
}
isCurvesWithPlane(plane) {
const planeLC = plane.transform(this.inverseMatrix);
return SemiEllipsoidSurface.unitISCurvesWithPlane(planeLC).map(c => c.transform(this.matrix));
}
isCurvesWithSemiCylinderSurface(surface) {
if (L3.containsPoint(surface.baseCurve.center, surface.dir, this.center)) {
assert(this.isSphere());
const ellipseProjected = surface.baseCurve.transform(M4.project(surface.baseCurve.getPlane(), surface.dir));
if (ellipseProjected.isCircular()) {
const thisRadius = this.f1.length();
const surfaceRadius = ellipseProjected.f1.length();
// sphereRadius² = distanceISFromCenter² + isRadius²
if (eq(thisRadius, surfaceRadius)) {
// return
}
else if (surfaceRadius < thisRadius) {
}
assert(false);
}
}
return this.isCurvesWithPCS(surface);
}
isTsForLine(line) {
assertInst(L3, line);
// transforming line manually has advantage that dir1 will not be renormalized,
// meaning that calculated values t for localLine are directly transferable to line
const anchorLC = this.inverseMatrix.transformPoint(line.anchor);
const dirLC = this.inverseMatrix.transformVector(line.dir1);
return SemiEllipsoidSurface.unitISTsWithLine(anchorLC, dirLC);
}
isCoplanarTo(surface) {
if (this === surface)
return true;
if (!hasConstructor(surface, SemiEllipsoidSurface))
return false;
if (!this.center.like(surface.center))
return false;
if (this.isSphere())
return surface.isSphere() && eq(this.f1.length(), this.f2.length());
const otherMatrixLC = this.inverseMatrix.times(surface.matrix);
// Ellipsoid with matrix otherMatrixLC is unit sphere iff otherMatrixLC is orthogonal
return otherMatrixLC.is3x3() && otherMatrixLC.isOrthogonal();
}
containsEllipse(ellipse) {
const ellipseLC = ellipse.transform(this.inverseMatrix);
const distEllipseLCCenter = ellipseLC.center.length();
const correctRadius = Math.sqrt(1 - Math.pow(distEllipseLCCenter, 2));
return lt(distEllipseLCCenter, 1)
&& ellipseLC.isCircular()
&& ellipseLC.f1.hasLength(correctRadius);
//&& le(0, ellipseLC.getAABB().min.y)
}
containsCurve(curve) {
if (curve instanceof SemiEllipseCurve) {
return this.containsEllipse(curve);
}
else {
return super.containsCurve(curve);
}
}
transform(m4) {
return new SemiEllipsoidSurface(m4.transformPoint(this.center), m4.transformVector(this.f1), m4.transformVector(this.f2), m4.transformVector(this.f3).times(m4.isMirroring() ? -1 : 1));
}
isInsideOut() {
return this.f1.cross(this.f2).dot(this.f3) < 0;
}
//implicitFunction() {
// return (pWC) => {
// const pLC = this.inverseMatrix.transformPoint(pWC)
// return (pLC.y > 0
// ? pLC.length() - 1
// : (-pLC.y + Math.hypot(pLC.x, pLC.z) - 1)) * this.normalDir
// }
//}
//didp(pWC) {
// const pLC = this.inverseMatrix.transformPoint(pWC)
// const didpLC = (pLC.y > 0
// ? pLC.unit()
// : V(pLC.x / Math.hypot(pLC.x, pLC.z), -1, pLC.z / Math.hypot(pLC.x, pLC.z))).times(this.normalDir)
// return this.inverseMatrix.transformVector(didpLC)
//}
flipped() {
return new SemiEllipsoidSurface(this.center, this.f1, this.f2, this.f3.negated());
}
normalSTFunc() {
// ugh
// paramtric ellipsoid point q(a, b)
// normal1 == (dq(a, b) / da) X (dq(a, b) / db) (Cross product of partial derivatives
// normal1 == cos b * (f2 X f3 * cos b * cos a + f3 X f1 * cos b * sin a + f1 X f2 * sin b)
return (a, b) => {
const { f1, f2, f3 } = this;
const normal = f2.cross(f3).times(Math.cos(b) * Math.cos(a))
.plus(f3.cross(f1).times(Math.cos(b) * Math.sin(a)))
.plus(f1.cross(f2).times(Math.sin(b)))
.unit();
return normal;
};
}
normalP(p) {
return this.pLCNormalWCMatrix.transformVector(this.inverseMatrix.transformPoint(p)).unit();
}
normalST(s, t) {
return this.pLCNormalWCMatrix.transformVector(V3.sphere(s, t)).unit();
}
stPFunc() {
return (pWC) => {
const pLC = this.inverseMatrix.transformPoint(pWC);
const alpha = abs$15(pLC.angleXY());
const beta = Math.asin(clamp(pLC.z, -1, 1));
assert(isFinite(alpha));
assert(isFinite(beta));
return new V3(alpha, beta, 0);
};
}
pSTFunc() {
// this(a, b) = f1 cos a cos b + f2 sin a cos b + f2 sin b
return (alpha, beta) => {
return this.matrix.transformPoint(V3.sphere(alpha, beta));
};
}
isSphere() {
return eq(this.f1.length(), this.f2.length())
&& eq(this.f2.length(), this.f3.length())
&& eq(this.f3.length(), this.f1.length())
&& this.f1.isPerpendicularTo(this.f2)
&& this.f2.isPerpendicularTo(this.f3)
&& this.f3.isPerpendicularTo(this.f1);
}
isVerticalSpheroid() {
return eq(this.f1.length(), this.f2.length())
&& this.f1.isPerpendicularTo(this.f2)
&& this.f2.isPerpendicularTo(this.f3)
&& this.f3.isPerpendicularTo(this.f1);
}
mainAxes() {
// q(a, b) = f1 cos a cos b + f2 sin a cos b + f3 sin b
// q(s, t, u) = s * f1 + t * f2 + u * f3 with s² + t² + u² = 1
// (del q(a, b) / del a) = f1 (-sin a) cos b + f2 cos a cos b
// (del q(a, b) / del b) = f1 cos a (-sin b) + f2 sin a (-sin b) + f2 cos b
// del q(s, t, u) / del a = -t f1 + s f2
// (del q(a, b) / del a) DOT q(a, b) == 0
// (f1 (-sin a) cos b + f2 cos a cos b) DOT (f1 cos a cos b + f2 sin a cos b + f2 sin b) == 0
// (del q(a, b) / del b) DOT q(a, b) == 0
// (f1 cos a (-sin b) + f2 sin a (-sin b) + f2 cos b) DOT (f1 cos a cos b + f2 sin a cos b + f2 sin b) == 0
// Solve[
// (f1 (-sin a) cos b + f2 cos a cos b) * (f1 cos a cos b + f2 sin a cos b + f2 sin b) = 0,
// (f1 cos a (-sin b) + f2 sin a (-sin b) + f2 cos b) * (f1 cos a cos b + f2 sin a cos b + f2 sin b) = 0}, a, b]
const { f1, f2, f3 } = this;
if (eq0(f1.dot(f2)) && eq0(f2.dot(f3)) && eq0(f3.dot(f1))) {
return this;
}
//const f = ([a, b], x?) => {
// const sinA = Math.sin(a), cosA = Math.cos(a), sinB = Math.sin(b), cosB = Math.cos(b)
// const centerToP = V3.add(f1.times(cosA * cosB), f2.times(sinA * cosB), f3.times(sinB))
// const centerToPdelA = f1.times(-sinA * cosB).plus(f2.times(cosA * cosB))
// const centerToPdelB = V3.add(f1.times(cosA * -sinB), f2.times(sinA * -sinB), f3.times(cosB))
// x && console.log(centerToP.sce, centerToPdelA.sce, centerToPdelB.sce)
// return [centerToP.dot(centerToPdelA), centerToP.dot(centerToPdelB)]
//}
//const mainF1Params = newtonIterate(f, [0, 0], 8), mainF1 = this.pSTFunc()(mainF1Params[0], mainF1Params[1])
//console.log(f(mainF1Params, 1).sce)
//const mainF2Params = newtonIterate(f, this.stPFunc()(f2.rejectedFrom(mainF1)).toArray(2), 8),
// mainF2 = this.pSTFunc()(mainF2Params[0], mainF2Params[1])
//console.log(this.normalSTFunc()(mainF2Params[0], mainF2Params[1]).sce)
//assert(mainF1.isPerpendicularTo(mainF2), mainF1, mainF2, mainF1.dot(mainF2), mainF1Params)
//const mainF3Params = this.stPFunc()(mainF1.cross(mainF2)), mainF3 = this.pSTFunc()(mainF3Params[0],
// mainF3Params[1]) return new EllipsoidSurface(this.center, mainF1, mainF2, mainF3)
const { U, SIGMA } = this.matrix.svd3();
assert(SIGMA.isDiagonal());
assert(U.isOrthogonal());
const U_SIGMA = U.times(SIGMA);
// column vectors of U_SIGMA
const [mainF1, mainF2, mainF3] = arrayFromFunction(3, i => new V3(U_SIGMA.m[i], U_SIGMA.m[i + 4], U_SIGMA.m[i + 8]));
return new SemiEllipsoidSurface(this.center, mainF1, mainF2, mainF3);
}
containsPoint(p) {
return eq0(this.implicitFunction()(p));
}
boundsFunction() {
return (a, b) => between(a, 0, PI$15) && between(b, -PI$15, PI$15);
}
volume() {
return 4 / 3 * Math.PI * this.f1.dot(this.f2.cross(this.f3));
}
loopContainsPoint(loop, p) {
if (!this.containsPoint(p))
return PointVsFace.OUTSIDE;
assertVectors(p);
const pLCXY = this.inverseMatrix.transformPoint(p).withElement('z', 0);
const testLine = new SemiEllipseCurve(this.center, this.f3, pLCXY.likeO() ? this.f2 : this.matrix.transformVector(pLCXY.unit()));
const pT = testLine.pointT(p);
if (P3.normalOnAnchor(this.f2.unit(), this.center).containsPoint(p)) {
let edgeT;
return loop.some(edge => edge.curve.containsPoint(p) && le(edge.minT, edgeT = edge.curve.pointT(p)) && le(edgeT, edge.maxT))
? PointVsFace.ON_EDGE
: PointVsFace.OUTSIDE;
}
const lineOut = testLine.normal;
const testPlane = P3.normalOnAnchor(testLine.normal, p);
const colinearEdges = loop.map((edge) => testLine.isColinearTo(edge.curve));
let inside = false;
function logIS(isP) {
const isT = testLine.pointT(isP);
if (eq(pT, isT)) {
return true;
}
else if (pT < isT && le(isT, PI$15)) {
inside = !inside;
}
}
for (let edgeIndex = 0; edgeIndex < loop.length; edgeIndex++) {
const edge = loop[edgeIndex];
const nextEdgeIndex = (edgeIndex + 1) % loop.length, nextEdge = loop[nextEdgeIndex];
//console.log(edge.toSource()) {p:V(2, -2.102, 0),
if (colinearEdges[edgeIndex]) {
let edgeT;
if (edge.curve.containsPoint(p) && le(edge.minT, edgeT = edge.curve.pointT(p)) && le(edgeT, edge.maxT)) {
return PointVsFace.ON_EDGE;
}
// edge colinear to intersection
const nextInside = colinearEdges[nextEdgeIndex] || dotCurve(lineOut, nextEdge.aDir, nextEdge.aDDT) < 0;
if (!nextInside && testLine.containsPoint(edge.b)) {
if (logIS(edge.b))
return PointVsFace.ON_EDGE;
}
}
else {
for (const edgeT of edge.edgeISTsWithPlane(testPlane)) {
if (edgeT == edge.bT) {
if (!testLine.containsPoint(edge.b))
continue;
// endpoint lies on intersection testLine
const edgeInside = dotCurve2(edge.curve, edge.bT, lineOut, -sign$12(edge.deltaT())) < 0; // TODO:
// bDDT
// negated?
const nextInside = colinearEdges[nextEdgeIndex] || dotCurve(lineOut, nextEdge.aDir, nextEdge.aDDT) < 0;
if (edgeInside != nextInside) {
if (logIS(edge.b))
return PointVsFace.ON_EDGE;
}
}
else if (edgeT != edge.aT) {
const p = edge.curve.at(edgeT);
if (!testLine.containsPoint(p))
continue;
// edge crosses testLine, neither starts nor ends on it
if (logIS(p))
return PointVsFace.ON_EDGE;
// TODO: tangents?
}
}
}
}
return inside ? PointVsFace.INSIDE : PointVsFace.OUTSIDE;
}
zDirVolumeForLoop2(loop) {
const angles = this.inverseMatrix.getZ().toAngles();
const T = M4.rotateY(-angles.theta).times(M4.rotateZ(-angles.phi)).times(this.inverseMatrix);
const rot90x = M4.rotateX(PI$15 / 2);
let totalVolume = 0;
assert(V3.X.isParallelTo(T.transformVector(V3.Z)));
//const zDistanceFactor = toT.transformVector(V3.Z).length()
loop.map(edge => edge.transform(T)).forEach((edge, edgeIndex, edges) => {
const nextEdgeIndex = (edgeIndex + 1) % edges.length, nextEdge = edges[nextEdgeIndex];
function f(t) {
const at2d = edge.curve.at(t).withElement('x', 0);
const result = 1 / 3 * (1 - (Math.pow(at2d.y, 2) + Math.pow(at2d.z, 2))) * edge.tangentAt(t).dot(rot90x.transformVector(at2d.unit()));
return result;
}
//if (edge.)
if (edge.b.like(V3.X)) {
const angleDiff = (edge.bDir.angleRelativeNormal(nextEdge.aDir, V3.X) + 2 * PI$15) % (2 * PI$15);
totalVolume += 2 / 3 * angleDiff;
}
if (edge.b.like(V3.X.negated())) {
const angleDiff = (edge.bDir.angleRelativeNormal(nextEdge.aDir, V3.X) + 2 * PI$15) % (2 * PI$15);
totalVolume += 2 / 3 * angleDiff;
}
const volume = gaussLegendreQuadrature24(f, edge.aT, edge.bT);
totalVolume += volume;
});
return totalVolume * this.f1.dot(this.f2.cross(this.f3));
}
surfaceAreaApprox() {
// See https://en.wikipedia.org/wiki/Ellipsoid#Surface_area
const mainAxes = this.mainAxes(), a = mainAxes.f1.length(), b = mainAxes.f2.length(), c = mainAxes.f3.length();
const p = 1.6075;
return 4 * PI$15 * Math.pow((Math.pow(a * b, p) + Math.pow(b * c, p) + Math.pow(c * a, p)) / 3, 1 / p);
}
surfaceArea() {
// See https://en.wikipedia.org/wiki/Ellipsoid#Surface_area
const mainAxes = this.mainAxes(), f1l = mainAxes.f1.length(), f2l = mainAxes.f2.length(), f3l = mainAxes.f3.length(), [c, b, a] = [f1l, f2l, f3l].sort(MINUS);
// https://en.wikipedia.org/w/index.php?title=Spheroid&oldid=761246800#Area
function spheroidArea(a, c) {
if (c < a) {
const eccentricity2 = 1 - Math.pow(c, 2) / Math.pow(a, 2);
const eccentricity = Math.sqrt(eccentricity2);
return 2 * PI$15 * Math.pow(a, 2) * (1 + (1 - eccentricity2) / Math.sqrt(eccentricity) * Math.atanh(eccentricity));
}
else {
const eccentricity = Math.sqrt(1 - Math.pow(a, 2) / Math.pow(c, 2));
return 2 * PI$15 * Math.pow(a, 2) * (1 + c / a / eccentricity * Math.asin(eccentricity));
}
}
if (eq(a, b)) {
return spheroidArea(a, c);
}
else if (eq(b, c)) {
return spheroidArea(b, a);
}
else if (eq(c, a)) {
return spheroidArea(c, b);
}
const phi = Math.acos(c / a);
const k2 = Math.pow(a, 2) * (Math.pow(b, 2) - Math.pow(c, 2)) / (Math.pow(b, 2) * (Math.pow(a, 2) - Math.pow(c, 2)));
const incompleteEllipticInt1 = gaussLegendreQuadrature24(phi => Math.pow(1 - k2 * Math.pow(Math.sin(phi), 2), -0.5), 0, phi);
const incompleteEllipticInt2 = gaussLegendreQuadrature24(phi => Math.pow(1 - k2 * Math.pow(Math.sin(phi), 2), 0.5), 0, phi);
return 2 * PI$15 * Math.pow(c, 2) + 2 * PI$15 * a * b / Math.sin(phi) * (incompleteEllipticInt2 * Math.pow(Math.sin(phi), 2) + incompleteEllipticInt1 * Math.pow(Math.cos(phi), 2));
}
getSeamPlane() {
const plane = P3.forAnchorAndPlaneVectors(this.center, this.f1, this.f3);
return plane.normal1.dot(this.f2) < 0 ? plane : plane.flipped();
}
asEllipsoidSurface() {
return new EllipsoidSurface(this.center, this.f1, this.f2, this.f3);
}
getExtremePoints() {
assert(this.isSphere());
const thisRadius = this.f1.length();
// points on the edge of the hemisphere don't need to be included, because if they can at most be on the edge
// of a face hemisphere can be orientated anyway, so dot with this.f2 to make sure they are "inside"
return [V3.X, V3.X.negated(), V3.Y, V3.Y.negated(), V3.Z, V3.Z.negated()]
.filter(p => lt(0, p.dot(this.f2)))
.map(p => p.times(thisRadius).plus(this.center));
}
}
SemiEllipsoidSurface.UNIT = new SemiEllipsoidSurface(V3.O, V3.X, V3.Y, V3.Z);
SemiEllipsoidSurface.prototype.uStep = PI$15 / 16;
SemiEllipsoidSurface.prototype.vStep = PI$15 / 16;
SemiEllipsoidSurface.prototype.sMin = 0;
SemiEllipsoidSurface.prototype.sMax = PI$15;
SemiEllipsoidSurface.prototype.tMin = -PI$15 / 2;
SemiEllipsoidSurface.prototype.tMax = PI$15 / 2;
class PlaneSurface$1 extends ParametricSurface {
constructor(plane, right = plane.normal1.getPerpendicular().unit(), up = plane.normal1.cross(right).unit(), sMin = -100, sMax = 100, tMin = -100, tMax = 100) {
super();
this.plane = plane;
this.right = right;
this.up = up;
this.sMin = sMin;
this.sMax = sMax;
this.tMin = tMin;
this.tMax = tMax;
assertInst(P3, plane);
assert(this.right.cross(this.up).like(this.plane.normal1));
this.matrix = M4.forSys(right, up, plane.normal1, plane.anchor);
}
static throughPoints(a, b, c) {
return new PlaneSurface$1(P3.throughPoints(a, b, c));
}
isCoplanarTo(surface) {
return surface instanceof PlaneSurface$1 && this.plane.isCoplanarToPlane(surface.plane);
}
isTsForLine(line) {
return line.isTsWithPlane(this.plane);
}
like(surface) {
return surface instanceof PlaneSurface$1 && this.plane.like(surface.plane);
}
pST(s, t) {
return this.matrix.transformPoint(new V3(s, t, 0));
}
implicitFunction() {
return p => this.plane.distanceToPointSigned(p);
}
isCurvesWithSurface(surface2) {
if (surface2 instanceof PlaneSurface$1) {
return this.isCurvesWithPlane(surface2.plane);
}
return super.isCurvesWithSurface(surface2);
}
isCurvesWithPlane(plane) {
if (this.plane.isParallelToPlane(plane)) {
return [];
}
return [this.plane.intersectionWithPlane(plane)];
}
edgeLoopCCW(contour) {
return isCCW(contour.flatMap(edge => edge.points()), this.plane.normal1);
// let totalAngle = 0
// for (let i = 0; i < contour.length; i++) {
// const ipp = (i + 1) % contour.length
// const edge = contour[i], nextEdge = contour[ipp]
// assert(edge.b.like(nextEdge.a), 'edges dont form a loop')
// if (edge.curve instanceof SemiEllipseCurve) {
// totalAngle += edge.rotViaPlane(this.plane.normal1)
// // console.log(edge.toString(), edge.rotViaPlane(this.plane.normal1))
// }
// totalAngle += edge.bDir.angleRelativeNormal(nextEdge.aDir, this.plane.normal1)
// }
// const result = totalAngle > 0
// const result2 = PlaneFace.prototype.calculateArea.apply({surface: this, contour: contour}).area > 0
// //assert (result == result2)
// return result2
}
loopContainsPoint(loop, p) {
const dir = this.right.plus(this.up.times(0.123)).unit();
const line = new L3(p, dir);
const lineOut = dir.cross(this.plane.normal1);
return Surface.loopContainsPointGeneral(loop, p, line, lineOut);
}
stPFunc() {
const matrixInverse = this.matrix.inversed();
return function (pWC) {
return matrixInverse.transformPoint(pWC);
};
}
pointFoot(pWC) {
return this.stP(pWC);
}
normalP(pWC) {
return this.plane.normal1;
}
containsPoint(p) {
return this.plane.containsPoint(p);
}
containsCurve(curve) {
return this.plane.containsCurve(curve);
}
transform(m4) {
return new PlaneSurface$1(this.plane.transform(m4));
}
flipped() {
return new PlaneSurface$1(this.plane.flipped(), this.right, this.up.negated());
}
getConstructorParameters() {
return [this.plane, this.right, this.up];
}
toMesh(xMin = -10, xMax = 10, yMin = -10, yMax = 10) {
const mesh = new Mesh()
.addIndexBuffer('TRIANGLES')
.addVertexBuffer('normals', 'LGL_Normal');
const matrix = M4.forSys(this.right, this.up, this.plane.normal1, this.plane.anchor);
mesh.vertices = [V(xMin, yMin), V(xMax, yMin), V(xMin, yMax), V(xMax, yMax)].map(p => matrix.transformPoint(p));
mesh.normals = arrayFromFunction(4, i => this.plane.normal1);
pushQuad(mesh.TRIANGLES, false, 0, 1, 2, 3);
mesh.compile();
return mesh;
}
dpds() {
return () => this.right;
}
dpdt() {
return () => this.up;
}
equals(obj) {
return undefined;
}
didp(pWC) {
return this.plane.normal1;
}
}
var TINF_OK = 0;
var TINF_DATA_ERROR = -3;
function Tree() {
this.table = new Uint16Array(16); /* table of code length counts */
this.trans = new Uint16Array(288); /* code -> symbol translation table */
}
function Data(source, dest) {
this.source = source;
this.sourceIndex = 0;
this.tag = 0;
this.bitcount = 0;
this.dest = dest;
this.destLen = 0;
this.ltree = new Tree(); /* dynamic length/symbol tree */
this.dtree = new Tree(); /* dynamic distance tree */
}
/* --------------------------------------------------- *
* -- uninitialized global data (static structures) -- *
* --------------------------------------------------- */
var sltree = new Tree();
var sdtree = new Tree();
/* extra bits and base tables for length codes */
var length_bits = new Uint8Array(30);
var length_base = new Uint16Array(30);
/* extra bits and base tables for distance codes */
var dist_bits = new Uint8Array(30);
var dist_base = new Uint16Array(30);
/* special ordering of code length codes */
var clcidx = new Uint8Array([
16, 17, 18, 0, 8, 7, 9, 6,
10, 5, 11, 4, 12, 3, 13, 2,
14, 1, 15
]);
/* used by tinf_decode_trees, avoids allocations every call */
var code_tree = new Tree();
var lengths = new Uint8Array(288 + 32);
/* ----------------------- *
* -- utility functions -- *
* ----------------------- */
/* build extra bits and base tables */
function tinf_build_bits_base(bits, base, delta, first) {
var i, sum;
/* build bits table */
for (i = 0; i < delta; ++i) bits[i] = 0;
for (i = 0; i < 30 - delta; ++i) bits[i + delta] = i / delta | 0;
/* build base table */
for (sum = first, i = 0; i < 30; ++i) {
base[i] = sum;
sum += 1 << bits[i];
}
}
/* build the fixed huffman trees */
function tinf_build_fixed_trees(lt, dt) {
var i;
/* build fixed length tree */
for (i = 0; i < 7; ++i) lt.table[i] = 0;
lt.table[7] = 24;
lt.table[8] = 152;
lt.table[9] = 112;
for (i = 0; i < 24; ++i) lt.trans[i] = 256 + i;
for (i = 0; i < 144; ++i) lt.trans[24 + i] = i;
for (i = 0; i < 8; ++i) lt.trans[24 + 144 + i] = 280 + i;
for (i = 0; i < 112; ++i) lt.trans[24 + 144 + 8 + i] = 144 + i;
/* build fixed distance tree */
for (i = 0; i < 5; ++i) dt.table[i] = 0;
dt.table[5] = 32;
for (i = 0; i < 32; ++i) dt.trans[i] = i;
}
/* given an array of code lengths, build a tree */
var offs = new Uint16Array(16);
function tinf_build_tree(t, lengths, off, num) {
var i, sum;
/* clear code length count table */
for (i = 0; i < 16; ++i) t.table[i] = 0;
/* scan symbol lengths, and sum code length counts */
for (i = 0; i < num; ++i) t.table[lengths[off + i]]++;
t.table[0] = 0;
/* compute offset table for distribution sort */
for (sum = 0, i = 0; i < 16; ++i) {
offs[i] = sum;
sum += t.table[i];
}
/* create code->symbol translation table (symbols sorted by code) */
for (i = 0; i < num; ++i) {
if (lengths[off + i]) t.trans[offs[lengths[off + i]]++] = i;
}
}
/* ---------------------- *
* -- decode functions -- *
* ---------------------- */
/* get one bit from source stream */
function tinf_getbit(d) {
/* check if tag is empty */
if (!d.bitcount--) {
/* load next tag */
d.tag = d.source[d.sourceIndex++];
d.bitcount = 7;
}
/* shift bit out of tag */
var bit = d.tag & 1;
d.tag >>>= 1;
return bit;
}
/* read a num bit value from a stream and add base */
function tinf_read_bits(d, num, base) {
if (!num)
return base;
while (d.bitcount < 24) {
d.tag |= d.source[d.sourceIndex++] << d.bitcount;
d.bitcount += 8;
}
var val = d.tag & (0xffff >>> (16 - num));
d.tag >>>= num;
d.bitcount -= num;
return val + base;
}
/* given a data stream and a tree, decode a symbol */
function tinf_decode_symbol(d, t) {
while (d.bitcount < 24) {
d.tag |= d.source[d.sourceIndex++] << d.bitcount;
d.bitcount += 8;
}
var sum = 0, cur = 0, len = 0;
var tag = d.tag;
/* get more bits while code value is above sum */
do {
cur = 2 * cur + (tag & 1);
tag >>>= 1;
++len;
sum += t.table[len];
cur -= t.table[len];
} while (cur >= 0);
d.tag = tag;
d.bitcount -= len;
return t.trans[sum + cur];
}
/* given a data stream, decode dynamic trees from it */
function tinf_decode_trees(d, lt, dt) {
var hlit, hdist, hclen;
var i, num, length;
/* get 5 bits HLIT (257-286) */
hlit = tinf_read_bits(d, 5, 257);
/* get 5 bits HDIST (1-32) */
hdist = tinf_read_bits(d, 5, 1);
/* get 4 bits HCLEN (4-19) */
hclen = tinf_read_bits(d, 4, 4);
for (i = 0; i < 19; ++i) lengths[i] = 0;
/* read code lengths for code length alphabet */
for (i = 0; i < hclen; ++i) {
/* get 3 bits code length (0-7) */
var clen = tinf_read_bits(d, 3, 0);
lengths[clcidx[i]] = clen;
}
/* build code length tree */
tinf_build_tree(code_tree, lengths, 0, 19);
/* decode code lengths for the dynamic trees */
for (num = 0; num < hlit + hdist;) {
var sym = tinf_decode_symbol(d, code_tree);
switch (sym) {
case 16:
/* copy previous code length 3-6 times (read 2 bits) */
var prev = lengths[num - 1];
for (length = tinf_read_bits(d, 2, 3); length; --length) {
lengths[num++] = prev;
}
break;
case 17:
/* repeat code length 0 for 3-10 times (read 3 bits) */
for (length = tinf_read_bits(d, 3, 3); length; --length) {
lengths[num++] = 0;
}
break;
case 18:
/* repeat code length 0 for 11-138 times (read 7 bits) */
for (length = tinf_read_bits(d, 7, 11); length; --length) {
lengths[num++] = 0;
}
break;
default:
/* values 0-15 represent the actual code lengths */
lengths[num++] = sym;
break;
}
}
/* build dynamic trees */
tinf_build_tree(lt, lengths, 0, hlit);
tinf_build_tree(dt, lengths, hlit, hdist);
}
/* ----------------------------- *
* -- block inflate functions -- *
* ----------------------------- */
/* given a stream and two trees, inflate a block of data */
function tinf_inflate_block_data(d, lt, dt) {
while (1) {
var sym = tinf_decode_symbol(d, lt);
/* check for end of block */
if (sym === 256) {
return TINF_OK;
}
if (sym < 256) {
d.dest[d.destLen++] = sym;
} else {
var length, dist, offs;
var i;
sym -= 257;
/* possibly get more bits from length code */
length = tinf_read_bits(d, length_bit
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment