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 $' - ] - } - }] }] } };