diff --git a/docs/developers.rst b/docs/developers.rst
index 538faaec3d..84c9e8f922 100644
--- a/docs/developers.rst
+++ b/docs/developers.rst
@@ -51,12 +51,12 @@ so there is no need to rerun this command unless you add a new file.
There are a number of utilities present in the file ``tests/test-utils.js``
that developers can use to make better unit tests. For example, a mocked
-vgl renderer can be used to hit code paths within webgl rendered layers. There
-are also methods for mocking global methods like ``requestAnimationFrame``
-to test complex, asynchronous code paths in a stable and repeatable manner.
-The `Sinon `_ testing library is also available to
-generate stubs, spies, and mocked methods. Because all tests share
-a global scope, they should be careful to clean up all mocking and
+webgl renderer can be used to hit code paths within webgl rendered layers.
+There are also methods for mocking global methods like
+``requestAnimationFrame`` to test complex, asynchronous code paths in a stable
+and repeatable manner. The `Sinon `_ testing library is
+also available to generate stubs, spies, and mocked methods. Because all tests
+share a global scope, they should be careful to clean up all mocking and
instrumentation after running. Ideally, each test should be runnable
independently and use jasmines ``beforeEach`` and ``afterEach`` methods
for setup and tear down.
diff --git a/package-lock.json b/package-lock.json
index 9d985bb283..8bf3051d48 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -20,8 +20,7 @@
"kdbush": "^4.0.0",
"mousetrap": "^1.6.5",
"polybooljs": "git+https://github.com/manubb/polybooljs#eps-logic",
- "proj4": "^2.7.5",
- "vgl": "0.3.11"
+ "proj4": "^2.7.5"
},
"devDependencies": {
"@babel/core": "^7.17.0",
@@ -12128,11 +12127,6 @@
"node": ">= 0.8"
}
},
- "node_modules/vgl": {
- "version": "0.3.11",
- "resolved": "https://registry.npmjs.org/vgl/-/vgl-0.3.11.tgz",
- "integrity": "sha512-2ESJJmRtueJz42nvvjPNl249/tsv9Ep/S0VN/1prspfd+BFXKxl+V2BAJdWRodJzfmlFRo7d9Y2m6DrgB6Ig6w=="
- },
"node_modules/void-elements": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz",
@@ -21851,11 +21845,6 @@
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
"dev": true
},
- "vgl": {
- "version": "0.3.11",
- "resolved": "https://registry.npmjs.org/vgl/-/vgl-0.3.11.tgz",
- "integrity": "sha512-2ESJJmRtueJz42nvvjPNl249/tsv9Ep/S0VN/1prspfd+BFXKxl+V2BAJdWRodJzfmlFRo7d9Y2m6DrgB6Ig6w=="
- },
"void-elements": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz",
diff --git a/package.json b/package.json
index 382ee800e8..dbe4e15518 100644
--- a/package.json
+++ b/package.json
@@ -23,8 +23,7 @@
"kdbush": "^4.0.0",
"mousetrap": "^1.6.5",
"polybooljs": "git+https://github.com/manubb/polybooljs#eps-logic",
- "proj4": "^2.7.5",
- "vgl": "0.3.11"
+ "proj4": "^2.7.5"
},
"optionalDependencies": {
"@egjs/hammerjs": "^2.0.8",
diff --git a/src/index.js b/src/index.js
index ef0f89a73c..e2a45b67e5 100644
--- a/src/index.js
+++ b/src/index.js
@@ -8,10 +8,6 @@
/*
* Bundled with the following libraries:
*
- * vgl
- * @copyright 2014-2016, Kitware, Inc.
- * @license Apache-2.0
- *
* Proj4js
* @copyright 2014, Mike Adair, Richard Greenwood, Didier Richard, Stephen Irons, Olivier Terral and Calvin Metcalf
* @license MIT
@@ -99,7 +95,8 @@ module.exports = $.extend({
svg: require('./svg'),
vtkjs: require('./vtkjs'),
webgl: require('./webgl'),
- gui: require('./ui')
+ gui: require('./ui'),
+ vgl: require('./vgl')
}, require('./registry'));
if (window && !window.$) {
diff --git a/src/util/common.js b/src/util/common.js
index d0ef8fc973..bf316b6726 100644
--- a/src/util/common.js
+++ b/src/util/common.js
@@ -328,7 +328,7 @@ var util = {
* is the correct size, return it. Otherwise, allocate a new buffer; any
* data in an old buffer is discarded.
*
- * @param {vgl.geometryData} geom The geometry to reference and modify.
+ * @param {geo.vgl.geometryData} geom The geometry to reference and modify.
* @param {string} srcName The name of the source.
* @param {number} len The number of elements for the array.
* @param {number} [allowLarger=0.2] If the existing buffer is larger than
diff --git a/src/util/mockVGL.js b/src/util/mockVGL.js
index b0471c8c7b..21e1dbeb86 100644
--- a/src/util/mockVGL.js
+++ b/src/util/mockVGL.js
@@ -18,7 +18,7 @@ module.exports = {};
*/
module.exports.mockWebglRenderer = function mockWebglRenderer(supported) {
'use strict';
- var vgl = require('vgl');
+ var vgl = require('../vgl');
if (supported === undefined) {
supported = true;
@@ -189,7 +189,7 @@ module.exports.mockWebglRenderer = function mockWebglRenderer(supported) {
* @alias geo.util.restoreWebglRenderer
*/
module.exports.restoreWebglRenderer = function () {
- var vgl = require('vgl');
+ var vgl = require('../vgl');
if (vgl._mocked) {
vgl.renderWindow = _renderWindow;
diff --git a/src/vgl/GL.js b/src/vgl/GL.js
new file mode 100644
index 0000000000..710e9080a1
--- /dev/null
+++ b/src/vgl/GL.js
@@ -0,0 +1,308 @@
+var vgl = require('./vgl');
+
+/**
+ * Wrap GL enums. Currently to get values of the enums we need to create
+ * or access the context.
+ *
+ * Using enums from here:
+ * https://github.com/toji/dart-gl-enums/blob/master/lib/gl_enums.dart
+ *
+ * @class
+ */
+vgl.GL = {
+ ACTIVE_ATTRIBUTES : 0x8B89,
+ ACTIVE_TEXTURE : 0x84E0,
+ ACTIVE_UNIFORMS : 0x8B86,
+ ALIASED_LINE_WIDTH_RANGE : 0x846E,
+ ALIASED_POINT_SIZE_RANGE : 0x846D,
+ ALPHA : 0x1906,
+ ALPHA_BITS : 0x0D55,
+ ALWAYS : 0x0207,
+ ARRAY_BUFFER : 0x8892,
+ ARRAY_BUFFER_BINDING : 0x8894,
+ ATTACHED_SHADERS : 0x8B85,
+ BACK : 0x0405,
+ BLEND : 0x0BE2,
+ BLEND_COLOR : 0x8005,
+ BLEND_DST_ALPHA : 0x80CA,
+ BLEND_DST_RGB : 0x80C8,
+ BLEND_EQUATION : 0x8009,
+ BLEND_EQUATION_ALPHA : 0x883D,
+ BLEND_EQUATION_RGB : 0x8009,
+ BLEND_SRC_ALPHA : 0x80CB,
+ BLEND_SRC_RGB : 0x80C9,
+ BLUE_BITS : 0x0D54,
+ BOOL : 0x8B56,
+ BOOL_VEC2 : 0x8B57,
+ BOOL_VEC3 : 0x8B58,
+ BOOL_VEC4 : 0x8B59,
+ BROWSER_DEFAULT_WEBGL : 0x9244,
+ BUFFER_SIZE : 0x8764,
+ BUFFER_USAGE : 0x8765,
+ BYTE : 0x1400,
+ CCW : 0x0901,
+ CLAMP_TO_EDGE : 0x812F,
+ COLOR_ATTACHMENT0 : 0x8CE0,
+ COLOR_BUFFER_BIT : 0x00004000,
+ COLOR_CLEAR_VALUE : 0x0C22,
+ COLOR_WRITEMASK : 0x0C23,
+ COMPILE_STATUS : 0x8B81,
+ COMPRESSED_TEXTURE_FORMATS : 0x86A3,
+ CONSTANT_ALPHA : 0x8003,
+ CONSTANT_COLOR : 0x8001,
+ CONTEXT_LOST_WEBGL : 0x9242,
+ CULL_FACE : 0x0B44,
+ CULL_FACE_MODE : 0x0B45,
+ CURRENT_PROGRAM : 0x8B8D,
+ CURRENT_VERTEX_ATTRIB : 0x8626,
+ CW : 0x0900,
+ DECR : 0x1E03,
+ DECR_WRAP : 0x8508,
+ DELETE_STATUS : 0x8B80,
+ DEPTH_ATTACHMENT : 0x8D00,
+ DEPTH_BITS : 0x0D56,
+ DEPTH_BUFFER_BIT : 0x00000100,
+ DEPTH_CLEAR_VALUE : 0x0B73,
+ DEPTH_COMPONENT : 0x1902,
+ DEPTH_COMPONENT16 : 0x81A5,
+ DEPTH_FUNC : 0x0B74,
+ DEPTH_RANGE : 0x0B70,
+ DEPTH_STENCIL : 0x84F9,
+ DEPTH_STENCIL_ATTACHMENT : 0x821A,
+ DEPTH_TEST : 0x0B71,
+ DEPTH_WRITEMASK : 0x0B72,
+ DITHER : 0x0BD0,
+ DONT_CARE : 0x1100,
+ DST_ALPHA : 0x0304,
+ DST_COLOR : 0x0306,
+ DYNAMIC_DRAW : 0x88E8,
+ ELEMENT_ARRAY_BUFFER : 0x8893,
+ ELEMENT_ARRAY_BUFFER_BINDING : 0x8895,
+ EQUAL : 0x0202,
+ FASTEST : 0x1101,
+ FLOAT : 0x1406,
+ FLOAT_MAT2 : 0x8B5A,
+ FLOAT_MAT3 : 0x8B5B,
+ FLOAT_MAT4 : 0x8B5C,
+ FLOAT_VEC2 : 0x8B50,
+ FLOAT_VEC3 : 0x8B51,
+ FLOAT_VEC4 : 0x8B52,
+ FRAGMENT_SHADER : 0x8B30,
+ FRAMEBUFFER : 0x8D40,
+ FRAMEBUFFER_ATTACHMENT_OBJECT_NAME : 0x8CD1,
+ FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE : 0x8CD0,
+ FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE : 0x8CD3,
+ FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL : 0x8CD2,
+ FRAMEBUFFER_BINDING : 0x8CA6,
+ FRAMEBUFFER_COMPLETE : 0x8CD5,
+ FRAMEBUFFER_INCOMPLETE_ATTACHMENT : 0x8CD6,
+ FRAMEBUFFER_INCOMPLETE_DIMENSIONS : 0x8CD9,
+ FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT : 0x8CD7,
+ FRAMEBUFFER_UNSUPPORTED : 0x8CDD,
+ FRONT : 0x0404,
+ FRONT_AND_BACK : 0x0408,
+ FRONT_FACE : 0x0B46,
+ FUNC_ADD : 0x8006,
+ FUNC_REVERSE_SUBTRACT : 0x800B,
+ FUNC_SUBTRACT : 0x800A,
+ GENERATE_MIPMAP_HINT : 0x8192,
+ GEQUAL : 0x0206,
+ GREATER : 0x0204,
+ GREEN_BITS : 0x0D53,
+ HIGH_FLOAT : 0x8DF2,
+ HIGH_INT : 0x8DF5,
+ INCR : 0x1E02,
+ INCR_WRAP : 0x8507,
+ INT : 0x1404,
+ INT_VEC2 : 0x8B53,
+ INT_VEC3 : 0x8B54,
+ INT_VEC4 : 0x8B55,
+ INVALID_ENUM : 0x0500,
+ INVALID_FRAMEBUFFER_OPERATION : 0x0506,
+ INVALID_OPERATION : 0x0502,
+ INVALID_VALUE : 0x0501,
+ INVERT : 0x150A,
+ KEEP : 0x1E00,
+ LEQUAL : 0x0203,
+ LESS : 0x0201,
+ LINEAR : 0x2601,
+ LINEAR_MIPMAP_LINEAR : 0x2703,
+ LINEAR_MIPMAP_NEAREST : 0x2701,
+ LINES : 0x0001,
+ LINE_LOOP : 0x0002,
+ LINE_STRIP : 0x0003,
+ LINE_WIDTH : 0x0B21,
+ LINK_STATUS : 0x8B82,
+ LOW_FLOAT : 0x8DF0,
+ LOW_INT : 0x8DF3,
+ LUMINANCE : 0x1909,
+ LUMINANCE_ALPHA : 0x190A,
+ MAX_COMBINED_TEXTURE_IMAGE_UNITS : 0x8B4D,
+ MAX_CUBE_MAP_TEXTURE_SIZE : 0x851C,
+ MAX_FRAGMENT_UNIFORM_VECTORS : 0x8DFD,
+ MAX_RENDERBUFFER_SIZE : 0x84E8,
+ MAX_TEXTURE_IMAGE_UNITS : 0x8872,
+ MAX_TEXTURE_SIZE : 0x0D33,
+ MAX_VARYING_VECTORS : 0x8DFC,
+ MAX_VERTEX_ATTRIBS : 0x8869,
+ MAX_VERTEX_TEXTURE_IMAGE_UNITS : 0x8B4C,
+ MAX_VERTEX_UNIFORM_VECTORS : 0x8DFB,
+ MAX_VIEWPORT_DIMS : 0x0D3A,
+ MEDIUM_FLOAT : 0x8DF1,
+ MEDIUM_INT : 0x8DF4,
+ MIRRORED_REPEAT : 0x8370,
+ NEAREST : 0x2600,
+ NEAREST_MIPMAP_LINEAR : 0x2702,
+ NEAREST_MIPMAP_NEAREST : 0x2700,
+ NEVER : 0x0200,
+ NICEST : 0x1102,
+ NONE : 0,
+ NOTEQUAL : 0x0205,
+ NO_ERROR : 0,
+ ONE : 1,
+ ONE_MINUS_CONSTANT_ALPHA : 0x8004,
+ ONE_MINUS_CONSTANT_COLOR : 0x8002,
+ ONE_MINUS_DST_ALPHA : 0x0305,
+ ONE_MINUS_DST_COLOR : 0x0307,
+ ONE_MINUS_SRC_ALPHA : 0x0303,
+ ONE_MINUS_SRC_COLOR : 0x0301,
+ OUT_OF_MEMORY : 0x0505,
+ PACK_ALIGNMENT : 0x0D05,
+ POINTS : 0x0000,
+ POLYGON_OFFSET_FACTOR : 0x8038,
+ POLYGON_OFFSET_FILL : 0x8037,
+ POLYGON_OFFSET_UNITS : 0x2A00,
+ RED_BITS : 0x0D52,
+ RENDERBUFFER : 0x8D41,
+ RENDERBUFFER_ALPHA_SIZE : 0x8D53,
+ RENDERBUFFER_BINDING : 0x8CA7,
+ RENDERBUFFER_BLUE_SIZE : 0x8D52,
+ RENDERBUFFER_DEPTH_SIZE : 0x8D54,
+ RENDERBUFFER_GREEN_SIZE : 0x8D51,
+ RENDERBUFFER_HEIGHT : 0x8D43,
+ RENDERBUFFER_INTERNAL_FORMAT : 0x8D44,
+ RENDERBUFFER_RED_SIZE : 0x8D50,
+ RENDERBUFFER_STENCIL_SIZE : 0x8D55,
+ RENDERBUFFER_WIDTH : 0x8D42,
+ RENDERER : 0x1F01,
+ REPEAT : 0x2901,
+ REPLACE : 0x1E01,
+ RGB : 0x1907,
+ RGB565 : 0x8D62,
+ RGB5_A1 : 0x8057,
+ RGBA : 0x1908,
+ RGBA4 : 0x8056,
+ SAMPLER_2D : 0x8B5E,
+ SAMPLER_CUBE : 0x8B60,
+ SAMPLES : 0x80A9,
+ SAMPLE_ALPHA_TO_COVERAGE : 0x809E,
+ SAMPLE_BUFFERS : 0x80A8,
+ SAMPLE_COVERAGE : 0x80A0,
+ SAMPLE_COVERAGE_INVERT : 0x80AB,
+ SAMPLE_COVERAGE_VALUE : 0x80AA,
+ SCISSOR_BOX : 0x0C10,
+ SCISSOR_TEST : 0x0C11,
+ SHADER_TYPE : 0x8B4F,
+ SHADING_LANGUAGE_VERSION : 0x8B8C,
+ SHORT : 0x1402,
+ SRC_ALPHA : 0x0302,
+ SRC_ALPHA_SATURATE : 0x0308,
+ SRC_COLOR : 0x0300,
+ STATIC_DRAW : 0x88E4,
+ STENCIL_ATTACHMENT : 0x8D20,
+ STENCIL_BACK_FAIL : 0x8801,
+ STENCIL_BACK_FUNC : 0x8800,
+ STENCIL_BACK_PASS_DEPTH_FAIL : 0x8802,
+ STENCIL_BACK_PASS_DEPTH_PASS : 0x8803,
+ STENCIL_BACK_REF : 0x8CA3,
+ STENCIL_BACK_VALUE_MASK : 0x8CA4,
+ STENCIL_BACK_WRITEMASK : 0x8CA5,
+ STENCIL_BITS : 0x0D57,
+ STENCIL_BUFFER_BIT : 0x00000400,
+ STENCIL_CLEAR_VALUE : 0x0B91,
+ STENCIL_FAIL : 0x0B94,
+ STENCIL_FUNC : 0x0B92,
+ STENCIL_INDEX : 0x1901,
+ STENCIL_INDEX8 : 0x8D48,
+ STENCIL_PASS_DEPTH_FAIL : 0x0B95,
+ STENCIL_PASS_DEPTH_PASS : 0x0B96,
+ STENCIL_REF : 0x0B97,
+ STENCIL_TEST : 0x0B90,
+ STENCIL_VALUE_MASK : 0x0B93,
+ STENCIL_WRITEMASK : 0x0B98,
+ STREAM_DRAW : 0x88E0,
+ SUBPIXEL_BITS : 0x0D50,
+ TEXTURE : 0x1702,
+ TEXTURE0 : 0x84C0,
+ TEXTURE1 : 0x84C1,
+ TEXTURE10 : 0x84CA,
+ TEXTURE11 : 0x84CB,
+ TEXTURE12 : 0x84CC,
+ TEXTURE13 : 0x84CD,
+ TEXTURE14 : 0x84CE,
+ TEXTURE15 : 0x84CF,
+ TEXTURE16 : 0x84D0,
+ TEXTURE17 : 0x84D1,
+ TEXTURE18 : 0x84D2,
+ TEXTURE19 : 0x84D3,
+ TEXTURE2 : 0x84C2,
+ TEXTURE20 : 0x84D4,
+ TEXTURE21 : 0x84D5,
+ TEXTURE22 : 0x84D6,
+ TEXTURE23 : 0x84D7,
+ TEXTURE24 : 0x84D8,
+ TEXTURE25 : 0x84D9,
+ TEXTURE26 : 0x84DA,
+ TEXTURE27 : 0x84DB,
+ TEXTURE28 : 0x84DC,
+ TEXTURE29 : 0x84DD,
+ TEXTURE3 : 0x84C3,
+ TEXTURE30 : 0x84DE,
+ TEXTURE31 : 0x84DF,
+ TEXTURE4 : 0x84C4,
+ TEXTURE5 : 0x84C5,
+ TEXTURE6 : 0x84C6,
+ TEXTURE7 : 0x84C7,
+ TEXTURE8 : 0x84C8,
+ TEXTURE9 : 0x84C9,
+ TEXTURE_2D : 0x0DE1,
+ TEXTURE_BINDING_2D : 0x8069,
+ TEXTURE_BINDING_CUBE_MAP : 0x8514,
+ TEXTURE_CUBE_MAP : 0x8513,
+ TEXTURE_CUBE_MAP_NEGATIVE_X : 0x8516,
+ TEXTURE_CUBE_MAP_NEGATIVE_Y : 0x8518,
+ TEXTURE_CUBE_MAP_NEGATIVE_Z : 0x851A,
+ TEXTURE_CUBE_MAP_POSITIVE_X : 0x8515,
+ TEXTURE_CUBE_MAP_POSITIVE_Y : 0x8517,
+ TEXTURE_CUBE_MAP_POSITIVE_Z : 0x8519,
+ TEXTURE_MAG_FILTER : 0x2800,
+ TEXTURE_MIN_FILTER : 0x2801,
+ TEXTURE_WRAP_S : 0x2802,
+ TEXTURE_WRAP_T : 0x2803,
+ TRIANGLES : 0x0004,
+ TRIANGLE_FAN : 0x0006,
+ TRIANGLE_STRIP : 0x0005,
+ UNPACK_ALIGNMENT : 0x0CF5,
+ UNPACK_COLORSPACE_CONVERSION_WEBGL : 0x9243,
+ UNPACK_FLIP_Y_WEBGL : 0x9240,
+ UNPACK_PREMULTIPLY_ALPHA_WEBGL : 0x9241,
+ UNSIGNED_BYTE : 0x1401,
+ UNSIGNED_INT : 0x1405,
+ UNSIGNED_SHORT : 0x1403,
+ UNSIGNED_SHORT_4_4_4_4 : 0x8033,
+ UNSIGNED_SHORT_5_5_5_1 : 0x8034,
+ UNSIGNED_SHORT_5_6_5 : 0x8363,
+ VALIDATE_STATUS : 0x8B83,
+ VENDOR : 0x1F00,
+ VERSION : 0x1F02,
+ VERTEX_ATTRIB_ARRAY_BUFFER_BINDING : 0x889F,
+ VERTEX_ATTRIB_ARRAY_ENABLED : 0x8622,
+ VERTEX_ATTRIB_ARRAY_NORMALIZED : 0x886A,
+ VERTEX_ATTRIB_ARRAY_POINTER : 0x8645,
+ VERTEX_ATTRIB_ARRAY_SIZE : 0x8623,
+ VERTEX_ATTRIB_ARRAY_STRIDE : 0x8624,
+ VERTEX_ATTRIB_ARRAY_TYPE : 0x8625,
+ VERTEX_SHADER : 0x8B31,
+ VIEWPORT : 0x0BA2,
+ ZERO : 0
+};
diff --git a/src/vgl/actor.js b/src/vgl/actor.js
new file mode 100644
index 0000000000..8fffb02aab
--- /dev/null
+++ b/src/vgl/actor.js
@@ -0,0 +1,110 @@
+var vgl = require('./vgl');
+var inherit = require('../inherit');
+var vec3 = require('gl-vec3');
+var mat4 = require('gl-mat4');
+
+/**
+ * Create a new instance of class actor.
+ *
+ * @class
+ * @alias vgl.actor
+ * @extends vgl.node
+ * @returns {vgl.actor}
+ */
+vgl.actor = function () {
+ 'use strict';
+
+ if (!(this instanceof vgl.actor)) {
+ return new vgl.actor();
+ }
+ vgl.node.call(this);
+
+ var m_this = this,
+ m_transformMatrix = mat4.create(),
+ m_referenceFrame = vgl.boundingObject.ReferenceFrame.Relative,
+ m_mapper = null;
+
+ /**
+ * Get transformation matrix used by the actor.
+ *
+ * @returns {mat4} The transformation matrix.
+ */
+ this.matrix = function () {
+ return m_transformMatrix;
+ };
+
+ /**
+ * Get reference frame for the transformations.
+ *
+ * @returns {string} Possible values are Absolute or Relative
+ */
+ this.referenceFrame = function () {
+ return m_referenceFrame;
+ };
+
+ /**
+ * Return mapper where actor gets it behavior and data.
+ *
+ * @returns {vgl.mapper}
+ */
+ this.mapper = function () {
+ return m_mapper;
+ };
+
+ /**
+ * Connect an actor to its data source.
+ *
+ * @param {vgl.mapper} mapper The vlg mapper object.
+ */
+ this.setMapper = function (mapper) {
+ if (mapper !== m_mapper) {
+ m_mapper = mapper;
+ m_this.boundsModified();
+ }
+ };
+
+ /**
+ * Compute actor bounds.
+ */
+ this.computeBounds = function () {
+ if (m_mapper === null || m_mapper === undefined) {
+ m_this.resetBounds();
+ return;
+ }
+
+ var computeBoundsTimestamp = m_this.computeBoundsTimestamp(),
+ mapperBounds, minPt, maxPt, newBounds;
+
+ if (m_this.boundsDirtyTimestamp().getMTime() > computeBoundsTimestamp.getMTime() ||
+ m_mapper.boundsDirtyTimestamp().getMTime() > computeBoundsTimestamp.getMTime()) {
+
+ m_mapper.computeBounds();
+ mapperBounds = m_mapper.bounds();
+
+ minPt = [mapperBounds[0], mapperBounds[2], mapperBounds[4]];
+ maxPt = [mapperBounds[1], mapperBounds[3], mapperBounds[5]];
+
+ vec3.transformMat4(minPt, minPt, m_transformMatrix);
+ vec3.transformMat4(maxPt, maxPt, m_transformMatrix);
+
+ newBounds = [
+ minPt[0] > maxPt[0] ? maxPt[0] : minPt[0],
+ minPt[0] > maxPt[0] ? minPt[0] : maxPt[0],
+ minPt[1] > maxPt[1] ? maxPt[1] : minPt[1],
+ minPt[1] > maxPt[1] ? minPt[1] : maxPt[1],
+ minPt[2] > maxPt[2] ? maxPt[2] : minPt[2],
+ minPt[2] > maxPt[2] ? minPt[2] : maxPt[2]
+ ];
+
+ m_this.setBounds(newBounds[0], newBounds[1],
+ newBounds[2], newBounds[3],
+ newBounds[4], newBounds[5]);
+
+ computeBoundsTimestamp.modified();
+ }
+ };
+
+ return m_this;
+};
+
+inherit(vgl.actor, vgl.node);
diff --git a/src/vgl/blend.js b/src/vgl/blend.js
new file mode 100644
index 0000000000..0faaa8c18c
--- /dev/null
+++ b/src/vgl/blend.js
@@ -0,0 +1,97 @@
+var vgl = require('./vgl');
+var inherit = require('../inherit');
+
+/**
+ * Create a new instance of class blendFunction.
+ *
+ * @class
+ * @alias vgl.blendFunction
+ * @param {number} source GL source constant.
+ * @param {number} destination GL destination constant.
+ * @returns {vgl.blendFunction}
+ */
+vgl.blendFunction = function (source, destination) {
+ 'use strict';
+
+ if (!(this instanceof vgl.blendFunction)) {
+ return new vgl.blendFunction(source, destination);
+ }
+
+ var m_source = source,
+ m_destination = destination;
+
+ /**
+ * Apply blend function to the current state.
+ *
+ * @param {vgl.renderState} renderState The state that contains the current
+ * render context.
+ */
+ this.apply = function (renderState) {
+ renderState.m_context.blendFuncSeparate(m_source, m_destination,
+ vgl.GL.ONE, vgl.GL.ONE_MINUS_SRC_ALPHA);
+ };
+
+ return this;
+};
+
+/**
+ * Create a new instance of class blend.
+ *
+ * @class
+ * @alias vgl.blend
+ * @extends vgl.materialAttribute
+ * @returns {vgl.blend}
+ */
+vgl.blend = function () {
+ 'use strict';
+
+ if (!(this instanceof vgl.blend)) {
+ return new vgl.blend();
+ }
+ vgl.materialAttribute.call(
+ this, vgl.materialAttributeType.Blend);
+
+ /** @private */
+ var m_wasEnabled = false,
+ m_blendFunction = vgl.blendFunction(vgl.GL.SRC_ALPHA,
+ vgl.GL.ONE_MINUS_SRC_ALPHA);
+
+ /**
+ * Bind blend attribute.
+ *
+ * @param {vgl.renderState} renderState The state containing the context.
+ * @returns {boolean} True if bound.
+ */
+ this.bind = function (renderState) {
+ m_wasEnabled = renderState.m_context.isEnabled(vgl.GL.BLEND);
+
+ if (this.enabled()) {
+ renderState.m_context.enable(vgl.GL.BLEND);
+ m_blendFunction.apply(renderState);
+ } else {
+ renderState.m_context.disable(vgl.GL.BLEND);
+ }
+
+ return true;
+ };
+
+ /**
+ * Undo bind blend attribute.
+ *
+ * @param {vgl.renderState} renderState The state containing the context.
+ * @returns {boolean} True if unbound.
+ */
+ this.undoBind = function (renderState) {
+ if (m_wasEnabled) {
+ renderState.m_context.enable(vgl.GL.BLEND);
+ } else {
+ renderState.m_context.disable(vgl.GL.BLEND);
+ }
+
+ return true;
+ };
+
+ return this;
+};
+
+inherit(vgl.blend, vgl.materialAttribute);
diff --git a/src/vgl/boundingObject.js b/src/vgl/boundingObject.js
new file mode 100644
index 0000000000..d724d70557
--- /dev/null
+++ b/src/vgl/boundingObject.js
@@ -0,0 +1,136 @@
+var vgl = require('./vgl');
+var inherit = require('../inherit');
+var timestamp = require('../timestamp');
+
+/**
+ * Create a new instance of class boundingObject.
+ *
+ * @class
+ * @alias vgl.boundingObject
+ * @extends vgl.object
+ * @returns {vgl.boundingObject}
+ */
+vgl.boundingObject = function () {
+ 'use strict';
+
+ if (!(this instanceof vgl.boundingObject)) {
+ return new vgl.boundingObject();
+ }
+ vgl.object.call(this);
+
+ var m_bounds = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
+ m_computeBoundsTimestamp = timestamp(),
+ m_boundsDirtyTimestamp = timestamp();
+
+ m_computeBoundsTimestamp.modified();
+ m_boundsDirtyTimestamp.modified();
+
+ /**
+ * Get current bounds of the object.
+ *
+ * @returns {number[]} The min x, max x, min y, max y, min z, max z bounding
+ * range of the object.
+ */
+ this.bounds = function () {
+ return m_bounds;
+ };
+
+ /**
+ * Check if bounds are valid.
+ *
+ * @param {number[]} bounds The six value bounds of the object.
+ * @returns {boolean} true if the bounds are valid.
+ */
+ this.hasValidBounds = function (bounds) {
+ if (bounds[0] === Number.MAX_VALUE ||
+ bounds[1] === -Number.MAX_VALUE ||
+ bounds[2] === Number.MAX_VALUE ||
+ bounds[3] === -Number.MAX_VALUE ||
+ bounds[4] === Number.MAX_VALUE ||
+ bounds[5] === -Number.MAX_VALUE) {
+ return false;
+ }
+
+ return true;
+ };
+
+ /**
+ * Set current bounds of the object.
+ *
+ * @param {number} minX Minimum x value.
+ * @param {number} maxX Maximum x value.
+ * @param {number} minY Minimum y value.
+ * @param {number} maxY Maximum y value.
+ * @param {number} minZ Minimum z value.
+ * @param {number} maxZ Maximum z value.
+ * @returns {boolean?} true if the bounds were set.
+ */
+ this.setBounds = function (minX, maxX, minY, maxY, minZ, maxZ) {
+ if (!this.hasValidBounds([minX, maxX, minY, maxY, minZ, maxZ])) {
+ return;
+ }
+
+ m_bounds[0] = minX;
+ m_bounds[1] = maxX;
+ m_bounds[2] = minY;
+ m_bounds[3] = maxY;
+ m_bounds[4] = minZ;
+ m_bounds[5] = maxZ;
+
+ this.modified();
+ m_computeBoundsTimestamp.modified();
+
+ return true;
+ };
+
+ /**
+ * Reset bounds to default values.
+ */
+ this.resetBounds = function () {
+ m_bounds[0] = Number.MAX_VALUE;
+ m_bounds[1] = -Number.MAX_VALUE;
+ m_bounds[2] = Number.MAX_VALUE;
+ m_bounds[3] = -Number.MAX_VALUE;
+ m_bounds[4] = Number.MAX_VALUE;
+ m_bounds[5] = -Number.MAX_VALUE;
+
+ this.modified();
+ };
+
+ /**
+ * Compute bounds of the object.
+ *
+ * Should be implemented by the concrete class.
+ */
+ this.computeBounds = function () {
+ };
+
+ /**
+ * Return bounds computation modification time.
+ *
+ * @returns {geo.timestamp}
+ */
+ this.computeBoundsTimestamp = function () {
+ return m_computeBoundsTimestamp;
+ };
+
+ /**
+ * Return bounds dirty timestamp.
+ *
+ * @returns {geo.timestamp}
+ */
+ this.boundsDirtyTimestamp = function () {
+ return m_boundsDirtyTimestamp;
+ };
+
+ this.resetBounds();
+
+ return this;
+};
+
+vgl.boundingObject.ReferenceFrame = {
+ Relative : 0,
+ Absolute : 1
+};
+
+inherit(vgl.boundingObject, vgl.object);
diff --git a/src/vgl/camera.js b/src/vgl/camera.js
new file mode 100644
index 0000000000..5c2e78bc31
--- /dev/null
+++ b/src/vgl/camera.js
@@ -0,0 +1,241 @@
+var vgl = require('./vgl');
+var inherit = require('../inherit');
+var timestamp = require('../timestamp');
+var vec3 = require('gl-vec3');
+var vec4 = require('gl-vec4');
+var mat4 = require('gl-mat4');
+
+/**
+ * Create a new instance of class camera.
+ *
+ * @class
+ * @alias vgl.camera
+ * @extends vgl.groupNode
+ * @param {object} arg
+ * @param {boolean?} arg.parallelProjection Optionally start with a parallel
+ * projection.
+ * @returns {vgl.camera}
+ */
+vgl.camera = function (arg) {
+ 'use strict';
+
+ if (!(this instanceof vgl.camera)) {
+ return new vgl.camera(arg);
+ }
+ vgl.groupNode.call(this);
+ arg = arg || {};
+
+ /** @private */
+ var m_viewAngle = (Math.PI * 30) / 180.0,
+ m_position = vec4.fromValues(0.0, 0.0, 1.0, 1.0),
+ m_focalPoint = vec4.fromValues(0.0, 0.0, 0.0, 1.0),
+ m_viewUp = vec4.fromValues(0.0, 1.0, 0.0, 0.0),
+ m_near = 0.01,
+ m_far = 10000.0,
+ m_viewAspect = 1.0,
+ m_directionOfProjection = vec4.fromValues(0.0, 0.0, -1.0, 0.0),
+ m_viewMatrix = mat4.create(),
+ m_projectionMatrix = mat4.create(),
+ m_computeModelViewMatrixTime = timestamp(),
+ m_computeProjectMatrixTime = timestamp(),
+ m_left = -1.0,
+ m_right = 1.0,
+ m_top = +1.0,
+ m_bottom = -1.0,
+ m_parallelExtents = {zoom: 1, tilesize: 256},
+ m_enableParallelProjection = false,
+ m_clearColor = [0.0, 0.0, 0.0, 0.0],
+ m_clearDepth = 1.0,
+ /*jshint bitwise: false */
+ m_clearMask = vgl.GL.COLOR_BUFFER_BIT |
+ vgl.GL.DEPTH_BUFFER_BIT;
+ /*jshint bitwise: true */
+
+ if (arg.parallelProjection !== undefined) {
+ m_enableParallelProjection = arg.parallelProjection ? true : false;
+ }
+
+ /**
+ * Set view aspect.
+ *
+ * @param {number} aspect Aspect ration (width / height).
+ */
+ this.setViewAspect = function (aspect) {
+ m_viewAspect = aspect;
+ this.modified();
+ };
+
+ /**
+ * Get parallel projection extents parameters.
+ *
+ * @returns {object} Extents object with width, height, zoom, and tilesize.
+ */
+ this.parallelExtents = function () {
+ return m_parallelExtents;
+ };
+
+ /**
+ * Set parallel projection extents parameters.
+ *
+ * @param {object} extents Extents object with width, height, zoom, and
+ * tilesize.
+ */
+ this.setParallelExtents = function (extents) {
+ var prop = ['width', 'height', 'zoom', 'tilesize'], mod = false, i, key;
+ for (i = 0; i < prop.length; i += 1) {
+ key = prop[i];
+ if (extents[key] !== undefined &&
+ extents[key] !== m_parallelExtents[key]) {
+ m_parallelExtents[key] = extents[key];
+ mod = true;
+ }
+ }
+ if (mod && m_parallelExtents.width && m_parallelExtents.height &&
+ m_parallelExtents.zoom !== undefined && m_parallelExtents.tilesize) {
+ var unitsPerPixel = this.unitsPerPixel(m_parallelExtents.zoom,
+ m_parallelExtents.tilesize);
+ m_right = unitsPerPixel * m_parallelExtents.width / 2;
+ m_left = -m_right;
+ m_top = unitsPerPixel * m_parallelExtents.height / 2;
+ m_bottom = -m_top;
+ this.modified();
+ }
+ };
+
+ /**
+ * Compute the units per pixel.
+ *
+ * @param {number} zoom Tile zoom level.
+ * @param {number} tilesize Number of pixels per tile (defaults to 256).
+ * @returns {number} unitsPerPixel.
+ */
+ this.unitsPerPixel = function (zoom, tilesize) {
+ tilesize = tilesize || 256;
+ return 360.0 * Math.pow(2, -zoom) / tilesize;
+ };
+
+ /**
+ * Return view-matrix for the camera This method does not compute the
+ * view-matrix for the camera. It is assumed that a call to computeViewMatrix
+ * has been made earlier.
+ *
+ * @returns {mat4}
+ */
+ this.viewMatrix = function () {
+ return this.computeViewMatrix();
+ };
+
+ /**
+ * Set the view-matrix for the camera and mark that it is up to date so that
+ * it won't be recomputed unless something else changes.
+ *
+ * @param {mat4} view new view matrix.
+ * @param {boolean} preserveType If true, clone the input using slice. This
+ * can be used to ensure the array is a specific precision.
+ */
+ this.setViewMatrix = function (view, preserveType) {
+ if (!preserveType) {
+ mat4.copy(m_viewMatrix, view);
+ } else {
+ m_viewMatrix = view.slice();
+ }
+ m_computeModelViewMatrixTime.modified();
+ };
+
+ /**
+ * Return camera projection matrix This method does not compute the
+ * projection-matrix for the camera. It is assumed that a call to
+ * computeProjectionMatrix has been made earlier.
+ *
+ * @returns {mat4}
+ */
+ this.projectionMatrix = function () {
+ return this.computeProjectionMatrix();
+ };
+
+ /**
+ * Set the projection-matrix for the camera and mark that it is up to date so
+ * that it won't be recomputed unless something else changes.
+ *
+ * @param {mat4} proj New projection matrix.
+ */
+ this.setProjectionMatrix = function (proj) {
+ mat4.copy(m_projectionMatrix, proj);
+ m_computeProjectMatrixTime.modified();
+ };
+
+ /**
+ * Return clear mask used by this camera.
+ *
+ * @returns {number}
+ */
+ this.clearMask = function () {
+ return m_clearMask;
+ };
+
+ /**
+ * Get clear color (background color) of the camera.
+ *
+ * @returns {Array}
+ */
+ this.clearColor = function () {
+ return m_clearColor;
+ };
+
+ /**
+ * Get the clear depth value.
+ *
+ * @returns {number}
+ */
+ this.clearDepth = function () {
+ return m_clearDepth;
+ };
+
+ /**
+ * Compute direction of projection.
+ */
+ this.computeDirectionOfProjection = function () {
+ vec3.subtract(m_directionOfProjection, m_focalPoint, m_position);
+ vec3.normalize(m_directionOfProjection, m_directionOfProjection);
+ this.modified();
+ };
+
+ /**
+ * Compute camera view matrix.
+ *
+ * @returns {mat4}
+ */
+ this.computeViewMatrix = function () {
+ if (m_computeModelViewMatrixTime.getMTime() < this.getMTime()) {
+ mat4.lookAt(m_viewMatrix, m_position, m_focalPoint, m_viewUp);
+ m_computeModelViewMatrixTime.modified();
+ }
+ return m_viewMatrix;
+ };
+
+ /**
+ * Compute camera projection matrix.
+ *
+ * @returns {mat4}
+ */
+ this.computeProjectionMatrix = function () {
+ if (m_computeProjectMatrixTime.getMTime() < this.getMTime()) {
+ if (!m_enableParallelProjection) {
+ mat4.perspective(m_projectionMatrix, m_viewAngle, m_viewAspect, m_near, m_far);
+ } else {
+ mat4.ortho(m_projectionMatrix,
+ m_left, m_right, m_bottom, m_top, m_near, m_far);
+ }
+
+ m_computeProjectMatrixTime.modified();
+ }
+
+ return m_projectionMatrix;
+ };
+
+ this.computeDirectionOfProjection();
+
+ return this;
+};
+
+inherit(vgl.camera, vgl.groupNode);
diff --git a/src/vgl/data.js b/src/vgl/data.js
new file mode 100644
index 0000000000..54f70452ce
--- /dev/null
+++ b/src/vgl/data.js
@@ -0,0 +1,28 @@
+var vgl = require('./vgl');
+
+/**
+ * Create a new instance of class data.
+ *
+ * @class
+ * @alias vgl.data
+ * @returns {vgl.data}
+ */
+vgl.data = function () {
+ 'use strict';
+
+ if (!(this instanceof vgl.data)) {
+ return new vgl.data();
+ }
+
+ /**
+ * Return data type. Should be implemented by the derived class.
+ */
+ this.type = function () {
+ };
+};
+
+vgl.data.raster = 0;
+vgl.data.point = 1;
+vgl.data.lineString = 2;
+vgl.data.polygon = 3;
+vgl.data.geometry = 10;
diff --git a/src/vgl/event.js b/src/vgl/event.js
new file mode 100644
index 0000000000..07cbdeb98f
--- /dev/null
+++ b/src/vgl/event.js
@@ -0,0 +1,33 @@
+var vgl = require('./vgl');
+
+vgl.event = {};
+
+/**
+ * types
+ */
+vgl.event.keyPress = 'vgl.event.keyPress';
+vgl.event.mousePress = 'vgl.event.mousePress';
+vgl.event.mouseRelease = 'vgl.event.mouseRelease';
+vgl.event.contextMenu = 'vgl.event.contextMenu';
+vgl.event.configure = 'vgl.event.configure';
+vgl.event.enable = 'vgl.event.enable';
+vgl.event.mouseWheel = 'vgl.event.mouseWheel';
+vgl.event.keyRelease = 'vgl.event.keyRelease';
+vgl.event.middleButtonPress = 'vgl.event.middleButtonPress';
+vgl.event.startInteraction = 'vgl.event.startInteraction';
+vgl.event.enter = 'vgl.event.enter';
+vgl.event.rightButtonPress = 'vgl.event.rightButtonPress';
+vgl.event.middleButtonRelease = 'vgl.event.middleButtonRelease';
+vgl.event.char = 'vgl.event.char';
+vgl.event.disable = 'vgl.event.disable';
+vgl.event.endInteraction = 'vgl.event.endInteraction';
+vgl.event.mouseMove = 'vgl.event.mouseMove';
+vgl.event.mouseOut = 'vgl.event.mouseOut';
+vgl.event.expose = 'vgl.event.expose';
+vgl.event.timer = 'vgl.event.timer';
+vgl.event.leftButtonPress = 'vgl.event.leftButtonPress';
+vgl.event.leave = 'vgl.event.leave';
+vgl.event.rightButtonRelease = 'vgl.event.rightButtonRelease';
+vgl.event.leftButtonRelease = 'vgl.event.leftButtonRelease';
+vgl.event.click = 'vgl.event.click';
+vgl.event.dblClick = 'vgl.event.dblClick';
diff --git a/src/vgl/geomData.js b/src/vgl/geomData.js
new file mode 100644
index 0000000000..8f968e694f
--- /dev/null
+++ b/src/vgl/geomData.js
@@ -0,0 +1,682 @@
+var vgl = require('./vgl');
+var inherit = require('../inherit');
+var timestamp = require('../timestamp');
+
+/**
+ * Create a new instance of class primitive.
+ *
+ * @class
+ * @alias vgl.primitive
+ * @returns {vgl.primitive}
+ */
+vgl.primitive = function () {
+ 'use strict';
+
+ if (!(this instanceof vgl.primitive)) {
+ return new vgl.primitive();
+ }
+
+ var m_primitiveType = 0,
+ m_indicesValueType = 0,
+ m_indices = null;
+
+ /**
+ * Get indices of the primitive.
+ *
+ * @returns {Uint16Array}
+ */
+ this.indices = function () {
+ return m_indices;
+ };
+
+ /**
+ * Return the number of indices.
+ *
+ * @returns {number} The number of indices.
+ */
+ this.numberOfIndices = function () {
+ return m_indices.length;
+ };
+
+ /*
+ * Return primitive type.
+ *
+ * @returns {number}
+ */
+ this.primitiveType = function () {
+ return m_primitiveType;
+ };
+
+ /**
+ * Set primitive type.
+ *
+ * @param {number} type The new type.
+ */
+ this.setPrimitiveType = function (type) {
+ m_primitiveType = type;
+ };
+
+ /**
+ * Return indices value type.
+ *
+ * @returns {number}
+ */
+ this.indicesValueType = function () {
+ return m_indicesValueType;
+ };
+
+ /**
+ * Set indices value type.
+ *
+ * @param {number} type
+ */
+ this.setIndicesValueType = function (type) {
+ m_indicesValueType = type;
+ };
+
+ /**
+ * Set indices from a array.
+ *
+ * @param {Array} indicesArray The array of new indices.
+ */
+ this.setIndices = function (indicesArray) {
+ // TODO Check for the type
+ m_indices = new Uint16Array(indicesArray);
+ };
+
+ return this;
+};
+
+/**
+ * Create a new instance of class triangles.
+ *
+ * @class
+ * @alias vgl.triangles
+ * @returns {vgl.triangles}
+ */
+vgl.triangles = function () {
+ 'use strict';
+
+ if (!(this instanceof vgl.triangles)) {
+ return new vgl.triangles();
+ }
+ vgl.primitive.call(this);
+
+ this.setPrimitiveType(vgl.GL.TRIANGLES);
+ this.setIndicesValueType(vgl.GL.UNSIGNED_SHORT);
+
+ return this;
+};
+
+inherit(vgl.triangles, vgl.primitive);
+
+/**
+ * Create a new instance of class points.
+ *
+ * @class
+ * @alias vgl.points
+ * @returns {vgl.points}
+ */
+vgl.points = function () {
+ 'use strict';
+
+ if (!(this instanceof vgl.points)) {
+ return new vgl.points();
+ }
+ vgl.primitive.call(this);
+
+ this.setPrimitiveType(vgl.GL.POINTS);
+ this.setIndicesValueType(vgl.GL.UNSIGNED_SHORT);
+
+ return this;
+};
+
+inherit(vgl.points, vgl.primitive);
+
+/**
+ * Create a new instance of class sourceData.
+ *
+ * @class
+ * @alias vgl.sourceData
+ * @param {object} arg
+ * @param {string?} arg.name Name of the source
+ * @returns {vgl.sourceData}
+ */
+vgl.sourceData = function (arg) {
+ 'use strict';
+
+ if (!(this instanceof vgl.sourceData)) {
+ return new vgl.sourceData(arg);
+ }
+
+ arg = arg || {};
+ var m_attributesMap = {},
+ m_data = [],
+ m_name = arg.name || 'Source ' + new Date().toISOString(),
+
+ /* Attribute data for the source */
+ vglAttributeData = function () {
+ // Number of components per group
+ // Type of data type (GL_FLOAT etc)
+ this.m_numberOfComponents = 0;
+ // Size of data type
+ this.m_dataType = 0;
+ this.m_dataTypeSize = 0;
+ // Specifies whether fixed-point data values should be normalized
+ // (true) or converted directly as fixed-point values (false)
+ // when they are accessed.
+ this.m_normalized = false;
+ // Strides for each attribute.
+ this.m_stride = 0;
+ // Offset
+ this.m_offset = 0;
+ };
+
+ /**
+ * Return raw data for this source.
+ *
+ * @returns {Array|Float32Array}
+ */
+ this.data = function () {
+ return m_data;
+ };
+
+ /**
+ * Return raw data for this source.
+ *
+ * @returns {Array|Float32Array}
+ */
+ this.getData = function () {
+ return this.data();
+ };
+
+ /**
+ * If the raw data is not a Float32Array, convert it to one. Then, return
+ * raw data for this source.
+ *
+ * @returns {Float32Array}
+ */
+ this.dataToFloat32Array = function () {
+ if (!(m_data instanceof Float32Array)) {
+ m_data = new Float32Array(m_data);
+ }
+ return m_data;
+ };
+
+ /**
+ * Set data for this source.
+ *
+ * @param {Array|Float32Array} data
+ */
+ this.setData = function (data) {
+ if (!(data instanceof Array) && !(data instanceof Float32Array)) {
+ console.log('[error] Requires array');
+ return;
+ }
+ if (data instanceof Float32Array) {
+ m_data = data;
+ } else {
+ m_data = data.slice(0);
+ }
+ };
+
+ /**
+ * Add new attribute data to the source.
+ *
+ * @param {string} key Attribute key.
+ * @param {number} dataType
+ * @param {number} sizeOfDataType
+ * @param {number} offset
+ * @param {number} stride
+ * @param {number} noOfComponents
+ * @param {boolean} normalized
+ */
+ this.addAttribute = function (key, dataType, sizeOfDataType, offset, stride,
+ noOfComponents, normalized) {
+
+ if (!m_attributesMap.hasOwnProperty(key)) {
+ var newAttr = new vglAttributeData();
+ newAttr.m_dataType = dataType;
+ newAttr.m_dataTypeSize = sizeOfDataType;
+ newAttr.m_offset = offset;
+ newAttr.m_stride = stride;
+ newAttr.m_numberOfComponents = noOfComponents;
+ newAttr.m_normalized = normalized;
+ m_attributesMap[key] = newAttr;
+ }
+ };
+
+ /**
+ * Check if there is attribute exists of a given key type.
+ *
+ * @param {string} key Attribute key.
+ * @returns {boolean}
+ */
+ this.hasKey = function (key) {
+ return m_attributesMap.hasOwnProperty(key);
+ };
+
+ /**
+ * Return keys of all attributes.
+ *
+ * @returns {string[]}
+ */
+ this.keys = function () {
+ return Object.keys(m_attributesMap);
+ };
+
+ /**
+ * Return number of components of the attribute data.
+ *
+ * @param {string} key Attribute key.
+ * @returns {number}
+ */
+ this.attributeNumberOfComponents = function (key) {
+ if (m_attributesMap.hasOwnProperty(key)) {
+ return m_attributesMap[key].m_numberOfComponents;
+ }
+
+ return 0;
+ };
+
+ /**
+ * Return if the attribute data is normalized.
+ *
+ * @param {string} key Attribute key.
+ * @returns {boolean}
+ */
+ this.normalized = function (key) {
+ if (m_attributesMap.hasOwnProperty(key)) {
+ return m_attributesMap[key].m_normalized;
+ }
+
+ return false;
+ };
+
+ /**
+ * Return attribute data type.
+ *
+ * @param {string} key Attribute key.
+ * @returns {number}
+ */
+ this.attributeDataType = function (key) {
+ if (m_attributesMap.hasOwnProperty(key)) {
+ return m_attributesMap[key].m_dataType;
+ }
+
+ return undefined;
+ };
+
+ /**
+ * Return attribute offset.
+ *
+ * @param {string} key Attribute key.
+ * @returns {number}
+ */
+ this.attributeOffset = function (key) {
+ if (m_attributesMap.hasOwnProperty(key)) {
+ return m_attributesMap[key].m_offset;
+ }
+
+ return 0;
+ };
+
+ /**
+ * Return attribute stride.
+ *
+ * @param {string} key Attribute key.
+ * @returns {number}
+ */
+ this.attributeStride = function (key) {
+ if (m_attributesMap.hasOwnProperty(key)) {
+ return m_attributesMap[key].m_stride;
+ }
+
+ return 0;
+ };
+
+ /**
+ * Virtual function to insert new vertex data at the end.
+ *
+ * @param {number|Array} vertexData
+ */
+ this.pushBack = function (vertexData) {
+ // Should be implemented by the base class
+ };
+
+ /**
+ * Insert new data block to the raw data.
+ *
+ * @param {number[]|Float32Array} data
+ */
+ this.insert = function (data) {
+ var i;
+
+ /* If we will are given a Float32Array and don't have any other data, use
+ * it directly. */
+ if (!m_data.length && data.length && data instanceof Float32Array) {
+ m_data = data;
+ return;
+ }
+ /* If our internal array is immutable and we will need to change it, create
+ * a regular mutable array from it. */
+ if (!m_data.slice && (m_data.length || !data.slice)) {
+ m_data = Array.prototype.slice.call(m_data);
+ }
+ if (!data.length) {
+ /* data is a singular value, so append it to our array */
+ m_data[m_data.length] = data;
+ } else {
+ /* We don't have any data currently, so it is faster to copy the data
+ * using slice. */
+ if (!m_data.length && data.slice) {
+ m_data = data.slice(0);
+ } else {
+ for (i = 0; i < data.length; i += 1) {
+ m_data[m_data.length] = data[i];
+ }
+ }
+ }
+ };
+
+ /**
+ * Return name of the source data.
+ *
+ * @returns {string}
+ */
+ this.name = function () {
+ return m_name;
+ };
+
+ return this;
+};
+
+/**
+ * Create a new instance of class sourceDataP3fv.
+ *
+ * @class
+ * @alias vgl.sourceDataAnyfv
+ * @param {number} size Number of sets of 4 floats.
+ * @param {string} key Attribute key.
+ * @param {object} arg Argument to pass to parent class.
+ * @returns {vgl.sourceDataAnyfv}
+ */
+vgl.sourceDataAnyfv = function (size, key, arg) {
+ 'use strict';
+ if (!(this instanceof vgl.sourceDataAnyfv)) {
+ return new vgl.sourceDataAnyfv(size, key, arg);
+ }
+
+ vgl.sourceData.call(this, arg);
+ this.addAttribute(key, vgl.GL.FLOAT,
+ 4, 0, size * 4, size, false);
+
+ this.pushBack = function (value) {
+ this.insert(value);
+ };
+
+ return this;
+};
+
+inherit(vgl.sourceDataAnyfv, vgl.sourceData);
+
+/**
+ * Create a new instance of class sourceDataP3fv.
+ *
+ * @class
+ * @alias vgl.sourceDataP3fv
+ * @param {object} arg Object to pass to parent class.
+ * @returns {vgl.sourceDataP3fv}
+ */
+vgl.sourceDataP3fv = function (arg) {
+ 'use strict';
+
+ if (!(this instanceof vgl.sourceDataP3fv)) {
+ return new vgl.sourceDataP3fv(arg);
+ }
+
+ vgl.sourceData.call(this, arg);
+
+ this.addAttribute(vgl.vertexAttributeKeys.Position, vgl.GL.FLOAT, 4, 0, 3 * 4, 3,
+ false);
+
+ this.pushBack = function (value) {
+ this.insert(value);
+ };
+
+ return this;
+};
+
+inherit(vgl.sourceDataP3fv, vgl.sourceData);
+
+/**
+ * Create a new instance of class sourceDataT2fv.
+ *
+ * @class
+ * @alias vgl.sourceDataT2fv
+ * @param {object} arg Object to pass to parent class.
+ * @returns {vgl.sourceDataT2fv}
+ */
+vgl.sourceDataT2fv = function (arg) {
+ 'use strict';
+
+ if (!(this instanceof vgl.sourceDataT2fv)) {
+ return new vgl.sourceDataT2fv(arg);
+ }
+
+ vgl.sourceData.call(this, arg);
+
+ this.addAttribute(vgl.vertexAttributeKeys.TextureCoordinate, vgl.GL.FLOAT, 4, 0,
+ 2 * 4, 2, false);
+
+ this.pushBack = function (value) {
+ this.insert(value);
+ };
+
+ return this;
+};
+
+inherit(vgl.sourceDataT2fv, vgl.sourceData);
+
+/**
+ * Create a new instance of class geometryData.
+ *
+ * @class
+ * @alias vgl.geometryData
+ * @returns {vgl.geometryData}
+ */
+vgl.geometryData = function () {
+ 'use strict';
+
+ if (!(this instanceof vgl.geometryData)) {
+ return new vgl.geometryData();
+ }
+ vgl.data.call(this);
+
+ var m_name = '',
+ m_primitives = [],
+ m_sources = [],
+ m_bounds = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
+ m_computeBoundsTimestamp = timestamp(),
+ m_boundsDirtyTimestamp = timestamp();
+
+ /**
+ * Return type.
+ *
+ * @returns {number}
+ */
+ this.type = function () {
+ return vgl.data.geometry;
+ };
+
+ /**
+ * Return ID of the geometry data.
+ *
+ * @returns {string}
+ */
+ this.name = function () {
+ return m_name;
+ };
+
+ /**
+ * Add new source.
+ *
+ * @param {vgl.sourceData} source
+ * @returns {boolean} True is the source was added.
+ */
+ this.addSource = function (source) {
+ if (m_sources.indexOf(source) === -1) {
+ m_sources.push(source);
+
+ if (source.hasKey(vgl.vertexAttributeKeys.Position)) {
+ m_boundsDirtyTimestamp.modified();
+ }
+ return true;
+ }
+
+ return false;
+ };
+
+ /**
+ * Return source for a given index. Returns 0 if not found.
+ *
+ * @param {number} index
+ * @returns {vgl.sourceData|number}
+ */
+ this.source = function (index) {
+ if (index < m_sources.length) {
+ return m_sources[index];
+ }
+
+ return 0;
+ };
+
+ /**
+ * Return source with a specified name. Returns 0 if not found.
+ *
+ * @param {string} sourceName
+ * @returns {vgl.sourceData|number}
+ */
+ this.sourceByName = function (sourceName) {
+ for (var i = 0; i < m_sources.length; i += 1) {
+ if (m_sources[i].name() === sourceName) {
+ return m_sources[i];
+ }
+ }
+ return 0;
+ };
+
+ /**
+ * Return number of sources.
+ *
+ * @returns {number}
+ */
+ this.numberOfSources = function () {
+ return m_sources.length;
+ };
+
+ /**
+ * Return source data given a key.
+ *
+ * @param {string} key
+ * @returns {vgl.sourceData|null}
+ */
+ this.sourceData = function (key) {
+ var i;
+
+ for (i = 0; i < m_sources.length; i += 1) {
+ if (m_sources[i].hasKey(key)) {
+ return m_sources[i];
+ }
+ }
+
+ return null;
+ };
+
+ /**
+ * Add new primitive.
+ *
+ * @param {vgl.primitive} primitive
+ * @returns {boolean}
+ */
+ this.addPrimitive = function (primitive) {
+ m_primitives.push(primitive);
+ return true;
+ };
+
+ /**
+ * Return primitive for a given index. Returns null if not found.
+ *
+ * @param {number} index
+ * @returns {vgl.primitive|null}
+ */
+ this.primitive = function (index) {
+ if (index < m_primitives.length) {
+ return m_primitives[index];
+ }
+
+ return null;
+ };
+
+ /**
+ * Return number of primitives.
+ *
+ * @returns {number}
+ */
+ this.numberOfPrimitives = function () {
+ return m_primitives.length;
+ };
+
+ /**
+ * Return bounds.
+ *
+ * @returns {number[]} Array of minX, maxX, minY, maxY, minZ, maxZ.
+ */
+ this.bounds = function () {
+ if (m_boundsDirtyTimestamp.getMTime() > m_computeBoundsTimestamp.getMTime()) {
+ this.computeBounds();
+ }
+ return m_bounds;
+ };
+
+ /**
+ * Check if bounds are dirty or mark them as such.
+ *
+ * @param {boolean} dirty true to set bounds as dirty.
+ * @returns {boolean} true if bounds are dirty.
+ */
+ this.boundsDirty = function (dirty) {
+ if (dirty) {
+ m_boundsDirtyTimestamp.modified();
+ }
+ return m_boundsDirtyTimestamp.getMTime() > m_computeBoundsTimestamp.getMTime();
+ };
+
+ /**
+ * Set bounds.
+ *
+ * @param {number} minX
+ * @param {number} maxX
+ * @param {number} minY
+ * @param {number} maxY
+ * @param {number} minZ
+ * @param {number} maxZ
+ * @returns {boolean} True if set.
+ */
+ this.setBounds = function (minX, maxX, minY, maxY, minZ, maxZ) {
+ m_bounds[0] = minX;
+ m_bounds[1] = maxX;
+ m_bounds[2] = minY;
+ m_bounds[3] = maxY;
+ m_bounds[4] = minZ;
+ m_bounds[5] = maxZ;
+
+ m_computeBoundsTimestamp.modified();
+
+ return true;
+ };
+
+ return this;
+};
+
+inherit(vgl.geometryData, vgl.data);
diff --git a/src/vgl/graphicsObject.js b/src/vgl/graphicsObject.js
new file mode 100644
index 0000000000..70f7b38d38
--- /dev/null
+++ b/src/vgl/graphicsObject.js
@@ -0,0 +1,45 @@
+var vgl = require('./vgl');
+var inherit = require('../inherit');
+
+/**
+ * Create a new instance of class graphicsObject.
+ *
+ * @class
+ * @alias vgl.graphicsObject
+ * @param {number} type A GL type.
+ * @returns {vgl.graphicsObject}
+ */
+vgl.graphicsObject = function (type) {
+ 'use strict';
+
+ if (!(this instanceof vgl.graphicsObject)) {
+ return new vgl.graphicsObject();
+ }
+ vgl.object.call(this);
+
+ var m_this = this;
+
+ /**
+ * Setup (initialize) the object.
+ *
+ * @param {vgl.renderState} renderState
+ * @returns {boolean}
+ */
+ this._setup = function (renderState) {
+ return false;
+ };
+
+ /**
+ * Remove any resources acquired before deletion.
+ *
+ * @param {vgl.renderState} renderState
+ * @returns {boolean}
+ */
+ this._cleanup = function (renderState) {
+ return false;
+ };
+
+ return m_this;
+};
+
+inherit(vgl.graphicsObject, vgl.object);
diff --git a/src/vgl/groupNode.js b/src/vgl/groupNode.js
new file mode 100644
index 0000000000..ec79bd578a
--- /dev/null
+++ b/src/vgl/groupNode.js
@@ -0,0 +1,101 @@
+var vgl = require('./vgl');
+var inherit = require('../inherit');
+
+/**
+ * Create a new instance of class groupNode.
+ *
+ * @class
+ * @alias vgl.groupNode
+ * @returns {vgl.groupNode}
+ */
+vgl.groupNode = function () {
+ 'use strict';
+
+ if (!(this instanceof vgl.groupNode)) {
+ return new vgl.groupNode();
+ }
+ vgl.node.call(this);
+
+ var m_children = [];
+
+ /**
+ * Make the incoming node a child of the group node.
+ *
+ * @param {vgl.node} childNode
+ * @returns {boolean}
+ */
+ this.addChild = function (childNode) {
+ if (childNode instanceof vgl.node) {
+ if (m_children.indexOf(childNode) === -1) {
+ childNode.setParent(this);
+ m_children.push(childNode);
+ this.boundsDirtyTimestamp().modified();
+ return true;
+ }
+ return false;
+ }
+
+ return false;
+ };
+
+ /**
+ * Remove parent-child relationship between the group and incoming node.
+ *
+ * @param {vgl.node} childNode
+ * @returns {boolean}
+ */
+ this.removeChild = function (childNode) {
+ if (childNode.parent() === this) {
+ var index = m_children.indexOf(childNode);
+ if (index >= 0) {
+ m_children.splice(index, 1);
+ childNode.setParent(null);
+ this.boundsDirtyTimestamp().modified();
+ return true;
+ }
+ }
+ };
+
+ /**
+ * Remove parent-child relationship between child nodes and the group node.
+ */
+ this.removeChildren = function () {
+ while (m_children.length) {
+ this.removeChild(m_children[0]);
+ }
+
+ this.modified();
+ };
+
+ /**
+ * Return children of this group node.
+ *
+ * @returns {vgl.node[]}
+ */
+ this.children = function () {
+ return m_children;
+ };
+
+ /**
+ * Return true if this group node has node as a child, false otherwise.
+ *
+ * @param {vgl.node} node
+ * @returns {boolean}
+ */
+ this.hasChild = function (node) {
+ var i = 0, child = false;
+
+ for (i = 0; i < m_children.length; i += 1) {
+ if (m_children[i] === node) {
+ child = true;
+ break;
+ }
+ }
+
+ return child;
+ };
+
+ return this;
+};
+
+inherit(vgl.groupNode, vgl.node);
diff --git a/src/vgl/index.js b/src/vgl/index.js
new file mode 100644
index 0000000000..2bf6a5da34
--- /dev/null
+++ b/src/vgl/index.js
@@ -0,0 +1,28 @@
+/**
+ * @namespace vgl
+ */
+module.exports = require('./vgl');
+
+require('./GL');
+require('./object');
+require('./boundingObject'); // requires object
+require('./mapper'); // requires boundingObject
+require('./event'); // requires object
+require('./graphicsObject'); // requires object
+require('./material'); // requires graphicsObject
+require('./materialAttribute'); // requires graphicsObject
+require('./node'); // requires boundingObject
+require('./actor'); // requires node
+require('./groupNode'); // requires node
+require('./camera'); // requires groupNode
+require('./blend'); // requires materialAttribute
+require('./data');
+require('./geomData'); // requires data
+require('./renderWindow'); // requires graphicsObject
+require('./renderer'); // requires graphcisObject
+require('./shader'); // requires object
+require('./shaderProgram'); // requires materialAttribute
+require('./texture'); // requires materialAttributes
+require('./uniform');
+require('./vertexAttribute');
+require('./viewer'); // requires object
diff --git a/src/vgl/mapper.js b/src/vgl/mapper.js
new file mode 100644
index 0000000000..671d2d3fd9
--- /dev/null
+++ b/src/vgl/mapper.js
@@ -0,0 +1,338 @@
+var vgl = require('./vgl');
+var inherit = require('../inherit');
+var timestamp = require('../timestamp');
+
+/**
+ * Create a new instance of class mapper.
+ *
+ * @class
+ * @alias vgl.mapper
+ * @param {object} arg
+ * @param {boolean} arg.dynamicDraw true if the dynamic draw flag should be
+ * set.
+ * @returns {vgl.mapper}
+ */
+vgl.mapper = function (arg) {
+ 'use strict';
+
+ if (!(this instanceof vgl.mapper)) {
+ return new vgl.mapper(arg);
+ }
+ vgl.boundingObject.call(this);
+
+ arg = arg || {};
+
+ var m_color = [0.0, 1.0, 1.0],
+ m_geomData = null,
+ m_buffers = [],
+ m_bufferVertexAttributeMap = {},
+ m_dynamicDraw = arg.dynamicDraw === undefined ? false : arg.dynamicDraw,
+ m_glCompileTimestamp = timestamp(),
+ m_context = null,
+ m_this = this;
+
+ /**
+ * Delete cached VBO.
+ *
+ * @param {vgl.renderState} renderState
+ */
+ this.deleteVertexBufferObjects = function (renderState) {
+ var i;
+ var context = m_context;
+ if (renderState) {
+ context = renderState.m_context;
+ }
+ if (context) {
+ for (i = 0; i < m_buffers.length; i += 1) {
+ context.deleteBuffer(m_buffers[i]);
+ }
+ }
+ };
+
+ /**
+ * Cleanup mapper.
+ *
+ * @param {vgl.renderState} renderState
+ */
+ this._cleanup = function (renderState) {
+ m_this.deleteVertexBufferObjects(renderState);
+ cleanUpDrawObjects(renderState);
+ m_this.modified();
+ };
+
+ /**
+ * Create new VBO for all its geometryData sources and primitives.
+ *
+ * @param {vgl.renderState} renderState
+ */
+ function createVertexBufferObjects(renderState) {
+ if (m_geomData) {
+ if (renderState) {
+ m_context = renderState.m_context;
+ }
+ var numberOfSources = m_geomData.numberOfSources(),
+ i, j, k, bufferId = null, keys, ks, numberOfPrimitives, data;
+
+ for (i = 0; i < numberOfSources; i += 1) {
+ bufferId = m_context.createBuffer();
+ m_context.bindBuffer(vgl.GL.ARRAY_BUFFER, bufferId);
+ data = m_geomData.source(i).data();
+ if (!(data instanceof Float32Array)) {
+ data = new Float32Array(data);
+ }
+ m_context.bufferData(vgl.GL.ARRAY_BUFFER, data,
+ m_dynamicDraw ? vgl.GL.DYNAMIC_DRAW :
+ vgl.GL.STATIC_DRAW);
+
+ keys = m_geomData.source(i).keys();
+ ks = [];
+
+ for (j = 0; j < keys.length; j += 1) {
+ ks.push(keys[j]);
+ }
+
+ m_bufferVertexAttributeMap[i] = ks;
+ m_buffers[i] = bufferId;
+ }
+
+ numberOfPrimitives = m_geomData.numberOfPrimitives();
+ for (k = 0; k < numberOfPrimitives; k += 1) {
+ bufferId = m_context.createBuffer();
+ m_context.bindBuffer(vgl.GL.ARRAY_BUFFER, bufferId);
+ m_context.bufferData(vgl.GL.ARRAY_BUFFER,
+ m_geomData.primitive(k).indices(), vgl.GL.STATIC_DRAW);
+ m_buffers[i] = bufferId;
+ i += 1;
+ }
+
+ m_glCompileTimestamp.modified();
+ }
+ }
+
+ /**
+ * Clear cache related to buffers.
+ *
+ * @param {vgl.renderState} renderState
+ */
+ function cleanUpDrawObjects(renderState) {
+ m_bufferVertexAttributeMap = {};
+ m_buffers = [];
+ }
+
+ /**
+ * Setup draw objects; Delete old ones and create new ones.
+ *
+ * @param {vgl.renderState} renderState
+ */
+ function setupDrawObjects(renderState) {
+ // Delete buffer objects from past if any.
+ m_this.deleteVertexBufferObjects(renderState);
+
+ // Clear any cache related to buffers
+ cleanUpDrawObjects(renderState);
+
+ // Now construct the new ones.
+ createVertexBufferObjects(renderState);
+ }
+
+ /**
+ * Compute bounds of the data.
+ */
+ this.computeBounds = function () {
+ if (m_geomData === null || typeof m_geomData === 'undefined') {
+ this.resetBounds();
+ return;
+ }
+
+ var computeBoundsTimestamp = this.computeBoundsTimestamp(),
+ boundsDirtyTimestamp = this.boundsDirtyTimestamp(),
+ geomBounds = null;
+
+ if (boundsDirtyTimestamp.getMTime() > computeBoundsTimestamp.getMTime()) {
+ geomBounds = m_geomData.bounds();
+
+ this.setBounds(geomBounds[0], geomBounds[1], geomBounds[2],
+ geomBounds[3], geomBounds[4], geomBounds[5]);
+
+ computeBoundsTimestamp.modified();
+ }
+ };
+
+ /**
+ * Get solid color of the geometry.
+ *
+ * @returns {number[]}
+ */
+ this.color = function () {
+ return m_color;
+ };
+
+ /**
+ * Return stored geometry data.
+ *
+ * @returns {number[]}
+ */
+ this.geometryData = function () {
+ return m_geomData;
+ };
+
+ /**
+ * Connect mapper to its geometry data.
+ *
+ * @param {number[]} geom
+ */
+ this.setGeometryData = function (geom) {
+ if (m_geomData !== geom) {
+ m_geomData = geom;
+
+ this.modified();
+ this.boundsDirtyTimestamp().modified();
+ }
+ };
+
+ /**
+ * Update the buffer used for a named source.
+ *
+ * @param {string} sourceName The name of the source to update.
+ * @param {object[]|Float32Array} values The values to use for the source.
+ * If not specified, use the source's own buffer.
+ * @param {vgl.renderState} renderState
+ * @returns {boolean} true if there was a context to update.
+ */
+ this.updateSourceBuffer = function (sourceName, values, renderState) {
+ if (renderState) {
+ m_context = renderState.m_context;
+ }
+ if (!m_context) {
+ return false;
+ }
+ var bufferIndex = -1;
+ for (var i = 0; i < m_geomData.numberOfSources(); i += 1) {
+ if (m_geomData.source(i).name() === sourceName) {
+ bufferIndex = i;
+ break;
+ }
+ }
+ if (bufferIndex < 0 || bufferIndex >= m_buffers.length) {
+ return false;
+ }
+ if (!values) {
+ values = m_geomData.source(i).dataToFloat32Array();
+ }
+ m_context.bindBuffer(vgl.GL.ARRAY_BUFFER, m_buffers[bufferIndex]);
+ if (values instanceof Float32Array) {
+ m_context.bufferSubData(vgl.GL.ARRAY_BUFFER, 0, values);
+ } else {
+ m_context.bufferSubData(vgl.GL.ARRAY_BUFFER, 0,
+ new Float32Array(values));
+ }
+ return true;
+ };
+
+ /**
+ * Get the buffer used for a named source. If the current buffer isn't a
+ * Float32Array, it is converted to one. This array can then be modified
+ * directly, after which updateSourceBuffer can be called to update the
+ * GL array.
+ *
+ * @param {string} sourceName The name of the source to update.
+ * @returns {Float32Array} An array used for this source.
+ */
+ this.getSourceBuffer = function (sourceName) {
+ var source = m_geomData.sourceByName(sourceName);
+ if (!source) {
+ return new Float32Array();
+ }
+ return source.dataToFloat32Array();
+ };
+
+ /**
+ * Render the mapper.
+ *
+ * @param {vgl.renderState} renderState The current rendering state object.
+ * @param {boolean} noUndoBindVertexData If true, do not unbind vertex data.
+ * This may be desirable if the render function is subclassed.
+ */
+ this.render = function (renderState, noUndoBindVertexData) {
+ if (this.getMTime() > m_glCompileTimestamp.getMTime() ||
+ renderState.m_contextChanged) {
+ setupDrawObjects(renderState);
+ }
+ m_context = renderState.m_context;
+
+ // Fixed vertex color
+ m_context.vertexAttrib3fv(vgl.vertexAttributeKeys.Color, this.color());
+
+ var bufferIndex = 0,
+ j = 0, i, noOfPrimitives = null, primitive = null;
+
+ for (i in m_bufferVertexAttributeMap) {
+ if (m_bufferVertexAttributeMap.hasOwnProperty(i)) {
+ m_context.bindBuffer(vgl.GL.ARRAY_BUFFER,
+ m_buffers[bufferIndex]);
+ for (j = 0; j < m_bufferVertexAttributeMap[i].length; j += 1) {
+ renderState.m_material
+ .bindVertexData(renderState, m_bufferVertexAttributeMap[i][j]);
+ }
+ bufferIndex += 1;
+ }
+ }
+
+ noOfPrimitives = m_geomData.numberOfPrimitives();
+ for (j = 0; j < noOfPrimitives; j += 1, bufferIndex += 1) {
+ primitive = m_geomData.primitive(j);
+ if (!primitive.numberOfIndices()) {
+ continue;
+ }
+ m_context.bindBuffer(vgl.GL.ARRAY_BUFFER, m_buffers[bufferIndex]);
+ switch (primitive.primitiveType()) {
+ case vgl.GL.POINTS:
+ m_context.drawArrays(vgl.GL.POINTS, 0, primitive.numberOfIndices());
+ break;
+ case vgl.GL.LINES:
+ m_context.drawArrays(vgl.GL.LINES, 0, primitive.numberOfIndices());
+ break;
+ case vgl.GL.LINE_STRIP:
+ m_context.drawArrays(vgl.GL.LINE_STRIP, 0, primitive.numberOfIndices());
+ break;
+ case vgl.GL.TRIANGLES:
+ m_context.drawArrays(vgl.GL.TRIANGLES, 0, primitive.numberOfIndices());
+ break;
+ case vgl.GL.TRIANGLE_STRIP:
+ m_context.drawArrays(vgl.GL.TRIANGLE_STRIP, 0, primitive.numberOfIndices());
+ break;
+ }
+ m_context.bindBuffer(vgl.GL.ARRAY_BUFFER, null);
+ }
+
+ /* If we are rendering multiple features in the same context, we must
+ * unbind the vertex data to make sure the next feature has a known state.
+ * This is optional.
+ */
+ if (!noUndoBindVertexData) {
+ this.undoBindVertexData(renderState);
+ }
+ };
+
+ /**
+ * Unbind the vertex data.
+ *
+ * @param {vgl.renderState} renderState
+ */
+ this.undoBindVertexData = function (renderState) {
+ var i, j;
+
+ for (i in m_bufferVertexAttributeMap) {
+ if (m_bufferVertexAttributeMap.hasOwnProperty(i)) {
+ for (j = 0; j < m_bufferVertexAttributeMap[i].length; j += 1) {
+ renderState.m_material
+ .undoBindVertexData(renderState, m_bufferVertexAttributeMap[i][j]);
+ }
+ }
+ }
+ };
+
+ return this;
+};
+
+inherit(vgl.mapper, vgl.boundingObject);
diff --git a/src/vgl/material.js b/src/vgl/material.js
new file mode 100644
index 0000000000..2134dceb95
--- /dev/null
+++ b/src/vgl/material.js
@@ -0,0 +1,205 @@
+var vgl = require('./vgl');
+var inherit = require('../inherit');
+
+/**
+ * Create a new instance of class material.
+ *
+ * @class
+ * @alias vgl.material
+ * @returns {vgl.material}
+ */
+vgl.material = function () {
+ 'use strict';
+
+ if (!(this instanceof vgl.material)) {
+ return new vgl.material();
+ }
+ vgl.graphicsObject.call(this);
+
+ var m_this = this,
+ m_shaderProgram = new vgl.shaderProgram(),
+ m_binNumber = 100,
+ m_textureAttributes = {},
+ m_attributes = {};
+
+ /**
+ * Return bin number for the material.
+ *
+ * @default 100
+ * @returns {number}
+ */
+ this.binNumber = function () {
+ return m_binNumber;
+ };
+
+ /**
+ * Set bin number for the material.
+ *
+ * @param {number} binNo
+ */
+ this.setBinNumber = function (binNo) {
+ m_binNumber = binNo;
+ m_this.modified();
+ };
+
+ /**
+ * Check if incoming attribute already exists in the material.
+ *
+ * @param {vgl.materialAttribute} attr
+ * @returns {boolean}
+ */
+ this.exists = function (attr) {
+ if (attr.type() === vgl.materialAttributeType.Texture) {
+ return m_textureAttributes.hasOwnProperty(attr.textureUnit());
+ }
+ return m_attributes.hasOwnProperty(attr.type());
+ };
+
+ /**
+ * Add a new attribute to the material.
+ *
+ * @param {vgl.materialAttribute} attr
+ * @returns {boolean}
+ */
+ this.addAttribute = function (attr) {
+ if (m_this.exists(attr)) {
+ return false;
+ }
+
+ if (attr.type() === vgl.materialAttributeType.Texture) {
+ // TODO Currently we don't check if we are replacing or not.
+ // It would be nice to have a flag for it.
+ m_textureAttributes[attr.textureUnit()] = attr;
+ m_this.modified();
+ return true;
+ }
+
+ // Shader is a very special attribute
+ if (attr.type() === vgl.materialAttributeType.ShaderProgram) {
+ m_shaderProgram = attr;
+ }
+
+ m_attributes[attr.type()] = attr;
+ m_this.modified();
+ return true;
+ };
+
+ /**
+ * Return shader program used by the material.
+ *
+ * @returns {vgl.shaderProgram}
+ */
+ this.shaderProgram = function () {
+ return m_shaderProgram;
+ };
+
+ /**
+ * Remove any resources acquired before deletion.
+ *
+ * @param {vgl.renderState} renderState
+ */
+ this._cleanup = function (renderState) {
+ for (var key in m_attributes) {
+ if (m_attributes.hasOwnProperty(key)) {
+ m_attributes[key]._cleanup(renderState);
+ }
+ }
+
+ for (key in m_textureAttributes) {
+ if (m_textureAttributes.hasOwnProperty(key)) {
+ m_textureAttributes[key]._cleanup(renderState);
+ }
+ }
+ m_shaderProgram._cleanup(renderState);
+ m_this.modified();
+ };
+
+ /**
+ * Bind and activate material states.
+ *
+ * @param {vgl.renderState} renderState
+ */
+ this.bind = function (renderState) {
+ var key = null;
+
+ m_shaderProgram.bind(renderState);
+
+ for (key in m_attributes) {
+ if (m_attributes.hasOwnProperty(key)) {
+ if (m_attributes[key] !== m_shaderProgram) {
+ m_attributes[key].bind(renderState);
+ }
+ }
+ }
+
+ for (key in m_textureAttributes) {
+ if (m_textureAttributes.hasOwnProperty(key)) {
+ m_textureAttributes[key].bind(renderState);
+ }
+ }
+ };
+
+ /**
+ * Undo-bind and de-activate material states.
+ *
+ * @param {vgl.renderState} renderState
+ */
+ this.undoBind = function (renderState) {
+ var key = null;
+ for (key in m_attributes) {
+ if (m_attributes.hasOwnProperty(key)) {
+ m_attributes[key].undoBind(renderState);
+ }
+ }
+
+ for (key in m_textureAttributes) {
+ if (m_textureAttributes.hasOwnProperty(key)) {
+ m_textureAttributes[key].undoBind(renderState);
+ }
+ }
+ };
+
+ /**
+ * Bind vertex data.
+ *
+ * @param {vgl.renderState} renderState
+ * @param {string} key
+ */
+ this.bindVertexData = function (renderState, key) {
+ var i = null;
+
+ for (i in m_attributes) {
+ if (m_attributes.hasOwnProperty(i)) {
+ m_attributes[i].bindVertexData(renderState, key);
+ }
+ }
+ };
+
+ /**
+ * Undo bind vertex data.
+ *
+ * @param {vgl.renderState} renderState
+ * @param {string} key
+ */
+ this.undoBindVertexData = function (renderState, key) {
+ var i = null;
+
+ for (i in m_attributes) {
+ if (m_attributes.hasOwnProperty(i)) {
+ m_attributes[i].undoBindVertexData(renderState, key);
+ }
+ }
+ };
+
+ return m_this;
+};
+
+vgl.material.RenderBin = {
+ Base : 0,
+ Default : 100,
+ Opaque : 100,
+ Transparent : 1000,
+ Overlay : 10000
+};
+
+inherit(vgl.material, vgl.graphicsObject);
diff --git a/src/vgl/materialAttribute.js b/src/vgl/materialAttribute.js
new file mode 100644
index 0000000000..2dc238e615
--- /dev/null
+++ b/src/vgl/materialAttribute.js
@@ -0,0 +1,76 @@
+var vgl = require('./vgl');
+var inherit = require('../inherit');
+
+vgl.materialAttributeType = {
+ Undefined : 0x0,
+ ShaderProgram : 0x1,
+ Texture : 0x2,
+ Blend : 0x3,
+ Depth : 0x4
+};
+
+/**
+ * Create a new instance of class materialAttribute.
+ *
+ * @class
+ * @alias vgl.materialAttribute
+ * @param {number} type
+ * @returns {vgl.materialAttribute}
+ */
+vgl.materialAttribute = function (type) {
+ 'use strict';
+
+ if (!(this instanceof vgl.materialAttribute)) {
+ return new vgl.materialAttribute(type);
+ }
+ vgl.graphicsObject.call(this);
+
+ /** @private */
+ var m_this = this,
+ m_type = type,
+ m_enabled = true;
+
+ /**
+ * Return type of the material attribute.
+ *
+ * @returns {number}
+ */
+ this.type = function () {
+ return m_type;
+ };
+
+ /**
+ * Return if material attribute is enabled or not.
+ *
+ * @returns {boolean}
+ */
+ this.enabled = function () {
+ return m_enabled;
+ };
+
+ /**
+ * Bind and activate vertex specific data.
+ *
+ * @param {vgl.renderState} renderState
+ * @param {string} key
+ * @returns {boolean}
+ */
+ this.bindVertexData = function (renderState, key) {
+ return false;
+ };
+
+ /**
+ * Undo bind and deactivate vertex specific data.
+ *
+ * @param {vgl.renderState} renderState
+ * @param {string} key
+ * @returns {boolean}
+ */
+ this.undoBindVertexData = function (renderState, key) {
+ return false;
+ };
+
+ return m_this;
+};
+
+inherit(vgl.materialAttribute, vgl.graphicsObject);
diff --git a/src/vgl/node.js b/src/vgl/node.js
new file mode 100644
index 0000000000..1bccc3bcb5
--- /dev/null
+++ b/src/vgl/node.js
@@ -0,0 +1,115 @@
+var vgl = require('./vgl');
+var inherit = require('../inherit');
+
+/**
+ * Create a new instance of class node.
+ *
+ * @class
+ * @alias vgl.node
+ * @returns {vgl.node}
+ */
+vgl.node = function () {
+ 'use strict';
+
+ if (!(this instanceof vgl.node)) {
+ return new vgl.node();
+ }
+ vgl.boundingObject.call(this);
+
+ var m_parent = null,
+ m_material = null,
+ m_visible = true;
+
+ /**
+ * Return active material used by the node.
+ *
+ * @returns {vgl.material}
+ */
+ this.material = function () {
+ return m_material;
+ };
+
+ /**
+ * Set material to be used the node.
+ *
+ * @param {vgl.material} material
+ * @returns {boolean}
+ */
+ this.setMaterial = function (material) {
+ if (material !== m_material) {
+ m_material = material;
+ this.modified();
+ return true;
+ }
+
+ return false;
+ };
+
+ /**
+ * Check if the node is visible or node.
+ *
+ * @returns {boolean}
+ */
+ this.visible = function () {
+ return m_visible;
+ };
+
+ /**
+ * Turn ON/OFF visibility of the node.
+ *
+ * @param {boolean} flag
+ * @returns {boolean}
+ */
+ this.setVisible = function (flag) {
+ if (flag !== m_visible) {
+ m_visible = flag;
+ this.modified();
+ return true;
+ }
+
+ return false;
+ };
+
+ /**
+ * Return current parent of the node.
+ *
+ * @returns {vgl.node}
+ */
+ this.parent = function () {
+ return m_parent;
+ };
+
+ /**
+ * Set parent of the node.
+ *
+ * @param {vgl.node} parent
+ * @returns {boolean}
+ */
+ this.setParent = function (parent) {
+ if (parent !== m_parent) {
+ if (m_parent !== null) {
+ m_parent.removeChild(this);
+ }
+ m_parent = parent;
+ this.modified();
+ return true;
+ }
+
+ return false;
+ };
+
+ /**
+ * Mark that the bounds are modified.
+ */
+ this.boundsModified = function () {
+ this.boundsDirtyTimestamp().modified();
+
+ if (m_parent !== null) {
+ m_parent.boundsModified();
+ }
+ };
+
+ return this;
+};
+
+inherit(vgl.node, vgl.boundingObject);
diff --git a/src/vgl/object.js b/src/vgl/object.js
new file mode 100644
index 0000000000..49fd2210f8
--- /dev/null
+++ b/src/vgl/object.js
@@ -0,0 +1,39 @@
+var vgl = require('./vgl');
+var timestamp = require('../timestamp');
+
+/**
+ * Create a new instance of class object.
+ *
+ * @class
+ * @alias vgl.object.
+ * @returns {vgl.object}
+ */
+vgl.object = function () {
+ 'use strict';
+
+ if (!(this instanceof vgl.object)) {
+ return new vgl.object();
+ }
+
+ /** @private */
+ var m_modifiedTime = timestamp();
+ m_modifiedTime.modified();
+
+ /**
+ * Mark the object modified.
+ */
+ this.modified = function () {
+ m_modifiedTime.modified();
+ };
+
+ /**
+ * Return modified time of the object.
+ *
+ * @returns {number}
+ */
+ this.getMTime = function () {
+ return m_modifiedTime.getMTime();
+ };
+
+ return this;
+};
diff --git a/src/vgl/renderWindow.js b/src/vgl/renderWindow.js
new file mode 100644
index 0000000000..61c559609c
--- /dev/null
+++ b/src/vgl/renderWindow.js
@@ -0,0 +1,200 @@
+var vgl = require('./vgl');
+var inherit = require('../inherit');
+
+/**
+ * Create a new instance of class renderWindow.
+ *
+ * @class
+ * @alias vgl.renderWindow
+ * @param {HTMLElement} canvas
+ * @returns {vgl.renderWindow}
+ */
+vgl.renderWindow = function (canvas) {
+ 'use strict';
+
+ if (!(this instanceof vgl.renderWindow)) {
+ return new vgl.renderWindow(canvas);
+ }
+ vgl.graphicsObject.call(this);
+
+ var m_this = this,
+ m_x = 0,
+ m_y = 0,
+ m_width = 400,
+ m_height = 400,
+ m_canvas = canvas,
+ m_activeRender = null,
+ m_renderers = [],
+ m_context = null;
+
+ /**
+ * Get size of the render window.
+ *
+ * @returns {number[]}
+ */
+ this.windowSize = function () {
+ return [m_width, m_height];
+ };
+
+ /**
+ * Get window position (top left coordinates).
+ *
+ * @returns {number[]}
+ */
+ this.windowPosition = function () {
+ return [m_x, m_y];
+ };
+
+ /**
+ * Return all renderers contained in the render window.
+ *
+ * @returns {vgl.renderer[]}
+ */
+ this.renderers = function () {
+ return m_renderers;
+ };
+
+ /**
+ * Get active renderer of the the render window.
+ *
+ * @returns {vgl.renderer}
+ */
+ this.activeRenderer = function () {
+ return m_activeRender;
+ };
+
+ /**
+ * Add renderer to the render window.
+ *
+ * @param {vgl.renderer} ren
+ * @returns {boolean}
+ */
+ this.addRenderer = function (ren) {
+ if (m_this.hasRenderer(ren) === false) {
+ m_renderers.push(ren);
+ ren.setRenderWindow(m_this);
+ if (m_activeRender === null) {
+ m_activeRender = ren;
+ }
+ if (ren.layer() !== 0) {
+ ren.camera().setClearMask(vgl.GL.DepthBufferBit);
+ }
+ m_this.modified();
+ return true;
+ }
+ return false;
+ };
+
+ /**
+ * Check if the renderer exists.
+ *
+ * @param {vgl.renderer} ren
+ * @returns {boolean}
+ */
+ this.hasRenderer = function (ren) {
+ var i;
+ for (i = 0; i < m_renderers.length; i += 1) {
+ if (ren === m_renderers[i]) {
+ return true;
+ }
+ }
+
+ return false;
+ };
+
+ /**
+ * Resize and reposition the window.
+ *
+ * @param {number} x
+ * @param {number} y
+ * @param {number} width
+ * @param {number} height
+ */
+ this.positionAndResize = function (x, y, width, height) {
+ m_x = x;
+ m_y = y;
+ m_width = width;
+ m_height = height;
+ var i;
+ for (i = 0; i < m_renderers.length; i += 1) {
+ m_renderers[i].positionAndResize(m_x, m_y, m_width, m_height);
+ }
+ m_this.modified();
+ };
+
+ /**
+ * Create the window.
+ *
+ * @param {vgl.renderState} renderState
+ * @returns {boolean}
+ */
+ this._setup = function (renderState) {
+ m_context = null;
+
+ try {
+ // Try to grab the standard context. If it fails, fallback to
+ // experimental.
+ m_context = m_canvas.getContext('webgl') ||
+ m_canvas.getContext('experimental-webgl');
+
+ // Set width and height of renderers if not set already
+ var i;
+ for (i = 0; i < m_renderers.length; i += 1) {
+ if ((m_renderers[i].width() > m_width) ||
+ m_renderers[i].width() === 0 ||
+ (m_renderers[i].height() > m_height) ||
+ m_renderers[i].height() === 0) {
+ m_renderers[i].resize(m_x, m_y, m_width, m_height);
+ }
+ }
+
+ return true;
+ } catch (e) {
+ }
+
+ // If we don't have a GL context, give up now
+ if (!m_context) {
+ console('[ERROR] Unable to initialize WebGL. Your browser may not support it.');
+ }
+
+ return false;
+ };
+
+ /**
+ * Return current GL context.
+ *
+ * @returns {WebGLRenderingContext}
+ */
+ this.context = function () {
+ return m_context;
+ };
+
+ /**
+ * Delete this window and release any graphics resources.
+ *
+ * @param {vgl.renderState} renderState
+ */
+ this._cleanup = function (renderState) {
+ var i;
+ for (i = 0; i < m_renderers.length; i += 1) {
+ m_renderers[i]._cleanup(renderState);
+ }
+ vgl.clearCachedShaders(renderState ? renderState.m_context : null);
+ m_this.modified();
+ };
+
+ /**
+ * Render the scene.
+ */
+ this.render = function () {
+ var i;
+ m_renderers.sort(function (a, b) { return a.layer() - b.layer(); });
+ for (i = 0; i < m_renderers.length; i += 1) {
+ m_renderers[i].render();
+ }
+ };
+
+ return m_this;
+};
+
+inherit(vgl.renderWindow, vgl.graphicsObject);
diff --git a/src/vgl/renderer.js b/src/vgl/renderer.js
new file mode 100644
index 0000000000..31b802546f
--- /dev/null
+++ b/src/vgl/renderer.js
@@ -0,0 +1,373 @@
+var vgl = require('./vgl');
+var inherit = require('../inherit');
+var mat4 = require('gl-mat4');
+
+/**
+ * Create a new instance of class renderState.
+ *
+ * @class
+ * @alias vgl.renderState
+ */
+vgl.renderState = function () {
+ 'use strict';
+
+ this.m_context = null;
+ this.m_modelViewMatrix = mat4.create();
+ this.m_normalMatrix = mat4.create();
+ this.m_projectionMatrix = null;
+ this.m_material = null;
+ this.m_mapper = null;
+};
+
+/**
+ * Create a new instance of class renderer.
+ *
+ * @class
+ * @alias vgl.renderer
+ * @extends vgl.graphicsObject
+ * @param {object} arg
+ * @returns {vgl.renderer}
+ */
+vgl.renderer = function (arg) {
+ 'use strict';
+
+ if (!(this instanceof vgl.renderer)) {
+ return new vgl.renderer(arg);
+ }
+ vgl.graphicsObject.call(this);
+ arg = arg || {};
+
+ /** @private */
+ var m_this = this;
+ m_this.m_renderWindow = null;
+ m_this.m_contextChanged = false;
+ m_this.m_sceneRoot = new vgl.groupNode();
+ m_this.m_camera = new vgl.camera(arg);
+ m_this.m_nearClippingPlaneTolerance = null;
+ m_this.m_x = 0;
+ m_this.m_y = 0;
+ m_this.m_width = 0;
+ m_this.m_height = 0;
+ m_this.m_resizable = true;
+ m_this.m_resetScene = true;
+ m_this.m_layer = 0;
+ m_this.m_renderPasses = null;
+ m_this.m_resetClippingRange = true;
+ m_this.m_depthBits = null;
+
+ m_this.m_camera.addChild(m_this.m_sceneRoot);
+
+ /**
+ * Get width of the renderer.
+ *
+ * @returns {number}
+ */
+ this.width = function () {
+ return m_this.m_width;
+ };
+
+ /**
+ * Get height of the renderer.
+ *
+ * @returns {number}
+ */
+ this.height = function () {
+ return m_this.m_height;
+ };
+
+ /**
+ * Get layer this renderer is associated with.
+ *
+ * @returns {number}
+ */
+ this.layer = function () {
+ return m_this.m_layer;
+ };
+
+ /**
+ * Set the layer this renderer is associated with.
+ *
+ * @param {number} layerNo
+ */
+ this.setLayer = function (layerNo) {
+ m_this.m_layer = layerNo;
+ m_this.modified();
+ };
+
+ /**
+ * Return render window (owner) of the renderer.
+ *
+ * @returns {vgl.renderWindow}
+ */
+ this.renderWindow = function () {
+ return m_this.m_renderWindow;
+ };
+
+ /**
+ * Set render window for the renderer.
+ *
+ * @param {vgl.renderWindow} renWin
+ */
+ this.setRenderWindow = function (renWin) {
+ if (m_this.m_renderWindow !== renWin) {
+ if (m_this.m_renderWindow) {
+ m_this.m_renderWindow.removeRenderer(this);
+ }
+ m_this.m_renderWindow = renWin;
+ m_this.m_contextChanged = true;
+ m_this.modified();
+ }
+ };
+
+ /**
+ * Get main camera of the renderer.
+ *
+ * @returns {vgl.camera}
+ */
+ this.camera = function () {
+ return m_this.m_camera;
+ };
+
+ /**
+ * Render the scene.
+ */
+ this.render = function () {
+ var i, renSt, children, actor = null, sortedActors = [],
+ mvMatrixInv = mat4.create(), clearColor = null;
+
+ renSt = new vgl.renderState();
+ renSt.m_renderer = m_this;
+ renSt.m_context = m_this.renderWindow().context();
+ if (!m_this.m_depthBits || m_this.m_contextChanged) {
+ m_this.m_depthBits = renSt.m_context.getParameter(vgl.GL.DEPTH_BITS);
+ }
+ renSt.m_contextChanged = m_this.m_contextChanged;
+
+ if (m_this.m_renderPasses) {
+ for (i = 0; i < m_this.m_renderPasses.length; i += 1) {
+ if (m_this.m_renderPasses[i].render(renSt)) {
+ // Stop the rendering if render pass returns false
+ console.log('returning');
+ m_this.m_renderPasses[i].remove(renSt);
+ return;
+ }
+ m_this.m_renderPasses[i].remove(renSt);
+ }
+ }
+
+ renSt.m_context.enable(vgl.GL.DEPTH_TEST);
+ renSt.m_context.depthFunc(vgl.GL.LEQUAL);
+
+ if (m_this.m_camera.clearMask() & vgl.GL.COLOR_BUFFER_BIT) {
+ clearColor = m_this.m_camera.clearColor();
+ renSt.m_context.clearColor(clearColor[0], clearColor[1],
+ clearColor[2], clearColor[3]);
+ }
+
+ if (m_this.m_camera.clearMask() & vgl.GL.DEPTH_BUFFER_BIT) {
+ renSt.m_context.clearDepth(m_this.m_camera.clearDepth());
+ }
+
+ renSt.m_context.clear(m_this.m_camera.clearMask());
+
+ // Set the viewport for this renderer
+ renSt.m_context.viewport(m_this.m_x, m_this.m_y,
+ m_this.m_width, m_this.m_height);
+
+ children = m_this.m_sceneRoot.children();
+
+ if (children.length > 0 && m_this.m_resetScene) {
+ m_this.m_resetScene = false;
+ }
+
+ for (i = 0; i < children.length; i += 1) {
+ actor = children[i];
+
+ // Compute the bounds even if the actor is not visible
+ actor.computeBounds();
+
+ // If bin number is < 0, then don't even bother
+ // rendering the data
+ if (actor.visible() && actor.material().binNumber() >= 0) {
+ sortedActors.push([actor.material().binNumber(), actor]);
+ }
+ }
+
+ // Now perform sorting
+ sortedActors.sort(function (a, b) { return a[0] - b[0]; });
+
+ for (i = 0; i < sortedActors.length; i += 1) {
+ actor = sortedActors[i][1];
+ if (actor.referenceFrame() ===
+ vgl.boundingObject.ReferenceFrame.Relative) {
+ var view = m_this.m_camera.viewMatrix();
+ /* If the view matrix is a plain array, keep it as such. This is
+ * intended to preserve precision, and will only be the case if the
+ * view matrix was created by deliberately setting it as an array. */
+ if (view instanceof Array) {
+ renSt.m_modelViewMatrix = new Array(16);
+ }
+ mat4.multiply(renSt.m_modelViewMatrix, view, actor.matrix());
+ renSt.m_projectionMatrix = m_this.m_camera.projectionMatrix();
+ renSt.m_modelViewAlignment = m_this.m_camera.viewAlignment();
+ } else {
+ renSt.m_modelViewMatrix = actor.matrix();
+ renSt.m_modelViewAlignment = null;
+ renSt.m_projectionMatrix = mat4.create();
+ mat4.ortho(renSt.m_projectionMatrix,
+ 0, m_this.m_width, 0, m_this.m_height, -1, 1);
+ }
+
+ mat4.invert(mvMatrixInv, renSt.m_modelViewMatrix);
+ mat4.transpose(renSt.m_normalMatrix, mvMatrixInv);
+ renSt.m_material = actor.material();
+ renSt.m_mapper = actor.mapper();
+
+ // TODO Fix this shortcut
+ renSt.m_material.bind(renSt);
+ renSt.m_mapper.render(renSt);
+ renSt.m_material.undoBind(renSt);
+ }
+
+ renSt.m_context.finish();
+ m_this.m_contextChanged = false;
+ m_this.m_lastRenderState = renSt;
+ };
+
+ /**
+ * Resize viewport given a width and height.
+ *
+ * @param {number} width
+ * @param {number} height
+ */
+ this.resize = function (width, height) {
+ if (!width || !height) {
+ return;
+ }
+ // @note: where do m_this.m_x and m_this.m_y come from?
+ m_this.positionAndResize(m_this.m_x, m_this.m_y, width, height);
+ };
+
+ /**
+ * Resize viewport given a position, width and height.
+ *
+ * @param {number} x
+ * @param {number} y
+ * @param {number} width
+ * @param {number} height
+ */
+ this.positionAndResize = function (x, y, width, height) {
+ var i;
+
+ // TODO move this code to camera
+ if (x < 0 || y < 0 || width <= 0 || height <= 0) {
+ console.log('[error] Invalid position and resize values',
+ x, y, width, height);
+ return;
+ }
+
+ //If we're allowing this renderer to resize ...
+ if (m_this.m_resizable) {
+ m_this.m_width = width;
+ m_this.m_height = height;
+
+ m_this.m_camera.setViewAspect(width / height);
+ m_this.m_camera.setParallelExtents({width: width, height: height});
+ m_this.modified();
+ }
+
+ if (m_this.m_renderPasses) {
+ for (i = 0; i < m_this.m_renderPasses.length; i += 1) {
+ m_this.m_renderPasses[i].resize(width, height);
+ m_this.m_renderPasses[i].renderer().positionAndResize(x, y, width, height);
+ }
+ }
+ };
+
+ /**
+ * Add new actor to the collection.
+ *
+ * @param {vgl.actor} actor
+ * @returns {boolean}
+ */
+ this.addActor = function (actor) {
+ if (actor instanceof vgl.actor) {
+ m_this.m_sceneRoot.addChild(actor);
+ m_this.modified();
+ return true;
+ }
+
+ return false;
+ };
+
+ /**
+ * Return true if this renderer has this actor attached, false otherwise.
+ *
+ * @param {vgl.actor} actor
+ * @returns {boolean}
+ */
+ this.hasActor = function (actor) {
+ return m_this.m_sceneRoot.hasChild(actor);
+ };
+
+ /**
+ * Remove the actor from the collection.
+ *
+ * @param {vgl.actor} actor
+ * @returns {boolean}
+ */
+ this.removeActor = function (actor) {
+ if (m_this.m_sceneRoot.children().indexOf(actor) !== -1) {
+ /* When we remove an actor, free the VBOs of the mapper and mark the
+ * mapper as modified; it will reallocate VBOs as necessary. */
+ if (m_this.m_lastRenderState) {
+ if (actor.mapper()) {
+ actor.mapper()._cleanup(m_this.m_lastRenderState);
+ }
+ if (actor.material()) {
+ actor.material()._cleanup(m_this.m_lastRenderState);
+ }
+ }
+ actor.modified();
+ m_this.m_sceneRoot.removeChild(actor);
+ m_this.modified();
+ return true;
+ }
+
+ return false;
+ };
+
+ /**
+ * If true the scene will be reset, otherwise the scene will not be
+ * automatically reset.
+ *
+ * @param {boolean} reset
+ */
+ this.setResetScene = function (reset) {
+ if (m_this.m_resetScene !== reset) {
+ m_this.m_resetScene = reset;
+ m_this.modified();
+ }
+ };
+
+ /**
+ * Cleanup.
+ *
+ * @param {vgl.renderState} renderState
+ */
+ this._cleanup = function (renderState) {
+ var children = m_this.m_sceneRoot.children();
+ for (var i = 0; i < children.length; i += 1) {
+ var actor = children[i];
+ actor.material()._cleanup(renderState);
+ actor.mapper()._cleanup(renderState);
+ }
+
+ m_this.m_sceneRoot.removeChildren();
+ m_this.modified();
+ };
+
+ return m_this;
+};
+
+inherit(vgl.renderer, vgl.graphicsObject);
diff --git a/src/vgl/shader.js b/src/vgl/shader.js
new file mode 100644
index 0000000000..9ad34e3ed5
--- /dev/null
+++ b/src/vgl/shader.js
@@ -0,0 +1,196 @@
+var vgl = require('./vgl');
+var inherit = require('../inherit');
+var timestamp = require('../timestamp');
+
+/**
+ * Create a new instance of class shader.
+ *
+ * @class
+ * @alias vgl.shader
+ * @extends vgl.object
+ * @param {number} type The GL shader type.
+ * @returns {vgl.shader}
+ */
+vgl.shader = function (type) {
+ 'use strict';
+
+ if (!(this instanceof vgl.shader)) {
+ return new vgl.shader(type);
+ }
+ vgl.object.call(this);
+
+ var m_shaderContexts = [],
+ m_shaderType = type,
+ m_shaderSource = '';
+
+ /**
+ * A shader can be associated with multiple contexts. Each context needs to
+ * be compiled and attached separately. These are tracked in the
+ * m_shaderContexts array.
+ *
+ * @param {vgl.renderState} renderState a renderState that includes a
+ * m_context value.
+ * @returns {object} an object with context, compileTimestamp, and, if
+ * compiled, a shaderHandle entry.
+ */
+ this._getContextEntry = function (renderState) {
+ var context = renderState.m_context, i, entry;
+ for (i = 0; i < m_shaderContexts.length; i += 1) {
+ if (m_shaderContexts[i].context === context) {
+ return m_shaderContexts[i];
+ }
+ }
+ entry = {
+ context: context,
+ compileTimestamp: timestamp()
+ };
+ m_shaderContexts.push(entry);
+ return entry;
+ };
+
+ /**
+ * Remove the context from the list of tracked contexts. This allows the
+ * associated shader handle to be GCed. Does nothing if the context is not
+ * in the list of tracked contexts.
+ *
+ * @param {vgl.renderState} renderState A renderState that includes a
+ * m_context value.
+ */
+ this.removeContext = function (renderState) {
+ var context = renderState.m_context, i;
+ for (i = 0; i < m_shaderContexts.length; i += 1) {
+ if (m_shaderContexts[i].context === context) {
+ m_shaderContexts.splice(i, 1);
+ return;
+ }
+ }
+ };
+
+ /**
+ * Get shader handle.
+ *
+ * @param {vgl.renderState} renderState
+ * @returns {number} GL shader handle
+ */
+ this.shaderHandle = function (renderState) {
+ var entry = this._getContextEntry(renderState);
+ return entry.shaderHandle;
+ };
+
+ /**
+ * Set shader source.
+ *
+ * @param {string} source
+ */
+ this.setShaderSource = function (source) {
+ m_shaderSource = source;
+ this.modified();
+ };
+
+ /**
+ * Compile the shader.
+ *
+ * @param {vgl.renderState} renderState
+ * @returns {number} GL shader handle.
+ */
+ this.compile = function (renderState) {
+ var entry = this._getContextEntry(renderState);
+ if (this.getMTime() < entry.compileTimestamp.getMTime()) {
+ return entry.shaderHandle;
+ }
+
+ renderState.m_context.deleteShader(entry.shaderHandle);
+ entry.shaderHandle = renderState.m_context.createShader(m_shaderType);
+ renderState.m_context.shaderSource(entry.shaderHandle, m_shaderSource);
+ renderState.m_context.compileShader(entry.shaderHandle);
+
+ // See if it compiled successfully
+ if (!renderState.m_context.getShaderParameter(entry.shaderHandle,
+ vgl.GL.COMPILE_STATUS)) {
+ console.log('[ERROR] An error occurred compiling the shaders: ' +
+ renderState.m_context.getShaderInfoLog(entry.shaderHandle));
+ console.log(m_shaderSource);
+ renderState.m_context.deleteShader(entry.shaderHandle);
+ return null;
+ }
+
+ entry.compileTimestamp.modified();
+
+ return entry.shaderHandle;
+ };
+
+ /**
+ * Attach shader to the program.
+ *
+ * @param {vgl.renderState} renderState
+ * @param {number} programHandle GL shader handler.
+ */
+ this.attachShader = function (renderState, programHandle) {
+ renderState.m_context.attachShader(
+ programHandle, this.shaderHandle(renderState));
+ };
+};
+
+inherit(vgl.shader, vgl.object);
+
+/* We can use the same shader multiple times if it is identical. This caches
+ * the last N shaders and will reuse them when possible. The cache keeps the
+ * most recently requested shader at the front. If you are doing anything more
+ * to a shader then creating it and setting its source once, do not use this
+ * cache.
+ */
+(function () {
+ 'use strict';
+ var m_shaderCache = [],
+ m_shaderCacheMaxSize = 10;
+
+ /**
+ * Get a shader from the cache. Create a new shader if necessary using a
+ * specific source.
+ *
+ * @param {number} type One of vgl.GL.*_SHADER
+ * @param {WebGLRenderingContext} context The GL context for the shader.
+ * @param {string} source The source code of the shader.
+ * @returns {number} GL shader handle
+ */
+ vgl.getCachedShader = function (type, context, source) {
+ for (var i = 0; i < m_shaderCache.length; i += 1) {
+ if (m_shaderCache[i].type === type &&
+ m_shaderCache[i].context === context &&
+ m_shaderCache[i].source === source) {
+ if (i) {
+ m_shaderCache.splice(0, 0, m_shaderCache.splice(i, 1)[0]);
+ }
+ return m_shaderCache[0].shader;
+ }
+ }
+ var shader = new vgl.shader(type);
+ shader.setShaderSource(source);
+ m_shaderCache.unshift({
+ type: type,
+ context: context,
+ source: source,
+ shader: shader
+ });
+ if (m_shaderCache.length >= m_shaderCacheMaxSize) {
+ m_shaderCache.splice(m_shaderCacheMaxSize,
+ m_shaderCache.length - m_shaderCacheMaxSize);
+ }
+ return shader;
+ };
+
+ /**
+ * Clear the shader cache.
+ *
+ * @param {WebGLRenderingContext} context The GL context to clear, or null
+ * for clear all.
+ */
+ vgl.clearCachedShaders = function (context) {
+ for (var i = m_shaderCache.length - 1; i >= 0; i -= 1) {
+ if (context === null || context === undefined ||
+ m_shaderCache[i].context === context) {
+ m_shaderCache.splice(i, 1);
+ }
+ }
+ };
+})();
diff --git a/src/vgl/shaderProgram.js b/src/vgl/shaderProgram.js
new file mode 100644
index 0000000000..f17a3b2676
--- /dev/null
+++ b/src/vgl/shaderProgram.js
@@ -0,0 +1,369 @@
+var vgl = require('./vgl');
+var inherit = require('../inherit');
+var timestamp = require('../timestamp');
+
+/**
+ * Create a new instance of class shaderProgram.
+ *
+ * @class
+ * @alias vgl.shaderProgram
+ * @returns {vgl.shaderProgram}
+ */
+vgl.shaderProgram = function () {
+ 'use strict';
+
+ if (!(this instanceof vgl.shaderProgram)) {
+ return new vgl.shaderProgram();
+ }
+ vgl.materialAttribute.call(
+ this, vgl.materialAttributeType.ShaderProgram);
+
+ var m_this = this,
+ m_programHandle = 0,
+ m_compileTimestamp = timestamp(),
+ m_bindTimestamp = timestamp(),
+ m_shaders = [],
+ m_uniforms = [],
+ m_vertexAttributes = {},
+ m_uniformNameToLocation = {},
+ m_vertexAttributeNameToLocation = {};
+
+ /**
+ * Query uniform location in the program.
+ *
+ * @param {vgl.renderState} renderState
+ * @param {string} name
+ * @returns {number}
+ */
+ this.queryUniformLocation = function (renderState, name) {
+ return renderState.m_context.getUniformLocation(m_programHandle, name);
+ };
+
+ /**
+ * Query attribute location in the program.
+ *
+ * @param {vgl.renderState} renderState
+ * @param {string} name
+ * @returns {number}
+ */
+ this.queryAttributeLocation = function (renderState, name) {
+ return renderState.m_context.getAttribLocation(m_programHandle, name);
+ };
+
+ /**
+ * Add a new shader to the program.
+ *
+ * @param {string} shader
+ * @returns {boolean}
+ */
+ this.addShader = function (shader) {
+ if (m_shaders.indexOf(shader) > -1) {
+ return false;
+ }
+
+ var i;
+ for (i = m_shaders.length - 2; i >= 0; i -= 1) {
+ if (m_shaders[i].shaderType() === shader.shaderType()) {
+ m_shaders.splice(i, 1);
+ }
+ }
+
+ m_shaders.push(shader);
+ m_this.modified();
+ return true;
+ };
+
+ /**
+ * Add a new uniform to the program.
+ *
+ * @param {vgl.uniform} uniform
+ * @returns {boolean}
+ */
+ this.addUniform = function (uniform) {
+ if (m_uniforms.indexOf(uniform) > -1) {
+ return false;
+ }
+
+ m_uniforms.push(uniform);
+ m_this.modified();
+ return true;
+ };
+
+ /**
+ * Add a new vertex attribute to the program.
+ *
+ * @param {vgl.vertexAttribute} attr
+ * @param {string} key
+ */
+ this.addVertexAttribute = function (attr, key) {
+ m_vertexAttributes[key] = attr;
+ m_this.modified();
+ };
+
+ /**
+ * Get uniform location.
+ *
+ * This method does not perform any query into the program but relies on
+ * the fact that it depends on a call to queryUniformLocation earlier.
+ *
+ * @param {string} name
+ * @returns {number}
+ */
+ this.uniformLocation = function (name) {
+ return m_uniformNameToLocation[name];
+ };
+
+ /**
+ * Get attribute location.
+ *
+ * This method does not perform any query into the program but relies on the
+ * fact that it depends on a call to queryUniformLocation earlier.
+ *
+ * @param {string} name
+ * @returns {number}
+ */
+ this.attributeLocation = function (name) {
+ return m_vertexAttributeNameToLocation[name];
+ };
+
+ /**
+ * Get uniform object using name as the key.
+ *
+ * @param {string} name
+ * @returns {vgl.uniform}
+ */
+ this.uniform = function (name) {
+ var i;
+ for (i = 0; i < m_uniforms.length; i += 1) {
+ if (m_uniforms[i].name() === name) {
+ return m_uniforms[i];
+ }
+ }
+
+ return null;
+ };
+
+ /**
+ * Update all uniforms.
+ *
+ * This method should not be used directly unless required.
+ *
+ * @param {vgl.renderState} renderState
+ */
+ this.updateUniforms = function (renderState) {
+ var i;
+
+ for (i = 0; i < m_uniforms.length; i += 1) {
+ m_uniforms[i].callGL(renderState,
+ m_uniformNameToLocation[m_uniforms[i].name()]);
+ }
+ };
+
+ /**
+ * Link shader program.
+ *
+ * @param {vgl.renderState} renderState
+ * @returns {boolean}
+ */
+ this.link = function (renderState) {
+ renderState.m_context.linkProgram(m_programHandle);
+
+ // If creating the shader program failed, alert
+ if (!renderState.m_context.getProgramParameter(m_programHandle,
+ vgl.GL.LINK_STATUS)) {
+ console.log('[ERROR] Unable to initialize the shader program.');
+ return false;
+ }
+
+ return true;
+ };
+
+ /**
+ * Use the shader program.
+ *
+ * @param {vgl.renderState} renderState
+ */
+ this.use = function (renderState) {
+ renderState.m_context.useProgram(m_programHandle);
+ };
+
+ /**
+ * Perform any initialization required.
+ *
+ * @param {vgl.renderState} renderState
+ */
+ this._setup = function (renderState) {
+ if (m_programHandle === 0) {
+ m_programHandle = renderState.m_context.createProgram();
+ }
+ };
+
+ /**
+ * Perform any clean up required when the program gets deleted.
+ *
+ * @param {vgl.renderState} renderState
+ */
+ this._cleanup = function (renderState) {
+ m_this.deleteVertexAndFragment(renderState);
+ m_this.deleteProgram(renderState);
+ m_this.modified();
+ };
+
+ /**
+ * Delete the shader program.
+ *
+ * @param {vgl.renderState} renderState
+ */
+ this.deleteProgram = function (renderState) {
+ if (m_programHandle) {
+ renderState.m_context.deleteProgram(m_programHandle);
+ }
+ m_programHandle = 0;
+ };
+
+ /**
+ * Delete vertex and fragment shaders.
+ *
+ * @param {vgl.renderState} renderState
+ */
+ this.deleteVertexAndFragment = function (renderState) {
+ var i;
+ for (i = 0; i < m_shaders.length; i += 1) {
+ if (m_shaders[i].shaderHandle(renderState)) {
+ renderState.m_context.detachShader(m_programHandle, m_shaders[i].shaderHandle(renderState));
+ }
+ renderState.m_context.deleteShader(m_shaders[i].shaderHandle(renderState));
+ m_shaders[i].removeContext(renderState);
+ }
+ };
+
+ /**
+ * Compile and link a shader.
+ *
+ * @param {vgl.renderState} renderState
+ */
+ this.compileAndLink = function (renderState) {
+ var i;
+
+ if (m_compileTimestamp.getMTime() >= this.getMTime()) {
+ return;
+ }
+
+ m_this._setup(renderState);
+
+ // Compile shaders
+ for (i = 0; i < m_shaders.length; i += 1) {
+ m_shaders[i].compile(renderState);
+ m_shaders[i].attachShader(renderState, m_programHandle);
+ }
+
+ m_this.bindAttributes(renderState);
+
+ // link program
+ if (!m_this.link(renderState)) {
+ console.log('[ERROR] Failed to link Program');
+ m_this._cleanup(renderState);
+ }
+
+ m_compileTimestamp.modified();
+ };
+
+ /**
+ * Bind the program with its shaders.
+ *
+ * @param {vgl.renderState} renderState
+ */
+ this.bind = function (renderState) {
+ var i = 0;
+
+ if (m_bindTimestamp.getMTime() < m_this.getMTime()) {
+
+ // Compile shaders
+ m_this.compileAndLink(renderState);
+
+ m_this.use(renderState);
+ m_this.bindUniforms(renderState);
+ m_bindTimestamp.modified();
+ } else {
+ m_this.use(renderState);
+ }
+
+ // Call update callback.
+ for (i = 0; i < m_uniforms.length; i += 1) {
+ m_uniforms[i].update(renderState, m_this);
+ }
+
+ // Now update values to GL.
+ m_this.updateUniforms(renderState);
+ };
+
+ /**
+ * Undo binding of the shader program.
+ *
+ * @param {vgl.renderState} renderState
+ */
+ this.undoBind = function (renderState) {
+ // REF https://www.khronos.org/opengles/sdk/docs/man/xhtml/glUseProgram.xml
+ // If program is 0, then the current rendering state refers to an invalid
+ // program object, and the results of vertex and fragment shader execution
+ // due to any glDrawArrays or glDrawElements commands are undefined
+ renderState.m_context.useProgram(null);
+ };
+
+ /**
+ * Bind vertex data.
+ *
+ * @param {vgl.renderState} renderState
+ * @param {string} key
+ */
+ this.bindVertexData = function (renderState, key) {
+ if (m_vertexAttributes.hasOwnProperty(key)) {
+ m_vertexAttributes[key].bindVertexData(renderState, key);
+ }
+ };
+
+ /**
+ * Undo bind vertex data.
+ *
+ * @param {vgl.renderState} renderState
+ * @param {string} key
+ */
+ this.undoBindVertexData = function (renderState, key) {
+ if (m_vertexAttributes.hasOwnProperty(key)) {
+ m_vertexAttributes[key].undoBindVertexData(renderState, key);
+ }
+ };
+
+ /**
+ * Bind uniforms.
+ *
+ * @param {vgl.renderState} renderState
+ */
+ this.bindUniforms = function (renderState) {
+ var i;
+ for (i = 0; i < m_uniforms.length; i += 1) {
+ m_uniformNameToLocation[m_uniforms[i].name()] = this
+ .queryUniformLocation(renderState, m_uniforms[i].name());
+ }
+ };
+
+ /**
+ * Bind vertex attributes.
+ *
+ * @param {vgl.renderState} renderState
+ */
+ this.bindAttributes = function (renderState) {
+ var key, name;
+ for (key in m_vertexAttributes) {
+ if (m_vertexAttributes.hasOwnProperty(key)) {
+ name = m_vertexAttributes[key].name();
+ renderState.m_context.bindAttribLocation(m_programHandle, key, name);
+ m_vertexAttributeNameToLocation[name] = key;
+ }
+ }
+ };
+
+ return m_this;
+};
+
+inherit(vgl.shaderProgram, vgl.materialAttribute);
diff --git a/src/vgl/texture.js b/src/vgl/texture.js
new file mode 100644
index 0000000000..816be6dfc0
--- /dev/null
+++ b/src/vgl/texture.js
@@ -0,0 +1,355 @@
+var vgl = require('./vgl');
+var inherit = require('../inherit');
+var timestamp = require('../timestamp');
+
+/**
+ * Create a new instance of class texture.
+ *
+ * @class
+ * @alias vgl.texture
+ * @returns {vgl.texture}
+ */
+vgl.texture = function () {
+ 'use strict';
+
+ if (!(this instanceof vgl.texture)) {
+ return new vgl.texture();
+ }
+ vgl.materialAttribute.call(this, vgl.materialAttributeType.Texture);
+
+ this.m_width = 0;
+ this.m_height = 0;
+ this.m_depth = 0;
+
+ this.m_textureHandle = null;
+ this.m_textureUnit = 0;
+
+ this.m_pixelFormat = vgl.GL.RGBA;
+ this.m_pixelDataType = vgl.GL.UNSIGNED_BYTE;
+ this.m_internalFormat = vgl.GL.RGBA;
+ this.m_nearestPixel = false;
+
+ this.m_image = null;
+
+ var m_setupTimestamp = timestamp(),
+ m_that = this;
+
+ function activateTextureUnit(renderState) {
+ if (m_that.m_textureUnit >= 0 && m_that.m_textureUnit < 32) {
+ renderState.m_context.activeTexture(vgl.GL.TEXTURE0 + m_that.m_textureUnit);
+ } else {
+ throw '[error] Texture unit ' + m_that.m_textureUnit + ' is not supported';
+ }
+ }
+
+ /**
+ * Create texture, update parameters, and bind data.
+ *
+ * @param {vgl.renderState} renderState
+ */
+ this.setup = function (renderState) {
+ // Activate the texture unit first
+ activateTextureUnit(renderState);
+
+ renderState.m_context.deleteTexture(this.m_textureHandle);
+ this.m_textureHandle = renderState.m_context.createTexture();
+ renderState.m_context.bindTexture(vgl.GL.TEXTURE_2D, this.m_textureHandle);
+ renderState.m_context.texParameteri(vgl.GL.TEXTURE_2D,
+ vgl.GL.TEXTURE_MIN_FILTER,
+ this.m_nearestPixel ? vgl.GL.NEAREST : vgl.GL.LINEAR);
+ renderState.m_context.texParameteri(vgl.GL.TEXTURE_2D,
+ vgl.GL.TEXTURE_MAG_FILTER,
+ this.m_nearestPixel ? vgl.GL.NEAREST : vgl.GL.LINEAR);
+ renderState.m_context.texParameteri(vgl.GL.TEXTURE_2D,
+ vgl.GL.TEXTURE_WRAP_S, vgl.GL.CLAMP_TO_EDGE);
+ renderState.m_context.texParameteri(vgl.GL.TEXTURE_2D,
+ vgl.GL.TEXTURE_WRAP_T, vgl.GL.CLAMP_TO_EDGE);
+
+ if (this.m_image !== null) {
+ renderState.m_context.pixelStorei(vgl.GL.UNPACK_ALIGNMENT, 1);
+ renderState.m_context.pixelStorei(vgl.GL.UNPACK_FLIP_Y_WEBGL, true);
+
+ this.updateDimensions();
+ this.computeInternalFormatUsingImage();
+
+ // console.log('m_internalFormat ' + this.m_internalFormat);
+ // console.log('m_pixelFormat ' + this.m_pixelFormat);
+ // console.log('m_pixelDataType ' + this.m_pixelDataType);
+
+ // FOR now support only 2D textures
+ renderState.m_context.texImage2D(vgl.GL.TEXTURE_2D, 0, this.m_internalFormat,
+ this.m_pixelFormat, this.m_pixelDataType, this.m_image);
+ } else {
+ renderState.m_context.texImage2D(vgl.GL.TEXTURE_2D, 0, this.m_internalFormat,
+ this.m_width, this.m_height, 0, this.m_pixelFormat, this.m_pixelDataType, null);
+ }
+
+ renderState.m_context.bindTexture(vgl.GL.TEXTURE_2D, null);
+ m_setupTimestamp.modified();
+ };
+
+ /**
+ * Create texture and if already created use it.
+ *
+ * @param {vgl.renderState} renderState
+ */
+ this.bind = function (renderState) {
+ // TODO Call setup via material setup
+ if (this.getMTime() > m_setupTimestamp.getMTime()) {
+ this.setup(renderState);
+ }
+
+ activateTextureUnit(renderState);
+ renderState.m_context.bindTexture(vgl.GL.TEXTURE_2D, this.m_textureHandle);
+ };
+
+ /**
+ * Turn off the use of this texture.
+ *
+ * @param {vgl.renderState} renderState
+ */
+ this.undoBind = function (renderState) {
+ renderState.m_context.bindTexture(vgl.GL.TEXTURE_2D, null);
+ };
+
+ /**
+ * Get image used by the texture.
+ *
+ * @returns {vgl.image}
+ */
+ this.image = function () {
+ return this.m_image;
+ };
+
+ /**
+ * Set image for the texture.
+ *
+ * @param {vgl.image} image
+ * @returns {boolean}
+ */
+ this.setImage = function (image) {
+ if (image !== null) {
+ this.m_image = image;
+ this.updateDimensions();
+ this.modified();
+ return true;
+ }
+
+ return false;
+ };
+
+ /**
+ * Get nearest pixel flag for the texture.
+ *
+ * @returns {boolean}
+ */
+ this.nearestPixel = function () {
+ return this.m_nearestPixel;
+ };
+
+ /**
+ * Set nearest pixel flag for the texture.
+ *
+ * @param {boolean} nearest pixel flag
+ * @returns {boolean}
+ */
+ this.setNearestPixel = function (nearest) {
+ nearest = nearest ? true : false;
+ if (nearest !== this.m_nearestPixel) {
+ this.m_nearestPixel = nearest;
+ this.modified();
+ return true;
+ }
+ return false;
+ };
+
+ /**
+ * Get texture unit of the texture.
+ *
+ * @returns {number}
+ */
+ this.textureUnit = function () {
+ return this.m_textureUnit;
+ };
+
+ /**
+ * Set texture unit of the texture. Default is 0.
+ *
+ * @param {number} unit
+ * @returns {boolean}
+ */
+ this.setTextureUnit = function (unit) {
+ if (this.m_textureUnit === unit) {
+ return false;
+ }
+
+ this.m_textureUnit = unit;
+ this.modified();
+ return true;
+ };
+
+ /**
+ * Compute internal format of the texture.
+ */
+ this.computeInternalFormatUsingImage = function () {
+ // Currently image does not define internal format
+ // and hence it's pixel format is the only way to query
+ // information on how color has been stored.
+ // switch (this.m_image.pixelFormat()) {
+ // case vgl.GL.RGB:
+ // this.m_internalFormat = vgl.GL.RGB;
+ // break;
+ // case vgl.GL.RGBA:
+ // this.m_internalFormat = vgl.GL.RGBA;
+ // break;
+ // case vgl.GL.Luminance:
+ // this.m_internalFormat = vgl.GL.Luminance;
+ // break;
+ // case vgl.GL.LuminanceAlpha:
+ // this.m_internalFormat = vgl.GL.LuminanceAlpha;
+ // break;
+ // // Do nothing when image pixel format is none or undefined.
+ // default:
+ // break;
+ // };
+
+ // TODO Fix this
+ this.m_internalFormat = vgl.GL.RGBA;
+ this.m_pixelFormat = vgl.GL.RGBA;
+ this.m_pixelDataType = vgl.GL.UNSIGNED_BYTE;
+ };
+
+ /**
+ * Update texture dimensions.
+ */
+ this.updateDimensions = function () {
+ if (this.m_image !== null) {
+ this.m_width = this.m_image.width;
+ this.m_height = this.m_image.height;
+ this.m_depth = 0; // Only 2D images are supported now
+ }
+ };
+
+ return this;
+};
+
+inherit(vgl.texture, vgl.materialAttribute);
+
+/**
+ * Create a new instance of class lookupTable.
+ *
+ * @class
+ * @alias vgl.lookupTable
+ * @returns {vgl.lookupTable}
+ */
+vgl.lookupTable = function () {
+ 'use strict';
+
+ if (!(this instanceof vgl.lookupTable)) {
+ return new vgl.lookupTable();
+ }
+ vgl.texture.call(this);
+
+ var m_setupTimestamp = timestamp();
+
+ this.m_colorTable = // paraview bwr colortable
+ [0.07514311, 0.468049805, 1, 1,
+ 0.247872569, 0.498782363, 1, 1,
+ 0.339526309, 0.528909511, 1, 1,
+ 0.409505078, 0.558608486, 1, 1,
+ 0.468487184, 0.588057293, 1, 1,
+ 0.520796675, 0.617435078, 1, 1,
+ 0.568724526, 0.646924167, 1, 1,
+ 0.613686735, 0.676713218, 1, 1,
+ 0.656658579, 0.707001303, 1, 1,
+ 0.698372844, 0.738002964, 1, 1,
+ 0.739424025, 0.769954435, 1, 1,
+ 0.780330104, 0.803121429, 1, 1,
+ 0.821573924, 0.837809045, 1, 1,
+ 0.863634967, 0.874374691, 1, 1,
+ 0.907017747, 0.913245283, 1, 1,
+ 0.936129275, 0.938743558, 0.983038586, 1,
+ 0.943467973, 0.943498599, 0.943398095, 1,
+ 0.990146732, 0.928791426, 0.917447482, 1,
+ 1, 0.88332677, 0.861943246, 1,
+ 1, 0.833985467, 0.803839606, 1,
+ 1, 0.788626485, 0.750707739, 1,
+ 1, 0.746206642, 0.701389973, 1,
+ 1, 0.70590052, 0.654994046, 1,
+ 1, 0.667019783, 0.610806959, 1,
+ 1, 0.6289553, 0.568237474, 1,
+ 1, 0.591130233, 0.526775617, 1,
+ 1, 0.552955184, 0.485962266, 1,
+ 1, 0.513776083, 0.445364274, 1,
+ 1, 0.472800903, 0.404551679, 1,
+ 1, 0.428977855, 0.363073592, 1,
+ 1, 0.380759558, 0.320428137, 1,
+ 0.961891484, 0.313155629, 0.265499262, 1,
+ 0.916482116, 0.236630659, 0.209939162, 1].map(
+ function (x) { return x * 255; });
+
+ /**
+ * Create lookup table, initialize parameters, and bind data to it.
+ *
+ * @param {vgl.renderState} renderState
+ */
+ this.setup = function (renderState) {
+ if (this.textureUnit() === 0) {
+ renderState.m_context.activeTexture(vgl.GL.TEXTURE0);
+ } else if (this.textureUnit() === 1) {
+ renderState.m_context.activeTexture(vgl.GL.TEXTURE1);
+ }
+
+ renderState.m_context.deleteTexture(this.m_textureHandle);
+ this.m_textureHandle = renderState.m_context.createTexture();
+ renderState.m_context.bindTexture(vgl.GL.TEXTURE_2D, this.m_textureHandle);
+ renderState.m_context.texParameteri(vgl.GL.TEXTURE_2D,
+ vgl.GL.TEXTURE_MIN_FILTER, vgl.GL.LINEAR);
+ renderState.m_context.texParameteri(vgl.GL.TEXTURE_2D,
+ vgl.GL.TEXTURE_MAG_FILTER, vgl.GL.LINEAR);
+ renderState.m_context.texParameteri(vgl.GL.TEXTURE_2D,
+ vgl.GL.TEXTURE_WRAP_S, vgl.GL.CLAMP_TO_EDGE);
+ renderState.m_context.texParameteri(vgl.GL.TEXTURE_2D,
+ vgl.GL.TEXTURE_WRAP_T, vgl.GL.CLAMP_TO_EDGE);
+ renderState.m_context.pixelStorei(vgl.GL.UNPACK_ALIGNMENT, 1);
+
+ this.m_width = this.m_colorTable.length / 4;
+ this.m_height = 1;
+ this.m_depth = 0;
+ renderState.m_context.texImage2D(vgl.GL.TEXTURE_2D,
+ 0, vgl.GL.RGBA, this.m_width, this.m_height, this.m_depth,
+ vgl.GL.RGBA, vgl.GL.UNSIGNED_BYTE, new Uint8Array(this.m_colorTable));
+
+ renderState.m_context.bindTexture(vgl.GL.TEXTURE_2D, null);
+ m_setupTimestamp.modified();
+ };
+
+ /**
+ * Get color table used by the lookup table.
+ *
+ * @returns {number[]}
+ */
+ this.colorTable = function () {
+ return this.m_colorTable;
+ };
+
+ /**
+ * Set color table used by the lookup table.
+ *
+ * @param {number[]} colors
+ * @returns {boolean}
+ */
+ this.setColorTable = function (colors) {
+ if (this.m_colorTable === colors) {
+ return false;
+ }
+
+ this.m_colorTable = colors;
+ this.modified();
+ return true;
+ };
+
+ return this;
+};
+
+inherit(vgl.lookupTable, vgl.texture);
diff --git a/src/vgl/uniform.js b/src/vgl/uniform.js
new file mode 100644
index 0000000000..f8e36aabfa
--- /dev/null
+++ b/src/vgl/uniform.js
@@ -0,0 +1,284 @@
+var vgl = require('./vgl');
+var inherit = require('../inherit');
+var mat4 = require('gl-mat4');
+
+/**
+ * Create a new instance of class uniform.
+ *
+ * @class
+ * @alias vgl.uniform
+ * @param {number} type The GL type, such as FLOAT or INT.
+ * @param {string} name The name of the uniform.
+ * @returns {vgl.uniform} OpenGL uniform encapsulation
+ */
+vgl.uniform = function (type, name) {
+ 'use strict';
+
+ if (!(this instanceof vgl.uniform)) {
+ return new vgl.uniform(type, name);
+ }
+
+ this.getTypeNumberOfComponents = function (type) {
+ switch (type) {
+ case vgl.GL.FLOAT:
+ case vgl.GL.INT:
+ case vgl.GL.BOOL:
+ return 1;
+
+ case vgl.GL.FLOAT_VEC2:
+ case vgl.GL.INT_VEC2:
+ case vgl.GL.BOOL_VEC2:
+ return 2;
+
+ case vgl.GL.FLOAT_VEC3:
+ case vgl.GL.INT_VEC3:
+ case vgl.GL.BOOL_VEC3:
+ return 3;
+
+ case vgl.GL.FLOAT_VEC4:
+ case vgl.GL.INT_VEC4:
+ case vgl.GL.BOOL_VEC4:
+ return 4;
+
+ case vgl.GL.FLOAT_MAT3:
+ return 9;
+
+ case vgl.GL.FLOAT_MAT4:
+ return 16;
+
+ default:
+ return 0;
+ }
+ };
+
+ var m_type = type,
+ m_name = name,
+ m_dataArray = [];
+
+ m_dataArray.length = this.getTypeNumberOfComponents(m_type);
+
+ /**
+ * Get name of the uniform.
+ *
+ * @returns {string}
+ */
+ this.name = function () {
+ return m_name;
+ };
+
+ /**
+ * Set value of the uniform.
+ *
+ * @param {Array|number} value
+ */
+ this.set = function (value) {
+ var i = 0, lendata = m_dataArray.length;
+ if (lendata !== 1) {
+ for (i = 0; i < lendata; i += 1) {
+ m_dataArray[i] = value[i];
+ }
+ } else {
+ m_dataArray[0] = value;
+ }
+ };
+
+ /**
+ * Call GL and pass updated values to the current shader.
+ *
+ * @param {vgl.renderState} renderState The current render state with the
+ * current context.
+ * @param {number} location The context location.
+ */
+ this.callGL = function (renderState, location) {
+ switch (m_type) {
+ case vgl.GL.BOOL:
+ case vgl.GL.INT:
+ renderState.m_context.uniform1iv(location, m_dataArray);
+ break;
+ case vgl.GL.FLOAT:
+ renderState.m_context.uniform1fv(location, m_dataArray);
+ break;
+ case vgl.GL.BOOL_VEC2:
+ case vgl.GL.INT_VEC2:
+ renderState.m_context.uniform2iv(location, m_dataArray);
+ break;
+ case vgl.GL.FLOAT_VEC2:
+ renderState.m_context.uniform2fv(location, m_dataArray);
+ break;
+ case vgl.GL.BOOL_VEC3:
+ case vgl.GL.INT_VEC3:
+ renderState.m_context.uniform3iv(location, m_dataArray);
+ break;
+ case vgl.GL.FLOAT_VEC3:
+ renderState.m_context.uniform3fv(location, m_dataArray);
+ break;
+ case vgl.GL.BOOL_VEC4:
+ case vgl.GL.INT_VEC4:
+ renderState.m_context.uniform4iv(location, m_dataArray);
+ break;
+ case vgl.GL.FLOAT_VEC4:
+ renderState.m_context.uniform4fv(location, m_dataArray);
+ break;
+ case vgl.GL.FLOAT_MAT3:
+ renderState.m_context.uniformMatrix3fv(location, vgl.GL.FALSE, m_dataArray);
+ break;
+ case vgl.GL.FLOAT_MAT4:
+ renderState.m_context.uniformMatrix4fv(location, vgl.GL.FALSE, m_dataArray);
+ break;
+ default:
+ break;
+ }
+ };
+
+ /**
+ * Virtual method to update the uniform.
+ *
+ * Should be implemented by the derived class.
+ *
+ * @param {vgl.renderState} renderState
+ * @param {vgl.shaderProgram} program
+ */
+ this.update = function (renderState, program) {
+ // Should be implemented by the derived class
+ };
+
+ return this;
+};
+
+/**
+ * Create new instance of class modelViewOriginUniform.
+ *
+ * @class
+ * @alias vgl.modelViewUniform
+ * @param {string} name
+ * @param {number[]} origin a triplet of floats.
+ * @returns {vgl.modelViewUniform}
+ */
+vgl.modelViewOriginUniform = function (name, origin) {
+ 'use strict';
+
+ if (!(this instanceof vgl.modelViewOriginUniform)) {
+ return new vgl.modelViewOriginUniform(name, origin);
+ }
+
+ if (!name) {
+ name = 'modelViewMatrix';
+ }
+ origin = origin || [0, 0, 0];
+
+ var m_origin = [origin[0], origin[1], origin[2] || 0];
+
+ vgl.uniform.call(this, vgl.GL.FLOAT_MAT4, name);
+
+ this.set(mat4.create());
+
+ /**
+ * Change the origin used by the uniform view matrix.
+ *
+ * @param {number[]} origin a triplet of floats.
+ */
+ this.setOrigin = function (origin) {
+ origin = origin || [0, 0, 0];
+ m_origin = [origin[0], origin[1], origin[2] || 0];
+ };
+
+ /**
+ * Update the uniform given a render state and shader program. This offsets
+ * the modelViewMatrix by the origin, and, if the model view should be
+ * aligned, aligns it appropriately. The alignment must be done after the
+ * origin offset to maintain precision.
+ *
+ * @param {vgl.renderState} renderState
+ * @param {vgl.shaderProgram} program
+ */
+ this.update = function (renderState, program) {
+ var view = renderState.m_modelViewMatrix;
+ if (renderState.m_modelViewAlignment) {
+ /* adjust alignment before origin. Otherwise, a changing origin can
+ * affect the rounding choice and result in a 1 pixe jitter. */
+ var align = renderState.m_modelViewAlignment;
+ /* Don't modify the original matrix. If we are in an environment where
+ * you can't slice an Float32Array, switch to a regular array */
+ view = view.slice ? view.slice() : Array.prototype.slice.call(view);
+ /* view[12] and view[13] are the x and y offsets. align.round is the
+ * units-per-pixel, and align.dx and .dy are either 0 or half the size of
+ * a unit-per-pixel. The alignment guarantees that the texels are
+ * aligned with screen pixels. */
+ view[12] = Math.round(view[12] / align.roundx) * align.roundx + align.dx;
+ view[13] = Math.round(view[13] / align.roundy) * align.roundy + align.dy;
+ }
+ view = mat4.translate(mat4.create(), view, m_origin);
+ this.set(view);
+ };
+
+ return this;
+};
+
+inherit(vgl.modelViewOriginUniform, vgl.uniform);
+
+/**
+ * Create a new instance of class projectionUniform.
+ *
+ * @class
+ * @alias vgl.projectionUniform
+ * @param {string} name
+ * @returns {vgl.projectionUniform}
+ */
+vgl.projectionUniform = function (name) {
+ 'use strict';
+
+ if (!(this instanceof vgl.projectionUniform)) {
+ return new vgl.projectionUniform(name);
+ }
+
+ if (!name) {
+ name = 'projectionMatrix';
+ }
+
+ vgl.uniform.call(this, vgl.GL.FLOAT_MAT4, name);
+
+ this.set(mat4.create());
+
+ /**
+ * Update the uniform given a render state and shader program.
+ *
+ * @param {vgl.renderState} renderState
+ * @param {vgl.shaderProgram} program
+ */
+ this.update = function (renderState, program) {
+ this.set(renderState.m_projectionMatrix);
+ };
+
+ return this;
+};
+
+inherit(vgl.projectionUniform, vgl.uniform);
+
+/**
+ * Create a new instance of class floatUniform.
+ *
+ * @class
+ * @alias vgl.floatUniform
+ * @param {string} name
+ * @param {number} value
+ * @returns {vgl.floatUniform}
+ */
+vgl.floatUniform = function (name, value) {
+ 'use strict';
+
+ if (!(this instanceof vgl.floatUniform)) {
+ return new vgl.floatUniform(name, value);
+ }
+
+ if (!name) {
+ name = 'floatUniform';
+ }
+
+ value = value === undefined ? 1.0 : value;
+
+ vgl.uniform.call(this, vgl.GL.FLOAT, name);
+
+ this.set(value);
+};
+
+inherit(vgl.floatUniform, vgl.uniform);
diff --git a/src/vgl/vertexAttribute.js b/src/vgl/vertexAttribute.js
new file mode 100644
index 0000000000..4da09976cb
--- /dev/null
+++ b/src/vgl/vertexAttribute.js
@@ -0,0 +1,83 @@
+var vgl = require('./vgl');
+
+vgl.vertexAttributeKeys = {
+ Position : 0,
+ Normal : 1,
+ TextureCoordinate : 2,
+ Color : 3,
+ Scalar: 4,
+ CountAttributeIndex : 5
+};
+
+vgl.vertexAttributeKeysIndexed = {
+ Zero : 0,
+ One : 1,
+ Two : 2,
+ Three : 3,
+ Four : 4,
+ Five : 5,
+ Six : 6,
+ Seven : 7,
+ Eight : 8,
+ Nine : 9
+};
+
+/**
+ * Create a new instance of vertexAttribute.
+ *
+ * @class
+ * @alias vgl.vertexAttribute
+ * @param {string} name Name of attribute.
+ * @returns {vgl.vertexAttribute}
+ */
+vgl.vertexAttribute = function (name) {
+ 'use strict';
+
+ if (!(this instanceof vgl.vertexAttribute)) {
+ return new vgl.vertexAttribute(name);
+ }
+
+ var m_name = name;
+
+ /**
+ * Get name of the vertex attribute.
+ *
+ * @returns {string}
+ */
+ this.name = function () {
+ return m_name;
+ };
+
+ /**
+ * Bind vertex data to the given render state.
+ *
+ * @param {vgl.renderState} renderState
+ * @param {vgl.vertexAttributeKeys} key
+ */
+ this.bindVertexData = function (renderState, key) {
+ var geometryData = renderState.m_mapper.geometryData(),
+ sourceData = geometryData.sourceData(key),
+ program = renderState.m_material.shaderProgram();
+
+ renderState.m_context.vertexAttribPointer(
+ program.attributeLocation(m_name),
+ sourceData.attributeNumberOfComponents(key),
+ sourceData.attributeDataType(key),
+ sourceData.normalized(key),
+ sourceData.attributeStride(key),
+ sourceData.attributeOffset(key));
+ renderState.m_context.enableVertexAttribArray(program.attributeLocation(m_name));
+ };
+
+ /**
+ * Undo bind vertex data for a given render state.
+ *
+ * @param {vgl.renderState} renderState
+ * @param {vgl.vertexAttributeKeys} key
+ */
+ this.undoBindVertexData = function (renderState, key) {
+ var program = renderState.m_material.shaderProgram();
+
+ renderState.m_context.disableVertexAttribArray(program.attributeLocation(m_name));
+ };
+};
diff --git a/src/vgl/vgl.js b/src/vgl/vgl.js
new file mode 100644
index 0000000000..f053ebf797
--- /dev/null
+++ b/src/vgl/vgl.js
@@ -0,0 +1 @@
+module.exports = {};
diff --git a/src/vgl/viewer.js b/src/vgl/viewer.js
new file mode 100644
index 0000000000..2d6fc52868
--- /dev/null
+++ b/src/vgl/viewer.js
@@ -0,0 +1,114 @@
+var $ = require('jquery');
+var vgl = require('./vgl');
+var inherit = require('../inherit');
+
+/**
+ * Create a new instance of class viewer.
+ *
+ * @class
+ * @alias vgl.viewer
+ * @extends vgl.object
+ * @param {HTMLElement} canvas Canvas element associated with the viewer.
+ * @param {object} options Options to send to the renderer.
+ * @returns {vgl.viewer}
+ */
+vgl.viewer = function (canvas, options) {
+ 'use strict';
+
+ if (!(this instanceof vgl.viewer)) {
+ return new vgl.viewer(canvas, options);
+ }
+
+ vgl.object.call(this);
+
+ var m_canvas = canvas,
+ m_renderer = vgl.renderer(options),
+ m_renderWindow = vgl.renderWindow(m_canvas);
+
+ /**
+ * Get canvas of the viewer.
+ *
+ * @returns {HTMLElement}
+ */
+ this.canvas = function () {
+ return m_canvas;
+ };
+
+ /**
+ * Return render window of the viewer.
+ *
+ * @returns {vgl.renderWindow}
+ */
+ this.renderWindow = function () {
+ return m_renderWindow;
+ };
+
+ /**
+ * Initialize the viewer.
+ *
+ * This is a must call or otherwise render context may not initialized
+ * properly.
+ */
+ this.init = function () {
+ if (m_renderWindow !== null) {
+ m_renderWindow._setup();
+ } else {
+ console.log('[ERROR] No render window attached');
+ }
+ };
+
+ /**
+ * Remove the viewer.
+ *
+ * @param {vgl.renderState} renderState Current render state.
+ */
+ this.exit = function (renderState) {
+ if (m_renderWindow !== null) {
+ m_renderWindow._cleanup(renderState);
+ } else {
+ console.log('[ERROR] No render window attached');
+ }
+ };
+
+ /**
+ * Render.
+ */
+ this.render = function () {
+ m_renderWindow.render();
+ };
+
+ /**
+ * Bind canvas mouse events to their default handlers.
+ */
+ this.bindEventHandlers = function () {
+ $(m_canvas).on('mousedown', this.handleMouseDown);
+ $(m_canvas).on('mouseup', this.handleMouseUp);
+ $(m_canvas).on('mousemove', this.handleMouseMove);
+ $(m_canvas).on('mousewheel', this.handleMouseWheel);
+ $(m_canvas).on('contextmenu', this.handleContextMenu);
+ };
+
+ /**
+ * Undo earlier bound handlers for canvas mouse events.
+ */
+ this.unbindEventHandlers = function () {
+ $(m_canvas).off('mousedown', this.handleMouseDown);
+ $(m_canvas).off('mouseup', this.handleMouseUp);
+ $(m_canvas).off('mousemove', this.handleMouseMove);
+ $(m_canvas).off('mousewheel', this.handleMouseWheel);
+ $(m_canvas).off('contextmenu', this.handleContextMenu);
+ };
+
+ /**
+ * Initialize.
+ */
+ this._init = function () {
+ this.bindEventHandlers();
+ m_renderWindow.addRenderer(m_renderer);
+ };
+
+ this._init();
+ return this;
+};
+
+inherit(vgl.viewer, vgl.object);
diff --git a/src/webgl/lineFeature.js b/src/webgl/lineFeature.js
index 6cb7b3cf6c..17cab248ef 100644
--- a/src/webgl/lineFeature.js
+++ b/src/webgl/lineFeature.js
@@ -62,7 +62,7 @@ var webgl_lineFeature = function (arg) {
arg = arg || {};
lineFeature.call(this, arg);
- var vgl = require('vgl');
+ var vgl = require('../vgl');
var transform = require('../transform');
var util = require('../util');
var object = require('./object');
diff --git a/src/webgl/lookupTable2D.js b/src/webgl/lookupTable2D.js
index bd0f3d6697..d5d8015f38 100644
--- a/src/webgl/lookupTable2D.js
+++ b/src/webgl/lookupTable2D.js
@@ -1,6 +1,6 @@
var inherit = require('../inherit');
var timestamp = require('../timestamp');
-var vgl = require('vgl');
+var vgl = require('../vgl');
/**
* Switch to a specific texture unit.
diff --git a/src/webgl/markerFeature.js b/src/webgl/markerFeature.js
index 2461895618..24248f52b9 100644
--- a/src/webgl/markerFeature.js
+++ b/src/webgl/markerFeature.js
@@ -21,7 +21,7 @@ var webgl_markerFeature = function (arg) {
arg = arg || {};
markerFeature.call(this, arg);
- var vgl = require('vgl');
+ var vgl = require('../vgl');
var transform = require('../transform');
var util = require('../util');
var object = require('./object');
diff --git a/src/webgl/meshColored.js b/src/webgl/meshColored.js
index 2c0ee2ffe5..fe1de2ae0d 100644
--- a/src/webgl/meshColored.js
+++ b/src/webgl/meshColored.js
@@ -11,7 +11,7 @@ var webgl_meshColored = function (arg) {
arg = arg || {};
- var vgl = require('vgl');
+ var vgl = require('../vgl');
var transform = require('../transform');
var util = require('../util');
var fragmentShader = require('./meshColored.frag');
diff --git a/src/webgl/pixelmapFeature.js b/src/webgl/pixelmapFeature.js
index aff1d3d541..a556d691df 100644
--- a/src/webgl/pixelmapFeature.js
+++ b/src/webgl/pixelmapFeature.js
@@ -24,7 +24,7 @@ var webgl_pixelmapFeature = function (arg) {
var object = require('./object');
object.call(this);
- const vgl = require('vgl');
+ const vgl = require('../vgl');
const fragmentShader = require('./pixelmapFeature.frag');
var m_quadFeature,
diff --git a/src/webgl/pointFeature.js b/src/webgl/pointFeature.js
index d2bc1afdf8..5870d41a75 100644
--- a/src/webgl/pointFeature.js
+++ b/src/webgl/pointFeature.js
@@ -21,7 +21,7 @@ var webgl_pointFeature = function (arg) {
arg = arg || {};
pointFeature.call(this, arg);
- var vgl = require('vgl');
+ var vgl = require('../vgl');
var transform = require('../transform');
var util = require('../util');
var object = require('./object');
diff --git a/src/webgl/polygonFeature.js b/src/webgl/polygonFeature.js
index 9f0489c68d..5b8a3db29d 100644
--- a/src/webgl/polygonFeature.js
+++ b/src/webgl/polygonFeature.js
@@ -19,7 +19,7 @@ var webgl_polygonFeature = function (arg) {
arg = arg || {};
polygonFeature.call(this, arg);
- var vgl = require('vgl');
+ var vgl = require('../vgl');
var earcut = require('earcut');
var transform = require('../transform');
var util = require('../util');
diff --git a/src/webgl/quadFeature.js b/src/webgl/quadFeature.js
index ce24d53c7f..81eaa3ab28 100644
--- a/src/webgl/quadFeature.js
+++ b/src/webgl/quadFeature.js
@@ -23,7 +23,7 @@ var webgl_quadFeature = function (arg) {
quadFeature.call(this, arg);
var $ = require('jquery');
- var vgl = require('vgl');
+ var vgl = require('../vgl');
var object = require('./object');
var fragmentShaderImage = require('./quadFeatureImage.frag');
var vertexShaderImage = require('./quadFeatureImage.vert');
diff --git a/src/webgl/webglRenderer.js b/src/webgl/webglRenderer.js
index 87398928e5..8eaadd4c0d 100644
--- a/src/webgl/webglRenderer.js
+++ b/src/webgl/webglRenderer.js
@@ -25,7 +25,7 @@ var webglRenderer = function (arg) {
renderer.call(this, arg);
var $ = require('jquery');
- var vgl = require('vgl');
+ var vgl = require('../vgl');
var mat4 = require('gl-mat4');
var util = require('../util');
var geo_event = require('../event');
diff --git a/tests/cases/lineFeature.js b/tests/cases/lineFeature.js
index 1393885145..eca4c6953a 100644
--- a/tests/cases/lineFeature.js
+++ b/tests/cases/lineFeature.js
@@ -10,7 +10,7 @@ var createMap = require('../test-utils').createMap;
var destroyMap = require('../test-utils').destroyMap;
var mockWebglRenderer = geo.util.mockWebglRenderer;
var restoreWebglRenderer = geo.util.restoreWebglRenderer;
-var vgl = require('vgl');
+var vgl = require('../test-utils').vgl;
var waitForIt = require('../test-utils').waitForIt;
var logCanvas2D = require('../test-utils').logCanvas2D;
diff --git a/tests/cases/markerFeature.js b/tests/cases/markerFeature.js
index ac219fa24a..d74aeea7fd 100644
--- a/tests/cases/markerFeature.js
+++ b/tests/cases/markerFeature.js
@@ -6,7 +6,7 @@ var createMap = require('../test-utils').createMap;
var destroyMap = require('../test-utils').destroyMap;
var mockWebglRenderer = geo.util.mockWebglRenderer;
var restoreWebglRenderer = geo.util.restoreWebglRenderer;
-var vgl = require('vgl');
+var vgl = require('../test-utils').vgl;
var waitForIt = require('../test-utils').waitForIt;
describe('geo.markerFeature', function () {
diff --git a/tests/cases/osmLayer.js b/tests/cases/osmLayer.js
index 9ce34225e5..3f4b16eaeb 100644
--- a/tests/cases/osmLayer.js
+++ b/tests/cases/osmLayer.js
@@ -15,7 +15,7 @@ describe('geo.core.osmLayer', function () {
var destroyMap = require('../test-utils').destroyMap;
var mockWebglRenderer = geo.util.mockWebglRenderer;
var restoreWebglRenderer = geo.util.restoreWebglRenderer;
- var vgl = require('vgl');
+ var vgl = require('../test-utils').vgl;
var closeToEqual = require('../test-utils').closeToEqual;
function create_map(opts) {
diff --git a/tests/cases/pixelmapFeature.js b/tests/cases/pixelmapFeature.js
index b79730bc08..553e7521a1 100644
--- a/tests/cases/pixelmapFeature.js
+++ b/tests/cases/pixelmapFeature.js
@@ -10,7 +10,7 @@ var waitForIt = require('../test-utils').waitForIt;
var logCanvas2D = require('../test-utils').logCanvas2D;
var mockWebglRenderer = geo.util.mockWebglRenderer;
var restoreWebglRenderer = geo.util.restoreWebglRenderer;
-var vgl = require('vgl');
+var vgl = require('../test-utils').vgl;
describe('geo.pixelmapFeature', function () {
'use strict';
diff --git a/tests/cases/pointFeature.js b/tests/cases/pointFeature.js
index 8a9d5798f0..3728bb9602 100644
--- a/tests/cases/pointFeature.js
+++ b/tests/cases/pointFeature.js
@@ -9,7 +9,7 @@ var createMap = require('../test-utils').createMap;
var destroyMap = require('../test-utils').destroyMap;
var mockWebglRenderer = geo.util.mockWebglRenderer;
var restoreWebglRenderer = geo.util.restoreWebglRenderer;
-var vgl = require('vgl');
+var vgl = require('../test-utils').vgl;
var waitForIt = require('../test-utils').waitForIt;
describe('geo.pointFeature', function () {
diff --git a/tests/cases/polygonFeature.js b/tests/cases/polygonFeature.js
index 9312d5b42b..ee139fc183 100644
--- a/tests/cases/polygonFeature.js
+++ b/tests/cases/polygonFeature.js
@@ -6,7 +6,7 @@ var createMap = require('../test-utils').createMap;
var destroyMap = require('../test-utils').destroyMap;
var mockWebglRenderer = geo.util.mockWebglRenderer;
var restoreWebglRenderer = geo.util.restoreWebglRenderer;
-var vgl = require('vgl');
+var vgl = require('../test-utils').vgl;
var waitForIt = require('../test-utils').waitForIt;
// var closeToArray = require('../test-utils').closeToArray;
diff --git a/tests/cases/quadFeature.js b/tests/cases/quadFeature.js
index a48fa2e979..02f17fb6ae 100644
--- a/tests/cases/quadFeature.js
+++ b/tests/cases/quadFeature.js
@@ -9,7 +9,7 @@ var createMap = require('../test-utils').createMap;
var destroyMap = require('../test-utils').destroyMap;
var mockWebglRenderer = geo.util.mockWebglRenderer;
var restoreWebglRenderer = geo.util.restoreWebglRenderer;
-var vgl = require('vgl');
+var vgl = require('../test-utils').vgl;
var waitForIt = require('../test-utils').waitForIt;
var closeToArray = require('../test-utils').closeToArray;
var logCanvas2D = require('../test-utils').logCanvas2D;
diff --git a/tests/cases/trackFeature.js b/tests/cases/trackFeature.js
index a4c336a478..20580ddc58 100644
--- a/tests/cases/trackFeature.js
+++ b/tests/cases/trackFeature.js
@@ -1,13 +1,10 @@
// Test geo.trackFeature, geo.svg.trackFeature, and geo.webgl.trackFeature
-// var $ = require('jquery');
var geo = require('../test-utils').geo;
var createMap = require('../test-utils').createMap;
var destroyMap = require('../test-utils').destroyMap;
var mockWebglRenderer = geo.util.mockWebglRenderer;
var restoreWebglRenderer = geo.util.restoreWebglRenderer;
-// var vgl = require('vgl');
-// var waitForIt = require('../test-utils').waitForIt;
describe('geo.trackFeature', function () {
'use strict';
diff --git a/tests/test-utils.js b/tests/test-utils.js
index 308796298c..41d28100d9 100644
--- a/tests/test-utils.js
+++ b/tests/test-utils.js
@@ -12,6 +12,7 @@ module.exports = {};
* Provides a uniform entry point into the geojs library.
*/
module.exports.geo = geo;
+module.exports.vgl = geo.vgl;
/**
* Create a pair of it functions. The first one waits for a function to return
* a truthy value, and the second one runs after the first has assured its
diff --git a/webpack.base.config.js b/webpack.base.config.js
index 78a90e1a86..5f8ea9fd4b 100644
--- a/webpack.base.config.js
+++ b/webpack.base.config.js
@@ -33,7 +33,6 @@ module.exports = {
alias: {
jquery: 'jquery/dist/jquery',
proj4: 'proj4/lib',
- vgl: 'vgl/vgl.js',
d3: 'd3/d3.js',
hammerjs: '@egjs/hammerjs/dist/hammer.js',
mousetrap: 'mousetrap/mousetrap.js'
@@ -105,29 +104,6 @@ module.exports = {
glsl: { chunkPath: 'src/webgl' }
}
}]
- }, {
- // vgl expects jQuery, gl-vec3/4, gl-mat4 to be in the global name space
- test: /vgl\.js$/,
- use: [{
- loader: 'expose-loader',
- options: {
- exposes: {
- globalName: 'vgl',
- override: true
- }
- }
- }, {
- loader: 'imports-loader',
- options: {
- type: 'commonjs',
- imports: [
- 'single gl-mat4 mat4',
- 'single gl-vec4 vec4',
- 'single gl-vec3 vec3',
- 'single jquery $'
- ]
- }
- }]
}]
}
};