class: center, middle, inverse, title-slide # Introduction and Motivation ## High Dimensional Data Analysis ### Anastasios Panagiotelis & Ruben Loaiza-Maya ### Lecture 1 --- class: inverse, center, middle # A Data Story --- # High-Dimensional Data? - First what do we mean by *High Dimensional*? - The data we look at will have: + Observations + Variables - Generally *High Dimensional* implies that the number of *variables* is large. -- - The term, high-dimensional also relates thinking about and visualising data as points in space. --- # US States - Five indicators of the quality of life in the 50 States of the USA in 1977. - Income, - Illiteracy rate, - High school graduation rate, - Life expectancy, - Murder rates. - Let's explore! --- # A dataset <div style="border: 1px solid #ddd; padding: 0px; overflow-y: scroll; height:500px; "><table class="table table-striped table-hover table-condensed" style="margin-left: auto; margin-right: auto;"> <thead> <tr> <th style="text-align:left;position: sticky; top:0; background-color: #FFFFFF;"> State </th> <th style="text-align:right;position: sticky; top:0; background-color: #FFFFFF;"> Income </th> <th style="text-align:right;position: sticky; top:0; background-color: #FFFFFF;"> Illiteracy </th> <th style="text-align:right;position: sticky; top:0; background-color: #FFFFFF;"> LifeExp </th> <th style="text-align:right;position: sticky; top:0; background-color: #FFFFFF;"> Murder </th> <th style="text-align:right;position: sticky; top:0; background-color: #FFFFFF;"> HSGrad </th> <th style="text-align:left;position: sticky; top:0; background-color: #FFFFFF;"> StateAbb </th> </tr> </thead> <tbody> <tr> <td style="text-align:left;"> Alabama </td> <td style="text-align:right;"> 3624 </td> <td style="text-align:right;"> 2.1 </td> <td style="text-align:right;"> 69.05 </td> <td style="text-align:right;"> 15.1 </td> <td style="text-align:right;"> 41.3 </td> <td style="text-align:left;"> AL </td> </tr> <tr> <td style="text-align:left;"> Alaska </td> <td style="text-align:right;"> 6315 </td> <td style="text-align:right;"> 1.5 </td> <td style="text-align:right;"> 69.31 </td> <td style="text-align:right;"> 11.3 </td> <td style="text-align:right;"> 66.7 </td> <td style="text-align:left;"> AK </td> </tr> <tr> <td style="text-align:left;"> Arizona </td> <td style="text-align:right;"> 4530 </td> <td style="text-align:right;"> 1.8 </td> <td style="text-align:right;"> 70.55 </td> <td style="text-align:right;"> 7.8 </td> <td style="text-align:right;"> 58.1 </td> <td style="text-align:left;"> AZ </td> </tr> <tr> <td style="text-align:left;"> Arkansas </td> <td style="text-align:right;"> 3378 </td> <td style="text-align:right;"> 1.9 </td> <td style="text-align:right;"> 70.66 </td> <td style="text-align:right;"> 10.1 </td> <td style="text-align:right;"> 39.9 </td> <td style="text-align:left;"> AR </td> </tr> <tr> <td style="text-align:left;"> California </td> <td style="text-align:right;"> 5114 </td> <td style="text-align:right;"> 1.1 </td> <td style="text-align:right;"> 71.71 </td> <td style="text-align:right;"> 10.3 </td> <td style="text-align:right;"> 62.6 </td> <td style="text-align:left;"> CA </td> </tr> <tr> <td style="text-align:left;"> Colorado </td> <td style="text-align:right;"> 4884 </td> <td style="text-align:right;"> 0.7 </td> <td style="text-align:right;"> 72.06 </td> <td style="text-align:right;"> 6.8 </td> <td style="text-align:right;"> 63.9 </td> <td style="text-align:left;"> CO </td> </tr> <tr> <td style="text-align:left;"> Connecticut </td> <td style="text-align:right;"> 5348 </td> <td style="text-align:right;"> 1.1 </td> <td style="text-align:right;"> 72.48 </td> <td style="text-align:right;"> 3.1 </td> <td style="text-align:right;"> 56.0 </td> <td style="text-align:left;"> CT </td> </tr> <tr> <td style="text-align:left;"> Delaware </td> <td style="text-align:right;"> 4809 </td> <td style="text-align:right;"> 0.9 </td> <td style="text-align:right;"> 70.06 </td> <td style="text-align:right;"> 6.2 </td> <td style="text-align:right;"> 54.6 </td> <td style="text-align:left;"> DE </td> </tr> <tr> <td style="text-align:left;"> Florida </td> <td style="text-align:right;"> 4815 </td> <td style="text-align:right;"> 1.3 </td> <td style="text-align:right;"> 70.66 </td> <td style="text-align:right;"> 10.7 </td> <td style="text-align:right;"> 52.6 </td> <td style="text-align:left;"> FL </td> </tr> <tr> <td style="text-align:left;"> Georgia </td> <td style="text-align:right;"> 4091 </td> <td style="text-align:right;"> 2.0 </td> <td style="text-align:right;"> 68.54 </td> <td style="text-align:right;"> 13.9 </td> <td style="text-align:right;"> 40.6 </td> <td style="text-align:left;"> GA </td> </tr> <tr> <td style="text-align:left;"> Hawaii </td> <td style="text-align:right;"> 4963 </td> <td style="text-align:right;"> 1.9 </td> <td style="text-align:right;"> 73.60 </td> <td style="text-align:right;"> 6.2 </td> <td style="text-align:right;"> 61.9 </td> <td style="text-align:left;"> HI </td> </tr> <tr> <td style="text-align:left;"> Idaho </td> <td style="text-align:right;"> 4119 </td> <td style="text-align:right;"> 0.6 </td> <td style="text-align:right;"> 71.87 </td> <td style="text-align:right;"> 5.3 </td> <td style="text-align:right;"> 59.5 </td> <td style="text-align:left;"> ID </td> </tr> <tr> <td style="text-align:left;"> Illinois </td> <td style="text-align:right;"> 5107 </td> <td style="text-align:right;"> 0.9 </td> <td style="text-align:right;"> 70.14 </td> <td style="text-align:right;"> 10.3 </td> <td style="text-align:right;"> 52.6 </td> <td style="text-align:left;"> IL </td> </tr> <tr> <td style="text-align:left;"> Indiana </td> <td style="text-align:right;"> 4458 </td> <td style="text-align:right;"> 0.7 </td> <td style="text-align:right;"> 70.88 </td> <td style="text-align:right;"> 7.1 </td> <td style="text-align:right;"> 52.9 </td> <td style="text-align:left;"> IN </td> </tr> <tr> <td style="text-align:left;"> Iowa </td> <td style="text-align:right;"> 4628 </td> <td style="text-align:right;"> 0.5 </td> <td style="text-align:right;"> 72.56 </td> <td style="text-align:right;"> 2.3 </td> <td style="text-align:right;"> 59.0 </td> <td style="text-align:left;"> IA </td> </tr> <tr> <td style="text-align:left;"> Kansas </td> <td style="text-align:right;"> 4669 </td> <td style="text-align:right;"> 0.6 </td> <td style="text-align:right;"> 72.58 </td> <td style="text-align:right;"> 4.5 </td> <td style="text-align:right;"> 59.9 </td> <td style="text-align:left;"> KS </td> </tr> <tr> <td style="text-align:left;"> Kentucky </td> <td style="text-align:right;"> 3712 </td> <td style="text-align:right;"> 1.6 </td> <td style="text-align:right;"> 70.10 </td> <td style="text-align:right;"> 10.6 </td> <td style="text-align:right;"> 38.5 </td> <td style="text-align:left;"> KY </td> </tr> <tr> <td style="text-align:left;"> Louisiana </td> <td style="text-align:right;"> 3545 </td> <td style="text-align:right;"> 2.8 </td> <td style="text-align:right;"> 68.76 </td> <td style="text-align:right;"> 13.2 </td> <td style="text-align:right;"> 42.2 </td> <td style="text-align:left;"> LA </td> </tr> <tr> <td style="text-align:left;"> Maine </td> <td style="text-align:right;"> 3694 </td> <td style="text-align:right;"> 0.7 </td> <td style="text-align:right;"> 70.39 </td> <td style="text-align:right;"> 2.7 </td> <td style="text-align:right;"> 54.7 </td> <td style="text-align:left;"> ME </td> </tr> <tr> <td style="text-align:left;"> Maryland </td> <td style="text-align:right;"> 5299 </td> <td style="text-align:right;"> 0.9 </td> <td style="text-align:right;"> 70.22 </td> <td style="text-align:right;"> 8.5 </td> <td style="text-align:right;"> 52.3 </td> <td style="text-align:left;"> MD </td> </tr> <tr> <td style="text-align:left;"> Massachusetts </td> <td style="text-align:right;"> 4755 </td> <td style="text-align:right;"> 1.1 </td> <td style="text-align:right;"> 71.83 </td> <td style="text-align:right;"> 3.3 </td> <td style="text-align:right;"> 58.5 </td> <td style="text-align:left;"> MA </td> </tr> <tr> <td style="text-align:left;"> Michigan </td> <td style="text-align:right;"> 4751 </td> <td style="text-align:right;"> 0.9 </td> <td style="text-align:right;"> 70.63 </td> <td style="text-align:right;"> 11.1 </td> <td style="text-align:right;"> 52.8 </td> <td style="text-align:left;"> MI </td> </tr> <tr> <td style="text-align:left;"> Minnesota </td> <td style="text-align:right;"> 4675 </td> <td style="text-align:right;"> 0.6 </td> <td style="text-align:right;"> 72.96 </td> <td style="text-align:right;"> 2.3 </td> <td style="text-align:right;"> 57.6 </td> <td style="text-align:left;"> MN </td> </tr> <tr> <td style="text-align:left;"> Mississippi </td> <td style="text-align:right;"> 3098 </td> <td style="text-align:right;"> 2.4 </td> <td style="text-align:right;"> 68.09 </td> <td style="text-align:right;"> 12.5 </td> <td style="text-align:right;"> 41.0 </td> <td style="text-align:left;"> MS </td> </tr> <tr> <td style="text-align:left;"> Missouri </td> <td style="text-align:right;"> 4254 </td> <td style="text-align:right;"> 0.8 </td> <td style="text-align:right;"> 70.69 </td> <td style="text-align:right;"> 9.3 </td> <td style="text-align:right;"> 48.8 </td> <td style="text-align:left;"> MO </td> </tr> <tr> <td style="text-align:left;"> Montana </td> <td style="text-align:right;"> 4347 </td> <td style="text-align:right;"> 0.6 </td> <td style="text-align:right;"> 70.56 </td> <td style="text-align:right;"> 5.0 </td> <td style="text-align:right;"> 59.2 </td> <td style="text-align:left;"> MT </td> </tr> <tr> <td style="text-align:left;"> Nebraska </td> <td style="text-align:right;"> 4508 </td> <td style="text-align:right;"> 0.6 </td> <td style="text-align:right;"> 72.60 </td> <td style="text-align:right;"> 2.9 </td> <td style="text-align:right;"> 59.3 </td> <td style="text-align:left;"> NE </td> </tr> <tr> <td style="text-align:left;"> Nevada </td> <td style="text-align:right;"> 5149 </td> <td style="text-align:right;"> 0.5 </td> <td style="text-align:right;"> 69.03 </td> <td style="text-align:right;"> 11.5 </td> <td style="text-align:right;"> 65.2 </td> <td style="text-align:left;"> NV </td> </tr> <tr> <td style="text-align:left;"> New Hampshire </td> <td style="text-align:right;"> 4281 </td> <td style="text-align:right;"> 0.7 </td> <td style="text-align:right;"> 71.23 </td> <td style="text-align:right;"> 3.3 </td> <td style="text-align:right;"> 57.6 </td> <td style="text-align:left;"> NH </td> </tr> <tr> <td style="text-align:left;"> New Jersey </td> <td style="text-align:right;"> 5237 </td> <td style="text-align:right;"> 1.1 </td> <td style="text-align:right;"> 70.93 </td> <td style="text-align:right;"> 5.2 </td> <td style="text-align:right;"> 52.5 </td> <td style="text-align:left;"> NJ </td> </tr> <tr> <td style="text-align:left;"> New Mexico </td> <td style="text-align:right;"> 3601 </td> <td style="text-align:right;"> 2.2 </td> <td style="text-align:right;"> 70.32 </td> <td style="text-align:right;"> 9.7 </td> <td style="text-align:right;"> 55.2 </td> <td style="text-align:left;"> NM </td> </tr> <tr> <td style="text-align:left;"> New York </td> <td style="text-align:right;"> 4903 </td> <td style="text-align:right;"> 1.4 </td> <td style="text-align:right;"> 70.55 </td> <td style="text-align:right;"> 10.9 </td> <td style="text-align:right;"> 52.7 </td> <td style="text-align:left;"> NY </td> </tr> <tr> <td style="text-align:left;"> North Carolina </td> <td style="text-align:right;"> 3875 </td> <td style="text-align:right;"> 1.8 </td> <td style="text-align:right;"> 69.21 </td> <td style="text-align:right;"> 11.1 </td> <td style="text-align:right;"> 38.5 </td> <td style="text-align:left;"> NC </td> </tr> <tr> <td style="text-align:left;"> North Dakota </td> <td style="text-align:right;"> 5087 </td> <td style="text-align:right;"> 0.8 </td> <td style="text-align:right;"> 72.78 </td> <td style="text-align:right;"> 1.4 </td> <td style="text-align:right;"> 50.3 </td> <td style="text-align:left;"> ND </td> </tr> <tr> <td style="text-align:left;"> Ohio </td> <td style="text-align:right;"> 4561 </td> <td style="text-align:right;"> 0.8 </td> <td style="text-align:right;"> 70.82 </td> <td style="text-align:right;"> 7.4 </td> <td style="text-align:right;"> 53.2 </td> <td style="text-align:left;"> OH </td> </tr> <tr> <td style="text-align:left;"> Oklahoma </td> <td style="text-align:right;"> 3983 </td> <td style="text-align:right;"> 1.1 </td> <td style="text-align:right;"> 71.42 </td> <td style="text-align:right;"> 6.4 </td> <td style="text-align:right;"> 51.6 </td> <td style="text-align:left;"> OK </td> </tr> <tr> <td style="text-align:left;"> Oregon </td> <td style="text-align:right;"> 4660 </td> <td style="text-align:right;"> 0.6 </td> <td style="text-align:right;"> 72.13 </td> <td style="text-align:right;"> 4.2 </td> <td style="text-align:right;"> 60.0 </td> <td style="text-align:left;"> OR </td> </tr> <tr> <td style="text-align:left;"> Pennsylvania </td> <td style="text-align:right;"> 4449 </td> <td style="text-align:right;"> 1.0 </td> <td style="text-align:right;"> 70.43 </td> <td style="text-align:right;"> 6.1 </td> <td style="text-align:right;"> 50.2 </td> <td style="text-align:left;"> PA </td> </tr> <tr> <td style="text-align:left;"> Rhode Island </td> <td style="text-align:right;"> 4558 </td> <td style="text-align:right;"> 1.3 </td> <td style="text-align:right;"> 71.90 </td> <td style="text-align:right;"> 2.4 </td> <td style="text-align:right;"> 46.4 </td> <td style="text-align:left;"> RI </td> </tr> <tr> <td style="text-align:left;"> South Carolina </td> <td style="text-align:right;"> 3635 </td> <td style="text-align:right;"> 2.3 </td> <td style="text-align:right;"> 67.96 </td> <td style="text-align:right;"> 11.6 </td> <td style="text-align:right;"> 37.8 </td> <td style="text-align:left;"> SC </td> </tr> <tr> <td style="text-align:left;"> South Dakota </td> <td style="text-align:right;"> 4167 </td> <td style="text-align:right;"> 0.5 </td> <td style="text-align:right;"> 72.08 </td> <td style="text-align:right;"> 1.7 </td> <td style="text-align:right;"> 53.3 </td> <td style="text-align:left;"> SD </td> </tr> <tr> <td style="text-align:left;"> Tennessee </td> <td style="text-align:right;"> 3821 </td> <td style="text-align:right;"> 1.7 </td> <td style="text-align:right;"> 70.11 </td> <td style="text-align:right;"> 11.0 </td> <td style="text-align:right;"> 41.8 </td> <td style="text-align:left;"> TN </td> </tr> <tr> <td style="text-align:left;"> Texas </td> <td style="text-align:right;"> 4188 </td> <td style="text-align:right;"> 2.2 </td> <td style="text-align:right;"> 70.90 </td> <td style="text-align:right;"> 12.2 </td> <td style="text-align:right;"> 47.4 </td> <td style="text-align:left;"> TX </td> </tr> <tr> <td style="text-align:left;"> Utah </td> <td style="text-align:right;"> 4022 </td> <td style="text-align:right;"> 0.6 </td> <td style="text-align:right;"> 72.90 </td> <td style="text-align:right;"> 4.5 </td> <td style="text-align:right;"> 67.3 </td> <td style="text-align:left;"> UT </td> </tr> <tr> <td style="text-align:left;"> Vermont </td> <td style="text-align:right;"> 3907 </td> <td style="text-align:right;"> 0.6 </td> <td style="text-align:right;"> 71.64 </td> <td style="text-align:right;"> 5.5 </td> <td style="text-align:right;"> 57.1 </td> <td style="text-align:left;"> VT </td> </tr> <tr> <td style="text-align:left;"> Virginia </td> <td style="text-align:right;"> 4701 </td> <td style="text-align:right;"> 1.4 </td> <td style="text-align:right;"> 70.08 </td> <td style="text-align:right;"> 9.5 </td> <td style="text-align:right;"> 47.8 </td> <td style="text-align:left;"> VA </td> </tr> <tr> <td style="text-align:left;"> Washington </td> <td style="text-align:right;"> 4864 </td> <td style="text-align:right;"> 0.6 </td> <td style="text-align:right;"> 71.72 </td> <td style="text-align:right;"> 4.3 </td> <td style="text-align:right;"> 63.5 </td> <td style="text-align:left;"> WA </td> </tr> <tr> <td style="text-align:left;"> West Virginia </td> <td style="text-align:right;"> 3617 </td> <td style="text-align:right;"> 1.4 </td> <td style="text-align:right;"> 69.48 </td> <td style="text-align:right;"> 6.7 </td> <td style="text-align:right;"> 41.6 </td> <td style="text-align:left;"> WV </td> </tr> <tr> <td style="text-align:left;"> Wisconsin </td> <td style="text-align:right;"> 4468 </td> <td style="text-align:right;"> 0.7 </td> <td style="text-align:right;"> 72.48 </td> <td style="text-align:right;"> 3.0 </td> <td style="text-align:right;"> 54.5 </td> <td style="text-align:left;"> WI </td> </tr> <tr> <td style="text-align:left;"> Wyoming </td> <td style="text-align:right;"> 4566 </td> <td style="text-align:right;"> 0.6 </td> <td style="text-align:right;"> 70.29 </td> <td style="text-align:right;"> 6.9 </td> <td style="text-align:right;"> 62.9 </td> <td style="text-align:left;"> WY </td> </tr> </tbody> </table></div> --- # Observations and Variables On the previous slide and in general: - Each row corresponds to an *observation* - In this example that is a State. - Each column corresponds to a *variable* - In this example that is an attribute of each State. --- # Histogram: Income
--- # Scatter-plot: Income v Mortality
--- # 3D Scatter-plot <img src="Intro_files/figure-html/unnamed-chunk-4-1.png" style="display: block; margin: auto;" /> --- # 3D Scatter-plot Click and drag to rotate
<script>/* * 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<float> 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<float> array); // copy 16 floats into the matrix sequence<float> 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(); // 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); };</script> <script>// To generate the help pages for this library, use // jsdoc --destination ../../../doc/rglwidgetClass --template ~/node_modules/jsdoc-baseline rglClass.src.js // To validate, use // setwd(".../inst/htmlwidgets/lib/rglClass") // hints <- js::jshint(readLines("rglClass.src.js")) // hints[, c("line", "reason")] /** * 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; }; /** * Multiply matrix by vector * @returns {number[]} * @param M {number[][]} Left operand * @param v {number[]} Right operand */ rglwidgetClass.prototype.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.prototype.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.prototype.vlen = function(v) { return Math.sqrt(this.dotprod(v, v)); }; /** * Dot product of two vectors * @instance rglwidgetClass * @returns {number} * @param a {number[]} * @param b {number[]} */ rglwidgetClass.prototype.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.prototype.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[]|number[][]} * @param b {number[]|number[][]} */ rglwidgetClass.prototype.cbind = function(a, b) { if (b.length < a.length) b = this.repeatToLen(b, a.length); else if (a.length < b.length) a = this.repeatToLen(a, b.length); return a.map(function(currentValue, index, array) { return 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.prototype.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.prototype.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)) { this.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 = this.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 */ 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]; }; /** * 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<value) { pow *= 2; } return pow; }; /** * Unique entries * @returns { any[] } * @param { any[] } arr - An array */ rglwidgetClass.prototype.unique = function(arr) { arr = [].concat(arr); return arr.filter(function(value, index, self) { return self.indexOf(value) === index; }); }; /** * Shallow compare of arrays * @returns { boolean } * @param { any[] } a - An array * @param { any[] } b - Another array */ rglwidgetClass.prototype.equalArrays = function(a, b) { return a === b || (a && b && a.length === b.length && a.every(function(v, i) {return v === b[i];})); }; /** * Repeat an array to a desired length * @returns {any[]} * @param {any | any[]} arr The input array * @param {number} len The desired output length */ rglwidgetClass.prototype.repeatToLen = function(arr, len) { arr = [].concat(arr); while (arr.length < len/2) arr = arr.concat(arr); return arr.concat(arr.slice(0, len - arr.length)); }; /** * Give a single alert message, not to be repeated. * @param {string} msg The message to give. */ rglwidgetClass.prototype.alertOnce = function(msg) { if (typeof this.alerted !== "undefined") return; this.alerted = true; alert(msg); }; rglwidgetClass.prototype.f_is_lit = 1; rglwidgetClass.prototype.f_is_smooth = 2; rglwidgetClass.prototype.f_has_texture = 4; rglwidgetClass.prototype.f_depth_sort = 8; rglwidgetClass.prototype.f_fixed_quads = 16; rglwidgetClass.prototype.f_is_transparent = 32; rglwidgetClass.prototype.f_is_lines = 64; rglwidgetClass.prototype.f_sprites_3d = 128; rglwidgetClass.prototype.f_sprite_3d = 256; rglwidgetClass.prototype.f_is_subscene = 512; rglwidgetClass.prototype.f_is_clipplanes = 1024; rglwidgetClass.prototype.f_fixed_size = 2048; rglwidgetClass.prototype.f_is_points = 4096; rglwidgetClass.prototype.f_is_twosided = 8192; rglwidgetClass.prototype.f_fat_lines = 16384; rglwidgetClass.prototype.f_is_brush = 32768; /** * 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 (flags & this.f_is_subscene) return "subscenes"; if (flags & this.f_is_clipplanes) return "clipplanes"; if (flags & this.f_is_transparent) return "transparent"; return "opaque"; }; /** * Get an object by id number. * @returns { Object } * @param {number} id */ rglwidgetClass.prototype.getObj = function(id) { if (typeof id !== "number") { this.alertOnce("getObj id is "+typeof id); } return this.scene.objects[id]; }; /** * Get ids of a particular type from a subscene or the whole scene * @returns { number[] } * @param {string} type What type of object? * @param {number} subscene Which subscene? If not given, find in the whole scene */ rglwidgetClass.prototype.getIdsByType = function(type, subscene) { var result = [], i, self = this; if (typeof subscene === "undefined") { Object.keys(this.scene.objects).forEach( function(key) { key = parseInt(key, 10); if (self.getObj(key).type === type) result.push(key); }); } else { ids = this.getObj(subscene).objects; for (i=0; i < ids.length; i++) { if (this.getObj(ids[i]).type === type) { result.push(ids[i]); } } } return result; }; /** * Get a particular material property for an id * @returns { any } * @param {number} id Which object? * @param {string} property Which material property? */ rglwidgetClass.prototype.getMaterial = function(id, property) { var obj = this.getObj(id), mat; if (typeof obj.material === "undefined") console.error("material undefined"); mat = obj.material[property]; if (typeof mat === "undefined") mat = this.scene.material[property]; return mat; }; /** * 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; }; /** * 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; 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<ids.length;j++) { id = ids[j]; i = thesub.objects.indexOf(id); if (i > -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; }; /** * 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(); }; /** * Generate the vertex shader for an object * @returns {string} * @param { number } id - Id of object */ rglwidgetClass.prototype.getVertexShader = function(id) { var obj = this.getObj(id), userShader = obj.userVertexShader, flags = obj.flags, type = obj.type, is_lit = flags & this.f_is_lit, has_texture = flags & this.f_has_texture, fixed_quads = flags & this.f_fixed_quads, sprites_3d = flags & this.f_sprites_3d, sprite_3d = flags & this.f_sprite_3d, nclipplanes = this.countClipplanes(), fixed_size = flags & this.f_fixed_size, is_points = flags & this.f_is_points, is_twosided = flags & this.f_is_twosided, fat_lines = flags & this.f_fat_lines, is_brush = flags & this.f_is_brush, result; if (type === "clipplanes" || sprites_3d) return; if (typeof userShader !== "undefined") return userShader; result = " /* ****** "+type+" object "+id+" vertex shader ****** */\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"; if ((is_lit && !fixed_quads && !is_brush) || sprite_3d) result = result + " attribute vec3 aNorm;\n"+ " uniform mat4 normMatrix;\n"+ " varying vec3 vNormal;\n"; if (has_texture || type === "text") result = result + " attribute vec2 aTexcoord;\n"+ " varying vec2 vTexcoord;\n"; if (fixed_size) result = result + " uniform vec2 textScale;\n"; if (fixed_quads) result = result + " attribute vec2 aOfs;\n"; else if (sprite_3d) result = result + " uniform vec3 uOrig;\n"+ " uniform float uSize;\n"+ " uniform mat4 usermat;\n"; if (is_twosided) result = result + " attribute vec3 aPos1;\n"+ " attribute vec3 aPos2;\n"+ " varying float normz;\n"; if (fat_lines) { result = result + " attribute vec3 aNext;\n"+ " attribute vec2 aPoint;\n"+ " varying vec2 vPoint;\n"+ " varying float vLength;\n"+ " uniform float uAspect;\n"+ " uniform float uLwd;\n"; } result = result + " void main(void) {\n"; if ((nclipplanes || (!fixed_quads && !sprite_3d)) && !is_brush) result = result + " vPosition = mvMatrix * vec4(aPos, 1.);\n"; if (!fixed_quads && !sprite_3d && !is_brush) result = result + " gl_Position = prMatrix * vPosition;\n"; if (is_points) { var size = this.getMaterial(id, "size"); result = result + " gl_PointSize = "+size.toFixed(1)+";\n"; } result = result + " vCol = aCol;\n"; if (is_lit && !fixed_quads && !sprite_3d && !is_brush) result = result + " vNormal = normalize((normMatrix * vec4(aNorm, 1.)).xyz);\n"; if (has_texture || type == "text") result = result + " vTexcoord = aTexcoord;\n"; if (fixed_size) result = result + " vec4 pos = prMatrix * mvMatrix * vec4(aPos, 1.);\n"+ " pos = pos/pos.w;\n"+ " gl_Position = pos + vec4(aOfs*textScale, 0.,0.);\n"; if (type == "sprites" && !fixed_size) result = result + " vec4 pos = mvMatrix * vec4(aPos, 1.);\n"+ " pos = pos/pos.w + vec4(aOfs, 0., 0.);\n"+ " gl_Position = prMatrix*pos;\n"; if (sprite_3d) result = result + " vNormal = normalize((normMatrix * vec4(aNorm, 1.)).xyz);\n"+ " vec4 pos = mvMatrix * vec4(uOrig, 1.);\n"+ " vPosition = pos/pos.w + vec4(uSize*(vec4(aPos, 1.)*usermat).xyz,0.);\n"+ " gl_Position = prMatrix * vPosition;\n"; if (is_twosided) result = result + " 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"; if (fat_lines) /* This code was inspired by Matt Deslauriers' code in https://mattdesl.svbtle.com/drawing-lines-is-hard */ result = result + " 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"; if (is_brush) result = result + " gl_Position = vec4(aPos, 1.);\n"; result = result + " }\n"; // console.log(result); return result; }; /** * Generate the fragment shader for an object * @returns {string} * @param { number } id - Id of object */ rglwidgetClass.prototype.getFragmentShader = function(id) { var obj = this.getObj(id), userShader = obj.userFragmentShader, flags = obj.flags, type = obj.type, is_lit = flags & this.f_is_lit, has_texture = flags & this.f_has_texture, fixed_quads = flags & this.f_fixed_quads, sprites_3d = flags & this.f_sprites_3d, is_twosided = (flags & this.f_is_twosided) > 0, fat_lines = flags & this.f_fat_lines, is_transparent = flags & this.f_is_transparent, nclipplanes = this.countClipplanes(), i, texture_format, nlights, result; if (type === "clipplanes" || sprites_3d) return; if (typeof userShader !== "undefined") return userShader; if (has_texture) texture_format = this.getMaterial(id, "textype"); result = "/* ****** "+type+" object "+id+" 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 (has_texture || type === "text") result = result + " varying vec2 vTexcoord;\n"+ " uniform sampler2D uSampler;\n"; if (is_lit && !fixed_quads) result = result + " varying vec3 vNormal;\n"; for (i = 0; i < nclipplanes; i++) result = result + " uniform vec4 vClipplane"+i+";\n"; if (is_lit) { nlights = this.countLights(); if (nlights) result = result + " uniform mat4 mvMatrix;\n"; else is_lit = false; } if (is_lit) { result = result + " uniform vec3 emission;\n"+ " uniform float shininess;\n"; for (i=0; i < nlights; i++) { result = result + " uniform vec3 ambient" + i + ";\n"+ " uniform vec3 specular" + i +"; // light*material\n"+ " uniform vec3 diffuse" + i + ";\n"+ " uniform vec3 lightDir" + i + ";\n"+ " uniform bool viewpoint" + i + ";\n"+ " uniform bool finite" + i + ";\n"; } } if (is_twosided) result = result + " uniform bool front;\n"+ " varying float normz;\n"; if (fat_lines) result = result + " varying vec2 vPoint;\n"+ " varying float vLength;\n"; result = result + " void main(void) {\n"; if (fat_lines) { result = result + " 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 (is_transparent && type == "linestrip") result = result+" if (neg && length(point) <= 1.0) discard;\n"; result = result + " point.y = min(point.y, 0.0);\n"+ " if (length(point) > 1.0) discard;\n"; } for (i=0; i < nclipplanes;i++) result = result + " if (dot(vPosition, vClipplane"+i+") < 0.0) discard;\n"; if (fixed_quads) { result = result + " vec3 n = vec3(0., 0., 1.);\n"; } else if (is_lit) { result = result + " vec3 n = normalize(vNormal);\n"; } if (is_twosided) { result = result + " if ((normz <= 0.) != front) discard;\n"; } if (is_lit) { result = result + " vec3 eye = normalize(-vPosition.xyz);\n"+ " vec3 lightdir;\n"+ " vec4 colDiff;\n"+ " vec3 halfVec;\n"+ " vec4 lighteffect = vec4(emission, 0.);\n"+ " vec3 col;\n"+ " float nDotL;\n"; if (!fixed_quads) { result = result + " n = -faceforward(n, n, eye);\n"; } for (i=0; i < nlights; i++) { result = result + " colDiff = vec4(vCol.rgb * diffuse" + i + ", vCol.a);\n"+ " lightdir = lightDir" + i + ";\n"+ " if (!viewpoint" + i +")\n"+ " lightdir = (mvMatrix * vec4(lightdir, 1.)).xyz;\n"+ " if (!finite" + i + ") {\n"+ " halfVec = normalize(lightdir + eye);\n"+ " } else {\n"+ " lightdir = normalize(lightdir - vPosition.xyz);\n"+ " halfVec = normalize(lightdir + eye);\n"+ " }\n"+ " col = ambient" + i + ";\n"+ " nDotL = dot(n, lightdir);\n"+ " col = col + max(nDotL, 0.) * colDiff.rgb;\n"+ " col = col + pow(max(dot(halfVec, n), 0.), shininess) * specular" + i + ";\n"+ " lighteffect = lighteffect + vec4(col, colDiff.a);\n"; } } else { result = result + " vec4 colDiff = vCol;\n"+ " vec4 lighteffect = colDiff;\n"; } if (type === "text") result = result + " vec4 textureColor = lighteffect*texture2D(uSampler, vTexcoord);\n"; if (has_texture) { result = result + { rgb: " vec4 textureColor = lighteffect*vec4(texture2D(uSampler, vTexcoord).rgb, 1.);\n", rgba: " vec4 textureColor = lighteffect*texture2D(uSampler, vTexcoord);\n", alpha: " vec4 textureColor = texture2D(uSampler, vTexcoord);\n"+ " float luminance = dot(vec3(1.,1.,1.), textureColor.rgb)/3.;\n"+ " textureColor = vec4(lighteffect.rgb, lighteffect.a*luminance);\n", luminance: " vec4 textureColor = vec4(lighteffect.rgb*dot(texture2D(uSampler, vTexcoord).rgb, vec3(1.,1.,1.))/3., lighteffect.a);\n", "luminance.alpha":" vec4 textureColor = texture2D(uSampler, vTexcoord);\n"+ " float luminance = dot(vec3(1.,1.,1.),textureColor.rgb)/3.;\n"+ " textureColor = vec4(lighteffect.rgb*luminance, lighteffect.a*textureColor.a);\n" }[texture_format]+ " gl_FragColor = textureColor;\n"; } else if (type === "text") { result = result + " if (textureColor.a < 0.1)\n"+ " discard;\n"+ " else\n"+ " gl_FragColor = textureColor;\n"; } else result = result + " gl_FragColor = lighteffect;\n"; //if (fat_lines) // result = result + " gl_FragColor = vec4(0.0, abs(point.x), abs(point.y), 1.0);" result = result + " }\n"; // console.log(result); return result; }; /** * Call gl functions to create and compile shader * @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; }; /** * 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), gl = self.gl || self.initGL(), 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.drawScene(); }; 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, textY, 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 = this.repeatToLen(cex, text.length); family = this.repeatToLen(family, text.length); font = this.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) { line += 1; offsety = lineoffsetsy[line] = offsety + 2*textHeight; 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}; }; /** * Set the gl viewport and scissor test * @param { number } id - id of subscene */ rglwidgetClass.prototype.setViewport = function(id) { var gl = this.gl || this.initGL(), 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}; gl.viewport(x, y, width, height); gl.scissor(x, y, width, 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; 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 !== "inherit") { var scale = subscene.par3d.scale, 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]); 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.setnormMatrix = function(subsceneid) { var self = this, recurse = function(id) { var sub = self.getObj(id), embedding = sub.embeddings.model; if (embedding !== "inherit") { var scale = sub.par3d.scale; self.normMatrix.scale(1/scale[0], 1/scale[1], 1/scale[2]); self.normMatrix.multRight(sub.par3d.userMatrix); } if (embedding !== "replace") recurse(sub.parent); }; self.normMatrix.makeIdentity(); recurse(subsceneid); }; /** * Set the combined projection-model-view matrix */ rglwidgetClass.prototype.setprmvMatrix = function() { this.prmvMatrix = new CanvasMatrix4( this.mvMatrix ); this.prmvMatrix.multRight( this.prMatrix ); }; /** * Count clipping planes in a scene * @returns {number} */ rglwidgetClass.prototype.countClipplanes = function() { return this.countObjs("clipplanes"); }; /** * Count lights in a scene * @returns { number } */ rglwidgetClass.prototype.countLights = function() { return this.countObjs("light"); }; /** * Count objects of specific type in a scene * @returns { number } * @param { string } type - Type of object to count */ rglwidgetClass.prototype.countObjs = function(type) { var self = this, bound = 0; Object.keys(this.scene.objects).forEach( function(key) { if (self.getObj(parseInt(key, 10)).type === type) bound = bound + 1; }); return bound; }; /** * 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 = []; 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); } }; /** * Copy object * @param { number } id - id of object to copy * @param { string } reuse - Document id of scene to reuse */ rglwidgetClass.prototype.copyObj = function(id, reuse) { var obj = this.getObj(id), prev = document.getElementById(reuse); if (prev !== null) { prev = prev.rglinstance; var prevobj = prev.getObj(id), fields = ["flags", "type", "colors", "vertices", "centers", "normals", "offsets", "texts", "cex", "family", "font", "adj", "material", "radii", "texcoords", "userMatrix", "ids", "dim", "par3d", "userMatrix", "viewpoint", "finite", "pos"], i; for (i = 0; i < fields.length; i++) { if (typeof prevobj[fields[i]] !== "undefined") obj[fields[i]] = prevobj[fields[i]]; } } else console.warn("copyObj failed"); }; /** * 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(id, 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 = [], obj = this.getObj(id), nPlanes = obj.normals.length; obj.bbox = bbox; obj.vertices = []; 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<nhits-2; i++) { which = 0; /* initialize to suppress warning */ for (j=i+1; j<nhits; j++) { if (face1[i] == face1[j] || face1[i] == face2[j] || face2[i] == face1[j] || face2[i] == face2[j] ) { which = j; break; } } if (which > i+1) { this.swap(x, i+1, which); this.swap(face1, i+1, which); this.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 = this.xprod(v0, v2); reverse = this.dotprod(vx, A) > 0; for (i=0; i<nhits-2; i++) { obj.vertices.push(x[0]); normals.push(A); for (j=1; j<3; j++) { obj.vertices.push(x[i + (reverse ? 3-j : j)]); normals.push(A); } } } } obj.pnormals = normals; }; rglwidgetClass.prototype.getAdj = function (pos, offset, text) { switch(pos) { case 1: return [0.5, 1 + offset]; case 2: return [1 + offset/text.length, 0.5]; case 3: return [0.5, -offset]; case 4: return [-offset/text.length, 0.5]; } } /** * Initialize object for display * @param { number } id - id of object to initialize */ rglwidgetClass.prototype.initObj = function(id) { var obj = this.getObj(id), flags = obj.flags, type = obj.type, is_lit = flags & this.f_is_lit, is_lines = flags & this.f_is_lines, fat_lines = flags & this.f_fat_lines, has_texture = flags & this.f_has_texture, fixed_quads = flags & this.f_fixed_quads, is_transparent = obj.is_transparent, depth_sort = flags & this.f_depth_sort, sprites_3d = flags & this.f_sprites_3d, sprite_3d = flags & this.f_sprite_3d, fixed_size = flags & this.f_fixed_size, is_twosided = (flags & this.f_is_twosided) > 0, is_brush = flags & this.f_is_brush, gl = this.gl || this.initGL(), polygon_offset, texinfo, drawtype, nclipplanes, f, nrows, oldrows, i,j,v,v1,v2, mat, uri, matobj, pass, passes, pmode, dim, nx, nz, attr; if (typeof id !== "number") { this.alertOnce("initObj id is "+typeof id); } obj.initialized = true; if (type === "bboxdeco" || type === "subscene") return; 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 = this.flatten(this.cbind(obj.normals, obj.offsets)); return; } if (type === "background" && typeof obj.ids !== "undefined") { obj.quad = this.flatten([].concat(obj.ids)); return; } polygon_offset = this.getMaterial(id, "polygon_offset"); if (polygon_offset[0] != 0 || polygon_offset[1] != 0) obj.polygon_offset = polygon_offset; if (is_transparent) { depth_sort = ["triangles", "quads", "surface", "spheres", "sprites", "text"].indexOf(type) >= 0; } if (is_brush) this.initSelection(id); if (typeof obj.vertices === "undefined") obj.vertices = []; v = obj.vertices; obj.vertexCount = v.length; if (!obj.vertexCount) return; if (is_twosided) { 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<Math.floor(v.length/nrow); i++) for (j=0; j<nrow; j++) { v1[nrow*i + j] = v[nrow*i + ((j+1) % nrow)]; v2[nrow*i + j] = v[nrow*i + ((j+2) % nrow)]; } } else if (obj.type == "surface") { dim = obj.dim[0]; nx = dim[0]; nz = dim[1]; for (j=0; j<nx; j++) { for (i=0; i<nz; i++) { if (i+1 < nz && j+1 < nx) { v2[j + nx*i] = v[j + nx*(i+1)]; v1[j + nx*i] = v[j+1 + nx*(i+1)]; } else if (i+1 < nz) { v2[j + nx*i] = v[j-1 + nx*i]; v1[j + nx*i] = v[j + nx*(i+1)]; } else { v2[j + nx*i] = v[j + nx*(i-1)]; v1[j + nx*i] = v[j-1 + nx*(i-1)]; } } } } obj.userAttributes.aPos1 = v1; obj.userAttributes.aPos2 = v2; } if (!sprites_3d) { if (gl.isContextLost()) return; obj.prog = gl.createProgram(); gl.attachShader(obj.prog, this.getShader( gl.VERTEX_SHADER, this.getVertexShader(id) )); gl.attachShader(obj.prog, this.getShader( gl.FRAGMENT_SHADER, this.getFragmentShader(id) )); // Force aPos to location 0, aCol to location 1 gl.bindAttribLocation(obj.prog, 0, "aPos"); gl.bindAttribLocation(obj.prog, 1, "aCol"); gl.linkProgram(obj.prog); var linked = gl.getProgramParameter(obj.prog, gl.LINK_STATUS); if (!linked) { // An error occurred while linking var lastError = gl.getProgramInfoLog(obj.prog); console.warn("Error in program linking:" + lastError); gl.deleteProgram(obj.prog); return; } } if (type === "text") { texinfo = this.drawTextToCanvas(obj.texts, this.flatten(obj.cex), this.flatten(obj.family), this.flatten(obj.family)); } if (fixed_quads && !sprites_3d) { obj.ofsLoc = gl.getAttribLocation(obj.prog, "aOfs"); } if (sprite_3d) { obj.origLoc = gl.getUniformLocation(obj.prog, "uOrig"); obj.sizeLoc = gl.getUniformLocation(obj.prog, "uSize"); obj.usermatLoc = gl.getUniformLocation(obj.prog, "usermat"); } if (has_texture || type == "text") { if (!obj.texture) obj.texture = gl.createTexture(); obj.texLoc = gl.getAttribLocation(obj.prog, "aTexcoord"); obj.sampler = gl.getUniformLocation(obj.prog, "uSampler"); } if (has_texture) { mat = obj.material; if (typeof mat.uri !== "undefined") uri = mat.uri; else if (typeof mat.uriElementId === "undefined") { matobj = this.getObj(mat.uriId); if (typeof matobj !== "undefined") { uri = matobj.material.uri; } else { uri = ""; } } else uri = document.getElementById(mat.uriElementId).rglinstance.getObj(mat.uriId).material.uri; this.loadImageToTexture(uri, obj.texture); } if (type === "text") { this.handleLoadedTexture(obj.texture, this.textureCanvas); } var stride = 3, nc, cofs, nofs, radofs, oofs, tofs, vnew, fnew, nextofs = -1, pointofs = -1, alias, colors, key, selection, filter, adj, pos, offset; obj.alias = undefined; colors = obj.colors; j = this.scene.crosstalk.id.indexOf(id); if (j >= 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 = this.cbind(v, colors); } else { cofs = -1; obj.onecolor = this.flatten(colors); } if (typeof obj.normals !== "undefined") { nofs = stride; stride = stride + 3; v = this.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 = this.cbind(v, obj.radii); } else if (obj.radii.length === 1) { v = v.map(function(row, i, arr) { return row.concat(obj.radii[0]);}); } } else radofs = -1; // Add default indices 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 += 2; vnew = new Array(4*v.length); fnew = new Array(4*v.length); alias = new Array(v.length); var rescale = fixed_size ? 72 : 1, size = obj.radii, s = rescale*size[0]/2; last = v.length; f = obj.f[0]; for (i=0; i < v.length; i++) { if (size.length > 1) s = rescale*size[i]/2; vnew[i] = v[i].concat([0,0,-s,-s]); fnew[4*i] = f[i]; vnew[last]= v[i].concat([1,0, s,-s]); fnew[4*i+1] = last++; vnew[last]= v[i].concat([1,1, s, s]); fnew[4*i+2] = last++; vnew[last]= v[i].concat([0,1,-s, s]); 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 += 2; 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 = this.flatten(obj.adj); if (typeof obj.pos !== "undefined") { pos = this.flatten(obj.pos); offset = adj[0]; } for (i=0; i < v.length; i++) { if (typeof pos !== "undefined") adj = this.getAdj(pos[i % pos.length], 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[tofs+2] = 2*(v1[tofs]-v1[tofs+2])*texinfo.widths[i]; v1[tofs+3] = 2*(v1[tofs+1]-v1[tofs+3])*texinfo.textHeights[i]; 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 = this.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 = this.cbind(v, obj.userAttributes[attr]); stride = v[0].length; obj.userAttribSizes[attr] = stride - obj.userAttribOffsets[attr]; } } } if (typeof obj.userUniforms !== "undefined") { obj.userUniformLocations = {}; for (attr in obj.userUniforms) obj.userUniformLocations[attr] = gl.getUniformLocation(obj.prog, attr); } if (sprites_3d) { obj.userMatrix = new CanvasMatrix4(obj.userMatrix); obj.objects = this.flatten([].concat(obj.ids)); is_lit = false; for (i=0; i < obj.objects.length; i++) this.initObj(obj.objects[i]); } if (is_lit && !fixed_quads) { obj.normLoc = gl.getAttribLocation(obj.prog, "aNorm"); } nclipplanes = this.countClipplanes(); if (nclipplanes && !sprites_3d) { obj.clipLoc = []; for (i=0; i < nclipplanes; i++) obj.clipLoc[i] = gl.getUniformLocation(obj.prog,"vClipplane" + i); } if (is_lit) { obj.emissionLoc = gl.getUniformLocation(obj.prog, "emission"); obj.emission = new Float32Array(this.stringToRgb(this.getMaterial(id, "emission"))); obj.shininessLoc = gl.getUniformLocation(obj.prog, "shininess"); obj.shininess = this.getMaterial(id, "shininess"); obj.nlights = this.countLights(); obj.ambientLoc = []; obj.ambient = new Float32Array(this.stringToRgb(this.getMaterial(id, "ambient"))); obj.specularLoc = []; obj.specular = new Float32Array(this.stringToRgb(this.getMaterial(id, "specular"))); obj.diffuseLoc = []; obj.lightDirLoc = []; obj.viewpointLoc = []; obj.finiteLoc = []; for (i=0; i < obj.nlights; i++) { obj.ambientLoc[i] = gl.getUniformLocation(obj.prog, "ambient" + i); obj.specularLoc[i] = gl.getUniformLocation(obj.prog, "specular" + i); obj.diffuseLoc[i] = gl.getUniformLocation(obj.prog, "diffuse" + i); obj.lightDirLoc[i] = gl.getUniformLocation(obj.prog, "lightDir" + i); obj.viewpointLoc[i] = gl.getUniformLocation(obj.prog, "viewpoint" + i); obj.finiteLoc[i] = gl.getUniformLocation(obj.prog, "finite" + i); } } obj.passes = is_twosided + 1; obj.pmode = new Array(obj.passes); for (pass = 0; pass < obj.passes; pass++) { if (type === "triangles" || type === "quads" || type === "surface") pmode = this.getMaterial(id, (pass === 0) ? "front" : "back"); else pmode = "filled"; obj.pmode[pass] = pmode; } obj.f.length = obj.passes; for (pass = 0; pass < obj.passes; pass++) { f = fnew = obj.f[pass]; pmode = obj.pmode[pass]; if (pmode === "culled") f = []; else if (pmode === "points") { // stay with default } else if ((type === "quads" || type === "text" || type === "sprites") && !sprites_3d) { nrows = Math.floor(obj.vertexCount/4); if (pmode === "filled") { fnew = Array(6*nrows); for (i=0; i < nrows; i++) { fnew[6*i] = f[4*i]; fnew[6*i+1] = f[4*i + 1]; fnew[6*i+2] = f[4*i + 2]; fnew[6*i+3] = f[4*i]; fnew[6*i+4] = f[4*i + 2]; fnew[6*i+5] = f[4*i + 3]; } } else { fnew = Array(8*nrows); for (i=0; i < nrows; i++) { fnew[8*i] = f[4*i]; fnew[8*i+1] = f[4*i + 1]; fnew[8*i+2] = f[4*i + 1]; fnew[8*i+3] = f[4*i + 2]; fnew[8*i+4] = f[4*i + 2]; fnew[8*i+5] = f[4*i + 3]; fnew[8*i+6] = f[4*i + 3]; fnew[8*i+7] = f[4*i]; } } } else if (type === "triangles") { nrows = Math.floor(obj.vertexCount/3); if (pmode === "filled") { fnew = Array(3*nrows); for (i=0; i < fnew.length; i++) { fnew[i] = f[i]; } } else if (pmode === "lines") { fnew = Array(6*nrows); for (i=0; i < nrows; i++) { fnew[6*i] = f[3*i]; fnew[6*i + 1] = f[3*i + 1]; fnew[6*i + 2] = f[3*i + 1]; fnew[6*i + 3] = f[3*i + 2]; fnew[6*i + 4] = f[3*i + 2]; fnew[6*i + 5] = f[3*i]; } } } else if (type === "spheres") { // default } else if (type === "surface") { dim = obj.dim[0]; nx = dim[0]; nz = dim[1]; if (pmode === "filled") { fnew = []; for (j=0; j<nx-1; j++) { for (i=0; i<nz-1; i++) { fnew.push(f[j + nx*i], f[j + nx*(i+1)], f[j + 1 + nx*(i+1)], f[j + nx*i], f[j + 1 + nx*(i+1)], f[j + 1 + nx*i]); } } } else if (pmode === "lines") { fnew = []; for (j=0; j<nx; j++) { for (i=0; i<nz; i++) { if (i+1 < nz) fnew.push(f[j + nx*i], f[j + nx*(i+1)]); if (j+1 < nx) fnew.push(f[j + nx*i], f[j+1 + nx*i]); } } } } obj.f[pass] = fnew; if (depth_sort) { drawtype = "DYNAMIC_DRAW"; } else { drawtype = "STATIC_DRAW"; } } if (fat_lines) { alias = undefined; obj.nextLoc = gl.getAttribLocation(obj.prog, "aNext"); obj.pointLoc = gl.getAttribLocation(obj.prog, "aPoint"); obj.aspectLoc = gl.getUniformLocation(obj.prog, "uAspect"); obj.lwdLoc = gl.getUniformLocation(obj.prog, "uLwd"); // Expand vertices to turn each segment into a pair of triangles for (pass = 0; pass < obj.passes; pass++) { f = obj.f[pass]; oldrows = f.length; if (obj.pmode[pass] === "lines") break; } if (type === "linestrip") nrows = 4*(oldrows - 1); else nrows = 2*oldrows; vnew = new Array(nrows); fnew = new Array(1.5*nrows); var fnext = new Array(nrows), fpt = new Array(nrows), pt, start, gap = type === "linestrip" ? 3 : 1; // We're going to turn each pair of vertices into 4 new ones, with the "next" and "pt" attributes // added. // We do this by copying the originals in the first pass, adding the new attributes, then in a // second pass add new vertices at the end. for (i = 0; i < v.length; i++) { vnew[i] = v[i].concat([0,0,0,0,0]); } nextofs = stride; pointofs = stride + 3; stride = stride + 5; // Now add the extras last = v.length - 1; ind = 0; alias = new Array(f.length); for (i = 0; i < f.length; i++) alias[i] = []; for (i = 0; i < f.length - 1; i++) { if (type !== "linestrip" && i % 2 == 1) continue; k = ++last; vnew[k] = vnew[f[i]].slice(); for (j=0; j<3; j++) vnew[k][nextofs + j] = vnew[f[i+1]][j]; vnew[k][pointofs] = -1; vnew[k][pointofs+1] = -1; fnew[ind] = k; last++; vnew[last] = vnew[k].slice(); vnew[last][pointofs] = 1; fnew[ind+1] = last; alias[f[i]].push(last-1, last); last++; k = last; vnew[k] = vnew[f[i+1]].slice(); for (j=0; j<3; j++) vnew[k][nextofs + j] = vnew[f[i]][j]; vnew[k][pointofs] = -1; vnew[k][pointofs+1] = 1; fnew[ind+2] = k; fnew[ind+3] = fnew[ind+1]; last++; vnew[last] = vnew[k].slice(); vnew[last][pointofs] = 1; fnew[ind+4] = last; fnew[ind+5] = fnew[ind+2]; ind += 6; alias[f[i+1]].push(last-1, last); } vnew.length = last+1; v = vnew; obj.vertexCount = v.length; if (typeof alias !== "undefined" && typeof obj.alias !== "undefined") { // Already have aliases from previous section? var oldalias = obj.alias, newalias = Array(obj.alias.length); for (i = 0; i < newalias.length; i++) { newalias[i] = oldalias[i].slice(); for (j = 0; j < oldalias[i].length; j++) Array.prototype.push.apply(newalias[i], alias[oldalias[j]]); // pushes each element } obj.alias = newalias; } else obj.alias = alias; for (pass = 0; pass < obj.passes; pass++) if (type === "lines" || type === "linestrip" || obj.pmode[pass] == "lines") { obj.f[pass] = fnew; } if (depth_sort) drawtype = "DYNAMIC_DRAW"; else drawtype = "STATIC_DRAW"; } for (pass = 0; pass < obj.passes; pass++) { if (obj.vertexCount > 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(this.flatten(v)); if (type !== "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 (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 (fixed_size) { obj.textScaleLoc = gl.getUniformLocation(obj.prog, "textScale"); } if (is_lit && !sprites_3d) { obj.normMatLoc = gl.getUniformLocation(obj.prog, "normMatrix"); } if (is_twosided) { obj.frontLoc = gl.getUniformLocation(obj.prog, "front"); } }; /** * Set gl depth test based on object's material * @param { number } id - object to use */ rglwidgetClass.prototype.setDepthTest = function(id) { var gl = this.gl || this.initGL(), tests = {never: gl.NEVER, less: gl.LESS, equal: gl.EQUAL, lequal:gl.LEQUAL, greater: gl.GREATER, notequal: gl.NOTEQUAL, gequal: gl.GEQUAL, always: gl.ALWAYS}, test = tests[this.getMaterial(id, "depth_test")]; gl.depthFunc(test); }; rglwidgetClass.prototype.mode4type = {points : "POINTS", linestrip : "LINE_STRIP", abclines : "LINES", lines : "LINES", sprites : "TRIANGLES", planes : "TRIANGLES", text : "TRIANGLES", quads : "TRIANGLES", surface : "TRIANGLES", triangles : "TRIANGLES"}; /** * Sort objects from back to front * @returns { number[] } * @param { Object } obj - object to sort */ rglwidgetClass.prototype.depthSort = function(obj) { var n = obj.centers.length, depths = new Float32Array(n), result = new Array(n), compare = function(i,j) { return depths[j] - depths[i]; }, z, w; for(i=0; i<n; i++) { z = this.prmvMatrix.m13*obj.centers[i][0] + this.prmvMatrix.m23*obj.centers[i][1] + this.prmvMatrix.m33*obj.centers[i][2] + this.prmvMatrix.m43; w = this.prmvMatrix.m14*obj.centers[i][0] + this.prmvMatrix.m24*obj.centers[i][1] + this.prmvMatrix.m34*obj.centers[i][2] + this.prmvMatrix.m44; depths[i] = z/w; result[i] = i; } result.sort(compare); return result; }; rglwidgetClass.prototype.disableArrays = function(obj, enabled) { var gl = this.gl || this.initGL(), objLocs = ["normLoc", "texLoc", "ofsLoc", "pointLoc", "nextLoc"], thisLocs = ["posLoc", "colLoc"], i, attr; for (i = 0; i < objLocs.length; i++) if (enabled[objLocs[i]]) gl.disableVertexAttribArray(obj[objLocs[i]]); for (i = 0; i < thisLocs.length; i++) if (enabled[thisLocs[i]]) gl.disableVertexAttribArray(this[objLocs[i]]); if (typeof obj.userAttributes !== "undefined") { for (attr in obj.userAttribSizes) { // Not all attributes may have been used gl.disableVertexAttribArray( obj.userAttribLocations[attr] ); } } } /** * Draw an object in a subscene * @param { number } id - object to draw * @param { number } subsceneid - id of subscene */ rglwidgetClass.prototype.drawObj = function(id, subsceneid) { var obj = this.getObj(id), subscene = this.getObj(subsceneid), flags = obj.flags, type = obj.type, is_lit = flags & this.f_is_lit, has_texture = flags & this.f_has_texture, fixed_quads = flags & this.f_fixed_quads, is_transparent = flags & this.f_is_transparent, depth_sort = flags & this.f_depth_sort, sprites_3d = flags & this.f_sprites_3d, sprite_3d = flags & this.f_sprite_3d, is_lines = flags & this.f_is_lines, fat_lines = flags & this.f_fat_lines, is_points = flags & this.f_is_points, fixed_size = flags & this.f_fixed_size, is_twosided = (flags & this.f_is_twosided) > 0, gl = this.gl || this.initGL(), mat, sphereMV, baseofs, ofs, sscale, i, count, light, pass, mode, pmode, attr, enabled = {}; if (typeof id !== "number") { this.alertOnce("drawObj id is "+typeof id); } if (type === "planes") { if (obj.bbox !== subscene.par3d.bbox || !obj.initialized) { this.planeUpdateTriangles(id, subscene.par3d.bbox); } } if (!obj.initialized) this.initObj(id); if (type === "clipplanes") { count = obj.offsets.length; var IMVClip = []; for (i=0; i < count; i++) { IMVClip[i] = this.multMV(this.invMatrix, obj.vClipplane.slice(4*i, 4*(i+1))); } obj.IMVClip = IMVClip; return; } if (type === "light" || type === "bboxdeco" || !obj.vertexCount) return; if (!is_transparent && obj.someHidden) { is_transparent = true; depth_sort = ["triangles", "quads", "surface", "spheres", "sprites", "text"].indexOf(type) >= 0; } this.setDepthTest(id); if (sprites_3d) { var norigs = obj.vertices.length, savenorm = new CanvasMatrix4(this.normMatrix); this.origs = obj.vertices; this.usermat = new Float32Array(obj.userMatrix.getAsArray()); this.radii = obj.radii; this.normMatrix = subscene.spriteNormmat; for (this.iOrig=0; this.iOrig < norigs; this.iOrig++) { for (i=0; i < obj.objects.length; i++) { this.drawObj(obj.objects[i], subsceneid); } } this.normMatrix = savenorm; return; } else { gl.useProgram(obj.prog); } if (typeof obj.polygon_offset !== "undefined") { gl.polygonOffset(obj.polygon_offset[0], obj.polygon_offset[1]); gl.enable(gl.POLYGON_OFFSET_FILL); } if (sprite_3d) { gl.uniform3fv(obj.origLoc, new Float32Array(this.origs[this.iOrig])); if (this.radii.length > 1) { gl.uniform1f(obj.sizeLoc, this.radii[this.iOrig][0]); } else { gl.uniform1f(obj.sizeLoc, this.radii[0][0]); } gl.uniformMatrix4fv(obj.usermatLoc, false, this.usermat); } if (type === "spheres") { gl.bindBuffer(gl.ARRAY_BUFFER, this.sphere.buf); } else { 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()) ); var clipcheck = 0, clipplaneids = subscene.clipplanes, clip, j; for (i=0; i < clipplaneids.length; i++) { clip = this.getObj(clipplaneids[i]); for (j=0; j < clip.offsets.length; j++) { gl.uniform4fv(obj.clipLoc[clipcheck + j], clip.IMVClip[j]); } clipcheck += clip.offsets.length; } if (typeof obj.clipLoc !== "undefined") for (i=clipcheck; i < obj.clipLoc.length; i++) gl.uniform4f(obj.clipLoc[i], 0,0,0,0); if (is_lit) { gl.uniformMatrix4fv( obj.normMatLoc, false, new Float32Array(this.normMatrix.getAsArray()) ); gl.uniform3fv( obj.emissionLoc, obj.emission); gl.uniform1f( obj.shininessLoc, obj.shininess); for (i=0; i < subscene.lights.length; i++) { light = this.getObj(subscene.lights[i]); if (!light.initialized) this.initObj(subscene.lights[i]); gl.uniform3fv( obj.ambientLoc[i], this.componentProduct(light.ambient, obj.ambient)); gl.uniform3fv( obj.specularLoc[i], this.componentProduct(light.specular, obj.specular)); gl.uniform3fv( obj.diffuseLoc[i], light.diffuse); gl.uniform3fv( obj.lightDirLoc[i], light.lightDir); gl.uniform1i( obj.viewpointLoc[i], light.viewpoint); gl.uniform1i( obj.finiteLoc[i], light.finite); } for (i=subscene.lights.length; i < obj.nlights; i++) { gl.uniform3f( obj.ambientLoc[i], 0,0,0); gl.uniform3f( obj.specularLoc[i], 0,0,0); gl.uniform3f( obj.diffuseLoc[i], 0,0,0); } } if (fixed_size) { gl.uniform2f( obj.textScaleLoc, 0.75/this.vp.width, 0.75/this.vp.height); } gl.enableVertexAttribArray( this.posLoc ); enabled.posLoc = true; var nc = obj.colorCount; count = obj.vertexCount; if (type === "spheres") { subscene = this.getObj(subsceneid); var scale = subscene.par3d.scale, scount = count, indices; gl.vertexAttribPointer(this.posLoc, 3, gl.FLOAT, false, 4*this.sphere.vOffsets.stride, 0); gl.enableVertexAttribArray(obj.normLoc ); enabled.normLoc = true; gl.vertexAttribPointer(obj.normLoc, 3, gl.FLOAT, false, 4*this.sphere.vOffsets.stride, 0); gl.disableVertexAttribArray( this.colLoc ); var sphereNorm = new CanvasMatrix4(); sphereNorm.scale(scale[0], scale[1], scale[2]); sphereNorm.multRight(this.normMatrix); gl.uniformMatrix4fv( obj.normMatLoc, false, new Float32Array(sphereNorm.getAsArray()) ); if (nc == 1) { gl.vertexAttrib4fv( this.colLoc, new Float32Array(obj.onecolor)); } if (has_texture) { gl.enableVertexAttribArray( obj.texLoc ); enabled.texLoc = true; gl.vertexAttribPointer(obj.texLoc, 2, gl.FLOAT, false, 4*this.sphere.vOffsets.stride, 4*this.sphere.vOffsets.tofs); gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, obj.texture); gl.uniform1i( obj.sampler, 0); } if (depth_sort) indices = this.depthSort(obj); for (i = 0; i < scount; i++) { sphereMV = new CanvasMatrix4(); if (depth_sort) { baseofs = indices[i]*obj.vOffsets.stride; } else { baseofs = i*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(this.mvMatrix); gl.uniformMatrix4fv( obj.mvMatLoc, false, new Float32Array(sphereMV.getAsArray()) ); if (nc > 1) { ofs = baseofs + obj.vOffsets.cofs; gl.vertexAttrib4f( this.colLoc, obj.values[ofs], obj.values[ofs+1], obj.values[ofs+2], obj.values[ofs+3] ); } gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.sphere.ibuf); gl.drawElements(gl.TRIANGLES, this.sphere.sphereCount, gl.UNSIGNED_SHORT, 0); } this.disableArrays(obj, enabled); if (typeof obj.polygon_offset !== "undefined") gl.disable(gl.POLYGON_OFFSET_FILL); return; } else { if (obj.colorCount === 1) { gl.disableVertexAttribArray( this.colLoc ); gl.vertexAttrib4fv( this.colLoc, new Float32Array(obj.onecolor)); } else { gl.enableVertexAttribArray( this.colLoc ); enabled.colLoc = true; gl.vertexAttribPointer(this.colLoc, 4, gl.FLOAT, false, 4*obj.vOffsets.stride, 4*obj.vOffsets.cofs); } } if (is_lit && obj.vOffsets.nofs > 0) { gl.enableVertexAttribArray( obj.normLoc ); enabled.normLoc = true; gl.vertexAttribPointer(obj.normLoc, 3, gl.FLOAT, false, 4*obj.vOffsets.stride, 4*obj.vOffsets.nofs); } if (has_texture || type === "text") { gl.enableVertexAttribArray( obj.texLoc ); enabled.texLoc = true; 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); } if (fixed_quads) { gl.enableVertexAttribArray( obj.ofsLoc ); enabled.ofsLoc = true; gl.vertexAttribPointer(obj.ofsLoc, 2, gl.FLOAT, false, 4*obj.vOffsets.stride, 4*obj.vOffsets.oofs); } if (typeof obj.userAttributes !== "undefined") { for (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]); } } 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.length === "undefined") gl.uniform1f(loc, uniform); else if (typeof uniform[0].length === "undefined") { 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 (uniform.length == 4 && uniform[0].length == 4) gl.uniformMatrix4fv(loc, false, new Float32Array(uniform.getAsArray())); else console.warn("unsupported uniform matrix"); } } } for (pass = 0; pass < obj.passes; pass++) { pmode = obj.pmode[pass]; if (pmode === "culled") continue; mode = fat_lines && (is_lines || pmode == "lines") ? "TRIANGLES" : this.mode4type[type]; if (depth_sort && pmode == "filled") {// Don't try depthsorting on wireframe or points var faces = this.depthSort(obj), nfaces = faces.length, frowsize = Math.floor(obj.f[pass].length/nfaces); if (type !== "spheres") { var f = obj.index_uint ? new Uint32Array(obj.f[pass].length) : new Uint16Array(obj.f[pass].length); for (i=0; i<nfaces; i++) { for (j=0; j<frowsize; j++) { f[frowsize*i + j] = obj.f[pass][frowsize*faces[i] + j]; } } gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, obj.ibuf[pass]); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, f, gl.DYNAMIC_DRAW); } } if (is_twosided) gl.uniform1i(obj.frontLoc, pass !== 0); if (type !== "spheres") gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, obj.ibuf[pass]); if (type === "sprites" || type === "text" || type === "quads") { count = count * 6/4; } else if (type === "surface") { count = obj.f[pass].length; } count = obj.f[pass].length; if (!is_lines && pmode === "lines" && !fat_lines) { mode = "LINES"; } else if (pmode === "points") { mode = "POINTS"; } if ((is_lines || pmode === "lines") && 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(id, "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); } if (typeof obj.polygon_offset !== "undefined") gl.disable(gl.POLYGON_OFFSET_FILL); }; /** * 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) { var gl = this.gl || this.initGL(), obj = this.getObj(id), bg, i; if (!obj.initialized) this.initObj(id); if (obj.colors.length) { bg = obj.colors[0]; gl.clearColor(bg[0], bg[1], bg[2], bg[3]); gl.depthMask(true); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); } if (typeof obj.quad !== "undefined") { this.prMatrix.makeIdentity(); this.mvMatrix.makeIdentity(); gl.disable(gl.BLEND); gl.disable(gl.DEPTH_TEST); gl.depthMask(false); for (i=0; i < obj.quad.length; i++) this.drawObj(obj.quad[i], subsceneid); } }; /** * Draw a subscene * @param { number } subsceneid - id of subscene * @param { boolean } opaquePass - is this the opaque drawing pass? */ rglwidgetClass.prototype.drawSubscene = function(subsceneid, opaquePass) { var gl = this.gl || this.initGL(), sub = this.getObj(subsceneid), objects = this.scene.objects, subids = sub.objects, subscene_has_faces = false, subscene_needs_sorting = false, flags, i, obj; if (sub.par3d.skipRedraw) return; for (i=0; i < subids.length; i++) { obj = objects[subids[i]]; flags = obj.flags; if (typeof flags !== "undefined") { subscene_has_faces |= (flags & this.f_is_lit) & !(flags & this.f_fixed_quads); obj.is_transparent = (flags & this.f_is_transparent) || obj.someHidden; subscene_needs_sorting |= (flags & this.f_depth_sort) || obj.is_transparent; } } this.setViewport(subsceneid); if (typeof sub.backgroundId !== "undefined" && opaquePass) this.drawBackground(sub.backgroundId, subsceneid); if (subids.length) { this.setprMatrix(subsceneid); this.setmvMatrix(subsceneid); if (subscene_has_faces) { this.setnormMatrix(subsceneid); if ((sub.flags & this.f_sprites_3d) && typeof sub.spriteNormmat === "undefined") { sub.spriteNormmat = new CanvasMatrix4(this.normMatrix); } } if (subscene_needs_sorting) this.setprmvMatrix(); var clipids = sub.clipplanes; if (typeof clipids === "undefined") { console.warn("bad clipids"); } if (clipids.length > 0) { this.invMatrix = new CanvasMatrix4(this.mvMatrix); this.invMatrix.invert(); for (i = 0; i < clipids.length; i++) this.drawObj(clipids[i], subsceneid); } subids = sub.opaque.concat(sub.transparent); if (opaquePass) { gl.enable(gl.DEPTH_TEST); gl.depthMask(true); gl.disable(gl.BLEND); for (i = 0; i < subids.length; i++) { if (!this.getObj(subids[i]).is_transparent) this.drawObj(subids[i], subsceneid); } } else { gl.depthMask(false); gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE); gl.enable(gl.BLEND); for (i = 0; i < subids.length; i++) { if (this.getObj(subids[i]).is_transparent) this.drawObj(subids[i], subsceneid); } } subids = sub.subscenes; for (i = 0; i < subids.length; i++) { this.drawSubscene(subids[i], opaquePass); } } }; /** * 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 = this.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.state = "inactive"; this.delFromSubscene(this.scene.brushId, this.select.subscene); } this.drawScene(); }; /** * Compute mouse coordinates relative to current canvas * @returns { Object } * @param { Object } event - event object from mouse click */ rglwidgetClass.prototype.relMouseCoords = function(event) { var totalOffsetX = 0, totalOffsetY = 0, currentElement = this.canvas; do { totalOffsetX += currentElement.offsetLeft; totalOffsetY += currentElement.offsetTop; currentElement = currentElement.offsetParent; } while(currentElement); var canvasX = event.pageX - totalOffsetX, canvasY = event.pageY - totalOffsetY; return {x:canvasX, y:canvasY}; }; /** * Set mouse handlers for the scene */ rglwidgetClass.prototype.setMouseHandlers = function() { var self = this, activeSubscene, handler, handlers = {}, drag = 0; handlers.rotBase = 0; this.screenToVector = function(x, y) { var viewport = this.getObj(activeSubscene).par3d.viewport, width = viewport.width*this.canvas.width, height = viewport.height*this.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 = this.getObj(activeSubscene), activeModel = this.getObj(this.useid(activeSub.id, "model")), i, l = activeModel.par3d.listeners; handlers.rotBase = this.screenToVector(x, y); this.saveMat = []; for (i = 0; i < l.length; i++) { activeSub = this.getObj(l[i]); activeSub.saveMat = new CanvasMatrix4(activeSub.par3d.userMatrix); } }; handlers.trackballmove = function(x,y) { var rotCurrent = this.screenToVector(x,y), rotBase = handlers.rotBase, dot = rotBase[0]*rotCurrent[0] + rotBase[1]*rotCurrent[1] + rotBase[2]*rotCurrent[2], angle = Math.acos( dot/this.vlen(rotBase)/this.vlen(rotCurrent) )*180.0/Math.PI, axis = this.xprod(rotBase, rotCurrent), objects = this.scene.objects, activeSub = this.getObj(activeSubscene), activeModel = this.getObj(this.useid(activeSub.id, "model")), l = activeModel.par3d.listeners, i; for (i = 0; i < l.length; i++) { activeSub = this.getObj(l[i]); activeSub.par3d.userMatrix.load(objects[l[i]].saveMat); activeSub.par3d.userMatrix.rotate(angle, axis[0], axis[1], axis[2]); } this.drawScene(); }; handlers.trackballend = 0; this.clamp = function(x, lo, hi) { return Math.max(lo, Math.min(x, hi)); }; this.screenToPolar = function(x,y) { var viewport = this.getObj(activeSubscene).par3d.viewport, width = viewport.width*this.canvas.width, height = viewport.height*this.canvas.height, r = Math.min(width, height)/2, dx = this.clamp(x - width/2, -r, r), dy = this.clamp(y - height/2, -r, r); return [Math.asin(dx/r), Math.asin(-dy/r)]; }; handlers.polardown = function(x,y) { var activeSub = this.getObj(activeSubscene), activeModel = this.getObj(this.useid(activeSub.id, "model")), i, l = activeModel.par3d.listeners; handlers.dragBase = this.screenToPolar(x, y); this.saveMat = []; for (i = 0; i < l.length; i++) { activeSub = this.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)]; } }; handlers.polarmove = function(x,y) { var dragCurrent = this.screenToPolar(x,y), activeSub = this.getObj(activeSubscene), activeModel = this.getObj(this.useid(activeSub.id, "model")), objects = this.scene.objects, l = activeModel.par3d.listeners, i, changepos = []; for (i = 0; i < l.length; i++) { activeSub = this.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); } this.drawScene(); }; handlers.polarend = 0; handlers.axisdown = function(x,y) { handlers.rotBase = this.screenToVector(x, this.canvas.height/2); var activeSub = this.getObj(activeSubscene), activeModel = this.getObj(this.useid(activeSub.id, "model")), i, l = activeModel.par3d.listeners; for (i = 0; i < l.length; i++) { activeSub = this.getObj(l[i]); activeSub.saveMat = new CanvasMatrix4(activeSub.par3d.userMatrix); } }; handlers.axismove = function(x,y) { var rotCurrent = this.screenToVector(x, this.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 = this.getObj(activeSubscene), activeModel = this.getObj(this.useid(activeSub.id, "model")), i, l = activeModel.par3d.listeners; for (i = 0; i < l.length; i++) { activeSub = this.getObj(l[i]); activeSub.par3d.userMatrix.load(activeSub.saveMat); activeSub.par3d.userMatrix.multLeft(rotMat); } this.drawScene(); }; handlers.axisend = 0; handlers.y0zoom = 0; handlers.zoom0 = 0; handlers.zoomdown = function(x, y) { var activeSub = this.getObj(activeSubscene), activeProjection = this.getObj(this.useid(activeSub.id, "projection")), i, l = activeProjection.par3d.listeners; handlers.y0zoom = y; for (i = 0; i < l.length; i++) { activeSub = this.getObj(l[i]); activeSub.zoom0 = Math.log(activeSub.par3d.zoom); } }; handlers.zoommove = function(x, y) { var activeSub = this.getObj(activeSubscene), activeProjection = this.getObj(this.useid(activeSub.id, "projection")), i, l = activeProjection.par3d.listeners; for (i = 0; i < l.length; i++) { activeSub = this.getObj(l[i]); activeSub.par3d.zoom = Math.exp(activeSub.zoom0 + (y-handlers.y0zoom)/this.canvas.height); } this.drawScene(); }; handlers.zoomend = 0; handlers.y0fov = 0; handlers.fovdown = function(x, y) { handlers.y0fov = y; var activeSub = this.getObj(activeSubscene), activeProjection = this.getObj(this.useid(activeSub.id, "projection")), i, l = activeProjection.par3d.listeners; for (i = 0; i < l.length; i++) { activeSub = this.getObj(l[i]); activeSub.fov0 = activeSub.par3d.FOV; } }; handlers.fovmove = function(x, y) { var activeSub = this.getObj(activeSubscene), activeProjection = this.getObj(this.useid(activeSub.id, "projection")), i, l = activeProjection.par3d.listeners; for (i = 0; i < l.length; i++) { activeSub = this.getObj(l[i]); activeSub.par3d.FOV = Math.max(1, Math.min(179, activeSub.fov0 + 180*(y-handlers.y0fov)/this.canvas.height)); } this.drawScene(); }; handlers.fovend = 0; handlers.selectingdown = function(x, y) { var viewport = this.getObj(activeSubscene).par3d.viewport, width = viewport.width*this.canvas.width, height = viewport.height*this.canvas.height, p = {x: 2.0*x/width - 1.0, y: 2.0*y/height - 1.0}; this.select.region = {p1: p, p2: p}; if (this.select.subscene && this.select.subscene != activeSubscene) this.delFromSubscene(this.scene.brushId, this.select.subscene); this.select.subscene = activeSubscene; this.addToSubscene(this.scene.brushId, activeSubscene); this.select.state = "changing"; if (typeof this.scene.brushId !== "undefined") this.getObj(this.scene.brushId).initialized = false; this.drawScene(); }; handlers.selectingmove = function(x, y) { var viewport = this.getObj(activeSubscene).par3d.viewport, width = viewport.width*this.canvas.width, height = viewport.height*this.canvas.height; if (this.select.state === "inactive") return; this.select.region.p2 = {x: 2.0*x/width - 1.0, y: 2.0*y/height - 1.0}; if (typeof this.scene.brushId !== "undefined") this.getObj(this.scene.brushId).initialized = false; this.drawScene(); }; handlers.selectingend = 0; this.canvas.onmousedown = function ( ev ){ 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 = ["left", "middle", "right"][ev.which-1]; 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."); }; this.canvas.onmouseup = function ( ev ){ if ( drag === 0 ) return; var f = handlers[handler + "end"]; if (f) { f.call(self); ev.preventDefault(); } drag = 0; }; this.canvas.onmouseout = this.canvas.onmouseup; this.canvas.onmousemove = function ( ev ) { if ( drag === 0 ) return; var f = handlers[handler + "move"]; if (f) { var coords = self.relMouseCoords(ev); coords.y = self.canvas.height - coords.y; coords = self.translateCoords(activeSubscene, coords); f.call(self, coords.x, coords.y); } }; handlers.wheelHandler = function(ev) { var del = 1.02, i; if (ev.shiftKey) del = 1.002; var ds = ((ev.detail || ev.wheelDelta) > 0) ? del : (1 / del); 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(); ev.preventDefault(); }; this.canvas.addEventListener("DOMMouseScroll", handlers.wheelHandler, false); this.canvas.addEventListener("mousewheel", handlers.wheelHandler, false); }; /** * 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; }; /** * 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; }; /** * 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}; }; /** * Initialize the sphere object */ rglwidgetClass.prototype.initSphere = function() { var verts = this.scene.sphereVerts, reuse = verts.reuse, result; if (typeof reuse !== "undefined") { var prev = document.getElementById(reuse).rglinstance.sphere; result = {values: prev.values, vOffsets: prev.vOffsets, it: prev.it}; } else result = {values: new Float32Array(this.flatten(this.cbind(this.transpose(verts.vb), this.transpose(verts.texcoords)))), it: new Uint16Array(this.flatten(this.transpose(verts.it))), vOffsets: {vofs:0, cofs:-1, nofs:-1, radofs:-1, oofs:-1, tofs:3, nextofs:-1, pointofs:-1, stride:5}}; result.sphereCount = result.it.length; this.sphere = result; }; /** * 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), width = this.canvas.width, height = this.canvas.height, 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]]; }; /** * Do the gl part of initializing the sphere */ rglwidgetClass.prototype.initSphereGL = function() { var gl = this.gl || this.initGL(), sphere = this.sphere; if (gl.isContextLost()) return; sphere.buf = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, sphere.buf); gl.bufferData(gl.ARRAY_BUFFER, sphere.values, gl.STATIC_DRAW); sphere.ibuf = gl.createBuffer(); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, sphere.ibuf); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, sphere.it, gl.STATIC_DRAW); return; }; /** * 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.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.") } }) } }; /** * Restart the WebGL canvas */ rglwidgetClass.prototype.restartCanvas = function() { var newcanvas = document.createElement("canvas"), self = this; newcanvas.width = this.el.width; newcanvas.height = this.el.height; newcanvas.addEventListener("webglcontextrestored", this.onContextRestored, false); newcanvas.addEventListener("webglcontextlost", this.onContextLost, false); while (this.el.firstChild) { this.el.removeChild(this.el.firstChild); } this.el.appendChild(newcanvas); this.canvas = newcanvas; 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; Object.keys(objs).forEach(function(key){ var id = parseInt(key, 10), obj = self.getObj(id); if (typeof obj.reuse !== "undefined") self.copyObj(id, obj.reuse); }); Object.keys(objs).forEach(function(key){ self.initSubscene(parseInt(key, 10)); }); this.setMouseHandlers(); this.initSphere(); this.onContextRestored = function(event) { 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(mutation) { self.slide.rgl.forEach(function(scene) { scene.lazyLoadScene.call(window); });});}); observer.observe(this.slide, { attributes: true, attributeFilter:["class"] }); } } }; /** * Start the writeWebGL scene. This is only used by writeWebGL; rglwidget has no debug element and does the drawing in rglwidget.js. */ rglwidgetClass.prototype.start = function() { if (typeof this.prefix !== "undefined") { this.debugelement = document.getElementById(this.prefix + "debug"); this.debug(""); } this.drag = 0; this.drawScene(); }; /** * Display a debug message * @param { string } msg - The message to display * @param { Object } [img] - Image to insert before message */ rglwidgetClass.prototype.debug = function(msg, img) { if (typeof this.debugelement !== "undefined" && this.debugelement !== null) { this.debugelement.innerHTML = msg; if (typeof img !== "undefined") { this.debugelement.insertBefore(img, this.debugelement.firstChild); } } else if (msg !== "") alert(msg); }; /** * Get the snapshot image of this scene * @returns { Object } The img DOM element */ rglwidgetClass.prototype.getSnapshot = function() { var img; if (typeof this.scene.snapshot !== "undefined") { img = document.createElement("img"); img.src = this.scene.snapshot; img.alt = "Snapshot"; } return img; }; /** * Initial test for WebGL */ rglwidgetClass.prototype.initGL0 = function() { if (!window.WebGLRenderingContext){ alert("Your browser does not support WebGL. See http://get.webgl.org"); return; } }; /** * If we are in an ioslides or slidy presentation, get the * DOM element of the current slide * @returns { Object } */ rglwidgetClass.prototype.getSlide = function() { var result = this.el, done = false; while (result && !done && this.scene.context.rmarkdown) { switch(this.scene.context.rmarkdown) { case "ioslides_presentation": if (result.tagName === "SLIDE") return result; break; case "slidy_presentation": if (result.tagName === "DIV" && result.classList.contains("slide")) return result; break; default: return null; } result = result.parentElement; } return null; }; /** * Is this scene visible in the browser? * @returns { boolean } */ rglwidgetClass.prototype.isInBrowserViewport = function() { var rect = this.canvas.getBoundingClientRect(), windHeight = (window.innerHeight || document.documentElement.clientHeight), windWidth = (window.innerWidth || document.documentElement.clientWidth); if (this.scene.context && this.scene.context.rmarkdown !== null) { if (this.slide) return (this.scene.context.rmarkdown === "ioslides_presentation" && this.slide.classList.contains("current")) || (this.scene.context.rmarkdown === "slidy_presentation" && !this.slide.classList.contains("hidden")); } return ( rect.top >= -windHeight && rect.left >= -windWidth && rect.bottom <= 2*windHeight && rect.right <= 2*windWidth); }; /** * Initialize WebGL * @returns { Object } the WebGL context */ rglwidgetClass.prototype.initGL = function() { var self = this; 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); this.index_uint = this.gl.getExtension("OES_element_index_uint"); var save = this.startDrawing(); this.initSphereGL(); Object.keys(this.scene.objects).forEach(function(key){ self.initObj(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; }; /** * Draw the whole scene */ rglwidgetClass.prototype.drawScene = function() { var gl = this.gl || this.initGL(), wasDrawing = this.startDrawing(); if (!wasDrawing) { if (this.select.state !== "inactive") this.selectionChanged(); gl.enable(gl.DEPTH_TEST); gl.depthFunc(gl.LEQUAL); gl.clearDepth(1.0); gl.clearColor(1,1,1,1); gl.depthMask(true); // Must be true before clearing depth buffer gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); this.drawSubscene(this.scene.rootSubscene, true); this.drawSubscene(this.scene.rootSubscene, false); } this.stopDrawing(wasDrawing); }; /** * 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, entries, subsceneid, adds = [], deletes = [], ismissing = function(x) { return fullset.indexOf(x) < 0; }, tointeger = function(x) { return parseInt(x, 10); }; if (isNaN(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 = this.repeatToLen(control.properties, ncol), objids = this.repeatToLen(control.objids, ncol), property, objid = objids[0], obj = this.getObj(objid), propvals, i, 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<entries.length; j++) { entry = entries[j]; newprop = properties[j]; newid = objids[j]; if (newprop !== property || newid != objid) { if (typeof property !== "undefined") putPropvals(propvals); property = newprop; objid = newid; obj = this.getObj(objid); propvals = getPropvals(); } if (control.interp) { v1 = values[ncol*(i-1) + j]; v2 = values[ncol*i + j]; this.setElement(propvals, entry, p*v1 + (1-p)*v2); } else if (!direct) { this.setElement(propvals, entry, values[ncol*value + j]); } else { this.setElement(propvals, entry, value[j]); } } putPropvals(propvals); needsBinding = []; for (j=0; j < entries.length; j++) { if (properties[j] === "values" && needsBinding.indexOf(objids[j]) === -1) { needsBinding.push(objids[j]); } } for (j=0; j < needsBinding.length; j++) { gl = this.gl || this.initGL(); obj = this.getObj(needsBinding[j]); gl.bindBuffer(gl.ARRAY_BUFFER, obj.buf); gl.bufferData(gl.ARRAY_BUFFER, obj.values, gl.STATIC_DRAW); } }; /** * Change the requested vertices * @param { Object } el - Element of the control; not used. * @param { Object } control - The vertext setter control data. */ rglwidgetClass.prototype.vertexSetter = function(el, control) { var svals = [].concat(control.param), j, k, p, a, propvals, stride, ofs, obj, entry, attrib, ofss = {x:"vofs", y:"vofs", z:"vofs", red:"cofs", green:"cofs", blue:"cofs", alpha:"cofs", radii:"radofs", nx:"nofs", ny:"nofs", nz:"nofs", ox:"oofs", oy:"oofs", oz:"oofs", ts:"tofs", tt:"tofs"}, pos = {x:0, y:1, z:2, red:0, green:1, blue:2, alpha:3,radii:0, nx:0, ny:1, nz:2, ox:0, oy:1, oz:2, ts:0, tt:1}, values = control.values, direct = values === null, ncol, interp = control.interp, vertices = [].concat(control.vertices), attributes = [].concat(control.attributes), value = control.value, newval, aliases, alias; ncol = Math.max(vertices.length, attributes.length); if (!ncol) return; vertices = this.repeatToLen(vertices, ncol); attributes = this.repeatToLen(attributes, ncol); if (direct) interp = false; /* JSON doesn't pass Infinity */ svals[0] = -Infinity; svals[svals.length - 1] = Infinity; for (j = 1; j < svals.length; j++) { if (value <= svals[j]) { if (interp) { if (svals[j] === Infinity) p = 1; else p = (svals[j] - value)/(svals[j] - svals[j-1]); } else { if (svals[j] - value > 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.initObj(control.objid); } propvals = obj.values; aliases = obj.alias; if (typeof aliases === "undefined") aliases = []; for (k=0; k<ncol; k++) { if (direct) { newval = value; } else if (interp) { newval = p*values[j-1][k] + (1-p)*values[j][k]; } else { newval = values[j][k]; } attrib = attributes[k]; vertex = vertices[k]; alias = aliases[vertex]; if (obj.type === "planes" || obj.type === "clipplanes") { ofs = ["nx", "ny", "nz", "offset"].indexOf(attrib); if (ofs >= 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, 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.initObj(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") { 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.initObj(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(); }; /** * Set mouse mode for a subscene * @param { string } mode - name of mode * @param { number } button - button number (1 to 3) * @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 = ["left", "right", "middle"][button - 1]; if (!stayActive && sub.par3d.mouseMode[which] === "selecting") this.clearBrush(null); sub.par3d.mouseMode[which] = mode; }; /** * 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; }; /** * Start playing timer object */ 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); };</script> <div id="testgldiv" class="rglWebGL"></div> <script type="text/javascript"> var testgldiv = document.getElementById("testgldiv"), testglrgl = new rglwidgetClass(); testgldiv.width = 505; testgldiv.height = 505; testglrgl.initialize(testgldiv, {"material":{"color":"#000000","alpha":1,"lit":true,"ambient":"#000000","specular":"#FFFFFF","emission":"#000000","shininess":50,"smooth":true,"front":"filled","back":"filled","size":3,"lwd":1,"fog":false,"point_antialias":false,"line_antialias":false,"texture":null,"textype":"rgb","texmipmap":false,"texminfilter":"linear","texmagfilter":"linear","texenvmap":false,"depth_mask":true,"depth_test":"less","isTransparent":false,"polygon_offset":[0,0]},"rootSubscene":1,"objects":{"7":{"id":7,"type":"points","material":{"lit":false,"size":8},"vertices":[[3624,69.05,41.3],[6315,69.31,66.7],[4530,70.55,58.1],[3378,70.66,39.9],[5114,71.71,62.6],[4884,72.06,63.9],[5348,72.48,56],[4809,70.06,54.6],[4815,70.66,52.6],[4091,68.54,40.6],[4963,73.6,61.9],[4119,71.87,59.5],[5107,70.14,52.6],[4458,70.88,52.9],[4628,72.56,59],[4669,72.58,59.9],[3712,70.1,38.5],[3545,68.76,42.2],[3694,70.39,54.7],[5299,70.22,52.3],[4755,71.83,58.5],[4751,70.63,52.8],[4675,72.96,57.6],[3098,68.09,41],[4254,70.69,48.8],[4347,70.56,59.2],[4508,72.6,59.3],[5149,69.03,65.2],[4281,71.23,57.6],[5237,70.93,52.5],[3601,70.32,55.2],[4903,70.55,52.7],[3875,69.21,38.5],[5087,72.78,50.3],[4561,70.82,53.2],[3983,71.42,51.6],[4660,72.13,60],[4449,70.43,50.2],[4558,71.9,46.4],[3635,67.96,37.8],[4167,72.08,53.3],[3821,70.11,41.8],[4188,70.9,47.4],[4022,72.9,67.3],[3907,71.64,57.1],[4701,70.08,47.8],[4864,71.72,63.5],[3617,69.48,41.6],[4468,72.48,54.5],[4566,70.29,62.9]],"colors":[[0,0,0,1]],"centers":[[3624,69.05,41.3],[6315,69.31,66.7],[4530,70.55,58.1],[3378,70.66,39.9],[5114,71.71,62.6],[4884,72.06,63.9],[5348,72.48,56],[4809,70.06,54.6],[4815,70.66,52.6],[4091,68.54,40.6],[4963,73.6,61.9],[4119,71.87,59.5],[5107,70.14,52.6],[4458,70.88,52.9],[4628,72.56,59],[4669,72.58,59.9],[3712,70.1,38.5],[3545,68.76,42.2],[3694,70.39,54.7],[5299,70.22,52.3],[4755,71.83,58.5],[4751,70.63,52.8],[4675,72.96,57.6],[3098,68.09,41],[4254,70.69,48.8],[4347,70.56,59.2],[4508,72.6,59.3],[5149,69.03,65.2],[4281,71.23,57.6],[5237,70.93,52.5],[3601,70.32,55.2],[4903,70.55,52.7],[3875,69.21,38.5],[5087,72.78,50.3],[4561,70.82,53.2],[3983,71.42,51.6],[4660,72.13,60],[4449,70.43,50.2],[4558,71.9,46.4],[3635,67.96,37.8],[4167,72.08,53.3],[3821,70.11,41.8],[4188,70.9,47.4],[4022,72.9,67.3],[3907,71.64,57.1],[4701,70.08,47.8],[4864,71.72,63.5],[3617,69.48,41.6],[4468,72.48,54.5],[4566,70.29,62.9]],"ignoreExtent":false,"flags":4096},"9":{"id":9,"type":"text","material":{"lit":false},"vertices":[[4706.5,67.00402,32.79975]],"colors":[[0,0,0,1]],"texts":[["Income"]],"cex":[[1]],"adj":[[0.5,0.5]],"centers":[[4706.5,67.00402,32.79975]],"family":[["sans"]],"font":[[1]],"ignoreExtent":true,"flags":2064},"10":{"id":10,"type":"text","material":{"lit":false},"vertices":[[2552.719,70.78,32.79975]],"colors":[[0,0,0,1]],"texts":[["LifeExp"]],"cex":[[1]],"adj":[[0.5,0.5]],"centers":[[2552.719,70.78,32.79975]],"family":[["sans"]],"font":[[1]],"ignoreExtent":true,"flags":2064},"11":{"id":11,"type":"text","material":{"lit":false},"vertices":[[2552.719,67.00402,52.55]],"colors":[[0,0,0,1]],"texts":[["HSGrad"]],"cex":[[1]],"adj":[[0.5,0.5]],"centers":[[2552.719,67.00402,52.55]],"family":[["sans"]],"font":[[1]],"ignoreExtent":true,"flags":2064},"5":{"id":5,"type":"light","vertices":[[0,0,1]],"colors":[[1,1,1,1],[1,1,1,1],[1,1,1,1]],"viewpoint":true,"finite":false},"4":{"id":4,"type":"background","material":{"fog":true},"colors":[[0.2980392,0.2980392,0.2980392,1]],"centers":[[0,0,0]],"sphere":false,"fogtype":"none","flags":0},"6":{"id":6,"type":"background","material":{"lit":false,"back":"lines"},"colors":[[1,1,1,1]],"centers":[[0,0,0]],"sphere":false,"fogtype":"none","flags":0},"8":{"id":8,"type":"bboxdeco","material":{"front":"lines","back":"lines"},"vertices":[[3500,"NA","NA"],[4000,"NA","NA"],[4500,"NA","NA"],[5000,"NA","NA"],[5500,"NA","NA"],[6000,"NA","NA"],["NA",68,"NA"],["NA",69,"NA"],["NA",70,"NA"],["NA",71,"NA"],["NA",72,"NA"],["NA",73,"NA"],["NA","NA",40],["NA","NA",45],["NA","NA",50],["NA","NA",55],["NA","NA",60],["NA","NA",65]],"colors":[[0,0,0,1]],"draw_front":true,"newIds":[40,41,42,43,44,45,46]},"1":{"id":1,"type":"subscene","par3d":{"antialias":8,"FOV":30,"ignoreExtent":false,"listeners":1,"mouseMode":{"left":"trackball","right":"zoom","middle":"fov","wheel":"pull"},"observer":[0,0,7872.38],"modelMatrix":[[0.5773754,0,0,-2717.417],[0,112.6372,59.16612,-11081.64],[0,-309.4683,21.53471,12900.13],[0,0,0,1]],"projMatrix":[[3.732051,0,0,0],[0,3.732051,0,0],[0,0,-3.863703,-28379.02],[0,0,-1,0]],"skipRedraw":false,"userMatrix":[[1,0,0,0],[0,0.3420201,0.9396926,0],[0,-0.9396926,0.3420201,0],[0,0,0,1]],"userProjection":[[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]],"scale":[0.5773754,329.3293,62.96327],"viewport":{"x":0,"y":0,"width":1,"height":1},"zoom":1,"bbox":[3098,6315,67.96,73.6,37.8,67.3],"windowRect":[90,95,594,599],"family":"sans","font":1,"cex":1,"useFreeType":true,"fontname":"/home/anastasios/R/x86_64-pc-linux-gnu-library/3.6/rgl/fonts/FreeSans.ttf","maxClipPlanes":8,"glVersion":3,"activeSubscene":0},"embeddings":{"viewport":"replace","projection":"replace","model":"replace","mouse":"replace"},"objects":[6,8,7,9,10,11,5,40,41,42,43,44,45,46],"subscenes":[],"flags":6736},"40":{"id":40,"type":"lines","material":{"lit":false},"vertices":[[3500,67.8754,37.3575],[6000,67.8754,37.3575],[3500,67.8754,37.3575],[3500,67.73017,36.59787],[4000,67.8754,37.3575],[4000,67.73017,36.59787],[4500,67.8754,37.3575],[4500,67.73017,36.59787],[5000,67.8754,37.3575],[5000,67.73017,36.59787],[5500,67.8754,37.3575],[5500,67.73017,36.59787],[6000,67.8754,37.3575],[6000,67.73017,36.59787]],"colors":[[0,0,0,1]],"centers":[[4750,67.8754,37.3575],[3500,67.80278,36.97768],[4000,67.80278,36.97768],[4500,67.80278,36.97768],[5000,67.80278,36.97768],[5500,67.80278,36.97768],[6000,67.80278,36.97768]],"ignoreExtent":true,"origId":8,"flags":64},"41":{"id":41,"type":"text","material":{"lit":false},"vertices":[[3500,67.43971,35.07862],[4000,67.43971,35.07862],[4500,67.43971,35.07862],[5000,67.43971,35.07862],[5500,67.43971,35.07862],[6000,67.43971,35.07862]],"colors":[[0,0,0,1]],"texts":[["3500"],["4000"],["4500"],["5000"],["5500"],["6000"]],"cex":[[1]],"adj":[[0.5,0.5]],"centers":[[3500,67.43971,35.07862],[4000,67.43971,35.07862],[4500,67.43971,35.07862],[5000,67.43971,35.07862],[5500,67.43971,35.07862],[6000,67.43971,35.07862]],"family":[["sans"]],"font":[[1]],"ignoreExtent":true,"origId":8,"flags":2064},"42":{"id":42,"type":"lines","material":{"lit":false},"vertices":[[3049.745,68,37.3575],[3049.745,73,37.3575],[3049.745,68,37.3575],[2966.907,68,36.59787],[3049.745,69,37.3575],[2966.907,69,36.59787],[3049.745,70,37.3575],[2966.907,70,36.59787],[3049.745,71,37.3575],[2966.907,71,36.59787],[3049.745,72,37.3575],[2966.907,72,36.59787],[3049.745,73,37.3575],[2966.907,73,36.59787]],"colors":[[0,0,0,1]],"centers":[[3049.745,70.5,37.3575],[3008.326,68,36.97768],[3008.326,69,36.97768],[3008.326,70,36.97768],[3008.326,71,36.97768],[3008.326,72,36.97768],[3008.326,73,36.97768]],"ignoreExtent":true,"origId":8,"flags":64},"43":{"id":43,"type":"text","material":{"lit":false},"vertices":[[2801.232,68,35.07862],[2801.232,69,35.07862],[2801.232,70,35.07862],[2801.232,71,35.07862],[2801.232,72,35.07862],[2801.232,73,35.07862]],"colors":[[0,0,0,1]],"texts":[["68"],["69"],["70"],["71"],["72"],["73"]],"cex":[[1]],"adj":[[0.5,0.5]],"centers":[[2801.232,68,35.07862],[2801.232,69,35.07862],[2801.232,70,35.07862],[2801.232,71,35.07862],[2801.232,72,35.07862],[2801.232,73,35.07862]],"family":[["sans"]],"font":[[1]],"ignoreExtent":true,"origId":8,"flags":2064},"44":{"id":44,"type":"lines","material":{"lit":false},"vertices":[[3049.745,67.8754,40],[3049.745,67.8754,65],[3049.745,67.8754,40],[2966.907,67.73017,40],[3049.745,67.8754,45],[2966.907,67.73017,45],[3049.745,67.8754,50],[2966.907,67.73017,50],[3049.745,67.8754,55],[2966.907,67.73017,55],[3049.745,67.8754,60],[2966.907,67.73017,60],[3049.745,67.8754,65],[2966.907,67.73017,65]],"colors":[[0,0,0,1]],"centers":[[3049.745,67.8754,52.5],[3008.326,67.80278,40],[3008.326,67.80278,45],[3008.326,67.80278,50],[3008.326,67.80278,55],[3008.326,67.80278,60],[3008.326,67.80278,65]],"ignoreExtent":true,"origId":8,"flags":64},"45":{"id":45,"type":"text","material":{"lit":false},"vertices":[[2801.232,67.43971,40],[2801.232,67.43971,45],[2801.232,67.43971,50],[2801.232,67.43971,55],[2801.232,67.43971,60],[2801.232,67.43971,65]],"colors":[[0,0,0,1]],"texts":[["40"],["45"],["50"],["55"],["60"],["65"]],"cex":[[1]],"adj":[[0.5,0.5]],"centers":[[2801.232,67.43971,40],[2801.232,67.43971,45],[2801.232,67.43971,50],[2801.232,67.43971,55],[2801.232,67.43971,60],[2801.232,67.43971,65]],"family":[["sans"]],"font":[[1]],"ignoreExtent":true,"origId":8,"flags":2064},"46":{"id":46,"type":"lines","material":{"lit":false},"vertices":[[3049.745,67.8754,37.3575],[3049.745,73.6846,37.3575],[3049.745,67.8754,67.7425],[3049.745,73.6846,67.7425],[3049.745,67.8754,37.3575],[3049.745,67.8754,67.7425],[3049.745,73.6846,37.3575],[3049.745,73.6846,67.7425],[3049.745,67.8754,37.3575],[6363.255,67.8754,37.3575],[3049.745,67.8754,67.7425],[6363.255,67.8754,67.7425],[3049.745,73.6846,37.3575],[6363.255,73.6846,37.3575],[3049.745,73.6846,67.7425],[6363.255,73.6846,67.7425],[6363.255,67.8754,37.3575],[6363.255,73.6846,37.3575],[6363.255,67.8754,67.7425],[6363.255,73.6846,67.7425],[6363.255,67.8754,37.3575],[6363.255,67.8754,67.7425],[6363.255,73.6846,37.3575],[6363.255,73.6846,67.7425]],"colors":[[0,0,0,1]],"centers":[[3049.745,70.78,37.3575],[3049.745,70.78,67.7425],[3049.745,67.8754,52.55],[3049.745,73.6846,52.55],[4706.5,67.8754,37.3575],[4706.5,67.8754,67.7425],[4706.5,73.6846,37.3575],[4706.5,73.6846,67.7425],[6363.255,70.78,37.3575],[6363.255,70.78,67.7425],[6363.255,67.8754,52.55],[6363.255,73.6846,52.55]],"ignoreExtent":true,"origId":8,"flags":64}},"snapshot":"data:image/png;base64,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v3RyQCcKd9xxB0387ne/69ixoxTxv/3tb61fQQ8gp1JNj6A3F4L+punTp7vd7hYtWtDf7OzsCxcueH3RnJWVFRsb27p1a0re//iP/2hoaKD5SUlJNH/MmDG079J0WVkZW4h60F+5ciUtLe2ll15i/z7zzDN333035fuJEydo4XRi0aFDByoNZWRk0DvK79UCsIVgVTQIenMh6L1U0I6Li/voo49ommKX4njKlCk03aVLl/79+7PQ37hxIz3nnXfeoemRI0eyUj8Vz/v27du7d2+2HFZH/7TM3//+d/aETz75pEmTJnSKsG3btqZNmx48eJAingU9hf6//vUves7169fpfeknR/NotAAmClZNH4N7oU2FT9/brVu33Nxc6d+1a9fOnDnT69s1v/jiC2n+s88+e++999KEVIT3/tQl/aVLl7y+oKf4HinDhiFkqCBPb0cF+ddee429BQt69hPC3HPPPTTH/10A7CJYNT2C3lxO//QrKytpF/zggw+8vhK6fzma5l++fFn6d8mSJVQe/+GHH7p27Zr1k86dO9PTvvrqK69q1c37P1m6dCmV1jt06PCnP/2J/qXz2RdeeIGWQD8t0nNYTX2wtjoAVqZYTR/+uLLAidM//V27dtEu+Pbbb2dnZ9NEYmIile7ZJVP699NPP5WeOWbMGJrz3Xffbdiw4bNbsU4ugwW91I8NGThwIP1auFyu4cOHs1ulfv3rX9PfYcOGsSdIrS2j6RcTwETyanqMI2g6pwf9pk2bKFVbtmy5aNGizz//fOHChZT1kydPpod+/vOf097p8Xj2799PJe64uDh6ZkVFhX/67969+5VXXmHTIZtXXrhwITU19Y9//OPEiRPvuusu1hfmX//6V1qsNGAhlehZ1ks1+wD2Iq+mp9xH0JvL6UH/8ccfU6rOnz9fmkPTsbGxNTU133777SOPPOL2GTFixBtvvMEqc3r16sVaVVI5JSMj44knnmAvpKCnH4wPZHbs2MGeMHLkyH/7t39raGi4cuXKbbfdRs+Xgr5169alpaX0nMcee6x9+/bSBV4A25FX02McQdM5PehZ0xcqmEtztm/fTnMOHz7M/v3hhx/q6+u9vh+ANm3aeH0Xb+mX4I477mjSpMl99913/vx59kwKbsVbn+iHwetr2xMfHy8NOfvhhx/S78c999zDgv6ZZ55p2rQpRXyMr/fKkydPGvkhAOhIXlHDzlPNWh/wIujr6uqaN2/+3nvvSXOWLVvmcrmqqqpyc3P9m7488MADjz/+uNd3F9XOnTtXrly5d+/eKBtBTpgwYcGCBayNzT/+8Q92qxT9tESzTADTBVTTW3xwBSdwetB7ff2XUVGaFeGpKN25c+dhw4bRdN++fe+//37Wv80777xD6f+3v/1N37f2D3ovjgcQRUA1PXZs0yHob44D3qdPH8rx1NRUt9udk5PDbpL64osv2rVr16xZs1atWsXHx7/99tu6vzUdDKx5JQt6dOUKYgiolEfQmw5Bf1NDQ8OePXs8Hk/AbUpUnP/www/Xrl1bVVXF430p6GfMmDFnzpzKykqvr0EnbhMHAQRU02McQdMh6M0UMGwsbioBYfhX0yPoTYdkMZP/KS1Ob0EkFPRSuKNO0nQIejP5hzsOBhCJfzU99m3TIejN5H8woB9XEIl/NT2C3nQIejNJQY8KehCPVE2PcQRNh3AxkxT0qKAH8UjV9FS0R9CbC0FvJun01qxzWzoOWT8NtBpoFwH6ksoxqJY0nUODXnGE2MbGxn379tH8L7/80pjVkILelCNB6hJZgrMK0JG5uzf4c2LQK44Qe+LEiaysLNYlPf0dNGgQ62WeK3YkmDIsA7sqIIdTbNARq7TBnYCmc1zQBxsh9oEHHujcuTPNoWkq1yclJf3Xf/2XAetDx4ApFfTSUFYBUIEDOmLV9GhoYDrHfQHBRoiNjY2dO3euNP/xxx8fMGCAAevDKkyMr6CXaucDoBkc6EgaSMfsFXE6Z30BKiPEdu/e/Ve/+hWbvnbt2l133TV+/Hht73ImEnRua8qJbbCqG5xig45YtSSGlzKds4JeZYTYffv2paam3n///VOmTMnKyuratSsbRkoDNvRrmFi86rqV4ZIX6nExFnTXycfstXA6ZwW9ygixJSUlt99+e8+ePUeNGtWxY8d7772X1dfzRllvVrxSaYuynv3YoHklcMIKNGavhdM5K+hVRohNSEh46aWX2Mzr16+PGDGiW7duDQ0NvFeJHQazAQSFoLcCZwW9ygix9Fca/dX700/C8ePHea8SK1YDCAwXfkznrKBXGSGWYv3UqVPSfI/HgyuTACAGZwW9N/gIsTTzl7/85aVLl7y+Una3bt369Olj8roCAOjBcUEfbITYnTt30py4uLjbbruN5vft27eiosLslQUA0IHjgt4bfITYa9eubdu2bfXq1QHzAQBszYlBDwDgKAh6AADBIegBAASHoAcAEByCHgBAcAh6AADBIegBAASHoAcAEByCHgBAcAh6AADBIegBAASHoAcAEByCHgBAcAh6AADBIegBAASHoAcAEByCHgBAcAh6AADBIegBAASHoAcAEByCHgBAcAh6AADBIegBAASHoAcAEByCHgBAcAh6AADBIegBAASHoAcAEByCHgBAcAh6AADBIegBAASHoAcAEByCHgBAcAh6AADBIegBAASHoAcAEByCHgBAcAh6AADBIegBAASHoAcAEByCHgBAcAh6AADBIejBHkpKSp588slLly6ZvSIA9oOgB3tYunRpTEzM2bNnzV4RAPtB0IM9IOgBNEPQgz34B/3q1atPnjx56NCh3/3ud7/97W/XrFnj/8yvvvrq97//fW5u7rvvvltXVyfNP378+KxZs2j+m2++eeHCBWk+W9pnn332/PPP5+XllZeXNzY2FhcXP/fccy+//PKZM2ekZ1ZVVS1YsICWMG/evG+//Zb7NgPoBEEP9uAf9B07dpw0aVJGRgal/C9+8Quav3jxYva0VatWxcbG3n///f/+7//eqlWrPn36XL9+neavW7cuLi7u3nvvfeKJJ2677bYOHTp8/fXX7CW0tEGDBnXv3p0SPD09PSkp6bHHHrvvvvueffbZxMREmsOWUFpaSg9lZWWNGTOmU6dONF1WVmbShwEQGQQ92ENA0KekpEil8oEDBw4YMIAmrly5Qvmbn5/P5lPZ3O12U9n82rVrbdu2HT9+PJtfXV3dtWvXkSNHsn9paT169Lh69arXdzZA7zJkyJAbN27Qvx6Ph/6lmVTGz8zMpJew+XSi0Ldv3969exv6EQBohaAHewgI+ilTpkgPUbL//Oc/p4mioiKXy3Xx4kXpocLCwn379m3dupVee+zYMWn+O++8Ex8fX19fz5b26quvsvk//vgjPfPPf/4z+5fl/pdffnnw4EGa8C/Cr169muagFRDYAoIe7CEg6F977TXpoRkzZrCgnz9/frt27eSvXbZsGRXtGxoapDklJSW0tG+++YYt7fXXX2fzWdBLlf5Hjx5lQb927VqaoPOArJ907tyZFfa5bTGAbhD0YA8BQS9Fs9cv6BcuXNi6dWv5a//nf/6HXsuq2plPPvmE5rALquEE/aZNm2hiw4YNn93q+++/57bFALpB0IM9hBP0H3/8MT3n1KlT0kMPP/zwvHnz9u/fT/P37NkjzX/11VdbtWpFse4NL+hPnDhBE59++qm0hN27d7/yyiscNxhAPwh6sIdwgp5i+s477xw6dGh1dbX3p4L8li1baDorK6t3797nz5+n6S+++CI5Ofn5559nLw8n6Gk6JyenV69e7CTgzJkzGRkZTzzxhFFbDxAVBD3YQzhB7/U1gkxJSWnevHlqaqrL5Zo+fTqb//e//z09PT0uLi4tLY3mDx8+XKp1CTPoKdy7desWGxt7xx13NGnS5L777mM/GwDWh6AH0Vy9enXz5s2rV68+ceKE//wffvihpKSE5rPg1uDmhs+rAAAAzklEQVTGjRs7d+5cuXLl3r17Gxsb9VhZACMg6AEABIegBwAQHIIeAEBwCHoAAMEh6AEABIegBwAQHIIeAEBwCHoAAMEh6AEABIegBwAQHIIeAEBwCHoAAMEh6AEABIegBwAQHIIeAEBwCHoAAMEh6AEABIegBwAQHIIeAEBwCHoAAMEh6AEABIegBwAQHIIeAEBwCHoAAMEh6AEABIegBwAQHIIeAEBwCHoAAMEh6AEABIegBwAQHIIeAEBwCHoAAMEh6AEABIegBwAQ3P8DUC8PzSGeRqIAAAAASUVORK5CYII=","width":505,"height":505,"sphereVerts":{"vb":[[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.07465783,0.1464466,0.2126075,0.2705981,0.3181896,0.3535534,0.3753303,0.3826834,0.3753303,0.3535534,0.3181896,0.2705981,0.2126075,0.1464466,0.07465783,0,0,0.1379497,0.2705981,0.3928475,0.5,0.5879378,0.6532815,0.6935199,0.7071068,0.6935199,0.6532815,0.5879378,0.5,0.3928475,0.2705981,0.1379497,0,0,0.18024,0.3535534,0.51328,0.6532815,0.7681778,0.8535534,0.9061274,0.9238795,0.9061274,0.8535534,0.7681778,0.6532815,0.51328,0.3535534,0.18024,0,0,0.1950903,0.3826834,0.5555702,0.7071068,0.8314696,0.9238795,0.9807853,1,0.9807853,0.9238795,0.8314696,0.7071068,0.5555702,0.3826834,0.1950903,0,0,0.18024,0.3535534,0.51328,0.6532815,0.7681778,0.8535534,0.9061274,0.9238795,0.9061274,0.8535534,0.7681778,0.6532815,0.51328,0.3535534,0.18024,0,0,0.1379497,0.2705981,0.3928475,0.5,0.5879378,0.6532815,0.6935199,0.7071068,0.6935199,0.6532815,0.5879378,0.5,0.3928475,0.2705981,0.1379497,0,0,0.07465783,0.1464466,0.2126075,0.2705981,0.3181896,0.3535534,0.3753303,0.3826834,0.3753303,0.3535534,0.3181896,0.2705981,0.2126075,0.1464466,0.07465783,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-0,-0.07465783,-0.1464466,-0.2126075,-0.2705981,-0.3181896,-0.3535534,-0.3753303,-0.3826834,-0.3753303,-0.3535534,-0.3181896,-0.2705981,-0.2126075,-0.1464466,-0.07465783,-0,-0,-0.1379497,-0.2705981,-0.3928475,-0.5,-0.5879378,-0.6532815,-0.6935199,-0.7071068,-0.6935199,-0.6532815,-0.5879378,-0.5,-0.3928475,-0.2705981,-0.1379497,-0,-0,-0.18024,-0.3535534,-0.51328,-0.6532815,-0.7681778,-0.8535534,-0.9061274,-0.9238795,-0.9061274,-0.8535534,-0.7681778,-0.6532815,-0.51328,-0.3535534,-0.18024,-0,-0,-0.1950903,-0.3826834,-0.5555702,-0.7071068,-0.8314696,-0.9238795,-0.9807853,-1,-0.9807853,-0.9238795,-0.8314696,-0.7071068,-0.5555702,-0.3826834,-0.1950903,-0,-0,-0.18024,-0.3535534,-0.51328,-0.6532815,-0.7681778,-0.8535534,-0.9061274,-0.9238795,-0.9061274,-0.8535534,-0.7681778,-0.6532815,-0.51328,-0.3535534,-0.18024,-0,-0,-0.1379497,-0.2705981,-0.3928475,-0.5,-0.5879378,-0.6532815,-0.6935199,-0.7071068,-0.6935199,-0.6532815,-0.5879378,-0.5,-0.3928475,-0.2705981,-0.1379497,-0,-0,-0.07465783,-0.1464466,-0.2126075,-0.2705981,-0.3181896,-0.3535534,-0.3753303,-0.3826834,-0.3753303,-0.3535534,-0.3181896,-0.2705981,-0.2126075,-0.1464466,-0.07465783,-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[-1,-0.9807853,-0.9238795,-0.8314696,-0.7071068,-0.5555702,-0.3826834,-0.1950903,0,0.1950903,0.3826834,0.5555702,0.7071068,0.8314696,0.9238795,0.9807853,1,-1,-0.9807853,-0.9238795,-0.8314696,-0.7071068,-0.5555702,-0.3826834,-0.1950903,0,0.1950903,0.3826834,0.5555702,0.7071068,0.8314696,0.9238795,0.9807853,1,-1,-0.9807853,-0.9238795,-0.8314696,-0.7071068,-0.5555702,-0.3826834,-0.1950903,0,0.1950903,0.3826834,0.5555702,0.7071068,0.8314696,0.9238795,0.9807853,1,-1,-0.9807853,-0.9238795,-0.8314696,-0.7071068,-0.5555702,-0.3826834,-0.1950903,0,0.1950903,0.3826834,0.5555702,0.7071068,0.8314696,0.9238795,0.9807853,1,-1,-0.9807853,-0.9238795,-0.8314696,-0.7071068,-0.5555702,-0.3826834,-0.1950903,0,0.1950903,0.3826834,0.5555702,0.7071068,0.8314696,0.9238795,0.9807853,1,-1,-0.9807853,-0.9238795,-0.8314696,-0.7071068,-0.5555702,-0.3826834,-0.1950903,0,0.1950903,0.3826834,0.5555702,0.7071068,0.8314696,0.9238795,0.9807853,1,-1,-0.9807853,-0.9238795,-0.8314696,-0.7071068,-0.5555702,-0.3826834,-0.1950903,0,0.1950903,0.3826834,0.5555702,0.7071068,0.8314696,0.9238795,0.9807853,1,-1,-0.9807853,-0.9238795,-0.8314696,-0.7071068,-0.5555702,-0.3826834,-0.1950903,0,0.1950903,0.3826834,0.5555702,0.7071068,0.8314696,0.9238795,0.9807853,1,-1,-0.9807853,-0.9238795,-0.8314696,-0.7071068,-0.5555702,-0.3826834,-0.1950903,0,0.1950903,0.3826834,0.5555702,0.7071068,0.8314696,0.9238795,0.9807853,1,-1,-0.9807853,-0.9238795,-0.8314696,-0.7071068,-0.5555702,-0.3826834,-0.1950903,0,0.1950903,0.3826834,0.5555702,0.7071068,0.8314696,0.9238795,0.9807853,1,-1,-0.9807853,-0.9238795,-0.8314696,-0.7071068,-0.5555702,-0.3826834,-0.1950903,0,0.1950903,0.3826834,0.5555702,0.7071068,0.8314696,0.9238795,0.9807853,1,-1,-0.9807853,-0.9238795,-0.8314696,-0.7071068,-0.5555702,-0.3826834,-0.1950903,0,0.1950903,0.3826834,0.5555702,0.7071068,0.8314696,0.9238795,0.9807853,1,-1,-0.9807853,-0.9238795,-0.8314696,-0.7071068,-0.5555702,-0.3826834,-0.1950903,0,0.1950903,0.3826834,0.5555702,0.7071068,0.8314696,0.9238795,0.9807853,1,-1,-0.9807853,-0.9238795,-0.8314696,-0.7071068,-0.5555702,-0.3826834,-0.1950903,0,0.1950903,0.3826834,0.5555702,0.7071068,0.8314696,0.9238795,0.9807853,1,-1,-0.9807853,-0.9238795,-0.8314696,-0.7071068,-0.5555702,-0.3826834,-0.1950903,0,0.1950903,0.3826834,0.5555702,0.7071068,0.8314696,0.9238795,0.9807853,1,-1,-0.9807853,-0.9238795,-0.8314696,-0.7071068,-0.5555702,-0.3826834,-0.1950903,0,0.1950903,0.3826834,0.5555702,0.7071068,0.8314696,0.9238795,0.9807853,1,-1,-0.9807853,-0.9238795,-0.8314696,-0.7071068,-0.5555702,-0.3826834,-0.1950903,0,0.1950903,0.3826834,0.5555702,0.7071068,0.8314696,0.9238795,0.9807853,1],[0,0.1950903,0.3826834,0.5555702,0.7071068,0.8314696,0.9238795,0.9807853,1,0.9807853,0.9238795,0.8314696,0.7071068,0.5555702,0.3826834,0.1950903,0,0,0.18024,0.3535534,0.51328,0.6532815,0.7681778,0.8535534,0.9061274,0.9238795,0.9061274,0.8535534,0.7681778,0.6532815,0.51328,0.3535534,0.18024,0,0,0.1379497,0.2705981,0.3928475,0.5,0.5879378,0.6532815,0.6935199,0.7071068,0.6935199,0.6532815,0.5879378,0.5,0.3928475,0.2705981,0.1379497,0,0,0.07465783,0.1464466,0.2126075,0.2705981,0.3181896,0.3535534,0.3753303,0.3826834,0.3753303,0.3535534,0.3181896,0.2705981,0.2126075,0.1464466,0.07465783,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-0,-0.07465783,-0.1464466,-0.2126075,-0.2705981,-0.3181896,-0.3535534,-0.3753303,-0.3826834,-0.3753303,-0.3535534,-0.3181896,-0.2705981,-0.2126075,-0.1464466,-0.07465783,-0,-0,-0.1379497,-0.2705981,-0.3928475,-0.5,-0.5879378,-0.6532815,-0.6935199,-0.7071068,-0.6935199,-0.6532815,-0.5879378,-0.5,-0.3928475,-0.2705981,-0.1379497,-0,-0,-0.18024,-0.3535534,-0.51328,-0.6532815,-0.7681778,-0.8535534,-0.9061274,-0.9238795,-0.9061274,-0.8535534,-0.7681778,-0.6532815,-0.51328,-0.3535534,-0.18024,-0,-0,-0.1950903,-0.3826834,-0.5555702,-0.7071068,-0.8314696,-0.9238795,-0.9807853,-1,-0.9807853,-0.9238795,-0.8314696,-0.7071068,-0.5555702,-0.3826834,-0.1950903,-0,-0,-0.18024,-0.3535534,-0.51328,-0.6532815,-0.7681778,-0.8535534,-0.9061274,-0.9238795,-0.9061274,-0.8535534,-0.7681778,-0.6532815,-0.51328,-0.3535534,-0.18024,-0,-0,-0.1379497,-0.2705981,-0.3928475,-0.5,-0.5879378,-0.6532815,-0.6935199,-0.7071068,-0.6935199,-0.6532815,-0.5879378,-0.5,-0.3928475,-0.2705981,-0.1379497,-0,-0,-0.07465783,-0.1464466,-0.2126075,-0.2705981,-0.3181896,-0.3535534,-0.3753303,-0.3826834,-0.3753303,-0.3535534,-0.3181896,-0.2705981,-0.2126075,-0.1464466,-0.07465783,-0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.07465783,0.1464466,0.2126075,0.2705981,0.3181896,0.3535534,0.3753303,0.3826834,0.3753303,0.3535534,0.3181896,0.2705981,0.2126075,0.1464466,0.07465783,0,0,0.1379497,0.2705981,0.3928475,0.5,0.5879378,0.6532815,0.6935199,0.7071068,0.6935199,0.6532815,0.5879378,0.5,0.3928475,0.2705981,0.1379497,0,0,0.18024,0.3535534,0.51328,0.6532815,0.7681778,0.8535534,0.9061274,0.9238795,0.9061274,0.8535534,0.7681778,0.6532815,0.51328,0.3535534,0.18024,0,0,0.1950903,0.3826834,0.5555702,0.7071068,0.8314696,0.9238795,0.9807853,1,0.9807853,0.9238795,0.8314696,0.7071068,0.5555702,0.3826834,0.1950903,0]],"it":[[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270],[17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288],[18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271]],"material":[],"normals":null,"texcoords":[[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.0625,0.0625,0.0625,0.0625,0.0625,0.0625,0.0625,0.0625,0.0625,0.0625,0.0625,0.0625,0.0625,0.0625,0.0625,0.0625,0.0625,0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.1875,0.1875,0.1875,0.1875,0.1875,0.1875,0.1875,0.1875,0.1875,0.1875,0.1875,0.1875,0.1875,0.1875,0.1875,0.1875,0.1875,0.25,0.25,0.25,0.25,0.25,0.25,0.25,0.25,0.25,0.25,0.25,0.25,0.25,0.25,0.25,0.25,0.25,0.3125,0.3125,0.3125,0.3125,0.3125,0.3125,0.3125,0.3125,0.3125,0.3125,0.3125,0.3125,0.3125,0.3125,0.3125,0.3125,0.3125,0.375,0.375,0.375,0.375,0.375,0.375,0.375,0.375,0.375,0.375,0.375,0.375,0.375,0.375,0.375,0.375,0.375,0.4375,0.4375,0.4375,0.4375,0.4375,0.4375,0.4375,0.4375,0.4375,0.4375,0.4375,0.4375,0.4375,0.4375,0.4375,0.4375,0.4375,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5625,0.5625,0.5625,0.5625,0.5625,0.5625,0.5625,0.5625,0.5625,0.5625,0.5625,0.5625,0.5625,0.5625,0.5625,0.5625,0.5625,0.625,0.625,0.625,0.625,0.625,0.625,0.625,0.625,0.625,0.625,0.625,0.625,0.625,0.625,0.625,0.625,0.625,0.6875,0.6875,0.6875,0.6875,0.6875,0.6875,0.6875,0.6875,0.6875,0.6875,0.6875,0.6875,0.6875,0.6875,0.6875,0.6875,0.6875,0.75,0.75,0.75,0.75,0.75,0.75,0.75,0.75,0.75,0.75,0.75,0.75,0.75,0.75,0.75,0.75,0.75,0.8125,0.8125,0.8125,0.8125,0.8125,0.8125,0.8125,0.8125,0.8125,0.8125,0.8125,0.8125,0.8125,0.8125,0.8125,0.8125,0.8125,0.875,0.875,0.875,0.875,0.875,0.875,0.875,0.875,0.875,0.875,0.875,0.875,0.875,0.875,0.875,0.875,0.875,0.9375,0.9375,0.9375,0.9375,0.9375,0.9375,0.9375,0.9375,0.9375,0.9375,0.9375,0.9375,0.9375,0.9375,0.9375,0.9375,0.9375,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],[0,0.0625,0.125,0.1875,0.25,0.3125,0.375,0.4375,0.5,0.5625,0.625,0.6875,0.75,0.8125,0.875,0.9375,1,0,0.0625,0.125,0.1875,0.25,0.3125,0.375,0.4375,0.5,0.5625,0.625,0.6875,0.75,0.8125,0.875,0.9375,1,0,0.0625,0.125,0.1875,0.25,0.3125,0.375,0.4375,0.5,0.5625,0.625,0.6875,0.75,0.8125,0.875,0.9375,1,0,0.0625,0.125,0.1875,0.25,0.3125,0.375,0.4375,0.5,0.5625,0.625,0.6875,0.75,0.8125,0.875,0.9375,1,0,0.0625,0.125,0.1875,0.25,0.3125,0.375,0.4375,0.5,0.5625,0.625,0.6875,0.75,0.8125,0.875,0.9375,1,0,0.0625,0.125,0.1875,0.25,0.3125,0.375,0.4375,0.5,0.5625,0.625,0.6875,0.75,0.8125,0.875,0.9375,1,0,0.0625,0.125,0.1875,0.25,0.3125,0.375,0.4375,0.5,0.5625,0.625,0.6875,0.75,0.8125,0.875,0.9375,1,0,0.0625,0.125,0.1875,0.25,0.3125,0.375,0.4375,0.5,0.5625,0.625,0.6875,0.75,0.8125,0.875,0.9375,1,0,0.0625,0.125,0.1875,0.25,0.3125,0.375,0.4375,0.5,0.5625,0.625,0.6875,0.75,0.8125,0.875,0.9375,1,0,0.0625,0.125,0.1875,0.25,0.3125,0.375,0.4375,0.5,0.5625,0.625,0.6875,0.75,0.8125,0.875,0.9375,1,0,0.0625,0.125,0.1875,0.25,0.3125,0.375,0.4375,0.5,0.5625,0.625,0.6875,0.75,0.8125,0.875,0.9375,1,0,0.0625,0.125,0.1875,0.25,0.3125,0.375,0.4375,0.5,0.5625,0.625,0.6875,0.75,0.8125,0.875,0.9375,1,0,0.0625,0.125,0.1875,0.25,0.3125,0.375,0.4375,0.5,0.5625,0.625,0.6875,0.75,0.8125,0.875,0.9375,1,0,0.0625,0.125,0.1875,0.25,0.3125,0.375,0.4375,0.5,0.5625,0.625,0.6875,0.75,0.8125,0.875,0.9375,1,0,0.0625,0.125,0.1875,0.25,0.3125,0.375,0.4375,0.5,0.5625,0.625,0.6875,0.75,0.8125,0.875,0.9375,1,0,0.0625,0.125,0.1875,0.25,0.3125,0.375,0.4375,0.5,0.5625,0.625,0.6875,0.75,0.8125,0.875,0.9375,1,0,0.0625,0.125,0.1875,0.25,0.3125,0.375,0.4375,0.5,0.5625,0.625,0.6875,0.75,0.8125,0.875,0.9375,1]]},"context":{"shiny":false,"rmarkdown":"xaringan::moon_reader"},"crosstalk":{"key":[],"group":[],"id":[],"options":[]}}); testglrgl.prefix = "testgl"; </script> <p id="testgldebug"> You must enable Javascript to view this page properly.</p> <script>testglrgl.start();</script> --- # Lessons learnt - With 2 variables we can do a 2-dimensional (2D) scatter plot. - This can be interpreted very easily - With 3 variables we can do a 3D scatter plot - This doesn't look great on a flat screen - We get more insight by *rotating* the plot - What about 5 variables? What about 100 variables? --- # Principal components - Later on we will cover the method of *principal components*. -- - This can be used to combine the variables into a single index. -- - This single index explains most of the variation in the data. -- - On the next slide we plot the first principal component on a map of the USA. --- # One PC on a map <img src="Intro_files/figure-html/unnamed-chunk-5-1.png" style="display: block; margin: auto;" /> --- # Multidimensional Scaling - Two states close to one another on the scatterplot had similar levels of income, and life expectancy. -- - Can we do something similar but for all five variables. -- - The method of *multidimensional scaling* finds two coordinates so that states close to one another on the scatterplot are close to one another across all five characteristics. --- #Multidimensional Scaling
--- # Factor Analysis - Later on we will attempt to attach possible interpretations to these constructed variables. -- - This is the objective of factor modelling. -- - In this context *factor* refers to a latent construct that cannot be directly observed but can be measured via its correlation with observable data. --- # Cluster Analysis - Even from the simple analysis so far, it appears that similar states can placed into a small number of groups. -- - The use of algorithms that achieve this task is known as *cluster analysis*. -- - It is extremely useful across a number of business disciplines. -- - On the following slide we group the states into two clusters and present them in different colors. --- # Cluster Analysis: Example
--- class: inverse, middle, center # A broad understanding of data. --- # Numerical Data - So far we looked at *numerical* data - This is also called *metric* data or *ratio* data - The differences and ratios between values of the variable have some meaningful interpretation. -- - A state with a mean income of $5000 has twice as much income as a state with a mean income of $2500. --- # Non-metric data - *Categorical* (or *nominal*) Data - The value of the variable does not measure the *size* of some characteristic. - *Ordinal* data - Different values of the variable measure *more* or *less* of a characteristic but not *how much* more or *how much* less. --- # Beer Data <div style="border: 1px solid #ddd; padding: 0px; overflow-y: scroll; height:500px; "><table class="table table-striped table-hover table-condensed" style="margin-left: auto; margin-right: auto;"> <thead> <tr> <th style="text-align:left;position: sticky; top:0; background-color: #FFFFFF;"> beer </th> <th style="text-align:left;position: sticky; top:0; background-color: #FFFFFF;"> rating </th> <th style="text-align:left;position: sticky; top:0; background-color: #FFFFFF;"> origin </th> <th style="text-align:left;position: sticky; top:0; background-color: #FFFFFF;"> avail </th> <th style="text-align:right;position: sticky; top:0; background-color: #FFFFFF;"> price </th> <th style="text-align:right;position: sticky; top:0; background-color: #FFFFFF;"> cost </th> <th style="text-align:right;position: sticky; top:0; background-color: #FFFFFF;"> calories </th> <th style="text-align:right;position: sticky; top:0; background-color: #FFFFFF;"> sodium </th> <th style="text-align:right;position: sticky; top:0; background-color: #FFFFFF;"> alcohol </th> <th style="text-align:left;position: sticky; top:0; background-color: #FFFFFF;"> light </th> </tr> </thead> <tbody> <tr> <td style="text-align:left;"> Budweiser Light </td> <td style="text-align:left;"> Good </td> <td style="text-align:left;"> USA </td> <td style="text-align:left;"> National </td> <td style="text-align:right;"> 2.63 </td> <td style="text-align:right;"> 0.44 </td> <td style="text-align:right;"> 113 </td> <td style="text-align:right;"> 8 </td> <td style="text-align:right;"> 3.7 </td> <td style="text-align:left;"> LIGHT </td> </tr> <tr> <td style="text-align:left;"> Coors Light </td> <td style="text-align:left;"> Good </td> <td style="text-align:left;"> USA </td> <td style="text-align:left;"> Regional </td> <td style="text-align:right;"> 2.73 </td> <td style="text-align:right;"> 0.46 </td> <td style="text-align:right;"> 102 </td> <td style="text-align:right;"> 15 </td> <td style="text-align:right;"> 4.1 </td> <td style="text-align:left;"> LIGHT </td> </tr> <tr> <td style="text-align:left;"> Michelob Light </td> <td style="text-align:left;"> Good </td> <td style="text-align:left;"> USA </td> <td style="text-align:left;"> National </td> <td style="text-align:right;"> 2.99 </td> <td style="text-align:right;"> 0.50 </td> <td style="text-align:right;"> 135 </td> <td style="text-align:right;"> 11 </td> <td style="text-align:right;"> 4.2 </td> <td style="text-align:left;"> LIGHT </td> </tr> <tr> <td style="text-align:left;"> Miller Light </td> <td style="text-align:left;"> Good </td> <td style="text-align:left;"> USA </td> <td style="text-align:left;"> National </td> <td style="text-align:right;"> 2.55 </td> <td style="text-align:right;"> 0.43 </td> <td style="text-align:right;"> 99 </td> <td style="text-align:right;"> 10 </td> <td style="text-align:right;"> 4.3 </td> <td style="text-align:left;"> LIGHT </td> </tr> <tr> <td style="text-align:left;"> Olympia Gold Light </td> <td style="text-align:left;"> Fair </td> <td style="text-align:left;"> USA </td> <td style="text-align:left;"> Regional </td> <td style="text-align:right;"> 2.75 </td> <td style="text-align:right;"> 0.46 </td> <td style="text-align:right;"> 72 </td> <td style="text-align:right;"> 6 </td> <td style="text-align:right;"> 2.9 </td> <td style="text-align:left;"> LIGHT </td> </tr> <tr> <td style="text-align:left;"> Pabst Extra Light </td> <td style="text-align:left;"> Fair </td> <td style="text-align:left;"> USA </td> <td style="text-align:left;"> National </td> <td style="text-align:right;"> 2.29 </td> <td style="text-align:right;"> 0.38 </td> <td style="text-align:right;"> 68 </td> <td style="text-align:right;"> 15 </td> <td style="text-align:right;"> 2.3 </td> <td style="text-align:left;"> LIGHT </td> </tr> <tr> <td style="text-align:left;"> Schlitz Light </td> <td style="text-align:left;"> Fair </td> <td style="text-align:left;"> USA </td> <td style="text-align:left;"> National </td> <td style="text-align:right;"> 2.79 </td> <td style="text-align:right;"> 0.47 </td> <td style="text-align:right;"> 97 </td> <td style="text-align:right;"> 7 </td> <td style="text-align:right;"> 4.2 </td> <td style="text-align:left;"> LIGHT </td> </tr> <tr> <td style="text-align:left;"> Anchor Steam </td> <td style="text-align:left;"> VeryGood </td> <td style="text-align:left;"> USA </td> <td style="text-align:left;"> Regional </td> <td style="text-align:right;"> 7.19 </td> <td style="text-align:right;"> 1.20 </td> <td style="text-align:right;"> 154 </td> <td style="text-align:right;"> 17 </td> <td style="text-align:right;"> 4.7 </td> <td style="text-align:left;"> NONLIGHT </td> </tr> <tr> <td style="text-align:left;"> Augsberger </td> <td style="text-align:left;"> Good </td> <td style="text-align:left;"> USA </td> <td style="text-align:left;"> Regional </td> <td style="text-align:right;"> 2.39 </td> <td style="text-align:right;"> 0.40 </td> <td style="text-align:right;"> 175 </td> <td style="text-align:right;"> 24 </td> <td style="text-align:right;"> 5.5 </td> <td style="text-align:left;"> NONLIGHT </td> </tr> <tr> <td style="text-align:left;"> Becks </td> <td style="text-align:left;"> Good </td> <td style="text-align:left;"> Germany </td> <td style="text-align:left;"> Regional </td> <td style="text-align:right;"> 4.55 </td> <td style="text-align:right;"> 0.76 </td> <td style="text-align:right;"> 150 </td> <td style="text-align:right;"> 19 </td> <td style="text-align:right;"> 4.7 </td> <td style="text-align:left;"> NONLIGHT </td> </tr> <tr> <td style="text-align:left;"> Blatz </td> <td style="text-align:left;"> Fair </td> <td style="text-align:left;"> USA </td> <td style="text-align:left;"> Regional </td> <td style="text-align:right;"> 1.79 </td> <td style="text-align:right;"> 0.30 </td> <td style="text-align:right;"> 144 </td> <td style="text-align:right;"> 13 </td> <td style="text-align:right;"> 4.6 </td> <td style="text-align:left;"> NONLIGHT </td> </tr> <tr> <td style="text-align:left;"> Budweiser </td> <td style="text-align:left;"> VeryGood </td> <td style="text-align:left;"> USA </td> <td style="text-align:left;"> National </td> <td style="text-align:right;"> 2.59 </td> <td style="text-align:right;"> 0.43 </td> <td style="text-align:right;"> 144 </td> <td style="text-align:right;"> 15 </td> <td style="text-align:right;"> 4.7 </td> <td style="text-align:left;"> NONLIGHT </td> </tr> <tr> <td style="text-align:left;"> Coors </td> <td style="text-align:left;"> Good </td> <td style="text-align:left;"> USA </td> <td style="text-align:left;"> Regional </td> <td style="text-align:right;"> 2.65 </td> <td style="text-align:right;"> 0.44 </td> <td style="text-align:right;"> 140 </td> <td style="text-align:right;"> 18 </td> <td style="text-align:right;"> 4.6 </td> <td style="text-align:left;"> NONLIGHT </td> </tr> <tr> <td style="text-align:left;"> Dos Equis </td> <td style="text-align:left;"> Good </td> <td style="text-align:left;"> Mexico </td> <td style="text-align:left;"> Regional </td> <td style="text-align:right;"> 4.22 </td> <td style="text-align:right;"> 0.70 </td> <td style="text-align:right;"> 145 </td> <td style="text-align:right;"> 14 </td> <td style="text-align:right;"> 4.5 </td> <td style="text-align:left;"> NONLIGHT </td> </tr> <tr> <td style="text-align:left;"> Hamms </td> <td style="text-align:left;"> Fair </td> <td style="text-align:left;"> USA </td> <td style="text-align:left;"> Regional </td> <td style="text-align:right;"> 2.59 </td> <td style="text-align:right;"> 0.43 </td> <td style="text-align:right;"> 136 </td> <td style="text-align:right;"> 19 </td> <td style="text-align:right;"> 4.4 </td> <td style="text-align:left;"> NONLIGHT </td> </tr> <tr> <td style="text-align:left;"> Heilmans Old Style </td> <td style="text-align:left;"> Fair </td> <td style="text-align:left;"> USA </td> <td style="text-align:left;"> Regional </td> <td style="text-align:right;"> 2.59 </td> <td style="text-align:right;"> 0.43 </td> <td style="text-align:right;"> 144 </td> <td style="text-align:right;"> 24 </td> <td style="text-align:right;"> 4.9 </td> <td style="text-align:left;"> NONLIGHT </td> </tr> <tr> <td style="text-align:left;"> Heineken </td> <td style="text-align:left;"> VeryGood </td> <td style="text-align:left;"> Holland </td> <td style="text-align:left;"> National </td> <td style="text-align:right;"> 4.59 </td> <td style="text-align:right;"> 0.77 </td> <td style="text-align:right;"> 152 </td> <td style="text-align:right;"> 11 </td> <td style="text-align:right;"> 5.0 </td> <td style="text-align:left;"> NONLIGHT </td> </tr> <tr> <td style="text-align:left;"> Henry Weinhard </td> <td style="text-align:left;"> VeryGood </td> <td style="text-align:left;"> USA </td> <td style="text-align:left;"> Regional </td> <td style="text-align:right;"> 3.65 </td> <td style="text-align:right;"> 0.61 </td> <td style="text-align:right;"> 149 </td> <td style="text-align:right;"> 7 </td> <td style="text-align:right;"> 4.7 </td> <td style="text-align:left;"> NONLIGHT </td> </tr> <tr> <td style="text-align:left;"> Kirin </td> <td style="text-align:left;"> Good </td> <td style="text-align:left;"> Japan </td> <td style="text-align:left;"> Regional </td> <td style="text-align:right;"> 4.75 </td> <td style="text-align:right;"> 0.79 </td> <td style="text-align:right;"> 149 </td> <td style="text-align:right;"> 6 </td> <td style="text-align:right;"> 5.0 </td> <td style="text-align:left;"> NONLIGHT </td> </tr> <tr> <td style="text-align:left;"> Kronenbourg </td> <td style="text-align:left;"> VeryGood </td> <td style="text-align:left;"> France </td> <td style="text-align:left;"> Regional </td> <td style="text-align:right;"> 4.39 </td> <td style="text-align:right;"> 0.73 </td> <td style="text-align:right;"> 170 </td> <td style="text-align:right;"> 7 </td> <td style="text-align:right;"> 5.2 </td> <td style="text-align:left;"> NONLIGHT </td> </tr> <tr> <td style="text-align:left;"> Labatts </td> <td style="text-align:left;"> VeryGood </td> <td style="text-align:left;"> Canada </td> <td style="text-align:left;"> Regional </td> <td style="text-align:right;"> 3.15 </td> <td style="text-align:right;"> 0.53 </td> <td style="text-align:right;"> 147 </td> <td style="text-align:right;"> 17 </td> <td style="text-align:right;"> 5.0 </td> <td style="text-align:left;"> NONLIGHT </td> </tr> <tr> <td style="text-align:left;"> Lowenbrau </td> <td style="text-align:left;"> VeryGood </td> <td style="text-align:left;"> USA </td> <td style="text-align:left;"> National </td> <td style="text-align:right;"> 2.89 </td> <td style="text-align:right;"> 0.48 </td> <td style="text-align:right;"> 157 </td> <td style="text-align:right;"> 15 </td> <td style="text-align:right;"> 4.9 </td> <td style="text-align:left;"> NONLIGHT </td> </tr> <tr> <td style="text-align:left;"> Michelob </td> <td style="text-align:left;"> VeryGood </td> <td style="text-align:left;"> USA </td> <td style="text-align:left;"> National </td> <td style="text-align:right;"> 2.99 </td> <td style="text-align:right;"> 0.50 </td> <td style="text-align:right;"> 162 </td> <td style="text-align:right;"> 10 </td> <td style="text-align:right;"> 5.0 </td> <td style="text-align:left;"> NONLIGHT </td> </tr> <tr> <td style="text-align:left;"> Miller High Life </td> <td style="text-align:left;"> VeryGood </td> <td style="text-align:left;"> USA </td> <td style="text-align:left;"> National </td> <td style="text-align:right;"> 2.49 </td> <td style="text-align:right;"> 0.42 </td> <td style="text-align:right;"> 149 </td> <td style="text-align:right;"> 17 </td> <td style="text-align:right;"> 4.7 </td> <td style="text-align:left;"> NONLIGHT </td> </tr> <tr> <td style="text-align:left;"> Molson </td> <td style="text-align:left;"> VeryGood </td> <td style="text-align:left;"> Canada </td> <td style="text-align:left;"> Regional </td> <td style="text-align:right;"> 3.35 </td> <td style="text-align:right;"> 0.56 </td> <td style="text-align:right;"> 154 </td> <td style="text-align:right;"> 17 </td> <td style="text-align:right;"> 5.1 </td> <td style="text-align:left;"> NONLIGHT </td> </tr> <tr> <td style="text-align:left;"> Old Milwaukee </td> <td style="text-align:left;"> Good </td> <td style="text-align:left;"> USA </td> <td style="text-align:left;"> Regional </td> <td style="text-align:right;"> 1.69 </td> <td style="text-align:right;"> 0.28 </td> <td style="text-align:right;"> 145 </td> <td style="text-align:right;"> 23 </td> <td style="text-align:right;"> 4.6 </td> <td style="text-align:left;"> NONLIGHT </td> </tr> <tr> <td style="text-align:left;"> Olympia </td> <td style="text-align:left;"> Good </td> <td style="text-align:left;"> USA </td> <td style="text-align:left;"> Regional </td> <td style="text-align:right;"> 2.65 </td> <td style="text-align:right;"> 0.44 </td> <td style="text-align:right;"> 153 </td> <td style="text-align:right;"> 27 </td> <td style="text-align:right;"> 4.6 </td> <td style="text-align:left;"> NONLIGHT </td> </tr> <tr> <td style="text-align:left;"> Pabst Blue Ribbon </td> <td style="text-align:left;"> Good </td> <td style="text-align:left;"> USA </td> <td style="text-align:left;"> National </td> <td style="text-align:right;"> 2.29 </td> <td style="text-align:right;"> 0.38 </td> <td style="text-align:right;"> 152 </td> <td style="text-align:right;"> 8 </td> <td style="text-align:right;"> 4.9 </td> <td style="text-align:left;"> NONLIGHT </td> </tr> <tr> <td style="text-align:left;"> Rolling Rock </td> <td style="text-align:left;"> Fair </td> <td style="text-align:left;"> USA </td> <td style="text-align:left;"> Regional </td> <td style="text-align:right;"> 2.15 </td> <td style="text-align:right;"> 0.36 </td> <td style="text-align:right;"> 144 </td> <td style="text-align:right;"> 8 </td> <td style="text-align:right;"> 4.7 </td> <td style="text-align:left;"> NONLIGHT </td> </tr> <tr> <td style="text-align:left;"> Schlitz </td> <td style="text-align:left;"> VeryGood </td> <td style="text-align:left;"> USA </td> <td style="text-align:left;"> National </td> <td style="text-align:right;"> 2.59 </td> <td style="text-align:right;"> 0.43 </td> <td style="text-align:right;"> 151 </td> <td style="text-align:right;"> 19 </td> <td style="text-align:right;"> 4.9 </td> <td style="text-align:left;"> NONLIGHT </td> </tr> <tr> <td style="text-align:left;"> Schmidts </td> <td style="text-align:left;"> Good </td> <td style="text-align:left;"> USA </td> <td style="text-align:left;"> Regional </td> <td style="text-align:right;"> 1.79 </td> <td style="text-align:right;"> 0.30 </td> <td style="text-align:right;"> 147 </td> <td style="text-align:right;"> 7 </td> <td style="text-align:right;"> 4.7 </td> <td style="text-align:left;"> NONLIGHT </td> </tr> <tr> <td style="text-align:left;"> Scotch Buy (Safeway) </td> <td style="text-align:left;"> Fair </td> <td style="text-align:left;"> USA </td> <td style="text-align:left;"> Regional </td> <td style="text-align:right;"> 1.59 </td> <td style="text-align:right;"> 0.27 </td> <td style="text-align:right;"> 145 </td> <td style="text-align:right;"> 18 </td> <td style="text-align:right;"> 4.5 </td> <td style="text-align:left;"> NONLIGHT </td> </tr> <tr> <td style="text-align:left;"> St Pauli Girl </td> <td style="text-align:left;"> Fair </td> <td style="text-align:left;"> Germany </td> <td style="text-align:left;"> Regional </td> <td style="text-align:right;"> 4.59 </td> <td style="text-align:right;"> 0.77 </td> <td style="text-align:right;"> 144 </td> <td style="text-align:right;"> 21 </td> <td style="text-align:right;"> 4.7 </td> <td style="text-align:left;"> NONLIGHT </td> </tr> <tr> <td style="text-align:left;"> Strohs Bohemian Style </td> <td style="text-align:left;"> Good </td> <td style="text-align:left;"> USA </td> <td style="text-align:left;"> Regional </td> <td style="text-align:right;"> 2.49 </td> <td style="text-align:right;"> 0.42 </td> <td style="text-align:right;"> 149 </td> <td style="text-align:right;"> 27 </td> <td style="text-align:right;"> 4.7 </td> <td style="text-align:left;"> NONLIGHT </td> </tr> <tr> <td style="text-align:left;"> Tuborg </td> <td style="text-align:left;"> Fair </td> <td style="text-align:left;"> USA </td> <td style="text-align:left;"> Regional </td> <td style="text-align:right;"> 2.59 </td> <td style="text-align:right;"> 0.43 </td> <td style="text-align:right;"> 155 </td> <td style="text-align:right;"> 13 </td> <td style="text-align:right;"> 5.0 </td> <td style="text-align:left;"> NONLIGHT </td> </tr> </tbody> </table></div> --- # Questions for you - How many variables in the Beer dataset? - Which are metric? - Which are nominal? - Which are ordinal? --- # Discussion - Price is an example of a numerical variable. - Country of Origin is an example of a nominal variable: - You can not have more or less *France-ness* or *Mexico-ness* - Rating is an example of an ordinal variable: - A very *good beer* is better than a *good* beer but we do not know how much better. --- # Cross tab - A useful tool for exploring non-metric variables is the cross tab. -- - Cross tabs that are small can be very useful in providing some indication of the relationships between categorical variables. -- - Since most Beers in our dataset are from the US, the following cross tab only looks at US beers against beers from all other countries combined. --- # Cross Tab: Rating v Origin International v US <table> <thead> <tr> <th style="text-align:left;"> </th> <th style="text-align:right;"> Int. </th> <th style="text-align:right;"> US </th> </tr> </thead> <tbody> <tr> <td style="text-align:left;"> VeryGood </td> <td style="text-align:right;"> 4 </td> <td style="text-align:right;"> 7 </td> </tr> <tr> <td style="text-align:left;"> Good </td> <td style="text-align:right;"> 3 </td> <td style="text-align:right;"> 11 </td> </tr> <tr> <td style="text-align:left;"> Fair </td> <td style="text-align:right;"> 1 </td> <td style="text-align:right;"> 9 </td> </tr> </tbody> </table> Is there a relationship between origin and rating? --- # Using all countries <div style="border: 1px solid #ddd; padding: 0px; overflow-y: scroll; height:300px; "><table class="table table-striped table-hover table-condensed" style="margin-left: auto; margin-right: auto;"> <thead> <tr> <th style="text-align:left;position: sticky; top:0; background-color: #FFFFFF;"> </th> <th style="text-align:right;position: sticky; top:0; background-color: #FFFFFF;"> USA </th> <th style="text-align:right;position: sticky; top:0; background-color: #FFFFFF;"> Canada </th> <th style="text-align:right;position: sticky; top:0; background-color: #FFFFFF;"> France </th> <th style="text-align:right;position: sticky; top:0; background-color: #FFFFFF;"> Holland </th> <th style="text-align:right;position: sticky; top:0; background-color: #FFFFFF;"> Mexico </th> <th style="text-align:right;position: sticky; top:0; background-color: #FFFFFF;"> Germany </th> <th style="text-align:right;position: sticky; top:0; background-color: #FFFFFF;"> Japan </th> </tr> </thead> <tbody> <tr> <td style="text-align:left;"> VeryGood </td> <td style="text-align:right;"> 7 </td> <td style="text-align:right;"> 2 </td> <td style="text-align:right;"> 1 </td> <td style="text-align:right;"> 1 </td> <td style="text-align:right;"> 0 </td> <td style="text-align:right;"> 0 </td> <td style="text-align:right;"> 0 </td> </tr> <tr> <td style="text-align:left;"> Good </td> <td style="text-align:right;"> 11 </td> <td style="text-align:right;"> 0 </td> <td style="text-align:right;"> 0 </td> <td style="text-align:right;"> 0 </td> <td style="text-align:right;"> 1 </td> <td style="text-align:right;"> 1 </td> <td style="text-align:right;"> 1 </td> </tr> <tr> <td style="text-align:left;"> Fair </td> <td style="text-align:right;"> 9 </td> <td style="text-align:right;"> 0 </td> <td style="text-align:right;"> 0 </td> <td style="text-align:right;"> 0 </td> <td style="text-align:right;"> 0 </td> <td style="text-align:right;"> 1 </td> <td style="text-align:right;"> 0 </td> </tr> </tbody> </table></div> Is it as easy to find a relationship now? --- #Correspondence Analysis - Large cross tabulations can be summarised and visualised with a technique known as *Correspondence Analysis*. -- - This technique is mostly used to visualise the relationship between two variables. -- - The problem is considered *high-dimensional* since the number of categories rather than the number of variables is large. -- - On the next slide is the output from correspondence analysis --- # Correspondence Analysis <img src="Intro_files/figure-html/unnamed-chunk-10-1.png" style="display: block; margin: auto;" /> --- # Other data - Data comes in even more unusual forms. - The list of your favourite musicians on Spotify - The words used in online reviews of hotels - A ranking of pairs of products from most similar to most dissimilar -- - All of these types of data can be analysed using methods covered in the unit.