From 9b613f9a4908f6d33d22c792f2acc86ec7e52d72 Mon Sep 17 00:00:00 2001 From: Bisaloo Date: Mon, 11 Dec 2023 11:37:41 +0000 Subject: [PATCH] =?UTF-8?q?Deploying=20to=20gh-pages=20from=20@=20rmaia/pa?= =?UTF-8?q?vo@a3e3986083756bfe5c0e07dbe3591e53e29cfba8=20=F0=9F=9A=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkgdown.yml | 4 +- reference/as.rimg.html | 13 +- reference/jnd2xyz.html | 2 +- reference/jndplot.html | 2 +- reference/jndrot.html | 2 +- .../CanvasMatrix4-1.2.8/CanvasMatrix.src.js | 729 +++++++++ .../libs/htmlwidgets-1.6.4/htmlwidgets.js | 901 +++++++++++ .../libs/rglWebGL-binding-1.2.8/rglWebGL.js | 79 + .../rglwidgetClass-1.2.8/animation.src.js | 153 ++ .../libs/rglwidgetClass-1.2.8/axes.src.js | 441 ++++++ .../libs/rglwidgetClass-1.2.8/buffer.src.js | 182 +++ .../libs/rglwidgetClass-1.2.8/controls.src.js | 591 +++++++ .../libs/rglwidgetClass-1.2.8/draw.src.js | 1370 +++++++++++++++++ .../libs/rglwidgetClass-1.2.8/init.src.js | 1318 ++++++++++++++++ .../libs/rglwidgetClass-1.2.8/mouse.src.js | 569 +++++++ .../libs/rglwidgetClass-1.2.8/pieces.src.js | 135 ++ .../libs/rglwidgetClass-1.2.8/pretty.src.js | 163 ++ .../rglwidgetClass-1.2.8/projection.src.js | 148 ++ reference/libs/rglwidgetClass-1.2.8/rgl.css | 21 + .../libs/rglwidgetClass-1.2.8/rglClass.src.js | 71 + .../libs/rglwidgetClass-1.2.8/rglTimer.src.js | 155 ++ .../rglwidgetClass-1.2.8/selection.src.js | 129 ++ .../libs/rglwidgetClass-1.2.8/shaders.src.js | 183 +++ .../rglwidgetClass-1.2.8/shadersrc.src.js | 445 ++++++ .../rglwidgetClass-1.2.8/subscenes.src.js | 179 +++ .../libs/rglwidgetClass-1.2.8/textures.src.js | 173 +++ .../libs/rglwidgetClass-1.2.8/utils.src.js | 654 ++++++++ reference/plot.colspace.html | 8 +- reference/simulate_spec.html | 32 +- reference/tcsplot.html | 8 +- 30 files changed, 8826 insertions(+), 34 deletions(-) create mode 100644 reference/libs/CanvasMatrix4-1.2.8/CanvasMatrix.src.js create mode 100644 reference/libs/htmlwidgets-1.6.4/htmlwidgets.js create mode 100644 reference/libs/rglWebGL-binding-1.2.8/rglWebGL.js create mode 100644 reference/libs/rglwidgetClass-1.2.8/animation.src.js create mode 100644 reference/libs/rglwidgetClass-1.2.8/axes.src.js create mode 100644 reference/libs/rglwidgetClass-1.2.8/buffer.src.js create mode 100644 reference/libs/rglwidgetClass-1.2.8/controls.src.js create mode 100644 reference/libs/rglwidgetClass-1.2.8/draw.src.js create mode 100644 reference/libs/rglwidgetClass-1.2.8/init.src.js create mode 100644 reference/libs/rglwidgetClass-1.2.8/mouse.src.js create mode 100644 reference/libs/rglwidgetClass-1.2.8/pieces.src.js create mode 100644 reference/libs/rglwidgetClass-1.2.8/pretty.src.js create mode 100644 reference/libs/rglwidgetClass-1.2.8/projection.src.js create mode 100644 reference/libs/rglwidgetClass-1.2.8/rgl.css create mode 100644 reference/libs/rglwidgetClass-1.2.8/rglClass.src.js create mode 100644 reference/libs/rglwidgetClass-1.2.8/rglTimer.src.js create mode 100644 reference/libs/rglwidgetClass-1.2.8/selection.src.js create mode 100644 reference/libs/rglwidgetClass-1.2.8/shaders.src.js create mode 100644 reference/libs/rglwidgetClass-1.2.8/shadersrc.src.js create mode 100644 reference/libs/rglwidgetClass-1.2.8/subscenes.src.js create mode 100644 reference/libs/rglwidgetClass-1.2.8/textures.src.js create mode 100644 reference/libs/rglwidgetClass-1.2.8/utils.src.js diff --git a/pkgdown.yml b/pkgdown.yml index 209bd26b0..f35218b31 100644 --- a/pkgdown.yml +++ b/pkgdown.yml @@ -1,9 +1,9 @@ pandoc: 2.19.2 pkgdown: 2.0.7.9000 -pkgdown_sha: 5a0a31f89d7c839c04a929844e4b7216196189ca +pkgdown_sha: 13d8d621105a3a527a05e46c6c805a6564c39791 articles: pavo: pavo.html -last_built: 2023-11-29T04:11Z +last_built: 2023-12-11T11:36Z urls: reference: http://pavo.colrverse.com/reference article: http://pavo.colrverse.com/articles diff --git a/reference/as.rimg.html b/reference/as.rimg.html index a3a73670b..88be6b0a9 100644 --- a/reference/as.rimg.html +++ b/reference/as.rimg.html @@ -118,12 +118,13 @@

Author

Examples


 # Generate some fake image data
-fake <- array(c(
-  as.matrix(rep(c(0.2, 0.4, 0.6), each = 250)),
-  as.matrix(rep(c(0.4, 0.7, 0.8), each = 250)),
-  as.matrix(rep(c(0.6, 0.1, 0.2), each = 250))
-),
-dim = c(750, 750, 3)
+fake <- array(
+  c(
+    as.matrix(rep(c(0.2, 0.4, 0.6), each = 250)),
+    as.matrix(rep(c(0.4, 0.7, 0.8), each = 250)),
+    as.matrix(rep(c(0.6, 0.1, 0.2), each = 250))
+  ),
+  dim = c(750, 750, 3)
 )
 
 # Inspect it
diff --git a/reference/jnd2xyz.html b/reference/jnd2xyz.html
index c664e14e8..91d44122a 100644
--- a/reference/jnd2xyz.html
+++ b/reference/jnd2xyz.html
@@ -153,7 +153,7 @@ 

Examples

data(flowers) # Estimate quantum catches visual phenotype of a Blue Tit -vis.flowers <- vismodel(flowers, visual = 'bluetit') +vis.flowers <- vismodel(flowers, visual = "bluetit") # Estimate noise-weighted colour distances between all flowers cd.flowers <- coldist(vis.flowers) diff --git a/reference/jndplot.html b/reference/jndplot.html index 1ef2aa048..7c26c54d2 100644 --- a/reference/jndplot.html +++ b/reference/jndplot.html @@ -175,7 +175,7 @@

Examples

data(flowers) # Estimate quantum catches visual phenotype of a Blue Tit -vis.flowers <- vismodel(flowers, visual = 'bluetit') +vis.flowers <- vismodel(flowers, visual = "bluetit") # Estimate noise-weighted colour distances between all flowers cd.flowers <- coldist(vis.flowers) diff --git a/reference/jndrot.html b/reference/jndrot.html index 71cd2983c..c505390a9 100644 --- a/reference/jndrot.html +++ b/reference/jndrot.html @@ -133,7 +133,7 @@

Examples

data(flowers) # Estimate quantum catches visual phenotype of a Blue Tit -vis.flowers <- vismodel(flowers, visual = 'bluetit') +vis.flowers <- vismodel(flowers, visual = "bluetit") # Estimate noise-weighted colour distances between all flowers cd.flowers <- coldist(vis.flowers) diff --git a/reference/libs/CanvasMatrix4-1.2.8/CanvasMatrix.src.js b/reference/libs/CanvasMatrix4-1.2.8/CanvasMatrix.src.js new file mode 100644 index 000000000..2bfbd588d --- /dev/null +++ b/reference/libs/CanvasMatrix4-1.2.8/CanvasMatrix.src.js @@ -0,0 +1,729 @@ +/* globals CanvasMatrix4: true */ +/* globals WebGLFloatArray */ +/* jshint eqeqeq: false */ +/* + * Copyright (C) 2009 Apple Inc. All Rights Reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + * Copyright (2016) Duncan Murdoch - fixed CanvasMatrix4.ortho, + * cleaned up. + */ +/* + CanvasMatrix4 class + + This class implements a 4x4 matrix. It has functions which + duplicate the functionality of the OpenGL matrix stack and + glut functions. + + IDL: + + [ + Constructor(in CanvasMatrix4 matrix), // copy passed matrix into new CanvasMatrix4 + Constructor(in sequence array) // create new CanvasMatrix4 with 16 floats (row major) + Constructor() // create new CanvasMatrix4 with identity matrix + ] + interface CanvasMatrix4 { + attribute float m11; + attribute float m12; + attribute float m13; + attribute float m14; + attribute float m21; + attribute float m22; + attribute float m23; + attribute float m24; + attribute float m31; + attribute float m32; + attribute float m33; + attribute float m34; + attribute float m41; + attribute float m42; + attribute float m43; + attribute float m44; + + void load(in CanvasMatrix4 matrix); // copy the values from the passed matrix + void load(in sequence array); // copy 16 floats into the matrix + sequence getAsArray(); // return the matrix as an array of 16 floats + WebGLFloatArray getAsCanvasFloatArray(); // return the matrix as a WebGLFloatArray with 16 values + void makeIdentity(); // replace the matrix with identity + void transpose(); // replace the matrix with its transpose + void invert(); // replace the matrix with its inverse + + void translate(in float x, in float y, in float z); // multiply the matrix by passed translation values on the right + void scale(in float x, in float y, in float z); // multiply the matrix by passed scale values on the right + void rotate(in float angle, // multiply the matrix by passed rotation values on the right + in float x, in float y, in float z); // (angle is in degrees) + void multRight(in CanvasMatrix matrix); // multiply the matrix by the passed matrix on the right + void multLeft(in CanvasMatrix matrix); // multiply the matrix by the passed matrix on the left + void ortho(in float left, in float right, // multiply the matrix by the passed ortho values on the right + in float bottom, in float top, + in float near, in float far); + void frustum(in float left, in float right, // multiply the matrix by the passed frustum values on the right + in float bottom, in float top, + in float near, in float far); + void perspective(in float fovy, in float aspect, // multiply the matrix by the passed perspective values on the right + in float zNear, in float zFar); + void lookat(in float eyex, in float eyey, in float eyez, // multiply the matrix by the passed lookat + in float ctrx, in float ctry, in float ctrz, // values on the right + in float upx, in float upy, in float upz); + } +*/ + +CanvasMatrix4 = function(m) +{ + if (typeof m == 'object') { + if ("length" in m && m.length >= 16) { + this.load(m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8], m[9], m[10], m[11], m[12], m[13], m[14], m[15]); + return; + } + else if (m instanceof CanvasMatrix4) { + this.load(m); + return; + } + } + this.makeIdentity(); +}; + +CanvasMatrix4.prototype.load = function() +{ + if (arguments.length == 1 && typeof arguments[0] == 'object') { + var matrix = arguments[0]; + + if ("length" in matrix && matrix.length == 16) { + this.m11 = matrix[0]; + this.m12 = matrix[1]; + this.m13 = matrix[2]; + this.m14 = matrix[3]; + + this.m21 = matrix[4]; + this.m22 = matrix[5]; + this.m23 = matrix[6]; + this.m24 = matrix[7]; + + this.m31 = matrix[8]; + this.m32 = matrix[9]; + this.m33 = matrix[10]; + this.m34 = matrix[11]; + + this.m41 = matrix[12]; + this.m42 = matrix[13]; + this.m43 = matrix[14]; + this.m44 = matrix[15]; + return; + } + + if (arguments[0] instanceof CanvasMatrix4) { + + this.m11 = matrix.m11; + this.m12 = matrix.m12; + this.m13 = matrix.m13; + this.m14 = matrix.m14; + + this.m21 = matrix.m21; + this.m22 = matrix.m22; + this.m23 = matrix.m23; + this.m24 = matrix.m24; + + this.m31 = matrix.m31; + this.m32 = matrix.m32; + this.m33 = matrix.m33; + this.m34 = matrix.m34; + + this.m41 = matrix.m41; + this.m42 = matrix.m42; + this.m43 = matrix.m43; + this.m44 = matrix.m44; + return; + } + } + + this.makeIdentity(); +}; + +CanvasMatrix4.prototype.getAsArray = function() +{ + return [ + this.m11, this.m12, this.m13, this.m14, + this.m21, this.m22, this.m23, this.m24, + this.m31, this.m32, this.m33, this.m34, + this.m41, this.m42, this.m43, this.m44 + ]; +}; + +CanvasMatrix4.prototype.getAsWebGLFloatArray = function() +{ + return new WebGLFloatArray(this.getAsArray()); +}; + +CanvasMatrix4.prototype.makeIdentity = function() +{ + this.m11 = 1; + this.m12 = 0; + this.m13 = 0; + this.m14 = 0; + + this.m21 = 0; + this.m22 = 1; + this.m23 = 0; + this.m24 = 0; + + this.m31 = 0; + this.m32 = 0; + this.m33 = 1; + this.m34 = 0; + + this.m41 = 0; + this.m42 = 0; + this.m43 = 0; + this.m44 = 1; +}; + +CanvasMatrix4.prototype.transpose = function() +{ + var tmp = this.m12; + this.m12 = this.m21; + this.m21 = tmp; + + tmp = this.m13; + this.m13 = this.m31; + this.m31 = tmp; + + tmp = this.m14; + this.m14 = this.m41; + this.m41 = tmp; + + tmp = this.m23; + this.m23 = this.m32; + this.m32 = tmp; + + tmp = this.m24; + this.m24 = this.m42; + this.m42 = tmp; + + tmp = this.m34; + this.m34 = this.m43; + this.m43 = tmp; +}; + +CanvasMatrix4.prototype.invert = function() +{ + // Calculate the 4x4 determinant + // If the determinant is zero, + // then the inverse matrix is not unique. + var det = this._determinant4x4(); + + if (Math.abs(det) < 1e-8) + return null; + + this._makeAdjoint(); + + // Scale the adjoint matrix to get the inverse + this.m11 /= det; + this.m12 /= det; + this.m13 /= det; + this.m14 /= det; + + this.m21 /= det; + this.m22 /= det; + this.m23 /= det; + this.m24 /= det; + + this.m31 /= det; + this.m32 /= det; + this.m33 /= det; + this.m34 /= det; + + this.m41 /= det; + this.m42 /= det; + this.m43 /= det; + this.m44 /= det; +}; + +CanvasMatrix4.prototype.translate = function(x,y,z) +{ + if (x === undefined) + x = 0; + if (y === undefined) + y = 0; + if (z === undefined) + z = 0; + + var matrix = new CanvasMatrix4(); + matrix.m41 = x; + matrix.m42 = y; + matrix.m43 = z; + + this.multRight(matrix); +}; + +CanvasMatrix4.prototype.scale = function(x,y,z) +{ + if (x === undefined) + x = 1; + if (z === undefined) { + if (y === undefined) { + y = x; + z = x; + } + else + z = 1; + } + else if (y === undefined) + y = x; + + var matrix = new CanvasMatrix4(); + matrix.m11 = x; + matrix.m22 = y; + matrix.m33 = z; + + this.multRight(matrix); +}; + +CanvasMatrix4.prototype.rotate = function(angle,x,y,z) +{ + // angles are in degrees. Switch to radians + angle = angle / 180 * Math.PI; + + angle /= 2; + var sinA = Math.sin(angle); + var cosA = Math.cos(angle); + var sinA2 = sinA * sinA; + + // normalize + var length = Math.sqrt(x * x + y * y + z * z); + if (length === 0) { + // bad vector, just use something reasonable + x = 0; + y = 0; + z = 1; + } else if (length != 1) { + x /= length; + y /= length; + z /= length; + } + + var mat = new CanvasMatrix4(); + + // optimize case where axis is along major axis + if (x == 1 && y === 0 && z === 0) { + mat.m11 = 1; + mat.m12 = 0; + mat.m13 = 0; + mat.m21 = 0; + mat.m22 = 1 - 2 * sinA2; + mat.m23 = 2 * sinA * cosA; + mat.m31 = 0; + mat.m32 = -2 * sinA * cosA; + mat.m33 = 1 - 2 * sinA2; + mat.m14 = mat.m24 = mat.m34 = 0; + mat.m41 = mat.m42 = mat.m43 = 0; + mat.m44 = 1; + } else if (x === 0 && y == 1 && z === 0) { + mat.m11 = 1 - 2 * sinA2; + mat.m12 = 0; + mat.m13 = -2 * sinA * cosA; + mat.m21 = 0; + mat.m22 = 1; + mat.m23 = 0; + mat.m31 = 2 * sinA * cosA; + mat.m32 = 0; + mat.m33 = 1 - 2 * sinA2; + mat.m14 = mat.m24 = mat.m34 = 0; + mat.m41 = mat.m42 = mat.m43 = 0; + mat.m44 = 1; + } else if (x === 0 && y === 0 && z == 1) { + mat.m11 = 1 - 2 * sinA2; + mat.m12 = 2 * sinA * cosA; + mat.m13 = 0; + mat.m21 = -2 * sinA * cosA; + mat.m22 = 1 - 2 * sinA2; + mat.m23 = 0; + mat.m31 = 0; + mat.m32 = 0; + mat.m33 = 1; + mat.m14 = mat.m24 = mat.m34 = 0; + mat.m41 = mat.m42 = mat.m43 = 0; + mat.m44 = 1; + } else { + var x2 = x*x; + var y2 = y*y; + var z2 = z*z; + + mat.m11 = 1 - 2 * (y2 + z2) * sinA2; + mat.m12 = 2 * (x * y * sinA2 + z * sinA * cosA); + mat.m13 = 2 * (x * z * sinA2 - y * sinA * cosA); + mat.m21 = 2 * (y * x * sinA2 - z * sinA * cosA); + mat.m22 = 1 - 2 * (z2 + x2) * sinA2; + mat.m23 = 2 * (y * z * sinA2 + x * sinA * cosA); + mat.m31 = 2 * (z * x * sinA2 + y * sinA * cosA); + mat.m32 = 2 * (z * y * sinA2 - x * sinA * cosA); + mat.m33 = 1 - 2 * (x2 + y2) * sinA2; + mat.m14 = mat.m24 = mat.m34 = 0; + mat.m41 = mat.m42 = mat.m43 = 0; + mat.m44 = 1; + } + this.multRight(mat); +}; + +CanvasMatrix4.prototype.multRight = function(mat) +{ + var m11 = (this.m11 * mat.m11 + this.m12 * mat.m21 + + this.m13 * mat.m31 + this.m14 * mat.m41); + var m12 = (this.m11 * mat.m12 + this.m12 * mat.m22 + + this.m13 * mat.m32 + this.m14 * mat.m42); + var m13 = (this.m11 * mat.m13 + this.m12 * mat.m23 + + this.m13 * mat.m33 + this.m14 * mat.m43); + var m14 = (this.m11 * mat.m14 + this.m12 * mat.m24 + + this.m13 * mat.m34 + this.m14 * mat.m44); + + var m21 = (this.m21 * mat.m11 + this.m22 * mat.m21 + + this.m23 * mat.m31 + this.m24 * mat.m41); + var m22 = (this.m21 * mat.m12 + this.m22 * mat.m22 + + this.m23 * mat.m32 + this.m24 * mat.m42); + var m23 = (this.m21 * mat.m13 + this.m22 * mat.m23 + + this.m23 * mat.m33 + this.m24 * mat.m43); + var m24 = (this.m21 * mat.m14 + this.m22 * mat.m24 + + this.m23 * mat.m34 + this.m24 * mat.m44); + + var m31 = (this.m31 * mat.m11 + this.m32 * mat.m21 + + this.m33 * mat.m31 + this.m34 * mat.m41); + var m32 = (this.m31 * mat.m12 + this.m32 * mat.m22 + + this.m33 * mat.m32 + this.m34 * mat.m42); + var m33 = (this.m31 * mat.m13 + this.m32 * mat.m23 + + this.m33 * mat.m33 + this.m34 * mat.m43); + var m34 = (this.m31 * mat.m14 + this.m32 * mat.m24 + + this.m33 * mat.m34 + this.m34 * mat.m44); + + var m41 = (this.m41 * mat.m11 + this.m42 * mat.m21 + + this.m43 * mat.m31 + this.m44 * mat.m41); + var m42 = (this.m41 * mat.m12 + this.m42 * mat.m22 + + this.m43 * mat.m32 + this.m44 * mat.m42); + var m43 = (this.m41 * mat.m13 + this.m42 * mat.m23 + + this.m43 * mat.m33 + this.m44 * mat.m43); + var m44 = (this.m41 * mat.m14 + this.m42 * mat.m24 + + this.m43 * mat.m34 + this.m44 * mat.m44); + + this.m11 = m11; + this.m12 = m12; + this.m13 = m13; + this.m14 = m14; + + this.m21 = m21; + this.m22 = m22; + this.m23 = m23; + this.m24 = m24; + + this.m31 = m31; + this.m32 = m32; + this.m33 = m33; + this.m34 = m34; + + this.m41 = m41; + this.m42 = m42; + this.m43 = m43; + this.m44 = m44; +}; + +CanvasMatrix4.prototype.multLeft = function(mat) +{ + var m11 = (mat.m11 * this.m11 + mat.m12 * this.m21 + + mat.m13 * this.m31 + mat.m14 * this.m41); + var m12 = (mat.m11 * this.m12 + mat.m12 * this.m22 + + mat.m13 * this.m32 + mat.m14 * this.m42); + var m13 = (mat.m11 * this.m13 + mat.m12 * this.m23 + + mat.m13 * this.m33 + mat.m14 * this.m43); + var m14 = (mat.m11 * this.m14 + mat.m12 * this.m24 + + mat.m13 * this.m34 + mat.m14 * this.m44); + + var m21 = (mat.m21 * this.m11 + mat.m22 * this.m21 + + mat.m23 * this.m31 + mat.m24 * this.m41); + var m22 = (mat.m21 * this.m12 + mat.m22 * this.m22 + + mat.m23 * this.m32 + mat.m24 * this.m42); + var m23 = (mat.m21 * this.m13 + mat.m22 * this.m23 + + mat.m23 * this.m33 + mat.m24 * this.m43); + var m24 = (mat.m21 * this.m14 + mat.m22 * this.m24 + + mat.m23 * this.m34 + mat.m24 * this.m44); + + var m31 = (mat.m31 * this.m11 + mat.m32 * this.m21 + + mat.m33 * this.m31 + mat.m34 * this.m41); + var m32 = (mat.m31 * this.m12 + mat.m32 * this.m22 + + mat.m33 * this.m32 + mat.m34 * this.m42); + var m33 = (mat.m31 * this.m13 + mat.m32 * this.m23 + + mat.m33 * this.m33 + mat.m34 * this.m43); + var m34 = (mat.m31 * this.m14 + mat.m32 * this.m24 + + mat.m33 * this.m34 + mat.m34 * this.m44); + + var m41 = (mat.m41 * this.m11 + mat.m42 * this.m21 + + mat.m43 * this.m31 + mat.m44 * this.m41); + var m42 = (mat.m41 * this.m12 + mat.m42 * this.m22 + + mat.m43 * this.m32 + mat.m44 * this.m42); + var m43 = (mat.m41 * this.m13 + mat.m42 * this.m23 + + mat.m43 * this.m33 + mat.m44 * this.m43); + var m44 = (mat.m41 * this.m14 + mat.m42 * this.m24 + + mat.m43 * this.m34 + mat.m44 * this.m44); + + this.m11 = m11; + this.m12 = m12; + this.m13 = m13; + this.m14 = m14; + + this.m21 = m21; + this.m22 = m22; + this.m23 = m23; + this.m24 = m24; + + this.m31 = m31; + this.m32 = m32; + this.m33 = m33; + this.m34 = m34; + + this.m41 = m41; + this.m42 = m42; + this.m43 = m43; + this.m44 = m44; +}; + +CanvasMatrix4.prototype.ortho = function(left, right, bottom, top, near, far) +{ + var tx = (left + right) / (left - right); + var ty = (top + bottom) / (bottom - top); + var tz = (far + near) / (near - far); + + var matrix = new CanvasMatrix4(); + matrix.m11 = 2 / (right - left); + matrix.m12 = 0; + matrix.m13 = 0; + matrix.m14 = 0; + matrix.m21 = 0; + matrix.m22 = 2 / (top - bottom); + matrix.m23 = 0; + matrix.m24 = 0; + matrix.m31 = 0; + matrix.m32 = 0; + matrix.m33 = -2 / (far - near); + matrix.m34 = 0; + matrix.m41 = tx; + matrix.m42 = ty; + matrix.m43 = tz; + matrix.m44 = 1; + + this.multRight(matrix); +}; + +CanvasMatrix4.prototype.frustum = function(left, right, bottom, top, near, far) +{ + var matrix = new CanvasMatrix4(); + var A = (right + left) / (right - left); + var B = (top + bottom) / (top - bottom); + var C = -(far + near) / (far - near); + var D = -(2 * far * near) / (far - near); + + matrix.m11 = (2 * near) / (right - left); + matrix.m12 = 0; + matrix.m13 = 0; + matrix.m14 = 0; + + matrix.m21 = 0; + matrix.m22 = 2 * near / (top - bottom); + matrix.m23 = 0; + matrix.m24 = 0; + + matrix.m31 = A; + matrix.m32 = B; + matrix.m33 = C; + matrix.m34 = -1; + + matrix.m41 = 0; + matrix.m42 = 0; + matrix.m43 = D; + matrix.m44 = 0; + + this.multRight(matrix); +}; + +CanvasMatrix4.prototype.perspective = function(fovy, aspect, zNear, zFar) +{ + var top = Math.tan(fovy * Math.PI / 360) * zNear; + var bottom = -top; + var left = aspect * bottom; + var right = aspect * top; + this.frustum(left, right, bottom, top, zNear, zFar); +}; + +CanvasMatrix4.prototype.lookat = function(eyex, eyey, eyez, centerx, centery, centerz, upx, upy, upz) +{ + var matrix = new CanvasMatrix4(), + xx, xy, xz; + + // Make rotation matrix + + // Z vector + var zx = eyex - centerx; + var zy = eyey - centery; + var zz = eyez - centerz; + var mag = Math.sqrt(zx * zx + zy * zy + zz * zz); + if (mag) { + zx /= mag; + zy /= mag; + zz /= mag; + } + + // Y vector + var yx = upx; + var yy = upy; + var yz = upz; + + // X vector = Y cross Z + xx = yy * zz - yz * zy; + xy = -yx * zz + yz * zx; + xz = yx * zy - yy * zx; + + // Recompute Y = Z cross X + yx = zy * xz - zz * xy; + yy = -zx * xz + zz * xx; + yx = zx * xy - zy * xx; + + // cross product gives area of parallelogram, which is < 1.0 for + // non-perpendicular unit-length vectors; so normalize x, y here + + mag = Math.sqrt(xx * xx + xy * xy + xz * xz); + if (mag) { + xx /= mag; + xy /= mag; + xz /= mag; + } + + mag = Math.sqrt(yx * yx + yy * yy + yz * yz); + if (mag) { + yx /= mag; + yy /= mag; + yz /= mag; + } + + matrix.m11 = xx; + matrix.m12 = xy; + matrix.m13 = xz; + matrix.m14 = 0; + + matrix.m21 = yx; + matrix.m22 = yy; + matrix.m23 = yz; + matrix.m24 = 0; + + matrix.m31 = zx; + matrix.m32 = zy; + matrix.m33 = zz; + matrix.m34 = 0; + + matrix.m41 = 0; + matrix.m42 = 0; + matrix.m43 = 0; + matrix.m44 = 1; + matrix.translate(-eyex, -eyey, -eyez); + + this.multRight(matrix); +}; + +// Support functions +CanvasMatrix4.prototype._determinant2x2 = function(a, b, c, d) +{ + return a * d - b * c; +}; + +CanvasMatrix4.prototype._determinant3x3 = function(a1, a2, a3, b1, b2, b3, c1, c2, c3) +{ + return a1 * this._determinant2x2(b2, b3, c2, c3) - + b1 * this._determinant2x2(a2, a3, c2, c3) + + c1 * this._determinant2x2(a2, a3, b2, b3); +}; + +CanvasMatrix4.prototype._determinant4x4 = function() +{ + var a1 = this.m11; + var b1 = this.m12; + var c1 = this.m13; + var d1 = this.m14; + + var a2 = this.m21; + var b2 = this.m22; + var c2 = this.m23; + var d2 = this.m24; + + var a3 = this.m31; + var b3 = this.m32; + var c3 = this.m33; + var d3 = this.m34; + + var a4 = this.m41; + var b4 = this.m42; + var c4 = this.m43; + var d4 = this.m44; + + return a1 * this._determinant3x3(b2, b3, b4, c2, c3, c4, d2, d3, d4) - + b1 * this._determinant3x3(a2, a3, a4, c2, c3, c4, d2, d3, d4) + + c1 * this._determinant3x3(a2, a3, a4, b2, b3, b4, d2, d3, d4) - + d1 * this._determinant3x3(a2, a3, a4, b2, b3, b4, c2, c3, c4); +}; + +CanvasMatrix4.prototype._makeAdjoint = function() +{ + var a1 = this.m11; + var b1 = this.m12; + var c1 = this.m13; + var d1 = this.m14; + + var a2 = this.m21; + var b2 = this.m22; + var c2 = this.m23; + var d2 = this.m24; + + var a3 = this.m31; + var b3 = this.m32; + var c3 = this.m33; + var d3 = this.m34; + + var a4 = this.m41; + var b4 = this.m42; + var c4 = this.m43; + var d4 = this.m44; + + // Row column labeling reversed since we transpose rows & columns + this.m11 = this._determinant3x3(b2, b3, b4, c2, c3, c4, d2, d3, d4); + this.m21 = - this._determinant3x3(a2, a3, a4, c2, c3, c4, d2, d3, d4); + this.m31 = this._determinant3x3(a2, a3, a4, b2, b3, b4, d2, d3, d4); + this.m41 = - this._determinant3x3(a2, a3, a4, b2, b3, b4, c2, c3, c4); + + this.m12 = - this._determinant3x3(b1, b3, b4, c1, c3, c4, d1, d3, d4); + this.m22 = this._determinant3x3(a1, a3, a4, c1, c3, c4, d1, d3, d4); + this.m32 = - this._determinant3x3(a1, a3, a4, b1, b3, b4, d1, d3, d4); + this.m42 = this._determinant3x3(a1, a3, a4, b1, b3, b4, c1, c3, c4); + + this.m13 = this._determinant3x3(b1, b2, b4, c1, c2, c4, d1, d2, d4); + this.m23 = - this._determinant3x3(a1, a2, a4, c1, c2, c4, d1, d2, d4); + this.m33 = this._determinant3x3(a1, a2, a4, b1, b2, b4, d1, d2, d4); + this.m43 = - this._determinant3x3(a1, a2, a4, b1, b2, b4, c1, c2, c4); + + this.m14 = - this._determinant3x3(b1, b2, b3, c1, c2, c3, d1, d2, d3); + this.m24 = this._determinant3x3(a1, a2, a3, c1, c2, c3, d1, d2, d3); + this.m34 = - this._determinant3x3(a1, a2, a3, b1, b2, b3, d1, d2, d3); + this.m44 = this._determinant3x3(a1, a2, a3, b1, b2, b3, c1, c2, c3); +}; diff --git a/reference/libs/htmlwidgets-1.6.4/htmlwidgets.js b/reference/libs/htmlwidgets-1.6.4/htmlwidgets.js new file mode 100644 index 000000000..1067d029f --- /dev/null +++ b/reference/libs/htmlwidgets-1.6.4/htmlwidgets.js @@ -0,0 +1,901 @@ +(function() { + // If window.HTMLWidgets is already defined, then use it; otherwise create a + // new object. This allows preceding code to set options that affect the + // initialization process (though none currently exist). + window.HTMLWidgets = window.HTMLWidgets || {}; + + // See if we're running in a viewer pane. If not, we're in a web browser. + var viewerMode = window.HTMLWidgets.viewerMode = + /\bviewer_pane=1\b/.test(window.location); + + // See if we're running in Shiny mode. If not, it's a static document. + // Note that static widgets can appear in both Shiny and static modes, but + // obviously, Shiny widgets can only appear in Shiny apps/documents. + var shinyMode = window.HTMLWidgets.shinyMode = + typeof(window.Shiny) !== "undefined" && !!window.Shiny.outputBindings; + + // We can't count on jQuery being available, so we implement our own + // version if necessary. + function querySelectorAll(scope, selector) { + if (typeof(jQuery) !== "undefined" && scope instanceof jQuery) { + return scope.find(selector); + } + if (scope.querySelectorAll) { + return scope.querySelectorAll(selector); + } + } + + function asArray(value) { + if (value === null) + return []; + if ($.isArray(value)) + return value; + return [value]; + } + + // Implement jQuery's extend + function extend(target /*, ... */) { + if (arguments.length == 1) { + return target; + } + for (var i = 1; i < arguments.length; i++) { + var source = arguments[i]; + for (var prop in source) { + if (source.hasOwnProperty(prop)) { + target[prop] = source[prop]; + } + } + } + return target; + } + + // IE8 doesn't support Array.forEach. + function forEach(values, callback, thisArg) { + if (values.forEach) { + values.forEach(callback, thisArg); + } else { + for (var i = 0; i < values.length; i++) { + callback.call(thisArg, values[i], i, values); + } + } + } + + // Replaces the specified method with the return value of funcSource. + // + // Note that funcSource should not BE the new method, it should be a function + // that RETURNS the new method. funcSource receives a single argument that is + // the overridden method, it can be called from the new method. The overridden + // method can be called like a regular function, it has the target permanently + // bound to it so "this" will work correctly. + function overrideMethod(target, methodName, funcSource) { + var superFunc = target[methodName] || function() {}; + var superFuncBound = function() { + return superFunc.apply(target, arguments); + }; + target[methodName] = funcSource(superFuncBound); + } + + // Add a method to delegator that, when invoked, calls + // delegatee.methodName. If there is no such method on + // the delegatee, but there was one on delegator before + // delegateMethod was called, then the original version + // is invoked instead. + // For example: + // + // var a = { + // method1: function() { console.log('a1'); } + // method2: function() { console.log('a2'); } + // }; + // var b = { + // method1: function() { console.log('b1'); } + // }; + // delegateMethod(a, b, "method1"); + // delegateMethod(a, b, "method2"); + // a.method1(); + // a.method2(); + // + // The output would be "b1", "a2". + function delegateMethod(delegator, delegatee, methodName) { + var inherited = delegator[methodName]; + delegator[methodName] = function() { + var target = delegatee; + var method = delegatee[methodName]; + + // The method doesn't exist on the delegatee. Instead, + // call the method on the delegator, if it exists. + if (!method) { + target = delegator; + method = inherited; + } + + if (method) { + return method.apply(target, arguments); + } + }; + } + + // Implement a vague facsimilie of jQuery's data method + function elementData(el, name, value) { + if (arguments.length == 2) { + return el["htmlwidget_data_" + name]; + } else if (arguments.length == 3) { + el["htmlwidget_data_" + name] = value; + return el; + } else { + throw new Error("Wrong number of arguments for elementData: " + + arguments.length); + } + } + + // http://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex + function escapeRegExp(str) { + return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); + } + + function hasClass(el, className) { + var re = new RegExp("\\b" + escapeRegExp(className) + "\\b"); + return re.test(el.className); + } + + // elements - array (or array-like object) of HTML elements + // className - class name to test for + // include - if true, only return elements with given className; + // if false, only return elements *without* given className + function filterByClass(elements, className, include) { + var results = []; + for (var i = 0; i < elements.length; i++) { + if (hasClass(elements[i], className) == include) + results.push(elements[i]); + } + return results; + } + + function on(obj, eventName, func) { + if (obj.addEventListener) { + obj.addEventListener(eventName, func, false); + } else if (obj.attachEvent) { + obj.attachEvent(eventName, func); + } + } + + function off(obj, eventName, func) { + if (obj.removeEventListener) + obj.removeEventListener(eventName, func, false); + else if (obj.detachEvent) { + obj.detachEvent(eventName, func); + } + } + + // Translate array of values to top/right/bottom/left, as usual with + // the "padding" CSS property + // https://developer.mozilla.org/en-US/docs/Web/CSS/padding + function unpackPadding(value) { + if (typeof(value) === "number") + value = [value]; + if (value.length === 1) { + return {top: value[0], right: value[0], bottom: value[0], left: value[0]}; + } + if (value.length === 2) { + return {top: value[0], right: value[1], bottom: value[0], left: value[1]}; + } + if (value.length === 3) { + return {top: value[0], right: value[1], bottom: value[2], left: value[1]}; + } + if (value.length === 4) { + return {top: value[0], right: value[1], bottom: value[2], left: value[3]}; + } + } + + // Convert an unpacked padding object to a CSS value + function paddingToCss(paddingObj) { + return paddingObj.top + "px " + paddingObj.right + "px " + paddingObj.bottom + "px " + paddingObj.left + "px"; + } + + // Makes a number suitable for CSS + function px(x) { + if (typeof(x) === "number") + return x + "px"; + else + return x; + } + + // Retrieves runtime widget sizing information for an element. + // The return value is either null, or an object with fill, padding, + // defaultWidth, defaultHeight fields. + function sizingPolicy(el) { + var sizingEl = document.querySelector("script[data-for='" + el.id + "'][type='application/htmlwidget-sizing']"); + if (!sizingEl) + return null; + var sp = JSON.parse(sizingEl.textContent || sizingEl.text || "{}"); + if (viewerMode) { + return sp.viewer; + } else { + return sp.browser; + } + } + + // @param tasks Array of strings (or falsy value, in which case no-op). + // Each element must be a valid JavaScript expression that yields a + // function. Or, can be an array of objects with "code" and "data" + // properties; in this case, the "code" property should be a string + // of JS that's an expr that yields a function, and "data" should be + // an object that will be added as an additional argument when that + // function is called. + // @param target The object that will be "this" for each function + // execution. + // @param args Array of arguments to be passed to the functions. (The + // same arguments will be passed to all functions.) + function evalAndRun(tasks, target, args) { + if (tasks) { + forEach(tasks, function(task) { + var theseArgs = args; + if (typeof(task) === "object") { + theseArgs = theseArgs.concat([task.data]); + task = task.code; + } + var taskFunc = tryEval(task); + if (typeof(taskFunc) !== "function") { + throw new Error("Task must be a function! Source:\n" + task); + } + taskFunc.apply(target, theseArgs); + }); + } + } + + // Attempt eval() both with and without enclosing in parentheses. + // Note that enclosing coerces a function declaration into + // an expression that eval() can parse + // (otherwise, a SyntaxError is thrown) + function tryEval(code) { + var result = null; + try { + result = eval("(" + code + ")"); + } catch(error) { + if (!(error instanceof SyntaxError)) { + throw error; + } + try { + result = eval(code); + } catch(e) { + if (e instanceof SyntaxError) { + throw error; + } else { + throw e; + } + } + } + return result; + } + + function initSizing(el) { + var sizing = sizingPolicy(el); + if (!sizing) + return; + + var cel = document.getElementById("htmlwidget_container"); + if (!cel) + return; + + if (typeof(sizing.padding) !== "undefined") { + document.body.style.margin = "0"; + document.body.style.padding = paddingToCss(unpackPadding(sizing.padding)); + } + + if (sizing.fill) { + document.body.style.overflow = "hidden"; + document.body.style.width = "100%"; + document.body.style.height = "100%"; + document.documentElement.style.width = "100%"; + document.documentElement.style.height = "100%"; + cel.style.position = "absolute"; + var pad = unpackPadding(sizing.padding); + cel.style.top = pad.top + "px"; + cel.style.right = pad.right + "px"; + cel.style.bottom = pad.bottom + "px"; + cel.style.left = pad.left + "px"; + el.style.width = "100%"; + el.style.height = "100%"; + + return { + getWidth: function() { return cel.getBoundingClientRect().width; }, + getHeight: function() { return cel.getBoundingClientRect().height; } + }; + + } else { + el.style.width = px(sizing.width); + el.style.height = px(sizing.height); + + return { + getWidth: function() { return cel.getBoundingClientRect().width; }, + getHeight: function() { return cel.getBoundingClientRect().height; } + }; + } + } + + // Default implementations for methods + var defaults = { + find: function(scope) { + return querySelectorAll(scope, "." + this.name); + }, + renderError: function(el, err) { + var $el = $(el); + + this.clearError(el); + + // Add all these error classes, as Shiny does + var errClass = "shiny-output-error"; + if (err.type !== null) { + // use the classes of the error condition as CSS class names + errClass = errClass + " " + $.map(asArray(err.type), function(type) { + return errClass + "-" + type; + }).join(" "); + } + errClass = errClass + " htmlwidgets-error"; + + // Is el inline or block? If inline or inline-block, just display:none it + // and add an inline error. + var display = $el.css("display"); + $el.data("restore-display-mode", display); + + if (display === "inline" || display === "inline-block") { + $el.hide(); + if (err.message !== "") { + var errorSpan = $("").addClass(errClass); + errorSpan.text(err.message); + $el.after(errorSpan); + } + } else if (display === "block") { + // If block, add an error just after the el, set visibility:none on the + // el, and position the error to be on top of the el. + // Mark it with a unique ID and CSS class so we can remove it later. + $el.css("visibility", "hidden"); + if (err.message !== "") { + var errorDiv = $("
").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/reference/libs/rglWebGL-binding-1.2.8/rglWebGL.js b/reference/libs/rglWebGL-binding-1.2.8/rglWebGL.js new file mode 100644 index 000000000..52c8fe649 --- /dev/null +++ b/reference/libs/rglWebGL-binding-1.2.8/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/reference/libs/rglwidgetClass-1.2.8/animation.src.js b/reference/libs/rglwidgetClass-1.2.8/animation.src.js new file mode 100644 index 000000000..cdca347d5 --- /dev/null +++ b/reference/libs/rglwidgetClass-1.2.8/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/reference/libs/rglwidgetClass-1.2.8/axes.src.js b/reference/libs/rglwidgetClass-1.2.8/axes.src.js new file mode 100644 index 000000000..485fa13c5 --- /dev/null +++ b/reference/libs/rglwidgetClass-1.2.8/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/reference/libs/rglwidgetClass-1.2.8/buffer.src.js b/reference/libs/rglwidgetClass-1.2.8/buffer.src.js new file mode 100644 index 000000000..13d2e1aee --- /dev/null +++ b/reference/libs/rglwidgetClass-1.2.8/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/reference/libs/rglwidgetClass-1.2.8/controls.src.js b/reference/libs/rglwidgetClass-1.2.8/controls.src.js new file mode 100644 index 000000000..ffc7be8f6 --- /dev/null +++ b/reference/libs/rglwidgetClass-1.2.8/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/reference/libs/rglwidgetClass-1.2.8/draw.src.js b/reference/libs/rglwidgetClass-1.2.8/draw.src.js new file mode 100644 index 000000000..90ce52e4f --- /dev/null +++ b/reference/libs/rglwidgetClass-1.2.8/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 (this.opaquePass && (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/reference/libs/rglwidgetClass-1.2.8/init.src.js b/reference/libs/rglwidgetClass-1.2.8/init.src.js new file mode 100644 index 000000000..ff8986965 --- /dev/null +++ b/reference/libs/rglwidgetClass-1.2.8/init.src.js @@ -0,0 +1,1318 @@ + /** + * 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, result = {}; + + for (i = 0; i <= sections; i++) { + phi.push(i/sections - 0.5); + } + + for (j = 0; j <= segments; j++) { + theta.push(2*j/segments); + for (i = 0; i <= sections; 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]); + // console.log("xyzst="+v[v.length-1]); + } + } + result.values = new Float32Array(rglwidgetClass.flatten(v)); + result.vertexCount = v.length; + + for (j = 0; j < segments; j++) { + for (i = 0; i < sections; i++) { + ind = i + (sections + 1)*j; + if (i > 0) // Not south pole + it.push([ind, + ind + sections + 1, + ind + 1]); + if (i < sections - 1) // Not north pole + it.push([ind + sections + 1, + ind + sections + 2, + ind + 1]); + } + } + 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") + fnew = []; + 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, + labelid = this.el.getAttribute("aria-labelledby"); + newcanvas.width = this.el.width; + newcanvas.height = this.el.height; + newcanvas.setAttribute("aria-labelledby", + labelid); + + if (typeof this.scene.altText !== "undefined") { + // We're in Shiny, so alter the label + var label = document.getElementById(labelid); + if (label) + label.innerHTML = this.scene.altText; + } + 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/reference/libs/rglwidgetClass-1.2.8/mouse.src.js b/reference/libs/rglwidgetClass-1.2.8/mouse.src.js new file mode 100644 index 000000000..ca992f83d --- /dev/null +++ b/reference/libs/rglwidgetClass-1.2.8/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/reference/libs/rglwidgetClass-1.2.8/pieces.src.js b/reference/libs/rglwidgetClass-1.2.8/pieces.src.js new file mode 100644 index 000000000..2ac8c91d8 --- /dev/null +++ b/reference/libs/rglwidgetClass-1.2.8/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/reference/libs/rglwidgetClass-1.2.8/pretty.src.js b/reference/libs/rglwidgetClass-1.2.8/pretty.src.js new file mode 100644 index 000000000..5f9145aa5 --- /dev/null +++ b/reference/libs/rglwidgetClass-1.2.8/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/reference/libs/rglwidgetClass-1.2.8/projection.src.js b/reference/libs/rglwidgetClass-1.2.8/projection.src.js new file mode 100644 index 000000000..4fc59c799 --- /dev/null +++ b/reference/libs/rglwidgetClass-1.2.8/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/reference/libs/rglwidgetClass-1.2.8/rgl.css b/reference/libs/rglwidgetClass-1.2.8/rgl.css new file mode 100644 index 000000000..b22aaf656 --- /dev/null +++ b/reference/libs/rglwidgetClass-1.2.8/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/reference/libs/rglwidgetClass-1.2.8/rglClass.src.js b/reference/libs/rglwidgetClass-1.2.8/rglClass.src.js new file mode 100644 index 000000000..475300ee3 --- /dev/null +++ b/reference/libs/rglwidgetClass-1.2.8/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/reference/libs/rglwidgetClass-1.2.8/rglTimer.src.js b/reference/libs/rglwidgetClass-1.2.8/rglTimer.src.js new file mode 100644 index 000000000..109155697 --- /dev/null +++ b/reference/libs/rglwidgetClass-1.2.8/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/reference/libs/rglwidgetClass-1.2.8/selection.src.js b/reference/libs/rglwidgetClass-1.2.8/selection.src.js new file mode 100644 index 000000000..a45e09e27 --- /dev/null +++ b/reference/libs/rglwidgetClass-1.2.8/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/reference/libs/rglwidgetClass-1.2.8/shaders.src.js b/reference/libs/rglwidgetClass-1.2.8/shaders.src.js new file mode 100644 index 000000000..e6466787d --- /dev/null +++ b/reference/libs/rglwidgetClass-1.2.8/shaders.src.js @@ -0,0 +1,183 @@ + /** + * 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 = !rglwidgetClass.isSet(flags, rglwidgetClass.f_sprites_3d) && + (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, + texmode, texenvmap, 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"; + defines = defines + "#define TEXMODE_" + texmode + "\n"; + if (texenvmap) + defines = defines + "#define USE_ENVMAP 1\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, "texmode"), + this.getMaterial(obj, "texenvmap"), + this.getMaterial(obj, "point_antialias"), + obj.defFlags + ); + + if (typeof vertex === "undefined") + vertex = rglwidgetClass.rgl_vertex_shader(); + + if (typeof fragment === "undefined") + fragment = rglwidgetClass.rgl_fragment_shader(); + +// 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/reference/libs/rglwidgetClass-1.2.8/shadersrc.src.js b/reference/libs/rglwidgetClass-1.2.8/shadersrc.src.js new file mode 100644 index 000000000..5dc11e941 --- /dev/null +++ b/reference/libs/rglwidgetClass-1.2.8/shadersrc.src.js @@ -0,0 +1,445 @@ +rglwidgetClass.rgl_vertex_shader = function() { +return "#line 2 1\n"+ +"// File 1 is the vertex shader\n"+ +"#ifdef GL_ES\n"+ +"#ifdef GL_FRAGMENT_PRECISION_HIGH\n"+ +"precision highp float;\n"+ +"#else\n"+ +"precision mediump float;\n"+ +"#endif\n"+ +"#endif\n"+ +"\n"+ +"attribute vec3 aPos;\n"+ +"attribute vec4 aCol;\n"+ +"uniform mat4 mvMatrix;\n"+ +"uniform mat4 prMatrix;\n"+ +"varying vec4 vCol;\n"+ +"varying vec4 vPosition;\n"+ +"\n"+ +"#ifdef NEEDS_VNORMAL\n"+ +"attribute vec3 aNorm;\n"+ +"uniform mat4 normMatrix;\n"+ +"varying vec4 vNormal;\n"+ +"#endif\n"+ +"\n"+ +"#if defined(HAS_TEXTURE) || defined (IS_TEXT)\n"+ +"attribute vec2 aTexcoord;\n"+ +"varying vec2 vTexcoord;\n"+ +"#endif\n"+ +"\n"+ +"#ifdef FIXED_SIZE\n"+ +"uniform vec3 textScale;\n"+ +"#endif\n"+ +"\n"+ +"#ifdef FIXED_QUADS\n"+ +"attribute vec3 aOfs;\n"+ +"#endif\n"+ +"\n"+ +"#ifdef IS_TWOSIDED\n"+ +"#ifdef HAS_NORMALS\n"+ +"varying float normz;\n"+ +"uniform mat4 invPrMatrix;\n"+ +"#else\n"+ +"attribute vec3 aPos1;\n"+ +"attribute vec3 aPos2;\n"+ +"varying float normz;\n"+ +"#endif\n"+ +"#endif // IS_TWOSIDED\n"+ +"\n"+ +"#ifdef FAT_LINES\n"+ +"attribute vec3 aNext;\n"+ +"attribute vec2 aPoint;\n"+ +"varying vec2 vPoint;\n"+ +"varying float vLength;\n"+ +"uniform float uAspect;\n"+ +"uniform float uLwd;\n"+ +"#endif\n"+ +"\n"+ +"#ifdef USE_ENVMAP\n"+ +"varying vec3 vReflection;\n"+ +"#endif\n"+ +"\n"+ +"void main(void) {\n"+ +" \n"+ +"#ifndef IS_BRUSH\n"+ +"#if defined(NCLIPPLANES) || !defined(FIXED_QUADS) || defined(HAS_FOG) || defined(USE_ENVMAP)\n"+ +" vPosition = mvMatrix * vec4(aPos, 1.);\n"+ +"#endif\n"+ +" \n"+ +"#ifndef FIXED_QUADS\n"+ +" gl_Position = prMatrix * vPosition;\n"+ +"#endif\n"+ +"#endif // !IS_BRUSH\n"+ +" \n"+ +"#ifdef IS_POINTS\n"+ +" gl_PointSize = POINTSIZE;\n"+ +"#endif\n"+ +" \n"+ +" vCol = aCol;\n"+ +" \n"+ +"// USE_ENVMAP implies NEEDS_VNORMAL\n"+ +"\n"+ +"#ifdef NEEDS_VNORMAL\n"+ +" vNormal = normMatrix * vec4(-aNorm, dot(aNorm, aPos));\n"+ +"#endif\n"+ +"\n"+ +"#ifdef USE_ENVMAP\n"+ +" vReflection = normalize(reflect(vPosition.xyz/vPosition.w, \n"+ +" normalize(vNormal.xyz/vNormal.w)));\n"+ +"#endif\n"+ +" \n"+ +"#ifdef IS_TWOSIDED\n"+ +"#ifdef HAS_NORMALS\n"+ +" /* normz should be calculated *after* projection */\n"+ +" normz = (invPrMatrix*vNormal).z;\n"+ +"#else\n"+ +" vec4 pos1 = prMatrix*(mvMatrix*vec4(aPos1, 1.));\n"+ +" pos1 = pos1/pos1.w - gl_Position/gl_Position.w;\n"+ +" vec4 pos2 = prMatrix*(mvMatrix*vec4(aPos2, 1.));\n"+ +" pos2 = pos2/pos2.w - gl_Position/gl_Position.w;\n"+ +" normz = pos1.x*pos2.y - pos1.y*pos2.x;\n"+ +"#endif\n"+ +"#endif // IS_TWOSIDED\n"+ +" \n"+ +"#ifdef NEEDS_VNORMAL\n"+ +" vNormal = vec4(normalize(vNormal.xyz/vNormal.w), 1);\n"+ +"#endif\n"+ +" \n"+ +"#if defined(HAS_TEXTURE) || defined(IS_TEXT)\n"+ +" vTexcoord = aTexcoord;\n"+ +"#endif\n"+ +" \n"+ +"#if defined(FIXED_SIZE) && !defined(ROTATING)\n"+ +" vec4 pos = prMatrix * mvMatrix * vec4(aPos, 1.);\n"+ +" pos = pos/pos.w;\n"+ +" gl_Position = pos + vec4(aOfs*textScale, 0.);\n"+ +"#endif\n"+ +" \n"+ +"#if defined(IS_SPRITES) && !defined(FIXED_SIZE)\n"+ +" vec4 pos = mvMatrix * vec4(aPos, 1.);\n"+ +" pos = pos/pos.w + vec4(aOfs, 0.);\n"+ +" gl_Position = prMatrix*pos;\n"+ +"#endif\n"+ +" \n"+ +"#ifdef FAT_LINES\n"+ +" /* This code was inspired by Matt Deslauriers' code in \n"+ +" https://mattdesl.svbtle.com/drawing-lines-is-hard */\n"+ +" vec2 aspectVec = vec2(uAspect, 1.0);\n"+ +" mat4 projViewModel = prMatrix * mvMatrix;\n"+ +" vec4 currentProjected = projViewModel * vec4(aPos, 1.0);\n"+ +" currentProjected = currentProjected/currentProjected.w;\n"+ +" vec4 nextProjected = projViewModel * vec4(aNext, 1.0);\n"+ +" vec2 currentScreen = currentProjected.xy * aspectVec;\n"+ +" vec2 nextScreen = (nextProjected.xy / nextProjected.w) * aspectVec;\n"+ +" float len = uLwd;\n"+ +" vec2 dir = vec2(1.0, 0.0);\n"+ +" vPoint = aPoint;\n"+ +" vLength = length(nextScreen - currentScreen)/2.0;\n"+ +" vLength = vLength/(vLength + len);\n"+ +" if (vLength > 0.0) {\n"+ +" dir = normalize(nextScreen - currentScreen);\n"+ +" }\n"+ +" vec2 normal = vec2(-dir.y, dir.x);\n"+ +" dir.x /= uAspect;\n"+ +" normal.x /= uAspect;\n"+ +" vec4 offset = vec4(len*(normal*aPoint.x*aPoint.y - dir), 0.0, 0.0);\n"+ +" gl_Position = currentProjected + offset;\n"+ +"#endif\n"+ +" \n"+ +"#ifdef IS_BRUSH\n"+ +" gl_Position = vec4(aPos, 1.);\n"+ +"#endif\n"+ +"}\n" ;}; +rglwidgetClass.rgl_fragment_shader = function() { +return "#line 2 2\n"+ +"// File 2 is the fragment shader\n"+ +"#ifdef GL_ES\n"+ +"#ifdef GL_FRAGMENT_PRECISION_HIGH\n"+ +"precision highp float;\n"+ +"#else\n"+ +"precision mediump float;\n"+ +"#endif\n"+ +"#endif\n"+ +"varying vec4 vCol; // carries alpha\n"+ +"varying vec4 vPosition;\n"+ +"#if defined(HAS_TEXTURE) || defined (IS_TEXT)\n"+ +"varying vec2 vTexcoord;\n"+ +"uniform sampler2D uSampler;\n"+ +"#endif\n"+ +"\n"+ +"#ifdef HAS_FOG\n"+ +"uniform int uFogMode;\n"+ +"uniform vec3 uFogColor;\n"+ +"uniform vec4 uFogParms;\n"+ +"#endif\n"+ +"\n"+ +"#if defined(IS_LIT) && !defined(FIXED_QUADS)\n"+ +"varying vec4 vNormal;\n"+ +"#endif\n"+ +"\n"+ +"#if NCLIPPLANES > 0\n"+ +"uniform vec4 vClipplane[NCLIPPLANES];\n"+ +"#endif\n"+ +"\n"+ +"#if NLIGHTS > 0\n"+ +"uniform mat4 mvMatrix;\n"+ +"#endif\n"+ +"\n"+ +"#ifdef IS_LIT\n"+ +"uniform vec3 emission;\n"+ +"uniform float shininess;\n"+ +"#if NLIGHTS > 0\n"+ +"uniform vec3 ambient[NLIGHTS];\n"+ +"uniform vec3 specular[NLIGHTS]; // light*material\n"+ +"uniform vec3 diffuse[NLIGHTS];\n"+ +"uniform vec3 lightDir[NLIGHTS];\n"+ +"uniform bool viewpoint[NLIGHTS];\n"+ +"uniform bool finite[NLIGHTS];\n"+ +"#endif\n"+ +"#endif // IS_LIT\n"+ +"\n"+ +"#ifdef IS_TWOSIDED\n"+ +"uniform bool front;\n"+ +"varying float normz;\n"+ +"#endif\n"+ +"\n"+ +"#ifdef FAT_LINES\n"+ +"varying vec2 vPoint;\n"+ +"varying float vLength;\n"+ +"#endif\n"+ +"\n"+ +"#ifdef USE_ENVMAP\n"+ +"varying vec3 vReflection;\n"+ +"#endif\n"+ +"\n"+ +"void main(void) {\n"+ +" vec4 fragColor;\n"+ +"#ifdef FAT_LINES\n"+ +" vec2 point = vPoint;\n"+ +" bool neg = point.y < 0.0;\n"+ +" point.y = neg ? (point.y + vLength)/(1.0 - vLength) :\n"+ +" -(point.y - vLength)/(1.0 - vLength);\n"+ +"#if defined(IS_TRANSPARENT) && defined(IS_LINESTRIP)\n"+ +" if (neg && length(point) <= 1.0) discard;\n"+ +"#endif\n"+ +" point.y = min(point.y, 0.0);\n"+ +" if (length(point) > 1.0) discard;\n"+ +"#endif // FAT_LINES\n"+ +" \n"+ +"#ifdef ROUND_POINTS\n"+ +" vec2 coord = gl_PointCoord - vec2(0.5);\n"+ +" if (length(coord) > 0.5) discard;\n"+ +"#endif\n"+ +" \n"+ +"#if NCLIPPLANES > 0\n"+ +" for (int i = 0; i < NCLIPPLANES; i++)\n"+ +" if (dot(vPosition, vClipplane[i]) < 0.0) discard;\n"+ +"#endif\n"+ +" \n"+ +"#ifdef FIXED_QUADS\n"+ +" vec3 n = vec3(0., 0., 1.);\n"+ +"#elif defined(IS_LIT)\n"+ +" vec3 n = normalize(vNormal.xyz);\n"+ +"#endif\n"+ +" \n"+ +"#ifdef IS_TWOSIDED\n"+ +" if ((normz <= 0.) != front) discard;\n"+ +"#endif\n"+ +"\n"+ +"#ifdef IS_LIT\n"+ +" vec3 eye = normalize(-vPosition.xyz/vPosition.w);\n"+ +" vec3 lightdir;\n"+ +" vec4 colDiff;\n"+ +" vec3 halfVec;\n"+ +" vec4 lighteffect = vec4(emission, 0.);\n"+ +" vec3 col;\n"+ +" float nDotL;\n"+ +"#ifdef FIXED_QUADS\n"+ +" n = -faceforward(n, n, eye);\n"+ +"#endif\n"+ +" \n"+ +"#if NLIGHTS > 0\n"+ +" for (int i=0;i 0) {\n"+ +" fogF = (uFogParms.y - vPosition.z/vPosition.w)/(uFogParms.y - uFogParms.x);\n"+ +" if (uFogMode > 1)\n"+ +" fogF = mix(uFogParms.w, 1.0, fogF);\n"+ +" fogF = fogF*uFogParms.z;\n"+ +" if (uFogMode == 2)\n"+ +" fogF = 1.0 - exp(-fogF);\n"+ +" // Docs are wrong: use (density*c)^2, not density*c^2\n"+ +" // https://gitlab.freedesktop.org/mesa/mesa/-/blob/master/src/mesa/swrast/s_fog.c#L58\n"+ +" else if (uFogMode == 3)\n"+ +" fogF = 1.0 - exp(-fogF*fogF);\n"+ +" fogF = clamp(fogF, 0.0, 1.0);\n"+ +" gl_FragColor = vec4(mix(fragColor.rgb, uFogColor, fogF), fragColor.a);\n"+ +" } else gl_FragColor = fragColor;\n"+ +"#else\n"+ +" gl_FragColor = fragColor;\n"+ +"#endif // HAS_FOG\n"+ +" \n"+ +"}\n" ;}; diff --git a/reference/libs/rglwidgetClass-1.2.8/subscenes.src.js b/reference/libs/rglwidgetClass-1.2.8/subscenes.src.js new file mode 100644 index 000000000..fc6e05d02 --- /dev/null +++ b/reference/libs/rglwidgetClass-1.2.8/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/reference/libs/rglwidgetClass-1.2.8/textures.src.js b/reference/libs/rglwidgetClass-1.2.8/textures.src.js new file mode 100644 index 000000000..3cf32d36b --- /dev/null +++ b/reference/libs/rglwidgetClass-1.2.8/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/reference/libs/rglwidgetClass-1.2.8/utils.src.js b/reference/libs/rglwidgetClass-1.2.8/utils.src.js new file mode 100644 index 000000000..00bc5fc71 --- /dev/null +++ b/reference/libs/rglwidgetClass-1.2.8/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/reference/plot.colspace.html b/reference/plot.colspace.html index b60fc75b5..720de168f 100644 --- a/reference/plot.colspace.html +++ b/reference/plot.colspace.html @@ -2,7 +2,7 @@ Plot spectra in a colourspace — plot.colspace • pavo +
@@ -181,9 +181,9 @@

Examples

tcs.breast <- subset(tcs.sicalis, "B") tcsplot(tcs.crown, col = "blue") tcspoints(tcs.breast, col = "red") - -
- + + + ## Plot convex hull in interactive tetrahedron tcsplot(tcs.sicalis, col = "blue", size = 0.005) tcsvol(tcs.sicalis) diff --git a/reference/simulate_spec.html b/reference/simulate_spec.html index ae4fcb68a..65059ce4d 100644 --- a/reference/simulate_spec.html +++ b/reference/simulate_spec.html @@ -178,36 +178,38 @@

Examples

# Simulate a set of Gaussian reflectance curves with peaks varying between 400-600nm # in increments of 10, then combine into a single rspec object, and plot the result -peaks <- seq(400, 600, 10) # Peak locations -reflectances <- lapply(seq_along(peaks), function(x) simulate_spec(wl_peak = peaks[x])) # Simulate -reflectances <- Reduce(merge, reflectances) # Combine -plot(reflectances) # Plot +peaks <- seq(400, 600, 10) # Peak locations +reflectances <- lapply(seq_along(peaks), function(x) simulate_spec(wl_peak = peaks[x])) # Simulate +reflectances <- Reduce(merge, reflectances) # Combine +plot(reflectances) # Plot # Simulate a set of Gaussian reflectance curves with a single peak at 500 nm, but # with maximum reflectance varying from 30 to 90% in 10% increments, then combine # into a single rspec object, and plot the result -ymax <- lapply(seq(30, 90, 10), function(x) c(0, x)) # Varying reflectance maxima -reflectances2 <- lapply(ymax, function(x) simulate_spec(wl_peak = 500, ylim = x)) # Simulate -reflectances2 <- Reduce(merge, reflectances2) # Combine +ymax <- lapply(seq(30, 90, 10), function(x) c(0, x)) # Varying reflectance maxima +reflectances2 <- lapply(ymax, function(x) simulate_spec(wl_peak = 500, ylim = x)) # Simulate +reflectances2 <- Reduce(merge, reflectances2) # Combine #> Warning: column names ‘spec_p500.x’, ‘spec_p500.y’ are duplicated in the result #> Warning: column names ‘spec_p500.x’, ‘spec_p500.y’ are duplicated in the result #> Warning: column names ‘spec_p500.x’, ‘spec_p500.y’, ‘spec_p500.x’, ‘spec_p500.y’ are duplicated in the result #> Warning: column names ‘spec_p500.x’, ‘spec_p500.y’, ‘spec_p500.x’, ‘spec_p500.y’ are duplicated in the result -plot(reflectances2) # Plot +plot(reflectances2) # Plot # To simulate non-reflectance spectra (like irradiances or radiances), it's often useful # to explore more 'extreme' parameters. Here's a simple example which simulates # natural daylight, as represented by the D65 standard daylight spectrum. -D65_real <- procspec(sensdata(illum = 'D65'), opt = 'smooth') # Official D65 daylight spectrum +D65_real <- procspec(sensdata(illum = "D65"), opt = "smooth") # Official D65 daylight spectrum #> processing options applied: #> smoothing spectra with a span of 0.25 -D65_sim <- simulate_spec(wl_peak = 400, - width_gauss = 1300, - skew_gauss = 10, - ylim = c(0, 1)) # Simulated D65 -cor.test(D65_real$D65, D65_sim$spec_p400) # >0.99 correlation +D65_sim <- simulate_spec( + wl_peak = 400, + width_gauss = 1300, + skew_gauss = 10, + ylim = c(0, 1) +) # Simulated D65 +cor.test(D65_real$D65, D65_sim$spec_p400) # >0.99 correlation #> #> Pearson's product-moment correlation #> @@ -220,7 +222,7 @@

Examples

#> cor #> 0.9961745 #> -plot(merge(D65_real, D65_sim), lty = 1:2, ylab = 'Irradiance (%)') # Merge and plot the two spectra +plot(merge(D65_real, D65_sim), lty = 1:2, ylab = "Irradiance (%)") # Merge and plot the two spectra
diff --git a/reference/tcsplot.html b/reference/tcsplot.html index 29a11290a..487a20e83 100644 --- a/reference/tcsplot.html +++ b/reference/tcsplot.html @@ -4,7 +4,7 @@ Plots points in a tetrahedral colour space"> +
@@ -245,9 +245,9 @@

Examples

tcs.breast <- subset(tcs.sicalis, "B") tcsplot(tcs.crown, col = "blue") tcspoints(tcs.breast, col = "red") - -
- + + + # For plotting convex hull tcsplot(tcs.sicalis, col = "blue", size = 0.005) tcsvol(tcs.sicalis)