").addClass(errClass).css("position", "absolute")
+ .css("top", el.offsetTop)
+ .css("left", el.offsetLeft)
+ // setting width can push out the page size, forcing otherwise
+ // unnecessary scrollbars to appear and making it impossible for
+ // the element to shrink; so use max-width instead
+ .css("maxWidth", el.offsetWidth)
+ .css("height", el.offsetHeight);
+ errorDiv.text(err.message);
+ $el.after(errorDiv);
+
+ // Really dumb way to keep the size/position of the error in sync with
+ // the parent element as the window is resized or whatever.
+ var intId = setInterval(function() {
+ if (!errorDiv[0].parentElement) {
+ clearInterval(intId);
+ return;
+ }
+ errorDiv
+ .css("top", el.offsetTop)
+ .css("left", el.offsetLeft)
+ .css("maxWidth", el.offsetWidth)
+ .css("height", el.offsetHeight);
+ }, 500);
+ }
+ }
+ },
+ clearError: function(el) {
+ var $el = $(el);
+ var display = $el.data("restore-display-mode");
+ $el.data("restore-display-mode", null);
+
+ if (display === "inline" || display === "inline-block") {
+ if (display)
+ $el.css("display", display);
+ $(el.nextSibling).filter(".htmlwidgets-error").remove();
+ } else if (display === "block"){
+ $el.css("visibility", "inherit");
+ $(el.nextSibling).filter(".htmlwidgets-error").remove();
+ }
+ },
+ sizing: {}
+ };
+
+ // Called by widget bindings to register a new type of widget. The definition
+ // object can contain the following properties:
+ // - name (required) - A string indicating the binding name, which will be
+ // used by default as the CSS classname to look for.
+ // - initialize (optional) - A function(el) that will be called once per
+ // widget element; if a value is returned, it will be passed as the third
+ // value to renderValue.
+ // - renderValue (required) - A function(el, data, initValue) that will be
+ // called with data. Static contexts will cause this to be called once per
+ // element; Shiny apps will cause this to be called multiple times per
+ // element, as the data changes.
+ window.HTMLWidgets.widget = function(definition) {
+ if (!definition.name) {
+ throw new Error("Widget must have a name");
+ }
+ if (!definition.type) {
+ throw new Error("Widget must have a type");
+ }
+ // Currently we only support output widgets
+ if (definition.type !== "output") {
+ throw new Error("Unrecognized widget type '" + definition.type + "'");
+ }
+ // TODO: Verify that .name is a valid CSS classname
+
+ // Support new-style instance-bound definitions. Old-style class-bound
+ // definitions have one widget "object" per widget per type/class of
+ // widget; the renderValue and resize methods on such widget objects
+ // take el and instance arguments, because the widget object can't
+ // store them. New-style instance-bound definitions have one widget
+ // object per widget instance; the definition that's passed in doesn't
+ // provide renderValue or resize methods at all, just the single method
+ // factory(el, width, height)
+ // which returns an object that has renderValue(x) and resize(w, h).
+ // This enables a far more natural programming style for the widget
+ // author, who can store per-instance state using either OO-style
+ // instance fields or functional-style closure variables (I guess this
+ // is in contrast to what can only be called C-style pseudo-OO which is
+ // what we required before).
+ if (definition.factory) {
+ definition = createLegacyDefinitionAdapter(definition);
+ }
+
+ if (!definition.renderValue) {
+ throw new Error("Widget must have a renderValue function");
+ }
+
+ // For static rendering (non-Shiny), use a simple widget registration
+ // scheme. We also use this scheme for Shiny apps/documents that also
+ // contain static widgets.
+ window.HTMLWidgets.widgets = window.HTMLWidgets.widgets || [];
+ // Merge defaults into the definition; don't mutate the original definition.
+ var staticBinding = extend({}, defaults, definition);
+ overrideMethod(staticBinding, "find", function(superfunc) {
+ return function(scope) {
+ var results = superfunc(scope);
+ // Filter out Shiny outputs, we only want the static kind
+ return filterByClass(results, "html-widget-output", false);
+ };
+ });
+ window.HTMLWidgets.widgets.push(staticBinding);
+
+ if (shinyMode) {
+ // Shiny is running. Register the definition with an output binding.
+ // The definition itself will not be the output binding, instead
+ // we will make an output binding object that delegates to the
+ // definition. This is because we foolishly used the same method
+ // name (renderValue) for htmlwidgets definition and Shiny bindings
+ // but they actually have quite different semantics (the Shiny
+ // bindings receive data that includes lots of metadata that it
+ // strips off before calling htmlwidgets renderValue). We can't
+ // just ignore the difference because in some widgets it's helpful
+ // to call this.renderValue() from inside of resize(), and if
+ // we're not delegating, then that call will go to the Shiny
+ // version instead of the htmlwidgets version.
+
+ // Merge defaults with definition, without mutating either.
+ var bindingDef = extend({}, defaults, definition);
+
+ // This object will be our actual Shiny binding.
+ var shinyBinding = new Shiny.OutputBinding();
+
+ // With a few exceptions, we'll want to simply use the bindingDef's
+ // version of methods if they are available, otherwise fall back to
+ // Shiny's defaults. NOTE: If Shiny's output bindings gain additional
+ // methods in the future, and we want them to be overrideable by
+ // HTMLWidget binding definitions, then we'll need to add them to this
+ // list.
+ delegateMethod(shinyBinding, bindingDef, "getId");
+ delegateMethod(shinyBinding, bindingDef, "onValueChange");
+ delegateMethod(shinyBinding, bindingDef, "onValueError");
+ delegateMethod(shinyBinding, bindingDef, "renderError");
+ delegateMethod(shinyBinding, bindingDef, "clearError");
+ delegateMethod(shinyBinding, bindingDef, "showProgress");
+
+ // The find, renderValue, and resize are handled differently, because we
+ // want to actually decorate the behavior of the bindingDef methods.
+
+ shinyBinding.find = function(scope) {
+ var results = bindingDef.find(scope);
+
+ // Only return elements that are Shiny outputs, not static ones
+ var dynamicResults = results.filter(".html-widget-output");
+
+ // It's possible that whatever caused Shiny to think there might be
+ // new dynamic outputs, also caused there to be new static outputs.
+ // Since there might be lots of different htmlwidgets bindings, we
+ // schedule execution for later--no need to staticRender multiple
+ // times.
+ if (results.length !== dynamicResults.length)
+ scheduleStaticRender();
+
+ return dynamicResults;
+ };
+
+ // Wrap renderValue to handle initialization, which unfortunately isn't
+ // supported natively by Shiny at the time of this writing.
+
+ shinyBinding.renderValue = function(el, data) {
+ Shiny.renderDependencies(data.deps);
+ // Resolve strings marked as javascript literals to objects
+ if (!(data.evals instanceof Array)) data.evals = [data.evals];
+ for (var i = 0; data.evals && i < data.evals.length; i++) {
+ window.HTMLWidgets.evaluateStringMember(data.x, data.evals[i]);
+ }
+ if (!bindingDef.renderOnNullValue) {
+ if (data.x === null) {
+ el.style.visibility = "hidden";
+ return;
+ } else {
+ el.style.visibility = "inherit";
+ }
+ }
+ if (!elementData(el, "initialized")) {
+ initSizing(el);
+
+ elementData(el, "initialized", true);
+ if (bindingDef.initialize) {
+ var rect = el.getBoundingClientRect();
+ var result = bindingDef.initialize(el, rect.width, rect.height);
+ elementData(el, "init_result", result);
+ }
+ }
+ bindingDef.renderValue(el, data.x, elementData(el, "init_result"));
+ evalAndRun(data.jsHooks.render, elementData(el, "init_result"), [el, data.x]);
+ };
+
+ // Only override resize if bindingDef implements it
+ if (bindingDef.resize) {
+ shinyBinding.resize = function(el, width, height) {
+ // Shiny can call resize before initialize/renderValue have been
+ // called, which doesn't make sense for widgets.
+ if (elementData(el, "initialized")) {
+ bindingDef.resize(el, width, height, elementData(el, "init_result"));
+ }
+ };
+ }
+
+ Shiny.outputBindings.register(shinyBinding, bindingDef.name);
+ }
+ };
+
+ var scheduleStaticRenderTimerId = null;
+ function scheduleStaticRender() {
+ if (!scheduleStaticRenderTimerId) {
+ scheduleStaticRenderTimerId = setTimeout(function() {
+ scheduleStaticRenderTimerId = null;
+ window.HTMLWidgets.staticRender();
+ }, 1);
+ }
+ }
+
+ // Render static widgets after the document finishes loading
+ // Statically render all elements that are of this widget's class
+ window.HTMLWidgets.staticRender = function() {
+ var bindings = window.HTMLWidgets.widgets || [];
+ forEach(bindings, function(binding) {
+ var matches = binding.find(document.documentElement);
+ forEach(matches, function(el) {
+ var sizeObj = initSizing(el, binding);
+
+ var getSize = function(el) {
+ if (sizeObj) {
+ return {w: sizeObj.getWidth(), h: sizeObj.getHeight()}
+ } else {
+ var rect = el.getBoundingClientRect();
+ return {w: rect.width, h: rect.height}
+ }
+ };
+
+ if (hasClass(el, "html-widget-static-bound"))
+ return;
+ el.className = el.className + " html-widget-static-bound";
+
+ var initResult;
+ if (binding.initialize) {
+ var size = getSize(el);
+ initResult = binding.initialize(el, size.w, size.h);
+ elementData(el, "init_result", initResult);
+ }
+
+ if (binding.resize) {
+ var lastSize = getSize(el);
+ var resizeHandler = function(e) {
+ var size = getSize(el);
+ if (size.w === 0 && size.h === 0)
+ return;
+ if (size.w === lastSize.w && size.h === lastSize.h)
+ return;
+ lastSize = size;
+ binding.resize(el, size.w, size.h, initResult);
+ };
+
+ on(window, "resize", resizeHandler);
+
+ // This is needed for cases where we're running in a Shiny
+ // app, but the widget itself is not a Shiny output, but
+ // rather a simple static widget. One example of this is
+ // an rmarkdown document that has runtime:shiny and widget
+ // that isn't in a render function. Shiny only knows to
+ // call resize handlers for Shiny outputs, not for static
+ // widgets, so we do it ourselves.
+ if (window.jQuery) {
+ window.jQuery(document).on(
+ "shown.htmlwidgets shown.bs.tab.htmlwidgets shown.bs.collapse.htmlwidgets",
+ resizeHandler
+ );
+ window.jQuery(document).on(
+ "hidden.htmlwidgets hidden.bs.tab.htmlwidgets hidden.bs.collapse.htmlwidgets",
+ resizeHandler
+ );
+ }
+
+ // This is needed for the specific case of ioslides, which
+ // flips slides between display:none and display:block.
+ // Ideally we would not have to have ioslide-specific code
+ // here, but rather have ioslides raise a generic event,
+ // but the rmarkdown package just went to CRAN so the
+ // window to getting that fixed may be long.
+ if (window.addEventListener) {
+ // It's OK to limit this to window.addEventListener
+ // browsers because ioslides itself only supports
+ // such browsers.
+ on(document, "slideenter", resizeHandler);
+ on(document, "slideleave", resizeHandler);
+ }
+ }
+
+ var scriptData = document.querySelector("script[data-for='" + el.id + "'][type='application/json']");
+ if (scriptData) {
+ var data = JSON.parse(scriptData.textContent || scriptData.text);
+ // Resolve strings marked as javascript literals to objects
+ if (!(data.evals instanceof Array)) data.evals = [data.evals];
+ for (var k = 0; data.evals && k < data.evals.length; k++) {
+ window.HTMLWidgets.evaluateStringMember(data.x, data.evals[k]);
+ }
+ binding.renderValue(el, data.x, initResult);
+ evalAndRun(data.jsHooks.render, initResult, [el, data.x]);
+ }
+ });
+ });
+
+ invokePostRenderHandlers();
+ }
+
+
+ function has_jQuery3() {
+ if (!window.jQuery) {
+ return false;
+ }
+ var $version = window.jQuery.fn.jquery;
+ var $major_version = parseInt($version.split(".")[0]);
+ return $major_version >= 3;
+ }
+
+ /*
+ / Shiny 1.4 bumped jQuery from 1.x to 3.x which means jQuery's
+ / on-ready handler (i.e., $(fn)) is now asyncronous (i.e., it now
+ / really means $(setTimeout(fn)).
+ / https://jquery.com/upgrade-guide/3.0/#breaking-change-document-ready-handlers-are-now-asynchronous
+ /
+ / Since Shiny uses $() to schedule initShiny, shiny>=1.4 calls initShiny
+ / one tick later than it did before, which means staticRender() is
+ / called renderValue() earlier than (advanced) widget authors might be expecting.
+ / https://github.com/rstudio/shiny/issues/2630
+ /
+ / For a concrete example, leaflet has some methods (e.g., updateBounds)
+ / which reference Shiny methods registered in initShiny (e.g., setInputValue).
+ / Since leaflet is privy to this life-cycle, it knows to use setTimeout() to
+ / delay execution of those methods (until Shiny methods are ready)
+ / https://github.com/rstudio/leaflet/blob/18ec981/javascript/src/index.js#L266-L268
+ /
+ / Ideally widget authors wouldn't need to use this setTimeout() hack that
+ / leaflet uses to call Shiny methods on a staticRender(). In the long run,
+ / the logic initShiny should be broken up so that method registration happens
+ / right away, but binding happens later.
+ */
+ function maybeStaticRenderLater() {
+ if (shinyMode && has_jQuery3()) {
+ window.jQuery(window.HTMLWidgets.staticRender);
+ } else {
+ window.HTMLWidgets.staticRender();
+ }
+ }
+
+ if (document.addEventListener) {
+ document.addEventListener("DOMContentLoaded", function() {
+ document.removeEventListener("DOMContentLoaded", arguments.callee, false);
+ maybeStaticRenderLater();
+ }, false);
+ } else if (document.attachEvent) {
+ document.attachEvent("onreadystatechange", function() {
+ if (document.readyState === "complete") {
+ document.detachEvent("onreadystatechange", arguments.callee);
+ maybeStaticRenderLater();
+ }
+ });
+ }
+
+
+ window.HTMLWidgets.getAttachmentUrl = function(depname, key) {
+ // If no key, default to the first item
+ if (typeof(key) === "undefined")
+ key = 1;
+
+ var link = document.getElementById(depname + "-" + key + "-attachment");
+ if (!link) {
+ throw new Error("Attachment " + depname + "/" + key + " not found in document");
+ }
+ return link.getAttribute("href");
+ };
+
+ window.HTMLWidgets.dataframeToD3 = function(df) {
+ var names = [];
+ var length;
+ for (var name in df) {
+ if (df.hasOwnProperty(name))
+ names.push(name);
+ if (typeof(df[name]) !== "object" || typeof(df[name].length) === "undefined") {
+ throw new Error("All fields must be arrays");
+ } else if (typeof(length) !== "undefined" && length !== df[name].length) {
+ throw new Error("All fields must be arrays of the same length");
+ }
+ length = df[name].length;
+ }
+ var results = [];
+ var item;
+ for (var row = 0; row < length; row++) {
+ item = {};
+ for (var col = 0; col < names.length; col++) {
+ item[names[col]] = df[names[col]][row];
+ }
+ results.push(item);
+ }
+ return results;
+ };
+
+ window.HTMLWidgets.transposeArray2D = function(array) {
+ if (array.length === 0) return array;
+ var newArray = array[0].map(function(col, i) {
+ return array.map(function(row) {
+ return row[i]
+ })
+ });
+ return newArray;
+ };
+ // Split value at splitChar, but allow splitChar to be escaped
+ // using escapeChar. Any other characters escaped by escapeChar
+ // will be included as usual (including escapeChar itself).
+ function splitWithEscape(value, splitChar, escapeChar) {
+ var results = [];
+ var escapeMode = false;
+ var currentResult = "";
+ for (var pos = 0; pos < value.length; pos++) {
+ if (!escapeMode) {
+ if (value[pos] === splitChar) {
+ results.push(currentResult);
+ currentResult = "";
+ } else if (value[pos] === escapeChar) {
+ escapeMode = true;
+ } else {
+ currentResult += value[pos];
+ }
+ } else {
+ currentResult += value[pos];
+ escapeMode = false;
+ }
+ }
+ if (currentResult !== "") {
+ results.push(currentResult);
+ }
+ return results;
+ }
+ // Function authored by Yihui/JJ Allaire
+ window.HTMLWidgets.evaluateStringMember = function(o, member) {
+ var parts = splitWithEscape(member, '.', '\\');
+ for (var i = 0, l = parts.length; i < l; i++) {
+ var part = parts[i];
+ // part may be a character or 'numeric' member name
+ if (o !== null && typeof o === "object" && part in o) {
+ if (i == (l - 1)) { // if we are at the end of the line then evalulate
+ if (typeof o[part] === "string")
+ o[part] = tryEval(o[part]);
+ } else { // otherwise continue to next embedded object
+ o = o[part];
+ }
+ }
+ }
+ };
+
+ // Retrieve the HTMLWidget instance (i.e. the return value of an
+ // HTMLWidget binding's initialize() or factory() function)
+ // associated with an element, or null if none.
+ window.HTMLWidgets.getInstance = function(el) {
+ return elementData(el, "init_result");
+ };
+
+ // Finds the first element in the scope that matches the selector,
+ // and returns the HTMLWidget instance (i.e. the return value of
+ // an HTMLWidget binding's initialize() or factory() function)
+ // associated with that element, if any. If no element matches the
+ // selector, or the first matching element has no HTMLWidget
+ // instance associated with it, then null is returned.
+ //
+ // The scope argument is optional, and defaults to window.document.
+ window.HTMLWidgets.find = function(scope, selector) {
+ if (arguments.length == 1) {
+ selector = scope;
+ scope = document;
+ }
+
+ var el = scope.querySelector(selector);
+ if (el === null) {
+ return null;
+ } else {
+ return window.HTMLWidgets.getInstance(el);
+ }
+ };
+
+ // Finds all elements in the scope that match the selector, and
+ // returns the HTMLWidget instances (i.e. the return values of
+ // an HTMLWidget binding's initialize() or factory() function)
+ // associated with the elements, in an array. If elements that
+ // match the selector don't have an associated HTMLWidget
+ // instance, the returned array will contain nulls.
+ //
+ // The scope argument is optional, and defaults to window.document.
+ window.HTMLWidgets.findAll = function(scope, selector) {
+ if (arguments.length == 1) {
+ selector = scope;
+ scope = document;
+ }
+
+ var nodes = scope.querySelectorAll(selector);
+ var results = [];
+ for (var i = 0; i < nodes.length; i++) {
+ results.push(window.HTMLWidgets.getInstance(nodes[i]));
+ }
+ return results;
+ };
+
+ var postRenderHandlers = [];
+ function invokePostRenderHandlers() {
+ while (postRenderHandlers.length) {
+ var handler = postRenderHandlers.shift();
+ if (handler) {
+ handler();
+ }
+ }
+ }
+
+ // Register the given callback function to be invoked after the
+ // next time static widgets are rendered.
+ window.HTMLWidgets.addPostRenderHandler = function(callback) {
+ postRenderHandlers.push(callback);
+ };
+
+ // Takes a new-style instance-bound definition, and returns an
+ // old-style class-bound definition. This saves us from having
+ // to rewrite all the logic in this file to accomodate both
+ // types of definitions.
+ function createLegacyDefinitionAdapter(defn) {
+ var result = {
+ name: defn.name,
+ type: defn.type,
+ initialize: function(el, width, height) {
+ return defn.factory(el, width, height);
+ },
+ renderValue: function(el, x, instance) {
+ return instance.renderValue(x);
+ },
+ resize: function(el, width, height, instance) {
+ return instance.resize(width, height);
+ }
+ };
+
+ if (defn.find)
+ result.find = defn.find;
+ if (defn.renderError)
+ result.renderError = defn.renderError;
+ if (defn.clearError)
+ result.clearError = defn.clearError;
+
+ return result;
+ }
+})();
diff --git a/docs/articles/linear-equations_files/rglWebGL-binding-1.0.1/rglWebGL.js b/docs/articles/linear-equations_files/rglWebGL-binding-1.0.1/rglWebGL.js
new file mode 100644
index 00000000..52c8fe64
--- /dev/null
+++ b/docs/articles/linear-equations_files/rglWebGL-binding-1.0.1/rglWebGL.js
@@ -0,0 +1,79 @@
+/* el is the div, holding the rgl object as el.rglinstance,
+ which holds x as el.rglinstance.scene
+ x is the JSON encoded rglwidget.
+*/
+
+
+HTMLWidgets.widget({
+
+ name: 'rglWebGL',
+
+ type: 'output',
+
+ factory: function(el, width, height) {
+ el.width = width;
+ el.height = height;
+ var rgl = new rglwidgetClass(),
+ onchangeselection = function(e) {
+ for (var i = 0; i < rgl.scene.crosstalk.sel_handle.length; i++)
+ rgl.clearBrush(except = e.rglSubsceneId);
+ rgl.selection(e, false);
+ },
+ onchangefilter = function(e) {
+ rgl.selection(e, true);
+ };
+
+ return {
+ renderValue: function(x) {
+ var i, pel, player, groups,
+ inShiny = (typeof Shiny !== "undefined");
+
+ x.crosstalk.group = groups = [].concat(x.crosstalk.group);
+ x.crosstalk.id = [].concat(x.crosstalk.id);
+ x.crosstalk.key = [].concat(x.crosstalk.key);
+ x.crosstalk.sel_handle = new Array(groups.length);
+ x.crosstalk.fil_handle = new Array(groups.length);
+ x.crosstalk.selection = [];
+ for (i = 0; i < groups.length; i++) {
+ x.crosstalk.sel_handle[i] = new crosstalk.SelectionHandle(groups[i], {sharedId: x.crosstalk.id[i]});
+ x.crosstalk.sel_handle[i].on("change", onchangeselection);
+ x.crosstalk.fil_handle[i] = new crosstalk.FilterHandle(groups[i], {sharedId: x.crosstalk.id[i]});
+ x.crosstalk.fil_handle[i].on("change", onchangefilter);
+ }
+ if (inShiny) {
+ // Shiny calls this multiple times, so we need extra cleanup
+ // between
+ rgl.sphere = undefined;
+ }
+ rgl.initialize(el, x);
+ rgl.initGL();
+
+ /* We might have been called after (some of) the players were rendered.
+ We need to make sure we respond to their initial values. */
+
+ if (typeof x.players !== "undefined") {
+ var players = [].concat(x.players);
+ for (i = 0; i < players.length; i++) {
+ pel = document.getElementById(players[i]);
+ if (pel) {
+ player = pel.rglPlayer;
+ if (player && (!player.initialized || inShiny)) {
+ rgl.Player(pel, player);
+ player.initialized = true;
+ }
+ }
+ }
+ }
+ rgl.drag = 0;
+ rgl.drawScene();
+ },
+
+ resize: function(width, height) {
+ el.width = width;
+ el.height = height;
+ el.rglinstance.resize(el);
+ el.rglinstance.drawScene();
+ }
+ };
+ }
+});
diff --git a/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/animation.src.js b/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/animation.src.js
new file mode 100644
index 00000000..cdca347d
--- /dev/null
+++ b/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/animation.src.js
@@ -0,0 +1,153 @@
+/**
+ * Methods related to animations
+ * @name ___METHODS_FOR_ANIMATION___
+ * @memberof rglwidgetClass
+ * @kind function
+ * @instance
+ */
+ /**
+ * Binary search
+ * @param x - x coordinates in increasing order
+ * @param newx - value to find, assumed to be in the range of x
+ * @result index of largest x value below newx
+ */
+ rglwidgetClass.bisect = function(x, newx) {
+ var lo = 0, hi = x.length - 1, mid;
+ while (lo < hi - 1) {
+ mid = Math.round((lo + hi)/2);
+ if (x[mid] < newx)
+ lo = mid;
+ else
+ hi = mid;
+ }
+ return lo;
+ };
+
+ /**
+ * Step interpolation (constant outside bounds)
+ * @param x - x coordinates in increasing order
+ * @param v - values at x; either a vector or matrix
+ * @param newx - value at which to evaluate
+ */
+ rglwidgetClass.step = function(x, v, newx) {
+ var n, lo;
+ if (newx <= x[0])
+ return v[0];
+ n = x.length;
+ if (newx >= x[n-1])
+ return v[n-1];
+ lo = this.bisect(x, newx);
+ return v[lo];
+ };
+
+ /**
+ * Linear interpolation (constant outside bounds)
+ * @param x - x coordinates in increasing order
+ * @param v - values at x; either a vector or matrix
+ * @param newx - value at which to evaluate
+ */
+ rglwidgetClass.lerp = function(x, v, newx) {
+ var i, n, lo, hi, alpha, result;
+ if (newx <= x[0])
+ return v[0];
+ n = x.length;
+ if (newx >= x[n-1])
+ return v[n-1];
+ lo = this.bisect(x, newx);
+ if (newx === x[lo])
+ return v[lo];
+ hi = lo + 1;
+ if (newx === x[hi])
+ return v[hi];
+ alpha = (newx - x[lo])/(x[hi] - x[lo]);
+ result = v[lo];
+ n = result.length;
+ if (typeof n !== "undefined") {
+ for (i = 0; i < n; i++)
+ result[i] = (1 - alpha)*result[i] + alpha*v[hi][i];
+ } else
+ result = (1 - alpha)*result + alpha*v[hi];
+ return result;
+ };
+
+ /**
+ * Spherical linear interpolation (constant outside bounds)
+ * @param x - x coordinates in increasing order
+ * @param v - a matrix of unit quaternions
+ * @param newx - value at which to evaluate
+ */
+ rglwidgetClass.slerp = function(x, v, newx) {
+ var n, lo, hi, alpha, result,
+ p0, p1, dot, Omega, alpha0, alpha1, len;
+ if (newx <= x[0])
+ return v[0];
+ if (newx >= x[n-1])
+ return v[n-1];
+ lo = this.bisect(x, newx);
+ if (newx === x[lo])
+ return v[lo];
+ hi = lo + 1;
+ if (newx === x[hi])
+ return v[hi];
+ p0 = v[lo];
+ p1 = v[hi];
+ dot = p0[0]*p1[0] +
+ p0[1]*p1[1] +
+ p0[2]*p1[2] +
+ p0[3]*p1[3];
+ if (dot < 0) {
+ p1 = [-p1[0], -p1[1], -p1[2], -p1[3]];
+ dot = -dot;
+ }
+ if (dot >= 1)
+ result = p1;
+ else {
+ alpha = (newx - x[lo])/(x[hi] - x[lo]);
+ Omega = Math.acos(dot);
+ alpha0 = Math.sin((1 - alpha)*Omega);
+ alpha1 = Math.sin(alpha*Omega);
+ result = [alpha0*p0[0] + alpha1*p1[0],
+ alpha0*p0[1] + alpha1*p1[1],
+ alpha0*p0[2] + alpha1*p1[2],
+ alpha0*p0[3] + alpha1*p1[3]];
+ }
+ len = Math.sqrt(result[0]*result[0] +
+ result[1]*result[1] +
+ result[2]*result[2] +
+ result[3]*result[3]);
+ return [result[0]/len,
+ result[1]/len,
+ result[2]/len,
+ result[3]/len];
+ };
+
+ /**
+ * Rotate using unit quaternion
+ * @param q - a single unit quaternion
+ */
+ rglwidgetClass.rotateByQuaternion = function(M, q) {
+
+ var xx = q[0]*q[0],
+ xy = q[0]*q[1],
+ xz = q[0]*q[2],
+ xw = q[0]*q[3],
+ yy = q[1]*q[1],
+ yz = q[1]*q[2],
+ yw = q[1]*q[3],
+ zz = q[2]*q[2],
+ zw = q[2]*q[3],
+ matrix = new CanvasMatrix4();
+ matrix.m11 = 1 - 2*(yy + zz);
+ matrix.m12 = 2*(xy + zw);
+ matrix.m13 = 2*(xz - yw);
+
+ matrix.m21 = 2*(xy - zw);
+ matrix.m22 = 1 - 2*(xx + zz);
+ matrix.m23 = 2*(yz + xw);
+
+ matrix.m31 = 2*(xz + yw);
+ matrix.m32 = 2*(yz - xw);
+ matrix.m33 = 1 - 2*(xx + yy);
+
+ M.multRight(matrix);
+ };
diff --git a/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/axes.src.js b/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/axes.src.js
new file mode 100644
index 00000000..485fa13c
--- /dev/null
+++ b/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/axes.src.js
@@ -0,0 +1,441 @@
+ /**
+ * Methods related to axes
+ * @name ___METHODS_FOR_AXES___
+ * @memberof rglwidgetClass
+ * @kind function
+ * @instance
+ */
+
+ /**
+ * Choose edges for ticks
+ * @param { Matrix } prmv - projection-model-view matrix
+ */
+ rglwidgetClass.prototype.getTickEdges = function(prmv){
+ var vertices = [[0,0,0,1], [0,0,1,1],
+ [0,1,0,1], [0,1,1,1],
+ [1,0,0,1], [1,0,1,1],
+ [1,1,0,1], [1,1,1,1]],
+ dim, i, j, k, edges, hull, step, result = [], proj = [],
+
+ // Filter to edges that are on sides that would
+ // be shown with a filled backing.
+
+ has_back = function(edge) {
+ var normals = [[], []],
+ verts = [vertices[edge[0]],
+ vertices[edge[1]]],
+ normal, m, n;
+ n = 0;
+ for (m=0; m<3; m++) {
+ if (verts[0][m] === verts[1][m]) {
+ normals[n] = [0,0,0,1];
+ normals[n][m] = 2*verts[0][m] - 1;
+ n++;
+ }
+ }
+ for (n=0; n<2; n++) {
+ normal = rglwidgetClass.multVM(normals[n], self.normMatrix);
+ if (normal[2] < 0 ||
+ (normal[2] === 0 && normal[0] < 0))
+ return true;
+ }
+ return false;
+ }, self = this;
+
+ for (i = 0; i < vertices.length; i++) {
+ proj[i] = rglwidgetClass.multVM(vertices[i], prmv);
+ proj[i][0] = proj[i][0]/proj[i][3];
+ proj[i][1] = proj[i][1]/proj[i][3];
+ proj[i][2] = i;
+ }
+ hull = rglwidgetClass.chull(proj.slice());
+ for (i = 0; i < hull.length; i++)
+ hull[i] = hull[i][2];
+ hull.push(hull[0]);
+ for (dim = 0; dim < 3; dim++) {
+ edges = [];
+ step = Math.pow(2, 2-dim);
+ for (i = 0; i < 4; i++) {
+ j = (dim === 0) ? i : (dim === 1) ? i + 2*(i>1) : 2*i;
+ for (k = 0; k < hull.length - 1; k++) {
+ if ((hull[k] === j && hull[k+1] === j + step) ||
+ (hull[k] === j+step && hull[k+1] === j))
+
+ edges.push([j, j+step], [j+step, j]);
+ }
+ }
+
+ edges = edges.filter(has_back);
+
+ // Find the edge with a vertex closest
+ // to the bottom left corner
+ if (edges.length) {
+ var best, best2, val = Infinity, newval;
+ for (i = 0; i < edges.length; i++) {
+ j = edges[i][0];
+ newval = proj[j][0] + proj[j][1];
+ if (newval < val) {
+ best = j;
+ best2 = edges[i][1];
+ val = newval;
+ }
+ }
+ if (typeof best !== "undefined") {
+ result[dim] = vertices[best].slice(0,3);
+ result[dim][dim] = undefined;
+ } else
+ result[dim] = undefined;
+ }
+ }
+ return result;
+ };
+
+ /**
+ * Choose tick locations
+ * @param { Object } obj - The bboxdeco
+ */
+ rglwidgetClass.prototype.getTickLocations = function(obj){
+ var dim, i, limits, locations = [], result = [[],[],[]], value,
+ len, delta, range, bbox = obj.bbox;
+ obj.needsAxisCallback = false;
+ for (dim = 0; dim < 3; dim++) {
+ limits = bbox.slice(2*dim, 2*dim + 2);
+ range = limits[1] - limits[0];
+ switch(obj.axes.mode[dim]) {
+ case "custom":
+ for (i=0; i < obj.vertices.length; i++) {
+ value = (obj.vertices[i][dim] - limits[0])/range;
+ if (typeof value !== "undefined" &&
+ !isNaN(value))
+ result[dim].push(value);
+ }
+ break;
+ case "fixedstep":
+ len = Math.floor(range/obj.axes.step[dim]);
+ delta = obj.axes.step[dim];
+ for (i = 0; i < len; i++)
+ result[dim].push(i*delta);
+ break;
+ case "fixednum":
+ len = obj.axes.nticks[dim];
+ delta = (len > 1) ? range/(len-1) : 0;
+ for (i = 0; i < len; i++)
+ result[dim].push(i*delta/range);
+ break;
+ case "pretty":
+ locations = this.R_pretty(limits[0], limits[1], 5,
+ 2, // min_n
+ 0.75, // shrink_sml
+ [1.5, 2.75], // high_u_fact
+ 0, // eps_correction
+ 0); // return_bounds)
+ for (i = locations.lo; i <= locations.up; i++) {
+ value = (i*locations.unit - limits[0])/range;
+ if (0 < value && value < 1)
+ result[dim].push(value);
+ }
+ break;
+ case "user":
+ obj.needsAxisCallback = true;
+ break;
+ }
+ }
+ return result;
+ };
+
+ /**
+ * Set tick vertices
+ * @param { Object } ticks - the tick object
+ * @param { Array } edges - Which edges get the ticks?
+ */
+ rglwidgetClass.prototype.getTickVertices = function(ticks) {
+ var dim, i, j, vertices = [], locations,
+ edges = ticks.edges, edge;
+ for (dim = 0; dim < 3; dim++) {
+ locations = ticks.locations[dim];
+ if (locations.length)
+ for (i = 0; i < locations.length; i++)
+ if (typeof edges[dim] !== "undefined") {
+ edge = edges[dim].slice();
+ edge[dim] = locations[i];
+ vertices.push(edge);
+ edge = edge.slice();
+ for (j = 0; j < 3; j++)
+ if ((dim < 2 && j === 1 - dim) ||
+ (dim === 2 && j === 0))
+ edge[j] += 2*(edge[j] - 0.5)/ticks.axes.marklen[dim];
+ vertices.push(edge);
+ }
+ }
+ ticks.vertices = vertices;
+ ticks.vertexCount = vertices.length;
+ ticks.values = new Float32Array(rglwidgetClass.flatten(vertices));
+ ticks.initialized = false;
+ };
+
+ /**
+ * Set tick label positions
+ * @param { Object } obj - the bbox object
+ */
+ rglwidgetClass.prototype.placeTickLabels = function(obj) {
+ var ticks = obj.ticks, labels = obj.labels, i,j,k,
+ vertices = [], tickvertices = ticks.vertices,
+ vertex, locations, dim, edges = obj.ticks.edges;
+ j = 0;
+ for (dim = 0; dim < 3; dim++) {
+ if (typeof edges[dim] === "undefined")
+ continue;
+ locations = ticks.locations[dim];
+ if (locations.length)
+ for (i = 0; i < locations.length; i++) {
+ if (isNaN(locations[i]))
+ continue;
+ while (j < tickvertices.length &&
+ tickvertices[j][dim] !== locations[i]) j++;
+ if (j >= tickvertices.length)
+ break;
+ vertex = tickvertices[j].slice();
+ for (k = 0; k < 3; k++)
+ vertex[k] += 2*(tickvertices[j+1][k] - vertex[k]);
+ vertices.push(vertex);
+ j += 2;
+ }
+ }
+ labels.vertices = vertices;
+ labels.centers = labels.vertices;
+ labels.initialized = false;
+ };
+
+ /**
+ * Set tick labels
+ * @param { Object } obj - the bbox object
+ */
+ rglwidgetClass.prototype.setTickLabels = function(obj) {
+ var ticks = obj.ticks, mode, locations, labels = [],
+ start = 0, nticks, dim, i, limits, range, values, max,
+ edges = obj.ticks.edges;
+ for (dim = 0; dim < 3; dim++) {
+ if (typeof edges[dim] === "undefined")
+ continue;
+ mode = obj.axes.mode[dim];
+ nticks = obj.axes.nticks[dim]; // used on input only for custom!
+ if (mode === "custom")
+ labels = labels.concat(obj.texts.slice(start, start + nticks));
+ else {
+ limits = obj.bbox.slice(2*dim, 2*(dim+1));
+ range = limits[1] - limits[0];
+ locations = ticks.locations[dim];
+ max = -Infinity;
+ values = [];
+ for (i = 0; i < locations.length; i++) {
+ values.push(limits[0] + range*locations[i]);
+ max = Math.max(max, Math.abs(values[i]));
+ }
+ for (i = 0; i < locations.length; i++) {
+ if (Math.abs(values[i])/max < Math.pow(10, -5))
+ values[i] = 0;
+ labels.push(rglwidgetClass.signif(values[i], 4).toString());
+ }
+ obj.axes.nticks[dim] = locations.length;
+ }
+ start += nticks;
+ }
+ obj.labels.texts = labels;
+ };
+
+ /**
+ * Set bboxdeco bbox and center vector
+ * @param { Object } obj - the bbox object
+ */
+ rglwidgetClass.prototype.setBbox = function(obj, subscene) {
+ var i, expand, center = [], bbox;
+ if (!obj.initialized)
+ this.initBBox(obj);
+
+ bbox = [].concat(subscene.par3d.bbox);
+ for (i = 0; i < 3; i++) {
+ expand = obj.axes.expand[i];
+ center[i] = (bbox[2*i] + bbox[2*i + 1])/2;
+ bbox[2*i] = center[i] - expand*(bbox[2*i + 1] - center[i]);
+ bbox[2*i+1] = center[i] + expand*(bbox[2*i + 1] - center[i]);
+ }
+ obj.bbox = bbox;
+ obj.center = center;
+ };
+
+ rglwidgetClass.prototype.setBBoxMatrices = function(obj) {
+ var saved = {normMatrix: new CanvasMatrix4(this.normMatrix),
+ mvMatrix: new CanvasMatrix4(this.mvMatrix)},
+ bboxNorm, bboxMV, bbox = obj.bbox, scale;
+
+ bboxNorm = new CanvasMatrix4();
+ scale = [bbox[1]-bbox[0], bbox[3]-bbox[2], bbox[5]-bbox[4]];
+ bboxNorm.scale(1/scale[0], 1/scale[1], 1/scale[2]);
+ bboxNorm.multRight(saved.normMatrix);
+ this.normMatrix = bboxNorm;
+
+ bboxMV = new CanvasMatrix4();
+ bboxMV.scale(scale[0], scale[1], scale[2]);
+ bboxMV.translate(bbox[0], bbox[2], bbox[4]);
+ bboxMV.multRight(saved.mvMatrix);
+ this.mvMatrix = obj.mvMatrix = bboxMV;
+
+ if (this.prmvMatrix === null)
+ saved.prmvMatrix = null;
+ else
+ saved.prmvMatrix = new CanvasMatrix4(this.prmvMatrix);
+
+ this.setprmvMatrix();
+ obj.prmvMatrix = this.prmvMatrix;
+
+ return saved;
+ };
+
+ rglwidgetClass.prototype.restoreBBoxMatrices = function(saved) {
+ this.normMatrix = saved.normMatrix;
+ this.mvMatrix = saved.mvMatrix;
+ this.prmvMatrix = saved.prmvMatrix;
+ };
+
+ rglwidgetClass.prototype.getMarginParameters = function(bboxdeco, material) {
+ // Assume we've run this.setBbox(bboxdeco, subscene);
+ var bbox = bboxdeco.bbox,
+ edge = [].concat(material.edge),
+ saved, edges, i,
+ at = material.margin, line, level, trans, scale;
+
+ if (material.floating) {
+ saved = this.setBBoxMatrices(bboxdeco);
+ edges = this.getTickEdges(this.prmvMatrix)[at];
+ this.restoreBBoxMatrices(saved);
+ if (typeof edges !== "undefined")
+ for (i = 0; i < 3; i++) {
+ if (edges[i] < 1) edges[i] = -1;
+ edge[i] = edge[i]*edges[i];
+ } else
+ return undefined;
+ }
+ switch(at) {
+ case 0: line = 1;
+ level = 2;
+ break;
+ case 1: line = 0;
+ level = 2;
+ break;
+ case 2: line = 0;
+ level = 1;
+ break;
+ }
+ scale = [edge[0]*(bbox[1]-bbox[0])/bboxdeco.axes.marklen[0],
+ edge[1]*(bbox[3]-bbox[2])/bboxdeco.axes.marklen[1],
+ edge[2]*(bbox[5]-bbox[4])/bboxdeco.axes.marklen[2]];
+ trans = [edge[0] === 1 ? bbox[1] : bbox[0],
+ edge[1] === 1 ? bbox[3] : bbox[2],
+ edge[2] === 1 ? bbox[5] : bbox[4]];
+ return {at: at, line: line, level: level, trans: trans, scale: scale};
+ };
+
+ rglwidgetClass.prototype.fixVertex = function(orig, parms, center, bbox) {
+ var vertex = [0,0,0];
+ if (rglwidgetClass.missing(orig[0]))
+ vertex[parms.at] = center[parms.at];
+ else if (orig[0] === "-Inf")
+ vertex[parms.at] = bbox[2*parms.at];
+ else if (orig[0] === "Inf")
+ vertex[parms.at] = bbox[2*parms.at + 1];
+ else
+ vertex[parms.at] = orig[0];
+ vertex[parms.line] = parms.scale[parms.line]*orig[1] +
+ parms.trans[parms.line];
+ vertex[parms.level] = parms.scale[parms.level]*orig[2] +
+ parms.trans[parms.level];
+ return vertex;
+ };
+
+ rglwidgetClass.prototype.fixNormal = function(orig, parms) {
+ var vertex = [0,0,0];
+ vertex[parms.at] = orig[0];
+ vertex[parms.line] = orig[1]/parms.scale[parms.line];
+ vertex[parms.level] = orig[2]/parms.scale[parms.level];
+ return vertex;
+ };
+
+ rglwidgetClass.prototype.marginVecToDataVec = function(obj, subscene) {
+ var bboxdeco = this.getBBoxDeco(subscene),
+ center, bbox, parms, parmsjson,
+ orig = obj.orig,
+ vertices = [], normals = [],
+ centers = [], i, vertex;
+ if (typeof orig === "undefined") {
+ orig = {vert: obj.vertices,
+ norm: obj.normals,
+ cent: obj.centers,
+ doNormals: typeof obj.normals !== "undefined",
+ doCenters: typeof obj.centers !== "undefined",
+ parms: ""
+ };
+ obj.orig = orig;
+ }
+
+ if (typeof bboxdeco !== "undefined") {
+ this.setBbox(bboxdeco, subscene);
+ center = bboxdeco.center;
+ bbox = bboxdeco.bbox;
+ parms = this.getMarginParameters(bboxdeco, obj.material);
+ if (typeof parms === "undefined")
+ return false; /* axis is not currently shown */
+
+ parmsjson = JSON.stringify(parms);
+ if (parmsjson === orig.parms)
+ return true; /* nothing has changed */
+
+ orig.parms = parmsjson;
+
+ for (i=0; i < orig.vert.length; i++) {
+ vertex = this.fixVertex(orig.vert[i], parms, center, bbox);
+ vertices.push(vertex);
+ }
+ obj.vertices = vertices;
+ if (orig.doNormals) {
+ for (i=0; i < orig.norm.length; i++) {
+ vertex = this.fixNormal(orig.norm[i], parms);
+ normals.push(vertex);
+ }
+ obj.normals = normals;
+ }
+ if (orig.doCenters) {
+ for (i=0; i < orig.cent.length; i++) {
+ vertex = this.fixVertex(orig.cent[i], parms, center, bbox);
+ centers.push(vertex);
+ }
+ obj.centers = centers;
+ }
+
+ obj.initialized = false;
+ return true;
+ } else {
+ console.warn("bboxdeco not found");
+ return false;
+ }
+ };
+
+ rglwidgetClass.prototype.doAxisCallback = function(obj, edges) {
+ var i, j, code, axis, fn;
+ for (i = 0; i < 3; i++) {
+ if (obj.axes.mode[i] === "user") {
+ axis = ["x", "y", "z"][i];
+ if (typeof obj.callbacks !== "undefined" &&
+ typeof (code = obj.callbacks[axis]) !== "undefined") {
+ if (typeof edges[i] !== "undefined")
+ for (j = 0; j < 3; j++)
+ if (typeof edges[i][j] !== "undefined")
+ axis = axis + (edges[i][j] > 0 ? "+" : "-");
+
+ /* jshint evil:true */
+ fn = Function('"use strict";return (' + code + ')')();
+ /* jshint evil:false */
+ fn.call(this, axis);
+ }
+ }
+ }
+ };
diff --git a/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/buffer.src.js b/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/buffer.src.js
new file mode 100644
index 00000000..13d2e1ae
--- /dev/null
+++ b/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/buffer.src.js
@@ -0,0 +1,182 @@
+/**
+ * Methods related to buffered data
+ * @name ___METHODS_FOR_BUFFERS___
+ * @memberof rglwidgetClass
+ * @kind function
+ * @instance
+ */
+ /**
+ * Detect rglBuffered object
+ * @param { Object } obj - vertices or similar
+ */
+ rglwidgetClass.prototype.isBuffered = function(obj) {
+ return typeof obj === "string";
+ };
+
+ /* The next two functions are taken from
+
+ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding
+
+ They were written by Mozilla Contributors and dedicated
+ to the public domain under CC0. */
+
+ /* Array of bytes to Base64 string decoding */
+ rglwidgetClass.prototype.b64ToUint6 = function(nChr) {
+ return nChr > 64 && nChr < 91 ? nChr - 65 :
+ nChr > 96 && nChr < 123 ? nChr - 71 :
+ nChr > 47 && nChr < 58 ? nChr + 4 :
+ nChr === 43 ? 62 :
+ nChr === 47 ? 63 :
+ 0;
+ };
+
+ /* jshint bitwise:false */
+ rglwidgetClass.prototype.base64DecToArr = function(sBase64, nBlocksSize) {
+ var sB64Enc = sBase64.replace(/[^A-Za-z0-9\+\/]/g, ""),
+ nInLen = sB64Enc.length,
+ nOutLen = nBlocksSize ? Math.ceil((nInLen * 3 + 1 >> 2) / nBlocksSize) * nBlocksSize : nInLen * 3 + 1 >> 2,
+ taBytes = new Uint8Array(nOutLen);
+ for (var nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx++) {
+ nMod4 = nInIdx & 3;
+ nUint24 |= this.b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << 6 * (3 - nMod4);
+ if (nMod4 === 3 || nInLen - nInIdx === 1) {
+ for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++) {
+ taBytes[nOutIdx] = nUint24 >>> (16 >>> nMod3 & 24) & 255;
+ }
+ nUint24 = 0;
+ }
+ }
+ return taBytes;
+ };
+ /* jshint bitwise:true */
+
+ rglwidgetClass.prototype.getArrayBuffer = function(base64) {
+ return this.base64DecToArr(base64, 4).buffer;
+ };
+
+ rglwidgetClass.prototype.getBufferedData = function(v) {
+ return this.readAccessor(parseInt(v, 10), this.scene.buffer);
+ };
+
+ rglwidgetClass.prototype.readAccessor = function(acc, buf) {
+ var typeSignedByte = 5120,
+ typeUnsignedByte = 5121,
+ typeSignedShort = 5122,
+ typeUnsignedShort = 5123,
+ typeSignedInt = 5124,
+ typeUnsignedInt = 5125,
+ typeFloat = 5126,
+ typeDouble = 5130,
+ accessor = buf.accessors[acc],
+ bufferView = buf.bufferViews[accessor.bufferView],
+ buffer = buf.buffers[bufferView.buffer],
+ bytes,
+ lens = {
+ SCALAR: 1,
+ VEC2: 2,
+ VEC3: 3,
+ VEC4: 4,
+ MAT2: 4,
+ MAT3: 9,
+ MAT4: 16
+ },
+ rowsizes = {
+ SCALAR: 1,
+ VEC2: 2,
+ VEC3: 3,
+ VEC4: 4,
+ MAT2: 2,
+ MAT3: 3,
+ MAT4: 4
+ },
+ offset = 0,
+ len = lens[accessor.type],
+ rowsize = rowsizes[accessor.type],
+ count = len * accessor.count,
+ nrows = count / rowsize,
+ values, arr = [], row, i, j, k;
+
+ if (typeof buffer.bytes === "string")
+ buffer.bytes = this.getArrayBuffer(buffer.bytes);
+
+ bytes = buffer.bytes;
+
+ if (typeof accessor.byteOffset !== "undefined")
+ offset += accessor.byteOffset;
+
+ if (typeof bufferView.byteOffset !== "undefined")
+ offset += bufferView.byteOffset;
+
+ switch (accessor.componentType) {
+ case typeSignedByte:
+ values = new Int8Array(buffer.bytes, offset, count);
+ break;
+
+ case typeUnsignedByte:
+ values = new Uint8Array(buffer.bytes, offset, count);
+ break;
+
+ case typeSignedShort:
+ values = new Int16Array(buffer.bytes, offset, count);
+ break;
+
+ case typeUnsignedShort:
+ values = new Uint16Array(buffer.bytes, offset, count);
+ break;
+
+ case typeSignedInt:
+ values = new Int32Array(buffer.bytes, offset, count);
+ break;
+
+ case typeUnsignedInt:
+ values = new Uint32Array(buffer.bytes, offset, count);
+ break;
+
+ case typeFloat:
+ values = new Float32Array(buffer.bytes, offset, count);
+ break;
+
+ case typeDouble:
+ values = new Float64Array(buffer.bytes, offset, count);
+ break;
+ }
+
+ /* This is all very inefficient, but is convenient
+ to work with the old code. */
+ k = 0;
+ for (i = 0; i < nrows; i++) {
+ row = [];
+ for (j = 0; j < rowsize; j++) {
+ if (accessor.normalized) {
+ switch(accessor.componentType) {
+ case typeSignedByte:
+ row.push(Math.max(values[k++]/127, -1.0));
+ break;
+ case typeSignedShort:
+ row.push(Math.max(values[k++]/32767, -1.0));
+ break;
+ case typeUnsignedByte:
+ row.push(values[k++]/255);
+ break;
+ case typeUnsignedShort:
+ row.push(values[k++]/65535);
+ break;
+ }
+ } else
+ row.push(values[k++]);
+ }
+ arr.push(row);
+ }
+ return arr;
+ };
+
+ rglwidgetClass.prototype.expandBufferedFields = function(obj) {
+ /* this list needs to match the one in convertScene.R */
+ var fields = ["vertices", "normals", "indices",
+ "texcoords", "colors", "centers"], i, field;
+ for (i = 0; i < fields.length; i++) {
+ field = obj[fields[i]];
+ if (this.isBuffered(field))
+ obj[fields[i]] = this.getBufferedData(field);
+ }
+ };
diff --git a/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/controls.src.js b/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/controls.src.js
new file mode 100644
index 00000000..ffc7be8f
--- /dev/null
+++ b/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/controls.src.js
@@ -0,0 +1,591 @@
+
+ /**
+ * Change the displayed subset
+ * @param { Object } el - Element of the control; not used.
+ * @param { Object } control - The subset control data.
+ */
+ rglwidgetClass.prototype.subsetSetter = function(el, control) {
+ if (typeof control.subscenes === "undefined" ||
+ control.subscenes === null)
+ control.subscenes = this.scene.rootSubscene;
+ var value = Math.round(control.value),
+ subscenes = [].concat(control.subscenes),
+ fullset = [].concat(control.fullset),
+ i, j, subsceneid,
+ adds = [], deletes = [];
+ if (rglwidgetClass.missing(value))
+ value = control.value = 0;
+ if (control.accumulate)
+ for (i=0; i <= value; i++)
+ adds = adds.concat(control.subsets[i]);
+ else
+ adds = adds.concat(control.subsets[value]);
+ deletes = fullset.filter(function(x) { return adds.indexOf(x) < 0; });
+ for (i = 0; i < subscenes.length; i++) {
+ subsceneid = subscenes[i];
+ if (typeof this.getObj(subsceneid) === "undefined")
+ this.alertOnce("typeof object is undefined");
+ for (j = 0; j < adds.length; j++)
+ this.addToSubscene(adds[j], subsceneid);
+ for (j = 0; j < deletes.length; j++)
+ this.delFromSubscene(deletes[j], subsceneid);
+ }
+ };
+
+ /**
+ * Change the requested property
+ * @param { Object } el - Element of the control; not used.
+ * @param { Object } control - The property setter control data.
+ */
+ rglwidgetClass.prototype.propertySetter = function(el, control) {
+ var value = control.value,
+ values = [].concat(control.values),
+ svals = [].concat(control.param),
+ direct = values[0] === null,
+ entries = [].concat(control.entries),
+ ncol = entries.length,
+ nrow = values.length/ncol,
+ properties = rglwidgetClass.repeatToLen(control.properties, ncol),
+ objids = rglwidgetClass.repeatToLen(control.objids, ncol),
+ property, objid = objids[0],
+ obj = this.getObj(objid),
+ propvals, i, j, v1, v2, p, entry, gl, needsBinding,
+ newprop, newid,
+
+ getPropvals = function() {
+ if (property === "userMatrix")
+ return obj.par3d.userMatrix.getAsArray();
+ else if (property === "scale" || property === "FOV" || property === "zoom")
+ return [].concat(obj.par3d[property]);
+ else
+ return [].concat(obj[property]);
+ },
+
+ putPropvals = function(newvals) {
+ if (newvals.length === 1)
+ newvals = newvals[0];
+ if (property === "userMatrix")
+ obj.par3d.userMatrix.load(newvals);
+ else if (property === "scale" || property === "FOV" || property === "zoom")
+ obj.par3d[property] = newvals;
+ else
+ obj[property] = newvals;
+ };
+
+ if (direct && typeof value === "undefined")
+ return;
+
+ if (control.interp) {
+ values = values.slice(0, ncol).concat(values).
+ concat(values.slice(ncol*(nrow-1), ncol*nrow));
+ svals = [-Infinity].concat(svals).concat(Infinity);
+ for (i = 1; i < svals.length; i++) {
+ if (value <= svals[i]) {
+ if (svals[i] === Infinity)
+ p = 1;
+ else
+ p = (svals[i] - value)/(svals[i] - svals[i-1]);
+ break;
+ }
+ }
+ } else if (!direct) {
+ value = Math.round(value);
+ }
+
+ for (j=0; j
value - svals[j-1])
+ j = j - 1;
+ }
+ break;
+ }
+ }
+
+ obj = this.getObj(control.objid);
+ // First, make sure color attributes vary in original
+ if (typeof obj.vOffsets !== "undefined") {
+ varies = true;
+ for (k = 0; k < ncol; k++) {
+ attrib = attributes[k];
+ if (typeof attrib !== "undefined") {
+ ofs = obj.vOffsets[ofss[attrib]];
+ if (ofs < 0) {
+ switch(attrib) {
+ case "alpha":
+ case "red":
+ case "green":
+ case "blue":
+ obj.colors = [obj.colors[0], obj.colors[0]];
+ break;
+ }
+ varies = false;
+ }
+ }
+ }
+ if (!varies)
+ this.initObjId(control.objid);
+ }
+ propvals = obj.values;
+ aliases = obj.alias;
+ if (typeof aliases === "undefined")
+ aliases = [];
+ for (k=0; k= 0) {
+ if (ofs < 3) {
+ if (obj.normals[vertex][ofs] !== newval) { // Assume no aliases here...
+ obj.normals[vertex][ofs] = newval;
+ obj.initialized = false;
+ }
+ } else {
+ if (obj.offsets[vertex][0] !== newval) {
+ obj.offsets[vertex][0] = newval;
+ obj.initialized = false;
+ }
+ }
+ continue;
+ }
+ }
+ // Not a plane setting...
+ ofs = obj.vOffsets[ofss[attrib]];
+ if (ofs < 0)
+ this.alertOnce("Attribute '"+attrib+"' not found in object "+control.objid);
+ else {
+ stride = obj.vOffsets.stride;
+ ofs = ofs + pos[attrib];
+ entry = vertex*stride + ofs;
+ propvals[entry] = newval;
+ if (typeof alias !== "undefined")
+ for (a = 0; a < alias.length; a++)
+ propvals[alias[a]*stride + ofs] = newval;
+ }
+ }
+ if (typeof obj.buf !== "undefined") {
+ var gl = this.gl || this.initGL();
+ gl.bindBuffer(gl.ARRAY_BUFFER, obj.buf);
+ gl.bufferData(gl.ARRAY_BUFFER, propvals, gl.STATIC_DRAW);
+ }
+ };
+
+ /**
+ * Change the requested vertex properties by age
+ * @param { Object } el - Element of the control; not used.
+ * @param { Object } control - The age setter control data.
+ */
+ rglwidgetClass.prototype.ageSetter = function(el, control) {
+ var objids = [].concat(control.objids),
+ nobjs = objids.length,
+ time = control.value,
+ births = [].concat(control.births),
+ ages = [].concat(control.ages),
+ steps = births.length,
+ j = Array(steps),
+ p = Array(steps),
+ i, k, l, age, j0, propvals, stride, ofs, objid, obj,
+ attrib, dim, varies, alias, aliases, a, d,
+ attribs = ["colors", "alpha", "radii", "vertices",
+ "normals", "origins", "texcoords",
+ "x", "y", "z",
+ "red", "green", "blue"],
+ ofss = ["cofs", "cofs", "radofs", "vofs",
+ "nofs", "oofs", "tofs",
+ "vofs", "vofs", "vofs",
+ "cofs", "cofs", "cofs"],
+ dims = [3,1,1,3,
+ 3,2,2,
+ 1,1,1,
+ 1,1,1],
+ pos = [0,3,0,0,
+ 0,0,0,
+ 0,1,2,
+ 0,1,2];
+ /* Infinity doesn't make it through JSON */
+ ages[0] = -Infinity;
+ ages[ages.length-1] = Infinity;
+ for (i = 0; i < steps; i++) {
+ if (births[i] !== null) { // NA in R becomes null
+ age = time - births[i];
+ for (j0 = 1; age > ages[j0]; j0++);
+ if (ages[j0] === Infinity)
+ p[i] = 1;
+ else if (ages[j0] > ages[j0-1])
+ p[i] = (ages[j0] - age)/(ages[j0] - ages[j0-1]);
+ else
+ p[i] = 0;
+ j[i] = j0;
+ }
+ }
+ // First, make sure color attributes vary in original
+ for (l = 0; l < nobjs; l++) {
+ objid = objids[l];
+ obj = this.getObj(objid);
+ varies = true;
+ if (typeof obj.vOffsets === "undefined")
+ continue;
+ for (k = 0; k < attribs.length; k++) {
+ attrib = control[attribs[k]];
+ if (typeof attrib !== "undefined") {
+ ofs = obj.vOffsets[ofss[k]];
+ if (ofs < 0) {
+ switch(attribs[k]) {
+ case "colors":
+ case "alpha":
+ case "red":
+ case "green":
+ case "blue":
+ obj.colors = [obj.colors[0], obj.colors[0]];
+ break;
+ }
+ varies = false;
+ }
+ }
+ }
+ if (!varies)
+ this.initObjId(objid);
+ }
+ for (l = 0; l < nobjs; l++) {
+ objid = objids[l];
+ obj = this.getObj(objid);
+ if (typeof obj.vOffsets === "undefined")
+ continue;
+ aliases = obj.alias;
+ if (typeof aliases === "undefined")
+ aliases = [];
+ propvals = obj.values;
+ stride = obj.vOffsets.stride;
+ for (k = 0; k < attribs.length; k++) {
+ attrib = control[attribs[k]];
+ if (typeof attrib !== "undefined") {
+ ofs = obj.vOffsets[ofss[k]];
+ if (ofs >= 0) {
+ dim = dims[k];
+ ofs = ofs + pos[k];
+ for (i = 0; i < steps; i++) {
+ alias = aliases[i];
+ if (births[i] !== null) {
+ for (d=0; d < dim; d++) {
+ propvals[i*stride + ofs + d] = p[i]*attrib[dim*(j[i]-1) + d] + (1-p[i])*attrib[dim*j[i] + d];
+ if (typeof alias !== "undefined")
+ for (a=0; a < alias.length; a++)
+ propvals[alias[a]*stride + ofs + d] = propvals[i*stride + ofs + d];
+ }
+ }
+ }
+ } else
+ this.alertOnce("\'"+attribs[k]+"\' property not found in object "+objid);
+ }
+ }
+ obj.values = propvals;
+ if (typeof obj.buf !== "undefined") {
+ var gl = this.gl || this.initGL();
+ gl.bindBuffer(gl.ARRAY_BUFFER, obj.buf);
+ gl.bufferData(gl.ARRAY_BUFFER, obj.values, gl.STATIC_DRAW);
+ }
+ }
+ };
+
+ /**
+ * Bridge to old style control
+ * @param { Object } el - Element of the control; not used.
+ * @param { Object } control - The bridge control data.
+ */
+ rglwidgetClass.prototype.oldBridge = function(el, control) {
+ var attrname, global = window[control.prefix + "rgl"];
+ if (global)
+ for (attrname in global)
+ this[attrname] = global[attrname];
+ window[control.prefix + "rgl"] = this;
+ };
+
+ /**
+ * Set up a player control
+ * @param { Object } el - The player control element
+ * @param { Object } control - The player data.
+ */
+ rglwidgetClass.prototype.Player = function(el, control) {
+ var
+ self = this,
+ components = [].concat(control.components),
+ buttonLabels = [].concat(control.buttonLabels),
+
+ Tick = function() { /* "this" will be a timer */
+ var i,
+ nominal = this.value,
+ slider = this.Slider,
+ labels = this.outputLabels,
+ output = this.Output,
+ step;
+ if (typeof slider !== "undefined" && nominal !== slider.value)
+ slider.value = nominal;
+ if (typeof output !== "undefined") {
+ step = Math.round((nominal - output.sliderMin)/output.sliderStep);
+ if (labels !== null) {
+ output.innerHTML = labels[step];
+ } else {
+ step = step*output.sliderStep + output.sliderMin;
+ output.innerHTML = step.toPrecision(output.outputPrecision);
+ }
+ }
+ for (i=0; i < this.actions.length; i++) {
+ this.actions[i].value = nominal;
+ }
+ self.applyControls(el, this.actions, false);
+ self.drawScene();
+ },
+
+ OnSliderInput = function() { /* "this" will be the slider */
+ this.rgltimer.value = Number(this.value);
+ this.rgltimer.Tick();
+ },
+
+ addSlider = function(min, max, step, value) {
+ var slider = document.createElement("input");
+ slider.type = "range";
+ slider.min = min;
+ slider.max = max;
+ slider.step = step;
+ slider.value = value;
+ slider.oninput = OnSliderInput;
+ slider.sliderActions = control.actions;
+ slider.sliderScene = this;
+ slider.className = "rgl-slider";
+ slider.id = el.id + "-slider";
+ el.rgltimer.Slider = slider;
+ slider.rgltimer = el.rgltimer;
+ el.appendChild(slider);
+ },
+
+ addLabel = function(labels, min, step, precision) {
+ var output = document.createElement("output");
+ output.sliderMin = min;
+ output.sliderStep = step;
+ output.outputPrecision = precision;
+ output.className = "rgl-label";
+ output.id = el.id + "-label";
+ el.rgltimer.Output = output;
+ el.rgltimer.outputLabels = labels;
+ el.appendChild(output);
+ },
+
+ addButton = function(which, label, active) {
+ var button = document.createElement("input"),
+ onclicks = {Reverse: function() { this.rgltimer.reverse();},
+ Play: function() { this.rgltimer.play();
+ this.value = this.rgltimer.enabled ? this.inactiveValue : this.activeValue; },
+ Slower: function() { this.rgltimer.slower(); },
+ Faster: function() { this.rgltimer.faster(); },
+ Reset: function() { this.rgltimer.reset(); },
+ Step: function() { this.rgltimer.step(); }
+ };
+ button.rgltimer = el.rgltimer;
+ button.type = "button";
+ button.value = label;
+ button.activeValue = label;
+ button.inactiveValue = active;
+ if (which === "Play")
+ button.rgltimer.PlayButton = button;
+ button.onclick = onclicks[which];
+ button.className = "rgl-button";
+ button.id = el.id + "-" + which;
+ el.appendChild(button);
+ };
+
+ if (typeof control.reinit !== "undefined" && control.reinit !== null) {
+ control.actions.reinit = control.reinit;
+ }
+ el.rgltimer = new rgltimerClass(Tick, control.start, control.interval, control.stop,
+ control.step, control.value, control.rate, control.loop, control.actions);
+ for (var i=0; i < components.length; i++) {
+ switch(components[i]) {
+ case "Slider": addSlider(control.start, control.stop,
+ control.step, control.value);
+ break;
+ case "Label": addLabel(control.labels, control.start,
+ control.step, control.precision);
+ break;
+ default:
+ addButton(components[i], buttonLabels[i], control.pause);
+ }
+ }
+ el.rgltimer.Tick();
+ };
+
+ /**
+ * Apply all registered controls
+ * @param { Object } el - DOM element of the control
+ * @param { Object } x - List of actions to apply
+ * @param { boolean } [draw=true] - Whether to redraw after applying
+ */
+ rglwidgetClass.prototype.applyControls = function(el, x, draw) {
+ var self = this, reinit = x.reinit, i, control, type;
+ for (i = 0; i < x.length; i++) {
+ control = x[i];
+ type = control.type;
+ self[type](el, control);
+ }
+ if (typeof reinit !== "undefined" && reinit !== null) {
+ reinit = [].concat(reinit);
+ for (i = 0; i < reinit.length; i++)
+ self.getObj(reinit[i]).initialized = false;
+ }
+ if (typeof draw === "undefined" || draw)
+ self.drawScene();
+ };
+
+ /**
+ * Handler for scene change
+ * @param { Object } message - What sort of scene change to do?
+ */
+ rglwidgetClass.prototype.sceneChangeHandler = function(message) {
+ var self = document.getElementById(message.elementId).rglinstance,
+ objs = message.objects, mat = message.material,
+ root = message.rootSubscene,
+ initSubs = message.initSubscenes,
+ redraw = message.redrawScene,
+ skipRedraw = message.skipRedraw,
+ deletes, subs, allsubs = [], i,j;
+ if (typeof message.delete !== "undefined") {
+ deletes = [].concat(message.delete);
+ if (typeof message.delfromSubscenes !== "undefined")
+ subs = [].concat(message.delfromSubscenes);
+ else
+ subs = [];
+ for (i = 0; i < deletes.length; i++) {
+ for (j = 0; j < subs.length; j++) {
+ self.delFromSubscene(deletes[i], subs[j]);
+ }
+ delete self.scene.objects[deletes[i]];
+ }
+ }
+ if (typeof objs !== "undefined") {
+ Object.keys(objs).forEach(function(key){
+ key = parseInt(key, 10);
+ self.scene.objects[key] = objs[key];
+ self.initObjId(key);
+ var obj = self.getObj(key),
+ subs = [].concat(obj.inSubscenes), k;
+ allsubs = allsubs.concat(subs);
+ for (k = 0; k < subs.length; k++)
+ self.addToSubscene(key, subs[k]);
+ });
+ }
+ if (typeof mat !== "undefined") {
+ self.scene.material = mat;
+ }
+ if (typeof root !== "undefined") {
+ self.scene.rootSubscene = root;
+ }
+ if (typeof initSubs !== "undefined")
+ allsubs = allsubs.concat(initSubs);
+ allsubs = self.unique(allsubs);
+ for (i = 0; i < allsubs.length; i++) {
+ self.initSubscene(allsubs[i]);
+ }
+ if (typeof skipRedraw !== "undefined") {
+ root = self.getObj(self.scene.rootSubscene);
+ root.par3d.skipRedraw = skipRedraw;
+ }
+ if (redraw)
+ self.drawScene();
+ };
diff --git a/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/draw.src.js b/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/draw.src.js
new file mode 100644
index 00000000..370afb15
--- /dev/null
+++ b/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/draw.src.js
@@ -0,0 +1,1370 @@
+ /**
+ * Methods related to drawing
+ * @name ___METHODS_FOR_DRAWING___
+ * @memberof rglwidgetClass
+ * @kind function
+ * @instance
+ */
+
+ /**
+ * Start drawing
+ * @returns { boolean } Previous state
+ */
+ rglwidgetClass.prototype.startDrawing = function() {
+ var value = this.drawing;
+ this.drawing = true;
+ return value;
+ };
+
+ /**
+ * Stop drawing and check for context loss
+ * @param { boolean } saved - Previous state
+ */
+ rglwidgetClass.prototype.stopDrawing = function(saved) {
+ this.drawing = saved;
+ if (!saved && this.gl && this.gl.isContextLost())
+ this.restartCanvas();
+ };
+
+ /**
+ * Update the triangles used to display a plane
+ * @param { number } id - id of the plane
+ * @param { Object } bbox - bounding box in which to display the plane
+ */
+ rglwidgetClass.prototype.planeUpdateTriangles = function(obj, bbox) {
+ var perms = [[0,0,1], [1,2,2], [2,1,0]],
+ x, xrow, elem, A, d, nhits, i, j, k, u, v, w, intersect, which, v0, v2, vx, reverse,
+ face1 = [], face2 = [], normals = [],
+ nPlanes = obj.normals.length, idx, center;
+ obj.bbox = bbox;
+ obj.vertices = [];
+ obj.centers = [];
+ obj.initialized = false;
+ for (elem = 0; elem < nPlanes; elem++) {
+// Vertex Av = normal.getRecycled(elem);
+ x = [];
+ A = obj.normals[elem];
+ d = obj.offsets[elem][0];
+ nhits = 0;
+ for (i=0; i<3; i++)
+ for (j=0; j<2; j++)
+ for (k=0; k<2; k++) {
+ u = perms[0][i];
+ v = perms[1][i];
+ w = perms[2][i];
+ if (A[w] !== 0.0) {
+ intersect = -(d + A[u]*bbox[j+2*u] + A[v]*bbox[k+2*v])/A[w];
+ if (bbox[2*w] < intersect && intersect < bbox[1+2*w]) {
+ xrow = [];
+ xrow[u] = bbox[j+2*u];
+ xrow[v] = bbox[k+2*v];
+ xrow[w] = intersect;
+ x.push(xrow);
+ face1[nhits] = j + 2*u;
+ face2[nhits] = k + 2*v;
+ nhits++;
+ }
+ }
+ }
+
+ if (nhits > 3) {
+ /* Re-order the intersections so the triangles work */
+ for (i=0; i i+1) {
+ rglwidgetClass.swap(x, i+1, which);
+ rglwidgetClass.swap(face1, i+1, which);
+ rglwidgetClass.swap(face2, i+1, which);
+ }
+ }
+ }
+ if (nhits >= 3) {
+ /* Put in order so that the normal points out the FRONT of the faces */
+ v0 = [x[0][0] - x[1][0] , x[0][1] - x[1][1], x[0][2] - x[1][2]];
+ v2 = [x[2][0] - x[1][0] , x[2][1] - x[1][1], x[2][2] - x[1][2]];
+ /* cross-product */
+ vx = rglwidgetClass.xprod(v0, v2);
+ reverse = rglwidgetClass.dotprod(vx, A) > 0;
+
+ for (i=0; i 0) {
+ clipplanedata = new Float32Array(4*n);
+ for (i=0; i < clipplaneids.length; i++) {
+ clip = this.getObj(clipplaneids[i]);
+ for (j=0; j < clip.offsets.length; j++) {
+ clipplanedata.set(clip.IMVClip[j], clipcheck);
+ clipcheck += 4;
+ }
+ }
+
+ // Leftovers are initialized to zero, which is fine
+ gl.uniform4fv(obj.clipLoc, clipplanedata);
+ }
+ };
+
+ /**
+ * Do code for lighting
+ * @param { object } obj - Object to work with
+ * @param { object } subscene - Subscene to work with
+ */
+ rglwidgetClass.prototype.doLighting = function(obj, subscene) {
+ var gl = this.gl, i, j, n, light,
+ ambient, specular, diffuse, lightDir, viewpoint, finite,
+ ambient0, specular0;
+
+ gl.uniform3fv( obj.emissionLoc, obj.emission);
+ gl.uniform1f( obj.shininessLoc, obj.shininess);
+ while ((typeof subscene.lights === "undefined" ||
+ subscene.lights.length === 0) &&
+ typeof subscene.parent !== "undefined")
+ subscene = this.getObj(subscene.parent);
+
+ if (typeof subscene.lights === "undefined")
+ return;
+
+ n = subscene.lights.length;
+
+ ambient = new Float32Array(3*n);
+ specular = new Float32Array(3*n);
+ diffuse = new Float32Array(3*n);
+ lightDir = new Float32Array(3*n);
+ viewpoint = new Int32Array(n);
+ finite = new Int32Array(n);
+
+ for (i=0; i < n; i++) {
+ light = this.getObj(subscene.lights[i]);
+ if (!light.initialized) this.initObj(light);
+ ambient0 = this.componentProduct(light.ambient, obj.ambient);
+ specular0 = this.componentProduct(light.specular, obj.specular);
+ for (j=0; j < 3; j++) {
+ ambient[3*i + j] = ambient0[j];
+ specular[3*i + j] = specular0[j];
+ diffuse[3*i + j] = light.diffuse[j];
+ lightDir[3*i + j] = light.lightDir[j];
+ }
+ viewpoint[i] = light.viewpoint;
+ finite[i] = light.finite;
+ }
+
+ for (i = n; i < obj.nlights; i++) {
+ for (j = 0; j < 3; j++) {
+ ambient[3*i + j] = 0.0;
+ specular[3*i + j] = 0.0;
+ diffuse[3*i + j] = 0.0;
+ }
+ }
+
+ gl.uniform3fv( obj.ambientLoc, ambient);
+ gl.uniform3fv( obj.specularLoc, specular);
+ gl.uniform3fv( obj.diffuseLoc, diffuse);
+ gl.uniform3fv( obj.lightDirLoc, lightDir);
+ gl.uniform1iv( obj.viewpointLoc, viewpoint);
+ gl.uniform1iv( obj.finiteLoc, finite);
+ };
+
+ /**
+ * Do code for colors
+ * @param { object } obj - Object to work with
+ */
+ rglwidgetClass.prototype.doColors = function(obj) {
+ var gl = this.gl;
+ if (obj.colorCount === 1) {
+ gl.disableVertexAttribArray( this.colLoc );
+ gl.vertexAttrib4fv( this.colLoc, new Float32Array(obj.onecolor));
+ return false;
+ } else {
+ gl.enableVertexAttribArray( this.colLoc );
+ gl.vertexAttribPointer(this.colLoc, 4, gl.FLOAT, false, 4*obj.vOffsets.stride, 4*obj.vOffsets.cofs);
+ return true;
+ }
+ };
+
+ /**
+ * Do code for normals
+ * @param { object } obj - Object to work with
+ */
+ rglwidgetClass.prototype.doNormals = function(obj) {
+ var gl = this.gl;
+ if (obj.vOffsets.nofs >= 0) {
+ gl.enableVertexAttribArray( obj.normLoc );
+ gl.vertexAttribPointer(obj.normLoc, 3, gl.FLOAT, false, 4*obj.vOffsets.stride, 4*obj.vOffsets.nofs);
+ return true;
+ } else
+ return false;
+ };
+
+ /**
+ * Do code for vNormal
+ * @param { object } obj - Object to work with
+ */
+ rglwidgetClass.prototype.doNormMat = function(obj) {
+ var gl = this.gl;
+
+ gl.uniformMatrix4fv( obj.normMatLoc, false, new Float32Array(this.normMatrix.getAsArray()) );
+ };
+
+ /**
+ * Do code for textures
+ * @param { object } obj - Object to work with
+ */
+ rglwidgetClass.prototype.doTexture = function(obj) {
+ var gl = this.gl,
+ is_sphere = obj.type === "sphere";
+ gl.enableVertexAttribArray( obj.texLoc );
+ if (is_sphere)
+ gl.vertexAttribPointer(obj.texLoc, 2, gl.FLOAT, false, 4*this.sphere.vOffsets.stride, 4*this.sphere.vOffsets.tofs);
+ else
+ gl.vertexAttribPointer(obj.texLoc, 2, gl.FLOAT, false, 4*obj.vOffsets.stride, 4*obj.vOffsets.tofs);
+ gl.activeTexture(gl.TEXTURE0);
+ gl.bindTexture(gl.TEXTURE_2D, obj.texture);
+ gl.uniform1i( obj.sampler, 0);
+ return true;
+ };
+
+ /**
+ * Do code for user attributes
+ * @param { object } obj - Object to work with
+ */
+ rglwidgetClass.prototype.doUserAttributes = function(obj) {
+ if (typeof obj.userAttributes !== "undefined") {
+ var gl = this.gl;
+ for (var attr in obj.userAttribSizes) { // Not all attributes may have been used
+ gl.enableVertexAttribArray( obj.userAttribLocations[attr] );
+ gl.vertexAttribPointer( obj.userAttribLocations[attr], obj.userAttribSizes[attr],
+ gl.FLOAT, false, 4*obj.vOffsets.stride, 4*obj.userAttribOffsets[attr]);
+ }
+ }
+ };
+
+ /**
+ * Do code for user uniforms
+ * @param { object } obj - Object to work with
+ */
+ rglwidgetClass.prototype.doUserUniforms = function(obj) {
+ var gl = this.gl, attr;
+ if (typeof obj.userUniforms !== "undefined") {
+ for (attr in obj.userUniformLocations) {
+ var loc = obj.userUniformLocations[attr];
+ if (loc !== null) {
+ var uniform = obj.userUniforms[attr];
+ if (typeof uniform !== "undefined") {
+ var dim = rglwidgetClass.arrayDim(uniform);
+ if (dim.length === 0)
+ gl.uniform1f(loc, uniform);
+ else if (dim.length === 1) {
+ uniform = new Float32Array(uniform);
+ switch(uniform.length) {
+ case 2: gl.uniform2fv(loc, uniform); break;
+ case 3: gl.uniform3fv(loc, uniform); break;
+ case 4: gl.uniform4fv(loc, uniform); break;
+ default: console.warn("bad uniform length");
+ }
+ } else if (dim.length === 2 && dim[0] === 4 && dim[1] === 4)
+ gl.uniformMatrix4fv(loc, false, new Float32Array(rglwidgetClass.flatten(uniform)));
+ else if (dim.length === 2) {
+ uniform = new Float32Array(rglwidgetClass.flatten(uniform));
+ switch(dim[[1]]) {
+ case 1: gl.uniform1fv(loc, uniform); break;
+ case 2: gl.uniform2fv(loc, uniform); break;
+ case 3: gl.uniform3fv(loc, uniform); break;
+ case 4: gl.uniform4fv(loc, uniform); break;
+ default: console.warn("bad uniform column count");
+ }
+ } else
+ console.warn("unsupported uniform shape");
+ }
+ }
+ }
+ }
+ if (typeof obj.userTextures !== "undefined") {
+ var has_texture = rglwidgetClass.isSet(obj.flags, rglwidgetClass.f_has_texture),
+ texnum = has_texture - 1;
+ for (attr in obj.userTextures) {
+ var texture = obj.userTextures[attr];
+ if (texture.sampler !== null) {
+ texnum += 1;
+ gl.activeTexture(gl.TEXTURE0 + texnum);
+ gl.bindTexture(gl.TEXTURE_2D, texture.texture);
+ gl.uniform1i( texture.sampler, texnum);
+ }
+ }
+ }
+ };
+
+ /**
+ * Load indices for complex drawing
+ * @param { object } obj - Object to work with
+ * @param { numeric } pass - Which pass of drawing?
+ * @param { array } indices - Indices to draw
+ */
+ rglwidgetClass.prototype.doLoadIndices = function(obj, pass, indices) {
+ var gl = this.gl,
+ f = obj.f[pass],
+ type = obj.type,
+ fat_lines = rglwidgetClass.isSet(obj.flags, rglwidgetClass.f_fat_lines),
+ fnew, step;
+ switch(type){
+ case "points":
+ step = 1;
+ break;
+ case "abclines":
+ case "lines":
+ if (fat_lines)
+ step = 6;
+ else
+ step = 2;
+ break;
+ case "linestrip":
+ if (fat_lines)
+ step = 6;
+ else
+ step = 1;
+ break;
+ case "sphere":
+ case "planes":
+ case "triangles":
+ step = 3;
+ break;
+ case "text":
+ case "sprites":
+ case "quads":
+ case "surface":
+ step = 6;
+ break;
+ default:
+ console.error("loadIndices for "+type);
+ return 0;
+ }
+ if (obj.index_uint)
+ fnew = new Uint32Array(step * indices.length);
+ else
+ fnew = new Uint16Array(step * indices.length);
+ for (var i = 0; i < indices.length; i++) {
+ for (var j = 0; j < step; j++) {
+ fnew[step*i + j] = f[step*indices[i] + j];
+ }
+ }
+ gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, fnew, gl.DYNAMIC_DRAW);
+ return fnew.length;
+ };
+
+ /**
+ * Do code for depth masking
+ * @param { boolean } mask - whether to mask
+ */
+ rglwidgetClass.prototype.doMasking = function(mask) {
+ var gl = this.gl;
+ gl.depthMask(mask);
+ };
+
+ /**
+ * Do code for alpha blending
+ * @param { boolean } blend - Whether to blend.
+ * @param { integer } objid - Object id
+ */
+ rglwidgetClass.prototype.doBlending = function(blend, objid) {
+ var gl = this.gl, blendfunc, obj,
+ blends = {zero: gl.ZERO,
+ one: gl.ONE,
+ src_color: gl.SRC_COLOR,
+ one_minus_src_color: gl.ONE_MINUS_SRC_COLOR,
+ dst_color: gl.DST_COLOR,
+ one_minus_dst_color: gl.ONE_MINUS_DST_COLOR,
+ src_alpha: gl.SRC_ALPHA,
+ one_minus_src_alpha: gl.ONE_MINUS_SRC_ALPHA,
+ dst_alpha: gl.DST_ALPHA,
+ one_minus_dst_alpha: gl.ONE_MINUS_DST_ALPHA,
+ constant_color: gl.CONSTANT_COLOR,
+ one_minus_constant_color: gl.ONE_MINUS_CONSTANT_COLOR,
+ constant_alpha: gl.CONSTANT_ALPHA,
+ one_minus_constant_alpha: gl.ONE_MINUS_CONSTANT_ALPHA,
+ src_alpha_saturate: gl.SRC_ALPHA_SATURATE};
+ if (blend) {
+ obj = this.getObj(objid);
+ blendfunc = this.getMaterial(obj, "blend");
+ gl.blendFuncSeparate(blends[blendfunc[0]],
+ blends[blendfunc[1]],
+ gl.ONE, gl.ONE);
+ gl.enable(gl.BLEND);
+ } else {
+ gl.disable(gl.BLEND);
+ }
+ };
+
+ /**
+ * Set up for fog in the subscene
+ * @param { object } obj - background object
+ * @param { object } subscene - which subscene
+ */
+ rglwidgetClass.prototype.doFog = function(obj, subscene) {
+ var gl = this.gl, fogmode, color,
+ observer = subscene.par3d.observer[2],
+ sintheta = Math.sin(subscene.par3d.FOV*Math.PI/180/2),
+ parms = [this.frustum.near - 2*observer,
+ this.frustum.far - 2*observer,
+ this.fogScale,
+ (1-sintheta)/(1+sintheta)];
+ if (typeof this.fogType === "undefined")
+ this.fogType = "none";
+ if (typeof this.fogScale === "undefined")
+ parms[2] = 1;
+ if (sintheta === 0)
+ parms[3] = 1/3;
+ switch(this.fogType){
+ case "none": fogmode = 0; break;
+ case "linear":
+ fogmode = 1; break;
+ case "exp":
+ fogmode = 2; break;
+ case "exp2":
+ fogmode = 3;
+ break;
+ default: console.error("Unknown fogtype "+this.fogType);
+ }
+ gl.uniform1i(obj.uFogMode, fogmode);
+ color = this.fogColor;
+ gl.uniform3f(obj.uFogColor, color[0], color[1], color[2]);
+ gl.uniform4f(obj.uFogParms, parms[0], parms[1], parms[2], parms[3]);
+ };
+
+ /* The draw methods are called twice. When
+ this.opaquePass is true, they should draw opaque parts
+ of the scene, and return the list of transparent
+ pieces. Here context is the context array on input,
+ modified when the matrices are changed.
+ When this.opaquePass is false, the context argument
+ contains a "piece", i.e. an ordered list of parts
+ of the object to draw. */
+
+ /**
+ * Draw simple object
+ * @param { object } obj - Object to draw
+ * @param { object } subscene - which subscene
+ * @param { array } context - Which context are we in?
+ */
+ rglwidgetClass.prototype.drawSimple = function(obj, subscene, context) {
+ var
+ fl,
+ is_transparent,
+ type = obj.type,
+ gl = this.gl || this.initGL(),
+ count,
+ pass, mode, pmode,
+ enabled = {};
+
+ if (!obj.initialized)
+ this.initObj(obj);
+
+ if (this.texturesLoading)
+ return[];
+
+ count = obj.vertexCount;
+ if (!count)
+ return [];
+
+ fl = obj.defFlags;
+ is_transparent = fl.is_transparent || obj.someHidden;
+
+ if (is_transparent && this.opaquePass)
+ return this.getPieces(context, obj.id, 0, obj);
+
+ this.doDepthTest(obj);
+
+ this.doMasking(this.getMaterial(obj, "depth_mask"));
+
+ gl.useProgram(obj.prog);
+
+ this.doPolygonOffset(obj);
+
+ gl.bindBuffer(gl.ARRAY_BUFFER, obj.buf);
+
+ gl.uniformMatrix4fv( obj.prMatLoc, false, new Float32Array(this.prMatrix.getAsArray()) );
+ gl.uniformMatrix4fv( obj.mvMatLoc, false, new Float32Array(this.mvMatrix.getAsArray()) );
+
+ this.doClipping(obj, subscene);
+
+ if (fl.needs_vnormal)
+ this.doNormMat(obj);
+
+ if (fl.is_lit)
+ this.doLighting(obj, subscene);
+
+ if (fl.has_fog)
+ this.doFog(obj, subscene);
+
+ this.doUserAttributes(obj);
+
+ this.doUserUniforms(obj);
+
+ gl.enableVertexAttribArray( this.posLoc );
+ enabled.posLoc = true;
+
+ if (fl.has_texture || obj.type === "text")
+ enabled.texLoc = this.doTexture(obj);
+
+ enabled.colLoc = this.doColors(obj);
+ enabled.normLoc = this.doNormals(obj);
+
+ if (fl.fixed_size) {
+ gl.uniform3f( obj.textScaleLoc, 0.75/this.vp.width, 0.75/this.vp.height, 1.0);
+ }
+
+ if (fl.fixed_quads) {
+ gl.enableVertexAttribArray( obj.ofsLoc );
+ enabled.ofsLoc = true;
+ gl.vertexAttribPointer(obj.ofsLoc, 3, gl.FLOAT, false, 4*obj.vOffsets.stride, 4*obj.vOffsets.oofs);
+ }
+
+ for (pass = 0; pass < obj.passes; pass++) {
+ pmode = obj.pmode[pass];
+ if (pmode === "culled")
+ continue;
+
+ mode = fl.fat_lines && (fl.is_lines || pmode === "lines") ? "TRIANGLES" : this.mode4type[type];
+
+ if (fl.is_twosided) {
+ gl.uniform1i(obj.frontLoc, pass !== 0);
+ if (fl.has_normals) {
+ gl.uniformMatrix4fv(obj.invPrMatLoc, false, new Float32Array(this.invPrMatrix.getAsArray()));
+ }
+ }
+
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, obj.ibuf[pass]);
+ if (!this.opaquePass) {
+ if (type === "sphere" && obj.fastTransparency)
+ count = this.doLoadIndices(obj, pass, this.sphere.fastpieces[0].indices);
+ else
+ count = this.doLoadIndices(obj, pass, context.indices);
+ } else {
+ count = obj.f[pass].length;
+ gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, obj.f[pass], gl.STATIC_DRAW);
+ }
+ if (!fl.is_lines && pmode === "lines" && !fl.fat_lines) {
+ mode = "LINES";
+ } else if (pmode === "points") {
+ mode = "POINTS";
+ }
+
+ if ((fl.is_lines || pmode === "lines") && fl.fat_lines) {
+ gl.enableVertexAttribArray(obj.pointLoc);
+ enabled.pointLoc = true;
+ gl.vertexAttribPointer(obj.pointLoc, 2, gl.FLOAT, false, 4*obj.vOffsets.stride, 4*obj.vOffsets.pointofs);
+ gl.enableVertexAttribArray(obj.nextLoc );
+ enabled.nextLoc = true;
+ gl.vertexAttribPointer(obj.nextLoc, 3, gl.FLOAT, false, 4*obj.vOffsets.stride, 4*obj.vOffsets.nextofs);
+ gl.uniform1f(obj.aspectLoc, this.vp.width/this.vp.height);
+ gl.uniform1f(obj.lwdLoc, this.getMaterial(obj, "lwd")/this.vp.height);
+ }
+
+ gl.vertexAttribPointer(this.posLoc, 3, gl.FLOAT, false, 4*obj.vOffsets.stride, 4*obj.vOffsets.vofs);
+
+ gl.drawElements(gl[mode], count, obj.index_uint ? gl.UNSIGNED_INT : gl.UNSIGNED_SHORT, 0);
+ }
+ this.disableArrays(obj, enabled);
+ return [];
+ };
+
+ /**
+ * Draw planes object
+ * @param { object } obj - Object to draw
+ * @param { object } subscene - which subscene
+ * @param { array } context - Which context are we in?
+ */
+ rglwidgetClass.prototype.drawPlanes = function(obj, subscene, context) {
+ if (obj.bbox !== subscene.par3d.bbox || !obj.initialized) {
+ this.planeUpdateTriangles(obj, subscene.par3d.bbox);
+ }
+ return this.drawSimple(obj, subscene, context);
+ };
+
+ /**
+ * @param { object } obj - object to draw
+ * @param { object } subscene
+ * @param { array } context
+ * @description
+ * Draw spheres in a subscene
+ *
+ * Drawing spheres happens in six ways:
+ * 1 opaquepass, not transparent: transform and draw this.sphere count times
+ * 2 opaquepass, transparent, not fast: transform & collect sphere pieces count times
+ * 3 opaquepass, transparent, fast: order the centres into separate pieces, order this.sphere once
+ * 4 not opaquepass, not transparent: do nothing
+ * 5 not opaquepass, transparent, not fast: transform for one sphere, draw one merged piece
+ * 6 not opaquepass, transparent, fast: transform for one sphere, draw this.sphere in fixed order.
+ **/
+
+ rglwidgetClass.prototype.drawSpheres = function(obj, subscene, context) {
+ var flags = obj.flags,
+ is_transparent = rglwidgetClass.isSet(flags, rglwidgetClass.f_is_transparent),
+ sphereMV, baseofs, ofs, sscale, i,
+ count, nc, scount, scale, indices, sphereNorm,
+ enabled = {}, drawing,
+ saveNorm = new CanvasMatrix4(this.normMatrix),
+ saveMV = new CanvasMatrix4(this.mvMatrix),
+ savePRMV = null,
+ result = [], idx, margin = obj.material.margin;
+
+ if (typeof margin !== "undefined")
+ if (!this.marginVecToDataVec(obj, subscene))
+ return [];
+
+ if (!obj.initialized)
+ this.initObj(obj);
+
+ count = obj.vertexCount;
+ if (!count)
+ return [];
+
+ is_transparent = is_transparent || obj.someHidden;
+
+ if (!this.opaquePass && !is_transparent)
+ return [];
+
+ if (this.prmvMatrix !== null)
+ savePRMV = new CanvasMatrix4(this.prmvMatrix);
+
+ scale = subscene.par3d.scale;
+ sphereNorm = new CanvasMatrix4();
+ sphereNorm.scale(scale[0], scale[1], scale[2]);
+ sphereNorm.multRight(saveNorm);
+ this.normMatrix = sphereNorm;
+
+ if (this.opaquePass) {
+ context = context.slice();
+ context.push(obj.id);
+ }
+
+ drawing = this.opaquePass !== is_transparent;
+ if (drawing) {
+ nc = obj.colorCount;
+ if (nc === 1) {
+ this.sphere.onecolor = obj.onecolor;
+ }
+ }
+
+ this.initShapeFromObj(this.sphere, obj);
+
+ if (!this.opaquePass && obj.fastTransparency && typeof this.sphere.fastpieces === "undefined") {
+ this.sphere.fastpieces = this.getPieces(context.context, obj.id, 0, this.sphere);
+ this.sphere.fastpieces = this.sortPieces(this.sphere.fastpieces);
+ this.sphere.fastpieces = this.mergePieces(this.sphere.fastpieces);
+ }
+
+ if (this.opaquePass)
+ scount = count;
+ else {
+ indices = context.indices;
+ if (obj.fastTransparency)
+ scount = indices.length; /* Each item gives the center of a whole sphere */
+ else
+ scount = 1; /* Each item is a fragment of the sphere, at location subid */
+ }
+ for (i = 0; i < scount; i++) {
+ sphereMV = new CanvasMatrix4();
+ if (this.opaquePass)
+ idx = i;
+ else if (obj.fastTransparency)
+ idx = indices[i];
+ else
+ idx = context.subid;
+ if (typeof idx === "undefined")
+ console.error("idx is undefined");
+ baseofs = idx*obj.vOffsets.stride;
+ ofs = baseofs + obj.vOffsets.radofs;
+ sscale = obj.values[ofs];
+
+ sphereMV.scale(sscale/scale[0], sscale/scale[1], sscale/scale[2]);
+ sphereMV.translate(obj.values[baseofs],
+ obj.values[baseofs+1],
+ obj.values[baseofs+2]);
+ sphereMV.multRight(saveMV);
+ this.mvMatrix = sphereMV;
+ this.setnormMatrix2();
+ this.setprmvMatrix();
+ if (drawing) {
+ if (nc > 1) {
+ this.sphere.onecolor = obj.values.slice(baseofs + obj.vOffsets.cofs, baseofs + obj.vOffsets.cofs + 4);
+ }
+ this.drawSimple(this.sphere, subscene, context);
+ } else
+ result = result.concat(this.getSpherePieces(context, i, obj));
+ }
+ if (drawing)
+ this.disableArrays(obj, enabled);
+ this.normMatrix = saveNorm;
+ this.mvMatrix = saveMV;
+ this.prmvMatrix = savePRMV;
+
+ return result;
+ };
+
+ /**
+ * Prepare clipplanes for drawing
+ * @param { object } obj - clip planes object
+ */
+ rglwidgetClass.prototype.drawClipplanes = function(obj) {
+ var count = obj.offsets.length,
+ IMVClip = [];
+ for (var i=0; i < count; i++) {
+ IMVClip[i] = rglwidgetClass.multMV(this.invMatrix, obj.vClipplane.slice(4*i, 4*(i+1)));
+ }
+ obj.IMVClip = IMVClip;
+ return [];
+ };
+
+ /**
+ * Prepare linestrip for drawing
+ * @param { object } obj - line strip object
+ * @param { object } subscene
+ * @param { array } context
+ */
+ rglwidgetClass.prototype.drawLinestrip = function(obj, subscene, context) {
+ var origIndices, i, j, margin = obj.material.margin;
+
+ if (typeof margin !== "undefined")
+ if (!this.marginVecToDataVec(obj, subscene))
+ return [];
+
+ if (this.opaquePass)
+ return this.drawSimple(obj, subscene, context);
+ origIndices = context.indices.slice();
+ for (i=0; i < origIndices.length; i++) {
+ j = origIndices[i];
+ if (j < obj.centers.length - 1) {
+ context.indices = [j, j+1];
+ this.drawSimple(obj, subscene, context);
+ }
+ }
+ context.indices = origIndices;
+ return [];
+ };
+
+ /**
+ * Draw a sprites object in a subscene
+ * @param { object } obj - object to draw
+ * @param { object } subscene
+ * @param { object } context
+ */
+ rglwidgetClass.prototype.drawSprites = function(obj, subscene, context) {
+ var flags = obj.flags,
+ is_transparent = rglwidgetClass.isSet(flags, rglwidgetClass.f_is_transparent),
+ sprites3d = rglwidgetClass.isSet(flags, rglwidgetClass.f_sprites_3d),
+ fixed_size = rglwidgetClass.isSet(flags, rglwidgetClass.f_fixed_size),
+ rotating = rglwidgetClass.isSet(flags, rglwidgetClass.f_rotating),
+ i,j,
+ origMV = new CanvasMatrix4( this.mvMatrix ),
+ origPRMV = null,
+ origPR,
+ pos, radius, userMatrix,
+ result = [], margin = obj.material.margin;
+
+ if (typeof margin !== "undefined")
+ if (!this.marginVecToDataVec(obj, subscene))
+ return [];
+
+ if (!sprites3d)
+ return this.drawSimple(obj, subscene, context);
+
+ if (!obj.initialized)
+ this.initObj(obj);
+
+ if (!obj.vertexCount)
+ return [];
+
+ is_transparent = is_transparent || obj.someHidden;
+
+ var norigs = obj.vertices.length,
+ savenorm = new CanvasMatrix4(this.normMatrix),
+ iOrig, adj, offset;
+
+ userMatrix = obj.userMatrix;
+
+ if (this.opaquePass) {
+ context = context.slice();
+ context.push(obj.id);
+ } else
+ norigs = 1;
+
+ if (this.prmvMatrix !== null)
+ origPRMV = new CanvasMatrix4( this.prmvMatrix );
+
+ offset = obj.offset;
+
+ if (fixed_size && !rotating) {
+ origPR = this.prMatrix;
+ this.prMatrix = new CanvasMatrix4();
+ }
+
+ for (iOrig=0; iOrig < norigs; iOrig++) {
+ if (this.opaquePass)
+ j = iOrig;
+ else
+ j = context.subid;
+ pos = [].concat(obj.vertices[j]).concat(1.0);
+ radius = obj.radii.length > 1 ? obj.radii[j][0] : obj.radii[0][0];
+ this.mvMatrix = new CanvasMatrix4(userMatrix);
+ adj = this.getAdj(obj, j, offset);
+ this.mvMatrix.translate(1 - 2*adj[0], 1 - 2*adj[1], 1 - 2*adj[2]);
+ this.mvMatrix.scale(radius, radius, radius);
+
+ if (fixed_size) {
+ var viewport = subscene.par3d.viewport,
+ winwidth = viewport.width*this.canvas.width,
+ winheight = viewport.height*this.canvas.height,
+ scalex = 27/winwidth, scaley = 27/winheight,
+ scale = Math.sqrt(scalex * scaley);
+ if (!rotating) {
+ pos = rglwidgetClass.multVM(pos, origMV);
+ pos = rglwidgetClass.multVM(pos, origPR);
+ this.mvMatrix.scale(scalex, scaley, scale);
+ } else {
+ scale = 4.0 * scale * subscene.par3d.zoom;
+ this.mvMatrix.scale(scale, scale, scale);
+ }
+ this.mvMatrix.translate(pos[0]/pos[3], pos[1]/pos[3], pos[2]/pos[3]);
+ if (rotating)
+ this.mvMatrix.multRight(origMV);
+ } else {
+ if (!rotating) {
+ pos = rglwidgetClass.multVM(pos, origMV);
+ this.mvMatrix.translate(pos[0]/pos[3], pos[1]/pos[3], pos[2]/pos[3]);
+ } else {
+ this.mvMatrix.translate(pos[0]/pos[3], pos[1]/pos[3], pos[2]/pos[3]);
+ this.mvMatrix.multRight(origMV);
+ }
+ }
+ this.setnormMatrix2();
+ this.setprmvMatrix();
+
+ for (i=0; i < obj.objects.length; i++)
+ if (this.opaquePass)
+ result = result.concat(this.drawObjId(obj.objects[i], subscene.id, context.concat(j)));
+ else
+ this.drawObjId(obj.objects[i], subscene.id, context);
+ }
+ this.normMatrix = savenorm;
+ this.mvMatrix = origMV;
+ if (fixed_size && !rotating)
+ this.prMatrix = origPR;
+ if (origPRMV !== null)
+ this.prmvMatrix = origPRMV;
+ return result;
+ };
+
+ /**
+ * Draw object that might be in margin
+ * @param { Object } obj - text object to draw
+ * @param { Object } subscene - subscene holding it
+ * @param { Object } context - context for drawing
+ */
+ rglwidgetClass.prototype.drawMarginal = function(obj, subscene, context) {
+ var margin = obj.material.margin;
+
+ if (typeof margin !== "undefined")
+ if (!this.marginVecToDataVec(obj, subscene))
+ return [];
+
+ return this.drawSimple(obj, subscene, context);
+ };
+
+ /**
+ * Draw bounding box and decorations
+ * @param { Object } obj - bboxdeco to draw
+ * @param { Object } subscene - subscene holding it
+ * @param { Object } context - context for drawing
+ */
+ rglwidgetClass.prototype.drawBBox = function(obj, subscene, context) {
+ var flags = obj.flags,
+ is_transparent = rglwidgetClass.isSet(flags, rglwidgetClass.f_is_transparent),
+ scale, bbox, indices,
+ enabled = {}, drawing,
+ result = [], idx, center, edges,
+ saved;
+
+ if (!obj.initialized)
+ this.initBBox(obj);
+
+ is_transparent = is_transparent || obj.someHidden;
+
+ if (!this.opaquePass && !is_transparent)
+ return result;
+
+ this.setBbox(obj, subscene);
+
+ saved = this.setBBoxMatrices(obj);
+
+ bbox = obj.bbox;
+ center = obj.center;
+
+ scale = [bbox[1]-bbox[0], bbox[3]-bbox[2], bbox[5]-bbox[4]];
+
+ if (!obj.cube.initialized) {
+ this.initObj(obj.cube);
+ }
+
+ if (this.opaquePass) {
+ context = context.slice();
+ context.push(obj.id);
+ }
+
+ drawing = this.opaquePass !== is_transparent;
+ this.cube.onecolor = obj.cube.onecolor;
+ this.initShapeFromObj(this.cube, obj.cube);
+
+ if (!this.opaquePass)
+ indices = context.indices;
+
+ if (this.opaquePass)
+ idx = 0;
+ else
+ idx = context.subid;
+ if (typeof idx === "undefined")
+ console.error("idx is undefined");
+
+ if (drawing) {
+ this.drawSimple(this.cube, subscene, context);
+ } else
+ result = result.concat(this.getCubePieces(context, obj));
+
+ if (!obj.ticks.initialized) {
+ obj.ticks.locations = this.getTickLocations(obj);
+ obj.ticks.edges = undefined;
+ }
+ edges = this.getTickEdges(this.prmvMatrix);
+ if (obj.needsAxisCallback)
+ this.doAxisCallback(obj, edges);
+ if (!obj.ticks.edges || edges.toString() !== obj.ticks.edges.toString()) {
+ obj.ticks.edges = edges;
+ this.getTickVertices(obj.ticks);
+ this.placeTickLabels(obj);
+ this.setTickLabels(obj);
+ }
+ if (!obj.ticks.initialized) {
+ this.initObj(obj.ticks);
+ this.initObj(obj.labels);
+ }
+
+ if (drawing) {
+ this.drawSimple(obj.ticks, subscene, context);
+ this.drawSimple(obj.labels, subscene, context);
+
+ this.disableArrays(obj, enabled);
+ } else {
+ result = result.concat(this.drawSimple(obj.ticks, subscene, context));
+ result = result.concat(this.drawSimple(obj.labels, subscene, context));
+ }
+
+ this.restoreBBoxMatrices(saved);
+
+ return result;
+ };
+
+ /**
+ * Use ids to choose object to draw
+ * @param { numeric } id - object to draw
+ * @param { numeric } subscene
+ * @param { array } context
+ */
+ rglwidgetClass.prototype.drawObjId = function(id, subsceneid, context) {
+ if (typeof id !== "number")
+ this.alertOnce("drawObjId id is "+typeof id);
+
+ return this.drawObj(this.getObj(id), this.getObj(subsceneid), context);
+ };
+
+ /**
+ * Draw an object in a subscene
+ * @param { object } obj - object to draw
+ * @param { object } subscene
+ * @param { array } context
+ */
+ rglwidgetClass.prototype.drawObj = function(obj, subscene, context) {
+ switch(obj.type) {
+ case "abclines":
+ case "surface":
+ return this.drawSimple(obj, subscene, context);
+ case "points":
+ case "lines":
+ case "triangles":
+ case "quads":
+ case "text":
+ return this.drawMarginal(obj, subscene, context);
+ case "linestrip":
+ return this.drawLinestrip(obj, subscene, context);
+ case "planes":
+ return this.drawPlanes(obj, subscene, context);
+ case "spheres":
+ return this.drawSpheres(obj, subscene, context);
+ case "clipplanes":
+ return this.drawClipplanes(obj);
+ case "sprites":
+ return this.drawSprites(obj, subscene, context);
+ case "light":
+ return [];
+ case "bboxdeco":
+ return this.drawBBox(obj, subscene, context);
+ }
+
+ console.error("drawObj for type = "+obj.type);
+ };
+
+ /**
+ * Draw the background for a subscene
+ * @param { number } id - id of background object
+ * @param { number } subsceneid - id of subscene
+ */
+ rglwidgetClass.prototype.drawBackground = function(id, subsceneid, context) {
+ var gl = this.gl || this.initGL(),
+ obj = this.getObj(id),
+ subscene,
+ bg, i, savepr, saveinvpr, savemv, savenorm, m, bbox, result = [],
+ savedm = gl.getParameter(gl.DEPTH_WRITEMASK),
+ savedt = gl.isEnabled(gl.DEPTH_TEST),
+ saveblend = gl.isEnabled(gl.BLEND);
+
+ if (!obj.initialized)
+ this.initObj(obj);
+
+ if (obj.colors.length) {
+ bg = obj.colors[0];
+ gl.depthMask(true);
+ gl.clear(gl.DEPTH_BUFFER_BIT);
+ gl.clearColor(bg[0], bg[1], bg[2], bg[3]);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ this.fogColor = bg;
+ } else {
+ this.fogColor = [0,0,0,0];
+ obj.colors = [[0,0,0,0]];
+ }
+
+ this.fogType = obj.fogtype;
+ this.fogScale = obj.fogscale;
+ gl.disable(gl.BLEND);
+ gl.disable(gl.DEPTH_TEST);
+ gl.depthMask(false);
+ if (typeof obj.quad !== "undefined") {
+ savepr = this.prMatrix;
+ saveinvpr = this.invPrMatrix;
+ savemv = this.mvMatrix;
+ this.prMatrix = new CanvasMatrix4();
+ this.invPrMatrix = new CanvasMatrix4();
+ this.mvMatrix = new CanvasMatrix4();
+ for (i=0; i < obj.quad.length; i++)
+ result = result.concat(this.drawObjId(obj.quad[i], subsceneid));
+ this.prMatrix = savepr;
+ this.invPrMatrix = saveinvpr;
+ this.mvMatrix = savemv;
+
+ } else if (obj.sphere) {
+ subscene = this.getObj(subsceneid);
+ savemv = this.mvMatrix;
+ savenorm = this.normMatrix;
+ bbox = subscene.par3d.bbox;
+ var center = [(bbox[0] + bbox[1])/2,
+ (bbox[2] + bbox[3])/2,
+ (bbox[4] + bbox[5])/2, 1],
+ scale = subscene.par3d.scale,
+ ranges = [bbox[1] - bbox[0],
+ bbox[3] - bbox[2],
+ bbox[5] - bbox[4]],
+ avgscale = rglwidgetClass.vlen(ranges)/Math.sqrt(3),
+ aspect = [ranges[0]*scale[0]/avgscale,
+ ranges[1]*scale[1]/avgscale,
+ ranges[2]*scale[2]/avgscale],
+ maxaspect = Math.max(aspect[0], aspect[1], aspect[2]),
+ zoom = subscene.par3d.zoom;
+ m = new CanvasMatrix4();
+ m.rotate(90, 1, 0, 0);
+ m.scale(zoom*2.0*maxaspect*ranges[0]/aspect[0],
+ zoom*2.0*maxaspect*ranges[1]/aspect[1],
+ zoom*2.0*maxaspect*ranges[2]/aspect[2]);
+ m.translate(center[0], center[1], center[2]);
+ m.multRight(savemv);
+ center = rglwidgetClass.multVM(center, savemv);
+ m.translate(-center[0], -center[1], -center[2]);
+ m.scale(1, 1, 0.25/zoom);
+ m.translate(center[0], center[1], center[2]);
+ this.mvMatrix = m;
+ this.initShapeFromObj(this.sphere, obj);
+ this.sphere.onecolor = obj.colors.length > 1 ? obj.colors[1] : obj.colors[0];
+
+ this.normMatrix = new CanvasMatrix4();
+
+ this.setnormMatrix2();
+ this.setprmvMatrix();
+
+ result = result.concat(this.drawSimple(this.sphere, subscene, context));
+ this.mvMatrix = savemv;
+ this.normMatrix = savenorm;
+ }
+ gl.depthMask(savedm);
+ if (savedt)
+ gl.enable(gl.DEPTH_TEST);
+ if (saveblend)
+ gl.enable(gl.BLEND);
+ return result;
+ };
+
+ /**
+ * Draw a subscene
+ * @param { number } subsceneid - id of subscene
+ * @param { array } context
+ */
+ rglwidgetClass.prototype.drawSubscene = function(subsceneid, context) {
+ var sub = this.getObj(subsceneid),
+ objects = this.scene.objects,
+ clipids = sub.clipplanes,
+ subids = sub.objects,
+ subscene_has_faces = false,
+ subscene_needs_sorting = false,
+ flags, i, obj, result = [];
+
+ if (sub.par3d.skipRedraw)
+ return result;
+
+ if (this.opaquePass) {
+ for (i=0; i < subids.length; i++) {
+ obj = objects[subids[i]];
+ flags = obj.flags;
+ if (typeof flags !== "undefined") {
+ subscene_has_faces = subscene_has_faces ||
+ (rglwidgetClass.isSet(flags, rglwidgetClass.f_is_lit) &&
+ !rglwidgetClass.isSet(flags, rglwidgetClass.f_fixed_quads));
+ obj.is_transparent = obj.someHidden ||
+ rglwidgetClass.isSet(flags, rglwidgetClass.f_is_transparent);
+ subscene_needs_sorting = subscene_needs_sorting ||
+ obj.is_transparent ||
+ rglwidgetClass.isSet(flags, rglwidgetClass.f_depth_sort);
+ }
+ }
+ }
+
+ this.setViewport(subsceneid);
+
+ this.setprMatrix(subsceneid);
+ this.setInvPrMatrix();
+ this.setmvMatrix(subsceneid);
+ this.setnormMatrix2();
+ this.setprmvMatrix();
+ this.invMatrix = new CanvasMatrix4(this.mvMatrix);
+ this.invMatrix.invert();
+
+ if (this.opaquePass) {
+ context = context.slice();
+ context.push(subsceneid);
+
+ this.doBlending(false);
+ this.subsceneid = subsceneid;
+ if (typeof this.sphere !== "undefined") // reset this.sphere.fastpieces; it will be recreated if needed
+ this.sphere.fastpieces = undefined;
+ if (typeof sub.backgroundId !== "undefined")
+ result = result.concat(this.drawBackground(sub.backgroundId, subsceneid, context));
+ }
+
+ if (subids.length) {
+
+ if (clipids.length > 0) {
+ for (i = 0; i < clipids.length; i++)
+ this.drawObjId(clipids[i], subsceneid);
+ }
+
+ subids = sub.opaque.concat(sub.transparent);
+ if (this.opaquePass) {
+ for (i = 0; i < subids.length; i++)
+ result = result.concat(this.drawObjId(subids[i], subsceneid, context));
+ subids = sub.subscenes;
+ for (i = 0; i < subids.length; i++)
+ result = result.concat(this.drawSubscene(subids[i], context));
+ }
+ }
+ return result;
+ };
+
+ /**
+ * Set the context for drawing transparently
+ * @param { array } context
+ */
+ rglwidgetClass.prototype.setContext = function(context) {
+ var result = [], objid, obj, type;
+ context = context.slice();
+ context.reverse();
+ while (context.length > 0) {
+ objid = context.pop();
+ obj = this.getObj(objid);
+ type = obj.type;
+ switch (type) {
+ case "subscene":
+ this.drawSubscene(objid, false);
+ break;
+ case "sprites":
+ result = result.concat(context.pop());
+ break;
+ case "spheres":
+ // this.initSphereFromObj(obj); // FIXME: not needed?
+ break;
+ case "bboxdeco":
+ result = result.concat(context.pop());
+ break;
+ default:
+ console.error("bad type '", type, "' in setContext");
+ }
+ }
+ return result;
+ };
+
+ /**
+ * Draw the transparent pieces of a scene
+ * @param {object} pieces
+ */
+ rglwidgetClass.prototype.drawPieces = function(pieces) {
+ var i, prevcontext = [], context;
+ for (i = 0; i < pieces.length; i++) {
+ context = pieces[i].context.slice();
+ if (context !== prevcontext) {
+ prevcontext = context.slice();
+ context = this.setContext(context);
+ this.doBlending(true, pieces[i].objid);
+ }
+ this.drawObjId(pieces[i].objid, this.subsceneid,
+ pieces[i]);
+ }
+ };
+
+ /**
+ * Draw the whole scene
+ */
+ rglwidgetClass.prototype.drawScene = function() {
+ var wasDrawing = this.startDrawing(),
+ pieces;
+ if (!wasDrawing) {
+ if (this.select.state !== "inactive")
+ this.selectionChanged();
+
+ this.doStartScene();
+ this.opaquePass = true;
+ pieces = this.drawSubscene(this.scene.rootSubscene, []);
+ this.opaquePass = false;
+ pieces = this.sortPieces(pieces);
+ pieces = this.mergePieces(pieces);
+ this.drawPieces(pieces);
+ }
+ this.stopDrawing(wasDrawing);
+ };
diff --git a/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/init.src.js b/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/init.src.js
new file mode 100644
index 00000000..48b313b6
--- /dev/null
+++ b/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/init.src.js
@@ -0,0 +1,1315 @@
+ /**
+ * Methods related to initialization
+ * @name ___METHODS_FOR_INITIALIZATION___
+ * @memberof rglwidgetClass
+ * @kind function
+ * @instance
+ */
+
+ /**
+ * Initial test for WebGL
+ */
+ rglwidgetClass.prototype.initGL0 = function() {
+ if (!window.WebGLRenderingContext){
+ this.alertOnce("Your browser does not support WebGL. See http://get.webgl.org");
+ return;
+ }
+ };
+
+ /**
+ * Initialize WebGL
+ * @returns { Object } the WebGL context
+ */
+ rglwidgetClass.prototype.initGL = function() {
+ var self = this, success = false;
+ if (this.gl) {
+ if (!this.drawing && this.gl.isContextLost())
+ this.restartCanvas();
+ else
+ return this.gl;
+ }
+ // if (!this.isInBrowserViewport()) return; Return what??? At this point we know this.gl is null.
+ this.canvas.addEventListener("webglcontextrestored",
+ this.onContextRestored, false);
+ this.canvas.addEventListener("webglcontextlost",
+ this.onContextLost, false);
+ this.gl = this.canvas.getContext("webgl", this.webGLoptions) ||
+ this.canvas.getContext("experimental-webgl", this.webGLoptions);
+ success = !!(this.gl && this.gl instanceof WebGLRenderingContext);
+ if (!success)
+ this.alertOnce("Your browser does not support WebGL. See http://get.webgl.org");
+ this.index_uint = this.gl.getExtension("OES_element_index_uint");
+ var save = this.startDrawing();
+ Object.keys(this.scene.objects).forEach(function(key){
+ self.initObjId(parseInt(key, 10));
+ });
+ this.stopDrawing(save);
+ return this.gl;
+ };
+
+ /**
+ * Resize the display to match element
+ * @param { Object } el - DOM element to match
+ */
+ rglwidgetClass.prototype.resize = function(el) {
+ this.canvas.width = el.width;
+ this.canvas.height = el.height;
+ };
+
+ /**
+ * Initialize the sphere object
+ */
+ rglwidgetClass.prototype.initSphere = function(sections, segments) {
+ var v = [], phi = [], theta = [], it = [], centers = [],
+ i, j, k, ind, mod1, pole, result = {};
+
+ for (i = 0; i < sections - 1; i++) {
+ phi.push((i + 1)/sections - 0.5);
+ }
+
+ for (j = 0; j < segments; j++) {
+ theta.push(2*j/segments);
+ for (i = 0; i < sections - 1; i++) {
+ /* These are [x,y,z,s,t]: */
+ v.push([Math.sin(Math.PI*theta[j]) * Math.cos(Math.PI*phi[i]),
+ Math.sin(Math.PI*phi[i]),
+ Math.cos(Math.PI*theta[j]) * Math.cos(Math.PI*phi[i]),
+ theta[j]/2,
+ phi[i] + 0.5]);
+ }
+ }
+ pole = v.length;
+ v.push([0, -1, 0, 0, 0]);
+ v.push([0, 1, 0, 0, 1]);
+ result.values = new Float32Array(rglwidgetClass.flatten(v));
+ result.vertexCount = v.length;
+
+ mod1 = segments*(sections - 1);
+ for (j = 0; j < segments; j++) {
+ for (i = 0; i < sections - 2; i++) {
+ ind = i + (sections - 1)*j;
+ it.push([ind % mod1,
+ (ind + sections - 1) % mod1,
+ (ind + sections) % mod1]);
+ it.push([ind % mod1,
+ (ind + sections) % mod1,
+ (ind + 1) % mod1]);
+ }
+ it.push([pole,
+ ((j + 1)*(sections - 1)) % mod1,
+ ((j + 1)*(sections - 1) - sections + 1) % mod1]);
+ it.push([pole + 1,
+ ((j + 1)*(sections - 1) - 1) % mod1,
+ ((j + 1)*(sections - 1) + sections - 2) % mod1]);
+ }
+ result.it = new Uint16Array(rglwidgetClass.flatten(it));
+
+ for (i = 0; i < it.length; i++) {
+ centers.push([0,0,0]);
+ for (j = 0; j < 3; j++) { // x,y,z
+ for (k = 0; k < 3; k++) {// vertices
+ centers[i][j] += v[it[i][k]][j]/3;
+ }
+ }
+ }
+ result.centers = centers;
+
+ result.vOffsets = {vofs:0, cofs:-1, nofs:0, radofs:-1, oofs:-1,
+ tofs:3, nextofs:-1, pointofs:-1, stride:5};
+
+ result.f = [];
+ result.indices = {};
+
+ result.colorCount = 1;
+ result.type = "sphere";
+ this.sphere = result;
+ this.initShapeGL(this.sphere);
+ };
+
+ /**
+ * Initialize the cube object
+ */
+ rglwidgetClass.prototype.initCube = function() {
+ var v = [[0, 0, 0], [1, 0, 0],
+ [0, 1, 0], [1, 1, 0],
+ [0, 0, 1], [1, 0, 1],
+ [0, 1, 1], [1, 1, 1]],
+ ib = [[0, 2, 3, 1],
+ [2, 6, 7, 3],
+ [1, 3, 7, 5],
+ [0, 4, 6, 2],
+ [0, 1, 5, 4],
+ [4, 5, 7, 6]],
+ centers = [], i, j, k,
+ i0, i1, i2,
+ normal, result = {};
+
+ for (i = 0; i < ib.length; i++) {
+ centers.push([0,0,0]);
+ for (j = 0; j < 3; j++) { // x,y,z
+ for (k = 0; k < 4; k++) {// vertices
+ centers[i][j] += v[ib[i][k]][j]/4;
+ }
+ }
+ }
+ result.centers = centers;
+ result.values = new Float32Array(6*4*3*2);
+ result.vertexCount = 24;
+ result.vertices = new Array(24);
+ result.normals = new Array(24);
+ for (i=0; i < 6; i++) {
+ for (j=0; j < 4; j++) {
+ i0 = ib[i][j];
+ result.vertices[4*i + j] = v[i0];
+ i1 = ib[i][(j + 1) % 4];
+ i2 = ib[i][(j + 2) % 4];
+ if (j === 0)
+ normal = rglwidgetClass.normalize(rglwidgetClass.xprod(rglwidgetClass.vdiff(v[i1], v[i0]),
+ rglwidgetClass.vdiff(v[i2], v[i0])));
+ result.normals[4*i + j] = normal;
+ for (k=0; k < 3; k++) {
+ result.values[i*24 + j*6 + k] = v[i0][k];
+ result.values[i*24 + j*6 + 3 + k] = normal[k];
+ }
+ }
+ for (j=0; j<4; j++)
+ ib[i][j] = 4*i + j;
+ }
+ result.ib = new Uint16Array(rglwidgetClass.flatten(ib));
+
+ result.vOffsets = {vofs:0, cofs:-1, nofs:3, radofs:-1, oofs:-1,
+ tofs:-1, nextofs:-1, pointofs:-1, stride:6};
+
+ result.f = [];
+ result.indices = {};
+
+ result.colorCount = 1;
+ result.type = "quads";
+ this.cube = result;
+ this.initShapeGL(this.cube);
+ };
+
+
+ /**
+ * Do the gl part of initializing the sphere and cube
+ */
+ rglwidgetClass.prototype.initShapeGL = function(shape) {
+ var gl = this.gl || this.initGL();
+ if (gl.isContextLost()) return;
+ shape.buf = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, shape.buf);
+ gl.bufferData(gl.ARRAY_BUFFER, shape.values, gl.STATIC_DRAW);
+ shape.ibuf = [gl.createBuffer(), gl.createBuffer()];
+ return;
+ };
+
+ /* Initialize common sphere object from spheres object
+ */
+ rglwidgetClass.prototype.initShapeFromObj = function(shape, obj) {
+ var i, pass, f, mode, self = this,
+ /* This function selects things that would be
+ the back, ignoring perspective -- this is what
+ we want for the bounding box decoration. */
+ is_back = function(i) {
+ var normal = [].concat(shape.normals[i]),
+ pt = shape.vertices[i];
+ normal.push(-rglwidgetClass.dotprod(normal, pt));
+ normal = rglwidgetClass.multVM(normal, self.normMatrix);
+ return normal[2] < 0 || (normal[2] === 0 && normal[0] < 0);
+ };
+ shape.ofsLoc = obj.ofsLoc;
+ shape.texLoc = obj.texLoc;
+ shape.texture = obj.texture;
+ shape.sampler = obj.sampler;
+ shape.uFogMode = obj.uFogMode;
+ shape.uFogColor = obj.uFogColor;
+ shape.uFogParms = obj.uFogParms;
+ shape.userAttribLocations = obj.userAttribLocations;
+ shape.userUniformLocations = obj.userUniformLocations;
+ shape.normLoc = obj.normLoc;
+ shape.invPrMatLoc = obj.invPrMatLoc;
+ shape.clipLoc = obj.clipLoc;
+ shape.nextLoc = obj.nextLoc;
+ shape.pointLoc = obj.pointLoc;
+ shape.aspectLoc = obj.aspectLoc;
+ shape.lwdLoc = obj.lwdLoc;
+ shape.prog = obj.prog;
+ shape.material = obj.material;
+ shape.flags = obj.flags;
+ shape.defFlags = obj.defFlags;
+ shape.someHidden = obj.someHidden;
+ shape.fastTransparency = obj.fastTransparency;
+ shape.nlights = obj.nlights;
+ shape.emission = obj.emission;
+ shape.emissionLoc = obj.emissionLoc;
+ shape.shininess = obj.shininess;
+ shape.shininessLoc = obj.shininessLoc;
+ shape.ambient = obj.ambient;
+ shape.ambientLoc = obj.ambientLoc;
+ shape.specular = obj.specular;
+ shape.specularLoc = obj.specularLoc;
+ shape.diffuse = obj.diffuse;
+ shape.diffuseLoc = obj.diffuseLoc;
+ shape.lightDir = obj.lightDir;
+ shape.lightDirLoc = obj.lightDirLoc;
+ shape.viewpoint = obj.viewpoint;
+ shape.viewpointLoc = obj.viewpointLoc;
+ shape.finite = obj.finite;
+ shape.finiteLoc = obj.finiteLoc;
+ shape.prMatLoc = obj.prMatLoc;
+ shape.mvMatLoc = obj.mvMatLoc;
+ shape.normMatLoc = obj.normMatLoc;
+ shape.frontLoc = obj.frontLoc;
+ shape.index_uint = false;
+ shape.is_transparent = obj.is_transparent;
+ shape.ignoreExtent = obj.ignoreExtent;
+ if (shape.passes !== obj.passes ||
+ JSON.stringify( shape.pmode) !== JSON.stringify(obj.pmode)) {
+ shape.passes = obj.passes;
+ shape.pmode = obj.pmode;
+ for (pass = 0; pass < obj.passes; pass++) {
+ mode = shape.pmode[pass];
+ if (typeof shape.indices[mode] === "undefined") {
+ f = [];
+ switch (mode) {
+ case "culled": break;
+ case "points":
+ f.length = shape.vertexCount;
+ for (i=0; i < f.length; i++)
+ f[i] = i;
+ break;
+ case "lines":
+ if (typeof shape.it !== "undefined") {
+ f.length = 2* shape.it.length;
+ for (i=0; i < shape.it.length/3; i++) {
+ f[6*i] = shape.it[3*i];
+ f[6*i + 1] = shape.it[3*i + 1];
+ f[6*i + 2] = shape.it[3*i + 1];
+ f[6*i + 3] = shape.it[3*i + 2];
+ f[6*i + 4] = shape.it[3*i + 2];
+ f[6*i + 5] = shape.it[3*i];
+ }
+ } else {
+ f.length = 2*shape.ib.length;
+ for (i=0; i < shape.ib.length/4; i++) {
+ f[8*i] = shape.ib[4*i];
+ f[8*i + 1] = shape.ib[4*i + 1];
+ f[8*i + 2] = shape.ib[4*i + 1];
+ f[8*i + 3] = shape.ib[4*i + 2];
+ f[8*i + 4] = shape.ib[4*i + 2];
+ f[8*i + 5] = shape.ib[4*i + 3];
+ f[8*i + 6] = shape.ib[4*i + 3];
+ f[8*i + 7] = shape.ib[4*i];
+ }
+ }
+ break;
+ case "filled":
+ if (typeof shape.it !== "undefined")
+ f = shape.it;
+ else if (typeof shape.ib !== "undefined") {
+ f.length = 1.5*shape.ib.length;
+ for (i=0; i < shape.ib.length/4; i++) {
+ f[6*i] = shape.ib[4*i];
+ f[6*i+1] = shape.ib[4*i + 1];
+ f[6*i+2] = shape.ib[4*i + 2];
+ f[6*i+3] = shape.ib[4*i];
+ f[6*i+4] = shape.ib[4*i + 2];
+ f[6*i+5] = shape.ib[4*i + 3];
+ }
+ }
+ break;
+ }
+ shape.indices[mode] = new Uint16Array(f);
+ }
+ }
+ }
+ for (pass = 0; pass < obj.passes; pass++) {
+ mode = shape.pmode[pass];
+ shape.f[pass] = shape.indices[mode];
+ if (typeof obj.draw_front !== "undefined" &&
+ !obj.draw_front) {
+ shape.f[pass] = shape.f[pass].filter(is_back);
+ }
+ }
+ // console.log("Names in shapes not in shape:"+JSON.stringify(rglwidgetClass.keydiff(obj, shape)));
+ shape.initialized = true;
+ };
+
+ /**
+ * Initialize a subscene
+ * @param { number } id - id of subscene.
+ */
+ rglwidgetClass.prototype.initSubscene = function(id) {
+ var sub = this.getObj(id),
+ i, obj;
+
+ if (sub.type !== "subscene")
+ return;
+
+ sub.par3d.userMatrix = this.toCanvasMatrix4(sub.par3d.userMatrix);
+ sub.par3d.userProjection = this.toCanvasMatrix4(sub.par3d.userProjection);
+ sub.par3d.userProjection.transpose();
+ sub.par3d.listeners = [].concat(sub.par3d.listeners);
+ sub.backgroundId = undefined;
+ sub.subscenes = [];
+ sub.clipplanes = [];
+ sub.transparent = [];
+ sub.opaque = [];
+ sub.lights = [];
+ sub.needsBegin = true;
+ if (typeof sub.objects !== "undefined")
+ sub.objects = [].concat(sub.objects); /* make sure it's an array */
+ for (i=0; i < sub.objects.length; i++) {
+ obj = this.getObj(sub.objects[i]);
+ if (typeof obj === "undefined") {
+ sub.objects.splice(i, 1);
+ i--;
+ } else if (obj.type === "background")
+ sub.backgroundId = obj.id;
+ else
+ sub[this.whichList(obj.id)].push(obj.id);
+ }
+ };
+
+ rglwidgetClass.prototype.initBBox = function(obj) {
+ if (!this.cube)
+ this.initCube();
+ obj.cube = {id: obj.id + 0.1,
+ type: "quads",
+ flags: obj.flags,
+ material: obj.material,
+ colors: [obj.colors[0]],
+ vertices: this.cube.vertices,
+ normals: this.cube.normals,
+ draw_front: obj.draw_front,
+ initialized: false
+ };
+ if (this.getMaterial(obj.cube, "front") !==
+ this.getMaterial(obj.cube, "back"))
+ /* jshint bitwise: false */
+ obj.cube.flags |= rglwidgetClass.f_is_twosided;
+ /* jshint bitwise: true */
+ this.scene.objects[obj.cube.id] = obj.cube;
+ obj.ticks = {id: obj.id + 0.2,
+ type: "lines",
+ flags: rglwidgetClass.f_has_fog,
+ material: obj.material,
+ colors: (obj.colors.length > 1 ? [obj.colors[1]] : [obj.colors[0]]),
+ axes: obj.axes,
+ initialized: false
+ };
+ this.scene.objects[obj.ticks.id] = obj.ticks;
+ obj.labels = {id: obj.id + 0.3,
+ type: "text",
+ flags: rglwidgetClass.f_has_fog +
+ rglwidgetClass.f_fixed_size +
+ rglwidgetClass.f_fixed_quads,
+ material: {lit: false},
+ colors: (obj.colors.length > 1 ? [obj.colors[1]] : [obj.colors[0]]),
+ cex: [[1]],
+ family: [["sans"]],
+ font: [[1]],
+ adj: [[0.5, 0.5, 0.5]],
+ ignoreExtent: true,
+ initialized: false
+ };
+ this.scene.objects[obj.labels.id] = obj.labels;
+ obj.initialized = true;
+ };
+
+ rglwidgetClass.prototype.initBackground = function(obj) {
+ var material, fl = obj.defFlags;
+ if (typeof obj.ids !== "undefined")
+ obj.quad = rglwidgetClass.flatten([].concat(obj.ids));
+ else if (obj.sphere) {
+ fl.has_normals = true;
+ fl.needs_vnormal = true;
+ obj.defFlags = fl;
+ material = obj.material;
+ material.front = "culled";
+ obj.vertices = [[0,0,0]];
+ obj.texcoords = [[0,0]];
+ }
+ };
+
+ /**
+ * Initialize object for display
+ * @param { number } id - id of object to initialize
+ */
+ rglwidgetClass.prototype.initObjId = function(id) {
+ if (typeof id !== "number") {
+ this.alertOnce("initObj id is "+typeof id);
+ }
+ return this.initObj(this.getObj(id));
+ };
+
+ /**
+ * Initialize object for display
+ * @param { Object } obj - object to initialize
+ */
+ rglwidgetClass.prototype.initObj = function(obj) {
+ var type = obj.type,
+ flags = obj.flags,
+ normals = obj.normals,
+ round_points = (typeof obj.material === "undefined") ?
+ false : this.getMaterial(obj, "point_antialias"),
+ has_indices = typeof obj.indices !== "undefined",
+ has_spheres = type === "spheres" ||
+ (type === "background" && obj.sphere),
+ sprites_3d = rglwidgetClass.isSet(flags, rglwidgetClass.f_sprites_3d),
+ depth_sort = rglwidgetClass.isSet(flags, rglwidgetClass.f_depth_sort),
+ gl = this.gl || this.initGL(),
+ fl, polygon_offset,
+ texinfo, drawtype, nclipplanes, f, nrows, oldrows,
+ i,j,v,v1,v2, mat, uri, matobj, pass, pmode,
+ dim, nx, nz, nrow, shaders;
+
+ obj.initialized = true;
+
+ obj.someHidden = false; // used in selection
+
+ this.expandBufferedFields(obj);
+
+ if (type === "subscene")
+ return;
+
+ obj.defFlags = fl = rglwidgetClass.getDefFlags(flags, type, normals, round_points);
+
+ obj.is_transparent = fl.is_transparent;
+
+ if (type === "bboxdeco")
+ return this.initBBox(obj);
+
+ if (has_spheres && typeof this.sphere === "undefined")
+ this.initSphere(16, 16);
+
+ if (type === "light") {
+ obj.ambient = new Float32Array(obj.colors[0].slice(0,3));
+ obj.diffuse = new Float32Array(obj.colors[1].slice(0,3));
+ obj.specular = new Float32Array(obj.colors[2].slice(0,3));
+ obj.lightDir = new Float32Array(obj.vertices[0]);
+ return;
+ }
+
+ if (type === "clipplanes") {
+ obj.vClipplane = rglwidgetClass.flatten(rglwidgetClass.cbind(obj.normals, obj.offsets));
+ return;
+ }
+
+ if (type === "background") {
+ this.initBackground(obj);
+ if (!obj.sphere)
+ return;
+ }
+
+ polygon_offset = this.getMaterial(obj, "polygon_offset");
+ if (polygon_offset[0] !== 0 || polygon_offset[1] !== 0)
+ obj.polygon_offset = polygon_offset;
+
+ if (fl.is_transparent) {
+ depth_sort = ["triangles", "quads", "surface",
+ "spheres", "sprites", "text",
+ "planes"].indexOf(type) >= 0;
+ }
+
+ if (fl.is_brush)
+ this.initSelection(obj.id);
+
+ if (typeof obj.vertices === "undefined")
+ obj.vertices = [];
+
+ v = obj.vertices;
+ if (has_indices)
+ obj.vertexCount = obj.indices.length;
+ else
+ obj.vertexCount = v.length;
+
+ if (!obj.vertexCount) return;
+
+ if (fl.is_twosided && !fl.has_normals && type !== "background") {
+ if (typeof obj.userAttributes === "undefined")
+ obj.userAttributes = {};
+ v1 = Array(v.length);
+ v2 = Array(v.length);
+ if (obj.type === "triangles" || obj.type === "quads") {
+ if (obj.type === "triangles")
+ nrow = 3;
+ else
+ nrow = 4;
+ for (i=0; i= 0) {
+ key = this.scene.crosstalk.key[j];
+ options = this.scene.crosstalk.options[j];
+ colors = colors.slice(0);
+ for (i = 0; i < v.length; i++)
+ colors[i] = obj.colors[i % obj.colors.length].slice(0);
+ if ( (selection = this.scene.crosstalk.selection) &&
+ (selection.length || !options.selectedIgnoreNone) )
+ for (i = 0; i < v.length; i++) {
+ if (!selection.includes(key[i])) {
+ if (options.deselectedColor)
+ colors[i] = options.deselectedColor.slice(0);
+ colors[i][3] = colors[i][3]*options.deselectedFade; /* default: mostly transparent if not selected */
+ } else if (options.selectedColor)
+ colors[i] = options.selectedColor.slice(0);
+ }
+ if ( (filter = this.scene.crosstalk.filter) )
+ for (i = 0; i < v.length; i++)
+ if (!filter.includes(key[i])) {
+ if (options.filteredColor)
+ colors[i] = options.filteredColor.slice(0);
+ colors[i][3] = colors[i][3]*options.filteredFade; /* default: completely hidden if filtered */
+ }
+ }
+
+ nc = obj.colorCount = colors.length;
+ if (nc > 1) {
+ cofs = stride;
+ stride = stride + 4;
+ v = rglwidgetClass.cbind(v, colors);
+ } else {
+ cofs = -1;
+ obj.onecolor = rglwidgetClass.flatten(colors);
+ }
+
+ if (fl.has_normals && !has_spheres) {
+ nofs = stride;
+ stride = stride + 3;
+ v = rglwidgetClass.cbind(v, typeof obj.pnormals !== "undefined" ? obj.pnormals : obj.normals);
+ } else
+ nofs = -1;
+
+ if (typeof obj.radii !== "undefined") {
+ radofs = stride;
+ stride = stride + 1;
+ // FIXME: always concat the radii?
+ if (obj.radii.length === v.length) {
+ v = rglwidgetClass.cbind(v, obj.radii);
+ } else if (obj.radii.length === 1) {
+ v = v.map(function(row) { return row.concat(obj.radii[0]);});
+ }
+ } else
+ radofs = -1;
+
+ // Add default indices
+ if (has_indices) {
+ f = Array(obj.indices.length);
+ for (i = 0; i < f.length; i++)
+ f[i] = obj.indices[i] - 1;
+ } else {
+ f = Array(v.length);
+ for (i = 0; i < v.length; i++)
+ f[i] = i;
+ }
+ obj.f = [f,f];
+
+ if (type === "sprites" && !sprites_3d) {
+ tofs = stride;
+ stride += 2;
+ oofs = stride;
+ stride += 3;
+ vnew = new Array(4*v.length);
+ fnew = new Array(4*v.length);
+ alias = new Array(v.length);
+ var rescale = fl.fixed_size ? 72 : 1,
+ size = obj.radii, s = rescale*size[0]/2;
+ last = v.length;
+ f = obj.f[0];
+ obj.adj = rglwidgetClass.flatten(obj.adj);
+ if (typeof obj.pos !== "undefined") {
+ obj.pos = rglwidgetClass.flatten(obj.pos);
+ offset = obj.adj[0];
+ } else
+ offset = 0;
+ for (i=0; i < v.length; i++) {
+ adj = this.getAdj(obj, i, offset);
+ if (size.length > 1)
+ s = rescale*size[i]/2;
+ adj[0] = 2*s*(adj[0] - 0.5);
+ adj[1] = 2*s*(adj[1] - 0.5);
+ adj[2] = 2*s*(adj[2] - 0.5);
+ vnew[i] = v[i].concat([0,0]).concat([-s-adj[0],
+ -s-adj[1],
+ -adj[2]]);
+ fnew[4*i] = f[i];
+ vnew[last]= v[i].concat([1,0]).concat([s-adj[0],
+ -s-adj[1],
+ -adj[2]]);
+ fnew[4*i+1] = last++;
+ vnew[last]= v[i].concat([1,1]).concat([s-adj[0],
+ s-adj[1],
+ -adj[2]]);
+ fnew[4*i+2] = last++;
+ vnew[last]= v[i].concat([0,1]).concat([-s-adj[0],
+ s-adj[1],
+ -adj[2]]);
+ fnew[4*i+3] = last++;
+ alias[i] = [last-3, last-2, last-1];
+ }
+ v = vnew;
+ obj.vertexCount = v.length;
+ obj.f = [fnew, fnew];
+ } else if (type === "text") {
+ tofs = stride;
+ stride += 2;
+ oofs = stride;
+ stride += 3;
+ vnew = new Array(4*v.length);
+ f = obj.f[0];
+ fnew = new Array(4*f.length);
+ alias = new Array(v.length);
+ last = v.length;
+ adj = rglwidgetClass.flatten(obj.adj);
+ if (typeof obj.pos !== "undefined") {
+ obj.pos = rglwidgetClass.flatten(obj.pos);
+ offset = adj[0];
+ } else
+ offset = 0;
+ for (i=0; i < v.length; i++) {
+ adj = this.getAdj(obj, i, offset, obj.texts[i]);
+ vnew[i] = v[i].concat([0,-0.5]).concat(adj);
+ fnew[4*i] = f[i];
+ vnew[last] = v[i].concat([1,-0.5]).concat(adj);
+ fnew[4*i+1] = last++;
+ vnew[last] = v[i].concat([1, 1.5]).concat(adj);
+ fnew[4*i+2] = last++;
+ vnew[last] = v[i].concat([0, 1.5]).concat(adj);
+ fnew[4*i+3] = last++;
+ alias[i] = [last-3, last-2, last-1];
+ for (j=0; j < 4; j++) {
+ v1 = vnew[fnew[4*i+j]];
+ v1[oofs] = 2*(v1[tofs]-v1[oofs])*texinfo.widths[i];
+ v1[oofs+1] = 2*(v1[tofs+1]-v1[oofs+1])*texinfo.textHeights[i];
+ v1[oofs+2] = 2*(0.5-v1[oofs+2])*texinfo.textHeights[i]/1000.0;
+ v1[tofs] = (texinfo.offsetsx[i] + v1[tofs]*texinfo.widths[i])/texinfo.canvasX;
+ v1[tofs+1] = 1.0-(texinfo.offsetsy[i] -
+ v1[tofs+1]*texinfo.textHeights[i])/texinfo.canvasY;
+ vnew[fnew[4*i+j]] = v1;
+ }
+ }
+ v = vnew;
+ obj.vertexCount = v.length;
+ obj.f = [fnew, fnew];
+ } else if (typeof obj.texcoords !== "undefined") {
+ tofs = stride;
+ stride += 2;
+ oofs = -1;
+ v = rglwidgetClass.cbind(v, obj.texcoords);
+ } else {
+ tofs = -1;
+ oofs = -1;
+ }
+
+ obj.alias = alias;
+
+ if (typeof obj.userAttributes !== "undefined") {
+ obj.userAttribOffsets = {};
+ obj.userAttribLocations = {};
+ obj.userAttribSizes = {};
+ for (attr in obj.userAttributes) {
+ obj.userAttribLocations[attr] = gl.getAttribLocation(obj.prog, attr);
+ if (obj.userAttribLocations[attr] >= 0) { // Attribute may not have been used
+ obj.userAttribOffsets[attr] = stride;
+ v = rglwidgetClass.cbind(v, obj.userAttributes[attr]);
+ stride = v[0].length;
+ obj.userAttribSizes[attr] = stride - obj.userAttribOffsets[attr];
+ } else
+ console.warn("attribute '"+attr+"' not found in object "+obj.id+".");
+ }
+ }
+
+ if (typeof obj.userUniforms !== "undefined" ||
+ typeof obj.userTextures !== "undefined") {
+ obj.userUniformLocations = {};
+ for (attr in obj.userUniforms) {
+ obj.userUniformLocations[attr] = gl.getUniformLocation(obj.prog, attr);
+ if (obj.userUniformLocations[attr] === null)
+ console.warn("uniform '"+attr+"' not found in object "+obj.id+".");
+ }
+ for (attr in obj.userTextures) {
+ var texture = obj.userTextures[attr];
+ texture.texture = gl.createTexture();
+ // This is a trick from https://stackoverflow.com/a/19748905/2554330 to avoid warnings
+ gl.bindTexture(gl.TEXTURE_2D, texture.texture);
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE,
+ new Uint8Array([255,255,255, 255])); // white
+ texture.sampler = gl.getUniformLocation(obj.prog, attr);
+ if (texture.sampler === null)
+ console.warn("sampler '"+attr+"' not found in object "+obj.id+".");
+ uri = texture.uri;
+ this.loadImageToTexture(uri, texture.texture);
+ }
+ }
+
+ if (sprites_3d) {
+ obj.userMatrix = new CanvasMatrix4();
+ obj.userMatrix.load(rglwidgetClass.flatten(obj.usermatrix));
+ obj.objects = rglwidgetClass.flatten([].concat(obj.ids));
+ fl.is_lit = false;
+ obj.adj = rglwidgetClass.flatten(obj.adj);
+ if (typeof obj.pos !== "undefined") {
+ obj.pos = rglwidgetClass.flatten(obj.pos);
+ obj.offset = obj.adj[0];
+ } else
+ obj.offset = 0;
+
+ for (i=0; i < obj.objects.length; i++)
+ this.initObjId(obj.objects[i]);
+ }
+
+ nclipplanes = this.countClipplanes();
+ if (nclipplanes && !sprites_3d) {
+ obj.clipLoc = gl.getUniformLocation(obj.prog,"vClipplane");
+ }
+
+ if (fl.is_lit) {
+ obj.emissionLoc = gl.getUniformLocation(obj.prog, "emission");
+ obj.emission = new Float32Array(this.stringToRgb(this.getMaterial(obj, "emission")));
+ obj.shininessLoc = gl.getUniformLocation(obj.prog, "shininess");
+ obj.shininess = this.getMaterial(obj, "shininess");
+ obj.nlights = this.countLights();
+ if (obj.nlights > 0) {
+ obj.ambient = new Float32Array(this.stringToRgb(this.getMaterial(obj, "ambient")));
+ obj.specular = new Float32Array(this.stringToRgb(this.getMaterial(obj, "specular")));
+ obj.ambientLoc = gl.getUniformLocation(obj.prog, "ambient");
+ obj.specularLoc = gl.getUniformLocation(obj.prog, "specular");
+ obj.diffuseLoc = gl.getUniformLocation(obj.prog, "diffuse" );
+ obj.lightDirLoc = gl.getUniformLocation(obj.prog, "lightDir");
+ obj.viewpointLoc = gl.getUniformLocation(obj.prog, "viewpoint");
+ obj.finiteLoc = gl.getUniformLocation(obj.prog, "finite" );
+ }
+ }
+
+ obj.passes = fl.is_twosided + 1;
+ obj.pmode = new Array(obj.passes);
+ for (pass = 0; pass < obj.passes; pass++) {
+ if (type === "triangles" || type === "quads" || type === "surface" || has_spheres)
+ pmode = this.getMaterial(obj, (pass === 0) ? "front" : "back");
+ else pmode = "filled";
+ obj.pmode[pass] = pmode;
+ }
+ if (!has_spheres) {
+ obj.f.length = obj.passes;
+ for (pass = 0; pass < obj.passes; pass++) {
+ f = fnew = obj.f[pass];
+ pmode = obj.pmode[pass];
+ if (pmode === "culled")
+ f = [];
+ else if (pmode === "points") {
+ // stay with default
+ } else if ((type === "quads" || type === "text" ||
+ type === "sprites") && !sprites_3d) {
+ nrows = Math.floor(obj.vertexCount/4);
+ if (pmode === "filled") {
+ fnew = Array(6*nrows);
+ for (i=0; i < nrows; i++) {
+ fnew[6*i] = f[4*i];
+ fnew[6*i+1] = f[4*i + 1];
+ fnew[6*i+2] = f[4*i + 2];
+ fnew[6*i+3] = f[4*i];
+ fnew[6*i+4] = f[4*i + 2];
+ fnew[6*i+5] = f[4*i + 3];
+ }
+ } else {
+ fnew = Array(8*nrows);
+ for (i=0; i < nrows; i++) {
+ fnew[8*i] = f[4*i];
+ fnew[8*i+1] = f[4*i + 1];
+ fnew[8*i+2] = f[4*i + 1];
+ fnew[8*i+3] = f[4*i + 2];
+ fnew[8*i+4] = f[4*i + 2];
+ fnew[8*i+5] = f[4*i + 3];
+ fnew[8*i+6] = f[4*i + 3];
+ fnew[8*i+7] = f[4*i];
+ }
+ }
+ } else if (type === "triangles") {
+ nrows = Math.floor(obj.vertexCount/3);
+ if (pmode === "filled") {
+ fnew = Array(3*nrows);
+ for (i=0; i < fnew.length; i++) {
+ fnew[i] = f[i];
+ }
+ } else if (pmode === "lines") {
+ fnew = Array(6*nrows);
+ for (i=0; i < nrows; i++) {
+ fnew[6*i] = f[3*i];
+ fnew[6*i + 1] = f[3*i + 1];
+ fnew[6*i + 2] = f[3*i + 1];
+ fnew[6*i + 3] = f[3*i + 2];
+ fnew[6*i + 4] = f[3*i + 2];
+ fnew[6*i + 5] = f[3*i];
+ }
+ }
+ } else if (has_spheres) {
+ // default
+ } else if (type === "surface") {
+ dim = obj.dim[0];
+ nx = dim[0];
+ nz = dim[1];
+ if (pmode === "filled") {
+ fnew = [];
+ for (j=0; j 65535) {
+ if (this.index_uint) {
+ obj.f[pass] = new Uint32Array(obj.f[pass]);
+ obj.index_uint = true;
+ } else
+ this.alertOnce("Object has "+obj.vertexCount+" vertices, not supported in this browser.");
+ } else {
+ obj.f[pass] = new Uint16Array(obj.f[pass]);
+ obj.index_uint = false;
+ }
+ }
+
+ if (stride !== v[0].length) {
+ this.alertOnce("problem in stride calculation");
+ }
+
+ obj.vOffsets = {vofs:0, cofs:cofs, nofs:nofs, radofs:radofs, oofs:oofs, tofs:tofs,
+ nextofs:nextofs, pointofs:pointofs, stride:stride};
+
+ obj.values = new Float32Array(rglwidgetClass.flatten(v));
+
+ if (!has_spheres && !sprites_3d) {
+ obj.buf = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, obj.buf);
+ gl.bufferData(gl.ARRAY_BUFFER, obj.values, gl.STATIC_DRAW); //
+ obj.ibuf = Array(obj.passes);
+ obj.ibuf[0] = gl.createBuffer();
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, obj.ibuf[0]);
+ gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, obj.f[0], gl[drawtype]);
+ if (fl.is_twosided) {
+ obj.ibuf[1] = gl.createBuffer();
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, obj.ibuf[1]);
+ gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, obj.f[1], gl[drawtype]);
+ }
+ }
+
+ if (!sprites_3d) {
+ obj.mvMatLoc = gl.getUniformLocation(obj.prog, "mvMatrix");
+ obj.prMatLoc = gl.getUniformLocation(obj.prog, "prMatrix");
+
+ if (fl.fixed_size) {
+ obj.textScaleLoc = gl.getUniformLocation(obj.prog, "textScale");
+ }
+ }
+
+ if (fl.needs_vnormal) {
+ obj.normLoc = gl.getAttribLocation(obj.prog, "aNorm");
+ obj.normMatLoc = gl.getUniformLocation(obj.prog, "normMatrix");
+ }
+
+ if (fl.is_twosided) {
+ obj.frontLoc = gl.getUniformLocation(obj.prog, "front");
+ if (fl.has_normals)
+ obj.invPrMatLoc = gl.getUniformLocation(obj.prog, "invPrMatrix");
+ }
+ };
+
+ /**
+ * Initialize the DOM object
+ * @param { Object } el - the DOM object
+ * @param { Object } x - the scene data sent by JSON from R
+ */
+ rglwidgetClass.prototype.initialize = function(el, x) {
+ this.textureCanvas = document.createElement("canvas");
+ this.textureCanvas.style.display = "block";
+ this.scene = x;
+ this.normMatrix = new CanvasMatrix4();
+ this.invPrMatrix = new CanvasMatrix4();
+ this.saveMat = {};
+ this.distance = null;
+ this.posLoc = 0;
+ this.colLoc = 1;
+ if (el) {
+ el.rglinstance = this;
+ this.el = el;
+ this.webGLoptions = el.rglinstance.scene.webGLoptions;
+ this.initCanvas();
+ }
+ if (typeof Shiny !== "undefined") {
+ var self = this;
+ Shiny.addCustomMessageHandler("shinyGetPar3d",
+ function(message) {
+ var i, param,
+ subscene = self.getObj(message.subscene),
+ parameters = [].concat(message.parameters),
+ result = {tag: message.tag, subscene: message.subscene};
+ if (typeof subscene !== "undefined") {
+ for (i = 0; i < parameters.length; i++) {
+ param = parameters[i];
+ result[param] = subscene.par3d[param];
+ }
+ } else {
+ console.log("subscene "+message.subscene+" undefined.");
+ }
+ Shiny.setInputValue("par3d:shinyPar3d", result, {priority: "event"});
+ });
+
+ Shiny.addCustomMessageHandler("shinySetPar3d",
+ function(message) {
+ var param = message.parameter,
+ subscene = self.getObj(message.subscene);
+ if (typeof subscene !== "undefined") {
+ subscene.par3d[param] = message.value;
+ subscene.initialized = false;
+ self.drawScene();
+ } else {
+ console.log("subscene "+message.subscene+" undefined.");
+ }
+ });
+
+ Shiny.addCustomMessageHandler("resetBrush",
+ function(message) {
+ if (message === self.scene.selectionInput) {
+ self.clearBrush(null);
+ self.recordSelection(0);
+ }
+ });
+ }
+ };
+
+ /**
+ * Restart the WebGL canvas
+ */
+ rglwidgetClass.prototype.restartCanvas = function() {
+ var newcanvas = document.createElement("canvas"),
+ self = this;
+ newcanvas.width = this.el.width;
+ newcanvas.height = this.el.height;
+ newcanvas.addEventListener("webglcontextrestored",
+ this.onContextRestored, false);
+ newcanvas.addEventListener("webglcontextlost",
+ this.onContextLost, false);
+ while (this.el.firstChild) {
+ this.el.removeChild(this.el.firstChild);
+ }
+ this.el.appendChild(newcanvas);
+ this.canvas = newcanvas;
+ if (this.scene.javascript) {
+ /* jshint evil:true */
+ Function('"use strict";' + this.scene.javascript)();
+ /* jshint evil:false */
+ }
+ this.setMouseHandlers();
+ if (this.gl)
+ Object.keys(this.scene.objects).forEach(function(key){
+ self.getObj(parseInt(key, 10)).texture = undefined;
+ });
+ this.gl = null;
+ };
+
+ /**
+ * Initialize the WebGL canvas
+ */
+ rglwidgetClass.prototype.initCanvas = function() {
+ this.restartCanvas();
+ var objs = this.scene.objects,
+ self = this;
+
+ /* These hold context specific data. In Shiny, they
+ need to be deleted. Elsewhere, they don't exist
+ and these are no-ops. */
+
+ delete this.cube;
+ delete this.sphere;
+
+ Object.keys(objs).forEach(function(key){
+ self.initSubscene(parseInt(key, 10));
+ });
+
+ this.onContextRestored = function() {
+ self.initGL();
+ self.drawScene();
+ };
+
+ this.onContextLost = function(event) {
+ if (!self.drawing)
+ this.gl = null;
+ event.preventDefault();
+ };
+
+ this.initGL0();
+ this.lazyLoadScene = function() {
+ if (typeof self.slide === "undefined")
+ self.slide = self.getSlide();
+ if (self.isInBrowserViewport()) {
+ if (!self.gl || self.gl.isContextLost())
+ self.initGL();
+ self.drawScene();
+ }
+ };
+ window.addEventListener("DOMContentLoaded", this.lazyLoadScene, false);
+ window.addEventListener("load", this.lazyLoadScene, false);
+ window.addEventListener("resize", this.lazyLoadScene, false);
+ window.addEventListener("scroll", this.lazyLoadScene, false);
+ this.slide = this.getSlide();
+ if (this.slide) {
+ if (typeof this.slide.rgl === "undefined")
+ this.slide.rgl = [this];
+ else
+ this.slide.rgl.push(this);
+ if (this.scene.context.rmarkdown)
+ if (this.scene.context.rmarkdown === "ioslides_presentation") {
+ this.slide.setAttribute("slideenter", "this.rgl.forEach(function(scene) { scene.lazyLoadScene.call(window);})");
+ } else if (this.scene.context.rmarkdown === "slidy_presentation") {
+ // This method would also work in ioslides, but it gets triggered
+ // something like 5 times per slide for every slide change, so
+ // you'd need a quicker function than lazyLoadScene.
+ var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver,
+ observer = new MutationObserver(function(mutations) {
+ mutations.forEach(function() {
+ self.slide.rgl.forEach(function(scene) { scene.lazyLoadScene.call(window); });});});
+ observer.observe(this.slide, { attributes: true, attributeFilter:["class"] });
+ }
+ }
+ };
diff --git a/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/mouse.src.js b/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/mouse.src.js
new file mode 100644
index 00000000..ca992f83
--- /dev/null
+++ b/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/mouse.src.js
@@ -0,0 +1,569 @@
+ /**
+ * Methods related to mouse handling
+ * @name ___METHODS_FOR_MOUSE_HANDLING___
+ * @memberof rglwidgetClass
+ * @kind function
+ * @instance
+ */
+
+ rglwidgetClass.prototype.getCursor = function(mode) {
+ switch(mode) {
+ case "none":
+ return "none";
+ case "trackball":
+ case "xAxis":
+ case "yAxis":
+ case "zAxis":
+ case "polar":
+ return "grab";
+ case "selecting":
+ return "crosshair";
+ case "fov":
+ case "zoom":
+ return "zoom-in";
+ case "user":
+ return "default";
+ }
+ return "dragging";
+ };
+
+ /**
+ * Set mouse mode for a subscene
+ * @param { string } mode - name of mode
+ * @param { number } button - button number (0 to 4)
+ * @param { number } subscene - subscene id number
+ * @param { number } stayActive - if truthy, don't clear brush
+ */
+ rglwidgetClass.prototype.setMouseMode = function(mode, button, subscene, stayActive) {
+ var sub = this.getObj(subscene),
+ which = ["none", "left", "right", "middle", "wheel"][button];
+ if (!stayActive && sub.par3d.mouseMode[which] === "selecting")
+ this.clearBrush(null);
+ sub.par3d.mouseMode[which] = mode;
+ if (button === 1 || (button === 0 && mode !== "none"))
+ this.canvas.style.cursor = this.getCursor(mode);
+ if (button === 0 && mode !== "none")
+ sub.needsBegin = mode;
+ };
+
+ /**
+ * Compute mouse coordinates relative to current canvas
+ * @returns { Object }
+ * @param { Object } event - event object from mouse click
+ */
+ rglwidgetClass.prototype.relMouseCoords = function(event) {
+ var rect = this.canvas.getBoundingClientRect();
+ return {x:event.clientX-rect.left, y:event.clientY-rect.top};
+ };
+
+ /**
+ * Send mouse selection to Shiny
+ */
+ rglwidgetClass.prototype.recordSelection = function(subid) {
+ var result = {};
+ if (typeof this.select !== "undefined" &&
+ typeof this.select.state !== "undefined" &&
+ this.select.state !== "inactive") {
+ result = { subscene: subid,
+ state: this.select.state,
+ region: this.select.region
+ };
+ this.setmvMatrix(subid);
+ result.model = this.mvMatrix;
+ this.setprMatrix(subid);
+ result.proj = this.prMatrix;
+ this.getViewport(subid);
+ result.view = this.vp;
+ } else
+ result.state = "inactive";
+ Shiny.setInputValue(this.scene.selectionInput + ":shinyMouse3d", result);
+ };
+
+ /**
+ * Set mouse handlers for the scene
+ */
+ rglwidgetClass.prototype.setMouseHandlers = function() {
+ var self = this, activeSubscene, handler,
+ handlers = {}, drag = 0;
+
+ handlers.rotBase = 0;
+
+ self.screenToVector = function(x, y) {
+ var viewport = self.getObj(activeSubscene).par3d.viewport,
+ width = viewport.width*self.canvas.width,
+ height = viewport.height*self.canvas.height,
+ radius = Math.max(width, height)/2.0,
+ cx = width/2.0,
+ cy = height/2.0,
+ px = (x-cx)/radius,
+ py = (y-cy)/radius,
+ plen = Math.sqrt(px*px+py*py);
+ if (plen > 1.e-6) {
+ px = px/plen;
+ py = py/plen;
+ }
+ var angle = (Math.SQRT2 - plen)/Math.SQRT2*Math.PI/2,
+ z = Math.sin(angle),
+ zlen = Math.sqrt(1.0 - z*z);
+ px = px * zlen;
+ py = py * zlen;
+ return [px, py, z];
+ };
+
+ handlers.trackballdown = function(x,y) {
+ var activeSub = self.getObj(activeSubscene),
+ activeModel = self.getObj(self.useid(activeSub.id, "model")),
+ i, l = activeModel.par3d.listeners;
+ handlers.rotBase = self.screenToVector(x, y);
+ self.saveMat = [];
+ for (i = 0; i < l.length; i++) {
+ activeSub = self.getObj(l[i]);
+ activeSub.saveMat = new CanvasMatrix4(activeSub.par3d.userMatrix);
+ }
+ self.canvas.style.cursor = "grabbing";
+ };
+
+ handlers.trackballmove = function(x,y) {
+ var rotCurrent = self.screenToVector(x,y),
+ rotBase = handlers.rotBase,
+ dot = rotBase[0]*rotCurrent[0] +
+ rotBase[1]*rotCurrent[1] +
+ rotBase[2]*rotCurrent[2],
+ angle = Math.acos( dot/rglwidgetClass.vlen(rotBase)/rglwidgetClass.vlen(rotCurrent) )*180.0/Math.PI,
+ axis = rglwidgetClass.xprod(rotBase, rotCurrent),
+ objects = self.scene.objects,
+ activeSub = self.getObj(activeSubscene),
+ activeModel = self.getObj(self.useid(activeSub.id, "model")),
+ l = activeModel.par3d.listeners,
+ i;
+ if (angle === 0.0)
+ return;
+ for (i = 0; i < l.length; i++) {
+ activeSub = self.getObj(l[i]);
+ activeSub.par3d.userMatrix.load(objects[l[i]].saveMat);
+ activeSub.par3d.userMatrix.rotate(angle, axis[0], axis[1], axis[2]);
+ }
+ self.drawScene();
+ };
+ handlers.trackballend = 0;
+
+ self.clamp = function(x, lo, hi) {
+ return Math.max(lo, Math.min(x, hi));
+ };
+
+ self.screenToPolar = function(x,y) {
+ var viewport = self.getObj(activeSubscene).par3d.viewport,
+ width = viewport.width*self.canvas.width,
+ height = viewport.height*self.canvas.height,
+ r = Math.min(width, height)/2,
+ dx = self.clamp(x - width/2, -r, r),
+ dy = self.clamp(y - height/2, -r, r);
+ return [Math.asin(dx/r), Math.asin(-dy/r)];
+ };
+
+ handlers.polardown = function(x,y) {
+ var activeSub = self.getObj(activeSubscene),
+ activeModel = self.getObj(self.useid(activeSub.id, "model")),
+ i, l = activeModel.par3d.listeners;
+ handlers.dragBase = self.screenToPolar(x, y);
+ self.saveMat = [];
+ for (i = 0; i < l.length; i++) {
+ activeSub = self.getObj(l[i]);
+ activeSub.saveMat = new CanvasMatrix4(activeSub.par3d.userMatrix);
+ activeSub.camBase = [-Math.atan2(activeSub.saveMat.m13, activeSub.saveMat.m11),
+ Math.atan2(activeSub.saveMat.m32, activeSub.saveMat.m22)];
+ }
+ self.canvas.style.cursor = "grabbing";
+ };
+
+ handlers.polarmove = function(x,y) {
+ var dragCurrent = self.screenToPolar(x,y),
+ activeSub = self.getObj(activeSubscene),
+ activeModel = self.getObj(self.useid(activeSub.id, "model")),
+ objects = self.scene.objects,
+ l = activeModel.par3d.listeners,
+ i, j, changepos = [];
+ for (i = 0; i < l.length; i++) {
+ activeSub = self.getObj(l[i]);
+ for (j=0; j<2; j++)
+ changepos[j] = -(dragCurrent[j] - handlers.dragBase[j]);
+ activeSub.par3d.userMatrix.makeIdentity();
+ activeSub.par3d.userMatrix.rotate(changepos[0]*180/Math.PI, 0,-1,0);
+ activeSub.par3d.userMatrix.multRight(objects[l[i]].saveMat);
+ activeSub.par3d.userMatrix.rotate(changepos[1]*180/Math.PI, -1,0,0);
+ }
+ self.drawScene();
+ };
+ handlers.polarend = 0;
+
+ handlers.axisdown = function(x) {
+ handlers.rotBase = self.screenToVector(x, self.canvas.height/2);
+ var activeSub = self.getObj(activeSubscene),
+ activeModel = self.getObj(self.useid(activeSub.id, "model")),
+ i, l = activeModel.par3d.listeners;
+ for (i = 0; i < l.length; i++) {
+ activeSub = self.getObj(l[i]);
+ activeSub.saveMat = new CanvasMatrix4(activeSub.par3d.userMatrix);
+ }
+ self.canvas.style.cursor = "grabbing";
+ };
+
+ handlers.axismove = function(x) {
+ var rotCurrent = self.screenToVector(x, self.canvas.height/2),
+ rotBase = handlers.rotBase,
+ angle = (rotCurrent[0] - rotBase[0])*180/Math.PI,
+ rotMat = new CanvasMatrix4();
+ rotMat.rotate(angle, handlers.axis[0], handlers.axis[1], handlers.axis[2]);
+ var activeSub = self.getObj(activeSubscene),
+ activeModel = self.getObj(self.useid(activeSub.id, "model")),
+ i, l = activeModel.par3d.listeners;
+ for (i = 0; i < l.length; i++) {
+ activeSub = self.getObj(l[i]);
+ activeSub.par3d.userMatrix.load(activeSub.saveMat);
+ activeSub.par3d.userMatrix.multLeft(rotMat);
+ }
+ self.drawScene();
+ };
+ handlers.axisend = 0;
+
+ handlers.y0zoom = 0;
+ handlers.zoomdown = function(x, y) {
+ var activeSub = self.getObj(activeSubscene),
+ activeProjection = self.getObj(self.useid(activeSub.id, "projection")),
+ i, l = activeProjection.par3d.listeners;
+ handlers.y0zoom = y;
+ for (i = 0; i < l.length; i++) {
+ activeSub = self.getObj(l[i]);
+ activeSub.zoom0 = Math.log(activeSub.par3d.zoom);
+ }
+ self.canvas.style.cursor = "zoom-in";
+ };
+ handlers.zoommove = function(x, y) {
+ var activeSub = self.getObj(activeSubscene),
+ activeProjection = self.getObj(self.useid(activeSub.id, "projection")),
+ i, l = activeProjection.par3d.listeners;
+ for (i = 0; i < l.length; i++) {
+ activeSub = self.getObj(l[i]);
+ activeSub.par3d.zoom = Math.exp(activeSub.zoom0 + (y-handlers.y0zoom)/self.canvas.height);
+ }
+ self.drawScene();
+ };
+ handlers.zoomend = 0;
+
+ handlers.y0fov = 0;
+ handlers.fovdown = function(x, y) {
+ handlers.y0fov = y;
+ var activeSub = self.getObj(activeSubscene),
+ activeProjection = self.getObj(self.useid(activeSub.id, "projection")),
+ i, l = activeProjection.par3d.listeners;
+ for (i = 0; i < l.length; i++) {
+ activeSub = self.getObj(l[i]);
+ activeSub.fov0 = activeSub.par3d.FOV;
+ }
+ self.canvas.style.cursor = "zoom-in";
+ };
+ handlers.fovmove = function(x, y) {
+ var activeSub = self.getObj(activeSubscene),
+ activeProjection = self.getObj(self.useid(activeSub.id, "projection")),
+ i, l = activeProjection.par3d.listeners;
+ for (i = 0; i < l.length; i++) {
+ activeSub = self.getObj(l[i]);
+ activeSub.par3d.FOV = Math.max(1, Math.min(179, activeSub.fov0 +
+ 180*(y-handlers.y0fov)/self.canvas.height));
+ }
+ self.drawScene();
+ };
+ handlers.fovend = 0;
+
+ handlers.selectingdown = function(x, y) {
+ var viewport = self.getObj(activeSubscene).par3d.viewport,
+ width = viewport.width*self.canvas.width,
+ height = viewport.height*self.canvas.height,
+ p = {x: 2.0*x/width - 1.0, y: 2.0*y/height - 1.0};
+ self.select.region = {p1: p, p2: p};
+ if (self.select.subscene && self.select.subscene !== activeSubscene)
+ self.delFromSubscene(self.scene.brushId, self.select.subscene);
+ self.select.subscene = activeSubscene;
+ self.addToSubscene(self.scene.brushId, activeSubscene);
+ self.select.state = "changing";
+ if (typeof self.scene.brushId !== "undefined")
+ self.getObj(self.scene.brushId).initialized = false;
+ if (typeof self.scene.selectionInput !== "undefined")
+ self.recordSelection(activeSubscene);
+ self.drawScene();
+ self.canvas.style.cursor = "crosshair";
+ };
+
+ handlers.selectingmove = function(x, y) {
+ var viewport = self.getObj(activeSubscene).par3d.viewport,
+ width = viewport.width*self.canvas.width,
+ height = viewport.height*self.canvas.height;
+ if (self.select.state === "inactive")
+ return;
+ self.select.region.p2 = {x: 2.0*x/width - 1.0, y: 2.0*y/height - 1.0};
+ if (typeof self.scene.brushId !== "undefined")
+ self.getObj(self.scene.brushId).initialized = false;
+ if (typeof self.scene.selectionInput !== "undefined")
+ self.recordSelection(activeSubscene);
+ self.drawScene();
+ };
+
+ handlers.selectingend = 0;
+ /* jshint evil:true */
+ handlers.userdown = function(x, y) {
+ var sub = self.getObj(activeSubscene),
+ code = sub.callbacks[drag].begin;
+ if (code) {
+ var fn = Function('"use strict";return (' + code + ')')();
+ fn.call(self, x, y);
+ }
+ };
+
+ handlers.usermove = function(x, y) {
+ var sub = self.getObj(activeSubscene),
+ code = sub.callbacks[drag].update;
+ if (code) {
+ var fn = Function('"use strict";return (' + code + ')')();
+ fn.call(self, x, y);
+ }
+ };
+
+ handlers.userend = function() {
+ var sub = self.getObj(activeSubscene),
+ code = sub.callbacks[drag].end;
+ if (code) {
+ var fn = Function('"use strict";return (' + code + ')')();
+ fn.call(self);
+ }
+ };
+
+ self.canvas.onpointerdown = function ( ev ){
+ // pointers and mice differ in capture rules;
+ // act like a mouse.
+ if (ev.target.hasPointerCapture(ev.pointerId))
+ ev.target.releasePointerCapture(ev.pointerId);
+
+ if (!ev.which) // Use w3c defns in preference to MS
+ switch (ev.button) {
+ case 0: ev.which = 1; break;
+ case 1:
+ case 4: ev.which = 2; break;
+ case 2: ev.which = 3;
+ }
+ drag = ["none", "left", "middle", "right", "wheel"][ev.which];
+ var coords = self.relMouseCoords(ev);
+ coords.y = self.canvas.height-coords.y;
+ activeSubscene = self.whichSubscene(coords);
+ var sub = self.getObj(activeSubscene), f;
+ handler = sub.par3d.mouseMode[drag];
+ switch (handler) {
+ case "xAxis":
+ handler = "axis";
+ handlers.axis = [1.0, 0.0, 0.0];
+ break;
+ case "yAxis":
+ handler = "axis";
+ handlers.axis = [0.0, 1.0, 0.0];
+ break;
+ case "zAxis":
+ handler = "axis";
+ handlers.axis = [0.0, 0.0, 1.0];
+ break;
+ }
+ f = handlers[handler + "down"];
+ if (f) {
+ coords = self.translateCoords(activeSubscene, coords);
+ f.call(self, coords.x, coords.y);
+ ev.preventDefault();
+ } else
+ console.warn("Mouse handler '" + handler + "' is not implemented.");
+
+ };
+
+ self.canvas.onpointerup = function ( ev ){
+ if ( !drag ) return;
+ var f = handlers[handler + "end"];
+ if (f) {
+ f.call(self);
+ ev.preventDefault();
+ }
+ drag = 0;
+ handlers.onpointermove( ev );
+ };
+
+ self.canvas.onpointerout = self.canvas.onpointerup;
+
+ handlers.onpointermove = function ( ev ) {
+ var coords = self.relMouseCoords(ev), sub, f;
+ coords.y = self.canvas.height - coords.y;
+ if (ev.buttons === 0) {
+ activeSubscene = self.whichSubscene(coords);
+ drag = "none";
+ sub = self.getObj(activeSubscene);
+ handler = sub.par3d.mouseMode.none;
+ if (handler !== "none") {
+ if (sub.needsBegin) {
+ f = handlers[handler + "down"];
+ if (f) {
+ coords = self.translateCoords(activeSubscene, coords);
+ f.call(self, coords.x, coords.y);
+ }
+ sub.needsBegin = 0;
+ }
+ self.canvas.style.cursor = self.getCursor(sub.par3d.mouseMode.none);
+ } else {
+ self.canvas.style.cursor = self.getCursor(sub.par3d.mouseMode.left);
+ return;
+ }
+ }
+ f = handlers[handler + "move"];
+ if (f) {
+ coords = self.translateCoords(activeSubscene, coords);
+ f.call(self, coords.x, coords.y);
+ }
+ };
+
+
+ self.canvas.onpointerenter = function() {
+ self.canvas.addEventListener("pointermove", handlers.onpointermove);
+ };
+
+ self.canvas.onpointerleave = function() {
+ self.canvas.removeEventListener("pointermove",
+ handlers.onpointermove);
+ };
+
+ handlers.setZoom = function(ds) {
+ var i;
+ if (typeof activeSubscene === "undefined")
+ activeSubscene = self.scene.rootSubscene;
+ var activeSub = self.getObj(activeSubscene),
+ activeProjection = self.getObj(self.useid(activeSub.id, "projection")),
+ l = activeProjection.par3d.listeners;
+
+ for (i = 0; i < l.length; i++) {
+ activeSub = self.getObj(l[i]);
+ activeSub.par3d.zoom *= ds;
+ }
+ self.drawScene();
+ };
+
+ handlers.pushwheel = function(ev) {
+ ev.deltaY = -ev.deltaY;
+ handlers.pullwheel(ev);
+ };
+
+ handlers.pullwheel = function(ev) {
+ var del = 1.05;
+ if (ev.shiftKey) del = 1.005;
+ var ds = ev.deltaY < 0 ? del : (1 / del);
+ handlers.setZoom(ds);
+ };
+
+ handlers.user2wheel = function(ev) {
+ var sub = self.getObj(activeSubscene),
+ code = sub.callbacks.wheel.rotate;
+ if (code) {
+ var fn = Function('"use strict";return (' + code + ')')();
+ /* jshint evil:false */
+ fn.call(self, ev.deltaY < 0 ? 1 : 2);
+ }
+ };
+
+ handlers.wheelHandler = function(ev) {
+ var coords = self.relMouseCoords(ev);
+ coords.y = self.canvas.height - coords.y;
+ activeSubscene = self.whichSubscene(coords);
+ var sub = self.getObj(activeSubscene), f,
+ handler = sub.par3d.mouseMode.wheel,
+ evlocal;
+
+ ev.deltaY = ev.deltaY || ev.detail || ev.deltaX || ev.wheelDelta;
+
+ switch(handler) {
+ case "none": break;
+ case "push":
+ case "pull":
+ case "user2":
+ f = handlers[handler + "wheel"];
+ if (f) {
+ evlocal = {};
+ evlocal.deltaY = ev.deltaY;
+ evlocal.shiftKey = ev.shiftKey;
+ evlocal.preventDefault = function() { ev.preventDefault(); };
+ f.call(self, evlocal);
+ }
+ break;
+ default:
+ evlocal = {};
+ evlocal.preventDefault = function() { ev.preventDefault(); };
+ evlocal.which = 4;
+ evlocal.clientX = self.canvas.width/2;
+ evlocal.clientY = self.canvas.height/2;
+ self.canvas.onpointerdown(evlocal);
+ evlocal.clientX += ev.deltaX;
+ evlocal.clientY += ev.deltaY;
+ handlers.onpointermove(evlocal);
+ self.canvas.onpointerup(evlocal);
+ }
+ ev.preventDefault();
+ };
+
+ handlers.get_finger_dist = function(ev) {
+ var diffX = ev.touches[0].clientX - ev.touches[1].clientX,
+ diffY = ev.touches[0].clientY - ev.touches[1].clientY;
+ return Math.sqrt(diffX * diffX + diffY * diffY);
+ };
+
+ handlers.touchstart = function(ev) {
+ var touch = ev.touches[0],
+ mouseEvent = new MouseEvent("pointerdown",
+ {
+ clientX: touch.clientX,
+ clientY: touch.clientY
+ });
+ ev.preventDefault();
+ if (ev.touches.length === 2) {
+ var coords = self.relMouseCoords(touch);
+ coords.y = self.canvas.height-coords.y;
+ activeSubscene = self.whichSubscene(coords);
+ handlers.finger_dist0 = handlers.get_finger_dist(ev);
+ handlers.zoomdown(coords.x, coords.y);
+ }
+ self.dispatchEvent(mouseEvent);
+ };
+
+ handlers.touchend = function(ev) {
+ var mouseEvent;
+ ev.preventDefault();
+ if (ev.touches.length === 1) {
+ mouseEvent = new MouseEvent("pointerup", {});
+ self.dispatchEvent(mouseEvent);
+ }
+ };
+
+ handlers.touchmove = function(ev) {
+ var touch = ev.touches[0],
+ mouseEvent;
+ ev.preventDefault();
+ if (ev.touches.length > 1) {
+ var coords = self.relMouseCoords(touch),
+ new_dist = handlers.get_finger_dist(ev);
+ coords.y = self.canvas.height*Math.log(handlers.finger_dist0/new_dist) + handlers.y0zoom;
+ handlers.zoommove(coords.x, coords.y);
+ } else {
+ mouseEvent = new MouseEvent("pointermove",
+ {
+ clientX: touch.clientX,
+ clientY: touch.clientY
+ });
+ self.dispatchEvent(mouseEvent);
+ }
+ };
+
+ self.canvas.addEventListener("DOMMouseScroll", handlers.wheelHandler, false);
+ self.canvas.addEventListener("mousewheel", handlers.wheelHandler, false);
+ self.canvas.addEventListener("touchstart", handlers.touchstart, {passive: false});
+ self.canvas.addEventListener("touchend", handlers.touchend, {passive: false});
+ self.canvas.addEventListener("touchmove", handlers.touchmove, {passive: false});
+ };
diff --git a/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/pieces.src.js b/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/pieces.src.js
new file mode 100644
index 00000000..2ac8c91d
--- /dev/null
+++ b/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/pieces.src.js
@@ -0,0 +1,135 @@
+/**
+ * Methods related to drawing transparent objects
+ * @name ___METHODS_FOR_TRANSPARENCY___
+ * @memberof rglwidgetClass
+ * @kind function
+ * @instance
+
+ * These functions order the centers of displayed objects so they
+ * can be drawn using the painters algorithm, necessary to support
+ * transparency.
+
+ * Note that objid is not obj.id when drawing spheres.
+ */
+
+/**
+ * Break objects into pieces
+ * @returns { array } Array of pieces
+ */
+ rglwidgetClass.prototype.getPieces = function(context, objid, subid, obj) {
+ var n = obj.centers.length,
+ depth,
+ result = new Array(n),
+ z, w, i;
+ context = context.slice();
+
+ for(i=0; i 0) {
+ var i,
+ thiscontext = pieces[0].context,
+ thisobjid = pieces[0].objid,
+ thissubid = pieces[0].subid,
+ indices = [];
+ for (i= 0; i < pieces.length; i++) {
+ if (pieces[i].context !== thiscontext ||
+ pieces[i].objid !== thisobjid ||
+ pieces[i].subid !== thissubid) {
+ result.push({context: thiscontext, objid: thisobjid,
+ subid: thissubid, indices: indices});
+ thiscontext = pieces[i].context;
+ thisobjid = pieces[i].objid;
+ thissubid = pieces[i].subid;
+ indices = [];
+ }
+ indices.push(pieces[i].index);
+ }
+ result.push({context: thiscontext, objid: thisobjid,
+ subid: thissubid,
+ indices: indices});
+ }
+ return result;
+ };
+
+ /**
+ * Sort pieces by depth
+ * @returns { array }
+ * @param { array } pieces - array of pieces
+ */
+ rglwidgetClass.prototype.sortPieces = function(pieces) {
+ var compare = function(i,j) {
+ var diff = j.depth - i.depth;
+ // We want to avoid context or obj changes,
+ // so sort on those next.
+ if (diff === 0) {
+ var c1 = j.context.slice(),
+ c2 = i.context.slice();
+ diff = c1.length - c2.length;
+ while (diff === 0 && c1.length > 0) {
+ diff = c1.pop() - c2.pop();
+ }
+ if (diff === 0)
+ diff = j.objid - i.objid;
+ if (diff === 0)
+ diff = j.subid - i.subid;
+ }
+ return diff;
+ }, result = [];
+ if (pieces.length)
+ result = pieces.sort(compare);
+ return result;
+ };
diff --git a/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/pretty.src.js b/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/pretty.src.js
new file mode 100644
index 00000000..5f9145aa
--- /dev/null
+++ b/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/pretty.src.js
@@ -0,0 +1,163 @@
+/**
+ * Pretty function from R
+ * @name ___PRETTY_FROM_R___
+ * @memberof rglwidgetClass
+ * @kind function
+ * @instance
+ */
+
+
+/* This file is translated from pretty.c, which was
+ taken from the R sources, r61744 of src/appl/pretty.c,
+ with minimal changes */
+
+/*
+ * R : A Computer Language for Statistical Data Analysis
+ * Copyright (C) 1995-2012 The R Core Team
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, a copy is available at
+ * http://www.r-project.org/Licenses/
+ */
+
+/**
+ * Construct pretty values to cover an interval
+ * @param { number } lo - lower end of interval
+ * @param { number } up - upper end of interval
+ * @param { number } ndiv - requested number of divisions
+ * @param { number } min_n - minimum divisions
+ * @param { number } shrink_sml - if too many cells, amount to shrink by
+ * @param { number } high_u_fact - bias in favour of larger units
+ * @param { number } eps_correction - correction to bounds
+ * @param { Boolean } return_bounds - whether to return bounds
+ * @description
+ * Pretty Intervals
+
+ * Constructs m "pretty" values which cover the given interval *lo <= *up
+ * m ~= *ndiv + 1 (i.e., ndiv := approximate number of INTERVALS)
+ *
+ * It is not quite clear what should happen for *lo = *up;
+ * S itself behaves quite funilly, then.
+ *
+ * In my opinion, a proper 'pretty' should always ensure
+ * *lo < *up, and hence *ndiv >=1 in the result.
+ * However, in S and here, we allow *lo == *up, and *ndiv = 0.
+ * Note however, that we are NOT COMPATIBLE to S. [Martin M.]
+ *
+ * NEW (0.63.2): ns, nu are double (==> no danger of integer overflow)
+ *
+ * We determine
+ * if the interval (up - lo) is ``small'' [<==> i_small == TRUE, below].
+ * For the ``i_small'' situation, there is a parameter shrink_sml,
+ * the factor by which the "scale" is shrunk. ~~~~~~~~~~
+ * It is advisable to set it to some (smaller) integer power of 2,
+ * since this enables exact floating point division.
+ */
+rglwidgetClass.prototype.R_pretty = function(
+ lo, up, ndiv, min_n, shrink_sml, high_u_fact,
+ eps_correction, return_bounds) {
+ /* From version 0.65 on, we had rounding_eps := 1e-5, before, r..eps = 0
+ * 1e-7 is consistent with seq.default() */
+ var rounding_eps = 1e-7, h = high_u_fact[0],
+ h5 = high_u_fact[1],
+ dx, cell, unit, base, U, ns, nu, k, i_small,
+ DBL_EPSILON = Number.EPSILON,
+ DBL_MIN = Number.MIN_VALUE,
+ DBL_MAX = Number.MAX_VALUE;
+
+ dx = up - lo;
+ /* cell := "scale" here */
+ if (dx === 0 && up === 0) { /* up == lo == 0 */
+ cell = 1;
+ i_small = true;
+ } else {
+ cell = Math.max(Math.abs(lo), Math.abs(up));
+ /* U = upper bound on cell/unit */
+ U = (1 + (h5 >= 1.5*h+0.5)) ? 1/(1+h) : 1.5/(1+h5);
+ /* added times 3, as several calculations here */
+ i_small = dx < cell * U * Math.max(1,ndiv) * DBL_EPSILON *3;
+ }
+
+ /*OLD: cell = FLT_EPSILON+ dx / *ndiv; FLT_EPSILON = 1.192e-07 */
+ if(i_small) {
+ if(cell > 10)
+ cell = 9 + cell/10;
+ cell *= shrink_sml;
+ if(min_n > 1) cell /= min_n;
+ } else {
+ cell = dx;
+ if(ndiv > 1) cell /= ndiv;
+ }
+
+ if(cell < 20*DBL_MIN) {
+ /* warning(_("Internal(pretty()): very small range.. corrected")); */
+ cell = 20*DBL_MIN;
+ } else if(cell * 10 > DBL_MAX) {
+ /* warning(_("Internal(pretty()): very large range.. corrected")); */
+ cell = 0.1*DBL_MAX;
+ }
+ base = Math.pow(10, Math.floor(Math.log10(cell))); /* base <= cell < 10*base */
+
+ /* unit : from { 1,2,5,10 } * base
+ * such that |u - cell| is small,
+ * favoring larger (if h > 1, else smaller) u values;
+ * favor '5' more than '2' if h5 > h (default h5 = .5 + 1.5 h) */
+ unit = base;
+ if((U = 2*base)-cell < h*(cell-unit)) { unit = U;
+ if((U = 5*base)-cell < h5*(cell-unit)) { unit = U;
+ if((U =10*base)-cell < h*(cell-unit)) unit = U; }}
+ /* Result: c := cell, u := unit, b := base
+ * c in [ 1, (2+ h) /(1+h) ] b ==> u= b
+ * c in ( (2+ h)/(1+h), (5+2h5)/(1+h5)] b ==> u= 2b
+ * c in ( (5+2h)/(1+h), (10+5h) /(1+h) ] b ==> u= 5b
+ * c in ((10+5h)/(1+h), 10 ) b ==> u=10b
+ *
+ * ===> 2/5 *(2+h)/(1+h) <= c/u <= (2+h)/(1+h) */
+
+ ns = Math.floor(lo/unit+rounding_eps);
+ nu = Math.ceil (up/unit-rounding_eps);
+
+ if(eps_correction && (eps_correction > 1 || !i_small)) {
+ if(lo !== 0.0) lo *= (1- DBL_EPSILON); else lo = -DBL_MIN;
+ if(up !== 0.0) up *= (1+ DBL_EPSILON); else up = +DBL_MIN;
+ }
+
+ while(ns*unit > lo + rounding_eps*unit) ns--;
+
+ while(nu*unit < up - rounding_eps*unit) nu++;
+
+ k = Math.floor(0.5 + nu - ns);
+ if(k < min_n) {
+ /* ensure that nu - ns == min_n */
+
+ k = min_n - k;
+ if(ns >= 0) {
+ nu += k/2;
+ ns -= k/2 + k%2;/* ==> nu-ns = old(nu-ns) + min_n -k = min_n */
+ } else {
+ ns -= k/2;
+ nu += k/2 + k%2;
+ }
+ ndiv = min_n;
+ } else {
+ ndiv = k;
+ }
+ if(return_bounds) { /* if()'s to ensure that result covers original range */
+ if(ns * unit < lo) lo = ns * unit;
+ if(nu * unit > up) up = nu * unit;
+ } else {
+ lo = ns;
+ up = nu;
+ }
+ return {lo:lo, up:up, ndiv:ndiv, unit:unit};
+};
diff --git a/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/projection.src.js b/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/projection.src.js
new file mode 100644
index 00000000..4fc59c79
--- /dev/null
+++ b/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/projection.src.js
@@ -0,0 +1,148 @@
+ /**
+ * Methods related to projections
+ * @name ___METHODS_FOR_PROJECTIONS___
+ * @memberof rglwidgetClass
+ * @kind function
+ * @instance
+ */
+
+ /**
+ * Get the viewport
+ */
+
+ rglwidgetClass.prototype.getViewport = function(id) {
+ var vp = this.getObj(id).par3d.viewport,
+ x = vp.x*this.canvas.width,
+ y = vp.y*this.canvas.height,
+ width = vp.width*this.canvas.width,
+ height = vp.height*this.canvas.height;
+ this.vp = {x:x, y:y, width:width, height:height};
+ };
+
+ /**
+ * Set the gl viewport and scissor test
+ * @param { number } id - id of subscene
+ */
+ rglwidgetClass.prototype.setViewport = function(id) {
+ var gl = this.gl || this.initGL();
+ this.getViewport(id);
+ gl.viewport(this.vp.x, this.vp.y, this.vp.width, this.vp.height);
+ gl.scissor(this.vp.x, this.vp.y, this.vp.width, this.vp.height);
+ gl.enable(gl.SCISSOR_TEST);
+ };
+
+ /**
+ * Set the projection matrix for a subscene
+ * @param { number } id - id of subscene
+ */
+ rglwidgetClass.prototype.setprMatrix = function(id) {
+ var subscene = this.getObj(id),
+ embedding = subscene.embeddings.projection;
+ if (embedding === "replace")
+ this.prMatrix.makeIdentity();
+ else
+ this.setprMatrix(subscene.parent);
+ if (embedding === "inherit")
+ return;
+ // This is based on the Frustum::enclose code from geom.cpp
+ var bbox = subscene.par3d.bbox,
+ scale = subscene.par3d.scale,
+ ranges = [(bbox[1]-bbox[0])*scale[0]/2,
+ (bbox[3]-bbox[2])*scale[1]/2,
+ (bbox[5]-bbox[4])*scale[2]/2],
+ radius = Math.sqrt(this.sumsq(ranges))*1.1; // A bit bigger to handle labels
+ if (radius <= 0) radius = 1;
+ var observer = subscene.par3d.observer,
+ distance = observer[2],
+ FOV = subscene.par3d.FOV, ortho = FOV === 0,
+ t = ortho ? 1 : Math.tan(FOV*Math.PI/360),
+ near = distance - radius,
+ far = distance + radius,
+ hlen,
+ aspect = this.vp.width/this.vp.height,
+ z = subscene.par3d.zoom,
+ userProjection = subscene.par3d.userProjection;
+ if (far < 0.0)
+ far = 1.0;
+ if (near < far/100.0)
+ near = far/100.0;
+ this.frustum = {near:near, far:far};
+ hlen = t*near;
+ if (ortho) {
+ if (aspect > 1)
+ this.prMatrix.ortho(-hlen*aspect*z, hlen*aspect*z,
+ -hlen*z, hlen*z, near, far);
+ else
+ this.prMatrix.ortho(-hlen*z, hlen*z,
+ -hlen*z/aspect, hlen*z/aspect,
+ near, far);
+ } else {
+ if (aspect > 1)
+ this.prMatrix.frustum(-hlen*aspect*z, hlen*aspect*z,
+ -hlen*z, hlen*z, near, far);
+ else
+ this.prMatrix.frustum(-hlen*z, hlen*z,
+ -hlen*z/aspect, hlen*z/aspect,
+ near, far);
+ }
+ this.prMatrix.multRight(userProjection);
+ };
+
+ /**
+ * Set the model-view matrix for a subscene
+ * @param { number } id - id of the subscene
+ */
+ rglwidgetClass.prototype.setmvMatrix = function(id) {
+ var observer = this.getObj(id).par3d.observer;
+ this.mvMatrix.makeIdentity();
+ this.setmodelMatrix(id);
+ this.mvMatrix.translate(-observer[0], -observer[1], -observer[2]);
+
+ };
+
+ /**
+ * Set the model matrix for a subscene
+ * @param { number } id - id of the subscene
+ */
+ rglwidgetClass.prototype.setmodelMatrix = function(id) {
+ var subscene = this.getObj(id),
+ embedding = subscene.embeddings.model;
+ if (embedding === "replace") {
+ var bbox = subscene.par3d.bbox,
+ center = [(bbox[0]+bbox[1])/2,
+ (bbox[2]+bbox[3])/2,
+ (bbox[4]+bbox[5])/2];
+ this.mvMatrix.translate(-center[0], -center[1], -center[2]);
+ }
+ if (embedding !== "inherit") {
+ var scale = subscene.par3d.scale;
+ this.mvMatrix.scale(scale[0], scale[1], scale[2]);
+ this.mvMatrix.multRight( subscene.par3d.userMatrix );
+ }
+ if (embedding !== "replace")
+ this.setmodelMatrix(subscene.parent);
+ };
+
+ /**
+ * Set the normals matrix for a subscene
+ * @param { number } subsceneid - id of the subscene
+ */
+ rglwidgetClass.prototype.setnormMatrix2 = function() {
+ this.normMatrix = new CanvasMatrix4(this.mvMatrix);
+ this.normMatrix.invert();
+ this.normMatrix.transpose();
+ };
+
+ /**
+ * Set the combined projection-model-view matrix
+ */
+ rglwidgetClass.prototype.setprmvMatrix = function() {
+ this.prmvMatrix = new CanvasMatrix4( this.mvMatrix );
+ this.prmvMatrix.multRight( this.prMatrix );
+ };
+
+ rglwidgetClass.prototype.setInvPrMatrix = function() {
+ this.invPrMatrix = new CanvasMatrix4( this.prMatrix );
+ this.invPrMatrix.invert();
+ this.invPrMatrix.transpose();
+ };
diff --git a/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/rgl.css b/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/rgl.css
new file mode 100644
index 00000000..b22aaf65
--- /dev/null
+++ b/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/rgl.css
@@ -0,0 +1,21 @@
+.rglPlayer {
+ width: auto;
+ height: auto;
+}
+
+.rglPlayer .rgl-button {
+ width: auto;
+ display: inline-block;
+ font-size: 75%;
+}
+
+.rglPlayer .rgl-slider {
+ display: inline-block;
+ width: 30%;
+}
+
+.rglPlayer .rgl-label {
+ display: inline;
+ padding-left: 6px;
+ padding-right: 6px;
+}
diff --git a/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/rglClass.src.js b/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/rglClass.src.js
new file mode 100644
index 00000000..475300ee
--- /dev/null
+++ b/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/rglClass.src.js
@@ -0,0 +1,71 @@
+//// To generate the help pages for this library, use
+
+// jsdoc --template /usr/local/lib/node_modules/foodoc/template *.src.js -R README.md -c JSDoc.json
+
+// To test, set environment variable RGL_DEBUGGING=true
+// before building.
+
+/* globals rglwidgetClass: true */
+
+/**
+ * The class of an rgl widget
+ * @class
+*/
+rglwidgetClass = function() {
+ this.canvas = null;
+ this.userMatrix = new CanvasMatrix4();
+ this.types = [];
+ this.prMatrix = new CanvasMatrix4();
+ this.mvMatrix = new CanvasMatrix4();
+ this.vp = null;
+ this.prmvMatrix = null;
+ this.origs = null;
+ this.gl = null;
+ this.scene = null;
+ this.select = {state: "inactive", subscene: null, region: {p1: {x:0, y:0}, p2: {x:0, y:0}}};
+ this.drawing = false;
+};
+
+ rglwidgetClass.f_is_lit = 1;
+ rglwidgetClass.f_is_smooth = 2;
+ rglwidgetClass.f_has_texture = 4;
+ rglwidgetClass.f_depth_sort = 8;
+ rglwidgetClass.f_fixed_quads = 16;
+ rglwidgetClass.f_is_transparent = 32;
+ rglwidgetClass.f_is_lines = 64;
+ rglwidgetClass.f_sprites_3d = 128;
+ rglwidgetClass.f_is_subscene = 256;
+ rglwidgetClass.f_is_clipplanes = 512;
+ rglwidgetClass.f_fixed_size = 1024;
+ rglwidgetClass.f_is_points = 2048;
+ rglwidgetClass.f_is_twosided = 4096;
+ rglwidgetClass.f_fat_lines = 8192;
+ rglwidgetClass.f_is_brush = 16384;
+ rglwidgetClass.f_has_fog = 32768;
+ rglwidgetClass.f_rotating = 65536;
+
+ rglwidgetClass.prototype.fogNone = 0;
+ rglwidgetClass.prototype.fogLinear = 1;
+ rglwidgetClass.prototype.fogExp = 2;
+ rglwidgetClass.prototype.fogExp2 = 3;
+
+ /**
+ * Methods related to obsolete approaches.
+ * @name ___OBSOLETE_METHODS___
+ * @memberof rglwidgetClass
+ * @kind function
+ * @instance
+ */
+
+ /**
+ * Start the writeWebGL scene. This is only used by writeWebGL; rglwidget has
+ no debug element.
+ */
+ rglwidgetClass.prototype.start = function() {
+ if (typeof this.prefix !== "undefined") {
+ this.debugelement = document.getElementById(this.prefix + "debug");
+ this.debug("");
+ }
+ this.drag = 0;
+ this.drawScene();
+ };
diff --git a/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/rglTimer.src.js b/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/rglTimer.src.js
new file mode 100644
index 00000000..10915569
--- /dev/null
+++ b/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/rglTimer.src.js
@@ -0,0 +1,155 @@
+
+/* globals rgltimerClass: true */
+
+/**
+ * The class of an rgl timer object
+ * @class
+*/
+
+/**
+ * Construct an rgltimerClass object
+ * @constructor
+ * @param { function } Tick - action when timer fires
+ * @param { number } startTime - nominal start time in seconds
+ * @param { number } interval - seconds between updates
+ * @param { number } stopTime - nominal stop time in seconds
+ * @param { number } stepSize - nominal step size
+ * @param { number } value - current nominal time
+ * @param { number } rate - nominal units per second
+ * @param { string } loop - "none", "cycle" or "oscillate"
+ * @param { Object } actions - list of actions
+ */
+rgltimerClass = function(Tick, startTime, interval, stopTime, stepSize, value, rate, loop, actions) {
+ this.enabled = false;
+ this.timerId = 0;
+ /** nominal start time in seconds */
+ this.startTime = startTime;
+ /** current nominal time */
+ this.value = value;
+ /** seconds between updates */
+ this.interval = interval;
+ /** nominal stop time */
+ this.stopTime = stopTime;
+ /** nominal step size */
+ this.stepSize = stepSize;
+ /** nominal units per second */
+ this.rate = rate;
+ /** "none", "cycle", or "oscillate" */
+ this.loop = loop;
+ /** real world start time */
+ this.realStart = undefined;
+ /** multiplier for fast-forward or reverse */
+ this.multiplier = 1;
+ this.actions = actions;
+ this.Tick = Tick;
+};
+
+
+ /**
+ * Methods related to players
+ * @name ___METHODS_FOR_PLAYERS___
+ * @memberof rgltimerClass
+ * @kind function
+ * @instance
+ */
+
+ /**
+ * Start playing
+ * @memberof rgltimerClass
+ */
+ rgltimerClass.prototype.play = function() {
+ if (this.enabled) {
+ this.enabled = false;
+ window.clearInterval(this.timerId);
+ this.timerId = 0;
+ return;
+ }
+ var tick = function(self) {
+ var now = new Date();
+ self.value = self.multiplier*self.rate*(now - self.realStart)/1000 + self.startTime;
+ self.forceToRange();
+ if (typeof self.Tick !== "undefined") {
+ self.Tick(self.value);
+ }
+
+ };
+ this.realStart = new Date() - 1000*(this.value - this.startTime)/this.rate/this.multiplier;
+ this.timerId = window.setInterval(tick, 1000*this.interval, this);
+ this.enabled = true;
+ };
+
+ /**
+ * Force value into legal range
+ */
+ rgltimerClass.prototype.forceToRange = function() {
+ if (this.value > this.stopTime + this.stepSize/2 || this.value < this.startTime - this.stepSize/2) {
+ if (!this.loop) {
+ this.reset();
+ } else {
+ var cycle = this.stopTime - this.startTime + this.stepSize,
+ newval = (this.value - this.startTime) % cycle + this.startTime;
+ if (newval < this.startTime) {
+ newval += cycle;
+ }
+ this.realStart += (this.value - newval)*1000/this.multiplier/this.rate;
+ this.value = newval;
+ }
+ }
+ };
+
+ /**
+ * Reset to start values
+ */
+ rgltimerClass.prototype.reset = function() {
+ this.value = this.startTime;
+ this.newmultiplier(1);
+ if (typeof this.Tick !== "undefined") {
+ this.Tick(this.value);
+ }
+ if (this.enabled)
+ this.play(); /* really pause... */
+ if (typeof this.PlayButton !== "undefined")
+ this.PlayButton.value = "Play";
+ };
+
+ /**
+ * Increase the multiplier to play faster
+ */
+ rgltimerClass.prototype.faster = function() {
+ this.newmultiplier(Math.SQRT2*this.multiplier);
+ };
+
+ /**
+ * Decrease the multiplier to play slower
+ */
+ rgltimerClass.prototype.slower = function() {
+ this.newmultiplier(this.multiplier/Math.SQRT2);
+ };
+
+ /**
+ * Change sign of multiplier to reverse direction
+ */
+ rgltimerClass.prototype.reverse = function() {
+ this.newmultiplier(-this.multiplier);
+ };
+
+ /**
+ * Set multiplier for play speed
+ * @param { number } newmult - new value
+ */
+ rgltimerClass.prototype.newmultiplier = function(newmult) {
+ if (newmult !== this.multiplier) {
+ this.realStart += 1000*(this.value - this.startTime)/this.rate*(1/this.multiplier - 1/newmult);
+ this.multiplier = newmult;
+ }
+ };
+
+ /**
+ * Take one step
+ */
+ rgltimerClass.prototype.step = function() {
+ this.value += this.rate*this.multiplier;
+ this.forceToRange();
+ if (typeof this.Tick !== "undefined")
+ this.Tick(this.value);
+ };
diff --git a/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/selection.src.js b/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/selection.src.js
new file mode 100644
index 00000000..a45e09e2
--- /dev/null
+++ b/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/selection.src.js
@@ -0,0 +1,129 @@
+ /**
+ * Methods related to selection
+ * @name ___METHODS_FOR_SELECTION___
+ * @memberof rglwidgetClass
+ * @kind function
+ * @instance
+ */
+
+ /**
+ * Respond to brush change
+ */
+ rglwidgetClass.prototype.selectionChanged = function() {
+ var i, j, k, id, subid = this.select.subscene, subscene,
+ objids, obj,
+ p1 = this.select.region.p1, p2 = this.select.region.p2,
+ filter, selection = [], handle, keys, xmin, x, xmax, ymin, y, ymax, z, v,
+ someHidden;
+ if (!subid)
+ return;
+ subscene = this.getObj(subid);
+ objids = subscene.objects;
+ filter = this.scene.crosstalk.filter;
+ this.setmvMatrix(subid);
+ this.setprMatrix(subid);
+ this.setprmvMatrix();
+ xmin = Math.min(p1.x, p2.x);
+ xmax = Math.max(p1.x, p2.x);
+ ymin = Math.min(p1.y, p2.y);
+ ymax = Math.max(p1.y, p2.y);
+ for (i = 0; i < objids.length; i++) {
+ id = objids[i];
+ j = this.scene.crosstalk.id.indexOf(id);
+ if (j >= 0) {
+ keys = this.scene.crosstalk.key[j];
+ obj = this.getObj(id);
+ someHidden = false;
+ for (k = 0; k < keys.length; k++) {
+ if (filter && filter.indexOf(keys[k]) < 0) {
+ someHidden = true;
+ continue;
+ }
+ v = [].concat(obj.vertices[k]).concat(1.0);
+ v = rglwidgetClass.multVM(v, this.prmvMatrix);
+ x = v[0]/v[3];
+ y = v[1]/v[3];
+ z = v[2]/v[3];
+ if (xmin <= x && x <= xmax && ymin <= y && y <= ymax && -1.0 <= z && z <= 1.0) {
+ selection.push(keys[k]);
+ } else
+ someHidden = true;
+ }
+ obj.someHidden = someHidden && (filter || selection.length);
+ obj.initialized = false;
+ /* Who should we notify? Only shared data in the current subscene, or everyone? */
+ if (!this.equalArrays(selection, this.scene.crosstalk.selection)) {
+ handle = this.scene.crosstalk.sel_handle[j];
+ handle.set(selection, {rglSubsceneId: this.select.subscene});
+ }
+ }
+ }
+ };
+
+ /**
+ * Respond to selection or filter change from crosstalk
+ * @param { Object } event - crosstalk event
+ * @param { boolean } filter - filter or selection?
+ */
+ rglwidgetClass.prototype.selection = function(event, filter) {
+ var i, j, ids, obj, keys, crosstalk = this.scene.crosstalk,
+ selection, someHidden;
+
+ // Record the message and find out if this event makes some objects have mixed values:
+
+ crosstalk = this.scene.crosstalk;
+
+ if (filter) {
+ filter = crosstalk.filter = event.value;
+ selection = crosstalk.selection;
+ } else {
+ selection = crosstalk.selection = event.value;
+ filter = crosstalk.filter;
+ }
+ ids = crosstalk.id;
+ for (i = 0; i < ids.length ; i++) {
+ obj = this.getObj(ids[i]);
+ obj.initialized = false;
+ keys = crosstalk.key[i];
+ someHidden = false;
+ for (j = 0; j < keys.length && !someHidden; j++) {
+ if ((filter && filter.indexOf(keys[j]) < 0) ||
+ (selection.length && selection.indexOf(keys[j]) < 0))
+ someHidden = true;
+ }
+ obj.someHidden = someHidden;
+ }
+ this.drawScene();
+ };
+
+ /**
+ * Clear the selection brush
+ * @param { number } except - Subscene that should ignore this request
+ */
+ rglwidgetClass.prototype.clearBrush = function(except) {
+ if (this.select.subscene !== except) {
+ this.select.region = {p1: {x:Infinity, y:Infinity},
+ p2: {x:Infinity, y:Infinity}};
+ this.selectionChanged();
+ this.select.state = "inactive";
+ this.delFromSubscene(this.scene.brushId, this.select.subscene);
+ }
+ this.drawScene();
+ };
+
+ /**
+ * Set the vertices in the selection box object
+ */
+ rglwidgetClass.prototype.initSelection = function(id) {
+ if (typeof this.select.region === "undefined")
+ return;
+ var obj = this.getObj(id),
+ p1 = this.select.region.p1,
+ p2 = this.select.region.p2;
+
+ obj.vertices = [[p1.x, p1.y, 0.0],
+ [p2.x, p1.y, 0.0],
+ [p2.x, p2.y, 0.0],
+ [p1.x, p2.y, 0.0],
+ [p1.x, p1.y, 0.0]];
+ };
diff --git a/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/shaders.src.js b/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/shaders.src.js
new file mode 100644
index 00000000..5f426ac2
--- /dev/null
+++ b/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/shaders.src.js
@@ -0,0 +1,175 @@
+ /**
+ * Methods related to shaders
+ * @name ___METHODS_FOR_SHADERS___
+ * @memberof rglwidgetClass
+ * @kind function
+ * @instance
+ */
+
+ /**
+ * Get flags that will end up as shader defines.
+ * Static method so it can be called from R
+ */
+ rglwidgetClass.getDefFlags = function(flags, type, normals, round_points) {
+ var f = {};
+ f.fat_lines = rglwidgetClass.isSet(flags, rglwidgetClass.f_fat_lines);
+ f.fixed_quads = rglwidgetClass.isSet(flags, rglwidgetClass.f_fixed_quads);
+ f.fixed_size = rglwidgetClass.isSet(flags, rglwidgetClass.f_fixed_size);
+ f.has_fog = rglwidgetClass.isSet(flags, rglwidgetClass.f_has_fog);
+ f.has_normals = (typeof normals !== "undefined") ||
+ type === "spheres";
+ f.has_texture = rglwidgetClass.isSet(flags, rglwidgetClass.f_has_texture);
+ f.is_brush = rglwidgetClass.isSet(flags, rglwidgetClass.f_is_brush);
+ f.is_lines = rglwidgetClass.isSet(flags, rglwidgetClass.f_is_lines);
+ f.is_lit = rglwidgetClass.isSet(flags, rglwidgetClass.f_is_lit);
+ f.is_points = rglwidgetClass.isSet(flags, rglwidgetClass.f_is_points);
+ f.is_transparent = rglwidgetClass.isSet(flags, rglwidgetClass.f_is_transparent);
+ f.is_twosided = rglwidgetClass.isSet(flags, rglwidgetClass.f_is_twosided);
+ f.needs_vnormal = (f.is_lit && !f.fixed_quads && !f.is_brush) || (f.is_twosided && f.has_normals);
+ f.rotating = rglwidgetClass.isSet(flags, rglwidgetClass.f_rotating);
+ f.round_points = round_points;
+ return f;
+ };
+
+
+ /**
+ * Generate the defines for the shader code for an object.
+ *
+ * This is a static method so it can be called from R.
+ *
+ * @returns {string}
+ * @param id - id of object
+ * @param type - type of object
+ * @param flags - object flags
+ * @param nclipplanes - number of clipping planes in scene
+ * (may not all be active)
+ * @param nlights - number of lights in scene (ditto)
+ * @param normals - normals for object
+ * @param pointSize - point size for object
+ * @param textype - texture type for object
+ * @param antialias - use antialiasing?
+ */
+ rglwidgetClass.getDefines = function(id, type, flags, nclipplanes, nlights, normals, pointSize, textype, antialias, fl) {
+ var
+ title, defines;
+
+ if (typeof fl === "undefined")
+ fl = rglwidgetClass.getDefFlags(flags, type, normals, antialias);
+
+ title = " /* ****** "+type+" object "+id+" shader ****** */\n";
+
+ defines = "#define NCLIPPLANES " + nclipplanes + "\n"+
+ "#define NLIGHTS " + nlights + "\n";
+
+ if (fl.fat_lines)
+ defines = defines + "#define FAT_LINES 1\n";
+
+ if (fl.fixed_quads)
+ defines = defines + "#define FIXED_QUADS 1\n";
+
+ if (fl.fixed_size)
+ defines = defines + "#define FIXED_SIZE 1\n";
+
+ if (fl.has_fog)
+ defines = defines + "#define HAS_FOG 1\n";
+
+ if (fl.has_normals)
+ defines = defines + "#define HAS_NORMALS 1\n";
+
+ if (fl.has_texture) {
+ defines = defines + "#define HAS_TEXTURE 1\n";
+ defines = defines + "#define TEXTURE_" + textype + "\n";
+ }
+
+ if (fl.is_brush)
+ defines = defines + "#define IS_BRUSH 1\n";
+
+ if (type === "linestrip")
+ defines = defines + "#define IS_LINESTRIP 1\n";
+
+ if (fl.is_lit)
+ defines = defines + "#define IS_LIT 1\n";
+
+ if (fl.is_points) {
+ defines = defines + "#define IS_POINTS 1\n";
+ defines = defines + "#define POINTSIZE " + Number.parseFloat(pointSize).toFixed(1) + "\n";
+ }
+
+ if (type === "sprites")
+ defines = defines + "#define IS_SPRITES 1\n";
+
+ if (type === "text")
+ defines = defines + "#define IS_TEXT 1\n";
+
+ if (fl.is_transparent)
+ defines = defines + "#define IS_TRANSPARENT 1\n";
+
+ if (fl.is_twosided)
+ defines = defines + "#define IS_TWOSIDED 1\n";
+
+ if (fl.needs_vnormal)
+ defines = defines + "#define NEEDS_VNORMAL 1\n";
+
+ if (fl.rotating)
+ defines = defines + "#define ROTATING 1\n";
+
+ if (fl.round_points)
+ defines = defines + "#define ROUND_POINTS 1\n";
+
+ // console.log(result);
+ return title + defines;
+ };
+
+ /**
+ * Create code for vertex and fragment shaders
+ * @returns {Object}
+ * @param { number } shaderType - gl code for shader type
+ * @param { string } code - code for the shader
+ */
+ rglwidgetClass.prototype.getShaders = function(obj) {
+ var header,
+ vertex = obj.userVertexShader,
+ fragment = obj.userFragmentShader;
+
+ header = rglwidgetClass.getDefines(
+ obj.id, obj.type, obj.flags,
+ this.countClipplanes(), this.countLights(),
+ obj.normals,
+ this.getMaterial(obj, "size"),
+ this.getMaterial(obj, "textype"),
+ this.getMaterial(obj, "point_antialias"),
+ obj.defFlags
+ );
+
+ if (typeof vertex === "undefined")
+ vertex = document.getElementById("rgl-vertex-shader").text;
+
+ if (typeof fragment === "undefined")
+ fragment = document.getElementById("rgl-fragment-shader").text;
+
+// console.log("vertex:");
+// console.log(header + vertex);
+// console.log("fragment:");
+// console.log(header + fragment);
+
+ return {vertex: header + vertex,
+ fragment: header + fragment};
+ };
+
+
+ /**
+ * Call gl functions to create and compile shader from code
+ * @returns {Object}
+ * @param { number } shaderType - gl code for shader type
+ * @param { string } code - code for the shader
+ */
+ rglwidgetClass.prototype.getShader = function(shaderType, code) {
+ var gl = this.gl, shader;
+ shader = gl.createShader(shaderType);
+ gl.shaderSource(shader, code);
+ gl.compileShader(shader);
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS) && !gl.isContextLost())
+ alert(gl.getShaderInfoLog(shader));
+ return shader;
+ };
+
diff --git a/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/subscenes.src.js b/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/subscenes.src.js
new file mode 100644
index 00000000..fc6e05d0
--- /dev/null
+++ b/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/subscenes.src.js
@@ -0,0 +1,179 @@
+ /**
+ * Methods related to subscenes
+ * @name ___METHODS_FOR_SUBSCENES___
+ * @memberof rglwidgetClass
+ * @kind function
+ * @instance
+ */
+
+ /**
+ * Is a particular id in a subscene?
+ * @returns { boolean }
+ * @param {number} id Which id?
+ * @param {number} subscene Which subscene id?
+ */
+ rglwidgetClass.prototype.inSubscene = function(id, subscene) {
+ return this.getObj(subscene).objects.indexOf(id) > -1;
+ };
+
+ /**
+ * Translate from window coordinates to viewport coordinates
+ * @returns { Object } translated coordinates
+ * @param { number } subsceneid - which subscene to use?
+ * @param { Object } coords - point to translate
+ */
+ rglwidgetClass.prototype.translateCoords = function(subsceneid, coords) {
+ var viewport = this.getObj(subsceneid).par3d.viewport;
+ return {x: coords.x - viewport.x*this.canvas.width,
+ y: coords.y - viewport.y*this.canvas.height};
+ };
+
+ /**
+ * Check whether point is in viewport of subscene
+ * @returns {boolean}
+ * @param { Object } coords - screen coordinates of point
+ * @param { number } subsceneid - subscene to check
+ */
+ rglwidgetClass.prototype.inViewport = function(coords, subsceneid) {
+ var viewport = this.getObj(subsceneid).par3d.viewport,
+ x0 = coords.x - viewport.x*this.canvas.width,
+ y0 = coords.y - viewport.y*this.canvas.height;
+ return 0 <= x0 && x0 <= viewport.width*this.canvas.width &&
+ 0 <= y0 && y0 <= viewport.height*this.canvas.height;
+ };
+
+ /**
+ * Find which subscene contains a point
+ * @returns { number } subscene id
+ * @param { Object } coords - coordinates of point
+ */
+ rglwidgetClass.prototype.whichSubscene = function(coords) {
+ var self = this,
+ recurse = function(subsceneid) {
+ var subscenes = self.getChildSubscenes(subsceneid), i, id;
+ for (i=0; i < subscenes.length; i++) {
+ id = recurse(subscenes[i]);
+ if (typeof(id) !== "undefined")
+ return(id);
+ }
+ if (self.inViewport(coords, subsceneid))
+ return(subsceneid);
+ else
+ return undefined;
+ },
+ rootid = this.scene.rootSubscene,
+ result = recurse(rootid);
+ if (typeof(result) === "undefined")
+ result = rootid;
+ return result;
+ };
+
+ /**
+ * Add an id to a subscene.
+ * @param {number} id Which id?
+ * @param {number} subscene Which subscene id?
+ */
+ rglwidgetClass.prototype.addToSubscene = function(id, subscene) {
+ var thelist,
+ thesub = this.getObj(subscene),
+ ids = [id],
+ obj = this.getObj(id), i;
+ if (typeof obj !== "undefined" && typeof (obj.newIds) !== "undefined") {
+ ids = ids.concat(obj.newIds);
+ }
+ thesub.objects = [].concat(thesub.objects);
+ for (i = 0; i < ids.length; i++) {
+ id = ids[i];
+ if (thesub.objects.indexOf(id) === -1) {
+ thelist = this.whichList(id);
+ thesub.objects.push(id);
+ thesub[thelist].push(id);
+ }
+ }
+ };
+
+ /**
+ * Delete an id from a subscene
+ * @param { number } id - the id to add
+ * @param { number } subscene - the id of the subscene
+ */
+ rglwidgetClass.prototype.delFromSubscene = function(id, subscene) {
+ var thelist,
+ thesub = this.getObj(subscene),
+ obj = this.getObj(id),
+ ids = [id], i, j;
+ if (typeof obj !== "undefined" && typeof (obj.newIds) !== "undefined")
+ ids = ids.concat(obj.newIds);
+ thesub.objects = [].concat(thesub.objects); // It might be a scalar
+ for (j=0; j -1) {
+ thesub.objects.splice(i, 1);
+ thelist = this.whichList(id);
+ i = thesub[thelist].indexOf(id);
+ thesub[thelist].splice(i, 1);
+ }
+ }
+ };
+
+ /**
+ * Set the ids in a subscene
+ * @param { number[] } ids - the ids to set
+ * @param { number } subsceneid - the id of the subscene
+ */
+ rglwidgetClass.prototype.setSubsceneEntries = function(ids, subsceneid) {
+ var sub = this.getObj(subsceneid);
+ sub.objects = ids;
+ this.initSubscene(subsceneid);
+ };
+
+ /**
+ * Get the ids in a subscene
+ * @returns {number[]}
+ * @param { number } subscene - the id of the subscene
+ */
+ rglwidgetClass.prototype.getSubsceneEntries = function(subscene) {
+ return this.getObj(subscene).objects;
+ };
+
+ /**
+ * Get the ids of the subscenes within a subscene
+ * @returns { number[] }
+ * @param { number } subscene - the id of the subscene
+ */
+ rglwidgetClass.prototype.getChildSubscenes = function(subscene) {
+ return this.getObj(subscene).subscenes;
+ };
+
+ /**
+ * Find a particular subscene by inheritance
+ * @returns { number } id of subscene to use
+ * @param { number } subsceneid - child subscene
+ * @param { string } type - type of inheritance: "projection" or "model"
+ */
+ rglwidgetClass.prototype.useid = function(subsceneid, type) {
+ var sub = this.getObj(subsceneid);
+ if (sub.embeddings[type] === "inherit")
+ return(this.useid(sub.parent, type));
+ else
+ return subsceneid;
+ };
+
+ /**
+ * Find bboxdeco for a subscene
+ * @returns { number } id of bboxdeco, or undefined if none
+ * @param { number } sub- subscene
+ */
+ rglwidgetClass.prototype.getBBoxDeco = function(sub) {
+ var objects = sub.objects, i, obj;
+ for (i = 0; i < objects.length; i++) {
+ obj = this.getObj(objects[i]);
+ if (obj.type === "bboxdeco")
+ return obj;
+ }
+ if (sub.parent)
+ return this.getBBoxDeco(this.getObj(sub.parent));
+ else
+ return undefined;
+ };
diff --git a/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/textures.src.js b/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/textures.src.js
new file mode 100644
index 00000000..3cf32d36
--- /dev/null
+++ b/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/textures.src.js
@@ -0,0 +1,173 @@
+ /**
+ * Methods related to textures
+ * @name ___METHODS_FOR_TEXTURES___
+ * @memberof rglwidgetClass
+ * @kind function
+ * @instance
+ */
+
+ rglwidgetClass.prototype.getTexFilter = function(filter) {
+ var gl = this.gl || this.initGL();
+ switch(filter) {
+ case "nearest": return gl.NEAREST;
+ case "linear": return gl.LINEAR;
+ case "nearest.mipmap.nearest": return gl.NEAREST_MIPMAP_NEAREST;
+ case "linear.mipmap.nearest": return gl.LINEAR_MIPMAP_NEAREST;
+ case "nearest.mipmap.linear": return gl.NEAREST_MIPMAP_LINEAR;
+ case "linear.mipmap.linear": return gl.LINEAR_MIPMAP_LINEAR;
+ default: console.error("Unknown filter: "+filter);
+ }
+ };
+
+ /**
+ * Handle a texture after its image has been loaded
+ * @param { Object } texture - the gl texture object
+ * @param { Object } textureCanvas - the canvas holding the image
+ */
+ rglwidgetClass.prototype.handleLoadedTexture = function(texture, textureCanvas) {
+ var gl = this.gl || this.initGL();
+ gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
+
+ gl.bindTexture(gl.TEXTURE_2D, texture);
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, textureCanvas);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST);
+ gl.generateMipmap(gl.TEXTURE_2D);
+
+ gl.bindTexture(gl.TEXTURE_2D, null);
+ };
+
+ /**
+ * Get maximum dimension of texture in current browser.
+ * @returns {number}
+ */
+ rglwidgetClass.prototype.getMaxTexSize = function() {
+ var gl = this.gl || this.initGL();
+ return Math.min(4096, gl.getParameter(gl.MAX_TEXTURE_SIZE));
+ };
+
+ /**
+ * Load an image to a texture
+ * @param { string } uri - The image location
+ * @param { Object } texture - the gl texture object
+ */
+ rglwidgetClass.prototype.loadImageToTexture = function(uri, texture) {
+ var canvas = this.textureCanvas,
+ ctx = canvas.getContext("2d"),
+ image = new Image(),
+ self = this;
+
+ image.onload = function() {
+
+ var w = image.width,
+ h = image.height,
+ canvasX = self.getPowerOfTwo(w),
+ canvasY = self.getPowerOfTwo(h),
+ maxTexSize = self.getMaxTexSize();
+ while (canvasX > 1 && canvasY > 1 && (canvasX > maxTexSize || canvasY > maxTexSize)) {
+ canvasX /= 2;
+ canvasY /= 2;
+ }
+ canvas.width = canvasX;
+ canvas.height = canvasY;
+ ctx.imageSmoothingEnabled = true;
+ ctx.drawImage(image, 0, 0, canvasX, canvasY);
+ self.handleLoadedTexture(texture, canvas);
+ self.texturesLoading -= 1;
+ if (!self.texturesLoading)
+ self.drawScene();
+ };
+ if (!self.texturesLoading)
+ self.texturesLoading = 0; // may have been undefined
+ self.texturesLoading += 1;
+ image.src = uri;
+ };
+
+ /**
+ * Draw text to the texture canvas
+ * @returns { Object } object with text measurements
+ * @param { string } text - the text
+ * @param { number } cex - expansion
+ * @param { string } family - font family
+ * @param { number } font - font number
+ */
+ rglwidgetClass.prototype.drawTextToCanvas = function(text, cex, family, font) {
+ var canvasX, canvasY,
+ scaling = 20,
+ textColour = "white",
+
+ backgroundColour = "rgba(0,0,0,0)",
+ canvas = this.textureCanvas,
+ ctx = canvas.getContext("2d"),
+ i, textHeight = 0, textHeights = [], width, widths = [],
+ offsetx, offsety = 0, line, lines = [], offsetsx = [],
+ offsetsy = [], lineoffsetsy = [], fontStrings = [],
+ maxTexSize = this.getMaxTexSize(),
+ getFontString = function(i) {
+ textHeights[i] = scaling*cex[i];
+ var fontString = textHeights[i] + "px",
+ family0 = family[i],
+ font0 = font[i];
+ if (family0 === "sans")
+ family0 = "sans-serif";
+ else if (family0 === "mono")
+ family0 = "monospace";
+ fontString = fontString + " " + family0;
+ if (font0 === 2 || font0 === 4)
+ fontString = "bold " + fontString;
+ if (font0 === 3 || font0 === 4)
+ fontString = "italic " + fontString;
+ return fontString;
+ };
+ cex = rglwidgetClass.repeatToLen(cex, text.length);
+ family = rglwidgetClass.repeatToLen(family, text.length);
+ font = rglwidgetClass.repeatToLen(font, text.length);
+
+ canvasX = 1;
+ line = -1;
+ offsetx = maxTexSize;
+ for (i = 0; i < text.length; i++) {
+ ctx.font = fontStrings[i] = getFontString(i);
+ width = widths[i] = ctx.measureText(text[i]).width;
+ if (offsetx + width > maxTexSize) {
+ offsety = offsety + 2*textHeight;
+ if (line >= 0)
+ lineoffsetsy[line] = offsety;
+ line += 1;
+ if (offsety > maxTexSize)
+ console.error("Too many strings for texture.");
+ textHeight = 0;
+ offsetx = 0;
+ }
+ textHeight = Math.max(textHeight, textHeights[i]);
+ offsetsx[i] = offsetx;
+ offsetx += width;
+ canvasX = Math.max(canvasX, offsetx);
+ lines[i] = line;
+ }
+ offsety = lineoffsetsy[line] = offsety + 2*textHeight;
+ for (i = 0; i < text.length; i++) {
+ offsetsy[i] = lineoffsetsy[lines[i]];
+ }
+
+ canvasX = this.getPowerOfTwo(canvasX);
+ canvasY = this.getPowerOfTwo(offsety);
+
+ canvas.width = canvasX;
+ canvas.height = canvasY;
+
+ ctx.fillStyle = backgroundColour;
+ ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
+
+ ctx.textBaseline = "alphabetic";
+ for(i = 0; i < text.length; i++) {
+ ctx.font = fontStrings[i];
+ ctx.fillStyle = textColour;
+ ctx.textAlign = "left";
+ ctx.fillText(text[i], offsetsx[i], offsetsy[i]);
+ }
+ return {canvasX:canvasX, canvasY:canvasY,
+ widths:widths, textHeights:textHeights,
+ offsetsx:offsetsx, offsetsy:offsetsy};
+ };
+
diff --git a/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/utils.src.js b/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/utils.src.js
new file mode 100644
index 00000000..00bc5fc7
--- /dev/null
+++ b/docs/articles/linear-equations_files/rglwidgetClass-1.0.1/utils.src.js
@@ -0,0 +1,654 @@
+ /**
+ * Utility methods
+ * @name ___UTILITY_METHODS___
+ * @memberof rglwidgetClass
+ * @kind function
+ * @instance
+ */
+
+ /**
+ * Multiply matrix by vector
+ * @returns {number[]}
+ * @param M {number[][]} Left operand
+ * @param v {number[]} Right operand
+ */
+ rglwidgetClass.multMV = function(M, v) {
+ return [ M.m11 * v[0] + M.m12 * v[1] + M.m13 * v[2] + M.m14 * v[3],
+ M.m21 * v[0] + M.m22 * v[1] + M.m23 * v[2] + M.m24 * v[3],
+ M.m31 * v[0] + M.m32 * v[1] + M.m33 * v[2] + M.m34 * v[3],
+ M.m41 * v[0] + M.m42 * v[1] + M.m43 * v[2] + M.m44 * v[3]
+ ];
+ };
+
+ /**
+ * Multiply row vector by Matrix
+ * @returns {number[]}
+ * @param v {number[]} left operand
+ * @param M {number[][]} right operand
+ */
+ rglwidgetClass.multVM = function(v, M) {
+ return [ M.m11 * v[0] + M.m21 * v[1] + M.m31 * v[2] + M.m41 * v[3],
+ M.m12 * v[0] + M.m22 * v[1] + M.m32 * v[2] + M.m42 * v[3],
+ M.m13 * v[0] + M.m23 * v[1] + M.m33 * v[2] + M.m43 * v[3],
+ M.m14 * v[0] + M.m24 * v[1] + M.m34 * v[2] + M.m44 * v[3]
+ ];
+ };
+
+ /**
+ * Euclidean length of a vector
+ * @returns {number}
+ * @param v {number[]}
+ */
+ rglwidgetClass.vlen = function(v) {
+ return Math.sqrt(rglwidgetClass.dotprod(v, v));
+ };
+
+ /**
+ * Dot product of two vectors
+ * @instance rglwidgetClass
+ * @returns {number}
+ * @param a {number[]}
+ * @param b {number[]}
+ */
+ rglwidgetClass.dotprod = function(a, b) {
+ return a[0]*b[0] + a[1]*b[1] + a[2]*b[2];
+ };
+
+ /**
+ * Cross product of two vectors
+ * @returns {number[]}
+ * @param a {number[]}
+ * @param b {number[]}
+ */
+ rglwidgetClass.xprod = function(a, b) {
+ return [a[1]*b[2] - a[2]*b[1],
+ a[2]*b[0] - a[0]*b[2],
+ a[0]*b[1] - a[1]*b[0]];
+ };
+
+ /**
+ * Bind vectors or matrices by columns
+ * @returns {number[][]}
+ * @param a {number[][]}
+ * @param b {number[]|number[][]}
+ */
+ rglwidgetClass.cbind = function(a, b) {
+ if (b.length < a.length)
+ b = rglwidgetClass.repeatToLen(b, a.length);
+ else if (a.length < b.length)
+ a = rglwidgetClass.repeatToLen(a, b.length);
+ return a.map(function(currentValue, index) {
+ return [].concat(currentValue).concat(b[index]);
+ });
+ };
+
+ /**
+ * Swap elements
+ * @returns {any[]}
+ * @param a {any[]}
+ * @param i {number} Element to swap
+ * @param j {number} Other element to swap
+ */
+ rglwidgetClass.swap = function(a, i, j) {
+ var temp = a[i];
+ a[i] = a[j];
+ a[j] = temp;
+ };
+
+ /**
+ * Flatten a matrix into a vector
+ * @returns {any[]}
+ * @param a {any[][]}
+ */
+ rglwidgetClass.flatten = function(arr, result) {
+ var value;
+ if (typeof result === "undefined") result = [];
+ for (var i = 0, length = arr.length; i < length; i++) {
+ value = arr[i];
+ if (Array.isArray(value)) {
+ rglwidgetClass.flatten(value, result);
+ } else {
+ result.push(value);
+ }
+ }
+ return result;
+ };
+
+ /**
+ * set element of 1d or 2d array as if it was flattened.
+ * Column major, zero based!
+ * @returns {any[]|any[][]}
+ * @param {any[]|any[][]} a - array
+ * @param {number} i - element
+ * @param {any} value
+ */
+ rglwidgetClass.prototype.setElement = function(a, i, value) {
+ if (Array.isArray(a[0])) {
+ var dim = a.length,
+ col = Math.floor(i/dim),
+ row = i % dim;
+ a[row][col] = value;
+ } else {
+ a[i] = value;
+ }
+ };
+
+ /**
+ * Transpose an array
+ * @returns {any[][]}
+ * @param {any[][]} a
+ */
+ rglwidgetClass.prototype.transpose = function(a) {
+ var newArray = [],
+ n = a.length,
+ m = a[0].length,
+ i;
+ for(i = 0; i < m; i++){
+ newArray.push([]);
+ }
+
+ for(i = 0; i < n; i++){
+ for(var j = 0; j < m; j++){
+ newArray[j].push(a[i][j]);
+ }
+ }
+ return newArray;
+ };
+
+ /**
+ * Calculate sum of squares of a numeric vector
+ * @returns {number}
+ * @param {number[]} x
+ */
+ rglwidgetClass.prototype.sumsq = function(x) {
+ var result = 0, i;
+ for (i=0; i < x.length; i++)
+ result += x[i]*x[i];
+ return result;
+ };
+
+ /**
+ * Convert a matrix to a CanvasMatrix4
+ * @returns {CanvasMatrix4}
+ * @param {number[][]|number[]} mat
+ */
+ rglwidgetClass.prototype.toCanvasMatrix4 = function(mat) {
+ if (mat instanceof CanvasMatrix4)
+ return mat;
+ var result = new CanvasMatrix4();
+ mat = rglwidgetClass.flatten(this.transpose(mat));
+ result.load(mat);
+ return result;
+ };
+
+ /**
+ * Convert an R-style numeric colour string to an rgb vector
+ * @returns {number[]}
+ * @param {string} s
+ */
+ /* jshint bitwise:false */
+ rglwidgetClass.prototype.stringToRgb = function(s) {
+ s = s.replace("#", "");
+ var bigint = parseInt(s, 16);
+ return [((bigint >> 16) & 255)/255,
+ ((bigint >> 8) & 255)/255,
+ (bigint & 255)/255];
+ };
+ /* jshint bitwise:true */
+ /**
+ * Which list does a particular id come from?
+ * @returns { string }
+ * @param {number} id The id to look up.
+ */
+ rglwidgetClass.prototype.whichList = function(id) {
+ var obj = this.getObj(id),
+ flags = obj.flags;
+ if (obj.type === "light")
+ return "lights";
+ if (rglwidgetClass.isSet(flags, rglwidgetClass.f_is_subscene))
+ return "subscenes";
+ if (rglwidgetClass.isSet(flags, rglwidgetClass.f_is_clipplanes))
+ return "clipplanes";
+ if (rglwidgetClass.isSet(flags, rglwidgetClass.f_is_transparent))
+ return "transparent";
+ return "opaque";
+ };
+
+ /**
+ * Take a component-by-component product of two 3 vectors
+ * @returns {number[]}
+ * @param {number[]} x
+ * @param {number[]} y
+ */
+ rglwidgetClass.prototype.componentProduct = function(x, y) {
+ if (typeof y === "undefined") {
+ this.alertOnce("Bad arg to componentProduct");
+ }
+ var result = new Float32Array(3), i;
+ for (i = 0; i<3; i++)
+ result[i] = x[i]*y[i];
+ return result;
+ };
+
+ /**
+ * Get next higher power of two
+ * @returns { number }
+ * @param { number } value - input value
+ */
+ rglwidgetClass.prototype.getPowerOfTwo = function(value) {
+ var pow = 1;
+ while(pow= -windHeight &&
+ rect.left >= -windWidth &&
+ rect.bottom <= 2*windHeight &&
+ rect.right <= 2*windWidth);
+ };
+
+ rglwidgetClass.keydiff = function(obj1, obj2) {
+ var keys = Object.keys(obj1), i, result = [];
+ for (i=0;i= 2 && cross(lower[lower.length - 2], lower[lower.length - 1], points[i]) <= 0) {
+ lower.pop();
+ }
+ lower.push(points[i]);
+ }
+
+ for (i = points.length - 1; i >= 0; i--) {
+ while (upper.length >= 2 && cross(upper[upper.length - 2], upper[upper.length - 1], points[i]) <= 0) {
+ upper.pop();
+ }
+ upper.push(points[i]);
+ }
+
+ upper.pop();
+ lower.pop();
+ return lower.concat(upper);
+ };
+
+ /**
+ * Round number to given precision
+ * @param { number } x
+ * @param { number } digits
+ * @returns { number }
+ */
+ rglwidgetClass.signif = function(x, digits) {
+ return parseFloat(x.toPrecision(digits));
+ };
+
+ /**
+ * Check for NA, NaN, undefined, or null
+ * @param x
+ * @returns { bool }
+ */
+ rglwidgetClass.missing = function(x) {
+ return x !== "-Inf" && x !== "Inf" &&
+ (isNaN(x) || x === null || typeof(x) === "undefined");
+ };
+
+ /**
+ * Write matrix to log
+ * @param M
+ */
+ rglwidgetClass.logMatrix = function(M) {
+ console.log("matrix(c("+M.m11+","+M.m12+","+M.m13+","+M.m14+",\n"+
+ M.m21+","+M.m22+","+M.m23+","+M.m24+",\n"+
+ M.m31+","+M.m32+","+M.m33+","+M.m34+",\n"+
+ M.m41+","+M.m42+","+M.m43+","+M.m44+"), byrow=TRUE, ncol=4)");
+ };
+
+ /**
+ * Write vector to log
+ * @param {vector} v
+ */
+
+ rglwidgetClass.logVec3 = function(v) {
+ console.log("c("+v[0]+","+v[1]+","+v[2]+")");
+ };
+
+ /**
+ * Sum two vectors
+ * @param {vector} x
+ * @param {vector} y
+ */
+ rglwidgetClass.vsum = function(x, y) {
+ var i, result = [].concat(x);
+ for (i = 0; i < y.length; i++)
+ result[i] += y[i];
+ return result;
+ };
+
+ /**
+ * difference of two vectors
+ * @param {vector} x
+ * @param {vector} y
+ */
+ rglwidgetClass.vdiff = function(x, y) {
+ return rglwidgetClass.vsum(x, rglwidgetClass.vscale(y, -1));
+ };
+
+ /**
+ * Scale a vector
+ * @param {number} s
+ * @param {vector} x
+ */
+ rglwidgetClass.vscale = function(x, s) {
+ var i, result = [].concat(x);
+ for (i = 0; i < x.length; i++)
+ result[i] *= s;
+ return result;
+ };
+
+ /**
+ * Normalize a vector
+ * @param {vector} v
+ */
+ rglwidgetClass.normalize = function(v) {
+ return rglwidgetClass.vscale(v, 1/rglwidgetClass.vlen(v));
+ };
+
+ /**
+ * Compute the dimensions of a regular array
+ * without checking that it is regular
+ */
+ rglwidgetClass.arrayDim = function(arr) {
+ var result = [];
+ while (typeof arr.length !== "undefined") {
+ result = result.concat(arr.length);
+ arr = arr[0];
+ }
+ return result;
+ };
diff --git a/docs/authors.html b/docs/authors.html
index ba7e6264..f3be1efa 100644
--- a/docs/authors.html
+++ b/docs/authors.html
@@ -1,5 +1,5 @@
-Authors and Citation • matlib Authors and Citation • matlib
@@ -17,7 +17,7 @@
matlib
- 0.9.6
+ 0.9.7
@@ -114,17 +114,17 @@