├── .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 ├── .zed └── settings.json ├── lib ├── util │ └── remove-primitive.js ├── 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 ├── cesium-sensor-volumes.js ├── copyright-header.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 ├── .npmignore ├── .editorconfig ├── index.html ├── LICENSE.md ├── examples ├── czml.html └── api.html ├── package.json ├── README.md ├── gulpfile.js └── dist ├── cesium-sensor-volumes.es.min.js └── cesium-sensor-volumes.engine.es.min.js /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .idea 3 | .project 4 | .settings 5 | .tmp/ 6 | coverage/ 7 | node_modules/ 8 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.zed/settings.json: -------------------------------------------------------------------------------- 1 | // Folder-specific settings 2 | // 3 | // For a full list of overridable settings, and general information on folder-specific settings, 4 | // see the documentation: https://zed.dev/docs/configuring-zed#settings-files 5 | { 6 | "format_on_save": "off" 7 | } 8 | -------------------------------------------------------------------------------- /lib/util/remove-primitive.js: -------------------------------------------------------------------------------- 1 | import { defined } from 'cesium'; 2 | 3 | export default function removePrimitive(entity, hash, primitives) { 4 | var data = hash[entity.id]; 5 | if (defined(data)) { 6 | var primitive = data.primitive; 7 | primitives.remove(primitive); 8 | if (!primitive.isDestroyed()) { 9 | primitive.destroy(); 10 | } 11 | delete hash[entity.id]; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.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 | #version 300 es 2 | 3 | in vec4 position; 4 | in vec3 normal; 5 | 6 | out vec3 v_positionWC; 7 | out vec3 v_positionEC; 8 | out vec3 v_normalEC; 9 | 10 | void main() 11 | { 12 | gl_Position = czm_modelViewProjection * position; 13 | v_positionWC = (czm_model * position).xyz; 14 | v_positionEC = (czm_modelView * position).xyz; 15 | v_normalEC = czm_normal * normal; 16 | } 17 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Cesium Sensor Volumes 5 | 6 | 12 | 13 | 14 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/cesium-sensor-volumes.js: -------------------------------------------------------------------------------- 1 | import initialize from './initialize'; 2 | import ConicSensorGraphics from './conic/conic-sensor-graphics'; 3 | import ConicSensorVisualizer from './conic/conic-sensor-visualizer'; 4 | import CustomPatternSensorGraphics from './custom/custom-pattern-sensor-graphics'; 5 | import CustomPatternSensorVisualizer from './custom/custom-pattern-sensor-visualizer'; 6 | import CustomSensorVolume from './custom/custom-sensor-volume'; 7 | import RectangularPyramidSensorVolume from './rectangular/rectangular-pyramid-sensor-volume'; 8 | import RectangularSensorGraphics from './rectangular/rectangular-sensor-graphics'; 9 | import RectangularSensorVisualizer from './rectangular/rectangular-sensor-visualizer'; 10 | 11 | initialize(); 12 | 13 | export default { 14 | ConicSensorGraphics, 15 | ConicSensorVisualizer, 16 | CustomPatternSensorGraphics, 17 | CustomPatternSensorVisualizer, 18 | CustomSensorVolume, 19 | RectangularPyramidSensorVolume, 20 | RectangularSensorGraphics, 21 | RectangularSensorVisualizer 22 | }; 23 | -------------------------------------------------------------------------------- /lib/copyright-header.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Cesium Sensor Volumes - https://github.com/Flowm/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/Flowm/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/sensor-volume.glsl: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | uniform vec4 u_intersectionColor; 4 | uniform float u_intersectionWidth; 5 | 6 | bool inSensorShadow(vec3 coneVertexWC, vec3 pointWC) 7 | { 8 | // Diagonal matrix from the unscaled ellipsoid space to the scaled space. 9 | vec3 D = czm_ellipsoidInverseRadii; 10 | 11 | // Sensor vertex in the scaled ellipsoid space 12 | vec3 q = D * coneVertexWC; 13 | float qMagnitudeSquared = dot(q, q); 14 | float test = qMagnitudeSquared - 1.0; 15 | 16 | // Sensor vertex to fragment vector in the ellipsoid's scaled space 17 | vec3 temp = D * pointWC - q; 18 | float d = dot(temp, q); 19 | 20 | // Behind silhouette plane and inside silhouette cone 21 | return (d < -test) && (d / length(temp) < -sqrt(test)); 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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.100.0", 4 | "description": "A Cesium plugin for visualizing sensor volumes.", 5 | "homepage": "https://github.com/Flowm/cesium-sensor-volumes", 6 | "license": "Apache-2.0", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/Flowm/cesium-sensor-volumes.git" 10 | }, 11 | "keywords": [ 12 | "cesium" 13 | ], 14 | "main": "dist/cesium-sensor-volumes.js", 15 | "module": "dist/cesium-sensor-volumes.es.js", 16 | "exports": { 17 | ".": { 18 | "import": "./dist/cesium-sensor-volumes.es.js", 19 | "require": "./dist/cesium-sensor-volumes.js" 20 | }, 21 | "./engine": { 22 | "import": "./dist/cesium-sensor-volumes.engine.es.js" 23 | } 24 | }, 25 | "dependencies": { 26 | "cesium": "^1.134" 27 | }, 28 | "devDependencies": { 29 | "@rollup/plugin-terser": "^0.2.1", 30 | "browser-sync": "^2.27.12", 31 | "del": "^6.1.1", 32 | "electron": "23.1.1", 33 | "glsl-strip-comments": "^1.0.0", 34 | "gulp": "^4.0.2", 35 | "gulp-babel": "^8.0.0", 36 | "gulp-xo": "^0.15.0", 37 | "jasmine-core": "^4.5.0", 38 | "karma": "^6.4.1", 39 | "karma-chrome-launcher": "^3.1.1", 40 | "karma-coverage": "^2.2.0", 41 | "karma-electron-launcher": "^0.3.0", 42 | "karma-jasmine": "^5.1.0", 43 | "karma-requirejs": "^1.1.0", 44 | "rollup": "^3.8.1", 45 | "rollup-plugin-string": "^3.0.0", 46 | "through2": "^4.0.2" 47 | }, 48 | "scripts": { 49 | "build": "gulp build", 50 | "start": "gulp serve", 51 | "lint": "gulp lint", 52 | "test": "gulp test", 53 | "ci": "gulp ci", 54 | "gulp": "gulp" 55 | }, 56 | "xo": { 57 | "globals": [ 58 | "define" 59 | ], 60 | "esnext": false, 61 | "rules": { 62 | "capitalized-comments": "off", 63 | "func-names": "off", 64 | "import/no-amd": "off", 65 | "import/no-extraneous-dependencies": "off", 66 | "import/no-unresolved": "off", 67 | "import/no-webpack-loader-syntax": "off", 68 | "object-curly-spacing": [ 69 | "error", 70 | "always", 71 | { 72 | "objectsInObjects": false, 73 | "arraysInObjects": false 74 | } 75 | ], 76 | "space-before-function-paren": [ 77 | "error", 78 | "never" 79 | ] 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [cesium](https://cesium.com/cesiumjs/)-sensor-volumes 2 | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0.html) 3 | 4 | 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. 5 | 6 | ## Install 7 | 8 | This version isn't installable from npm as it is a customized version. 9 | 10 | ## Usage 11 | 12 | Prebuilt minified and unminified versions of the plugin are in the [dist](dist/) directory. 13 | 14 | ### Using with the `cesium` package 15 | 16 | Include the `cesium-sensor-volumes.js` file using a `script` tag after the `Cesium.js` `script` tag. 17 | 18 | The plugin automatically adds support for the CZML properties `agi_conicSensor`, `agi_customPatternSensor`, and `agi_rectangularSensor`. 19 | 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. 22 | The CZML processing does this automatically. 23 | 24 | ```html 25 | 26 | 27 | 37 | ``` 38 | 39 | ### Using with `@cesium/engine` for better tree-shaking 40 | 41 | For projects using `@cesium/engine` instead of the full `cesium` package, you can use the engine-specific builds for more efficient tree-shaking: 42 | 43 | **ES Module:** 44 | ```javascript 45 | import CesiumSensorVolumes from 'cesium-sensor-volumes/engine'; 46 | // or directly: 47 | // import CesiumSensorVolumes from './dist/cesium-sensor-volumes.engine.es.js'; 48 | ``` 49 | 50 | The engine builds (`cesium-sensor-volumes.engine.es.js` and `cesium-sensor-volumes.engine.es.min.js`) import from `@cesium/engine` instead of `cesium`, allowing bundlers to better optimize your final bundle size. 51 | 52 | **Note:** When using the engine builds, make sure you have `@cesium/engine` installed in your project: 53 | ```bash 54 | npm install @cesium/engine 55 | ``` 56 | 57 | ### Examples 58 | 59 | Simple examples are included in the [examples](examples/) folder. 60 | To run locally, run `npm start` and navigate to [http://localhost:3000](http://localhost:3000) and select the example application to run. 61 | 62 | ## Build 63 | 64 | To build, run `npm install`, then run `npm run build`. 65 | 66 | ## License 67 | 68 | Apache 2.0. Free for commercial and non-commercial use. See [LICENSE.md](LICENSE.md). 69 | -------------------------------------------------------------------------------- /lib/custom/custom-sensor-volume-fs.glsl: -------------------------------------------------------------------------------- 1 | #version 300 es 2 | 3 | uniform bool u_showIntersection; 4 | uniform bool u_showThroughEllipsoid; 5 | 6 | uniform float u_sensorRadius; 7 | uniform float u_normalDirection; 8 | 9 | in vec3 v_positionWC; 10 | in vec3 v_positionEC; 11 | in vec3 v_normalEC; 12 | 13 | vec4 getColor(float sensorRadius, vec3 pointEC) 14 | { 15 | czm_materialInput materialInput; 16 | 17 | vec3 pointMC = (czm_inverseModelView * vec4(pointEC, 1.0)).xyz; 18 | materialInput.st = sensor2dTextureCoordinates(sensorRadius, pointMC); 19 | materialInput.str = pointMC / sensorRadius; 20 | 21 | vec3 positionToEyeEC = -v_positionEC; 22 | materialInput.positionToEyeEC = positionToEyeEC; 23 | 24 | vec3 normalEC = normalize(v_normalEC); 25 | materialInput.normalEC = u_normalDirection * normalEC; 26 | 27 | czm_material material = czm_getMaterial(materialInput); 28 | return mix(czm_phong(normalize(positionToEyeEC), material, czm_lightDirectionEC), vec4(material.diffuse, material.alpha), 0.4); 29 | } 30 | 31 | bool isOnBoundary(float value, float epsilon) 32 | { 33 | float width = getIntersectionWidth(); 34 | float tolerance = width * epsilon; 35 | 36 | float delta = max(abs(dFdx(value)), abs(dFdy(value))); 37 | float pixels = width * delta; 38 | float temp = abs(value); 39 | // There are a couple things going on here. 40 | // First we test the value at the current fragment to see if it is within the tolerance. 41 | // We also want to check if the value of an adjacent pixel is within the tolerance, 42 | // but we don't want to admit points that are obviously not on the surface. 43 | // For example, if we are looking for "value" to be close to 0, but value is 1 and the adjacent value is 2, 44 | // then the delta would be 1 and "temp - delta" would be "1 - 1" which is zero even though neither of 45 | // the points is close to zero. 46 | return temp < tolerance && temp < pixels || (delta < 10.0 * tolerance && temp - delta < tolerance && temp < pixels); 47 | } 48 | 49 | vec4 shade(bool isOnBoundary) 50 | { 51 | if (u_showIntersection && isOnBoundary) 52 | { 53 | return getIntersectionColor(); 54 | } 55 | return getColor(u_sensorRadius, v_positionEC); 56 | } 57 | 58 | float ellipsoidSurfaceFunction(vec3 point) 59 | { 60 | vec3 scaled = czm_ellipsoidInverseRadii * point; 61 | return dot(scaled, scaled) - 1.0; 62 | } 63 | 64 | void main() 65 | { 66 | vec3 sensorVertexWC = czm_model[3].xyz; // (0.0, 0.0, 0.0) in model coordinates 67 | vec3 sensorVertexEC = czm_modelView[3].xyz; // (0.0, 0.0, 0.0) in model coordinates 68 | 69 | float ellipsoidValue = ellipsoidSurfaceFunction(v_positionWC); 70 | 71 | // Occluded by the ellipsoid? 72 | if (!u_showThroughEllipsoid) 73 | { 74 | // Discard if in the ellipsoid 75 | // PERFORMANCE_IDEA: A coarse check for ellipsoid intersection could be done on the CPU first. 76 | if (ellipsoidValue < 0.0) 77 | { 78 | discard; 79 | } 80 | 81 | // Discard if in the sensor's shadow 82 | if (inSensorShadow(sensorVertexWC, v_positionWC)) 83 | { 84 | discard; 85 | } 86 | } 87 | 88 | // Discard if not in the sensor's sphere 89 | // PERFORMANCE_IDEA: We can omit this check if the radius is Number.POSITIVE_INFINITY. 90 | if (distance(v_positionEC, sensorVertexEC) > u_sensorRadius) 91 | { 92 | discard; 93 | } 94 | 95 | // Notes: Each surface functions should have an associated tolerance based on the floating point error. 96 | bool isOnEllipsoid = isOnBoundary(ellipsoidValue, czm_epsilon3); 97 | out_FragColor = shade(isOnEllipsoid); 98 | } 99 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /lib/rectangular/rectangular-pyramid-sensor-volume.js: -------------------------------------------------------------------------------- 1 | import { 2 | clone, 3 | defined, 4 | destroyObject, 5 | DeveloperError, 6 | Frozen, 7 | Math as CesiumMath, 8 | Spherical 9 | } from 'cesium'; 10 | import CustomSensorVolume from '../custom/custom-sensor-volume'; 11 | 12 | function assignSpherical(index, array, clock, cone) { 13 | var spherical = array[index]; 14 | if (!defined(spherical)) { 15 | spherical = new Spherical(); 16 | array[index] = spherical; 17 | } 18 | spherical.clock = clock; 19 | spherical.cone = cone; 20 | spherical.magnitude = 1.0; 21 | } 22 | 23 | function updateDirections(rectangularSensor) { 24 | var directions = rectangularSensor._customSensor.directions; 25 | 26 | // At 90 degrees the sensor is completely open, and tan() goes to infinity. 27 | var tanX = Math.tan(Math.min(rectangularSensor._xHalfAngle, CesiumMath.toRadians(89.0))); 28 | var tanY = Math.tan(Math.min(rectangularSensor._yHalfAngle, CesiumMath.toRadians(89.0))); 29 | var theta = Math.atan(tanX / tanY); 30 | var cone = Math.atan(Math.sqrt((tanX * tanX) + (tanY * tanY))); 31 | 32 | assignSpherical(0, directions, theta, cone); 33 | assignSpherical(1, directions, CesiumMath.toRadians(180.0) - theta, cone); 34 | assignSpherical(2, directions, CesiumMath.toRadians(180.0) + theta, cone); 35 | assignSpherical(3, directions, -theta, cone); 36 | 37 | directions.length = 4; 38 | rectangularSensor._customSensor.directions = directions; 39 | } 40 | 41 | const RectangularPyramidSensorVolume = function(options) { 42 | options = options ?? Frozen.EMPTY_OBJECT; 43 | 44 | var customSensorOptions = clone(options); 45 | customSensorOptions._pickPrimitive = options._pickPrimitive ?? this; 46 | customSensorOptions.directions = undefined; 47 | this._customSensor = new CustomSensorVolume(customSensorOptions); 48 | 49 | this._xHalfAngle = options.xHalfAngle ?? CesiumMath.PI_OVER_TWO; 50 | this._yHalfAngle = options.yHalfAngle ?? CesiumMath.PI_OVER_TWO; 51 | 52 | updateDirections(this); 53 | }; 54 | 55 | Object.defineProperties(RectangularPyramidSensorVolume.prototype, { 56 | xHalfAngle: { 57 | get: function() { 58 | return this._xHalfAngle; 59 | }, 60 | set: function(value) { 61 | // >>includeStart('debug', pragmas.debug) 62 | if (value > CesiumMath.PI_OVER_TWO) { 63 | throw new DeveloperError('xHalfAngle must be less than or equal to 90 degrees.'); 64 | } 65 | // >>includeEnd('debug'); 66 | 67 | if (this._xHalfAngle !== value) { 68 | this._xHalfAngle = value; 69 | updateDirections(this); 70 | } 71 | } 72 | }, 73 | yHalfAngle: { 74 | get: function() { 75 | return this._yHalfAngle; 76 | }, 77 | set: function(value) { 78 | // >>includeStart('debug', pragmas.debug) 79 | if (value > CesiumMath.PI_OVER_TWO) { 80 | throw new DeveloperError('yHalfAngle must be less than or equal to 90 degrees.'); 81 | } 82 | // >>includeEnd('debug'); 83 | 84 | if (this._yHalfAngle !== value) { 85 | this._yHalfAngle = value; 86 | updateDirections(this); 87 | } 88 | } 89 | }, 90 | show: { 91 | get: function() { 92 | return this._customSensor.show; 93 | }, 94 | set: function(value) { 95 | this._customSensor.show = value; 96 | } 97 | }, 98 | showIntersection: { 99 | get: function() { 100 | return this._customSensor.showIntersection; 101 | }, 102 | set: function(value) { 103 | this._customSensor.showIntersection = value; 104 | } 105 | }, 106 | showThroughEllipsoid: { 107 | get: function() { 108 | return this._customSensor.showThroughEllipsoid; 109 | }, 110 | set: function(value) { 111 | this._customSensor.showThroughEllipsoid = value; 112 | } 113 | }, 114 | modelMatrix: { 115 | get: function() { 116 | return this._customSensor.modelMatrix; 117 | }, 118 | set: function(value) { 119 | this._customSensor.modelMatrix = value; 120 | } 121 | }, 122 | radius: { 123 | get: function() { 124 | return this._customSensor.radius; 125 | }, 126 | set: function(value) { 127 | this._customSensor.radius = value; 128 | } 129 | }, 130 | lateralSurfaceMaterial: { 131 | get: function() { 132 | return this._customSensor.lateralSurfaceMaterial; 133 | }, 134 | set: function(value) { 135 | this._customSensor.lateralSurfaceMaterial = value; 136 | } 137 | }, 138 | intersectionColor: { 139 | get: function() { 140 | return this._customSensor.intersectionColor; 141 | }, 142 | set: function(value) { 143 | this._customSensor.intersectionColor = value; 144 | } 145 | }, 146 | intersectionWidth: { 147 | get: function() { 148 | return this._customSensor.intersectionWidth; 149 | }, 150 | set: function(value) { 151 | this._customSensor.intersectionWidth = value; 152 | } 153 | }, 154 | id: { 155 | get: function() { 156 | return this._customSensor.id; 157 | }, 158 | set: function(value) { 159 | this._customSensor.id = value; 160 | } 161 | } 162 | }); 163 | 164 | RectangularPyramidSensorVolume.prototype.update = function(frameState) { 165 | this._customSensor.update(frameState); 166 | }; 167 | 168 | RectangularPyramidSensorVolume.prototype.isDestroyed = function() { 169 | return false; 170 | }; 171 | 172 | RectangularPyramidSensorVolume.prototype.destroy = function() { 173 | this._customSensor = this._customSensor && this._customSensor.destroy(); 174 | return destroyObject(this); 175 | }; 176 | 177 | export default RectangularPyramidSensorVolume; 178 | -------------------------------------------------------------------------------- /lib/custom/custom-pattern-sensor-graphics.js: -------------------------------------------------------------------------------- 1 | import { 2 | defined, 3 | DeveloperError, 4 | Event, 5 | Frozen, 6 | createMaterialPropertyDescriptor, 7 | createPropertyDescriptor 8 | } from 'cesium'; 9 | 10 | /** 11 | * An optionally time-dynamic custom patterned sensor. 12 | * 13 | * @alias CustomPatternSensorGraphics 14 | * @constructor 15 | */ 16 | const CustomPatternSensorGraphics = function(options) { 17 | this._directions = undefined; 18 | this._directionsSubscription = undefined; 19 | 20 | this._lateralSurfaceMaterial = undefined; 21 | this._lateralSurfaceMaterialSubscription = undefined; 22 | 23 | this._intersectionColor = undefined; 24 | this._intersectionColorSubscription = undefined; 25 | this._intersectionWidth = undefined; 26 | this._intersectionWidthSubscription = undefined; 27 | this._showIntersection = undefined; 28 | this._showIntersectionSubscription = undefined; 29 | this._radius = undefined; 30 | this._radiusSubscription = undefined; 31 | this._show = undefined; 32 | this._showSubscription = undefined; 33 | this._definitionChanged = new Event(); 34 | 35 | this.merge(options ?? Frozen.EMPTY_OBJECT); 36 | }; 37 | 38 | Object.defineProperties(CustomPatternSensorGraphics.prototype, { 39 | /** 40 | * Gets the event that is raised whenever a new property is assigned. 41 | * @memberof CustomPatternSensorGraphics.prototype 42 | * 43 | * @type {Event} 44 | * @readonly 45 | */ 46 | definitionChanged: { 47 | get: function() { 48 | return this._definitionChanged; 49 | } 50 | }, 51 | 52 | /** 53 | * A {@link Property} which returns an array of {@link Spherical} instances representing the sensor's projection. 54 | * @memberof CustomPatternSensorGraphics.prototype 55 | * @type {Property} 56 | */ 57 | directions: createPropertyDescriptor('directions'), 58 | 59 | /** 60 | * Gets or sets the {@link MaterialProperty} specifying the the sensor's appearance. 61 | * @memberof CustomPatternSensorGraphics.prototype 62 | * @type {MaterialProperty} 63 | */ 64 | lateralSurfaceMaterial: createMaterialPropertyDescriptor('lateralSurfaceMaterial'), 65 | 66 | /** 67 | * 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. 68 | * @memberof CustomPatternSensorGraphics.prototype 69 | * @type {Property} 70 | */ 71 | intersectionColor: createPropertyDescriptor('intersectionColor'), 72 | 73 | /** 74 | * Gets or sets the numeric {@link Property} specifying the width of the line formed by the intersection of the sensor and other central bodies. 75 | * @memberof CustomPatternSensorGraphics.prototype 76 | * @type {Property} 77 | */ 78 | intersectionWidth: createPropertyDescriptor('intersectionWidth'), 79 | 80 | /** 81 | * Gets or sets the boolean {@link Property} specifying the visibility of the line formed by the intersection of the sensor and other central bodies. 82 | * @memberof CustomPatternSensorGraphics.prototype 83 | * @type {Property} 84 | */ 85 | showIntersection: createPropertyDescriptor('showIntersection'), 86 | 87 | /** 88 | * Gets or sets the numeric {@link Property} specifying the radius of the sensor's projection. 89 | * @memberof CustomPatternSensorGraphics.prototype 90 | * @type {Property} 91 | */ 92 | radius: createPropertyDescriptor('radius'), 93 | 94 | /** 95 | * Gets or sets the boolean {@link Property} specifying the visibility of the sensor. 96 | * @memberof CustomPatternSensorGraphics.prototype 97 | * @type {Property} 98 | */ 99 | show: createPropertyDescriptor('show') 100 | }); 101 | 102 | /** 103 | * Duplicates a CustomPatternSensorGraphics instance. 104 | * 105 | * @param {CustomPatternSensorGraphics} [result] The object onto which to store the result. 106 | * @returns {CustomPatternSensorGraphics} The modified result parameter or a new instance if one was not provided. 107 | */ 108 | CustomPatternSensorGraphics.prototype.clone = function(result) { 109 | if (!defined(result)) { 110 | result = new CustomPatternSensorGraphics(); 111 | } 112 | result.directions = this.directions; 113 | result.radius = this.radius; 114 | result.show = this.show; 115 | result.showIntersection = this.showIntersection; 116 | result.intersectionColor = this.intersectionColor; 117 | result.intersectionWidth = this.intersectionWidth; 118 | result.lateralSurfaceMaterial = this.lateralSurfaceMaterial; 119 | return result; 120 | }; 121 | 122 | /** 123 | * Assigns each unassigned property on this object to the value 124 | * of the same property on the provided source object. 125 | * 126 | * @param {CustomPatternSensorGraphics} source The object to be merged into this object. 127 | */ 128 | CustomPatternSensorGraphics.prototype.merge = function(source) { 129 | // >>includeStart('debug', pragmas.debug); 130 | if (!defined(source)) { 131 | throw new DeveloperError('source is required.'); 132 | } 133 | // >>includeEnd('debug'); 134 | 135 | this.directions = this.directions ?? source.directions; 136 | this.radius = this.radius ?? source.radius; 137 | this.show = this.show ?? source.show; 138 | this.showIntersection = this.showIntersection ?? source.showIntersection; 139 | this.intersectionColor = this.intersectionColor ?? source.intersectionColor; 140 | this.intersectionWidth = this.intersectionWidth ?? source.intersectionWidth; 141 | this.lateralSurfaceMaterial = this.lateralSurfaceMaterial ?? source.lateralSurfaceMaterial; 142 | }; 143 | 144 | export default CustomPatternSensorGraphics; 145 | -------------------------------------------------------------------------------- /lib/rectangular/rectangular-sensor-graphics.js: -------------------------------------------------------------------------------- 1 | import { 2 | defined, 3 | DeveloperError, 4 | Event, 5 | createPropertyDescriptor 6 | } from 'cesium'; 7 | 8 | /** 9 | * An optionally time-dynamic pyramid. 10 | * 11 | * @alias RectangularSensorGraphics 12 | * @constructor 13 | */ 14 | const RectangularSensorGraphics = function() { 15 | this._xHalfAngle = undefined; 16 | this._xHalfAngleSubscription = undefined; 17 | this._yHalfAngle = undefined; 18 | this._yHalfAngleSubscription = undefined; 19 | 20 | this._lateralSurfaceMaterial = undefined; 21 | this._lateralSurfaceMaterialSubscription = undefined; 22 | 23 | this._intersectionColor = undefined; 24 | this._intersectionColorSubscription = undefined; 25 | this._intersectionWidth = undefined; 26 | this._intersectionWidthSubscription = undefined; 27 | this._showIntersection = undefined; 28 | this._showIntersectionSubscription = undefined; 29 | this._radius = undefined; 30 | this._radiusSubscription = undefined; 31 | this._show = undefined; 32 | this._showSubscription = undefined; 33 | this._definitionChanged = new Event(); 34 | }; 35 | 36 | Object.defineProperties(RectangularSensorGraphics.prototype, { 37 | /** 38 | * Gets the event that is raised whenever a new property is assigned. 39 | * @memberof RectangularSensorGraphics.prototype 40 | * 41 | * @type {Event} 42 | * @readonly 43 | */ 44 | definitionChanged: { 45 | get: function() { 46 | return this._definitionChanged; 47 | } 48 | }, 49 | 50 | /** 51 | * A {@link Property} which returns an array of {@link Spherical} instances representing the pyramid's projection. 52 | * @memberof RectangularSensorGraphics.prototype 53 | * @type {Property} 54 | */ 55 | xHalfAngle: createPropertyDescriptor('xHalfAngle'), 56 | 57 | /** 58 | * A {@link Property} which returns an array of {@link Spherical} instances representing the pyramid's projection. 59 | * @memberof RectangularSensorGraphics.prototype 60 | * @type {Property} 61 | */ 62 | yHalfAngle: createPropertyDescriptor('yHalfAngle'), 63 | 64 | /** 65 | * Gets or sets the {@link MaterialProperty} specifying the the pyramid's appearance. 66 | * @memberof RectangularSensorGraphics.prototype 67 | * @type {MaterialProperty} 68 | */ 69 | lateralSurfaceMaterial: createPropertyDescriptor('lateralSurfaceMaterial'), 70 | 71 | /** 72 | * 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. 73 | * @memberof RectangularSensorGraphics.prototype 74 | * @type {Property} 75 | */ 76 | intersectionColor: createPropertyDescriptor('intersectionColor'), 77 | 78 | /** 79 | * Gets or sets the numeric {@link Property} specifying the width of the line formed by the intersection of the pyramid and other central bodies. 80 | * @memberof RectangularSensorGraphics.prototype 81 | * @type {Property} 82 | */ 83 | intersectionWidth: createPropertyDescriptor('intersectionWidth'), 84 | 85 | /** 86 | * Gets or sets the boolean {@link Property} specifying the visibility of the line formed by the intersection of the pyramid and other central bodies. 87 | * @memberof RectangularSensorGraphics.prototype 88 | * @type {Property} 89 | */ 90 | showIntersection: createPropertyDescriptor('showIntersection'), 91 | 92 | /** 93 | * Gets or sets the numeric {@link Property} specifying the radius of the pyramid's projection. 94 | * @memberof RectangularSensorGraphics.prototype 95 | * @type {Property} 96 | */ 97 | radius: createPropertyDescriptor('radius'), 98 | 99 | /** 100 | * Gets or sets the boolean {@link Property} specifying the visibility of the pyramid. 101 | * @memberof RectangularSensorGraphics.prototype 102 | * @type {Property} 103 | */ 104 | show: createPropertyDescriptor('show') 105 | }); 106 | 107 | /** 108 | * Duplicates a RectangularSensorGraphics instance. 109 | * 110 | * @param {RectangularSensorGraphics} [result] The object onto which to store the result. 111 | * @returns {RectangularSensorGraphics} The modified result parameter or a new instance if one was not provided. 112 | */ 113 | RectangularSensorGraphics.prototype.clone = function(result) { 114 | if (!defined(result)) { 115 | result = new RectangularSensorGraphics(); 116 | } 117 | result.xHalfAngle = this.xHalfAngle; 118 | result.yHalfAngle = this.yHalfAngle; 119 | result.radius = this.radius; 120 | result.show = this.show; 121 | result.showIntersection = this.showIntersection; 122 | result.intersectionColor = this.intersectionColor; 123 | result.intersectionWidth = this.intersectionWidth; 124 | result.lateralSurfaceMaterial = this.lateralSurfaceMaterial; 125 | return result; 126 | }; 127 | 128 | /** 129 | * Assigns each unassigned property on this object to the value 130 | * of the same property on the provided source object. 131 | * 132 | * @param {RectangularSensorGraphics} source The object to be merged into this object. 133 | */ 134 | RectangularSensorGraphics.prototype.merge = function(source) { 135 | // >>includeStart('debug', pragmas.debug); 136 | if (!defined(source)) { 137 | throw new DeveloperError('source is required.'); 138 | } 139 | // >>includeEnd('debug'); 140 | 141 | this.xHalfAngle = this.xHalfAngle ?? source.xHalfAngle; 142 | this.yHalfAngle = this.yHalfAngle ?? source.yHalfAngle; 143 | this.radius = this.radius ?? source.radius; 144 | this.show = this.show ?? source.show; 145 | this.showIntersection = this.showIntersection ?? source.showIntersection; 146 | this.intersectionColor = this.intersectionColor ?? source.intersectionColor; 147 | this.intersectionWidth = this.intersectionWidth ?? source.intersectionWidth; 148 | this.lateralSurfaceMaterial = this.lateralSurfaceMaterial ?? source.lateralSurfaceMaterial; 149 | }; 150 | 151 | export default RectangularSensorGraphics; 152 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | 6 | var gulp = require('gulp'); 7 | var through = require('through2'); 8 | var del = require('del'); 9 | var xo = require('gulp-xo'); 10 | 11 | var rollup = require('rollup'); 12 | var { string } = require('rollup-plugin-string'); 13 | var terser = require('@rollup/plugin-terser'); 14 | var glslStripComments = require('glsl-strip-comments'); 15 | 16 | var browserSync = require('browser-sync').create(); 17 | 18 | var reload = browserSync.reload; 19 | 20 | function runLint(src) { 21 | return gulp.src(src) 22 | .pipe(xo()); 23 | } 24 | function lint() { 25 | return runLint(['lib/**/*.js', 'gulp/**/*.js', 'gulpfile.js']); 26 | } 27 | exports.lint = lint; 28 | 29 | function clean() { 30 | return del(['coverage', '.tmp', 'dist']); 31 | } 32 | exports.clean = clean; 33 | 34 | function preprocessShaders() { 35 | return gulp.src('lib/**/*.glsl') 36 | .pipe(through.obj(function(file, _, cb) { 37 | if (file.isBuffer()) { 38 | const output = glslStripComments(file.contents.toString(), { version: '300 es' }); 39 | file.contents = Buffer.from(output); 40 | } 41 | cb(null, file); 42 | })) 43 | .pipe(gulp.dest('.tmp')); 44 | } 45 | 46 | function preprocessJs() { 47 | return gulp.src(['lib/**/*.js']) 48 | .pipe(gulp.dest('.tmp')); 49 | } 50 | 51 | function getCopyrightHeaders() { 52 | return fs.readFileSync('lib/copyright-header.js').toString(); 53 | } 54 | 55 | async function buildEs() { 56 | const bundle = await rollup.rollup({ 57 | input: '.tmp/cesium-sensor-volumes.js', 58 | plugins: [ 59 | string({ 60 | include: '**/*.glsl' 61 | }) 62 | ], 63 | external: id => id === 'cesium' || /Cesium/.test(id) 64 | }); 65 | 66 | await bundle.write({ 67 | file: 'dist/cesium-sensor-volumes.es.js', 68 | format: 'es', 69 | banner: getCopyrightHeaders() 70 | }); 71 | await bundle.write({ 72 | file: 'dist/cesium-sensor-volumes.es.min.js', 73 | format: 'es', 74 | plugins: [terser({ 75 | format: { 76 | comments: function(node, comment) { 77 | if (comment.type === 'comment2') { 78 | return /Copyright/i.test(comment.value); 79 | } 80 | } 81 | } 82 | })], 83 | banner: getCopyrightHeaders() 84 | }); 85 | } 86 | 87 | function createEngineBuilds() { 88 | return gulp.src(['dist/cesium-sensor-volumes.es.js', 'dist/cesium-sensor-volumes.es.min.js']) 89 | .pipe(through.obj(function(file, _, cb) { 90 | if (file.isBuffer()) { 91 | var content = file.contents.toString(); 92 | // Replace 'cesium' imports with '@cesium/engine' 93 | content = content.replace(/from ['"]cesium['"]/g, "from '@cesium/engine'"); 94 | content = content.replace(/from"cesium"/g, 'from"@cesium/engine"'); 95 | file.contents = Buffer.from(content); 96 | } 97 | // Rename the file to add .engine 98 | var basename = file.basename.replace('.es.js', '.engine.es.js').replace('.es.min.js', '.engine.es.min.js'); 99 | file.basename = basename; 100 | cb(null, file); 101 | })) 102 | .pipe(gulp.dest('dist')); 103 | } 104 | 105 | exports.buildEs = gulp.series(clean, gulp.parallel(preprocessShaders, preprocessJs), buildEs); 106 | 107 | function generateShims() { 108 | // Search for Cesium modules and add shim modules that pull from the Cesium global 109 | return gulp.src(['./dist/cesium-sensor-volumes.es.js']) 110 | .pipe(through.obj(function(file, _, cb) { 111 | if (file.isBuffer()) { 112 | // Handle destructured imports from 'cesium' 113 | var cesiumDestructuredRegex = /import\s*\{\s*([^}]+)\s*\}\s*from\s*['"]cesium['"];?/g; 114 | var content = file.contents.toString(); 115 | 116 | // Replace destructured imports with Cesium global references 117 | const output = content.replace(cesiumDestructuredRegex, (match, imports) => { 118 | // Parse the imports, handling aliases like "Math as CesiumMath" 119 | const importList = imports.split(',').map(imp => { 120 | const trimmed = imp.trim(); 121 | if (trimmed.includes(' as ')) { 122 | const [cesiumName, localName] = trimmed.split(' as ').map(s => s.trim()); 123 | return `const ${localName} = Cesium.${cesiumName};`; 124 | } else { 125 | return `const ${trimmed} = Cesium.${trimmed};`; 126 | } 127 | }); 128 | return importList.join('\n'); 129 | }); 130 | file.contents = Buffer.from(output); 131 | } 132 | cb(null, file); 133 | })) 134 | .pipe(gulp.dest('.tmp/shimmed')); 135 | } 136 | 137 | async function buildUmd() { 138 | const bundle = await rollup.rollup({ 139 | input: '.tmp/shimmed/cesium-sensor-volumes.es.js' 140 | }); 141 | await bundle.write({ 142 | file: 'dist/cesium-sensor-volumes.js', 143 | name: 'CesiumSensorVolumes', 144 | format: 'umd' 145 | }); 146 | await bundle.write({ 147 | file: 'dist/cesium-sensor-volumes.min.js', 148 | name: 'CesiumSensorVolumes', 149 | plugins: [terser({ 150 | format: { 151 | comments: function(node, comment) { 152 | if (comment.type === 'comment2') { 153 | return /Copyright/i.test(comment.value); 154 | } 155 | } 156 | } 157 | })], 158 | format: 'umd' 159 | }); 160 | } 161 | exports.build = gulp.series(exports.buildEs, createEngineBuilds, generateShims, buildUmd); 162 | 163 | exports.buildReload = gulp.series(exports.build, reload); 164 | 165 | function run(cb) { 166 | browserSync.init({ 167 | server: '.' 168 | }, cb); 169 | } 170 | 171 | function watch(cb) { 172 | gulp.watch(['examples/**/*.html', 'examples/**/*.czml'], reload); 173 | gulp.watch(['lib/**/*.glsl'], exports.buildReload); 174 | gulp.watch(['lib/**/*.js'], exports.buildReload); 175 | cb(); 176 | } 177 | exports.serve = gulp.series(exports.build, run, watch); 178 | 179 | function lintTest() { 180 | return runLint(['test/**/*.js']); 181 | } 182 | 183 | function test(done, options) { 184 | var Server = require('karma').Server; 185 | 186 | var server = new Server(Object.assign({ 187 | configFile: path.join(__dirname, '/test/karma.conf.js'), 188 | singleRun: true 189 | }, options), done); 190 | 191 | server.start(); 192 | } 193 | exports.test = gulp.series(lintTest, test); 194 | 195 | function testCI(done) { 196 | test(done, { 197 | browsers: ['Electron'], 198 | client: { 199 | args: [true] 200 | } 201 | }); 202 | } 203 | exports.testCI = gulp.series(lintTest, testCI); 204 | exports.ci = gulp.series(lint, testCI, exports.build); -------------------------------------------------------------------------------- /lib/conic/conic-sensor-graphics.js: -------------------------------------------------------------------------------- 1 | import { 2 | defined, 3 | DeveloperError, 4 | Event, 5 | Frozen, 6 | createMaterialPropertyDescriptor, 7 | createPropertyDescriptor 8 | } from 'cesium'; 9 | 10 | /** 11 | * An optionally time-dynamic cone. 12 | * 13 | * @alias ConicSensorGraphics 14 | * @constructor 15 | */ 16 | const ConicSensorGraphics = function(options) { 17 | this._minimumClockAngle = undefined; 18 | this._minimumClockAngleSubscription = undefined; 19 | this._maximumClockAngle = undefined; 20 | this._maximumClockAngleSubscription = undefined; 21 | this._innerHalfAngle = undefined; 22 | this._innerHalfAngleSubscription = undefined; 23 | this._outerHalfAngle = undefined; 24 | this._outerHalfAngleSubscription = undefined; 25 | this._lateralSurfaceMaterial = undefined; 26 | this._lateralSurfaceMaterialSubscription = undefined; 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 | this.merge(options ?? Frozen.EMPTY_OBJECT); 40 | }; 41 | 42 | Object.defineProperties(ConicSensorGraphics.prototype, { 43 | /** 44 | * Gets the event that is raised whenever a new property is assigned. 45 | * @memberof ConicSensorGraphics.prototype 46 | * 47 | * @type {Event} 48 | * @readonly 49 | */ 50 | definitionChanged: { 51 | get: function() { 52 | return this._definitionChanged; 53 | } 54 | }, 55 | 56 | /** 57 | * Gets or sets the numeric {@link Property} specifying the the cone's minimum clock angle. 58 | * @memberof ConicSensorGraphics.prototype 59 | * @type {Property} 60 | */ 61 | minimumClockAngle: createPropertyDescriptor('minimumClockAngle'), 62 | 63 | /** 64 | * Gets or sets the numeric {@link Property} specifying the the cone's maximum clock angle. 65 | * @memberof ConicSensorGraphics.prototype 66 | * @type {Property} 67 | */ 68 | maximumClockAngle: createPropertyDescriptor('maximumClockAngle'), 69 | 70 | /** 71 | * Gets or sets the numeric {@link Property} specifying the the cone's inner half-angle. 72 | * @memberof ConicSensorGraphics.prototype 73 | * @type {Property} 74 | */ 75 | innerHalfAngle: createPropertyDescriptor('innerHalfAngle'), 76 | 77 | /** 78 | * Gets or sets the numeric {@link Property} specifying the the cone's outer half-angle. 79 | * @memberof ConicSensorGraphics.prototype 80 | * @type {Property} 81 | */ 82 | outerHalfAngle: createPropertyDescriptor('outerHalfAngle'), 83 | 84 | /** 85 | * Gets or sets the {@link MaterialProperty} specifying the the cone's appearance. 86 | * @memberof ConicSensorGraphics.prototype 87 | * @type {MaterialProperty} 88 | */ 89 | lateralSurfaceMaterial: createMaterialPropertyDescriptor('lateralSurfaceMaterial'), 90 | 91 | /** 92 | * 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. 93 | * @memberof ConicSensorGraphics.prototype 94 | * @type {Property} 95 | */ 96 | intersectionColor: createPropertyDescriptor('intersectionColor'), 97 | 98 | /** 99 | * Gets or sets the numeric {@link Property} specifying the width of the line formed by the intersection of the cone and other central bodies. 100 | * @memberof ConicSensorGraphics.prototype 101 | * @type {Property} 102 | */ 103 | intersectionWidth: createPropertyDescriptor('intersectionWidth'), 104 | 105 | /** 106 | * Gets or sets the boolean {@link Property} specifying the visibility of the line formed by the intersection of the cone and other central bodies. 107 | * @memberof ConicSensorGraphics.prototype 108 | * @type {Property} 109 | */ 110 | showIntersection: createPropertyDescriptor('showIntersection'), 111 | 112 | /** 113 | * Gets or sets the numeric {@link Property} specifying the radius of the cone's projection. 114 | * @memberof ConicSensorGraphics.prototype 115 | * @type {Property} 116 | */ 117 | radius: createPropertyDescriptor('radius'), 118 | 119 | /** 120 | * Gets or sets the boolean {@link Property} specifying the visibility of the cone. 121 | * @memberof ConicSensorGraphics.prototype 122 | * @type {Property} 123 | */ 124 | show: createPropertyDescriptor('show') 125 | }); 126 | 127 | /** 128 | * Duplicates a ConicSensorGraphics instance. 129 | * 130 | * @param {ConicSensorGraphics} [result] The object onto which to store the result. 131 | * @returns {ConicSensorGraphics} The modified result parameter or a new instance if one was not provided. 132 | */ 133 | ConicSensorGraphics.prototype.clone = function(result) { 134 | if (!defined(result)) { 135 | result = new ConicSensorGraphics(); 136 | } 137 | result.show = this.show; 138 | result.innerHalfAngle = this.innerHalfAngle; 139 | result.outerHalfAngle = this.outerHalfAngle; 140 | result.minimumClockAngle = this.minimumClockAngle; 141 | result.maximumClockAngle = this.maximumClockAngle; 142 | result.radius = this.radius; 143 | result.showIntersection = this.showIntersection; 144 | result.intersectionColor = this.intersectionColor; 145 | result.intersectionWidth = this.intersectionWidth; 146 | result.lateralSurfaceMaterial = this.lateralSurfaceMaterial; 147 | return result; 148 | }; 149 | 150 | /** 151 | * Assigns each unassigned property on this object to the value 152 | * of the same property on the provided source object. 153 | * 154 | * @param {ConicSensorGraphics} source The object to be merged into this object. 155 | */ 156 | ConicSensorGraphics.prototype.merge = function(source) { 157 | // >>includeStart('debug', pragmas.debug); 158 | if (!defined(source)) { 159 | throw new DeveloperError('source is required.'); 160 | } 161 | // >>includeEnd('debug'); 162 | 163 | this.show = this.show ?? source.show; 164 | this.innerHalfAngle = this.innerHalfAngle ?? source.innerHalfAngle; 165 | this.outerHalfAngle = this.outerHalfAngle ?? source.outerHalfAngle; 166 | this.minimumClockAngle = this.minimumClockAngle ?? source.minimumClockAngle; 167 | this.maximumClockAngle = this.maximumClockAngle ?? source.maximumClockAngle; 168 | this.radius = this.radius ?? source.radius; 169 | this.showIntersection = this.showIntersection ?? source.showIntersection; 170 | this.intersectionColor = this.intersectionColor ?? source.intersectionColor; 171 | this.intersectionWidth = this.intersectionWidth ?? source.intersectionWidth; 172 | this.lateralSurfaceMaterial = this.lateralSurfaceMaterial ?? source.lateralSurfaceMaterial; 173 | }; 174 | 175 | export default ConicSensorGraphics; 176 | -------------------------------------------------------------------------------- /lib/custom/custom-pattern-sensor-visualizer.js: -------------------------------------------------------------------------------- 1 | import { 2 | AssociativeArray, 3 | Cartesian3, 4 | Color, 5 | defined, 6 | destroyObject, 7 | DeveloperError, 8 | Matrix3, 9 | Matrix4, 10 | Quaternion, 11 | MaterialProperty, 12 | Property 13 | } from 'cesium'; 14 | import CustomSensorVolume from '../custom/custom-sensor-volume'; 15 | import removePrimitive from '../util/remove-primitive'; 16 | 17 | const defaultIntersectionColor = Color.WHITE; 18 | const defaultIntersectionWidth = 1.0; 19 | const defaultRadius = Number.POSITIVE_INFINITY; 20 | 21 | const matrix3Scratch = new Matrix3(); 22 | const cachedPosition = new Cartesian3(); 23 | const cachedOrientation = new Quaternion(); 24 | 25 | /** 26 | * A {@link Visualizer} which maps {@link Entity#customPatternSensor} to a {@link CustomPatternSensor}. 27 | * @alias CustomPatternSensorVisualizer 28 | * @constructor 29 | * 30 | * @param {Scene} scene The scene the primitives will be rendered in. 31 | * @param {EntityCollection} entityCollection The entityCollection to visualize. 32 | */ 33 | const CustomPatternSensorVisualizer = function(scene, entityCollection) { 34 | // >>includeStart('debug', pragmas.debug); 35 | if (!defined(scene)) { 36 | throw new DeveloperError('scene is required.'); 37 | } 38 | if (!defined(entityCollection)) { 39 | throw new DeveloperError('entityCollection is required.'); 40 | } 41 | // >>includeEnd('debug'); 42 | 43 | entityCollection.collectionChanged.addEventListener(CustomPatternSensorVisualizer.prototype._onCollectionChanged, this); 44 | 45 | this._scene = scene; 46 | this._primitives = scene.primitives; 47 | this._entityCollection = entityCollection; 48 | this._hash = {}; 49 | this._entitiesToVisualize = new AssociativeArray(); 50 | 51 | this._onCollectionChanged(entityCollection, entityCollection.values, [], []); 52 | }; 53 | 54 | /** 55 | * Updates the primitives created by this visualizer to match their 56 | * Entity counterpart at the given time. 57 | * 58 | * @param {JulianDate} time The time to update to. 59 | * @returns {Boolean} This function always returns true. 60 | */ 61 | CustomPatternSensorVisualizer.prototype.update = function(time) { 62 | // >>includeStart('debug', pragmas.debug); 63 | if (!defined(time)) { 64 | throw new DeveloperError('time is required.'); 65 | } 66 | // >>includeEnd('debug'); 67 | 68 | var entities = this._entitiesToVisualize.values; 69 | var hash = this._hash; 70 | var primitives = this._primitives; 71 | 72 | for (var i = 0, len = entities.length; i < len; i++) { 73 | var entity = entities[i]; 74 | var customPatternSensorGraphics = entity._customPatternSensor; 75 | 76 | var position; 77 | var orientation; 78 | var directions; 79 | var data = hash[entity.id]; 80 | var show = entity.isShowing && entity.isAvailable(time) && Property.getValueOrDefault(customPatternSensorGraphics._show, time, true); 81 | 82 | if (show) { 83 | position = Property.getValueOrUndefined(entity._position, time, cachedPosition); 84 | orientation = Property.getValueOrUndefined(entity._orientation, time, cachedOrientation); 85 | directions = Property.getValueOrUndefined(customPatternSensorGraphics._directions, time); 86 | show = defined(position) && defined(orientation) && defined(directions); 87 | } 88 | 89 | if (!show) { 90 | // don't bother creating or updating anything else 91 | if (defined(data)) { 92 | data.primitive.show = false; 93 | } 94 | continue; 95 | } 96 | 97 | var primitive = defined(data) ? data.primitive : undefined; 98 | if (!defined(primitive)) { 99 | primitive = new CustomSensorVolume(); 100 | primitive.id = entity; 101 | primitives.add(primitive); 102 | 103 | data = { 104 | primitive: primitive, 105 | position: undefined, 106 | orientation: undefined 107 | }; 108 | hash[entity.id] = data; 109 | } 110 | 111 | if (!Cartesian3.equals(position, data.position) || !Quaternion.equals(orientation, data.orientation)) { 112 | Matrix4.fromRotationTranslation(Matrix3.fromQuaternion(orientation, matrix3Scratch), position, primitive.modelMatrix); 113 | data.position = Cartesian3.clone(position, data.position); 114 | data.orientation = Quaternion.clone(orientation, data.orientation); 115 | } 116 | 117 | primitive.show = true; 118 | primitive.directions = directions; 119 | primitive.radius = Property.getValueOrDefault(customPatternSensorGraphics._radius, time, defaultRadius); 120 | primitive.lateralSurfaceMaterial = MaterialProperty.getValue(time, customPatternSensorGraphics._lateralSurfaceMaterial, primitive.lateralSurfaceMaterial); 121 | primitive.intersectionColor = Property.getValueOrClonedDefault(customPatternSensorGraphics._intersectionColor, time, defaultIntersectionColor, primitive.intersectionColor); 122 | primitive.intersectionWidth = Property.getValueOrDefault(customPatternSensorGraphics._intersectionWidth, time, defaultIntersectionWidth); 123 | } 124 | return true; 125 | }; 126 | 127 | /** 128 | * Returns true if this object was destroyed; otherwise, false. 129 | * 130 | * @returns {Boolean} True if this object was destroyed; otherwise, false. 131 | */ 132 | CustomPatternSensorVisualizer.prototype.isDestroyed = function() { 133 | return false; 134 | }; 135 | 136 | /** 137 | * Removes and destroys all primitives created by this instance. 138 | */ 139 | CustomPatternSensorVisualizer.prototype.destroy = function() { 140 | var entities = this._entitiesToVisualize.values; 141 | var hash = this._hash; 142 | var primitives = this._primitives; 143 | for (var i = entities.length - 1; i > -1; i--) { 144 | removePrimitive(entities[i], hash, primitives); 145 | } 146 | return destroyObject(this); 147 | }; 148 | 149 | /** 150 | * @private 151 | */ 152 | CustomPatternSensorVisualizer.prototype._onCollectionChanged = function(entityCollection, added, removed, changed) { 153 | var i; 154 | var entity; 155 | var entities = this._entitiesToVisualize; 156 | var hash = this._hash; 157 | var primitives = this._primitives; 158 | 159 | for (i = added.length - 1; i > -1; i--) { 160 | entity = added[i]; 161 | if (defined(entity._customPatternSensor) && defined(entity._position) && defined(entity._orientation)) { 162 | entities.set(entity.id, entity); 163 | } 164 | } 165 | 166 | for (i = changed.length - 1; i > -1; i--) { 167 | entity = changed[i]; 168 | if (defined(entity._customPatternSensor) && defined(entity._position) && defined(entity._orientation)) { 169 | entities.set(entity.id, entity); 170 | } else { 171 | removePrimitive(entity, hash, primitives); 172 | entities.remove(entity.id); 173 | } 174 | } 175 | 176 | for (i = removed.length - 1; i > -1; i--) { 177 | entity = removed[i]; 178 | removePrimitive(entity, hash, primitives); 179 | entities.remove(entity.id); 180 | } 181 | }; 182 | 183 | export default CustomPatternSensorVisualizer; 184 | -------------------------------------------------------------------------------- /lib/rectangular/rectangular-sensor-visualizer.js: -------------------------------------------------------------------------------- 1 | import { 2 | AssociativeArray, 3 | Cartesian3, 4 | Color, 5 | defined, 6 | destroyObject, 7 | DeveloperError, 8 | Math as CesiumMath, 9 | Matrix3, 10 | Matrix4, 11 | Quaternion, 12 | MaterialProperty, 13 | Property 14 | } from 'cesium'; 15 | import removePrimitive from '../util/remove-primitive'; 16 | import RectangularPyramidSensorVolume from './rectangular-pyramid-sensor-volume'; 17 | 18 | const defaultIntersectionColor = Color.WHITE; 19 | const defaultIntersectionWidth = 1.0; 20 | const defaultRadius = Number.POSITIVE_INFINITY; 21 | 22 | const matrix3Scratch = new Matrix3(); 23 | const cachedPosition = new Cartesian3(); 24 | const cachedOrientation = new Quaternion(); 25 | 26 | /** 27 | * A {@link Visualizer} which maps {@link Entity#rectangularSensor} to a {@link RectangularSensor}. 28 | * @alias RectangularSensorVisualizer 29 | * @constructor 30 | * 31 | * @param {Scene} scene The scene the primitives will be rendered in. 32 | * @param {EntityCollection} entityCollection The entityCollection to visualize. 33 | */ 34 | const RectangularSensorVisualizer = function(scene, entityCollection) { 35 | // >>includeStart('debug', pragmas.debug); 36 | if (!defined(scene)) { 37 | throw new DeveloperError('scene is required.'); 38 | } 39 | if (!defined(entityCollection)) { 40 | throw new DeveloperError('entityCollection is required.'); 41 | } 42 | // >>includeEnd('debug'); 43 | 44 | entityCollection.collectionChanged.addEventListener(RectangularSensorVisualizer.prototype._onCollectionChanged, this); 45 | 46 | this._scene = scene; 47 | this._primitives = scene.primitives; 48 | this._entityCollection = entityCollection; 49 | this._hash = {}; 50 | this._entitiesToVisualize = new AssociativeArray(); 51 | 52 | this._onCollectionChanged(entityCollection, entityCollection.values, [], []); 53 | }; 54 | 55 | /** 56 | * Updates the primitives created by this visualizer to match their 57 | * Entity counterpart at the given time. 58 | * 59 | * @param {JulianDate} time The time to update to. 60 | * @returns {Boolean} This function always returns true. 61 | */ 62 | RectangularSensorVisualizer.prototype.update = function(time) { 63 | // >>includeStart('debug', pragmas.debug); 64 | if (!defined(time)) { 65 | throw new DeveloperError('time is required.'); 66 | } 67 | // >>includeEnd('debug'); 68 | 69 | var entities = this._entitiesToVisualize.values; 70 | var hash = this._hash; 71 | var primitives = this._primitives; 72 | 73 | for (var i = 0, len = entities.length; i < len; i++) { 74 | var entity = entities[i]; 75 | var rectangularSensorGraphics = entity._rectangularSensor; 76 | 77 | var position; 78 | var orientation; 79 | var data = hash[entity.id]; 80 | var show = entity.isShowing && entity.isAvailable(time) && Property.getValueOrDefault(rectangularSensorGraphics._show, time, true); 81 | 82 | if (show) { 83 | position = Property.getValueOrUndefined(entity._position, time, cachedPosition); 84 | orientation = Property.getValueOrUndefined(entity._orientation, time, cachedOrientation); 85 | show = defined(position) && defined(orientation); 86 | } 87 | 88 | if (!show) { 89 | // don't bother creating or updating anything else 90 | if (defined(data)) { 91 | data.primitive.show = false; 92 | } 93 | continue; 94 | } 95 | 96 | var primitive = defined(data) ? data.primitive : undefined; 97 | if (!defined(primitive)) { 98 | primitive = new RectangularPyramidSensorVolume(); 99 | primitive.id = entity; 100 | primitives.add(primitive); 101 | 102 | data = { 103 | primitive: primitive, 104 | position: undefined, 105 | orientation: undefined 106 | }; 107 | hash[entity.id] = data; 108 | } 109 | 110 | if (!Cartesian3.equals(position, data.position) || !Quaternion.equals(orientation, data.orientation)) { 111 | Matrix4.fromRotationTranslation(Matrix3.fromQuaternion(orientation, matrix3Scratch), position, primitive.modelMatrix); 112 | data.position = Cartesian3.clone(position, data.position); 113 | data.orientation = Quaternion.clone(orientation, data.orientation); 114 | } 115 | 116 | primitive.show = true; 117 | primitive.xHalfAngle = Property.getValueOrDefault(rectangularSensorGraphics._xHalfAngle, time, CesiumMath.PI_OVER_TWO); 118 | primitive.yHalfAngle = Property.getValueOrDefault(rectangularSensorGraphics._yHalfAngle, time, CesiumMath.PI_OVER_TWO); 119 | primitive.radius = Property.getValueOrDefault(rectangularSensorGraphics._radius, time, defaultRadius); 120 | primitive.lateralSurfaceMaterial = MaterialProperty.getValue(time, rectangularSensorGraphics._lateralSurfaceMaterial, primitive.lateralSurfaceMaterial); 121 | primitive.intersectionColor = Property.getValueOrClonedDefault(rectangularSensorGraphics._intersectionColor, time, defaultIntersectionColor, primitive.intersectionColor); 122 | primitive.intersectionWidth = Property.getValueOrDefault(rectangularSensorGraphics._intersectionWidth, time, defaultIntersectionWidth); 123 | } 124 | return true; 125 | }; 126 | 127 | /** 128 | * Returns true if this object was destroyed; otherwise, false. 129 | * 130 | * @returns {Boolean} True if this object was destroyed; otherwise, false. 131 | */ 132 | RectangularSensorVisualizer.prototype.isDestroyed = function() { 133 | return false; 134 | }; 135 | 136 | /** 137 | * Removes and destroys all primitives created by this instance. 138 | */ 139 | RectangularSensorVisualizer.prototype.destroy = function() { 140 | var entities = this._entitiesToVisualize.values; 141 | var hash = this._hash; 142 | var primitives = this._primitives; 143 | for (var i = entities.length - 1; i > -1; i--) { 144 | removePrimitive(entities[i], hash, primitives); 145 | } 146 | return destroyObject(this); 147 | }; 148 | 149 | /** 150 | * @private 151 | */ 152 | RectangularSensorVisualizer.prototype._onCollectionChanged = function(entityCollection, added, removed, changed) { 153 | var i; 154 | var entity; 155 | var entities = this._entitiesToVisualize; 156 | var hash = this._hash; 157 | var primitives = this._primitives; 158 | 159 | for (i = added.length - 1; i > -1; i--) { 160 | entity = added[i]; 161 | if (defined(entity._rectangularSensor) && defined(entity._position) && defined(entity._orientation)) { 162 | entities.set(entity.id, entity); 163 | } 164 | } 165 | 166 | for (i = changed.length - 1; i > -1; i--) { 167 | entity = changed[i]; 168 | if (defined(entity._rectangularSensor) && defined(entity._position) && defined(entity._orientation)) { 169 | entities.set(entity.id, entity); 170 | } else { 171 | removePrimitive(entity, hash, primitives); 172 | entities.remove(entity.id); 173 | } 174 | } 175 | 176 | for (i = removed.length - 1; i > -1; i--) { 177 | entity = removed[i]; 178 | removePrimitive(entity, hash, primitives); 179 | entities.remove(entity.id); 180 | } 181 | }; 182 | 183 | export default RectangularSensorVisualizer; 184 | -------------------------------------------------------------------------------- /lib/initialize.js: -------------------------------------------------------------------------------- 1 | import { 2 | Cartesian3, 3 | Color, 4 | defined, 5 | Spherical, 6 | TimeInterval, 7 | CzmlDataSource, 8 | DataSourceDisplay 9 | } from 'cesium'; 10 | import ConicSensorGraphics from './conic/conic-sensor-graphics'; 11 | import ConicSensorVisualizer from './conic/conic-sensor-visualizer'; 12 | import CustomPatternSensorGraphics from './custom/custom-pattern-sensor-graphics'; 13 | import CustomPatternSensorVisualizer from './custom/custom-pattern-sensor-visualizer'; 14 | import RectangularSensorGraphics from './rectangular/rectangular-sensor-graphics'; 15 | import RectangularSensorVisualizer from './rectangular/rectangular-sensor-visualizer'; 16 | 17 | var processPacketData = CzmlDataSource.processPacketData; 18 | var processMaterialPacketData = CzmlDataSource.processMaterialPacketData; 19 | 20 | // eslint-disable-next-line max-params 21 | function processDirectionData(customPatternSensor, directions, interval, sourceUri, entityCollection) { 22 | var i; 23 | var len; 24 | var values = []; 25 | var unitSphericals = directions.unitSpherical; 26 | var sphericals = directions.spherical; 27 | var unitCartesians = directions.unitCartesian; 28 | var cartesians = directions.cartesian; 29 | 30 | if (defined(unitSphericals)) { 31 | for (i = 0, len = unitSphericals.length; i < len; i += 2) { 32 | values.push(new Spherical(unitSphericals[i], unitSphericals[i + 1])); 33 | } 34 | directions.array = values; 35 | } else if (defined(sphericals)) { 36 | for (i = 0, len = sphericals.length; i < len; i += 3) { 37 | values.push(new Spherical(sphericals[i], sphericals[i + 1], sphericals[i + 2])); 38 | } 39 | directions.array = values; 40 | } else if (defined(unitCartesians)) { 41 | for (i = 0, len = unitCartesians.length; i < len; i += 3) { 42 | var tmp = Spherical.fromCartesian3(new Cartesian3(unitCartesians[i], unitCartesians[i + 1], unitCartesians[i + 2])); 43 | Spherical.normalize(tmp, tmp); 44 | values.push(tmp); 45 | } 46 | directions.array = values; 47 | } else if (defined(cartesians)) { 48 | for (i = 0, len = cartesians.length; i < len; i += 3) { 49 | values.push(Spherical.fromCartesian3(new Cartesian3(cartesians[i], cartesians[i + 1], cartesians[i + 2]))); 50 | } 51 | directions.array = values; 52 | } 53 | processPacketData(Array, customPatternSensor, 'directions', directions, interval, sourceUri, entityCollection); 54 | } 55 | 56 | // eslint-disable-next-line max-params 57 | function processCommonSensorProperties(sensor, sensorData, interval, sourceUri, entityCollection) { 58 | processPacketData(Boolean, sensor, 'show', sensorData.show, interval, sourceUri, entityCollection); 59 | processPacketData(Number, sensor, 'radius', sensorData.radius, interval, sourceUri, entityCollection); 60 | processPacketData(Boolean, sensor, 'showIntersection', sensorData.showIntersection, interval, sourceUri, entityCollection); 61 | processPacketData(Color, sensor, 'intersectionColor', sensorData.intersectionColor, interval, sourceUri, entityCollection); 62 | processPacketData(Number, sensor, 'intersectionWidth', sensorData.intersectionWidth, interval, sourceUri, entityCollection); 63 | processMaterialPacketData(sensor, 'lateralSurfaceMaterial', sensorData.lateralSurfaceMaterial, interval, sourceUri, entityCollection); 64 | } 65 | 66 | var iso8601Scratch = { 67 | iso8601: undefined 68 | }; 69 | 70 | function processConicSensor(entity, packet, entityCollection, sourceUri) { 71 | var conicSensorData = packet.agi_conicSensor; 72 | if (!defined(conicSensorData)) { 73 | return; 74 | } 75 | 76 | var interval; 77 | var intervalString = conicSensorData.interval; 78 | if (defined(intervalString)) { 79 | iso8601Scratch.iso8601 = intervalString; 80 | interval = TimeInterval.fromIso8601(iso8601Scratch); 81 | } 82 | 83 | var conicSensor = entity.conicSensor; 84 | if (!defined(conicSensor)) { 85 | entity.addProperty('conicSensor'); 86 | conicSensor = new ConicSensorGraphics(); 87 | entity.conicSensor = conicSensor; 88 | } 89 | 90 | processCommonSensorProperties(conicSensor, conicSensorData, interval, sourceUri, entityCollection); 91 | processPacketData(Number, conicSensor, 'innerHalfAngle', conicSensorData.innerHalfAngle, interval, sourceUri, entityCollection); 92 | processPacketData(Number, conicSensor, 'outerHalfAngle', conicSensorData.outerHalfAngle, interval, sourceUri, entityCollection); 93 | processPacketData(Number, conicSensor, 'minimumClockAngle', conicSensorData.minimumClockAngle, interval, sourceUri, entityCollection); 94 | processPacketData(Number, conicSensor, 'maximumClockAngle', conicSensorData.maximumClockAngle, interval, sourceUri, entityCollection); 95 | } 96 | 97 | function processCustomPatternSensor(entity, packet, entityCollection, sourceUri) { 98 | var customPatternSensorData = packet.agi_customPatternSensor; 99 | if (!defined(customPatternSensorData)) { 100 | return; 101 | } 102 | 103 | var interval; 104 | var intervalString = customPatternSensorData.interval; 105 | if (defined(intervalString)) { 106 | iso8601Scratch.iso8601 = intervalString; 107 | interval = TimeInterval.fromIso8601(iso8601Scratch); 108 | } 109 | 110 | var customPatternSensor = entity.customPatternSensor; 111 | if (!defined(customPatternSensor)) { 112 | entity.addProperty('customPatternSensor'); 113 | customPatternSensor = new CustomPatternSensorGraphics(); 114 | entity.customPatternSensor = customPatternSensor; 115 | } 116 | 117 | processCommonSensorProperties(customPatternSensor, customPatternSensorData, interval, sourceUri, entityCollection); 118 | 119 | // The directions property is a special case value that can be an array of unitSpherical or unit Cartesians. 120 | // We pre-process this into Spherical instances and then process it like any other array. 121 | var directions = customPatternSensorData.directions; 122 | if (defined(directions)) { 123 | if (Array.isArray(directions)) { 124 | var length = directions.length; 125 | for (var i = 0; i < length; i++) { 126 | processDirectionData(customPatternSensor, directions[i], interval, sourceUri, entityCollection); 127 | } 128 | } else { 129 | processDirectionData(customPatternSensor, directions, interval, sourceUri, entityCollection); 130 | } 131 | } 132 | } 133 | 134 | function processRectangularSensor(entity, packet, entityCollection, sourceUri) { 135 | var rectangularSensorData = packet.agi_rectangularSensor; 136 | if (!defined(rectangularSensorData)) { 137 | return; 138 | } 139 | 140 | var interval; 141 | var intervalString = rectangularSensorData.interval; 142 | if (defined(intervalString)) { 143 | iso8601Scratch.iso8601 = intervalString; 144 | interval = TimeInterval.fromIso8601(iso8601Scratch); 145 | } 146 | 147 | var rectangularSensor = entity.rectangularSensor; 148 | if (!defined(rectangularSensor)) { 149 | entity.addProperty('rectangularSensor'); 150 | rectangularSensor = new RectangularSensorGraphics(); 151 | entity.rectangularSensor = rectangularSensor; 152 | } 153 | 154 | processCommonSensorProperties(rectangularSensor, rectangularSensorData, interval, sourceUri, entityCollection); 155 | processPacketData(Number, rectangularSensor, 'xHalfAngle', rectangularSensorData.xHalfAngle, interval, sourceUri, entityCollection); 156 | processPacketData(Number, rectangularSensor, 'yHalfAngle', rectangularSensorData.yHalfAngle, interval, sourceUri, entityCollection); 157 | } 158 | 159 | var initialized = false; 160 | 161 | export default 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 | -------------------------------------------------------------------------------- /lib/conic/conic-sensor-visualizer.js: -------------------------------------------------------------------------------- 1 | import { 2 | AssociativeArray, 3 | Cartesian3, 4 | Color, 5 | defined, 6 | destroyObject, 7 | DeveloperError, 8 | Math as CesiumMath, 9 | Matrix3, 10 | Matrix4, 11 | Quaternion, 12 | Spherical, 13 | MaterialProperty, 14 | Property 15 | } from 'cesium'; 16 | import CustomSensorVolume from '../custom/custom-sensor-volume'; 17 | import removePrimitive from '../util/remove-primitive'; 18 | 19 | const defaultIntersectionColor = Color.WHITE; 20 | const defaultIntersectionWidth = 1.0; 21 | const defaultRadius = Number.POSITIVE_INFINITY; 22 | 23 | const matrix3Scratch = new Matrix3(); 24 | const cachedPosition = new Cartesian3(); 25 | const cachedOrientation = new Quaternion(); 26 | 27 | function assignSpherical(index, array, clock, cone) { 28 | var spherical = array[index]; 29 | if (!defined(spherical)) { 30 | spherical = new Spherical(); 31 | array[index] = spherical; 32 | } 33 | spherical.clock = clock; 34 | spherical.cone = cone; 35 | spherical.magnitude = 1.0; 36 | } 37 | 38 | // eslint-disable-next-line max-params 39 | function computeDirections(primitive, minimumClockAngle, maximumClockAngle, innerHalfAngle, outerHalfAngle) { 40 | var directions = primitive.directions; 41 | var angle; 42 | var i = 0; 43 | var angleStep = CesiumMath.toRadians(2.0); 44 | if (minimumClockAngle === 0.0 && maximumClockAngle === CesiumMath.TWO_PI) { 45 | // No clock angle limits, so this is just a circle. 46 | // There might be a hole but we're ignoring it for now. 47 | for (angle = 0.0; angle < CesiumMath.TWO_PI; angle += angleStep) { 48 | assignSpherical(i++, directions, angle, outerHalfAngle); 49 | } 50 | } else { 51 | // There are clock angle limits. 52 | for (angle = minimumClockAngle; angle < maximumClockAngle; angle += angleStep) { 53 | assignSpherical(i++, directions, angle, outerHalfAngle); 54 | } 55 | assignSpherical(i++, directions, maximumClockAngle, outerHalfAngle); 56 | if (innerHalfAngle) { 57 | for (angle = maximumClockAngle; angle > minimumClockAngle; angle -= angleStep) { 58 | assignSpherical(i++, directions, angle, innerHalfAngle); 59 | } 60 | assignSpherical(i++, directions, minimumClockAngle, innerHalfAngle); 61 | } else { 62 | assignSpherical(i++, directions, maximumClockAngle, 0.0); 63 | } 64 | } 65 | directions.length = i; 66 | primitive.directions = directions; 67 | } 68 | 69 | /** 70 | * A {@link Visualizer} which maps {@link Entity#conicSensor} to a {@link ConicSensor}. 71 | * @alias ConicSensorVisualizer 72 | * @constructor 73 | * 74 | * @param {Scene} scene The scene the primitives will be rendered in. 75 | * @param {EntityCollection} entityCollection The entityCollection to visualize. 76 | */ 77 | const ConicSensorVisualizer = function(scene, entityCollection) { 78 | // >>includeStart('debug', pragmas.debug); 79 | if (!defined(scene)) { 80 | throw new DeveloperError('scene is required.'); 81 | } 82 | if (!defined(entityCollection)) { 83 | throw new DeveloperError('entityCollection is required.'); 84 | } 85 | // >>includeEnd('debug'); 86 | 87 | entityCollection.collectionChanged.addEventListener(ConicSensorVisualizer.prototype._onCollectionChanged, this); 88 | 89 | this._scene = scene; 90 | this._primitives = scene.primitives; 91 | this._entityCollection = entityCollection; 92 | this._hash = {}; 93 | this._entitiesToVisualize = new AssociativeArray(); 94 | 95 | this._onCollectionChanged(entityCollection, entityCollection.values, [], []); 96 | }; 97 | 98 | /** 99 | * Updates the primitives created by this visualizer to match their 100 | * Entity counterpart at the given time. 101 | * 102 | * @param {JulianDate} time The time to update to. 103 | * @returns {Boolean} This function always returns true. 104 | */ 105 | ConicSensorVisualizer.prototype.update = function(time) { 106 | // >>includeStart('debug', pragmas.debug); 107 | if (!defined(time)) { 108 | throw new DeveloperError('time is required.'); 109 | } 110 | // >>includeEnd('debug'); 111 | 112 | var entities = this._entitiesToVisualize.values; 113 | var hash = this._hash; 114 | var primitives = this._primitives; 115 | 116 | for (var i = 0, len = entities.length; i < len; i++) { 117 | var entity = entities[i]; 118 | var conicSensorGraphics = entity._conicSensor; 119 | 120 | var position; 121 | var orientation; 122 | var data = hash[entity.id]; 123 | var show = entity.isShowing && entity.isAvailable(time) && Property.getValueOrDefault(conicSensorGraphics._show, time, true); 124 | 125 | if (show) { 126 | position = Property.getValueOrUndefined(entity._position, time, cachedPosition); 127 | orientation = Property.getValueOrUndefined(entity._orientation, time, cachedOrientation); 128 | show = defined(position) && defined(orientation); 129 | } 130 | 131 | if (!show) { 132 | // don't bother creating or updating anything else 133 | if (defined(data)) { 134 | data.primitive.show = false; 135 | } 136 | continue; 137 | } 138 | 139 | var primitive = defined(data) ? data.primitive : undefined; 140 | if (!defined(primitive)) { 141 | primitive = new CustomSensorVolume(); 142 | primitive.id = entity; 143 | primitives.add(primitive); 144 | 145 | data = { 146 | primitive: primitive, 147 | position: undefined, 148 | orientation: undefined, 149 | minimumClockAngle: undefined, 150 | maximumClockAngle: undefined, 151 | innerHalfAngle: undefined, 152 | outerHalfAngle: undefined 153 | }; 154 | hash[entity.id] = data; 155 | } 156 | 157 | if (!Cartesian3.equals(position, data.position) || !Quaternion.equals(orientation, data.orientation)) { 158 | Matrix4.fromRotationTranslation(Matrix3.fromQuaternion(orientation, matrix3Scratch), position, primitive.modelMatrix); 159 | data.position = Cartesian3.clone(position, data.position); 160 | data.orientation = Quaternion.clone(orientation, data.orientation); 161 | } 162 | 163 | primitive.show = true; 164 | var minimumClockAngle = Property.getValueOrDefault(conicSensorGraphics._minimumClockAngle, time, 0); 165 | var maximumClockAngle = Property.getValueOrDefault(conicSensorGraphics._maximumClockAngle, time, CesiumMath.TWO_PI); 166 | var innerHalfAngle = Property.getValueOrDefault(conicSensorGraphics._innerHalfAngle, time, 0); 167 | var outerHalfAngle = Property.getValueOrDefault(conicSensorGraphics._outerHalfAngle, time, Math.PI); 168 | 169 | if (minimumClockAngle !== data.minimumClockAngle || 170 | maximumClockAngle !== data.maximumClockAngle || 171 | innerHalfAngle !== data.innerHalfAngle || 172 | outerHalfAngle !== data.outerHalfAngle 173 | ) { 174 | computeDirections(primitive, minimumClockAngle, maximumClockAngle, innerHalfAngle, outerHalfAngle); 175 | data.innerHalfAngle = innerHalfAngle; 176 | data.maximumClockAngle = maximumClockAngle; 177 | data.outerHalfAngle = outerHalfAngle; 178 | data.minimumClockAngle = minimumClockAngle; 179 | } 180 | 181 | primitive.radius = Property.getValueOrDefault(conicSensorGraphics._radius, time, defaultRadius); 182 | primitive.lateralSurfaceMaterial = MaterialProperty.getValue(time, conicSensorGraphics._lateralSurfaceMaterial, primitive.lateralSurfaceMaterial); 183 | primitive.intersectionColor = Property.getValueOrClonedDefault(conicSensorGraphics._intersectionColor, time, defaultIntersectionColor, primitive.intersectionColor); 184 | primitive.intersectionWidth = Property.getValueOrDefault(conicSensorGraphics._intersectionWidth, time, defaultIntersectionWidth); 185 | } 186 | return true; 187 | }; 188 | 189 | /** 190 | * Returns true if this object was destroyed; otherwise, false. 191 | * 192 | * @returns {Boolean} True if this object was destroyed; otherwise, false. 193 | */ 194 | ConicSensorVisualizer.prototype.isDestroyed = function() { 195 | return false; 196 | }; 197 | 198 | /** 199 | * Removes and destroys all primitives created by this instance. 200 | */ 201 | ConicSensorVisualizer.prototype.destroy = function() { 202 | var entities = this._entitiesToVisualize.values; 203 | var hash = this._hash; 204 | var primitives = this._primitives; 205 | for (var i = entities.length - 1; i > -1; i--) { 206 | removePrimitive(entities[i], hash, primitives); 207 | } 208 | return destroyObject(this); 209 | }; 210 | 211 | /** 212 | * @private 213 | */ 214 | ConicSensorVisualizer.prototype._onCollectionChanged = function(entityCollection, added, removed, changed) { 215 | var i; 216 | var entity; 217 | var entities = this._entitiesToVisualize; 218 | var hash = this._hash; 219 | var primitives = this._primitives; 220 | 221 | for (i = added.length - 1; i > -1; i--) { 222 | entity = added[i]; 223 | if (defined(entity._conicSensor) && defined(entity._position) && defined(entity._orientation)) { 224 | entities.set(entity.id, entity); 225 | } 226 | } 227 | 228 | for (i = changed.length - 1; i > -1; i--) { 229 | entity = changed[i]; 230 | if (defined(entity._conicSensor) && defined(entity._position) && defined(entity._orientation)) { 231 | entities.set(entity.id, entity); 232 | } else { 233 | removePrimitive(entity, hash, primitives); 234 | entities.remove(entity.id); 235 | } 236 | } 237 | 238 | for (i = removed.length - 1; i > -1; i--) { 239 | entity = removed[i]; 240 | removePrimitive(entity, hash, primitives); 241 | entities.remove(entity.id); 242 | } 243 | }; 244 | 245 | export default ConicSensorVisualizer; 246 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | import { 2 | BoundingSphere, 3 | Cartesian3, 4 | Color, 5 | combine, 6 | ComponentDatatype, 7 | defined, 8 | destroyObject, 9 | DeveloperError, 10 | Frozen, 11 | Matrix4, 12 | PrimitiveType, 13 | Buffer, 14 | BufferUsage, 15 | DrawCommand, 16 | Pass, 17 | RenderState, 18 | ShaderProgram, 19 | ShaderSource, 20 | VertexArray, 21 | BlendingState, 22 | CullFace, 23 | Material, 24 | SceneMode 25 | } from 'cesium'; 26 | 27 | // eslint-disable-next-line import/extensions 28 | import SensorVolume from '../sensor-volume.glsl'; 29 | // eslint-disable-next-line import/extensions 30 | import CustomSensorVolumeFS from './custom-sensor-volume-fs.glsl'; 31 | // eslint-disable-next-line import/extensions 32 | import CustomSensorVolumeVS from './custom-sensor-volume-vs.glsl'; 33 | 34 | const attributeLocations = { 35 | position: 0, 36 | normal: 1 37 | }; 38 | 39 | const FAR = 5906376272000.0; // distance from the Sun to Pluto in meters. 40 | 41 | /** 42 | * DOC_TBA 43 | * 44 | * @alias CustomSensorVolume 45 | * @constructor 46 | */ 47 | const CustomSensorVolume = function(options) { 48 | options = options ?? Frozen.EMPTY_OBJECT; 49 | 50 | this._pickId = undefined; 51 | this._pickPrimitive = options._pickPrimitive ?? this; 52 | 53 | this._frontFaceColorCommand = new DrawCommand(); 54 | this._backFaceColorCommand = new DrawCommand(); 55 | this._pickCommand = new DrawCommand(); 56 | 57 | this._boundingSphere = new BoundingSphere(); 58 | this._boundingSphereWC = new BoundingSphere(); 59 | 60 | this._frontFaceColorCommand.primitiveType = PrimitiveType.TRIANGLES; 61 | this._frontFaceColorCommand.boundingVolume = this._boundingSphereWC; 62 | this._frontFaceColorCommand.owner = this; 63 | 64 | this._backFaceColorCommand.primitiveType = this._frontFaceColorCommand.primitiveType; 65 | this._backFaceColorCommand.boundingVolume = this._frontFaceColorCommand.boundingVolume; 66 | this._backFaceColorCommand.owner = this; 67 | 68 | this._pickCommand.primitiveType = this._frontFaceColorCommand.primitiveType; 69 | this._pickCommand.boundingVolume = this._frontFaceColorCommand.boundingVolume; 70 | this._pickCommand.owner = this; 71 | 72 | /** 73 | * true if this sensor will be shown; otherwise, false 74 | * 75 | * @type {Boolean} 76 | * @default true 77 | */ 78 | this.show = options.show ?? true; 79 | 80 | /** 81 | * When true, a polyline is shown where the sensor outline intersections the globe. 82 | * 83 | * @type {Boolean} 84 | * 85 | * @default true 86 | * 87 | * @see CustomSensorVolume#intersectionColor 88 | */ 89 | this.showIntersection = options.showIntersection ?? true; 90 | 91 | /** 92 | *

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

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

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

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

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

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

339 | * 340 | * @exception {DeveloperError} this.radius must be greater than or equal to zero. 341 | * @exception {DeveloperError} this.lateralSurfaceMaterial must be defined. 342 | */ 343 | // eslint-disable-next-line complexity 344 | CustomSensorVolume.prototype.update = function(frameState) { 345 | this._mode = frameState.mode; 346 | if (!this.show || this._mode !== SceneMode.SCENE3D) { 347 | return; 348 | } 349 | 350 | var context = frameState.context; 351 | var commandList = frameState.commandList; 352 | 353 | // >>includeStart('debug', pragmas.debug); 354 | if (this.radius < 0.0) { 355 | throw new DeveloperError('this.radius must be greater than or equal to zero.'); 356 | } 357 | if (!defined(this.lateralSurfaceMaterial)) { 358 | throw new DeveloperError('this.lateralSurfaceMaterial must be defined.'); 359 | } 360 | // >>includeEnd('debug'); 361 | 362 | var translucent = this.lateralSurfaceMaterial.isTranslucent(); 363 | 364 | // Initial render state creation 365 | if ((this._showThroughEllipsoid !== this.showThroughEllipsoid) || 366 | (!defined(this._frontFaceColorCommand.renderState)) || 367 | (this._translucent !== translucent) 368 | ) { 369 | this._showThroughEllipsoid = this.showThroughEllipsoid; 370 | this._translucent = translucent; 371 | 372 | var rs; 373 | 374 | if (translucent) { 375 | rs = RenderState.fromCache({ 376 | depthTest: { 377 | // This would be better served by depth testing with a depth buffer that does not 378 | // include the ellipsoid depth - or a g-buffer containing an ellipsoid mask 379 | // so we can selectively depth test. 380 | enabled: !this.showThroughEllipsoid 381 | }, 382 | depthMask: false, 383 | blending: BlendingState.ALPHA_BLEND, 384 | cull: { 385 | enabled: true, 386 | face: CullFace.BACK 387 | } 388 | }); 389 | 390 | this._frontFaceColorCommand.renderState = rs; 391 | this._frontFaceColorCommand.pass = Pass.TRANSLUCENT; 392 | 393 | rs = RenderState.fromCache({ 394 | depthTest: { 395 | enabled: !this.showThroughEllipsoid 396 | }, 397 | depthMask: false, 398 | blending: BlendingState.ALPHA_BLEND, 399 | cull: { 400 | enabled: true, 401 | face: CullFace.FRONT 402 | } 403 | }); 404 | 405 | this._backFaceColorCommand.renderState = rs; 406 | this._backFaceColorCommand.pass = Pass.TRANSLUCENT; 407 | 408 | rs = RenderState.fromCache({ 409 | depthTest: { 410 | enabled: !this.showThroughEllipsoid 411 | }, 412 | depthMask: false, 413 | blending: BlendingState.ALPHA_BLEND 414 | }); 415 | this._pickCommand.renderState = rs; 416 | } else { 417 | rs = RenderState.fromCache({ 418 | depthTest: { 419 | enabled: true 420 | }, 421 | depthMask: true 422 | }); 423 | this._frontFaceColorCommand.renderState = rs; 424 | this._frontFaceColorCommand.pass = Pass.OPAQUE; 425 | 426 | rs = RenderState.fromCache({ 427 | depthTest: { 428 | enabled: true 429 | }, 430 | depthMask: true 431 | }); 432 | this._pickCommand.renderState = rs; 433 | } 434 | } 435 | 436 | // Recreate vertex buffer when directions change 437 | var directionsChanged = this._directionsDirty; 438 | if (directionsChanged) { 439 | this._directionsDirty = false; 440 | this._va = this._va && this._va.destroy(); 441 | 442 | var directions = this._directions; 443 | if (directions && (directions.length >= 3)) { 444 | this._frontFaceColorCommand.vertexArray = createVertexArray(this, context); 445 | this._backFaceColorCommand.vertexArray = this._frontFaceColorCommand.vertexArray; 446 | this._pickCommand.vertexArray = this._frontFaceColorCommand.vertexArray; 447 | } 448 | } 449 | 450 | if (!defined(this._frontFaceColorCommand.vertexArray)) { 451 | return; 452 | } 453 | 454 | var pass = frameState.passes; 455 | 456 | var modelMatrixChanged = !Matrix4.equals(this.modelMatrix, this._modelMatrix); 457 | if (modelMatrixChanged) { 458 | Matrix4.clone(this.modelMatrix, this._modelMatrix); 459 | } 460 | 461 | if (directionsChanged || modelMatrixChanged) { 462 | BoundingSphere.transform(this._boundingSphere, this.modelMatrix, this._boundingSphereWC); 463 | } 464 | 465 | this._frontFaceColorCommand.modelMatrix = this.modelMatrix; 466 | this._backFaceColorCommand.modelMatrix = this._frontFaceColorCommand.modelMatrix; 467 | this._pickCommand.modelMatrix = this._frontFaceColorCommand.modelMatrix; 468 | 469 | var materialChanged = this._lateralSurfaceMaterial !== this.lateralSurfaceMaterial; 470 | this._lateralSurfaceMaterial = this.lateralSurfaceMaterial; 471 | this._lateralSurfaceMaterial.update(context); 472 | 473 | if (pass.render) { 474 | var frontFaceColorCommand = this._frontFaceColorCommand; 475 | var backFaceColorCommand = this._backFaceColorCommand; 476 | 477 | // Recompile shader when material changes 478 | if (materialChanged || !defined(frontFaceColorCommand.shaderProgram)) { 479 | var fsSource = new ShaderSource({ 480 | sources: [SensorVolume, this._lateralSurfaceMaterial.shaderSource, CustomSensorVolumeFS] 481 | }); 482 | 483 | frontFaceColorCommand.shaderProgram = ShaderProgram.replaceCache({ 484 | context: context, 485 | shaderProgram: frontFaceColorCommand.shaderProgram, 486 | vertexShaderSource: CustomSensorVolumeVS, 487 | fragmentShaderSource: fsSource, 488 | attributeLocations: attributeLocations 489 | }); 490 | 491 | frontFaceColorCommand.uniformMap = combine(this._uniforms, this._lateralSurfaceMaterial._uniforms); 492 | 493 | backFaceColorCommand.shaderProgram = frontFaceColorCommand.shaderProgram; 494 | backFaceColorCommand.uniformMap = combine(this._uniforms, this._lateralSurfaceMaterial._uniforms); 495 | // eslint-disable-next-line camelcase 496 | backFaceColorCommand.uniformMap.u_normalDirection = function() { 497 | return -1.0; 498 | }; 499 | } 500 | 501 | if (translucent) { 502 | commandList.push(this._backFaceColorCommand, this._frontFaceColorCommand); 503 | } else { 504 | commandList.push(this._frontFaceColorCommand); 505 | } 506 | } 507 | 508 | if (pass.pick) { 509 | var pickCommand = this._pickCommand; 510 | 511 | if (!defined(this._pickId) || (this._id !== this.id)) { 512 | this._id = this.id; 513 | this._pickId = this._pickId && this._pickId.destroy(); 514 | this._pickId = context.createPickId({ 515 | primitive: this._pickPrimitive, 516 | id: this.id 517 | }); 518 | } 519 | 520 | // Recompile shader when material changes 521 | if (materialChanged || !defined(pickCommand.shaderProgram)) { 522 | var pickFS = new ShaderSource({ 523 | sources: [SensorVolume, this._lateralSurfaceMaterial.shaderSource, CustomSensorVolumeFS], 524 | pickColorQualifier: 'uniform' 525 | }); 526 | 527 | pickCommand.shaderProgram = ShaderProgram.replaceCache({ 528 | context: context, 529 | shaderProgram: pickCommand.shaderProgram, 530 | vertexShaderSource: CustomSensorVolumeVS, 531 | fragmentShaderSource: pickFS, 532 | attributeLocations: attributeLocations 533 | }); 534 | 535 | var that = this; 536 | var uniforms = { 537 | // eslint-disable-next-line camelcase 538 | czm_pickColor: function() { 539 | return that._pickId.color; 540 | } 541 | }; 542 | pickCommand.uniformMap = combine(combine(this._uniforms, this._lateralSurfaceMaterial._uniforms), uniforms); 543 | } 544 | 545 | pickCommand.pass = translucent ? Pass.TRANSLUCENT : Pass.OPAQUE; 546 | commandList.push(pickCommand); 547 | } 548 | }; 549 | 550 | /** 551 | * DOC_TBA 552 | */ 553 | CustomSensorVolume.prototype.isDestroyed = function() { 554 | return false; 555 | }; 556 | 557 | /** 558 | * DOC_TBA 559 | */ 560 | CustomSensorVolume.prototype.destroy = function() { 561 | this._frontFaceColorCommand.vertexArray = this._frontFaceColorCommand.vertexArray && this._frontFaceColorCommand.vertexArray.destroy(); 562 | this._frontFaceColorCommand.shaderProgram = this._frontFaceColorCommand.shaderProgram && this._frontFaceColorCommand.shaderProgram.destroy(); 563 | this._pickCommand.shaderProgram = this._pickCommand.shaderProgram && this._pickCommand.shaderProgram.destroy(); 564 | this._pickId = this._pickId && this._pickId.destroy(); 565 | return destroyObject(this); 566 | }; 567 | 568 | export default CustomSensorVolume; 569 | -------------------------------------------------------------------------------- /dist/cesium-sensor-volumes.es.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Cesium Sensor Volumes - https://github.com/Flowm/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/Flowm/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 | import{createPropertyDescriptor as i,createMaterialPropertyDescriptor as t,defined as e,DeveloperError as n,Event as o,Frozen as r,Cartesian3 as s,SceneMode as a,RenderState as l,BlendingState as h,CullFace as c,Pass as u,Matrix4 as d,BoundingSphere as m,ShaderSource as f,ShaderProgram as _,combine as p,destroyObject as C,DrawCommand as g,PrimitiveType as v,Material as w,Color as S,Buffer as y,BufferUsage as A,ComponentDatatype as M,VertexArray as I,Matrix3 as x,Quaternion as T,AssociativeArray as b,Property as E,Math as k,MaterialProperty as H,Spherical as V,clone as W,CzmlDataSource as P,DataSourceDisplay as O,TimeInterval as F}from"cesium";const z=function(i){this._minimumClockAngle=void 0,this._minimumClockAngleSubscription=void 0,this._maximumClockAngle=void 0,this._maximumClockAngleSubscription=void 0,this._innerHalfAngle=void 0,this._innerHalfAngleSubscription=void 0,this._outerHalfAngle=void 0,this._outerHalfAngleSubscription=void 0,this._lateralSurfaceMaterial=void 0,this._lateralSurfaceMaterialSubscription=void 0,this._intersectionColor=void 0,this._intersectionColorSubscription=void 0,this._intersectionWidth=void 0,this._intersectionWidthSubscription=void 0,this._showIntersection=void 0,this._showIntersectionSubscription=void 0,this._radius=void 0,this._radiusSubscription=void 0,this._show=void 0,this._showSubscription=void 0,this._definitionChanged=new o,this.merge(i??r.EMPTY_OBJECT)};Object.defineProperties(z.prototype,{definitionChanged:{get:function(){return this._definitionChanged}},minimumClockAngle:i("minimumClockAngle"),maximumClockAngle:i("maximumClockAngle"),innerHalfAngle:i("innerHalfAngle"),outerHalfAngle:i("outerHalfAngle"),lateralSurfaceMaterial:t("lateralSurfaceMaterial"),intersectionColor:i("intersectionColor"),intersectionWidth:i("intersectionWidth"),showIntersection:i("showIntersection"),radius:i("radius"),show:i("show")}),z.prototype.clone=function(i){return e(i)||(i=new z),i.show=this.show,i.innerHalfAngle=this.innerHalfAngle,i.outerHalfAngle=this.outerHalfAngle,i.minimumClockAngle=this.minimumClockAngle,i.maximumClockAngle=this.maximumClockAngle,i.radius=this.radius,i.showIntersection=this.showIntersection,i.intersectionColor=this.intersectionColor,i.intersectionWidth=this.intersectionWidth,i.lateralSurfaceMaterial=this.lateralSurfaceMaterial,i},z.prototype.merge=function(i){if(!e(i))throw new n("source is required.");this.show=this.show??i.show,this.innerHalfAngle=this.innerHalfAngle??i.innerHalfAngle,this.outerHalfAngle=this.outerHalfAngle??i.outerHalfAngle,this.minimumClockAngle=this.minimumClockAngle??i.minimumClockAngle,this.maximumClockAngle=this.maximumClockAngle??i.maximumClockAngle,this.radius=this.radius??i.radius,this.showIntersection=this.showIntersection??i.showIntersection,this.intersectionColor=this.intersectionColor??i.intersectionColor,this.intersectionWidth=this.intersectionWidth??i.intersectionWidth,this.lateralSurfaceMaterial=this.lateralSurfaceMaterial??i.lateralSurfaceMaterial};var D="#version 300 es\n\nuniform vec4 u_intersectionColor;\nuniform float u_intersectionWidth;\n\nbool inSensorShadow(vec3 coneVertexWC, vec3 pointWC)\n{\n \n vec3 D = czm_ellipsoidInverseRadii;\n\n \n vec3 q = D * coneVertexWC;\n float qMagnitudeSquared = dot(q, q);\n float test = qMagnitudeSquared - 1.0;\n\n \n vec3 temp = D * pointWC - q;\n float d = dot(temp, q);\n\n \n return (d < -test) && (d / length(temp) < -sqrt(test));\n}\n\nvec4 getIntersectionColor()\n{\n return u_intersectionColor;\n}\n\nfloat getIntersectionWidth()\n{\n return u_intersectionWidth;\n}\n\nvec2 sensor2dTextureCoordinates(float sensorRadius, vec3 pointMC)\n{\n \n float t = pointMC.z / sensorRadius;\n float s = 1.0 + (atan(pointMC.y, pointMC.x) / czm_twoPi);\n s = s - floor(s);\n\n return vec2(s, t);\n}\n",R="#version 300 es\n\nuniform bool u_showIntersection;\nuniform bool u_showThroughEllipsoid;\n\nuniform float u_sensorRadius;\nuniform float u_normalDirection;\n\nin vec3 v_positionWC;\nin vec3 v_positionEC;\nin vec3 v_normalEC;\n\nvec4 getColor(float sensorRadius, vec3 pointEC)\n{\n czm_materialInput materialInput;\n\n vec3 pointMC = (czm_inverseModelView * vec4(pointEC, 1.0)).xyz;\n materialInput.st = sensor2dTextureCoordinates(sensorRadius, pointMC);\n materialInput.str = pointMC / sensorRadius;\n\n vec3 positionToEyeEC = -v_positionEC;\n materialInput.positionToEyeEC = positionToEyeEC;\n\n vec3 normalEC = normalize(v_normalEC);\n materialInput.normalEC = u_normalDirection * normalEC;\n\n czm_material material = czm_getMaterial(materialInput);\n return mix(czm_phong(normalize(positionToEyeEC), material, czm_lightDirectionEC), vec4(material.diffuse, material.alpha), 0.4);\n}\n\nbool isOnBoundary(float value, float epsilon)\n{\n float width = getIntersectionWidth();\n float tolerance = width * epsilon;\n\n float delta = max(abs(dFdx(value)), abs(dFdy(value)));\n float pixels = width * delta;\n float temp = abs(value);\n \n \n \n \n \n \n \n return temp < tolerance && temp < pixels || (delta < 10.0 * tolerance && temp - delta < tolerance && temp < pixels);\n}\n\nvec4 shade(bool isOnBoundary)\n{\n if (u_showIntersection && isOnBoundary)\n {\n return getIntersectionColor();\n }\n return getColor(u_sensorRadius, v_positionEC);\n}\n\nfloat ellipsoidSurfaceFunction(vec3 point)\n{\n vec3 scaled = czm_ellipsoidInverseRadii * point;\n return dot(scaled, scaled) - 1.0;\n}\n\nvoid main()\n{\n vec3 sensorVertexWC = czm_model[3].xyz; \n vec3 sensorVertexEC = czm_modelView[3].xyz; \n\n float ellipsoidValue = ellipsoidSurfaceFunction(v_positionWC);\n\n \n if (!u_showThroughEllipsoid)\n {\n \n \n if (ellipsoidValue < 0.0)\n {\n discard;\n }\n\n \n if (inSensorShadow(sensorVertexWC, v_positionWC))\n {\n discard;\n }\n }\n\n \n \n if (distance(v_positionEC, sensorVertexEC) > u_sensorRadius)\n {\n discard;\n }\n\n \n bool isOnEllipsoid = isOnBoundary(ellipsoidValue, czm_epsilon3);\n out_FragColor = shade(isOnEllipsoid);\n}\n",N="#version 300 es\n\nin vec4 position;\nin vec3 normal;\n\nout vec3 v_positionWC;\nout vec3 v_positionEC;\nout vec3 v_normalEC;\n\nvoid main()\n{\n gl_Position = czm_modelViewProjection * position;\n v_positionWC = (czm_model * position).xyz;\n v_positionEC = (czm_modelView * position).xyz;\n v_normalEC = czm_normal * normal;\n}\n";const q={position:0,normal:1},B=5906376272e3,L=function(i){i=i??r.EMPTY_OBJECT,this._pickId=void 0,this._pickPrimitive=i._pickPrimitive??this,this._frontFaceColorCommand=new g,this._backFaceColorCommand=new g,this._pickCommand=new g,this._boundingSphere=new m,this._boundingSphereWC=new m,this._frontFaceColorCommand.primitiveType=v.TRIANGLES,this._frontFaceColorCommand.boundingVolume=this._boundingSphereWC,this._frontFaceColorCommand.owner=this,this._backFaceColorCommand.primitiveType=this._frontFaceColorCommand.primitiveType,this._backFaceColorCommand.boundingVolume=this._frontFaceColorCommand.boundingVolume,this._backFaceColorCommand.owner=this,this._pickCommand.primitiveType=this._frontFaceColorCommand.primitiveType,this._pickCommand.boundingVolume=this._frontFaceColorCommand.boundingVolume,this._pickCommand.owner=this,this.show=i.show??!0,this.showIntersection=i.showIntersection??!0,this.showThroughEllipsoid=i.showThroughEllipsoid??!1,this._showThroughEllipsoid=this.showThroughEllipsoid,this.modelMatrix=d.clone(i.modelMatrix??d.IDENTITY),this._modelMatrix=new d,this.radius=i.radius??Number.POSITIVE_INFINITY,this._directions=void 0,this._directionsDirty=!1,this.directions=e(i.directions)?i.directions:[],this.lateralSurfaceMaterial=e(i.lateralSurfaceMaterial)?i.lateralSurfaceMaterial:w.fromType(w.ColorType),this._lateralSurfaceMaterial=void 0,this._translucent=void 0,this.intersectionColor=S.clone(i.intersectionColor??S.WHITE),this.intersectionWidth=i.intersectionWidth??5,this.id=i.id,this._id=void 0;var t=this;this._uniforms={u_showThroughEllipsoid:function(){return t.showThroughEllipsoid},u_showIntersection:function(){return t.showIntersection},u_sensorRadius:function(){return isFinite(t.radius)?t.radius:B},u_intersectionColor:function(){return t.intersectionColor},u_intersectionWidth:function(){return t.intersectionWidth},u_normalDirection:function(){return 1}},this._mode=a.SCENE3D};Object.defineProperties(L.prototype,{directions:{get:function(){return this._directions},set:function(i){this._directions=i,this._directionsDirty=!0}}});const U=new s,Y=new s,j=new s;const Q=new s;function G(i,t){for(var e=function(i){for(var t=i._directions,e=t.length,n=new Float32Array(3*e),o=isFinite(i.radius)?i.radius:B,r=[s.ZERO],a=e-2,l=e-1,h=0;h=3&&(this._frontFaceColorCommand.vertexArray=G(this,t),this._backFaceColorCommand.vertexArray=this._frontFaceColorCommand.vertexArray,this._pickCommand.vertexArray=this._frontFaceColorCommand.vertexArray)}if(e(this._frontFaceColorCommand.vertexArray)){var v=i.passes,w=!d.equals(this.modelMatrix,this._modelMatrix);w&&d.clone(this.modelMatrix,this._modelMatrix),(C||w)&&m.transform(this._boundingSphere,this.modelMatrix,this._boundingSphereWC),this._frontFaceColorCommand.modelMatrix=this.modelMatrix,this._backFaceColorCommand.modelMatrix=this._frontFaceColorCommand.modelMatrix,this._pickCommand.modelMatrix=this._frontFaceColorCommand.modelMatrix;var S=this._lateralSurfaceMaterial!==this.lateralSurfaceMaterial;if(this._lateralSurfaceMaterial=this.lateralSurfaceMaterial,this._lateralSurfaceMaterial.update(t),v.render){var y=this._frontFaceColorCommand,A=this._backFaceColorCommand;if(S||!e(y.shaderProgram)){var M=new f({sources:[D,this._lateralSurfaceMaterial.shaderSource,R]});y.shaderProgram=_.replaceCache({context:t,shaderProgram:y.shaderProgram,vertexShaderSource:N,fragmentShaderSource:M,attributeLocations:q}),y.uniformMap=p(this._uniforms,this._lateralSurfaceMaterial._uniforms),A.shaderProgram=y.shaderProgram,A.uniformMap=p(this._uniforms,this._lateralSurfaceMaterial._uniforms),A.uniformMap.u_normalDirection=function(){return-1}}s?o.push(this._backFaceColorCommand,this._frontFaceColorCommand):o.push(this._frontFaceColorCommand)}if(v.pick){var I=this._pickCommand;if(e(this._pickId)&&this._id===this.id||(this._id=this.id,this._pickId=this._pickId&&this._pickId.destroy(),this._pickId=t.createPickId({primitive:this._pickPrimitive,id:this.id})),S||!e(I.shaderProgram)){var x=new f({sources:[D,this._lateralSurfaceMaterial.shaderSource,R],pickColorQualifier:"uniform"});I.shaderProgram=_.replaceCache({context:t,shaderProgram:I.shaderProgram,vertexShaderSource:N,fragmentShaderSource:x,attributeLocations:q});var T=this,b={czm_pickColor:function(){return T._pickId.color}};I.uniformMap=p(p(this._uniforms,this._lateralSurfaceMaterial._uniforms),b)}I.pass=s?u.TRANSLUCENT:u.OPAQUE,o.push(I)}}}},L.prototype.isDestroyed=function(){return!1},L.prototype.destroy=function(){return this._frontFaceColorCommand.vertexArray=this._frontFaceColorCommand.vertexArray&&this._frontFaceColorCommand.vertexArray.destroy(),this._frontFaceColorCommand.shaderProgram=this._frontFaceColorCommand.shaderProgram&&this._frontFaceColorCommand.shaderProgram.destroy(),this._pickCommand.shaderProgram=this._pickCommand.shaderProgram&&this._pickCommand.shaderProgram.destroy(),this._pickId=this._pickId&&this._pickId.destroy(),C(this)};const K=S.WHITE,Z=Number.POSITIVE_INFINITY,X=new x,$=new s,ii=new T;function ti(i,t,n,o){var r=t[i];e(r)||(r=new V,t[i]=r),r.clock=n,r.cone=o,r.magnitude=1}function ei(i,t,e,n,o){var r,s=i.directions,a=0,l=k.toRadians(2);if(0===t&&e===k.TWO_PI)for(r=0;rt;r-=l)ti(a++,s,r,n);ti(a++,s,t,n)}else ti(a++,s,e,0)}s.length=a,i.directions=s}const ni=function(i,t){if(!e(i))throw new n("scene is required.");if(!e(t))throw new n("entityCollection is required.");t.collectionChanged.addEventListener(ni.prototype._onCollectionChanged,this),this._scene=i,this._primitives=i.primitives,this._entityCollection=t,this._hash={},this._entitiesToVisualize=new b,this._onCollectionChanged(t,t.values,[],[])};ni.prototype.update=function(i){if(!e(i))throw new n("time is required.");for(var t=this._entitiesToVisualize.values,o=this._hash,r=this._primitives,a=0,l=t.length;a-1;n--)J(i[n],t,e);return C(this)},ni.prototype._onCollectionChanged=function(i,t,n,o){var r,s,a=this._entitiesToVisualize,l=this._hash,h=this._primitives;for(r=t.length-1;r>-1;r--)s=t[r],e(s._conicSensor)&&e(s._position)&&e(s._orientation)&&a.set(s.id,s);for(r=o.length-1;r>-1;r--)s=o[r],e(s._conicSensor)&&e(s._position)&&e(s._orientation)?a.set(s.id,s):(J(s,l,h),a.remove(s.id));for(r=n.length-1;r>-1;r--)J(s=n[r],l,h),a.remove(s.id)};const oi=function(i){this._directions=void 0,this._directionsSubscription=void 0,this._lateralSurfaceMaterial=void 0,this._lateralSurfaceMaterialSubscription=void 0,this._intersectionColor=void 0,this._intersectionColorSubscription=void 0,this._intersectionWidth=void 0,this._intersectionWidthSubscription=void 0,this._showIntersection=void 0,this._showIntersectionSubscription=void 0,this._radius=void 0,this._radiusSubscription=void 0,this._show=void 0,this._showSubscription=void 0,this._definitionChanged=new o,this.merge(i??r.EMPTY_OBJECT)};Object.defineProperties(oi.prototype,{definitionChanged:{get:function(){return this._definitionChanged}},directions:i("directions"),lateralSurfaceMaterial:t("lateralSurfaceMaterial"),intersectionColor:i("intersectionColor"),intersectionWidth:i("intersectionWidth"),showIntersection:i("showIntersection"),radius:i("radius"),show:i("show")}),oi.prototype.clone=function(i){return e(i)||(i=new oi),i.directions=this.directions,i.radius=this.radius,i.show=this.show,i.showIntersection=this.showIntersection,i.intersectionColor=this.intersectionColor,i.intersectionWidth=this.intersectionWidth,i.lateralSurfaceMaterial=this.lateralSurfaceMaterial,i},oi.prototype.merge=function(i){if(!e(i))throw new n("source is required.");this.directions=this.directions??i.directions,this.radius=this.radius??i.radius,this.show=this.show??i.show,this.showIntersection=this.showIntersection??i.showIntersection,this.intersectionColor=this.intersectionColor??i.intersectionColor,this.intersectionWidth=this.intersectionWidth??i.intersectionWidth,this.lateralSurfaceMaterial=this.lateralSurfaceMaterial??i.lateralSurfaceMaterial};const ri=S.WHITE,si=Number.POSITIVE_INFINITY,ai=new x,li=new s,hi=new T,ci=function(i,t){if(!e(i))throw new n("scene is required.");if(!e(t))throw new n("entityCollection is required.");t.collectionChanged.addEventListener(ci.prototype._onCollectionChanged,this),this._scene=i,this._primitives=i.primitives,this._entityCollection=t,this._hash={},this._entitiesToVisualize=new b,this._onCollectionChanged(t,t.values,[],[])};ci.prototype.update=function(i){if(!e(i))throw new n("time is required.");for(var t=this._entitiesToVisualize.values,o=this._hash,r=this._primitives,a=0,l=t.length;a-1;n--)J(i[n],t,e);return C(this)},ci.prototype._onCollectionChanged=function(i,t,n,o){var r,s,a=this._entitiesToVisualize,l=this._hash,h=this._primitives;for(r=t.length-1;r>-1;r--)s=t[r],e(s._customPatternSensor)&&e(s._position)&&e(s._orientation)&&a.set(s.id,s);for(r=o.length-1;r>-1;r--)s=o[r],e(s._customPatternSensor)&&e(s._position)&&e(s._orientation)?a.set(s.id,s):(J(s,l,h),a.remove(s.id));for(r=n.length-1;r>-1;r--)J(s=n[r],l,h),a.remove(s.id)};const ui=function(){this._xHalfAngle=void 0,this._xHalfAngleSubscription=void 0,this._yHalfAngle=void 0,this._yHalfAngleSubscription=void 0,this._lateralSurfaceMaterial=void 0,this._lateralSurfaceMaterialSubscription=void 0,this._intersectionColor=void 0,this._intersectionColorSubscription=void 0,this._intersectionWidth=void 0,this._intersectionWidthSubscription=void 0,this._showIntersection=void 0,this._showIntersectionSubscription=void 0,this._radius=void 0,this._radiusSubscription=void 0,this._show=void 0,this._showSubscription=void 0,this._definitionChanged=new o};function di(i,t,n,o){var r=t[i];e(r)||(r=new V,t[i]=r),r.clock=n,r.cone=o,r.magnitude=1}function mi(i){var t=i._customSensor.directions,e=Math.tan(Math.min(i._xHalfAngle,k.toRadians(89))),n=Math.tan(Math.min(i._yHalfAngle,k.toRadians(89))),o=Math.atan(e/n),r=Math.atan(Math.sqrt(e*e+n*n));di(0,t,o,r),di(1,t,k.toRadians(180)-o,r),di(2,t,k.toRadians(180)+o,r),di(3,t,-o,r),t.length=4,i._customSensor.directions=t}Object.defineProperties(ui.prototype,{definitionChanged:{get:function(){return this._definitionChanged}},xHalfAngle:i("xHalfAngle"),yHalfAngle:i("yHalfAngle"),lateralSurfaceMaterial:i("lateralSurfaceMaterial"),intersectionColor:i("intersectionColor"),intersectionWidth:i("intersectionWidth"),showIntersection:i("showIntersection"),radius:i("radius"),show:i("show")}),ui.prototype.clone=function(i){return e(i)||(i=new ui),i.xHalfAngle=this.xHalfAngle,i.yHalfAngle=this.yHalfAngle,i.radius=this.radius,i.show=this.show,i.showIntersection=this.showIntersection,i.intersectionColor=this.intersectionColor,i.intersectionWidth=this.intersectionWidth,i.lateralSurfaceMaterial=this.lateralSurfaceMaterial,i},ui.prototype.merge=function(i){if(!e(i))throw new n("source is required.");this.xHalfAngle=this.xHalfAngle??i.xHalfAngle,this.yHalfAngle=this.yHalfAngle??i.yHalfAngle,this.radius=this.radius??i.radius,this.show=this.show??i.show,this.showIntersection=this.showIntersection??i.showIntersection,this.intersectionColor=this.intersectionColor??i.intersectionColor,this.intersectionWidth=this.intersectionWidth??i.intersectionWidth,this.lateralSurfaceMaterial=this.lateralSurfaceMaterial??i.lateralSurfaceMaterial};const fi=function(i){i=i??r.EMPTY_OBJECT;var t=W(i);t._pickPrimitive=i._pickPrimitive??this,t.directions=void 0,this._customSensor=new L(t),this._xHalfAngle=i.xHalfAngle??k.PI_OVER_TWO,this._yHalfAngle=i.yHalfAngle??k.PI_OVER_TWO,mi(this)};Object.defineProperties(fi.prototype,{xHalfAngle:{get:function(){return this._xHalfAngle},set:function(i){if(i>k.PI_OVER_TWO)throw new n("xHalfAngle must be less than or equal to 90 degrees.");this._xHalfAngle!==i&&(this._xHalfAngle=i,mi(this))}},yHalfAngle:{get:function(){return this._yHalfAngle},set:function(i){if(i>k.PI_OVER_TWO)throw new n("yHalfAngle must be less than or equal to 90 degrees.");this._yHalfAngle!==i&&(this._yHalfAngle=i,mi(this))}},show:{get:function(){return this._customSensor.show},set:function(i){this._customSensor.show=i}},showIntersection:{get:function(){return this._customSensor.showIntersection},set:function(i){this._customSensor.showIntersection=i}},showThroughEllipsoid:{get:function(){return this._customSensor.showThroughEllipsoid},set:function(i){this._customSensor.showThroughEllipsoid=i}},modelMatrix:{get:function(){return this._customSensor.modelMatrix},set:function(i){this._customSensor.modelMatrix=i}},radius:{get:function(){return this._customSensor.radius},set:function(i){this._customSensor.radius=i}},lateralSurfaceMaterial:{get:function(){return this._customSensor.lateralSurfaceMaterial},set:function(i){this._customSensor.lateralSurfaceMaterial=i}},intersectionColor:{get:function(){return this._customSensor.intersectionColor},set:function(i){this._customSensor.intersectionColor=i}},intersectionWidth:{get:function(){return this._customSensor.intersectionWidth},set:function(i){this._customSensor.intersectionWidth=i}},id:{get:function(){return this._customSensor.id},set:function(i){this._customSensor.id=i}}}),fi.prototype.update=function(i){this._customSensor.update(i)},fi.prototype.isDestroyed=function(){return!1},fi.prototype.destroy=function(){return this._customSensor=this._customSensor&&this._customSensor.destroy(),C(this)};const _i=S.WHITE,pi=Number.POSITIVE_INFINITY,Ci=new x,gi=new s,vi=new T,wi=function(i,t){if(!e(i))throw new n("scene is required.");if(!e(t))throw new n("entityCollection is required.");t.collectionChanged.addEventListener(wi.prototype._onCollectionChanged,this),this._scene=i,this._primitives=i.primitives,this._entityCollection=t,this._hash={},this._entitiesToVisualize=new b,this._onCollectionChanged(t,t.values,[],[])};wi.prototype.update=function(i){if(!e(i))throw new n("time is required.");for(var t=this._entitiesToVisualize.values,o=this._hash,r=this._primitives,a=0,l=t.length;a-1;n--)J(i[n],t,e);return C(this)},wi.prototype._onCollectionChanged=function(i,t,n,o){var r,s,a=this._entitiesToVisualize,l=this._hash,h=this._primitives;for(r=t.length-1;r>-1;r--)s=t[r],e(s._rectangularSensor)&&e(s._position)&&e(s._orientation)&&a.set(s.id,s);for(r=o.length-1;r>-1;r--)s=o[r],e(s._rectangularSensor)&&e(s._position)&&e(s._orientation)?a.set(s.id,s):(J(s,l,h),a.remove(s.id));for(r=n.length-1;r>-1;r--)J(s=n[r],l,h),a.remove(s.id)};var Si=P.processPacketData,yi=P.processMaterialPacketData;function Ai(i,t,n,o,r){var a,l,h=[],c=t.unitSpherical,u=t.spherical,d=t.unitCartesian,m=t.cartesian;if(e(c)){for(a=0,l=c.length;a=3&&(this._frontFaceColorCommand.vertexArray=G(this,t),this._backFaceColorCommand.vertexArray=this._frontFaceColorCommand.vertexArray,this._pickCommand.vertexArray=this._frontFaceColorCommand.vertexArray)}if(e(this._frontFaceColorCommand.vertexArray)){var v=i.passes,w=!d.equals(this.modelMatrix,this._modelMatrix);w&&d.clone(this.modelMatrix,this._modelMatrix),(C||w)&&m.transform(this._boundingSphere,this.modelMatrix,this._boundingSphereWC),this._frontFaceColorCommand.modelMatrix=this.modelMatrix,this._backFaceColorCommand.modelMatrix=this._frontFaceColorCommand.modelMatrix,this._pickCommand.modelMatrix=this._frontFaceColorCommand.modelMatrix;var S=this._lateralSurfaceMaterial!==this.lateralSurfaceMaterial;if(this._lateralSurfaceMaterial=this.lateralSurfaceMaterial,this._lateralSurfaceMaterial.update(t),v.render){var y=this._frontFaceColorCommand,A=this._backFaceColorCommand;if(S||!e(y.shaderProgram)){var M=new f({sources:[D,this._lateralSurfaceMaterial.shaderSource,R]});y.shaderProgram=_.replaceCache({context:t,shaderProgram:y.shaderProgram,vertexShaderSource:N,fragmentShaderSource:M,attributeLocations:q}),y.uniformMap=p(this._uniforms,this._lateralSurfaceMaterial._uniforms),A.shaderProgram=y.shaderProgram,A.uniformMap=p(this._uniforms,this._lateralSurfaceMaterial._uniforms),A.uniformMap.u_normalDirection=function(){return-1}}s?o.push(this._backFaceColorCommand,this._frontFaceColorCommand):o.push(this._frontFaceColorCommand)}if(v.pick){var I=this._pickCommand;if(e(this._pickId)&&this._id===this.id||(this._id=this.id,this._pickId=this._pickId&&this._pickId.destroy(),this._pickId=t.createPickId({primitive:this._pickPrimitive,id:this.id})),S||!e(I.shaderProgram)){var x=new f({sources:[D,this._lateralSurfaceMaterial.shaderSource,R],pickColorQualifier:"uniform"});I.shaderProgram=_.replaceCache({context:t,shaderProgram:I.shaderProgram,vertexShaderSource:N,fragmentShaderSource:x,attributeLocations:q});var T=this,b={czm_pickColor:function(){return T._pickId.color}};I.uniformMap=p(p(this._uniforms,this._lateralSurfaceMaterial._uniforms),b)}I.pass=s?u.TRANSLUCENT:u.OPAQUE,o.push(I)}}}},L.prototype.isDestroyed=function(){return!1},L.prototype.destroy=function(){return this._frontFaceColorCommand.vertexArray=this._frontFaceColorCommand.vertexArray&&this._frontFaceColorCommand.vertexArray.destroy(),this._frontFaceColorCommand.shaderProgram=this._frontFaceColorCommand.shaderProgram&&this._frontFaceColorCommand.shaderProgram.destroy(),this._pickCommand.shaderProgram=this._pickCommand.shaderProgram&&this._pickCommand.shaderProgram.destroy(),this._pickId=this._pickId&&this._pickId.destroy(),C(this)};const K=S.WHITE,Z=Number.POSITIVE_INFINITY,X=new x,$=new s,ii=new T;function ti(i,t,n,o){var r=t[i];e(r)||(r=new V,t[i]=r),r.clock=n,r.cone=o,r.magnitude=1}function ei(i,t,e,n,o){var r,s=i.directions,a=0,l=k.toRadians(2);if(0===t&&e===k.TWO_PI)for(r=0;rt;r-=l)ti(a++,s,r,n);ti(a++,s,t,n)}else ti(a++,s,e,0)}s.length=a,i.directions=s}const ni=function(i,t){if(!e(i))throw new n("scene is required.");if(!e(t))throw new n("entityCollection is required.");t.collectionChanged.addEventListener(ni.prototype._onCollectionChanged,this),this._scene=i,this._primitives=i.primitives,this._entityCollection=t,this._hash={},this._entitiesToVisualize=new b,this._onCollectionChanged(t,t.values,[],[])};ni.prototype.update=function(i){if(!e(i))throw new n("time is required.");for(var t=this._entitiesToVisualize.values,o=this._hash,r=this._primitives,a=0,l=t.length;a-1;n--)J(i[n],t,e);return C(this)},ni.prototype._onCollectionChanged=function(i,t,n,o){var r,s,a=this._entitiesToVisualize,l=this._hash,h=this._primitives;for(r=t.length-1;r>-1;r--)s=t[r],e(s._conicSensor)&&e(s._position)&&e(s._orientation)&&a.set(s.id,s);for(r=o.length-1;r>-1;r--)s=o[r],e(s._conicSensor)&&e(s._position)&&e(s._orientation)?a.set(s.id,s):(J(s,l,h),a.remove(s.id));for(r=n.length-1;r>-1;r--)J(s=n[r],l,h),a.remove(s.id)};const oi=function(i){this._directions=void 0,this._directionsSubscription=void 0,this._lateralSurfaceMaterial=void 0,this._lateralSurfaceMaterialSubscription=void 0,this._intersectionColor=void 0,this._intersectionColorSubscription=void 0,this._intersectionWidth=void 0,this._intersectionWidthSubscription=void 0,this._showIntersection=void 0,this._showIntersectionSubscription=void 0,this._radius=void 0,this._radiusSubscription=void 0,this._show=void 0,this._showSubscription=void 0,this._definitionChanged=new o,this.merge(i??r.EMPTY_OBJECT)};Object.defineProperties(oi.prototype,{definitionChanged:{get:function(){return this._definitionChanged}},directions:i("directions"),lateralSurfaceMaterial:t("lateralSurfaceMaterial"),intersectionColor:i("intersectionColor"),intersectionWidth:i("intersectionWidth"),showIntersection:i("showIntersection"),radius:i("radius"),show:i("show")}),oi.prototype.clone=function(i){return e(i)||(i=new oi),i.directions=this.directions,i.radius=this.radius,i.show=this.show,i.showIntersection=this.showIntersection,i.intersectionColor=this.intersectionColor,i.intersectionWidth=this.intersectionWidth,i.lateralSurfaceMaterial=this.lateralSurfaceMaterial,i},oi.prototype.merge=function(i){if(!e(i))throw new n("source is required.");this.directions=this.directions??i.directions,this.radius=this.radius??i.radius,this.show=this.show??i.show,this.showIntersection=this.showIntersection??i.showIntersection,this.intersectionColor=this.intersectionColor??i.intersectionColor,this.intersectionWidth=this.intersectionWidth??i.intersectionWidth,this.lateralSurfaceMaterial=this.lateralSurfaceMaterial??i.lateralSurfaceMaterial};const ri=S.WHITE,si=Number.POSITIVE_INFINITY,ai=new x,li=new s,hi=new T,ci=function(i,t){if(!e(i))throw new n("scene is required.");if(!e(t))throw new n("entityCollection is required.");t.collectionChanged.addEventListener(ci.prototype._onCollectionChanged,this),this._scene=i,this._primitives=i.primitives,this._entityCollection=t,this._hash={},this._entitiesToVisualize=new b,this._onCollectionChanged(t,t.values,[],[])};ci.prototype.update=function(i){if(!e(i))throw new n("time is required.");for(var t=this._entitiesToVisualize.values,o=this._hash,r=this._primitives,a=0,l=t.length;a-1;n--)J(i[n],t,e);return C(this)},ci.prototype._onCollectionChanged=function(i,t,n,o){var r,s,a=this._entitiesToVisualize,l=this._hash,h=this._primitives;for(r=t.length-1;r>-1;r--)s=t[r],e(s._customPatternSensor)&&e(s._position)&&e(s._orientation)&&a.set(s.id,s);for(r=o.length-1;r>-1;r--)s=o[r],e(s._customPatternSensor)&&e(s._position)&&e(s._orientation)?a.set(s.id,s):(J(s,l,h),a.remove(s.id));for(r=n.length-1;r>-1;r--)J(s=n[r],l,h),a.remove(s.id)};const ui=function(){this._xHalfAngle=void 0,this._xHalfAngleSubscription=void 0,this._yHalfAngle=void 0,this._yHalfAngleSubscription=void 0,this._lateralSurfaceMaterial=void 0,this._lateralSurfaceMaterialSubscription=void 0,this._intersectionColor=void 0,this._intersectionColorSubscription=void 0,this._intersectionWidth=void 0,this._intersectionWidthSubscription=void 0,this._showIntersection=void 0,this._showIntersectionSubscription=void 0,this._radius=void 0,this._radiusSubscription=void 0,this._show=void 0,this._showSubscription=void 0,this._definitionChanged=new o};function di(i,t,n,o){var r=t[i];e(r)||(r=new V,t[i]=r),r.clock=n,r.cone=o,r.magnitude=1}function mi(i){var t=i._customSensor.directions,e=Math.tan(Math.min(i._xHalfAngle,k.toRadians(89))),n=Math.tan(Math.min(i._yHalfAngle,k.toRadians(89))),o=Math.atan(e/n),r=Math.atan(Math.sqrt(e*e+n*n));di(0,t,o,r),di(1,t,k.toRadians(180)-o,r),di(2,t,k.toRadians(180)+o,r),di(3,t,-o,r),t.length=4,i._customSensor.directions=t}Object.defineProperties(ui.prototype,{definitionChanged:{get:function(){return this._definitionChanged}},xHalfAngle:i("xHalfAngle"),yHalfAngle:i("yHalfAngle"),lateralSurfaceMaterial:i("lateralSurfaceMaterial"),intersectionColor:i("intersectionColor"),intersectionWidth:i("intersectionWidth"),showIntersection:i("showIntersection"),radius:i("radius"),show:i("show")}),ui.prototype.clone=function(i){return e(i)||(i=new ui),i.xHalfAngle=this.xHalfAngle,i.yHalfAngle=this.yHalfAngle,i.radius=this.radius,i.show=this.show,i.showIntersection=this.showIntersection,i.intersectionColor=this.intersectionColor,i.intersectionWidth=this.intersectionWidth,i.lateralSurfaceMaterial=this.lateralSurfaceMaterial,i},ui.prototype.merge=function(i){if(!e(i))throw new n("source is required.");this.xHalfAngle=this.xHalfAngle??i.xHalfAngle,this.yHalfAngle=this.yHalfAngle??i.yHalfAngle,this.radius=this.radius??i.radius,this.show=this.show??i.show,this.showIntersection=this.showIntersection??i.showIntersection,this.intersectionColor=this.intersectionColor??i.intersectionColor,this.intersectionWidth=this.intersectionWidth??i.intersectionWidth,this.lateralSurfaceMaterial=this.lateralSurfaceMaterial??i.lateralSurfaceMaterial};const fi=function(i){i=i??r.EMPTY_OBJECT;var t=W(i);t._pickPrimitive=i._pickPrimitive??this,t.directions=void 0,this._customSensor=new L(t),this._xHalfAngle=i.xHalfAngle??k.PI_OVER_TWO,this._yHalfAngle=i.yHalfAngle??k.PI_OVER_TWO,mi(this)};Object.defineProperties(fi.prototype,{xHalfAngle:{get:function(){return this._xHalfAngle},set:function(i){if(i>k.PI_OVER_TWO)throw new n("xHalfAngle must be less than or equal to 90 degrees.");this._xHalfAngle!==i&&(this._xHalfAngle=i,mi(this))}},yHalfAngle:{get:function(){return this._yHalfAngle},set:function(i){if(i>k.PI_OVER_TWO)throw new n("yHalfAngle must be less than or equal to 90 degrees.");this._yHalfAngle!==i&&(this._yHalfAngle=i,mi(this))}},show:{get:function(){return this._customSensor.show},set:function(i){this._customSensor.show=i}},showIntersection:{get:function(){return this._customSensor.showIntersection},set:function(i){this._customSensor.showIntersection=i}},showThroughEllipsoid:{get:function(){return this._customSensor.showThroughEllipsoid},set:function(i){this._customSensor.showThroughEllipsoid=i}},modelMatrix:{get:function(){return this._customSensor.modelMatrix},set:function(i){this._customSensor.modelMatrix=i}},radius:{get:function(){return this._customSensor.radius},set:function(i){this._customSensor.radius=i}},lateralSurfaceMaterial:{get:function(){return this._customSensor.lateralSurfaceMaterial},set:function(i){this._customSensor.lateralSurfaceMaterial=i}},intersectionColor:{get:function(){return this._customSensor.intersectionColor},set:function(i){this._customSensor.intersectionColor=i}},intersectionWidth:{get:function(){return this._customSensor.intersectionWidth},set:function(i){this._customSensor.intersectionWidth=i}},id:{get:function(){return this._customSensor.id},set:function(i){this._customSensor.id=i}}}),fi.prototype.update=function(i){this._customSensor.update(i)},fi.prototype.isDestroyed=function(){return!1},fi.prototype.destroy=function(){return this._customSensor=this._customSensor&&this._customSensor.destroy(),C(this)};const _i=S.WHITE,pi=Number.POSITIVE_INFINITY,Ci=new x,gi=new s,vi=new T,wi=function(i,t){if(!e(i))throw new n("scene is required.");if(!e(t))throw new n("entityCollection is required.");t.collectionChanged.addEventListener(wi.prototype._onCollectionChanged,this),this._scene=i,this._primitives=i.primitives,this._entityCollection=t,this._hash={},this._entitiesToVisualize=new b,this._onCollectionChanged(t,t.values,[],[])};wi.prototype.update=function(i){if(!e(i))throw new n("time is required.");for(var t=this._entitiesToVisualize.values,o=this._hash,r=this._primitives,a=0,l=t.length;a-1;n--)J(i[n],t,e);return C(this)},wi.prototype._onCollectionChanged=function(i,t,n,o){var r,s,a=this._entitiesToVisualize,l=this._hash,h=this._primitives;for(r=t.length-1;r>-1;r--)s=t[r],e(s._rectangularSensor)&&e(s._position)&&e(s._orientation)&&a.set(s.id,s);for(r=o.length-1;r>-1;r--)s=o[r],e(s._rectangularSensor)&&e(s._position)&&e(s._orientation)?a.set(s.id,s):(J(s,l,h),a.remove(s.id));for(r=n.length-1;r>-1;r--)J(s=n[r],l,h),a.remove(s.id)};var Si=P.processPacketData,yi=P.processMaterialPacketData;function Ai(i,t,n,o,r){var a,l,h=[],c=t.unitSpherical,u=t.spherical,d=t.unitCartesian,m=t.cartesian;if(e(c)){for(a=0,l=c.length;a