├── .gitattributes ├── .gitignore ├── .travis.yml ├── test ├── util │ ├── destroy-canvas.js │ ├── create-canvas.js │ └── create-scene.js ├── matchers │ ├── add-to-throw-developer-error-matcher.js │ └── make-throw-function.js ├── initialize-webgl-spec.js ├── spec-main.js ├── karma.conf.js ├── custom │ ├── custom-pattern-sensor-graphics-spec.js │ └── custom-pattern-sensor-visualizer-webgl-spec.js ├── rectangular │ ├── rectangular-sensor-graphics-spec.js │ └── rectangular-sensor-visualizer-webgl-spec.js └── conic │ ├── conic-sensor-graphics-spec.js │ └── conic-sensor-visualizer-webgl-spec.js ├── .npmignore ├── .editorconfig ├── lib ├── custom │ ├── custom-sensor-volume-vs.glsl │ ├── custom-sensor-volume-fs.glsl │ ├── custom-pattern-sensor-graphics.js │ ├── custom-pattern-sensor-visualizer.js │ └── custom-sensor-volume.js ├── util │ └── remove-primitive.js ├── main.js ├── copyright-header.js ├── cesium-sensor-volumes.js ├── sensor-volume.glsl ├── rectangular │ ├── rectangular-pyramid-sensor-volume.js │ ├── rectangular-sensor-graphics.js │ └── rectangular-sensor-visualizer.js ├── conic │ ├── conic-sensor-graphics.js │ └── conic-sensor-visualizer.js └── initialize.js ├── gulp ├── plugin-stream.js ├── generate-shims.js └── process-shaders.js ├── index.html ├── examples ├── czml.html └── api.html ├── LICENSE.md ├── package.json ├── README.md └── gulpfile.js /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | .project 4 | .settings 5 | node_modules/ 6 | dist/ 7 | .tmp/ 8 | coverage/ 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '6' 4 | sudo: false 5 | before_script: 6 | - export DISPLAY=:99.0 7 | - sh -e /etc/init.d/xvfb start 8 | script: 9 | - npm run ci 10 | -------------------------------------------------------------------------------- /test/util/destroy-canvas.js: -------------------------------------------------------------------------------- 1 | /* eslint-env browser */ 2 | define(function() { 3 | 'use strict'; 4 | 5 | return function destroyCanvas(canvas) { 6 | if (canvas) { 7 | document.body.removeChild(canvas); 8 | } 9 | }; 10 | }); 11 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # IDE files 2 | .idea/ 3 | *.iml 4 | .project 5 | .settings/ 6 | 7 | # dependencies 8 | /node_modules/ 9 | 10 | # output 11 | /.tmp/ 12 | /coverage/ 13 | 14 | # dev files 15 | /examples/ 16 | /gulp/ 17 | /test/ 18 | 19 | /.editorconfig 20 | /.gitignore 21 | /.gitattributes 22 | /.travis.yml 23 | /gulpfile.js 24 | /index.html 25 | 26 | /lib/main.js 27 | /lib/copyright-header.js 28 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = tab 6 | indent_size = 4 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | end_of_line = lf 10 | insert_final_newline = true 11 | 12 | [*.json] 13 | indent_style = space 14 | indent_size = 2 15 | 16 | [*.yml] 17 | indent_style = space 18 | indent_size = 2 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | -------------------------------------------------------------------------------- /lib/custom/custom-sensor-volume-vs.glsl: -------------------------------------------------------------------------------- 1 | attribute vec4 position; 2 | attribute vec3 normal; 3 | 4 | varying vec3 v_positionWC; 5 | varying vec3 v_positionEC; 6 | varying vec3 v_normalEC; 7 | 8 | void main() 9 | { 10 | gl_Position = czm_modelViewProjection * position; 11 | v_positionWC = (czm_model * position).xyz; 12 | v_positionEC = (czm_modelView * position).xyz; 13 | v_normalEC = czm_normal * normal; 14 | } -------------------------------------------------------------------------------- /test/matchers/add-to-throw-developer-error-matcher.js: -------------------------------------------------------------------------------- 1 | define(function(require) { 2 | 'use strict'; 3 | 4 | var makeThrowFunction = require('./make-throw-function'); 5 | var DeveloperError = require('Cesium/Core/DeveloperError'); 6 | 7 | return function() { 8 | /* global jasmine */ 9 | jasmine.addMatchers({ 10 | toThrowDeveloperError: makeThrowFunction(true, DeveloperError, 'DeveloperError') 11 | }); 12 | }; 13 | }); 14 | -------------------------------------------------------------------------------- /lib/util/remove-primitive.js: -------------------------------------------------------------------------------- 1 | define(function(require) { 2 | 'use strict'; 3 | 4 | var defined = require('Cesium/Core/defined'); 5 | 6 | return function removePrimitive(entity, hash, primitives) { 7 | var data = hash[entity.id]; 8 | if (defined(data)) { 9 | var primitive = data.primitive; 10 | primitives.remove(primitive); 11 | if (!primitive.isDestroyed()) { 12 | primitive.destroy(); 13 | } 14 | delete hash[entity.id]; 15 | } 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /gulp/plugin-stream.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gutil = require('gulp-util'); 4 | var through = require('through2'); 5 | 6 | module.exports = function(PLUGIN_NAME, transform, flush) { 7 | return through.obj(function(file, enc, cb) { 8 | if (file.isNull()) { 9 | cb(null, file); 10 | return; 11 | } 12 | 13 | if (file.isStream()) { 14 | cb(new gutil.PluginError(PLUGIN_NAME, 'Streaming not supported')); 15 | return; 16 | } 17 | 18 | transform(file, enc, cb); 19 | }, flush); 20 | }; 21 | -------------------------------------------------------------------------------- /lib/main.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-dynamic-require 2 | require([ 3 | 'cesium-sensor-volumes' 4 | ], function( 5 | CesiumSensorVolumes 6 | ) { 7 | 'use strict'; 8 | /* global window, self */ 9 | 10 | var scope; 11 | if (typeof window === 'undefined') { 12 | if (typeof self === 'undefined') { 13 | scope = {}; 14 | } else { 15 | scope = self; 16 | } 17 | } else { 18 | scope = window; 19 | } 20 | 21 | scope.CesiumSensorVolumes = CesiumSensorVolumes; 22 | }, undefined, true); 23 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Cesium Sensor Volumes 5 | 6 | 12 | 13 | 14 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /test/util/create-canvas.js: -------------------------------------------------------------------------------- 1 | /* eslint-env browser */ 2 | define(function(require) { 3 | 'use strict'; 4 | 5 | var defaultValue = require('Cesium/Core/defaultValue'); 6 | 7 | var canvasCount = 0; 8 | 9 | return function createCanvas(width, height) { 10 | width = defaultValue(width, 1); 11 | height = defaultValue(height, 1); 12 | 13 | var canvas = document.createElement('canvas'); 14 | canvas.id = 'canvas' + canvasCount++; 15 | canvas.setAttribute('width', width); 16 | canvas.setAttribute('clientWidth', width); 17 | canvas.setAttribute('height', height); 18 | canvas.setAttribute('clientHeight', height); 19 | canvas.innerHTML = 'To view this web page, upgrade your browser; it does not support the HTML5 canvas element.'; 20 | document.body.appendChild(canvas); 21 | 22 | return canvas; 23 | }; 24 | }); 25 | -------------------------------------------------------------------------------- /lib/copyright-header.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Cesium Sensor Volumes - https://github.com/jlouns/cesium-sensor-volumes 3 | * 4 | * Copyright 2016 Jonathan Lounsbury 5 | * Copyright 2011-2014 Analytical Graphics Inc. and Cesium Contributors 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | * 19 | * Portions licensed separately. 20 | * See https://github.com/jlouns/cesium-sensor-volumes/blob/master/LICENSE.md for full licensing details. 21 | * 22 | * Derived from Cesium Sensors - https://github.com/AnalyticalGraphicsInc/cesium-sensors 23 | */ 24 | -------------------------------------------------------------------------------- /test/initialize-webgl-spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-nested-callbacks */ 2 | define([ 3 | 'Cesium/DataSources/CzmlDataSource', 4 | 'Cesium/DataSources/DataSourceCollection', 5 | 'Cesium/DataSources/DataSourceDisplay', 6 | 'initialize', 7 | './util/create-scene' 8 | ], function( 9 | CzmlDataSource, 10 | DataSourceCollection, 11 | DataSourceDisplay, 12 | initialize, 13 | createScene 14 | ) { 15 | 'use strict'; 16 | 17 | /* global describe, it, beforeAll, afterAll */ 18 | 19 | describe('initialize', function() { 20 | var scene; 21 | 22 | beforeAll(function() { 23 | scene = createScene(); 24 | }); 25 | 26 | afterAll(function() { 27 | scene.destroyForSpecs(); 28 | }); 29 | 30 | it('should create a data source collection', function() { 31 | initialize(); 32 | 33 | var dataSourceCollection = new DataSourceCollection(); 34 | 35 | // eslint-disable-next-line no-new 36 | new DataSourceDisplay({ 37 | scene: scene, 38 | dataSourceCollection: dataSourceCollection 39 | }); 40 | 41 | dataSourceCollection.add(new CzmlDataSource()); 42 | }); 43 | }); 44 | }, 'WebGL'); 45 | -------------------------------------------------------------------------------- /gulp/generate-shims.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gutil = require('gulp-util'); 4 | var pluginStream = require('./plugin-stream'); 5 | 6 | var PLUGIN_NAME = 'cesium-sensors-generate-shims'; 7 | 8 | module.exports = function() { 9 | var shims = {}; 10 | 11 | return pluginStream(PLUGIN_NAME, function(file, enc, cb) { 12 | var contents = file.contents.toString(); 13 | // Search for Cesium modules and add shim 14 | // modules that pull from the Cesium global 15 | 16 | var cesiumRequireRegex = /'Cesium\/\w*\/(\w*)'/g; 17 | var match; 18 | while ((match = cesiumRequireRegex.exec(contents)) !== null) { 19 | if (match[0] in shims) { 20 | continue; 21 | } 22 | 23 | shims[match[0]] = 'define(' + match[0] + ', function() { return Cesium[\'' + match[1] + '\']; });'; 24 | } 25 | 26 | cb(); 27 | }, function(cb) { 28 | var shimContents = Object.keys(shims).map(function(key) { 29 | return shims[key]; 30 | }).join('\n'); 31 | 32 | shimContents = '\'use strict\';\n' + 33 | '/* global Cesium */\n' + 34 | shimContents; 35 | 36 | this.push(new gutil.File({ 37 | path: 'cesium-shims.js', 38 | contents: Buffer.from(shimContents) 39 | })); 40 | 41 | cb(); 42 | }); 43 | }; 44 | -------------------------------------------------------------------------------- /test/spec-main.js: -------------------------------------------------------------------------------- 1 | /* eslint-env browser */ 2 | 'use strict'; 3 | 4 | var allTestFiles = []; 5 | var TEST_REGEXP = /^\/base\/test\/.*(spec)\.js$/; 6 | var WEBGL_REGEXP = /webgl/; 7 | 8 | function pathToModule(path) { 9 | return path.replace(/^\/base\//, '').replace(/\.js$/, ''); 10 | } 11 | 12 | var excludeWebGl = false; 13 | if (window.__karma__.config.args) { 14 | excludeWebGl = window.__karma__.config.args[0]; 15 | } 16 | 17 | Object.keys(window.__karma__.files).forEach(function(file) { 18 | if (TEST_REGEXP.test(file)) { 19 | // exclude WebGL tests 20 | if (!excludeWebGl || !WEBGL_REGEXP.test(file)) { 21 | // Normalize paths to RequireJS module names. 22 | allTestFiles.push(pathToModule(file)); 23 | } 24 | } 25 | }); 26 | 27 | require.config({ 28 | // Karma serves files under /base, which is the basePath from your config file 29 | baseUrl: '/base/lib', 30 | 31 | paths: { 32 | Cesium: '../node_modules/cesium/Source', 33 | 34 | text: '../node_modules/requirejs-text/text', 35 | 36 | test: '../test' 37 | }, 38 | 39 | // dynamically load all test files 40 | deps: allTestFiles, 41 | 42 | // we have to kickoff jasmine, as it is asynchronous 43 | callback: window.__karma__.start 44 | }); 45 | -------------------------------------------------------------------------------- /test/matchers/make-throw-function.js: -------------------------------------------------------------------------------- 1 | define(function() { 2 | 'use strict'; 3 | 4 | return function makeThrowFunction(debug, Type, name) { 5 | if (debug) { 6 | return function() { 7 | return { 8 | compare: function(actual) { 9 | // based on the built-in Jasmine toThrow matcher 10 | var result = false; 11 | var exception; 12 | 13 | if (typeof actual !== 'function') { 14 | throw new TypeError('Actual is not a function'); 15 | } 16 | 17 | try { 18 | actual(); 19 | } catch (err) { 20 | exception = err; 21 | } 22 | 23 | if (exception) { 24 | result = exception instanceof Type; 25 | } 26 | 27 | var message; 28 | if (result) { 29 | message = ['Expected function not to throw ' + name + ' , but it threw', exception.message || exception].join(' '); 30 | } else { 31 | message = 'Expected function to throw ' + name + '.'; 32 | } 33 | 34 | return { 35 | pass: result, 36 | message: message 37 | }; 38 | } 39 | }; 40 | }; 41 | } 42 | 43 | return function() { 44 | return { 45 | compare: function() { 46 | return { pass: true }; 47 | }, 48 | negativeCompare: function() { 49 | return { pass: true }; 50 | } 51 | }; 52 | }; 53 | }; 54 | }); 55 | -------------------------------------------------------------------------------- /lib/cesium-sensor-volumes.js: -------------------------------------------------------------------------------- 1 | define(function(require) { 2 | 'use strict'; 3 | 4 | var initialize = require('./initialize'); 5 | var ConicSensorGraphics = require('./conic/conic-sensor-graphics'); 6 | var ConicSensorVisualizer = require('./conic/conic-sensor-visualizer'); 7 | var CustomPatternSensorGraphics = require('./custom/custom-pattern-sensor-graphics'); 8 | var CustomPatternSensorVisualizer = require('./custom/custom-pattern-sensor-visualizer'); 9 | var CustomSensorVolume = require('./custom/custom-sensor-volume'); 10 | var RectangularPyramidSensorVolume = require('./rectangular/rectangular-pyramid-sensor-volume'); 11 | var RectangularSensorGraphics = require('./rectangular/rectangular-sensor-graphics'); 12 | var RectangularSensorVisualizer = require('./rectangular/rectangular-sensor-visualizer'); 13 | 14 | initialize(); 15 | 16 | return { 17 | ConicSensorGraphics: ConicSensorGraphics, 18 | ConicSensorVisualizer: ConicSensorVisualizer, 19 | CustomPatternSensorGraphics: CustomPatternSensorGraphics, 20 | CustomPatternSensorVisualizer: CustomPatternSensorVisualizer, 21 | CustomSensorVolume: CustomSensorVolume, 22 | RectangularPyramidSensorVolume: RectangularPyramidSensorVolume, 23 | RectangularSensorGraphics: RectangularSensorGraphics, 24 | RectangularSensorVisualizer: RectangularSensorVisualizer 25 | }; 26 | }); 27 | -------------------------------------------------------------------------------- /lib/sensor-volume.glsl: -------------------------------------------------------------------------------- 1 | uniform vec4 u_intersectionColor; 2 | uniform float u_intersectionWidth; 3 | 4 | bool inSensorShadow(vec3 coneVertexWC, czm_ellipsoid ellipsoidEC, vec3 pointWC) 5 | { 6 | // Diagonal matrix from the unscaled ellipsoid space to the scaled space. 7 | vec3 D = ellipsoidEC.inverseRadii; 8 | 9 | // Sensor vertex in the scaled ellipsoid space 10 | vec3 q = D * coneVertexWC; 11 | float qMagnitudeSquared = dot(q, q); 12 | float test = qMagnitudeSquared - 1.0; 13 | 14 | // Sensor vertex to fragment vector in the ellipsoid's scaled space 15 | vec3 temp = D * pointWC - q; 16 | float d = dot(temp, q); 17 | 18 | // Behind silhouette plane and inside silhouette cone 19 | return (d < -test) && (d / length(temp) < -sqrt(test)); 20 | } 21 | 22 | /////////////////////////////////////////////////////////////////////////////// 23 | 24 | vec4 getIntersectionColor() 25 | { 26 | return u_intersectionColor; 27 | } 28 | 29 | float getIntersectionWidth() 30 | { 31 | return u_intersectionWidth; 32 | } 33 | 34 | vec2 sensor2dTextureCoordinates(float sensorRadius, vec3 pointMC) 35 | { 36 | // (s, t) both in the range [0, 1] 37 | float t = pointMC.z / sensorRadius; 38 | float s = 1.0 + (atan(pointMC.y, pointMC.x) / czm_twoPi); 39 | s = s - floor(s); 40 | 41 | return vec2(s, t); 42 | } 43 | -------------------------------------------------------------------------------- /examples/czml.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Cesium Sensor Volumes Example 11 | 12 | 13 | 14 | 15 | 16 | 40 | 41 | 42 |
43 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /test/karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config) { 2 | 'use strict'; 3 | config.set({ 4 | 5 | // base path that will be used to resolve all patterns (eg. files, exclude) 6 | basePath: '../', 7 | 8 | // frameworks to use 9 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 10 | frameworks: ['jasmine', 'requirejs'], 11 | 12 | // list of files / patterns to load in the browser 13 | files: [ 14 | 'test/spec-main.js', 15 | { pattern: 'node_modules/cesium/Source/**/*', included: false }, 16 | { pattern: 'node_modules/requirejs-text/*.js', included: false }, 17 | { pattern: 'lib/**/*.js', included: false }, 18 | { pattern: 'lib/**/*.glsl', included: false }, 19 | { pattern: 'test/**/*.js', included: false } 20 | ], 21 | 22 | // list of files to exclude 23 | exclude: [ 24 | 'lib/main.js' 25 | ], 26 | 27 | preprocessors: { 28 | 'lib/**/*.js': ['coverage'] 29 | }, 30 | 31 | // test results reporter to use 32 | // possible values: 'dots', 'progress' 33 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 34 | reporters: ['progress', 'coverage'], 35 | 36 | junitReporter: { 37 | outputFile: 'spec_out/unit.xml', 38 | suite: 'unit' 39 | }, 40 | 41 | // web server port 42 | port: 9876, 43 | 44 | // enable / disable colors in the output (reporters and logs) 45 | colors: true, 46 | 47 | // level of logging 48 | logLevel: config.LOG_INFO, 49 | 50 | // enable / disable watching file and executing tests whenever any file changes 51 | autoWatch: true, 52 | 53 | // start these browsers 54 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 55 | browsers: ['Chrome'], 56 | 57 | // Continuous Integration mode 58 | // if true, Karma captures browsers, runs the tests and exits 59 | singleRun: false 60 | }); 61 | }; 62 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | This plugin is based on the cesium-sensors plugin by Analytical Graphics Inc. and Cesium Contributors: https://github.com/AnalyticalGraphicsInc/cesium-sensors 2 | 3 | Copyright 2016 Jonathan Lounsbury 4 | Copyright 2011-2014 Analytical Graphics Inc. and Cesium Contributors 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 11 | 12 | Third-Party Code 13 | ================ 14 | 15 | cesium-sensor-volumes includes the following third-party code. 16 | 17 | ### almond 18 | 19 | https://github.com/jrburke/almond 20 | 21 | > Copyright (c) 2010-2011, The Dojo Foundation 22 | > 23 | > Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 24 | > 25 | > The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 26 | > 27 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 28 | 29 | -------------------------------------------------------------------------------- /gulp/process-shaders.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gutil = require('gulp-util'); 4 | var pluginStream = require('./plugin-stream'); 5 | 6 | var PLUGIN_NAME = 'cesium-sensors-process-shaders'; 7 | 8 | module.exports = function() { 9 | var shaderLicenseComments = []; 10 | 11 | return pluginStream(PLUGIN_NAME, function(file, enc, cb) { 12 | var contents = file.contents.toString(); 13 | contents = contents.replace(/\r\n/gm, '\n'); 14 | 15 | // eslint-disable-next-line no-useless-escape 16 | var licenseComments = contents.match(/\/\*\*(?:[^*\/]|\*(?!\/)|\n)*?@license(?:.|\n)*?\*\//gm); 17 | if (licenseComments !== null) { 18 | shaderLicenseComments = shaderLicenseComments.concat(licenseComments); 19 | } 20 | 21 | var newContents = []; 22 | // Remove comments. Code ported from 23 | // https://github.com/apache/ant/blob/master/src/main/org/apache/tools/ant/filters/StripJavaComments.java 24 | for (var i = 0; i < contents.length; ++i) { 25 | var c = contents.charAt(i); 26 | if (c === '/') { 27 | c = contents.charAt(++i); 28 | if (c === '/') { 29 | while (c !== '\r' && c !== '\n' && i < contents.length) { 30 | c = contents.charAt(++i); 31 | } 32 | } else if (c === '*') { 33 | while (i < contents.length) { 34 | c = contents.charAt(++i); 35 | if (c === '*') { 36 | c = contents.charAt(++i); 37 | while (c === '*') { 38 | c = contents.charAt(++i); 39 | } 40 | if (c === '/') { 41 | c = contents.charAt(++i); 42 | break; 43 | } 44 | } 45 | } 46 | } else { 47 | --i; 48 | c = '/'; 49 | } 50 | } 51 | newContents.push(c); 52 | } 53 | 54 | newContents = newContents.join(''); 55 | newContents = newContents.replace(/\s+$/gm, '').replace(/^\s+/gm, '').replace(/\n+/gm, '\n'); 56 | 57 | cb(null, new gutil.File({ 58 | path: file.relative, 59 | contents: Buffer.from(newContents) 60 | })); 61 | }, function(cb) { 62 | this.push(new gutil.File({ 63 | path: 'shader-copyright-header.js', 64 | contents: Buffer.from(shaderLicenseComments.join('\n')) 65 | })); 66 | cb(); 67 | }); 68 | }; 69 | -------------------------------------------------------------------------------- /test/util/create-scene.js: -------------------------------------------------------------------------------- 1 | /* eslint-env browser */ 2 | define(function(require) { 3 | 'use strict'; 4 | 5 | var Cartesian2 = require('Cesium/Core/Cartesian2'); 6 | var clone = require('Cesium/Core/clone'); 7 | var defaultValue = require('Cesium/Core/defaultValue'); 8 | var defined = require('Cesium/Core/defined'); 9 | var queryToObject = require('Cesium/Core/queryToObject'); 10 | var Scene = require('Cesium/Scene/Scene'); 11 | var createCanvas = require('./create-canvas'); 12 | var destroyCanvas = require('./destroy-canvas'); 13 | 14 | return function createScene(options) { 15 | options = defaultValue(options, {}); 16 | 17 | // save the canvas so we don't try to clone an HTMLCanvasElement 18 | var canvas = defined(options.canvas) ? options.canvas : createCanvas(); 19 | options.canvas = undefined; 20 | 21 | options = clone(options, true); 22 | 23 | options.canvas = canvas; 24 | options.contextOptions = defaultValue(options.contextOptions, {}); 25 | 26 | var contextOptions = options.contextOptions; 27 | contextOptions.webgl = defaultValue(contextOptions.webgl, {}); 28 | contextOptions.webgl.antialias = defaultValue(contextOptions.webgl.antialias, false); 29 | 30 | var scene = new Scene(options); 31 | 32 | var parameters = queryToObject(window.location.search.substring(1)); 33 | if (defined(parameters.webglValidation)) { 34 | var context = scene.context; 35 | context.validateShaderProgram = true; 36 | context.validateFramebuffer = true; 37 | context.logShaderCompilation = true; 38 | context.throwOnWebGLError = true; 39 | } 40 | 41 | // Add functions for test 42 | scene.destroyForSpecs = function() { 43 | var canvas = scene.canvas; 44 | scene.destroy(); 45 | destroyCanvas(canvas); 46 | }; 47 | 48 | scene.renderForSpecs = function(time) { 49 | scene.initializeFrame(); 50 | scene.render(time); 51 | return scene.context.readPixels(); 52 | }; 53 | 54 | scene.pickForSpecs = function() { 55 | return scene.pick(new Cartesian2(0, 0)); 56 | }; 57 | 58 | scene.rethrowRenderErrors = defaultValue(options.rethrowRenderErrors, true); 59 | 60 | return scene; 61 | }; 62 | }); 63 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cesium-sensor-volumes", 3 | "version": "1.32.0", 4 | "description": "A Cesium plugin for visualizing sensor volumes.", 5 | "homepage": "https://cesiumjs.org", 6 | "license": "Apache-2.0", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/jlouns/cesium-sensor-volumes.git" 10 | }, 11 | "keywords": [ 12 | "cesium" 13 | ], 14 | "dependencies": { 15 | "cesium": "1.32.1", 16 | "requirejs": "^2.3.3", 17 | "requirejs-text": "^2.0.15" 18 | }, 19 | "devDependencies": { 20 | "almond": "^0.3.3", 21 | "browser-sync": "^2.18.8", 22 | "del": "^2.2.2", 23 | "electron": "1.6.5", 24 | "event-stream": "^3.3.4", 25 | "globby": "^6.1.0", 26 | "gulp": "^3.9.0", 27 | "gulp-concat": "^2.6.1", 28 | "gulp-if": "^2.0.2", 29 | "gulp-order": "^1.1.1", 30 | "gulp-requirejs-optimize": "^1.2.0", 31 | "gulp-size": "^2.0.0", 32 | "gulp-util": "^3.0.8", 33 | "gulp-xo": "^0.15.0", 34 | "jasmine-core": "^2.5.2", 35 | "karma": "^1.6.0", 36 | "karma-chrome-launcher": "^2.0.0", 37 | "karma-coverage": "^1.1.1", 38 | "karma-electron-launcher": "^0.2.0", 39 | "karma-jasmine": "^1.1.0", 40 | "karma-requirejs": "^1.1.0", 41 | "lodash.assign": "^4.2.0", 42 | "merge-stream": "^1.0.1", 43 | "run-sequence": "^1.2.2", 44 | "through2": "^2.0.3" 45 | }, 46 | "scripts": { 47 | "build": "gulp build", 48 | "start": "gulp serve", 49 | "test": "gulp test", 50 | "ci": "gulp ci" 51 | }, 52 | "xo": { 53 | "globals": [ 54 | "define" 55 | ], 56 | "esnext": false, 57 | "rules": { 58 | "capitalized-comments": "off", 59 | "func-names": "off", 60 | "import/no-amd": "off", 61 | "import/no-extraneous-dependencies": "off", 62 | "import/no-unresolved": "off", 63 | "import/no-webpack-loader-syntax": "off", 64 | "object-curly-spacing": [ 65 | "error", 66 | "always", 67 | { 68 | "objectsInObjects": false, 69 | "arraysInObjects": false 70 | } 71 | ], 72 | "space-before-function-paren": [ 73 | "error", 74 | "never" 75 | ] 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [cesium](https://cesiumjs.org)-sensor-volumes 2 | [![Build Status](https://travis-ci.org/jlouns/cesium-sensor-volumes.svg?branch=master)](https://travis-ci.org/jlouns/cesium-sensor-volumes)  3 | [![npm version](https://badge.fury.io/js/cesium-sensor-volumes.svg)](https://badge.fury.io/js/cesium-sensor-volumes) 4 | [![Dependency Status](https://david-dm.org/jlouns/cesium-sensor-volumes.svg)](https://david-dm.org/jlouns/cesium-sensor-volumes) 5 | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0.html) 6 | 7 | A Cesium plugin for visualizing sensor volumes. Based on [cesium-sensors](https://github.com/AnalyticalGraphicsInc/cesium-sensors) and evolved to support more recent Cesium versions. 8 | 9 | ## Install 10 | 11 | ```sh 12 | $ npm install --save cesium-sensor-volumes 13 | ``` 14 | 15 | ## Usage 16 | 17 | Prebuilt minified and unminified versions of the plugin are in the [dist](dist/) directory. Include the `cesium-sensor-volumes.js` file using a `script` tag after the `Cesium.js` `script` tag. 18 | 19 | The plugin automatically adds support for the CZML properties `agi_conicSensor`, `agi_customPatternSensor`, and `agi_rectangularSensor`. The corresponding `Entity` properties are `conicSensor`, `customPatternSensor`, and `rectangularSensor`. 20 | 21 | In order to load data directly into `Entity` objects that you create directly, you must call `entity.addProperty` to create each of the sensor properties you wish to use. The CZML processing does this automatically. 22 | 23 | ```html 24 | 25 | 26 | 38 | ``` 39 | 40 | ### Examples 41 | 42 | Simple examples are included in the [examples](examples/) folder. To run locally, run `npm start` and navigate to [http://localhost:3000](http://localhost:3000) and select the example application to run. 43 | 44 | ## Build 45 | 46 | To build, run `npm install`, then run `npm run build`. 47 | 48 | ## License 49 | 50 | Apache 2.0. Free for commercial and non-commercial use. See [LICENSE.md](LICENSE.md). 51 | -------------------------------------------------------------------------------- /lib/custom/custom-sensor-volume-fs.glsl: -------------------------------------------------------------------------------- 1 | #ifdef GL_OES_standard_derivatives 2 | #extension GL_OES_standard_derivatives : enable 3 | #endif 4 | 5 | uniform bool u_showIntersection; 6 | uniform bool u_showThroughEllipsoid; 7 | 8 | uniform float u_sensorRadius; 9 | uniform float u_normalDirection; 10 | 11 | varying vec3 v_positionWC; 12 | varying vec3 v_positionEC; 13 | varying vec3 v_normalEC; 14 | 15 | vec4 getColor(float sensorRadius, vec3 pointEC) 16 | { 17 | czm_materialInput materialInput; 18 | 19 | vec3 pointMC = (czm_inverseModelView * vec4(pointEC, 1.0)).xyz; 20 | materialInput.st = sensor2dTextureCoordinates(sensorRadius, pointMC); 21 | materialInput.str = pointMC / sensorRadius; 22 | 23 | vec3 positionToEyeEC = -v_positionEC; 24 | materialInput.positionToEyeEC = positionToEyeEC; 25 | 26 | vec3 normalEC = normalize(v_normalEC); 27 | materialInput.normalEC = u_normalDirection * normalEC; 28 | 29 | czm_material material = czm_getMaterial(materialInput); 30 | return mix(czm_phong(normalize(positionToEyeEC), material), vec4(material.diffuse, material.alpha), 0.4); 31 | } 32 | 33 | bool isOnBoundary(float value, float epsilon) 34 | { 35 | float width = getIntersectionWidth(); 36 | float tolerance = width * epsilon; 37 | 38 | #ifdef GL_OES_standard_derivatives 39 | float delta = max(abs(dFdx(value)), abs(dFdy(value))); 40 | float pixels = width * delta; 41 | float temp = abs(value); 42 | // There are a couple things going on here. 43 | // First we test the value at the current fragment to see if it is within the tolerance. 44 | // We also want to check if the value of an adjacent pixel is within the tolerance, 45 | // but we don't want to admit points that are obviously not on the surface. 46 | // For example, if we are looking for "value" to be close to 0, but value is 1 and the adjacent value is 2, 47 | // then the delta would be 1 and "temp - delta" would be "1 - 1" which is zero even though neither of 48 | // the points is close to zero. 49 | return temp < tolerance && temp < pixels || (delta < 10.0 * tolerance && temp - delta < tolerance && temp < pixels); 50 | #else 51 | return abs(value) < tolerance; 52 | #endif 53 | } 54 | 55 | vec4 shade(bool isOnBoundary) 56 | { 57 | if (u_showIntersection && isOnBoundary) 58 | { 59 | return getIntersectionColor(); 60 | } 61 | return getColor(u_sensorRadius, v_positionEC); 62 | } 63 | 64 | float ellipsoidSurfaceFunction(czm_ellipsoid ellipsoid, vec3 point) 65 | { 66 | vec3 scaled = ellipsoid.inverseRadii * point; 67 | return dot(scaled, scaled) - 1.0; 68 | } 69 | 70 | void main() 71 | { 72 | vec3 sensorVertexWC = czm_model[3].xyz; // (0.0, 0.0, 0.0) in model coordinates 73 | vec3 sensorVertexEC = czm_modelView[3].xyz; // (0.0, 0.0, 0.0) in model coordinates 74 | 75 | czm_ellipsoid ellipsoid = czm_getWgs84EllipsoidEC(); 76 | float ellipsoidValue = ellipsoidSurfaceFunction(ellipsoid, v_positionWC); 77 | 78 | // Occluded by the ellipsoid? 79 | if (!u_showThroughEllipsoid) 80 | { 81 | // Discard if in the ellipsoid 82 | // PERFORMANCE_IDEA: A coarse check for ellipsoid intersection could be done on the CPU first. 83 | if (ellipsoidValue < 0.0) 84 | { 85 | discard; 86 | } 87 | 88 | // Discard if in the sensor's shadow 89 | if (inSensorShadow(sensorVertexWC, ellipsoid, v_positionWC)) 90 | { 91 | discard; 92 | } 93 | } 94 | 95 | // Discard if not in the sensor's sphere 96 | // PERFORMANCE_IDEA: We can omit this check if the radius is Number.POSITIVE_INFINITY. 97 | if (distance(v_positionEC, sensorVertexEC) > u_sensorRadius) 98 | { 99 | discard; 100 | } 101 | 102 | // Notes: Each surface functions should have an associated tolerance based on the floating point error. 103 | bool isOnEllipsoid = isOnBoundary(ellipsoidValue, czm_epsilon3); 104 | gl_FragColor = shade(isOnEllipsoid); 105 | } 106 | -------------------------------------------------------------------------------- /examples/api.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Cesium Sensor Volumes Example 11 | 12 | 13 | 14 | 15 | 16 | 46 | 47 | 48 |
49 |
50 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /test/custom/custom-pattern-sensor-graphics-spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-nested-callbacks */ 2 | define([ 3 | 'custom/custom-pattern-sensor-graphics', 4 | 'Cesium/Core/Color', 5 | 'Cesium/DataSources/ColorMaterialProperty', 6 | 'Cesium/DataSources/ConstantProperty', 7 | '../matchers/add-to-throw-developer-error-matcher' 8 | ], function( 9 | CustomPatternSensorGraphics, 10 | Color, 11 | ColorMaterialProperty, 12 | ConstantProperty, 13 | addToThrowDeveloperErrorMatcher 14 | ) { 15 | 'use strict'; 16 | 17 | /* global describe, it, beforeEach, expect */ 18 | 19 | describe('custom pattern sensor graphics', function() { 20 | describe('merge', function() { 21 | beforeEach(addToThrowDeveloperErrorMatcher); 22 | 23 | it('should assign unassigned properties', function() { 24 | var source = new CustomPatternSensorGraphics(); 25 | source.lateralSurfaceMaterial = new ColorMaterialProperty(); 26 | source.directions = new ConstantProperty([]); 27 | source.intersectionColor = new ConstantProperty(Color.WHITE); 28 | source.radius = new ConstantProperty(1); 29 | source.show = new ConstantProperty(true); 30 | source.showIntersection = new ConstantProperty(true); 31 | source.intersectionWidth = new ConstantProperty(1); 32 | 33 | var target = new CustomPatternSensorGraphics(); 34 | target.merge(source); 35 | 36 | expect(target.lateralSurfaceMaterial).toBe(source.lateralSurfaceMaterial); 37 | expect(target.directions).toBe(source.directions); 38 | expect(target.intersectionColor).toBe(source.intersectionColor); 39 | expect(target.radius).toBe(source.radius); 40 | expect(target.show).toBe(source.show); 41 | expect(target.showIntersection).toBe(source.showIntersection); 42 | expect(target.intersectionWidth).toBe(source.intersectionWidth); 43 | }); 44 | 45 | it('should not assign assigned properties', function() { 46 | var source = new CustomPatternSensorGraphics(); 47 | source.lateralSurfaceMaterial = new ColorMaterialProperty(); 48 | source.directions = new ConstantProperty([]); 49 | source.intersectionColor = new ConstantProperty(Color.WHITE); 50 | source.radius = new ConstantProperty(1); 51 | source.show = new ConstantProperty(true); 52 | source.showIntersection = new ConstantProperty(true); 53 | source.intersectionWidth = new ConstantProperty(1); 54 | 55 | var lateralSurfaceMaterial = new ColorMaterialProperty(); 56 | var directions = new ConstantProperty([]); 57 | var intersectionColor = new ConstantProperty(Color.WHITE); 58 | var radius = new ConstantProperty(1); 59 | var show = new ConstantProperty(true); 60 | var showIntersection = new ConstantProperty(true); 61 | var intersectionWidth = new ConstantProperty(1); 62 | 63 | var target = new CustomPatternSensorGraphics(); 64 | target.lateralSurfaceMaterial = lateralSurfaceMaterial; 65 | target.directions = directions; 66 | target.intersectionColor = intersectionColor; 67 | target.radius = radius; 68 | target.show = show; 69 | target.showIntersection = showIntersection; 70 | target.intersectionWidth = intersectionWidth; 71 | 72 | target.merge(source); 73 | 74 | expect(target.lateralSurfaceMaterial).toBe(lateralSurfaceMaterial); 75 | expect(target.directions).toBe(directions); 76 | expect(target.intersectionColor).toBe(intersectionColor); 77 | expect(target.radius).toBe(radius); 78 | expect(target.show).toBe(show); 79 | expect(target.showIntersection).toBe(showIntersection); 80 | expect(target.intersectionWidth).toBe(intersectionWidth); 81 | }); 82 | 83 | it('should throw if source undefined', function() { 84 | var target = new CustomPatternSensorGraphics(); 85 | expect(function() { 86 | target.merge(undefined); 87 | }).toThrowDeveloperError(); 88 | }); 89 | }); 90 | 91 | it('should clone', function() { 92 | var source = new CustomPatternSensorGraphics(); 93 | source.lateralSurfaceMaterial = new ColorMaterialProperty(); 94 | source.directions = new ConstantProperty([]); 95 | source.intersectionColor = new ConstantProperty(Color.WHITE); 96 | source.radius = new ConstantProperty(1); 97 | source.show = new ConstantProperty(true); 98 | source.showIntersection = new ConstantProperty(true); 99 | source.intersectionWidth = new ConstantProperty(1); 100 | 101 | var result = source.clone(); 102 | expect(result.lateralSurfaceMaterial).toBe(source.lateralSurfaceMaterial); 103 | expect(result.directions).toBe(source.directions); 104 | expect(result.intersectionColor).toBe(source.intersectionColor); 105 | expect(result.radius).toBe(source.radius); 106 | expect(result.show).toBe(source.show); 107 | expect(result.showIntersection).toBe(source.showIntersection); 108 | expect(result.intersectionWidth).toBe(source.intersectionWidth); 109 | }); 110 | }); 111 | }); 112 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | 6 | var es = require('event-stream'); 7 | var globby = require('globby'); 8 | var gulp = require('gulp'); 9 | var assign = require('lodash.assign'); 10 | 11 | // load plugins 12 | var browserSync = require('browser-sync').create(); 13 | var concat = require('gulp-concat'); 14 | var del = require('del'); 15 | var gulpif = require('gulp-if'); 16 | var order = require('gulp-order'); 17 | var requirejsOptimize = require('gulp-requirejs-optimize'); 18 | var runSequence = require('run-sequence'); 19 | var size = require('gulp-size'); 20 | var xo = require('gulp-xo'); 21 | 22 | var reload = browserSync.reload; 23 | 24 | var generateShims = require('./gulp/generate-shims'); 25 | var processShaders = require('./gulp/process-shaders'); 26 | 27 | var runLint = function(src) { 28 | return gulp.src(src) 29 | .pipe(xo()); 30 | }; 31 | 32 | gulp.task('lint', function() { 33 | return runLint(['lib/**/*.js', 'gulp/**/*.js', 'gulpfile.js']); 34 | }); 35 | 36 | gulp.task('shaders', function() { 37 | return gulp.src('lib/**/*.glsl') 38 | .pipe(processShaders()) 39 | .pipe(gulp.dest('.tmp/shaders')); 40 | }); 41 | 42 | gulp.task('create-main-js', function() { 43 | return gulp.src(['lib/**/*.js']) 44 | .pipe(gulpif('!main.js', generateShims())) 45 | .pipe(order([ 46 | '!main.js', 47 | 'main.js' 48 | ])) 49 | .pipe(concat('main.js')) 50 | .pipe(gulp.dest('.tmp')); 51 | }); 52 | 53 | function getCopyrightHeaders() { 54 | var copyrightHeader = fs.readFileSync('lib/copyright-header.js').toString(); 55 | var shaderCopyrightHeader = fs.readFileSync('.tmp/shaders/shader-copyright-header.js').toString(); 56 | 57 | return copyrightHeader + '\n' + shaderCopyrightHeader; 58 | } 59 | 60 | function optimize(options) { 61 | var source = path.join(options.baseUrl, options.include) + '.js'; 62 | return gulp.src(source) 63 | .pipe(requirejsOptimize(options)); 64 | } 65 | 66 | gulp.task('scripts', ['create-main-js', 'shaders'], function() { 67 | var copyright = getCopyrightHeaders(); 68 | 69 | var requirejsOptions = { 70 | name: '../node_modules/almond/almond', 71 | 72 | wrap: { 73 | start: copyright + '(function() {', 74 | end: '})();' 75 | }, 76 | 77 | useStrict: true, 78 | 79 | inlineText: true, 80 | stubModules: ['text'], 81 | 82 | skipModuleInsertion: true, 83 | 84 | baseUrl: 'lib', 85 | 86 | include: '../.tmp/main', 87 | paths: { 88 | text: '../node_modules/requirejs-text/text' 89 | } 90 | }; 91 | 92 | var unminified = optimize(assign({}, requirejsOptions, { 93 | out: 'cesium-sensor-volumes.js', 94 | optimize: 'none' 95 | })); 96 | 97 | var minifiedOptions = assign({}, requirejsOptions, { 98 | out: 'cesium-sensor-volumes.min.js', 99 | optimize: 'uglify2' 100 | }); 101 | 102 | // Use minified versions of shaders 103 | globby.sync(['lib/**/*.glsl']).forEach(function(shader) { 104 | shader = path.relative('lib', shader).replace(/\\/g, '/').replace(/\.glsl$/, ''); 105 | minifiedOptions.paths[shader] = path.join('../.tmp/shaders', shader); 106 | }); 107 | 108 | var minified = optimize(minifiedOptions); 109 | 110 | return es.merge(unminified, minified) 111 | .pipe(gulp.dest('dist')); 112 | }); 113 | 114 | gulp.task('clean', del.bind(null, ['coverage', '.tmp', 'dist'])); 115 | 116 | gulp.task('test-lint', function() { 117 | return runLint(['test/**/*.js']); 118 | }); 119 | 120 | function test(done, options) { 121 | var Server = require('karma').Server; 122 | 123 | var server = new Server(assign({ 124 | configFile: path.join(__dirname, '/test/karma.conf.js'), 125 | singleRun: true 126 | }, options), done); 127 | 128 | server.start(); 129 | } 130 | 131 | gulp.task('test', ['test-lint'], function(done) { 132 | test(done); 133 | }); 134 | 135 | gulp.task('test-ci', ['test-lint'], function(done) { 136 | test(done, { 137 | browsers: ['Electron'], 138 | client: { 139 | args: [true] 140 | } 141 | }); 142 | }); 143 | 144 | gulp.task('serve', function(done) { 145 | runSequence('build', 'run', 'watch', done); 146 | }); 147 | 148 | gulp.task('run', function(done) { 149 | browserSync.init({ 150 | server: '.' 151 | }, done); 152 | }); 153 | 154 | gulp.task('watch', function() { 155 | gulp.watch(['examples/**/*.html', 'examples/**/*.czml'], reload); 156 | gulp.watch(['lib/**/*.glsl'], ['build-reload']); 157 | gulp.watch(['lib/**/*.js'], ['build-reload']); 158 | }); 159 | 160 | gulp.task('build-reload', ['build'], reload); 161 | 162 | gulp.task('build', ['lint', 'scripts'], function() { 163 | return gulp.src('dist/**/*') 164 | .pipe(size({ title: 'build', gzip: true })); 165 | }); 166 | 167 | gulp.task('ci', function(done) { 168 | runSequence('lint', 'test-ci', 'build', done); 169 | }); 170 | 171 | gulp.task('default', function(done) { 172 | runSequence('clean', 'build', done); 173 | }); 174 | -------------------------------------------------------------------------------- /test/rectangular/rectangular-sensor-graphics-spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-nested-callbacks */ 2 | define([ 3 | 'rectangular/rectangular-sensor-graphics', 4 | 'Cesium/Core/Color', 5 | 'Cesium/DataSources/ColorMaterialProperty', 6 | 'Cesium/DataSources/ConstantProperty', 7 | '../matchers/add-to-throw-developer-error-matcher' 8 | ], function( 9 | RectangularSensorGraphics, 10 | Color, 11 | ColorMaterialProperty, 12 | ConstantProperty, 13 | addToThrowDeveloperErrorMatcher 14 | ) { 15 | 'use strict'; 16 | 17 | /* global describe, it, beforeEach, expect */ 18 | 19 | describe('rectangular sensor graphics', function() { 20 | describe('merge', function() { 21 | beforeEach(addToThrowDeveloperErrorMatcher); 22 | 23 | it('should assign unassigned properties', function() { 24 | var source = new RectangularSensorGraphics(); 25 | source.lateralSurfaceMaterial = new ColorMaterialProperty(); 26 | source.xHalfAngle = new ConstantProperty(); 27 | source.yHalfAngle = new ConstantProperty(); 28 | source.intersectionColor = new ConstantProperty(); 29 | source.radius = new ConstantProperty(); 30 | source.show = new ConstantProperty(); 31 | source.showIntersection = new ConstantProperty(); 32 | source.intersectionWidth = new ConstantProperty(); 33 | 34 | var target = new RectangularSensorGraphics(); 35 | target.merge(source); 36 | 37 | expect(target.lateralSurfaceMaterial).toBe(source.lateralSurfaceMaterial); 38 | expect(target.xHalfAngle).toBe(source.xHalfAngle); 39 | expect(target.yHalfAngle).toBe(source.yHalfAngle); 40 | expect(target.intersectionColor).toBe(source.intersectionColor); 41 | expect(target.radius).toBe(source.radius); 42 | expect(target.show).toBe(source.show); 43 | expect(target.showIntersection).toBe(source.showIntersection); 44 | expect(target.intersectionWidth).toBe(source.intersectionWidth); 45 | }); 46 | 47 | it('should not assign assigned properties', function() { 48 | var source = new RectangularSensorGraphics(); 49 | source.lateralSurfaceMaterial = new ColorMaterialProperty(); 50 | source.xHalfAngle = new ConstantProperty(); 51 | source.yHalfAngle = new ConstantProperty(); 52 | source.intersectionColor = new ConstantProperty(); 53 | source.radius = new ConstantProperty(); 54 | source.show = new ConstantProperty(); 55 | source.showIntersection = new ConstantProperty(); 56 | source.intersectionWidth = new ConstantProperty(); 57 | 58 | var lateralSurfaceMaterial = new ColorMaterialProperty(); 59 | var xHalfAngle = new ConstantProperty(); 60 | var yHalfAngle = new ConstantProperty(); 61 | var intersectionColor = new ConstantProperty(); 62 | var radius = new ConstantProperty(); 63 | var show = new ConstantProperty(); 64 | var showIntersection = new ConstantProperty(); 65 | var intersectionWidth = new ConstantProperty(); 66 | 67 | var target = new RectangularSensorGraphics(); 68 | target.lateralSurfaceMaterial = lateralSurfaceMaterial; 69 | target.xHalfAngle = xHalfAngle; 70 | target.yHalfAngle = yHalfAngle; 71 | target.intersectionColor = intersectionColor; 72 | target.radius = radius; 73 | target.show = show; 74 | target.showIntersection = showIntersection; 75 | target.intersectionWidth = intersectionWidth; 76 | 77 | target.merge(source); 78 | 79 | expect(target.lateralSurfaceMaterial).toBe(lateralSurfaceMaterial); 80 | expect(target.xHalfAngle).toBe(xHalfAngle); 81 | expect(target.yHalfAngle).toBe(yHalfAngle); 82 | expect(target.intersectionColor).toBe(intersectionColor); 83 | expect(target.radius).toBe(radius); 84 | expect(target.show).toBe(show); 85 | expect(target.showIntersection).toBe(showIntersection); 86 | expect(target.intersectionWidth).toBe(intersectionWidth); 87 | }); 88 | 89 | it('should throws if source undefined', function() { 90 | var target = new RectangularSensorGraphics(); 91 | expect(function() { 92 | target.merge(undefined); 93 | }).toThrowDeveloperError(); 94 | }); 95 | }); 96 | 97 | it('should clone', function() { 98 | var source = new RectangularSensorGraphics(); 99 | source.lateralSurfaceMaterial = new ColorMaterialProperty(); 100 | source.xHalfAngle = new ConstantProperty(); 101 | source.yHalfAngle = new ConstantProperty(); 102 | source.intersectionColor = new ConstantProperty(); 103 | source.radius = new ConstantProperty(); 104 | source.show = new ConstantProperty(); 105 | source.showIntersection = new ConstantProperty(); 106 | source.intersectionWidth = new ConstantProperty(); 107 | 108 | var result = source.clone(); 109 | expect(result.lateralSurfaceMaterial).toBe(source.lateralSurfaceMaterial); 110 | expect(result.xHalfAngle).toBe(source.xHalfAngle); 111 | expect(result.yHalfAngle).toBe(source.yHalfAngle); 112 | expect(result.intersectionColor).toBe(source.intersectionColor); 113 | expect(result.radius).toBe(source.radius); 114 | expect(result.show).toBe(source.show); 115 | expect(result.showIntersection).toBe(source.showIntersection); 116 | expect(result.intersectionWidth).toBe(source.intersectionWidth); 117 | }); 118 | }); 119 | }); 120 | -------------------------------------------------------------------------------- /lib/rectangular/rectangular-pyramid-sensor-volume.js: -------------------------------------------------------------------------------- 1 | define(function(require) { 2 | 'use strict'; 3 | 4 | var clone = require('Cesium/Core/clone'); 5 | var defaultValue = require('Cesium/Core/defaultValue'); 6 | var defined = require('Cesium/Core/defined'); 7 | var defineProperties = require('Cesium/Core/defineProperties'); 8 | var destroyObject = require('Cesium/Core/destroyObject'); 9 | var DeveloperError = require('Cesium/Core/DeveloperError'); 10 | var CesiumMath = require('Cesium/Core/Math'); 11 | var Spherical = require('Cesium/Core/Spherical'); 12 | 13 | var CustomSensorVolume = require('../custom/custom-sensor-volume'); 14 | 15 | function assignSpherical(index, array, clock, cone) { 16 | var spherical = array[index]; 17 | if (!defined(spherical)) { 18 | spherical = new Spherical(); 19 | array[index] = spherical; 20 | } 21 | spherical.clock = clock; 22 | spherical.cone = cone; 23 | spherical.magnitude = 1.0; 24 | } 25 | 26 | function updateDirections(rectangularSensor) { 27 | var directions = rectangularSensor._customSensor.directions; 28 | 29 | // At 90 degrees the sensor is completely open, and tan() goes to infinity. 30 | var tanX = Math.tan(Math.min(rectangularSensor._xHalfAngle, CesiumMath.toRadians(89.0))); 31 | var tanY = Math.tan(Math.min(rectangularSensor._yHalfAngle, CesiumMath.toRadians(89.0))); 32 | var theta = Math.atan(tanX / tanY); 33 | var cone = Math.atan(Math.sqrt((tanX * tanX) + (tanY * tanY))); 34 | 35 | assignSpherical(0, directions, theta, cone); 36 | assignSpherical(1, directions, CesiumMath.toRadians(180.0) - theta, cone); 37 | assignSpherical(2, directions, CesiumMath.toRadians(180.0) + theta, cone); 38 | assignSpherical(3, directions, -theta, cone); 39 | 40 | directions.length = 4; 41 | rectangularSensor._customSensor.directions = directions; 42 | } 43 | 44 | var RectangularPyramidSensorVolume = function(options) { 45 | options = defaultValue(options, defaultValue.EMPTY_OBJECT); 46 | 47 | var customSensorOptions = clone(options); 48 | customSensorOptions._pickPrimitive = defaultValue(options._pickPrimitive, this); 49 | customSensorOptions.directions = undefined; 50 | this._customSensor = new CustomSensorVolume(customSensorOptions); 51 | 52 | this._xHalfAngle = defaultValue(options.xHalfAngle, CesiumMath.PI_OVER_TWO); 53 | this._yHalfAngle = defaultValue(options.yHalfAngle, CesiumMath.PI_OVER_TWO); 54 | 55 | updateDirections(this); 56 | }; 57 | 58 | defineProperties(RectangularPyramidSensorVolume.prototype, { 59 | xHalfAngle: { 60 | get: function() { 61 | return this._xHalfAngle; 62 | }, 63 | set: function(value) { 64 | // >>includeStart('debug', pragmas.debug) 65 | if (value > CesiumMath.PI_OVER_TWO) { 66 | throw new DeveloperError('xHalfAngle must be less than or equal to 90 degrees.'); 67 | } 68 | // >>includeEnd('debug'); 69 | 70 | if (this._xHalfAngle !== value) { 71 | this._xHalfAngle = value; 72 | updateDirections(this); 73 | } 74 | } 75 | }, 76 | yHalfAngle: { 77 | get: function() { 78 | return this._yHalfAngle; 79 | }, 80 | set: function(value) { 81 | // >>includeStart('debug', pragmas.debug) 82 | if (value > CesiumMath.PI_OVER_TWO) { 83 | throw new DeveloperError('yHalfAngle must be less than or equal to 90 degrees.'); 84 | } 85 | // >>includeEnd('debug'); 86 | 87 | if (this._yHalfAngle !== value) { 88 | this._yHalfAngle = value; 89 | updateDirections(this); 90 | } 91 | } 92 | }, 93 | show: { 94 | get: function() { 95 | return this._customSensor.show; 96 | }, 97 | set: function(value) { 98 | this._customSensor.show = value; 99 | } 100 | }, 101 | showIntersection: { 102 | get: function() { 103 | return this._customSensor.showIntersection; 104 | }, 105 | set: function(value) { 106 | this._customSensor.showIntersection = value; 107 | } 108 | }, 109 | showThroughEllipsoid: { 110 | get: function() { 111 | return this._customSensor.showThroughEllipsoid; 112 | }, 113 | set: function(value) { 114 | this._customSensor.showThroughEllipsoid = value; 115 | } 116 | }, 117 | modelMatrix: { 118 | get: function() { 119 | return this._customSensor.modelMatrix; 120 | }, 121 | set: function(value) { 122 | this._customSensor.modelMatrix = value; 123 | } 124 | }, 125 | radius: { 126 | get: function() { 127 | return this._customSensor.radius; 128 | }, 129 | set: function(value) { 130 | this._customSensor.radius = value; 131 | } 132 | }, 133 | lateralSurfaceMaterial: { 134 | get: function() { 135 | return this._customSensor.lateralSurfaceMaterial; 136 | }, 137 | set: function(value) { 138 | this._customSensor.lateralSurfaceMaterial = value; 139 | } 140 | }, 141 | intersectionColor: { 142 | get: function() { 143 | return this._customSensor.intersectionColor; 144 | }, 145 | set: function(value) { 146 | this._customSensor.intersectionColor = value; 147 | } 148 | }, 149 | intersectionWidth: { 150 | get: function() { 151 | return this._customSensor.intersectionWidth; 152 | }, 153 | set: function(value) { 154 | this._customSensor.intersectionWidth = value; 155 | } 156 | }, 157 | id: { 158 | get: function() { 159 | return this._customSensor.id; 160 | }, 161 | set: function(value) { 162 | this._customSensor.id = value; 163 | } 164 | } 165 | }); 166 | 167 | RectangularPyramidSensorVolume.prototype.update = function(frameState) { 168 | this._customSensor.update(frameState); 169 | }; 170 | 171 | RectangularPyramidSensorVolume.prototype.isDestroyed = function() { 172 | return false; 173 | }; 174 | 175 | RectangularPyramidSensorVolume.prototype.destroy = function() { 176 | this._customSensor = this._customSensor && this._customSensor.destroy(); 177 | return destroyObject(this); 178 | }; 179 | 180 | return RectangularPyramidSensorVolume; 181 | }); 182 | -------------------------------------------------------------------------------- /lib/custom/custom-pattern-sensor-graphics.js: -------------------------------------------------------------------------------- 1 | define(function(require) { 2 | 'use strict'; 3 | 4 | var defaultValue = require('Cesium/Core/defaultValue'); 5 | var defined = require('Cesium/Core/defined'); 6 | var defineProperties = require('Cesium/Core/defineProperties'); 7 | var DeveloperError = require('Cesium/Core/DeveloperError'); 8 | var Event = require('Cesium/Core/Event'); 9 | 10 | var createMaterialPropertyDescriptor = require('Cesium/DataSources/createMaterialPropertyDescriptor'); 11 | var createPropertyDescriptor = require('Cesium/DataSources/createPropertyDescriptor'); 12 | 13 | /** 14 | * An optionally time-dynamic custom patterned sensor. 15 | * 16 | * @alias CustomPatternSensorGraphics 17 | * @constructor 18 | */ 19 | var CustomPatternSensorGraphics = function(options) { 20 | this._directions = undefined; 21 | this._directionsSubscription = undefined; 22 | 23 | this._lateralSurfaceMaterial = undefined; 24 | this._lateralSurfaceMaterialSubscription = undefined; 25 | 26 | this._intersectionColor = undefined; 27 | this._intersectionColorSubscription = undefined; 28 | this._intersectionWidth = undefined; 29 | this._intersectionWidthSubscription = undefined; 30 | this._showIntersection = undefined; 31 | this._showIntersectionSubscription = undefined; 32 | this._radius = undefined; 33 | this._radiusSubscription = undefined; 34 | this._show = undefined; 35 | this._showSubscription = undefined; 36 | this._definitionChanged = new Event(); 37 | 38 | this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); 39 | }; 40 | 41 | defineProperties(CustomPatternSensorGraphics.prototype, { 42 | /** 43 | * Gets the event that is raised whenever a new property is assigned. 44 | * @memberof CustomPatternSensorGraphics.prototype 45 | * 46 | * @type {Event} 47 | * @readonly 48 | */ 49 | definitionChanged: { 50 | get: function() { 51 | return this._definitionChanged; 52 | } 53 | }, 54 | 55 | /** 56 | * A {@link Property} which returns an array of {@link Spherical} instances representing the sensor's projection. 57 | * @memberof CustomPatternSensorGraphics.prototype 58 | * @type {Property} 59 | */ 60 | directions: createPropertyDescriptor('directions'), 61 | 62 | /** 63 | * Gets or sets the {@link MaterialProperty} specifying the the sensor's appearance. 64 | * @memberof CustomPatternSensorGraphics.prototype 65 | * @type {MaterialProperty} 66 | */ 67 | lateralSurfaceMaterial: createMaterialPropertyDescriptor('lateralSurfaceMaterial'), 68 | 69 | /** 70 | * Gets or sets the {@link Color} {@link Property} specifying the color of the line formed by the intersection of the sensor and other central bodies. 71 | * @memberof CustomPatternSensorGraphics.prototype 72 | * @type {Property} 73 | */ 74 | intersectionColor: createPropertyDescriptor('intersectionColor'), 75 | 76 | /** 77 | * Gets or sets the numeric {@link Property} specifying the width of the line formed by the intersection of the sensor and other central bodies. 78 | * @memberof CustomPatternSensorGraphics.prototype 79 | * @type {Property} 80 | */ 81 | intersectionWidth: createPropertyDescriptor('intersectionWidth'), 82 | 83 | /** 84 | * Gets or sets the boolean {@link Property} specifying the visibility of the line formed by the intersection of the sensor and other central bodies. 85 | * @memberof CustomPatternSensorGraphics.prototype 86 | * @type {Property} 87 | */ 88 | showIntersection: createPropertyDescriptor('showIntersection'), 89 | 90 | /** 91 | * Gets or sets the numeric {@link Property} specifying the radius of the sensor's projection. 92 | * @memberof CustomPatternSensorGraphics.prototype 93 | * @type {Property} 94 | */ 95 | radius: createPropertyDescriptor('radius'), 96 | 97 | /** 98 | * Gets or sets the boolean {@link Property} specifying the visibility of the sensor. 99 | * @memberof CustomPatternSensorGraphics.prototype 100 | * @type {Property} 101 | */ 102 | show: createPropertyDescriptor('show') 103 | }); 104 | 105 | /** 106 | * Duplicates a CustomPatternSensorGraphics instance. 107 | * 108 | * @param {CustomPatternSensorGraphics} [result] The object onto which to store the result. 109 | * @returns {CustomPatternSensorGraphics} The modified result parameter or a new instance if one was not provided. 110 | */ 111 | CustomPatternSensorGraphics.prototype.clone = function(result) { 112 | if (!defined(result)) { 113 | result = new CustomPatternSensorGraphics(); 114 | } 115 | result.directions = this.directions; 116 | result.radius = this.radius; 117 | result.show = this.show; 118 | result.showIntersection = this.showIntersection; 119 | result.intersectionColor = this.intersectionColor; 120 | result.intersectionWidth = this.intersectionWidth; 121 | result.lateralSurfaceMaterial = this.lateralSurfaceMaterial; 122 | return result; 123 | }; 124 | 125 | /** 126 | * Assigns each unassigned property on this object to the value 127 | * of the same property on the provided source object. 128 | * 129 | * @param {CustomPatternSensorGraphics} source The object to be merged into this object. 130 | */ 131 | CustomPatternSensorGraphics.prototype.merge = function(source) { 132 | // >>includeStart('debug', pragmas.debug); 133 | if (!defined(source)) { 134 | throw new DeveloperError('source is required.'); 135 | } 136 | // >>includeEnd('debug'); 137 | 138 | this.directions = defaultValue(this.directions, source.directions); 139 | this.radius = defaultValue(this.radius, source.radius); 140 | this.show = defaultValue(this.show, source.show); 141 | this.showIntersection = defaultValue(this.showIntersection, source.showIntersection); 142 | this.intersectionColor = defaultValue(this.intersectionColor, source.intersectionColor); 143 | this.intersectionWidth = defaultValue(this.intersectionWidth, source.intersectionWidth); 144 | this.lateralSurfaceMaterial = defaultValue(this.lateralSurfaceMaterial, source.lateralSurfaceMaterial); 145 | }; 146 | 147 | return CustomPatternSensorGraphics; 148 | }); 149 | -------------------------------------------------------------------------------- /test/conic/conic-sensor-graphics-spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-nested-callbacks */ 2 | define([ 3 | 'conic/conic-sensor-graphics', 4 | 'Cesium/Core/Color', 5 | 'Cesium/DataSources/ColorMaterialProperty', 6 | 'Cesium/DataSources/ConstantProperty', 7 | '../matchers/add-to-throw-developer-error-matcher' 8 | ], function( 9 | ConicSensorGraphics, 10 | Color, 11 | ColorMaterialProperty, 12 | ConstantProperty, 13 | addToThrowDeveloperErrorMatcher 14 | ) { 15 | 'use strict'; 16 | 17 | /* global describe, it, beforeEach, expect */ 18 | 19 | describe('conic sensor graphics', function() { 20 | describe('merge', function() { 21 | beforeEach(addToThrowDeveloperErrorMatcher); 22 | 23 | it('should assign unassigned properties', function() { 24 | var source = new ConicSensorGraphics(); 25 | source.lateralSurfaceMaterial = new ColorMaterialProperty(); 26 | source.innerHalfAngle = new ConstantProperty(1); 27 | source.maximumClockAngle = new ConstantProperty(1); 28 | source.minimumClockAngle = new ConstantProperty(1); 29 | source.outerHalfAngle = new ConstantProperty(1); 30 | source.intersectionColor = new ConstantProperty(Color.WHITE); 31 | source.radius = new ConstantProperty(1); 32 | source.show = new ConstantProperty(true); 33 | source.showIntersection = new ConstantProperty(true); 34 | source.intersectionWidth = new ConstantProperty(1); 35 | 36 | var target = new ConicSensorGraphics(); 37 | target.merge(source); 38 | 39 | expect(target.lateralSurfaceMaterial).toBe(source.lateralSurfaceMaterial); 40 | expect(target.innerHalfAngle).toBe(source.innerHalfAngle); 41 | expect(target.maximumClockAngle).toBe(source.maximumClockAngle); 42 | expect(target.minimumClockAngle).toBe(source.minimumClockAngle); 43 | expect(target.outerHalfAngle).toBe(source.outerHalfAngle); 44 | expect(target.intersectionColor).toBe(source.intersectionColor); 45 | expect(target.radius).toBe(source.radius); 46 | expect(target.show).toBe(source.show); 47 | expect(target.showIntersection).toBe(source.showIntersection); 48 | expect(target.intersectionWidth).toBe(source.intersectionWidth); 49 | }); 50 | 51 | it('should not assign assigned properties', function() { 52 | var source = new ConicSensorGraphics(); 53 | source.lateralSurfaceMaterial = new ColorMaterialProperty(); 54 | source.innerHalfAngle = new ConstantProperty(1); 55 | source.maximumClockAngle = new ConstantProperty(1); 56 | source.minimumClockAngle = new ConstantProperty(1); 57 | source.outerHalfAngle = new ConstantProperty(1); 58 | source.intersectionColor = new ConstantProperty(Color.WHITE); 59 | source.radius = new ConstantProperty(1); 60 | source.show = new ConstantProperty(true); 61 | source.showIntersection = new ConstantProperty(true); 62 | source.intersectionWidth = new ConstantProperty(1); 63 | 64 | var lateralSurfaceMaterial = new ColorMaterialProperty(); 65 | var innerHalfAngle = new ConstantProperty(1); 66 | var maximumClockAngle = new ConstantProperty(1); 67 | var minimumClockAngle = new ConstantProperty(1); 68 | var outerHalfAngle = new ConstantProperty(1); 69 | var intersectionColor = new ConstantProperty(Color.WHITE); 70 | var radius = new ConstantProperty(1); 71 | var show = new ConstantProperty(true); 72 | var showIntersection = new ConstantProperty(true); 73 | var intersectionWidth = new ConstantProperty(1); 74 | 75 | var target = new ConicSensorGraphics(); 76 | target.lateralSurfaceMaterial = lateralSurfaceMaterial; 77 | target.innerHalfAngle = innerHalfAngle; 78 | target.maximumClockAngle = maximumClockAngle; 79 | target.minimumClockAngle = minimumClockAngle; 80 | target.outerHalfAngle = outerHalfAngle; 81 | target.intersectionColor = intersectionColor; 82 | target.radius = radius; 83 | target.show = show; 84 | target.showIntersection = showIntersection; 85 | target.intersectionWidth = intersectionWidth; 86 | 87 | target.merge(source); 88 | 89 | expect(target.lateralSurfaceMaterial).toBe(lateralSurfaceMaterial); 90 | expect(target.innerHalfAngle).toBe(innerHalfAngle); 91 | expect(target.maximumClockAngle).toBe(maximumClockAngle); 92 | expect(target.minimumClockAngle).toBe(minimumClockAngle); 93 | expect(target.outerHalfAngle).toBe(outerHalfAngle); 94 | expect(target.intersectionColor).toBe(intersectionColor); 95 | expect(target.radius).toBe(radius); 96 | expect(target.show).toBe(show); 97 | expect(target.showIntersection).toBe(showIntersection); 98 | expect(target.intersectionWidth).toBe(intersectionWidth); 99 | }); 100 | 101 | it('should throw if source undefined', function() { 102 | var target = new ConicSensorGraphics(); 103 | expect(function() { 104 | target.merge(undefined); 105 | }).toThrowDeveloperError(); 106 | }); 107 | }); 108 | 109 | it('should clone', function() { 110 | var source = new ConicSensorGraphics(); 111 | source.lateralSurfaceMaterial = new ColorMaterialProperty(); 112 | source.innerHalfAngle = new ConstantProperty(1); 113 | source.maximumClockAngle = new ConstantProperty(1); 114 | source.minimumClockAngle = new ConstantProperty(1); 115 | source.outerHalfAngle = new ConstantProperty(1); 116 | source.intersectionColor = new ConstantProperty(Color.WHITE); 117 | source.radius = new ConstantProperty(1); 118 | source.show = new ConstantProperty(true); 119 | source.showIntersection = new ConstantProperty(true); 120 | source.intersectionWidth = new ConstantProperty(1); 121 | 122 | var result = source.clone(); 123 | expect(result.lateralSurfaceMaterial).toBe(source.lateralSurfaceMaterial); 124 | expect(result.innerHalfAngle).toBe(source.innerHalfAngle); 125 | expect(result.maximumClockAngle).toBe(source.maximumClockAngle); 126 | expect(result.minimumClockAngle).toBe(source.minimumClockAngle); 127 | expect(result.outerHalfAngle).toBe(source.outerHalfAngle); 128 | expect(result.intersectionColor).toBe(source.intersectionColor); 129 | expect(result.radius).toBe(source.radius); 130 | expect(result.show).toBe(source.show); 131 | expect(result.showIntersection).toBe(source.showIntersection); 132 | expect(result.intersectionWidth).toBe(source.intersectionWidth); 133 | }); 134 | }); 135 | }); 136 | -------------------------------------------------------------------------------- /lib/rectangular/rectangular-sensor-graphics.js: -------------------------------------------------------------------------------- 1 | define(function(require) { 2 | 'use strict'; 3 | 4 | var defaultValue = require('Cesium/Core/defaultValue'); 5 | var defined = require('Cesium/Core/defined'); 6 | var defineProperties = require('Cesium/Core/defineProperties'); 7 | var DeveloperError = require('Cesium/Core/DeveloperError'); 8 | var Event = require('Cesium/Core/Event'); 9 | 10 | var createPropertyDescriptor = require('Cesium/DataSources/createPropertyDescriptor'); 11 | 12 | /** 13 | * An optionally time-dynamic pyramid. 14 | * 15 | * @alias RectangularSensorGraphics 16 | * @constructor 17 | */ 18 | var RectangularSensorGraphics = function() { 19 | this._xHalfAngle = undefined; 20 | this._xHalfAngleSubscription = undefined; 21 | this._yHalfAngle = undefined; 22 | this._yHalfAngleSubscription = undefined; 23 | 24 | this._lateralSurfaceMaterial = undefined; 25 | this._lateralSurfaceMaterialSubscription = undefined; 26 | 27 | this._intersectionColor = undefined; 28 | this._intersectionColorSubscription = undefined; 29 | this._intersectionWidth = undefined; 30 | this._intersectionWidthSubscription = undefined; 31 | this._showIntersection = undefined; 32 | this._showIntersectionSubscription = undefined; 33 | this._radius = undefined; 34 | this._radiusSubscription = undefined; 35 | this._show = undefined; 36 | this._showSubscription = undefined; 37 | this._definitionChanged = new Event(); 38 | }; 39 | 40 | defineProperties(RectangularSensorGraphics.prototype, { 41 | /** 42 | * Gets the event that is raised whenever a new property is assigned. 43 | * @memberof RectangularSensorGraphics.prototype 44 | * 45 | * @type {Event} 46 | * @readonly 47 | */ 48 | definitionChanged: { 49 | get: function() { 50 | return this._definitionChanged; 51 | } 52 | }, 53 | 54 | /** 55 | * A {@link Property} which returns an array of {@link Spherical} instances representing the pyramid's projection. 56 | * @memberof RectangularSensorGraphics.prototype 57 | * @type {Property} 58 | */ 59 | xHalfAngle: createPropertyDescriptor('xHalfAngle'), 60 | 61 | /** 62 | * A {@link Property} which returns an array of {@link Spherical} instances representing the pyramid's projection. 63 | * @memberof RectangularSensorGraphics.prototype 64 | * @type {Property} 65 | */ 66 | yHalfAngle: createPropertyDescriptor('yHalfAngle'), 67 | 68 | /** 69 | * Gets or sets the {@link MaterialProperty} specifying the the pyramid's appearance. 70 | * @memberof RectangularSensorGraphics.prototype 71 | * @type {MaterialProperty} 72 | */ 73 | lateralSurfaceMaterial: createPropertyDescriptor('lateralSurfaceMaterial'), 74 | 75 | /** 76 | * Gets or sets the {@link Color} {@link Property} specifying the color of the line formed by the intersection of the pyramid and other central bodies. 77 | * @memberof RectangularSensorGraphics.prototype 78 | * @type {Property} 79 | */ 80 | intersectionColor: createPropertyDescriptor('intersectionColor'), 81 | 82 | /** 83 | * Gets or sets the numeric {@link Property} specifying the width of the line formed by the intersection of the pyramid and other central bodies. 84 | * @memberof RectangularSensorGraphics.prototype 85 | * @type {Property} 86 | */ 87 | intersectionWidth: createPropertyDescriptor('intersectionWidth'), 88 | 89 | /** 90 | * Gets or sets the boolean {@link Property} specifying the visibility of the line formed by the intersection of the pyramid and other central bodies. 91 | * @memberof RectangularSensorGraphics.prototype 92 | * @type {Property} 93 | */ 94 | showIntersection: createPropertyDescriptor('showIntersection'), 95 | 96 | /** 97 | * Gets or sets the numeric {@link Property} specifying the radius of the pyramid's projection. 98 | * @memberof RectangularSensorGraphics.prototype 99 | * @type {Property} 100 | */ 101 | radius: createPropertyDescriptor('radius'), 102 | 103 | /** 104 | * Gets or sets the boolean {@link Property} specifying the visibility of the pyramid. 105 | * @memberof RectangularSensorGraphics.prototype 106 | * @type {Property} 107 | */ 108 | show: createPropertyDescriptor('show') 109 | }); 110 | 111 | /** 112 | * Duplicates a RectangularSensorGraphics instance. 113 | * 114 | * @param {RectangularSensorGraphics} [result] The object onto which to store the result. 115 | * @returns {RectangularSensorGraphics} The modified result parameter or a new instance if one was not provided. 116 | */ 117 | RectangularSensorGraphics.prototype.clone = function(result) { 118 | if (!defined(result)) { 119 | result = new RectangularSensorGraphics(); 120 | } 121 | result.xHalfAngle = this.xHalfAngle; 122 | result.yHalfAngle = this.yHalfAngle; 123 | result.radius = this.radius; 124 | result.show = this.show; 125 | result.showIntersection = this.showIntersection; 126 | result.intersectionColor = this.intersectionColor; 127 | result.intersectionWidth = this.intersectionWidth; 128 | result.lateralSurfaceMaterial = this.lateralSurfaceMaterial; 129 | return result; 130 | }; 131 | 132 | /** 133 | * Assigns each unassigned property on this object to the value 134 | * of the same property on the provided source object. 135 | * 136 | * @param {RectangularSensorGraphics} source The object to be merged into this object. 137 | */ 138 | RectangularSensorGraphics.prototype.merge = function(source) { 139 | // >>includeStart('debug', pragmas.debug); 140 | if (!defined(source)) { 141 | throw new DeveloperError('source is required.'); 142 | } 143 | // >>includeEnd('debug'); 144 | 145 | this.xHalfAngle = defaultValue(this.xHalfAngle, source.xHalfAngle); 146 | this.yHalfAngle = defaultValue(this.yHalfAngle, source.yHalfAngle); 147 | this.radius = defaultValue(this.radius, source.radius); 148 | this.show = defaultValue(this.show, source.show); 149 | this.showIntersection = defaultValue(this.showIntersection, source.showIntersection); 150 | this.intersectionColor = defaultValue(this.intersectionColor, source.intersectionColor); 151 | this.intersectionWidth = defaultValue(this.intersectionWidth, source.intersectionWidth); 152 | this.lateralSurfaceMaterial = defaultValue(this.lateralSurfaceMaterial, source.lateralSurfaceMaterial); 153 | }; 154 | 155 | return RectangularSensorGraphics; 156 | }); 157 | -------------------------------------------------------------------------------- /lib/custom/custom-pattern-sensor-visualizer.js: -------------------------------------------------------------------------------- 1 | define(function(require) { 2 | 'use strict'; 3 | 4 | var AssociativeArray = require('Cesium/Core/AssociativeArray'); 5 | var Cartesian3 = require('Cesium/Core/Cartesian3'); 6 | var Color = require('Cesium/Core/Color'); 7 | var defined = require('Cesium/Core/defined'); 8 | var destroyObject = require('Cesium/Core/destroyObject'); 9 | var DeveloperError = require('Cesium/Core/DeveloperError'); 10 | var Matrix3 = require('Cesium/Core/Matrix3'); 11 | var Matrix4 = require('Cesium/Core/Matrix4'); 12 | var Quaternion = require('Cesium/Core/Quaternion'); 13 | var MaterialProperty = require('Cesium/DataSources/MaterialProperty'); 14 | var Property = require('Cesium/DataSources/Property'); 15 | 16 | var CustomSensorVolume = require('../custom/custom-sensor-volume'); 17 | var removePrimitive = require('../util/remove-primitive'); 18 | 19 | var defaultIntersectionColor = Color.WHITE; 20 | var defaultIntersectionWidth = 1.0; 21 | var defaultRadius = Number.POSITIVE_INFINITY; 22 | 23 | var matrix3Scratch = new Matrix3(); 24 | var cachedPosition = new Cartesian3(); 25 | var cachedOrientation = new Quaternion(); 26 | 27 | /** 28 | * A {@link Visualizer} which maps {@link Entity#customPatternSensor} to a {@link CustomPatternSensor}. 29 | * @alias CustomPatternSensorVisualizer 30 | * @constructor 31 | * 32 | * @param {Scene} scene The scene the primitives will be rendered in. 33 | * @param {EntityCollection} entityCollection The entityCollection to visualize. 34 | */ 35 | var CustomPatternSensorVisualizer = function(scene, entityCollection) { 36 | // >>includeStart('debug', pragmas.debug); 37 | if (!defined(scene)) { 38 | throw new DeveloperError('scene is required.'); 39 | } 40 | if (!defined(entityCollection)) { 41 | throw new DeveloperError('entityCollection is required.'); 42 | } 43 | // >>includeEnd('debug'); 44 | 45 | entityCollection.collectionChanged.addEventListener(CustomPatternSensorVisualizer.prototype._onCollectionChanged, this); 46 | 47 | this._scene = scene; 48 | this._primitives = scene.primitives; 49 | this._entityCollection = entityCollection; 50 | this._hash = {}; 51 | this._entitiesToVisualize = new AssociativeArray(); 52 | 53 | this._onCollectionChanged(entityCollection, entityCollection.values, [], []); 54 | }; 55 | 56 | /** 57 | * Updates the primitives created by this visualizer to match their 58 | * Entity counterpart at the given time. 59 | * 60 | * @param {JulianDate} time The time to update to. 61 | * @returns {Boolean} This function always returns true. 62 | */ 63 | CustomPatternSensorVisualizer.prototype.update = function(time) { 64 | // >>includeStart('debug', pragmas.debug); 65 | if (!defined(time)) { 66 | throw new DeveloperError('time is required.'); 67 | } 68 | // >>includeEnd('debug'); 69 | 70 | var entities = this._entitiesToVisualize.values; 71 | var hash = this._hash; 72 | var primitives = this._primitives; 73 | 74 | for (var i = 0, len = entities.length; i < len; i++) { 75 | var entity = entities[i]; 76 | var customPatternSensorGraphics = entity._customPatternSensor; 77 | 78 | var position; 79 | var orientation; 80 | var directions; 81 | var data = hash[entity.id]; 82 | var show = entity.isShowing && entity.isAvailable(time) && Property.getValueOrDefault(customPatternSensorGraphics._show, time, true); 83 | 84 | if (show) { 85 | position = Property.getValueOrUndefined(entity._position, time, cachedPosition); 86 | orientation = Property.getValueOrUndefined(entity._orientation, time, cachedOrientation); 87 | directions = Property.getValueOrUndefined(customPatternSensorGraphics._directions, time); 88 | show = defined(position) && defined(orientation) && defined(directions); 89 | } 90 | 91 | if (!show) { 92 | // don't bother creating or updating anything else 93 | if (defined(data)) { 94 | data.primitive.show = false; 95 | } 96 | continue; 97 | } 98 | 99 | var primitive = defined(data) ? data.primitive : undefined; 100 | if (!defined(primitive)) { 101 | primitive = new CustomSensorVolume(); 102 | primitive.id = entity; 103 | primitives.add(primitive); 104 | 105 | data = { 106 | primitive: primitive, 107 | position: undefined, 108 | orientation: undefined 109 | }; 110 | hash[entity.id] = data; 111 | } 112 | 113 | if (!Cartesian3.equals(position, data.position) || !Quaternion.equals(orientation, data.orientation)) { 114 | Matrix4.fromRotationTranslation(Matrix3.fromQuaternion(orientation, matrix3Scratch), position, primitive.modelMatrix); 115 | data.position = Cartesian3.clone(position, data.position); 116 | data.orientation = Quaternion.clone(orientation, data.orientation); 117 | } 118 | 119 | primitive.show = true; 120 | primitive.directions = directions; 121 | primitive.radius = Property.getValueOrDefault(customPatternSensorGraphics._radius, time, defaultRadius); 122 | primitive.lateralSurfaceMaterial = MaterialProperty.getValue(time, customPatternSensorGraphics._lateralSurfaceMaterial, primitive.lateralSurfaceMaterial); 123 | primitive.intersectionColor = Property.getValueOrClonedDefault(customPatternSensorGraphics._intersectionColor, time, defaultIntersectionColor, primitive.intersectionColor); 124 | primitive.intersectionWidth = Property.getValueOrDefault(customPatternSensorGraphics._intersectionWidth, time, defaultIntersectionWidth); 125 | } 126 | return true; 127 | }; 128 | 129 | /** 130 | * Returns true if this object was destroyed; otherwise, false. 131 | * 132 | * @returns {Boolean} True if this object was destroyed; otherwise, false. 133 | */ 134 | CustomPatternSensorVisualizer.prototype.isDestroyed = function() { 135 | return false; 136 | }; 137 | 138 | /** 139 | * Removes and destroys all primitives created by this instance. 140 | */ 141 | CustomPatternSensorVisualizer.prototype.destroy = function() { 142 | var entities = this._entitiesToVisualize.values; 143 | var hash = this._hash; 144 | var primitives = this._primitives; 145 | for (var i = entities.length - 1; i > -1; i--) { 146 | removePrimitive(entities[i], hash, primitives); 147 | } 148 | return destroyObject(this); 149 | }; 150 | 151 | /** 152 | * @private 153 | */ 154 | CustomPatternSensorVisualizer.prototype._onCollectionChanged = function(entityCollection, added, removed, changed) { 155 | var i; 156 | var entity; 157 | var entities = this._entitiesToVisualize; 158 | var hash = this._hash; 159 | var primitives = this._primitives; 160 | 161 | for (i = added.length - 1; i > -1; i--) { 162 | entity = added[i]; 163 | if (defined(entity._customPatternSensor) && defined(entity._position) && defined(entity._orientation)) { 164 | entities.set(entity.id, entity); 165 | } 166 | } 167 | 168 | for (i = changed.length - 1; i > -1; i--) { 169 | entity = changed[i]; 170 | if (defined(entity._customPatternSensor) && defined(entity._position) && defined(entity._orientation)) { 171 | entities.set(entity.id, entity); 172 | } else { 173 | removePrimitive(entity, hash, primitives); 174 | entities.remove(entity.id); 175 | } 176 | } 177 | 178 | for (i = removed.length - 1; i > -1; i--) { 179 | entity = removed[i]; 180 | removePrimitive(entity, hash, primitives); 181 | entities.remove(entity.id); 182 | } 183 | }; 184 | 185 | return CustomPatternSensorVisualizer; 186 | }); 187 | -------------------------------------------------------------------------------- /lib/conic/conic-sensor-graphics.js: -------------------------------------------------------------------------------- 1 | define(function(require) { 2 | 'use strict'; 3 | 4 | var defaultValue = require('Cesium/Core/defaultValue'); 5 | var defined = require('Cesium/Core/defined'); 6 | var defineProperties = require('Cesium/Core/defineProperties'); 7 | var DeveloperError = require('Cesium/Core/DeveloperError'); 8 | var Event = require('Cesium/Core/Event'); 9 | 10 | var createMaterialPropertyDescriptor = require('Cesium/DataSources/createMaterialPropertyDescriptor'); 11 | var createPropertyDescriptor = require('Cesium/DataSources/createPropertyDescriptor'); 12 | 13 | /** 14 | * An optionally time-dynamic cone. 15 | * 16 | * @alias ConicSensorGraphics 17 | * @constructor 18 | */ 19 | var ConicSensorGraphics = function(options) { 20 | this._minimumClockAngle = undefined; 21 | this._minimumClockAngleSubscription = undefined; 22 | this._maximumClockAngle = undefined; 23 | this._maximumClockAngleSubscription = undefined; 24 | this._innerHalfAngle = undefined; 25 | this._innerHalfAngleSubscription = undefined; 26 | this._outerHalfAngle = undefined; 27 | this._outerHalfAngleSubscription = undefined; 28 | this._lateralSurfaceMaterial = undefined; 29 | this._lateralSurfaceMaterialSubscription = undefined; 30 | this._intersectionColor = undefined; 31 | this._intersectionColorSubscription = undefined; 32 | this._intersectionWidth = undefined; 33 | this._intersectionWidthSubscription = undefined; 34 | this._showIntersection = undefined; 35 | this._showIntersectionSubscription = undefined; 36 | this._radius = undefined; 37 | this._radiusSubscription = undefined; 38 | this._show = undefined; 39 | this._showSubscription = undefined; 40 | this._definitionChanged = new Event(); 41 | 42 | this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); 43 | }; 44 | 45 | defineProperties(ConicSensorGraphics.prototype, { 46 | /** 47 | * Gets the event that is raised whenever a new property is assigned. 48 | * @memberof ConicSensorGraphics.prototype 49 | * 50 | * @type {Event} 51 | * @readonly 52 | */ 53 | definitionChanged: { 54 | get: function() { 55 | return this._definitionChanged; 56 | } 57 | }, 58 | 59 | /** 60 | * Gets or sets the numeric {@link Property} specifying the the cone's minimum clock angle. 61 | * @memberof ConicSensorGraphics.prototype 62 | * @type {Property} 63 | */ 64 | minimumClockAngle: createPropertyDescriptor('minimumClockAngle'), 65 | 66 | /** 67 | * Gets or sets the numeric {@link Property} specifying the the cone's maximum clock angle. 68 | * @memberof ConicSensorGraphics.prototype 69 | * @type {Property} 70 | */ 71 | maximumClockAngle: createPropertyDescriptor('maximumClockAngle'), 72 | 73 | /** 74 | * Gets or sets the numeric {@link Property} specifying the the cone's inner half-angle. 75 | * @memberof ConicSensorGraphics.prototype 76 | * @type {Property} 77 | */ 78 | innerHalfAngle: createPropertyDescriptor('innerHalfAngle'), 79 | 80 | /** 81 | * Gets or sets the numeric {@link Property} specifying the the cone's outer half-angle. 82 | * @memberof ConicSensorGraphics.prototype 83 | * @type {Property} 84 | */ 85 | outerHalfAngle: createPropertyDescriptor('outerHalfAngle'), 86 | 87 | /** 88 | * Gets or sets the {@link MaterialProperty} specifying the the cone's appearance. 89 | * @memberof ConicSensorGraphics.prototype 90 | * @type {MaterialProperty} 91 | */ 92 | lateralSurfaceMaterial: createMaterialPropertyDescriptor('lateralSurfaceMaterial'), 93 | 94 | /** 95 | * Gets or sets the {@link Color} {@link Property} specifying the color of the line formed by the intersection of the cone and other central bodies. 96 | * @memberof ConicSensorGraphics.prototype 97 | * @type {Property} 98 | */ 99 | intersectionColor: createPropertyDescriptor('intersectionColor'), 100 | 101 | /** 102 | * Gets or sets the numeric {@link Property} specifying the width of the line formed by the intersection of the cone and other central bodies. 103 | * @memberof ConicSensorGraphics.prototype 104 | * @type {Property} 105 | */ 106 | intersectionWidth: createPropertyDescriptor('intersectionWidth'), 107 | 108 | /** 109 | * Gets or sets the boolean {@link Property} specifying the visibility of the line formed by the intersection of the cone and other central bodies. 110 | * @memberof ConicSensorGraphics.prototype 111 | * @type {Property} 112 | */ 113 | showIntersection: createPropertyDescriptor('showIntersection'), 114 | 115 | /** 116 | * Gets or sets the numeric {@link Property} specifying the radius of the cone's projection. 117 | * @memberof ConicSensorGraphics.prototype 118 | * @type {Property} 119 | */ 120 | radius: createPropertyDescriptor('radius'), 121 | 122 | /** 123 | * Gets or sets the boolean {@link Property} specifying the visibility of the cone. 124 | * @memberof ConicSensorGraphics.prototype 125 | * @type {Property} 126 | */ 127 | show: createPropertyDescriptor('show') 128 | }); 129 | 130 | /** 131 | * Duplicates a ConicSensorGraphics instance. 132 | * 133 | * @param {ConicSensorGraphics} [result] The object onto which to store the result. 134 | * @returns {ConicSensorGraphics} The modified result parameter or a new instance if one was not provided. 135 | */ 136 | ConicSensorGraphics.prototype.clone = function(result) { 137 | if (!defined(result)) { 138 | result = new ConicSensorGraphics(); 139 | } 140 | result.show = this.show; 141 | result.innerHalfAngle = this.innerHalfAngle; 142 | result.outerHalfAngle = this.outerHalfAngle; 143 | result.minimumClockAngle = this.minimumClockAngle; 144 | result.maximumClockAngle = this.maximumClockAngle; 145 | result.radius = this.radius; 146 | result.showIntersection = this.showIntersection; 147 | result.intersectionColor = this.intersectionColor; 148 | result.intersectionWidth = this.intersectionWidth; 149 | result.lateralSurfaceMaterial = this.lateralSurfaceMaterial; 150 | return result; 151 | }; 152 | 153 | /** 154 | * Assigns each unassigned property on this object to the value 155 | * of the same property on the provided source object. 156 | * 157 | * @param {ConicSensorGraphics} source The object to be merged into this object. 158 | */ 159 | ConicSensorGraphics.prototype.merge = function(source) { 160 | // >>includeStart('debug', pragmas.debug); 161 | if (!defined(source)) { 162 | throw new DeveloperError('source is required.'); 163 | } 164 | // >>includeEnd('debug'); 165 | 166 | this.show = defaultValue(this.show, source.show); 167 | this.innerHalfAngle = defaultValue(this.innerHalfAngle, source.innerHalfAngle); 168 | this.outerHalfAngle = defaultValue(this.outerHalfAngle, source.outerHalfAngle); 169 | this.minimumClockAngle = defaultValue(this.minimumClockAngle, source.minimumClockAngle); 170 | this.maximumClockAngle = defaultValue(this.maximumClockAngle, source.maximumClockAngle); 171 | this.radius = defaultValue(this.radius, source.radius); 172 | this.showIntersection = defaultValue(this.showIntersection, source.showIntersection); 173 | this.intersectionColor = defaultValue(this.intersectionColor, source.intersectionColor); 174 | this.intersectionWidth = defaultValue(this.intersectionWidth, source.intersectionWidth); 175 | this.lateralSurfaceMaterial = defaultValue(this.lateralSurfaceMaterial, source.lateralSurfaceMaterial); 176 | }; 177 | 178 | return ConicSensorGraphics; 179 | }); 180 | -------------------------------------------------------------------------------- /lib/rectangular/rectangular-sensor-visualizer.js: -------------------------------------------------------------------------------- 1 | define(function(require) { 2 | 'use strict'; 3 | 4 | var AssociativeArray = require('Cesium/Core/AssociativeArray'); 5 | var Cartesian3 = require('Cesium/Core/Cartesian3'); 6 | var Color = require('Cesium/Core/Color'); 7 | var defined = require('Cesium/Core/defined'); 8 | var destroyObject = require('Cesium/Core/destroyObject'); 9 | var DeveloperError = require('Cesium/Core/DeveloperError'); 10 | var CesiumMath = require('Cesium/Core/Math'); 11 | var Matrix3 = require('Cesium/Core/Matrix3'); 12 | var Matrix4 = require('Cesium/Core/Matrix4'); 13 | var Quaternion = require('Cesium/Core/Quaternion'); 14 | var MaterialProperty = require('Cesium/DataSources/MaterialProperty'); 15 | var Property = require('Cesium/DataSources/Property'); 16 | 17 | var RectangularPyramidSensorVolume = require('./rectangular-pyramid-sensor-volume'); 18 | var removePrimitive = require('../util/remove-primitive'); 19 | 20 | var defaultIntersectionColor = Color.WHITE; 21 | var defaultIntersectionWidth = 1.0; 22 | var defaultRadius = Number.POSITIVE_INFINITY; 23 | 24 | var matrix3Scratch = new Matrix3(); 25 | var cachedPosition = new Cartesian3(); 26 | var cachedOrientation = new Quaternion(); 27 | 28 | /** 29 | * A {@link Visualizer} which maps {@link Entity#rectangularSensor} to a {@link RectangularSensor}. 30 | * @alias RectangularSensorVisualizer 31 | * @constructor 32 | * 33 | * @param {Scene} scene The scene the primitives will be rendered in. 34 | * @param {EntityCollection} entityCollection The entityCollection to visualize. 35 | */ 36 | var RectangularSensorVisualizer = function(scene, entityCollection) { 37 | // >>includeStart('debug', pragmas.debug); 38 | if (!defined(scene)) { 39 | throw new DeveloperError('scene is required.'); 40 | } 41 | if (!defined(entityCollection)) { 42 | throw new DeveloperError('entityCollection is required.'); 43 | } 44 | // >>includeEnd('debug'); 45 | 46 | entityCollection.collectionChanged.addEventListener(RectangularSensorVisualizer.prototype._onCollectionChanged, this); 47 | 48 | this._scene = scene; 49 | this._primitives = scene.primitives; 50 | this._entityCollection = entityCollection; 51 | this._hash = {}; 52 | this._entitiesToVisualize = new AssociativeArray(); 53 | 54 | this._onCollectionChanged(entityCollection, entityCollection.values, [], []); 55 | }; 56 | 57 | /** 58 | * Updates the primitives created by this visualizer to match their 59 | * Entity counterpart at the given time. 60 | * 61 | * @param {JulianDate} time The time to update to. 62 | * @returns {Boolean} This function always returns true. 63 | */ 64 | RectangularSensorVisualizer.prototype.update = function(time) { 65 | // >>includeStart('debug', pragmas.debug); 66 | if (!defined(time)) { 67 | throw new DeveloperError('time is required.'); 68 | } 69 | // >>includeEnd('debug'); 70 | 71 | var entities = this._entitiesToVisualize.values; 72 | var hash = this._hash; 73 | var primitives = this._primitives; 74 | 75 | for (var i = 0, len = entities.length; i < len; i++) { 76 | var entity = entities[i]; 77 | var rectangularSensorGraphics = entity._rectangularSensor; 78 | 79 | var position; 80 | var orientation; 81 | var data = hash[entity.id]; 82 | var show = entity.isShowing && entity.isAvailable(time) && Property.getValueOrDefault(rectangularSensorGraphics._show, time, true); 83 | 84 | if (show) { 85 | position = Property.getValueOrUndefined(entity._position, time, cachedPosition); 86 | orientation = Property.getValueOrUndefined(entity._orientation, time, cachedOrientation); 87 | show = defined(position) && defined(orientation); 88 | } 89 | 90 | if (!show) { 91 | // don't bother creating or updating anything else 92 | if (defined(data)) { 93 | data.primitive.show = false; 94 | } 95 | continue; 96 | } 97 | 98 | var primitive = defined(data) ? data.primitive : undefined; 99 | if (!defined(primitive)) { 100 | primitive = new RectangularPyramidSensorVolume(); 101 | primitive.id = entity; 102 | primitives.add(primitive); 103 | 104 | data = { 105 | primitive: primitive, 106 | position: undefined, 107 | orientation: undefined 108 | }; 109 | hash[entity.id] = data; 110 | } 111 | 112 | if (!Cartesian3.equals(position, data.position) || !Quaternion.equals(orientation, data.orientation)) { 113 | Matrix4.fromRotationTranslation(Matrix3.fromQuaternion(orientation, matrix3Scratch), position, primitive.modelMatrix); 114 | data.position = Cartesian3.clone(position, data.position); 115 | data.orientation = Quaternion.clone(orientation, data.orientation); 116 | } 117 | 118 | primitive.show = true; 119 | primitive.xHalfAngle = Property.getValueOrDefault(rectangularSensorGraphics._xHalfAngle, time, CesiumMath.PI_OVER_TWO); 120 | primitive.yHalfAngle = Property.getValueOrDefault(rectangularSensorGraphics._yHalfAngle, time, CesiumMath.PI_OVER_TWO); 121 | primitive.radius = Property.getValueOrDefault(rectangularSensorGraphics._radius, time, defaultRadius); 122 | primitive.lateralSurfaceMaterial = MaterialProperty.getValue(time, rectangularSensorGraphics._lateralSurfaceMaterial, primitive.lateralSurfaceMaterial); 123 | primitive.intersectionColor = Property.getValueOrClonedDefault(rectangularSensorGraphics._intersectionColor, time, defaultIntersectionColor, primitive.intersectionColor); 124 | primitive.intersectionWidth = Property.getValueOrDefault(rectangularSensorGraphics._intersectionWidth, time, defaultIntersectionWidth); 125 | } 126 | return true; 127 | }; 128 | 129 | /** 130 | * Returns true if this object was destroyed; otherwise, false. 131 | * 132 | * @returns {Boolean} True if this object was destroyed; otherwise, false. 133 | */ 134 | RectangularSensorVisualizer.prototype.isDestroyed = function() { 135 | return false; 136 | }; 137 | 138 | /** 139 | * Removes and destroys all primitives created by this instance. 140 | */ 141 | RectangularSensorVisualizer.prototype.destroy = function() { 142 | var entities = this._entitiesToVisualize.values; 143 | var hash = this._hash; 144 | var primitives = this._primitives; 145 | for (var i = entities.length - 1; i > -1; i--) { 146 | removePrimitive(entities[i], hash, primitives); 147 | } 148 | return destroyObject(this); 149 | }; 150 | 151 | /** 152 | * @private 153 | */ 154 | RectangularSensorVisualizer.prototype._onCollectionChanged = function(entityCollection, added, removed, changed) { 155 | var i; 156 | var entity; 157 | var entities = this._entitiesToVisualize; 158 | var hash = this._hash; 159 | var primitives = this._primitives; 160 | 161 | for (i = added.length - 1; i > -1; i--) { 162 | entity = added[i]; 163 | if (defined(entity._rectangularSensor) && defined(entity._position) && defined(entity._orientation)) { 164 | entities.set(entity.id, entity); 165 | } 166 | } 167 | 168 | for (i = changed.length - 1; i > -1; i--) { 169 | entity = changed[i]; 170 | if (defined(entity._rectangularSensor) && defined(entity._position) && defined(entity._orientation)) { 171 | entities.set(entity.id, entity); 172 | } else { 173 | removePrimitive(entity, hash, primitives); 174 | entities.remove(entity.id); 175 | } 176 | } 177 | 178 | for (i = removed.length - 1; i > -1; i--) { 179 | entity = removed[i]; 180 | removePrimitive(entity, hash, primitives); 181 | entities.remove(entity.id); 182 | } 183 | }; 184 | 185 | return RectangularSensorVisualizer; 186 | }); 187 | -------------------------------------------------------------------------------- /lib/initialize.js: -------------------------------------------------------------------------------- 1 | define(function(require) { 2 | 'use strict'; 3 | 4 | var Cartesian3 = require('Cesium/Core/Cartesian3'); 5 | var Color = require('Cesium/Core/Color'); 6 | var defined = require('Cesium/Core/defined'); 7 | var Spherical = require('Cesium/Core/Spherical'); 8 | var TimeInterval = require('Cesium/Core/TimeInterval'); 9 | 10 | var CzmlDataSource = require('Cesium/DataSources/CzmlDataSource'); 11 | var DataSourceDisplay = require('Cesium/DataSources/DataSourceDisplay'); 12 | 13 | var ConicSensorGraphics = require('./conic/conic-sensor-graphics'); 14 | var ConicSensorVisualizer = require('./conic/conic-sensor-visualizer'); 15 | var CustomPatternSensorGraphics = require('./custom/custom-pattern-sensor-graphics'); 16 | var CustomPatternSensorVisualizer = require('./custom/custom-pattern-sensor-visualizer'); 17 | var RectangularSensorGraphics = require('./rectangular/rectangular-sensor-graphics'); 18 | var RectangularSensorVisualizer = require('./rectangular/rectangular-sensor-visualizer'); 19 | 20 | var processPacketData = CzmlDataSource.processPacketData; 21 | var processMaterialPacketData = CzmlDataSource.processMaterialPacketData; 22 | 23 | function processDirectionData(customPatternSensor, directions, interval, sourceUri, entityCollection) { 24 | var i; 25 | var len; 26 | var values = []; 27 | var unitSphericals = directions.unitSpherical; 28 | var sphericals = directions.spherical; 29 | var unitCartesians = directions.unitCartesian; 30 | var cartesians = directions.cartesian; 31 | 32 | if (defined(unitSphericals)) { 33 | for (i = 0, len = unitSphericals.length; i < len; i += 2) { 34 | values.push(new Spherical(unitSphericals[i], unitSphericals[i + 1])); 35 | } 36 | directions.array = values; 37 | } else if (defined(sphericals)) { 38 | for (i = 0, len = sphericals.length; i < len; i += 3) { 39 | values.push(new Spherical(sphericals[i], sphericals[i + 1], sphericals[i + 2])); 40 | } 41 | directions.array = values; 42 | } else if (defined(unitCartesians)) { 43 | for (i = 0, len = unitCartesians.length; i < len; i += 3) { 44 | var tmp = Spherical.fromCartesian3(new Cartesian3(unitCartesians[i], unitCartesians[i + 1], unitCartesians[i + 2])); 45 | Spherical.normalize(tmp, tmp); 46 | values.push(tmp); 47 | } 48 | directions.array = values; 49 | } else if (defined(cartesians)) { 50 | for (i = 0, len = cartesians.length; i < len; i += 3) { 51 | values.push(Spherical.fromCartesian3(new Cartesian3(cartesians[i], cartesians[i + 1], cartesians[i + 2]))); 52 | } 53 | directions.array = values; 54 | } 55 | processPacketData(Array, customPatternSensor, 'directions', directions, interval, sourceUri, entityCollection); 56 | } 57 | 58 | function processCommonSensorProperties(sensor, sensorData, interval, sourceUri, entityCollection) { 59 | processPacketData(Boolean, sensor, 'show', sensorData.show, interval, sourceUri, entityCollection); 60 | processPacketData(Number, sensor, 'radius', sensorData.radius, interval, sourceUri, entityCollection); 61 | processPacketData(Boolean, sensor, 'showIntersection', sensorData.showIntersection, interval, sourceUri, entityCollection); 62 | processPacketData(Color, sensor, 'intersectionColor', sensorData.intersectionColor, interval, sourceUri, entityCollection); 63 | processPacketData(Number, sensor, 'intersectionWidth', sensorData.intersectionWidth, interval, sourceUri, entityCollection); 64 | processMaterialPacketData(sensor, 'lateralSurfaceMaterial', sensorData.lateralSurfaceMaterial, interval, sourceUri, entityCollection); 65 | } 66 | 67 | var iso8601Scratch = { 68 | iso8601: undefined 69 | }; 70 | 71 | function processConicSensor(entity, packet, entityCollection, sourceUri) { 72 | var conicSensorData = packet.agi_conicSensor; 73 | if (!defined(conicSensorData)) { 74 | return; 75 | } 76 | 77 | var interval; 78 | var intervalString = conicSensorData.interval; 79 | if (defined(intervalString)) { 80 | iso8601Scratch.iso8601 = intervalString; 81 | interval = TimeInterval.fromIso8601(iso8601Scratch); 82 | } 83 | 84 | var conicSensor = entity.conicSensor; 85 | if (!defined(conicSensor)) { 86 | entity.addProperty('conicSensor'); 87 | conicSensor = new ConicSensorGraphics(); 88 | entity.conicSensor = conicSensor; 89 | } 90 | 91 | processCommonSensorProperties(conicSensor, conicSensorData, interval, sourceUri, entityCollection); 92 | processPacketData(Number, conicSensor, 'innerHalfAngle', conicSensorData.innerHalfAngle, interval, sourceUri, entityCollection); 93 | processPacketData(Number, conicSensor, 'outerHalfAngle', conicSensorData.outerHalfAngle, interval, sourceUri, entityCollection); 94 | processPacketData(Number, conicSensor, 'minimumClockAngle', conicSensorData.minimumClockAngle, interval, sourceUri, entityCollection); 95 | processPacketData(Number, conicSensor, 'maximumClockAngle', conicSensorData.maximumClockAngle, interval, sourceUri, entityCollection); 96 | } 97 | 98 | function processCustomPatternSensor(entity, packet, entityCollection, sourceUri) { 99 | var customPatternSensorData = packet.agi_customPatternSensor; 100 | if (!defined(customPatternSensorData)) { 101 | return; 102 | } 103 | 104 | var interval; 105 | var intervalString = customPatternSensorData.interval; 106 | if (defined(intervalString)) { 107 | iso8601Scratch.iso8601 = intervalString; 108 | interval = TimeInterval.fromIso8601(iso8601Scratch); 109 | } 110 | 111 | var customPatternSensor = entity.customPatternSensor; 112 | if (!defined(customPatternSensor)) { 113 | entity.addProperty('customPatternSensor'); 114 | customPatternSensor = new CustomPatternSensorGraphics(); 115 | entity.customPatternSensor = customPatternSensor; 116 | } 117 | 118 | processCommonSensorProperties(customPatternSensor, customPatternSensorData, interval, sourceUri, entityCollection); 119 | 120 | // The directions property is a special case value that can be an array of unitSpherical or unit Cartesians. 121 | // We pre-process this into Spherical instances and then process it like any other array. 122 | var directions = customPatternSensorData.directions; 123 | if (defined(directions)) { 124 | if (Array.isArray(directions)) { 125 | var length = directions.length; 126 | for (var i = 0; i < length; i++) { 127 | processDirectionData(customPatternSensor, directions[i], interval, sourceUri, entityCollection); 128 | } 129 | } else { 130 | processDirectionData(customPatternSensor, directions, interval, sourceUri, entityCollection); 131 | } 132 | } 133 | } 134 | 135 | function processRectangularSensor(entity, packet, entityCollection, sourceUri) { 136 | var rectangularSensorData = packet.agi_rectangularSensor; 137 | if (!defined(rectangularSensorData)) { 138 | return; 139 | } 140 | 141 | var interval; 142 | var intervalString = rectangularSensorData.interval; 143 | if (defined(intervalString)) { 144 | iso8601Scratch.iso8601 = intervalString; 145 | interval = TimeInterval.fromIso8601(iso8601Scratch); 146 | } 147 | 148 | var rectangularSensor = entity.rectangularSensor; 149 | if (!defined(rectangularSensor)) { 150 | entity.addProperty('rectangularSensor'); 151 | rectangularSensor = new RectangularSensorGraphics(); 152 | entity.rectangularSensor = rectangularSensor; 153 | } 154 | 155 | processCommonSensorProperties(rectangularSensor, rectangularSensorData, interval, sourceUri, entityCollection); 156 | processPacketData(Number, rectangularSensor, 'xHalfAngle', rectangularSensorData.xHalfAngle, interval, sourceUri, entityCollection); 157 | processPacketData(Number, rectangularSensor, 'yHalfAngle', rectangularSensorData.yHalfAngle, interval, sourceUri, entityCollection); 158 | } 159 | 160 | var initialized = false; 161 | return function initialize() { 162 | if (initialized) { 163 | return; 164 | } 165 | 166 | CzmlDataSource.updaters.push(processConicSensor, processCustomPatternSensor, processRectangularSensor); 167 | 168 | var originalDefaultVisualizersCallback = DataSourceDisplay.defaultVisualizersCallback; 169 | DataSourceDisplay.defaultVisualizersCallback = function(scene, entityCluster, dataSource) { 170 | var entities = dataSource.entities; 171 | var array = originalDefaultVisualizersCallback(scene, entityCluster, dataSource); 172 | return array.concat([ 173 | new ConicSensorVisualizer(scene, entities), 174 | new CustomPatternSensorVisualizer(scene, entities), 175 | new RectangularSensorVisualizer(scene, entities) 176 | ]); 177 | }; 178 | 179 | initialized = true; 180 | }; 181 | }); 182 | -------------------------------------------------------------------------------- /test/rectangular/rectangular-sensor-visualizer-webgl-spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-nested-callbacks */ 2 | define([ 3 | 'rectangular/rectangular-sensor-graphics', 4 | 'rectangular/rectangular-sensor-visualizer', 5 | 'Cesium/Core/Cartesian3', 6 | 'Cesium/Core/Color', 7 | 'Cesium/Core/JulianDate', 8 | 'Cesium/Core/Math', 9 | 'Cesium/Core/Matrix3', 10 | 'Cesium/Core/Matrix4', 11 | 'Cesium/Core/Quaternion', 12 | 'Cesium/Core/Spherical', 13 | 'Cesium/DataSources/ColorMaterialProperty', 14 | 'Cesium/DataSources/ConstantProperty', 15 | 'Cesium/DataSources/EntityCollection', 16 | '../util/create-scene', 17 | '../matchers/add-to-throw-developer-error-matcher' 18 | ], function( 19 | RectangularSensorGraphics, 20 | RectangularSensorVisualizer, 21 | Cartesian3, 22 | Color, 23 | JulianDate, 24 | CesiumMath, 25 | Matrix3, 26 | Matrix4, 27 | Quaternion, 28 | Spherical, 29 | ColorMaterialProperty, 30 | ConstantProperty, 31 | EntityCollection, 32 | createScene, 33 | addToThrowDeveloperErrorMatcher 34 | ) { 35 | 'use strict'; 36 | 37 | /* global describe, it, beforeAll, afterAll, beforeEach, afterEach, expect */ 38 | 39 | describe('rectangular sensor visualizer', function() { 40 | var scene; 41 | var visualizer; 42 | 43 | beforeAll(function() { 44 | scene = createScene(); 45 | }); 46 | 47 | afterAll(function() { 48 | scene.destroyForSpecs(); 49 | }); 50 | 51 | beforeEach(addToThrowDeveloperErrorMatcher); 52 | 53 | afterEach(function() { 54 | visualizer = visualizer && visualizer.destroy(); 55 | }); 56 | 57 | describe('constructor', function() { 58 | it('should throw if no scene is passed', function() { 59 | expect(function() { 60 | return new RectangularSensorVisualizer(); 61 | }).toThrowDeveloperError(); 62 | }); 63 | }); 64 | 65 | describe('update', function() { 66 | it('should throw if no time specified', function() { 67 | var entityCollection = new EntityCollection(); 68 | visualizer = new RectangularSensorVisualizer(scene, entityCollection); 69 | expect(function() { 70 | visualizer.update(); 71 | }).toThrowDeveloperError(); 72 | }); 73 | }); 74 | 75 | describe('isDestroy', function() { 76 | it('should return false until destroyed', function() { 77 | var entityCollection = new EntityCollection(); 78 | visualizer = new RectangularSensorVisualizer(scene, entityCollection); 79 | expect(visualizer.isDestroyed()).toEqual(false); 80 | visualizer.destroy(); 81 | expect(visualizer.isDestroyed()).toEqual(true); 82 | visualizer = undefined; 83 | }); 84 | }); 85 | 86 | it('should not create a primitive from an object with no rectangularSensor', function() { 87 | var entityCollection = new EntityCollection(); 88 | visualizer = new RectangularSensorVisualizer(scene, entityCollection); 89 | 90 | var testObject = entityCollection.getOrCreateEntity('test'); 91 | testObject.position = new ConstantProperty(new Cartesian3(1234, 5678, 9101112)); 92 | testObject.orientation = new ConstantProperty(new Quaternion(0, 0, 0, 1)); 93 | visualizer.update(JulianDate.now()); 94 | expect(scene.primitives.length).toEqual(0); 95 | }); 96 | 97 | it('should not create a primitive from an object with no position', function() { 98 | var entityCollection = new EntityCollection(); 99 | visualizer = new RectangularSensorVisualizer(scene, entityCollection); 100 | 101 | var testObject = entityCollection.getOrCreateEntity('test'); 102 | testObject.addProperty('rectangularSensor'); 103 | testObject.orientation = new ConstantProperty(new Quaternion(0, 0, 0, 1)); 104 | var rectangularSensor = new RectangularSensorGraphics(); 105 | rectangularSensor.xHalfAngle = new ConstantProperty(0.1); 106 | rectangularSensor.yHalfAngle = new ConstantProperty(0.2); 107 | testObject.rectangularSensor = rectangularSensor; 108 | visualizer.update(JulianDate.now()); 109 | expect(scene.primitives.length).toEqual(0); 110 | }); 111 | 112 | it('should not create a primitive from an object with no orientation', function() { 113 | var entityCollection = new EntityCollection(); 114 | visualizer = new RectangularSensorVisualizer(scene, entityCollection); 115 | 116 | var testObject = entityCollection.getOrCreateEntity('test'); 117 | testObject.addProperty('rectangularSensor'); 118 | testObject.position = new ConstantProperty(new Cartesian3(1234, 5678, 9101112)); 119 | var rectangularSensor = new RectangularSensorGraphics(); 120 | rectangularSensor.xHalfAngle = new ConstantProperty(0.1); 121 | rectangularSensor.yHalfAngle = new ConstantProperty(0.2); 122 | testObject.rectangularSensor = rectangularSensor; 123 | visualizer.update(JulianDate.now()); 124 | expect(scene.primitives.length).toEqual(0); 125 | }); 126 | 127 | it('should cause a sensor to be created and updated', function() { 128 | var time = JulianDate.now(); 129 | var entityCollection = new EntityCollection(); 130 | visualizer = new RectangularSensorVisualizer(scene, entityCollection); 131 | 132 | var testObject = entityCollection.getOrCreateEntity('test'); 133 | testObject.addProperty('rectangularSensor'); 134 | testObject.show = true; 135 | testObject.position = new ConstantProperty(new Cartesian3(1234, 5678, 9101112)); 136 | testObject.orientation = new ConstantProperty(new Quaternion(0, 0, Math.sin(CesiumMath.PI_OVER_FOUR), Math.cos(CesiumMath.PI_OVER_FOUR))); 137 | 138 | var rectangularSensor = new RectangularSensorGraphics(); 139 | rectangularSensor.xHalfAngle = new ConstantProperty(0.1); 140 | rectangularSensor.yHalfAngle = new ConstantProperty(0.2); 141 | rectangularSensor.intersectionColor = new ConstantProperty(new Color(0.1, 0.2, 0.3, 0.4)); 142 | rectangularSensor.intersectionWidth = new ConstantProperty(0.5); 143 | rectangularSensor.showIntersection = new ConstantProperty(true); 144 | rectangularSensor.radius = new ConstantProperty(123.5); 145 | rectangularSensor.show = new ConstantProperty(true); 146 | rectangularSensor.lateralSurfaceMaterial = new ColorMaterialProperty(Color.WHITE); 147 | testObject.rectangularSensor = rectangularSensor; 148 | visualizer.update(time); 149 | 150 | expect(scene.primitives.length).toEqual(1); 151 | var p = scene.primitives.get(0); 152 | expect(p.intersectionColor).toEqual(testObject.rectangularSensor.intersectionColor.getValue(time)); 153 | expect(p.intersectionWidth).toEqual(testObject.rectangularSensor.intersectionWidth.getValue(time)); 154 | expect(p.showIntersection).toEqual(testObject.rectangularSensor.showIntersection.getValue(time)); 155 | expect(p.radius).toEqual(testObject.rectangularSensor.radius.getValue(time)); 156 | expect(p.modelMatrix).toEqual(Matrix4.fromRotationTranslation(Matrix3.fromQuaternion(testObject.orientation.getValue(time)), testObject.position.getValue(time))); 157 | expect(p.show).toEqual(testObject.rectangularSensor.show.getValue(time)); 158 | expect(p.lateralSurfaceMaterial.uniforms).toEqual(testObject.rectangularSensor.lateralSurfaceMaterial.getValue(time)); 159 | 160 | testObject.show = false; 161 | visualizer.update(time); 162 | expect(p.show).toBe(false); 163 | 164 | testObject.show = true; 165 | visualizer.update(time); 166 | expect(p.show).toBe(true); 167 | 168 | rectangularSensor.show.setValue(false); 169 | visualizer.update(time); 170 | expect(p.show).toBe(false); 171 | }); 172 | 173 | it('should remove primitives', function() { 174 | var entityCollection = new EntityCollection(); 175 | visualizer = new RectangularSensorVisualizer(scene, entityCollection); 176 | 177 | var testObject = entityCollection.getOrCreateEntity('test'); 178 | testObject.addProperty('rectangularSensor'); 179 | testObject.position = new ConstantProperty(new Cartesian3(1234, 5678, 9101112)); 180 | testObject.orientation = new ConstantProperty(new Quaternion(0, 0, 0, 1)); 181 | var rectangularSensor = new RectangularSensorGraphics(); 182 | rectangularSensor.xHalfAngle = new ConstantProperty(0.1); 183 | rectangularSensor.yHalfAngle = new ConstantProperty(0.2); 184 | testObject.rectangularSensor = rectangularSensor; 185 | 186 | var time = JulianDate.now(); 187 | expect(scene.primitives.length).toEqual(0); 188 | visualizer.update(time); 189 | expect(scene.primitives.length).toEqual(1); 190 | expect(scene.primitives.get(0).show).toEqual(true); 191 | entityCollection.removeAll(); 192 | visualizer.update(time); 193 | expect(scene.primitives.length).toEqual(0); 194 | }); 195 | 196 | it('should set entity property', function() { 197 | var entityCollection = new EntityCollection(); 198 | visualizer = new RectangularSensorVisualizer(scene, entityCollection); 199 | 200 | var testObject = entityCollection.getOrCreateEntity('test'); 201 | testObject.addProperty('rectangularSensor'); 202 | testObject.position = new ConstantProperty(new Cartesian3(1234, 5678, 9101112)); 203 | testObject.orientation = new ConstantProperty(new Quaternion(0, 0, 0, 1)); 204 | var rectangularSensor = new RectangularSensorGraphics(); 205 | rectangularSensor.xHalfAngle = new ConstantProperty(0.1); 206 | rectangularSensor.yHalfAngle = new ConstantProperty(0.2); 207 | testObject.rectangularSensor = rectangularSensor; 208 | 209 | var time = JulianDate.now(); 210 | visualizer.update(time); 211 | expect(scene.primitives.get(0).id).toEqual(testObject); 212 | }); 213 | }); 214 | }, 'WebGL'); 215 | -------------------------------------------------------------------------------- /test/custom/custom-pattern-sensor-visualizer-webgl-spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-nested-callbacks */ 2 | define([ 3 | 'custom/custom-pattern-sensor-graphics', 4 | 'custom/custom-pattern-sensor-visualizer', 5 | 'Cesium/Core/Cartesian3', 6 | 'Cesium/Core/Color', 7 | 'Cesium/Core/JulianDate', 8 | 'Cesium/Core/Math', 9 | 'Cesium/Core/Matrix3', 10 | 'Cesium/Core/Matrix4', 11 | 'Cesium/Core/Quaternion', 12 | 'Cesium/Core/Spherical', 13 | 'Cesium/DataSources/ColorMaterialProperty', 14 | 'Cesium/DataSources/ConstantProperty', 15 | 'Cesium/DataSources/EntityCollection', 16 | '../util/create-scene', 17 | '../matchers/add-to-throw-developer-error-matcher' 18 | ], function( 19 | CustomPatternSensorGraphics, 20 | CustomPatternSensorVisualizer, 21 | Cartesian3, 22 | Color, 23 | JulianDate, 24 | CesiumMath, 25 | Matrix3, 26 | Matrix4, 27 | Quaternion, 28 | Spherical, 29 | ColorMaterialProperty, 30 | ConstantProperty, 31 | EntityCollection, 32 | createScene, 33 | addToThrowDeveloperErrorMatcher 34 | ) { 35 | 'use strict'; 36 | 37 | /* global describe, it, beforeAll, afterAll, beforeEach, afterEach, expect */ 38 | 39 | describe('custom pattern sensor visualizer', function() { 40 | var scene; 41 | var visualizer; 42 | 43 | beforeAll(function() { 44 | scene = createScene(); 45 | }); 46 | 47 | afterAll(function() { 48 | scene.destroyForSpecs(); 49 | }); 50 | 51 | beforeEach(addToThrowDeveloperErrorMatcher); 52 | 53 | afterEach(function() { 54 | visualizer = visualizer && visualizer.destroy(); 55 | }); 56 | 57 | describe('constructor', function() { 58 | it('should throw if no scene is passed', function() { 59 | expect(function() { 60 | return new CustomPatternSensorVisualizer(); 61 | }).toThrowDeveloperError(); 62 | }); 63 | }); 64 | 65 | describe('isDestroy', function() { 66 | it('should return false until destroyed', function() { 67 | var entityCollection = new EntityCollection(); 68 | visualizer = new CustomPatternSensorVisualizer(scene, entityCollection); 69 | expect(visualizer.isDestroyed()).toEqual(false); 70 | visualizer.destroy(); 71 | expect(visualizer.isDestroyed()).toEqual(true); 72 | visualizer = undefined; 73 | }); 74 | }); 75 | 76 | describe('update', function() { 77 | it('should throw if no time specified', function() { 78 | var entityCollection = new EntityCollection(); 79 | visualizer = new CustomPatternSensorVisualizer(scene, entityCollection); 80 | expect(function() { 81 | visualizer.update(); 82 | }).toThrowDeveloperError(); 83 | }); 84 | }); 85 | 86 | it('should not create a primitive from an object with no customPatternSensor', function() { 87 | var entityCollection = new EntityCollection(); 88 | visualizer = new CustomPatternSensorVisualizer(scene, entityCollection); 89 | 90 | var testObject = entityCollection.getOrCreateEntity('test'); 91 | testObject.position = new ConstantProperty(new Cartesian3(1234, 5678, 9101112)); 92 | testObject.orientation = new ConstantProperty(new Quaternion(0, 0, 0, 1)); 93 | visualizer.update(JulianDate.now()); 94 | expect(scene.primitives.length).toEqual(0); 95 | }); 96 | 97 | it('should not create a primitive from an object with no position', function() { 98 | var entityCollection = new EntityCollection(); 99 | visualizer = new CustomPatternSensorVisualizer(scene, entityCollection); 100 | 101 | var testObject = entityCollection.getOrCreateEntity('test'); 102 | testObject.addProperty('customPatternSensor'); 103 | testObject.orientation = new ConstantProperty(new Quaternion(0, 0, 0, 1)); 104 | var customPatternSensor = new CustomPatternSensorGraphics(); 105 | customPatternSensor.directions = new ConstantProperty([new Spherical(0, 0, 0), new Spherical(1, 0, 0), new Spherical(2, 0, 0), new Spherical(3, 0, 0)]); 106 | testObject.customPatternSensor = customPatternSensor; 107 | visualizer.update(JulianDate.now()); 108 | expect(scene.primitives.length).toEqual(0); 109 | }); 110 | 111 | it('should not create a primitive from object with no orientation', function() { 112 | var entityCollection = new EntityCollection(); 113 | visualizer = new CustomPatternSensorVisualizer(scene, entityCollection); 114 | 115 | var testObject = entityCollection.getOrCreateEntity('test'); 116 | testObject.addProperty('customPatternSensor'); 117 | testObject.position = new ConstantProperty(new Cartesian3(1234, 5678, 9101112)); 118 | var customPatternSensor = new CustomPatternSensorGraphics(); 119 | customPatternSensor.directions = new ConstantProperty([new Spherical(0, 0, 0), new Spherical(1, 0, 0), new Spherical(2, 0, 0), new Spherical(3, 0, 0)]); 120 | testObject.customPatternSensor = customPatternSensor; 121 | visualizer.update(JulianDate.now()); 122 | expect(scene.primitives.length).toEqual(0); 123 | }); 124 | 125 | it('should cause a CustomSensor to be created and updated', function() { 126 | var time = JulianDate.now(); 127 | var entityCollection = new EntityCollection(); 128 | visualizer = new CustomPatternSensorVisualizer(scene, entityCollection); 129 | 130 | var testObject = entityCollection.getOrCreateEntity('test'); 131 | testObject.addProperty('customPatternSensor'); 132 | testObject.show = true; 133 | testObject.position = new ConstantProperty(new Cartesian3(1234, 5678, 9101112)); 134 | testObject.orientation = new ConstantProperty(new Quaternion(0, 0, Math.sin(CesiumMath.PI_OVER_FOUR), Math.cos(CesiumMath.PI_OVER_FOUR))); 135 | 136 | var customPatternSensor = new CustomPatternSensorGraphics(); 137 | customPatternSensor.directions = new ConstantProperty([new Spherical(0, 0, 0), new Spherical(1, 0, 0), new Spherical(2, 0, 0), new Spherical(3, 0, 0)]); 138 | customPatternSensor.intersectionColor = new ConstantProperty(new Color(0.1, 0.2, 0.3, 0.4)); 139 | customPatternSensor.intersectionWidth = new ConstantProperty(0.5); 140 | customPatternSensor.showIntersection = new ConstantProperty(true); 141 | customPatternSensor.radius = new ConstantProperty(123.5); 142 | customPatternSensor.show = new ConstantProperty(true); 143 | customPatternSensor.lateralSurfaceMaterial = new ColorMaterialProperty(Color.WHITE); 144 | testObject.customPatternSensor = customPatternSensor; 145 | visualizer.update(time); 146 | 147 | expect(scene.primitives.length).toEqual(1); 148 | var p = scene.primitives.get(0); 149 | expect(p.intersectionColor).toEqual(testObject.customPatternSensor.intersectionColor.getValue(time)); 150 | expect(p.intersectionWidth).toEqual(testObject.customPatternSensor.intersectionWidth.getValue(time)); 151 | expect(p.showIntersection).toEqual(testObject.customPatternSensor.showIntersection.getValue(time)); 152 | expect(p.radius).toEqual(testObject.customPatternSensor.radius.getValue(time)); 153 | expect(p.modelMatrix).toEqual(Matrix4.fromRotationTranslation(Matrix3.fromQuaternion(testObject.orientation.getValue(time)), testObject.position.getValue(time))); 154 | expect(p.show).toEqual(testObject.customPatternSensor.show.getValue(time)); 155 | expect(p.lateralSurfaceMaterial.uniforms).toEqual(testObject.customPatternSensor.lateralSurfaceMaterial.getValue(time)); 156 | 157 | testObject.show = false; 158 | visualizer.update(time); 159 | expect(p.show).toBe(false); 160 | 161 | testObject.show = true; 162 | visualizer.update(time); 163 | expect(p.show).toBe(true); 164 | 165 | customPatternSensor.show.setValue(false); 166 | visualizer.update(time); 167 | expect(p.show).toBe(false); 168 | }); 169 | 170 | it('should remove primitives', function() { 171 | var entityCollection = new EntityCollection(); 172 | visualizer = new CustomPatternSensorVisualizer(scene, entityCollection); 173 | 174 | var testObject = entityCollection.getOrCreateEntity('test'); 175 | testObject.addProperty('customPatternSensor'); 176 | testObject.position = new ConstantProperty(new Cartesian3(1234, 5678, 9101112)); 177 | testObject.orientation = new ConstantProperty(new Quaternion(0, 0, 0, 1)); 178 | var customPatternSensor = new CustomPatternSensorGraphics(); 179 | customPatternSensor.directions = new ConstantProperty([new Spherical(0, 0, 0), new Spherical(1, 0, 0), new Spherical(2, 0, 0), new Spherical(3, 0, 0)]); 180 | testObject.customPatternSensor = customPatternSensor; 181 | 182 | var time = JulianDate.now(); 183 | expect(scene.primitives.length).toEqual(0); 184 | visualizer.update(time); 185 | expect(scene.primitives.length).toEqual(1); 186 | expect(scene.primitives.get(0).show).toEqual(true); 187 | entityCollection.removeAll(); 188 | visualizer.update(time); 189 | expect(scene.primitives.length).toEqual(0); 190 | }); 191 | 192 | it('should set entity property', function() { 193 | var entityCollection = new EntityCollection(); 194 | visualizer = new CustomPatternSensorVisualizer(scene, entityCollection); 195 | 196 | var testObject = entityCollection.getOrCreateEntity('test'); 197 | testObject.addProperty('customPatternSensor'); 198 | testObject.position = new ConstantProperty(new Cartesian3(1234, 5678, 9101112)); 199 | testObject.orientation = new ConstantProperty(new Quaternion(0, 0, 0, 1)); 200 | var customPatternSensor = new CustomPatternSensorGraphics(); 201 | customPatternSensor.directions = new ConstantProperty([new Spherical(0, 0, 0), new Spherical(1, 0, 0), new Spherical(2, 0, 0), new Spherical(3, 0, 0)]); 202 | testObject.customPatternSensor = customPatternSensor; 203 | 204 | var time = JulianDate.now(); 205 | visualizer.update(time); 206 | expect(scene.primitives.get(0).id).toEqual(testObject); 207 | }); 208 | }); 209 | }); 210 | -------------------------------------------------------------------------------- /lib/conic/conic-sensor-visualizer.js: -------------------------------------------------------------------------------- 1 | define(function(require) { 2 | 'use strict'; 3 | 4 | var AssociativeArray = require('Cesium/Core/AssociativeArray'); 5 | var Cartesian3 = require('Cesium/Core/Cartesian3'); 6 | var Color = require('Cesium/Core/Color'); 7 | var defined = require('Cesium/Core/defined'); 8 | var destroyObject = require('Cesium/Core/destroyObject'); 9 | var DeveloperError = require('Cesium/Core/DeveloperError'); 10 | var CesiumMath = require('Cesium/Core/Math'); 11 | var Matrix3 = require('Cesium/Core/Matrix3'); 12 | var Matrix4 = require('Cesium/Core/Matrix4'); 13 | var Quaternion = require('Cesium/Core/Quaternion'); 14 | var Spherical = require('Cesium/Core/Spherical'); 15 | var MaterialProperty = require('Cesium/DataSources/MaterialProperty'); 16 | var Property = require('Cesium/DataSources/Property'); 17 | 18 | var CustomSensorVolume = require('../custom/custom-sensor-volume'); 19 | var removePrimitive = require('../util/remove-primitive'); 20 | 21 | var defaultIntersectionColor = Color.WHITE; 22 | var defaultIntersectionWidth = 1.0; 23 | var defaultRadius = Number.POSITIVE_INFINITY; 24 | 25 | var matrix3Scratch = new Matrix3(); 26 | var cachedPosition = new Cartesian3(); 27 | var cachedOrientation = new Quaternion(); 28 | 29 | function assignSpherical(index, array, clock, cone) { 30 | var spherical = array[index]; 31 | if (!defined(spherical)) { 32 | spherical = new Spherical(); 33 | array[index] = spherical; 34 | } 35 | spherical.clock = clock; 36 | spherical.cone = cone; 37 | spherical.magnitude = 1.0; 38 | } 39 | 40 | function computeDirections(primitive, minimumClockAngle, maximumClockAngle, innerHalfAngle, outerHalfAngle) { 41 | var directions = primitive.directions; 42 | var angle; 43 | var i = 0; 44 | var angleStep = CesiumMath.toRadians(2.0); 45 | if (minimumClockAngle === 0.0 && maximumClockAngle === CesiumMath.TWO_PI) { 46 | // No clock angle limits, so this is just a circle. 47 | // There might be a hole but we're ignoring it for now. 48 | for (angle = 0.0; angle < CesiumMath.TWO_PI; angle += angleStep) { 49 | assignSpherical(i++, directions, angle, outerHalfAngle); 50 | } 51 | } else { 52 | // There are clock angle limits. 53 | for (angle = minimumClockAngle; angle < maximumClockAngle; angle += angleStep) { 54 | assignSpherical(i++, directions, angle, outerHalfAngle); 55 | } 56 | assignSpherical(i++, directions, maximumClockAngle, outerHalfAngle); 57 | if (innerHalfAngle) { 58 | for (angle = maximumClockAngle; angle > minimumClockAngle; angle -= angleStep) { 59 | assignSpherical(i++, directions, angle, innerHalfAngle); 60 | } 61 | assignSpherical(i++, directions, minimumClockAngle, innerHalfAngle); 62 | } else { 63 | assignSpherical(i++, directions, maximumClockAngle, 0.0); 64 | } 65 | } 66 | directions.length = i; 67 | primitive.directions = directions; 68 | } 69 | 70 | /** 71 | * A {@link Visualizer} which maps {@link Entity#conicSensor} to a {@link ConicSensor}. 72 | * @alias ConicSensorVisualizer 73 | * @constructor 74 | * 75 | * @param {Scene} scene The scene the primitives will be rendered in. 76 | * @param {EntityCollection} entityCollection The entityCollection to visualize. 77 | */ 78 | var ConicSensorVisualizer = function(scene, entityCollection) { 79 | // >>includeStart('debug', pragmas.debug); 80 | if (!defined(scene)) { 81 | throw new DeveloperError('scene is required.'); 82 | } 83 | if (!defined(entityCollection)) { 84 | throw new DeveloperError('entityCollection is required.'); 85 | } 86 | // >>includeEnd('debug'); 87 | 88 | entityCollection.collectionChanged.addEventListener(ConicSensorVisualizer.prototype._onCollectionChanged, this); 89 | 90 | this._scene = scene; 91 | this._primitives = scene.primitives; 92 | this._entityCollection = entityCollection; 93 | this._hash = {}; 94 | this._entitiesToVisualize = new AssociativeArray(); 95 | 96 | this._onCollectionChanged(entityCollection, entityCollection.values, [], []); 97 | }; 98 | 99 | /** 100 | * Updates the primitives created by this visualizer to match their 101 | * Entity counterpart at the given time. 102 | * 103 | * @param {JulianDate} time The time to update to. 104 | * @returns {Boolean} This function always returns true. 105 | */ 106 | ConicSensorVisualizer.prototype.update = function(time) { 107 | // >>includeStart('debug', pragmas.debug); 108 | if (!defined(time)) { 109 | throw new DeveloperError('time is required.'); 110 | } 111 | // >>includeEnd('debug'); 112 | 113 | var entities = this._entitiesToVisualize.values; 114 | var hash = this._hash; 115 | var primitives = this._primitives; 116 | 117 | for (var i = 0, len = entities.length; i < len; i++) { 118 | var entity = entities[i]; 119 | var conicSensorGraphics = entity._conicSensor; 120 | 121 | var position; 122 | var orientation; 123 | var data = hash[entity.id]; 124 | var show = entity.isShowing && entity.isAvailable(time) && Property.getValueOrDefault(conicSensorGraphics._show, time, true); 125 | 126 | if (show) { 127 | position = Property.getValueOrUndefined(entity._position, time, cachedPosition); 128 | orientation = Property.getValueOrUndefined(entity._orientation, time, cachedOrientation); 129 | show = defined(position) && defined(orientation); 130 | } 131 | 132 | if (!show) { 133 | // don't bother creating or updating anything else 134 | if (defined(data)) { 135 | data.primitive.show = false; 136 | } 137 | continue; 138 | } 139 | 140 | var primitive = defined(data) ? data.primitive : undefined; 141 | if (!defined(primitive)) { 142 | primitive = new CustomSensorVolume(); 143 | primitive.id = entity; 144 | primitives.add(primitive); 145 | 146 | data = { 147 | primitive: primitive, 148 | position: undefined, 149 | orientation: undefined, 150 | minimumClockAngle: undefined, 151 | maximumClockAngle: undefined, 152 | innerHalfAngle: undefined, 153 | outerHalfAngle: undefined 154 | }; 155 | hash[entity.id] = data; 156 | } 157 | 158 | if (!Cartesian3.equals(position, data.position) || !Quaternion.equals(orientation, data.orientation)) { 159 | Matrix4.fromRotationTranslation(Matrix3.fromQuaternion(orientation, matrix3Scratch), position, primitive.modelMatrix); 160 | data.position = Cartesian3.clone(position, data.position); 161 | data.orientation = Quaternion.clone(orientation, data.orientation); 162 | } 163 | 164 | primitive.show = true; 165 | var minimumClockAngle = Property.getValueOrDefault(conicSensorGraphics._minimumClockAngle, time, 0); 166 | var maximumClockAngle = Property.getValueOrDefault(conicSensorGraphics._maximumClockAngle, time, CesiumMath.TWO_PI); 167 | var innerHalfAngle = Property.getValueOrDefault(conicSensorGraphics._innerHalfAngle, time, 0); 168 | var outerHalfAngle = Property.getValueOrDefault(conicSensorGraphics._outerHalfAngle, time, Math.PI); 169 | 170 | if (minimumClockAngle !== data.minimumClockAngle || 171 | maximumClockAngle !== data.maximumClockAngle || 172 | innerHalfAngle !== data.innerHalfAngle || 173 | outerHalfAngle !== data.outerHalfAngle 174 | ) { 175 | computeDirections(primitive, minimumClockAngle, maximumClockAngle, innerHalfAngle, outerHalfAngle); 176 | data.innerHalfAngle = innerHalfAngle; 177 | data.maximumClockAngle = maximumClockAngle; 178 | data.outerHalfAngle = outerHalfAngle; 179 | data.minimumClockAngle = minimumClockAngle; 180 | } 181 | 182 | primitive.radius = Property.getValueOrDefault(conicSensorGraphics._radius, time, defaultRadius); 183 | primitive.lateralSurfaceMaterial = MaterialProperty.getValue(time, conicSensorGraphics._lateralSurfaceMaterial, primitive.lateralSurfaceMaterial); 184 | primitive.intersectionColor = Property.getValueOrClonedDefault(conicSensorGraphics._intersectionColor, time, defaultIntersectionColor, primitive.intersectionColor); 185 | primitive.intersectionWidth = Property.getValueOrDefault(conicSensorGraphics._intersectionWidth, time, defaultIntersectionWidth); 186 | } 187 | return true; 188 | }; 189 | 190 | /** 191 | * Returns true if this object was destroyed; otherwise, false. 192 | * 193 | * @returns {Boolean} True if this object was destroyed; otherwise, false. 194 | */ 195 | ConicSensorVisualizer.prototype.isDestroyed = function() { 196 | return false; 197 | }; 198 | 199 | /** 200 | * Removes and destroys all primitives created by this instance. 201 | */ 202 | ConicSensorVisualizer.prototype.destroy = function() { 203 | var entities = this._entitiesToVisualize.values; 204 | var hash = this._hash; 205 | var primitives = this._primitives; 206 | for (var i = entities.length - 1; i > -1; i--) { 207 | removePrimitive(entities[i], hash, primitives); 208 | } 209 | return destroyObject(this); 210 | }; 211 | 212 | /** 213 | * @private 214 | */ 215 | ConicSensorVisualizer.prototype._onCollectionChanged = function(entityCollection, added, removed, changed) { 216 | var i; 217 | var entity; 218 | var entities = this._entitiesToVisualize; 219 | var hash = this._hash; 220 | var primitives = this._primitives; 221 | 222 | for (i = added.length - 1; i > -1; i--) { 223 | entity = added[i]; 224 | if (defined(entity._conicSensor) && defined(entity._position) && defined(entity._orientation)) { 225 | entities.set(entity.id, entity); 226 | } 227 | } 228 | 229 | for (i = changed.length - 1; i > -1; i--) { 230 | entity = changed[i]; 231 | if (defined(entity._conicSensor) && defined(entity._position) && defined(entity._orientation)) { 232 | entities.set(entity.id, entity); 233 | } else { 234 | removePrimitive(entity, hash, primitives); 235 | entities.remove(entity.id); 236 | } 237 | } 238 | 239 | for (i = removed.length - 1; i > -1; i--) { 240 | entity = removed[i]; 241 | removePrimitive(entity, hash, primitives); 242 | entities.remove(entity.id); 243 | } 244 | }; 245 | 246 | return ConicSensorVisualizer; 247 | }); 248 | -------------------------------------------------------------------------------- /test/conic/conic-sensor-visualizer-webgl-spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-nested-callbacks */ 2 | define([ 3 | 'conic/conic-sensor-graphics', 4 | 'conic/conic-sensor-visualizer', 5 | 'Cesium/Core/Cartesian3', 6 | 'Cesium/Core/Color', 7 | 'Cesium/Core/JulianDate', 8 | 'Cesium/Core/Math', 9 | 'Cesium/Core/Matrix3', 10 | 'Cesium/Core/Matrix4', 11 | 'Cesium/Core/Quaternion', 12 | 'Cesium/DataSources/ColorMaterialProperty', 13 | 'Cesium/DataSources/ConstantProperty', 14 | 'Cesium/DataSources/EntityCollection', 15 | '../util/create-scene', 16 | '../matchers/add-to-throw-developer-error-matcher' 17 | ], function( 18 | ConicSensorGraphics, 19 | ConicSensorVisualizer, 20 | Cartesian3, 21 | Color, 22 | JulianDate, 23 | CesiumMath, 24 | Matrix3, 25 | Matrix4, 26 | Quaternion, 27 | ColorMaterialProperty, 28 | ConstantProperty, 29 | EntityCollection, 30 | createScene, 31 | addToThrowDeveloperErrorMatcher 32 | ) { 33 | 'use strict'; 34 | 35 | /* global describe, it, beforeAll, afterAll, beforeEach, afterEach, expect */ 36 | 37 | describe('conic sensor visualizer', function() { 38 | var scene; 39 | var visualizer; 40 | 41 | beforeAll(function() { 42 | scene = createScene(); 43 | }); 44 | 45 | afterAll(function() { 46 | scene.destroyForSpecs(); 47 | }); 48 | 49 | beforeEach(addToThrowDeveloperErrorMatcher); 50 | 51 | afterEach(function() { 52 | visualizer = visualizer && visualizer.destroy(); 53 | }); 54 | 55 | describe('constructor', function() { 56 | it('should throw if no scene is passed', function() { 57 | expect(function() { 58 | return new ConicSensorVisualizer(); 59 | }).toThrowDeveloperError(); 60 | }); 61 | }); 62 | 63 | describe('update', function() { 64 | it('should throw if no time specified', function() { 65 | var entityCollection = new EntityCollection(); 66 | visualizer = new ConicSensorVisualizer(scene, entityCollection); 67 | expect(function() { 68 | visualizer.update(); 69 | }).toThrowDeveloperError(); 70 | }); 71 | }); 72 | 73 | describe('isDestroy', function() { 74 | it('should return false until destroyed', function() { 75 | var entityCollection = new EntityCollection(); 76 | visualizer = new ConicSensorVisualizer(scene, entityCollection); 77 | expect(visualizer.isDestroyed()).toEqual(false); 78 | visualizer.destroy(); 79 | expect(visualizer.isDestroyed()).toEqual(true); 80 | visualizer = undefined; 81 | }); 82 | }); 83 | 84 | it('should not create a primitive from an object with no conicSensor', function() { 85 | var entityCollection = new EntityCollection(); 86 | visualizer = new ConicSensorVisualizer(scene, entityCollection); 87 | 88 | var testObject = entityCollection.getOrCreateEntity('test'); 89 | testObject.position = new ConstantProperty(new Cartesian3(1234, 5678, 9101112)); 90 | testObject.orientation = new ConstantProperty(new Quaternion(0, 0, 0, 1)); 91 | visualizer.update(JulianDate.now()); 92 | expect(scene.primitives.length).toEqual(0); 93 | }); 94 | 95 | it('should not create a primitive from an object with no position', function() { 96 | var entityCollection = new EntityCollection(); 97 | visualizer = new ConicSensorVisualizer(scene, entityCollection); 98 | 99 | var testObject = entityCollection.getOrCreateEntity('test'); 100 | testObject.addProperty('conicSensor'); 101 | testObject.orientation = new ConstantProperty(new Quaternion(0, 0, 0, 1)); 102 | var conicSensor = new ConicSensorGraphics(); 103 | conicSensor.maximumClockAngle = new ConstantProperty(1); 104 | conicSensor.outerHalfAngle = new ConstantProperty(1); 105 | testObject.conicSensor = conicSensor; 106 | visualizer.update(JulianDate.now()); 107 | expect(scene.primitives.length).toEqual(0); 108 | }); 109 | 110 | it('should not create a primitive from an object with no orientation', function() { 111 | var entityCollection = new EntityCollection(); 112 | visualizer = new ConicSensorVisualizer(scene, entityCollection); 113 | 114 | var testObject = entityCollection.getOrCreateEntity('test'); 115 | testObject.addProperty('conicSensor'); 116 | testObject.position = new ConstantProperty(new Cartesian3(1234, 5678, 9101112)); 117 | var conicSensor = new ConicSensorGraphics(); 118 | conicSensor.maximumClockAngle = new ConstantProperty(1); 119 | conicSensor.outerHalfAngle = new ConstantProperty(1); 120 | testObject.conicSensor = conicSensor; 121 | visualizer.update(JulianDate.now()); 122 | expect(scene.primitives.length).toEqual(0); 123 | }); 124 | 125 | it('should cause a ComplexConicSensor to be created and updated', function() { 126 | var time = JulianDate.now(); 127 | var entityCollection = new EntityCollection(); 128 | visualizer = new ConicSensorVisualizer(scene, entityCollection); 129 | 130 | var testObject = entityCollection.getOrCreateEntity('test'); 131 | testObject.addProperty('conicSensor'); 132 | testObject.show = true; 133 | testObject.position = new ConstantProperty(new Cartesian3(1234, 5678, 9101112)); 134 | testObject.orientation = new ConstantProperty(new Quaternion(0, 0, Math.sin(CesiumMath.PI_OVER_FOUR), Math.cos(CesiumMath.PI_OVER_FOUR))); 135 | 136 | var conicSensor = new ConicSensorGraphics(); 137 | conicSensor.minimumClockAngle = new ConstantProperty(0.1); 138 | conicSensor.maximumClockAngle = new ConstantProperty(0.2); 139 | conicSensor.innerHalfAngle = new ConstantProperty(0.3); 140 | conicSensor.outerHalfAngle = new ConstantProperty(0.4); 141 | conicSensor.intersectionColor = new ConstantProperty(new Color(0.1, 0.2, 0.3, 0.4)); 142 | conicSensor.intersectionWidth = new ConstantProperty(0.5); 143 | conicSensor.showIntersection = new ConstantProperty(true); 144 | conicSensor.radius = new ConstantProperty(123.5); 145 | conicSensor.show = new ConstantProperty(true); 146 | conicSensor.lateralSurfaceMaterial = new ColorMaterialProperty(Color.WHITE); 147 | 148 | testObject.conicSensor = conicSensor; 149 | 150 | visualizer.update(time); 151 | expect(scene.primitives.length).toEqual(1); 152 | 153 | var c = scene.primitives.get(0); 154 | expect(c.directions.length).toBeGreaterThan(0); 155 | expect(c.intersectionColor).toEqual(testObject.conicSensor.intersectionColor.getValue(time)); 156 | expect(c.intersectionWidth).toEqual(testObject.conicSensor.intersectionWidth.getValue(time)); 157 | expect(c.showIntersection).toEqual(testObject.conicSensor.showIntersection.getValue(time)); 158 | expect(c.radius).toEqual(testObject.conicSensor.radius.getValue(time)); 159 | expect(c.modelMatrix).toEqual(Matrix4.fromRotationTranslation(Matrix3.fromQuaternion(testObject.orientation.getValue(time)), testObject.position.getValue(time))); 160 | expect(c.show).toEqual(testObject.conicSensor.show.getValue(time)); 161 | expect(c.lateralSurfaceMaterial.uniforms).toEqual(testObject.conicSensor.lateralSurfaceMaterial.getValue(time)); 162 | 163 | testObject.show = false; 164 | visualizer.update(time); 165 | expect(c.show).toBe(false); 166 | 167 | testObject.show = true; 168 | visualizer.update(time); 169 | expect(c.show).toBe(true); 170 | 171 | conicSensor.show.setValue(false); 172 | visualizer.update(time); 173 | expect(c.show).toBe(false); 174 | }); 175 | 176 | it('should set IntersectionColor correctly with multiple conicSensors', function() { 177 | var time = JulianDate.now(); 178 | var entityCollection = new EntityCollection(); 179 | visualizer = new ConicSensorVisualizer(scene, entityCollection); 180 | 181 | var testObject = entityCollection.getOrCreateEntity('test'); 182 | testObject.addProperty('conicSensor'); 183 | testObject.position = new ConstantProperty(new Cartesian3(1234, 5678, 9101112)); 184 | testObject.orientation = new ConstantProperty(new Quaternion(0, 0, 0, 1)); 185 | 186 | var testObject2 = entityCollection.getOrCreateEntity('test2'); 187 | testObject2.addProperty('conicSensor'); 188 | testObject2.position = new ConstantProperty(new Cartesian3(1234, 5678, 9101112)); 189 | testObject2.orientation = new ConstantProperty(new Quaternion(0, 0, 0, 1)); 190 | 191 | var conicSensor = new ConicSensorGraphics(); 192 | conicSensor.intersectionColor = new ConstantProperty(new Color(0.1, 0.2, 0.3, 0.4)); 193 | testObject.conicSensor = conicSensor; 194 | 195 | var conicSensor2 = new ConicSensorGraphics(); 196 | conicSensor2.intersectionColor = new ConstantProperty(new Color(0.4, 0.3, 0.2, 0.1)); 197 | testObject2.conicSensor = conicSensor2; 198 | 199 | visualizer.update(time); 200 | 201 | expect(scene.primitives.length).toEqual(2); 202 | var c = scene.primitives.get(0); 203 | expect(c.intersectionColor).toEqual(testObject.conicSensor.intersectionColor.getValue(time)); 204 | 205 | c = scene.primitives.get(1); 206 | expect(c.intersectionColor).toEqual(testObject2.conicSensor.intersectionColor.getValue(time)); 207 | }); 208 | 209 | it('should create a ComplexConicSensor with CZML defaults from an empty conicSensor', function() { 210 | var time = JulianDate.now(); 211 | var entityCollection = new EntityCollection(); 212 | visualizer = new ConicSensorVisualizer(scene, entityCollection); 213 | 214 | var testObject = entityCollection.getOrCreateEntity('test'); 215 | testObject.addProperty('conicSensor'); 216 | testObject.position = new ConstantProperty(new Cartesian3(1234, 5678, 9101112)); 217 | testObject.orientation = new ConstantProperty(new Quaternion(0, 0, 0, 1)); 218 | 219 | testObject.conicSensor = new ConicSensorGraphics(); 220 | visualizer.update(time); 221 | 222 | expect(scene.primitives.length).toEqual(1); 223 | var c = scene.primitives.get(0); 224 | expect(c.directions.length).toBeGreaterThan(0); 225 | expect(isFinite(c.radius)).toEqual(false); 226 | expect(c.show).toEqual(true); 227 | }); 228 | 229 | it('should remove primitives', function() { 230 | var entityCollection = new EntityCollection(); 231 | visualizer = new ConicSensorVisualizer(scene, entityCollection); 232 | 233 | var testObject = entityCollection.getOrCreateEntity('test'); 234 | testObject.addProperty('conicSensor'); 235 | testObject.position = new ConstantProperty(new Cartesian3(1234, 5678, 9101112)); 236 | testObject.orientation = new ConstantProperty(new Quaternion(0, 0, 0, 1)); 237 | var conicSensor = new ConicSensorGraphics(); 238 | conicSensor.maximumClockAngle = new ConstantProperty(1); 239 | conicSensor.outerHalfAngle = new ConstantProperty(1); 240 | testObject.conicSensor = conicSensor; 241 | 242 | var time = JulianDate.now(); 243 | expect(scene.primitives.length).toEqual(0); 244 | visualizer.update(time); 245 | expect(scene.primitives.length).toEqual(1); 246 | expect(scene.primitives.get(0).show).toEqual(true); 247 | entityCollection.removeAll(); 248 | visualizer.update(time); 249 | expect(scene.primitives.length).toEqual(0); 250 | }); 251 | 252 | it('should set entity property', function() { 253 | var entityCollection = new EntityCollection(); 254 | visualizer = new ConicSensorVisualizer(scene, entityCollection); 255 | 256 | var testObject = entityCollection.getOrCreateEntity('test'); 257 | testObject.addProperty('conicSensor'); 258 | testObject.position = new ConstantProperty(new Cartesian3(1234, 5678, 9101112)); 259 | testObject.orientation = new ConstantProperty(new Quaternion(0, 0, 0, 1)); 260 | var conicSensor = new ConicSensorGraphics(); 261 | conicSensor.maximumClockAngle = new ConstantProperty(1); 262 | conicSensor.outerHalfAngle = new ConstantProperty(1); 263 | testObject.conicSensor = conicSensor; 264 | 265 | var time = JulianDate.now(); 266 | visualizer.update(time); 267 | expect(scene.primitives.get(0).id).toEqual(testObject); 268 | }); 269 | }); 270 | }); 271 | -------------------------------------------------------------------------------- /lib/custom/custom-sensor-volume.js: -------------------------------------------------------------------------------- 1 | define(function(require) { 2 | 'use strict'; 3 | 4 | var BoundingSphere = require('Cesium/Core/BoundingSphere'); 5 | var Cartesian3 = require('Cesium/Core/Cartesian3'); 6 | var Color = require('Cesium/Core/Color'); 7 | var combine = require('Cesium/Core/combine'); 8 | var ComponentDatatype = require('Cesium/Core/ComponentDatatype'); 9 | var defaultValue = require('Cesium/Core/defaultValue'); 10 | var defined = require('Cesium/Core/defined'); 11 | var defineProperties = require('Cesium/Core/defineProperties'); 12 | var destroyObject = require('Cesium/Core/destroyObject'); 13 | var DeveloperError = require('Cesium/Core/DeveloperError'); 14 | var Matrix4 = require('Cesium/Core/Matrix4'); 15 | var PrimitiveType = require('Cesium/Core/PrimitiveType'); 16 | var Buffer = require('Cesium/Renderer/Buffer'); 17 | var BufferUsage = require('Cesium/Renderer/BufferUsage'); 18 | var DrawCommand = require('Cesium/Renderer/DrawCommand'); 19 | var Pass = require('Cesium/Renderer/Pass'); 20 | var RenderState = require('Cesium/Renderer/RenderState'); 21 | var ShaderProgram = require('Cesium/Renderer/ShaderProgram'); 22 | var ShaderSource = require('Cesium/Renderer/ShaderSource'); 23 | var VertexArray = require('Cesium/Renderer/VertexArray'); 24 | var BlendingState = require('Cesium/Scene/BlendingState'); 25 | var CullFace = require('Cesium/Scene/CullFace'); 26 | var Material = require('Cesium/Scene/Material'); 27 | var SceneMode = require('Cesium/Scene/SceneMode'); 28 | 29 | var CustomSensorVolumeFS = require('text!./custom-sensor-volume-fs.glsl'); 30 | var CustomSensorVolumeVS = require('text!./custom-sensor-volume-vs.glsl'); 31 | var SensorVolume = require('text!../sensor-volume.glsl'); 32 | 33 | var attributeLocations = { 34 | position: 0, 35 | normal: 1 36 | }; 37 | 38 | var FAR = 5906376272000.0; // distance from the Sun to Pluto in meters. 39 | 40 | /** 41 | * DOC_TBA 42 | * 43 | * @alias CustomSensorVolume 44 | * @constructor 45 | */ 46 | var CustomSensorVolume = function(options) { 47 | options = defaultValue(options, defaultValue.EMPTY_OBJECT); 48 | 49 | this._pickId = undefined; 50 | this._pickPrimitive = defaultValue(options._pickPrimitive, this); 51 | 52 | this._frontFaceColorCommand = new DrawCommand(); 53 | this._backFaceColorCommand = new DrawCommand(); 54 | this._pickCommand = new DrawCommand(); 55 | 56 | this._boundingSphere = new BoundingSphere(); 57 | this._boundingSphereWC = new BoundingSphere(); 58 | 59 | this._frontFaceColorCommand.primitiveType = PrimitiveType.TRIANGLES; 60 | this._frontFaceColorCommand.boundingVolume = this._boundingSphereWC; 61 | this._frontFaceColorCommand.owner = this; 62 | 63 | this._backFaceColorCommand.primitiveType = this._frontFaceColorCommand.primitiveType; 64 | this._backFaceColorCommand.boundingVolume = this._frontFaceColorCommand.boundingVolume; 65 | this._backFaceColorCommand.owner = this; 66 | 67 | this._pickCommand.primitiveType = this._frontFaceColorCommand.primitiveType; 68 | this._pickCommand.boundingVolume = this._frontFaceColorCommand.boundingVolume; 69 | this._pickCommand.owner = this; 70 | 71 | /** 72 | * true if this sensor will be shown; otherwise, false 73 | * 74 | * @type {Boolean} 75 | * @default true 76 | */ 77 | this.show = defaultValue(options.show, true); 78 | 79 | /** 80 | * When true, a polyline is shown where the sensor outline intersections the globe. 81 | * 82 | * @type {Boolean} 83 | * 84 | * @default true 85 | * 86 | * @see CustomSensorVolume#intersectionColor 87 | */ 88 | this.showIntersection = defaultValue(options.showIntersection, true); 89 | 90 | /** 91 | *

92 | * Determines if a sensor intersecting the ellipsoid is drawn through the ellipsoid and potentially out 93 | * to the other side, or if the part of the sensor intersecting the ellipsoid stops at the ellipsoid. 94 | *

95 | * 96 | * @type {Boolean} 97 | * @default false 98 | */ 99 | this.showThroughEllipsoid = defaultValue(options.showThroughEllipsoid, false); 100 | this._showThroughEllipsoid = this.showThroughEllipsoid; 101 | 102 | /** 103 | * The 4x4 transformation matrix that transforms this sensor from model to world coordinates. In it's model 104 | * coordinates, the sensor's principal direction is along the positive z-axis. The clock angle, sometimes 105 | * called azimuth, is the angle in the sensor's X-Y plane measured from the positive X-axis toward the positive 106 | * Y-axis. The cone angle, sometimes called elevation, is the angle out of the X-Y plane along the positive Z-axis. 107 | *

108 | *
109 | *
110 | * Model coordinate system for a custom sensor 111 | *
112 | * 113 | * @type {Matrix4} 114 | * @default {@link Matrix4.IDENTITY} 115 | * 116 | * @example 117 | * // The sensor's vertex is located on the surface at -75.59777 degrees longitude and 40.03883 degrees latitude. 118 | * // The sensor's opens upward, along the surface normal. 119 | * var center = Cesium.Cartesian3.fromDegrees(-75.59777, 40.03883); 120 | * sensor.modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(center); 121 | */ 122 | this.modelMatrix = Matrix4.clone(defaultValue(options.modelMatrix, Matrix4.IDENTITY)); 123 | this._modelMatrix = new Matrix4(); 124 | 125 | /** 126 | * DOC_TBA 127 | * 128 | * @type {Number} 129 | * @default {@link Number.POSITIVE_INFINITY} 130 | */ 131 | this.radius = defaultValue(options.radius, Number.POSITIVE_INFINITY); 132 | 133 | this._directions = undefined; 134 | this._directionsDirty = false; 135 | this.directions = defined(options.directions) ? options.directions : []; 136 | 137 | /** 138 | * The surface appearance of the sensor. This can be one of several built-in {@link Material} objects or a custom material, scripted with 139 | * {@link https://github.com/AnalyticalGraphicsInc/cesium/wiki/Fabric|Fabric}. 140 | *

141 | * The default material is Material.ColorType. 142 | *

143 | * 144 | * @type {Material} 145 | * @default Material.fromType(Material.ColorType) 146 | * 147 | * @see {@link https://github.com/AnalyticalGraphicsInc/cesium/wiki/Fabric|Fabric} 148 | * 149 | * @example 150 | * // 1. Change the color of the default material to yellow 151 | * sensor.lateralSurfaceMaterial.uniforms.color = new Cesium.Color(1.0, 1.0, 0.0, 1.0); 152 | * 153 | * // 2. Change material to horizontal stripes 154 | * sensor.lateralSurfaceMaterial = Cesium.Material.fromType(Material.StripeType); 155 | */ 156 | this.lateralSurfaceMaterial = defined(options.lateralSurfaceMaterial) ? options.lateralSurfaceMaterial : Material.fromType(Material.ColorType); 157 | this._lateralSurfaceMaterial = undefined; 158 | this._translucent = undefined; 159 | 160 | /** 161 | * The color of the polyline where the sensor outline intersects the globe. The default is {@link Color.WHITE}. 162 | * 163 | * @type {Color} 164 | * @default {@link Color.WHITE} 165 | * 166 | * @see CustomSensorVolume#showIntersection 167 | */ 168 | this.intersectionColor = Color.clone(defaultValue(options.intersectionColor, Color.WHITE)); 169 | 170 | /** 171 | * The approximate pixel width of the polyline where the sensor outline intersects the globe. The default is 5.0. 172 | * 173 | * @type {Number} 174 | * @default 5.0 175 | * 176 | * @see CustomSensorVolume#showIntersection 177 | */ 178 | this.intersectionWidth = defaultValue(options.intersectionWidth, 5.0); 179 | 180 | /** 181 | * User-defined object returned when the sensors is picked. 182 | * 183 | * @type Object 184 | * 185 | * @default undefined 186 | * 187 | * @see Scene#pick 188 | */ 189 | this.id = options.id; 190 | this._id = undefined; 191 | 192 | var that = this; 193 | 194 | /* eslint-disable camelcase */ 195 | this._uniforms = { 196 | u_showThroughEllipsoid: function() { 197 | return that.showThroughEllipsoid; 198 | }, 199 | u_showIntersection: function() { 200 | return that.showIntersection; 201 | }, 202 | u_sensorRadius: function() { 203 | return isFinite(that.radius) ? that.radius : FAR; 204 | }, 205 | u_intersectionColor: function() { 206 | return that.intersectionColor; 207 | }, 208 | u_intersectionWidth: function() { 209 | return that.intersectionWidth; 210 | }, 211 | u_normalDirection: function() { 212 | return 1.0; 213 | } 214 | }; 215 | /* eslint-enable camelcase */ 216 | 217 | this._mode = SceneMode.SCENE3D; 218 | }; 219 | 220 | defineProperties(CustomSensorVolume.prototype, { 221 | directions: { 222 | get: function() { 223 | return this._directions; 224 | }, 225 | set: function(value) { 226 | this._directions = value; 227 | this._directionsDirty = true; 228 | } 229 | } 230 | }); 231 | 232 | var n0Scratch = new Cartesian3(); 233 | var n1Scratch = new Cartesian3(); 234 | var n2Scratch = new Cartesian3(); 235 | function computePositions(customSensorVolume) { 236 | var directions = customSensorVolume._directions; 237 | var length = directions.length; 238 | var positions = new Float32Array(3 * length); 239 | var r = isFinite(customSensorVolume.radius) ? customSensorVolume.radius : FAR; 240 | 241 | var boundingVolumePositions = [Cartesian3.ZERO]; 242 | 243 | for (var i = length - 2, j = length - 1, k = 0; k < length; i = j++, j = k++) { 244 | // PERFORMANCE_IDEA: We can avoid redundant operations for adjacent edges. 245 | var n0 = Cartesian3.fromSpherical(directions[i], n0Scratch); 246 | var n1 = Cartesian3.fromSpherical(directions[j], n1Scratch); 247 | var n2 = Cartesian3.fromSpherical(directions[k], n2Scratch); 248 | 249 | // Extend position so the volume encompasses the sensor's radius. 250 | var theta = Math.max(Cartesian3.angleBetween(n0, n1), Cartesian3.angleBetween(n1, n2)); 251 | var distance = r / Math.cos(theta * 0.5); 252 | var p = Cartesian3.multiplyByScalar(n1, distance, new Cartesian3()); 253 | 254 | positions[(j * 3)] = p.x; 255 | positions[(j * 3) + 1] = p.y; 256 | positions[(j * 3) + 2] = p.z; 257 | 258 | boundingVolumePositions.push(p); 259 | } 260 | 261 | BoundingSphere.fromPoints(boundingVolumePositions, customSensorVolume._boundingSphere); 262 | 263 | return positions; 264 | } 265 | 266 | var nScratch = new Cartesian3(); 267 | function createVertexArray(customSensorVolume, context) { 268 | var positions = computePositions(customSensorVolume); 269 | 270 | var length = customSensorVolume._directions.length; 271 | var vertices = new Float32Array(2 * 3 * 3 * length); 272 | 273 | var k = 0; 274 | for (var i = length - 1, j = 0; j < length; i = j++) { 275 | var p0 = new Cartesian3(positions[(i * 3)], positions[(i * 3) + 1], positions[(i * 3) + 2]); 276 | var p1 = new Cartesian3(positions[(j * 3)], positions[(j * 3) + 1], positions[(j * 3) + 2]); 277 | var n = Cartesian3.normalize(Cartesian3.cross(p1, p0, nScratch), nScratch); // Per-face normals 278 | 279 | vertices[k++] = 0.0; // Sensor vertex 280 | vertices[k++] = 0.0; 281 | vertices[k++] = 0.0; 282 | vertices[k++] = n.x; 283 | vertices[k++] = n.y; 284 | vertices[k++] = n.z; 285 | 286 | vertices[k++] = p1.x; 287 | vertices[k++] = p1.y; 288 | vertices[k++] = p1.z; 289 | vertices[k++] = n.x; 290 | vertices[k++] = n.y; 291 | vertices[k++] = n.z; 292 | 293 | vertices[k++] = p0.x; 294 | vertices[k++] = p0.y; 295 | vertices[k++] = p0.z; 296 | vertices[k++] = n.x; 297 | vertices[k++] = n.y; 298 | vertices[k++] = n.z; 299 | } 300 | 301 | var vertexBuffer = Buffer.createVertexBuffer({ 302 | context: context, 303 | typedArray: new Float32Array(vertices), 304 | usage: BufferUsage.STATIC_DRAW 305 | }); 306 | 307 | var stride = 2 * 3 * Float32Array.BYTES_PER_ELEMENT; 308 | 309 | var attributes = [{ 310 | index: attributeLocations.position, 311 | vertexBuffer: vertexBuffer, 312 | componentsPerAttribute: 3, 313 | componentDatatype: ComponentDatatype.FLOAT, 314 | offsetInBytes: 0, 315 | strideInBytes: stride 316 | }, { 317 | index: attributeLocations.normal, 318 | vertexBuffer: vertexBuffer, 319 | componentsPerAttribute: 3, 320 | componentDatatype: ComponentDatatype.FLOAT, 321 | offsetInBytes: 3 * Float32Array.BYTES_PER_ELEMENT, 322 | strideInBytes: stride 323 | }]; 324 | 325 | return new VertexArray({ 326 | context: context, 327 | attributes: attributes 328 | }); 329 | } 330 | 331 | /** 332 | * Called when {@link Viewer} or {@link CesiumWidget} render the scene to 333 | * get the draw commands needed to render this primitive. 334 | *

335 | * Do not call this function directly. This is documented just to 336 | * list the exceptions that may be propagated when the scene is rendered: 337 | *

338 | * 339 | * @exception {DeveloperError} this.radius must be greater than or equal to zero. 340 | * @exception {DeveloperError} this.lateralSurfaceMaterial must be defined. 341 | */ 342 | CustomSensorVolume.prototype.update = function(frameState) { 343 | this._mode = frameState.mode; 344 | if (!this.show || this._mode !== SceneMode.SCENE3D) { 345 | return; 346 | } 347 | 348 | var context = frameState.context; 349 | var commandList = frameState.commandList; 350 | 351 | // >>includeStart('debug', pragmas.debug); 352 | if (this.radius < 0.0) { 353 | throw new DeveloperError('this.radius must be greater than or equal to zero.'); 354 | } 355 | if (!defined(this.lateralSurfaceMaterial)) { 356 | throw new DeveloperError('this.lateralSurfaceMaterial must be defined.'); 357 | } 358 | // >>includeEnd('debug'); 359 | 360 | var translucent = this.lateralSurfaceMaterial.isTranslucent(); 361 | 362 | // Initial render state creation 363 | if ((this._showThroughEllipsoid !== this.showThroughEllipsoid) || 364 | (!defined(this._frontFaceColorCommand.renderState)) || 365 | (this._translucent !== translucent) 366 | ) { 367 | this._showThroughEllipsoid = this.showThroughEllipsoid; 368 | this._translucent = translucent; 369 | 370 | var rs; 371 | 372 | if (translucent) { 373 | rs = RenderState.fromCache({ 374 | depthTest: { 375 | // This would be better served by depth testing with a depth buffer that does not 376 | // include the ellipsoid depth - or a g-buffer containing an ellipsoid mask 377 | // so we can selectively depth test. 378 | enabled: !this.showThroughEllipsoid 379 | }, 380 | depthMask: false, 381 | blending: BlendingState.ALPHA_BLEND, 382 | cull: { 383 | enabled: true, 384 | face: CullFace.BACK 385 | } 386 | }); 387 | 388 | this._frontFaceColorCommand.renderState = rs; 389 | this._frontFaceColorCommand.pass = Pass.TRANSLUCENT; 390 | 391 | rs = RenderState.fromCache({ 392 | depthTest: { 393 | enabled: !this.showThroughEllipsoid 394 | }, 395 | depthMask: false, 396 | blending: BlendingState.ALPHA_BLEND, 397 | cull: { 398 | enabled: true, 399 | face: CullFace.FRONT 400 | } 401 | }); 402 | 403 | this._backFaceColorCommand.renderState = rs; 404 | this._backFaceColorCommand.pass = Pass.TRANSLUCENT; 405 | 406 | rs = RenderState.fromCache({ 407 | depthTest: { 408 | enabled: !this.showThroughEllipsoid 409 | }, 410 | depthMask: false, 411 | blending: BlendingState.ALPHA_BLEND 412 | }); 413 | this._pickCommand.renderState = rs; 414 | } else { 415 | rs = RenderState.fromCache({ 416 | depthTest: { 417 | enabled: true 418 | }, 419 | depthMask: true 420 | }); 421 | this._frontFaceColorCommand.renderState = rs; 422 | this._frontFaceColorCommand.pass = Pass.OPAQUE; 423 | 424 | rs = RenderState.fromCache({ 425 | depthTest: { 426 | enabled: true 427 | }, 428 | depthMask: true 429 | }); 430 | this._pickCommand.renderState = rs; 431 | } 432 | } 433 | 434 | // Recreate vertex buffer when directions change 435 | var directionsChanged = this._directionsDirty; 436 | if (directionsChanged) { 437 | this._directionsDirty = false; 438 | this._va = this._va && this._va.destroy(); 439 | 440 | var directions = this._directions; 441 | if (directions && (directions.length >= 3)) { 442 | this._frontFaceColorCommand.vertexArray = createVertexArray(this, context); 443 | this._backFaceColorCommand.vertexArray = this._frontFaceColorCommand.vertexArray; 444 | this._pickCommand.vertexArray = this._frontFaceColorCommand.vertexArray; 445 | } 446 | } 447 | 448 | if (!defined(this._frontFaceColorCommand.vertexArray)) { 449 | return; 450 | } 451 | 452 | var pass = frameState.passes; 453 | 454 | var modelMatrixChanged = !Matrix4.equals(this.modelMatrix, this._modelMatrix); 455 | if (modelMatrixChanged) { 456 | Matrix4.clone(this.modelMatrix, this._modelMatrix); 457 | } 458 | 459 | if (directionsChanged || modelMatrixChanged) { 460 | BoundingSphere.transform(this._boundingSphere, this.modelMatrix, this._boundingSphereWC); 461 | } 462 | 463 | this._frontFaceColorCommand.modelMatrix = this.modelMatrix; 464 | this._backFaceColorCommand.modelMatrix = this._frontFaceColorCommand.modelMatrix; 465 | this._pickCommand.modelMatrix = this._frontFaceColorCommand.modelMatrix; 466 | 467 | var materialChanged = this._lateralSurfaceMaterial !== this.lateralSurfaceMaterial; 468 | this._lateralSurfaceMaterial = this.lateralSurfaceMaterial; 469 | this._lateralSurfaceMaterial.update(context); 470 | 471 | if (pass.render) { 472 | var frontFaceColorCommand = this._frontFaceColorCommand; 473 | var backFaceColorCommand = this._backFaceColorCommand; 474 | 475 | // Recompile shader when material changes 476 | if (materialChanged || !defined(frontFaceColorCommand.shaderProgram)) { 477 | var fsSource = new ShaderSource({ 478 | sources: [SensorVolume, this._lateralSurfaceMaterial.shaderSource, CustomSensorVolumeFS] 479 | }); 480 | 481 | frontFaceColorCommand.shaderProgram = ShaderProgram.replaceCache({ 482 | context: context, 483 | shaderProgram: frontFaceColorCommand.shaderProgram, 484 | vertexShaderSource: CustomSensorVolumeVS, 485 | fragmentShaderSource: fsSource, 486 | attributeLocations: attributeLocations 487 | }); 488 | 489 | frontFaceColorCommand.uniformMap = combine(this._uniforms, this._lateralSurfaceMaterial._uniforms); 490 | 491 | backFaceColorCommand.shaderProgram = frontFaceColorCommand.shaderProgram; 492 | backFaceColorCommand.uniformMap = combine(this._uniforms, this._lateralSurfaceMaterial._uniforms); 493 | // eslint-disable-next-line camelcase 494 | backFaceColorCommand.uniformMap.u_normalDirection = function() { 495 | return -1.0; 496 | }; 497 | } 498 | 499 | if (translucent) { 500 | commandList.push(this._backFaceColorCommand, this._frontFaceColorCommand); 501 | } else { 502 | commandList.push(this._frontFaceColorCommand); 503 | } 504 | } 505 | 506 | if (pass.pick) { 507 | var pickCommand = this._pickCommand; 508 | 509 | if (!defined(this._pickId) || (this._id !== this.id)) { 510 | this._id = this.id; 511 | this._pickId = this._pickId && this._pickId.destroy(); 512 | this._pickId = context.createPickId({ 513 | primitive: this._pickPrimitive, 514 | id: this.id 515 | }); 516 | } 517 | 518 | // Recompile shader when material changes 519 | if (materialChanged || !defined(pickCommand.shaderProgram)) { 520 | var pickFS = new ShaderSource({ 521 | sources: [SensorVolume, this._lateralSurfaceMaterial.shaderSource, CustomSensorVolumeFS], 522 | pickColorQualifier: 'uniform' 523 | }); 524 | 525 | pickCommand.shaderProgram = ShaderProgram.replaceCache({ 526 | context: context, 527 | shaderProgram: pickCommand.shaderProgram, 528 | vertexShaderSource: CustomSensorVolumeVS, 529 | fragmentShaderSource: pickFS, 530 | attributeLocations: attributeLocations 531 | }); 532 | 533 | var that = this; 534 | var uniforms = { 535 | // eslint-disable-next-line camelcase 536 | czm_pickColor: function() { 537 | return that._pickId.color; 538 | } 539 | }; 540 | pickCommand.uniformMap = combine(combine(this._uniforms, this._lateralSurfaceMaterial._uniforms), uniforms); 541 | } 542 | 543 | pickCommand.pass = translucent ? Pass.TRANSLUCENT : Pass.OPAQUE; 544 | commandList.push(pickCommand); 545 | } 546 | }; 547 | 548 | /** 549 | * DOC_TBA 550 | */ 551 | CustomSensorVolume.prototype.isDestroyed = function() { 552 | return false; 553 | }; 554 | 555 | /** 556 | * DOC_TBA 557 | */ 558 | CustomSensorVolume.prototype.destroy = function() { 559 | this._frontFaceColorCommand.vertexArray = this._frontFaceColorCommand.vertexArray && this._frontFaceColorCommand.vertexArray.destroy(); 560 | this._frontFaceColorCommand.shaderProgram = this._frontFaceColorCommand.shaderProgram && this._frontFaceColorCommand.shaderProgram.destroy(); 561 | this._pickCommand.shaderProgram = this._pickCommand.shaderProgram && this._pickCommand.shaderProgram.destroy(); 562 | this._pickId = this._pickId && this._pickId.destroy(); 563 | return destroyObject(this); 564 | }; 565 | 566 | return CustomSensorVolume; 567 | }); 568 | --------------------------------------------------------------------------------