From 2c0f5167fb57da81dc5d7189b886696b1c1cd979 Mon Sep 17 00:00:00 2001 From: mixxen Date: Fri, 3 Nov 2023 17:55:59 -1000 Subject: [PATCH] Initial commit. --- .editorconfig | 15 + .eslintrc.js | 26 + .gitignore | 5 + .npmignore | 18 + .travis.yml | 7 + LICENSE | 21 + README.md | 47 ++ app/index.css | 17 + app/index.html | 9 + app/index.js | 20 + package.json | 52 ++ screenshot.jpg | Bin 0 -> 185488 bytes src/engine/Universe.js | 248 ++++++ src/engine/cesium/CallbackPositionProperty.js | 155 ++++ .../cesium/CompoundElementVisualizer.js | 48 ++ src/engine/cesium/CoverageGridVisualizer.js | 100 +++ src/engine/cesium/GeoBeltVisualizer.js | 30 + .../cesium/SensorFieldOfRegardVisualizer.js | 48 ++ .../cesium/SensorFieldOfVIewVisualizer.js | 37 + src/engine/cesium/utils.js | 173 ++++ src/engine/dynamics/gimbal.js | 49 ++ src/engine/dynamics/lagrange.js | 50 ++ src/engine/dynamics/math.js | 70 ++ src/engine/dynamics/twobody.js | 130 +++ src/engine/graph/Group.js | 68 ++ src/engine/graph/Node.js | 175 ++++ src/engine/graph/TransformGroup.js | 130 +++ src/engine/objects/AzElGimbal.js | 42 + src/engine/objects/Earth.js | 31 + src/engine/objects/EarthGroundStation.js | 102 +++ src/engine/objects/ElectroOpticalSensor.js | 41 + src/engine/objects/EphemerisObject.js | 59 ++ src/engine/objects/Gimbal.js | 113 +++ .../objects/LagrangeInterpolatedObject.js | 57 ++ src/engine/objects/SGP4Satellite.js | 47 ++ src/engine/objects/SimObject.js | 211 +++++ src/engine/objects/TwoBodySatellite.js | 46 ++ src/index.js | 32 + src/io/tle.js | 26 + src/widgets/InfoBox.js | 198 +++++ src/widgets/InfoBoxViewModel.js | 116 +++ src/widgets/Toolbar.js | 122 +++ src/widgets/Viewer.js | 755 ++++++++++++++++++ webpack.config.js | 68 ++ 44 files changed, 3814 insertions(+) create mode 100644 .editorconfig create mode 100644 .eslintrc.js create mode 100755 .gitignore create mode 100644 .npmignore create mode 100755 .travis.yml create mode 100755 LICENSE create mode 100755 README.md create mode 100644 app/index.css create mode 100644 app/index.html create mode 100644 app/index.js create mode 100755 package.json create mode 100644 screenshot.jpg create mode 100644 src/engine/Universe.js create mode 100644 src/engine/cesium/CallbackPositionProperty.js create mode 100644 src/engine/cesium/CompoundElementVisualizer.js create mode 100644 src/engine/cesium/CoverageGridVisualizer.js create mode 100644 src/engine/cesium/GeoBeltVisualizer.js create mode 100644 src/engine/cesium/SensorFieldOfRegardVisualizer.js create mode 100644 src/engine/cesium/SensorFieldOfVIewVisualizer.js create mode 100644 src/engine/cesium/utils.js create mode 100644 src/engine/dynamics/gimbal.js create mode 100644 src/engine/dynamics/lagrange.js create mode 100644 src/engine/dynamics/math.js create mode 100644 src/engine/dynamics/twobody.js create mode 100644 src/engine/graph/Group.js create mode 100644 src/engine/graph/Node.js create mode 100644 src/engine/graph/TransformGroup.js create mode 100644 src/engine/objects/AzElGimbal.js create mode 100644 src/engine/objects/Earth.js create mode 100644 src/engine/objects/EarthGroundStation.js create mode 100644 src/engine/objects/ElectroOpticalSensor.js create mode 100644 src/engine/objects/EphemerisObject.js create mode 100644 src/engine/objects/Gimbal.js create mode 100644 src/engine/objects/LagrangeInterpolatedObject.js create mode 100644 src/engine/objects/SGP4Satellite.js create mode 100644 src/engine/objects/SimObject.js create mode 100644 src/engine/objects/TwoBodySatellite.js create mode 100755 src/index.js create mode 100644 src/io/tle.js create mode 100644 src/widgets/InfoBox.js create mode 100644 src/widgets/InfoBoxViewModel.js create mode 100644 src/widgets/Toolbar.js create mode 100644 src/widgets/Viewer.js create mode 100755 webpack.config.js diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..d09913e --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +# http://editorconfig.org +root = true + +[*] +indent_style = space +indent_size = 2 +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false + +[*.glsl] +indent_size = 4 \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..9a559ec --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,26 @@ +module.exports = { + env: { + browser: true, + es2021: true, + }, + extends: 'node', + overrides: [ + { + env: { + node: true, + }, + files: [ + '.eslintrc.{js,cjs}', + ], + parserOptions: { + sourceType: 'script', + }, + }, + ], + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + }, + rules: { + }, +}; diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..80fe4c6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/dist/ +/node_modules/ +package-lock.json +.DS_Store +/module/ \ No newline at end of file diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..39b1334 --- /dev/null +++ b/.npmignore @@ -0,0 +1,18 @@ +.git +.gitignore +.eslintrc +.babelrc +.npmignore +.travis.yml +package-lock.json +yarn.lock +webpack.config.js +webpack.module.js +.vscode/ +dist/ +build/ +node_modules/ +app/ +tests/ +doc/ +modules/ \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100755 index 0000000..e835430 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,7 @@ +language: node_js +node_js: + - '14' + - '16' + - '18' +script: + - npm run build \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100755 index 0000000..d9f08fa --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100755 index 0000000..dc7e6c9 --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +SatSim JS +========= + +SatSim source code was developed under contract with ARFL/RDSM, and is approved for public release under Public Affairs release approval #AFRL-2022-1116. + +![screenshot](screenshot.jpg "screenshot") + +## Installation + +```sh +npm install satsim +``` + +## Usage + +index.js + +```javascript +import { Universe, createViewer } from "satsim"; +import "cesium/Build/Cesium/Widgets/widgets.css"; + +const universe = new Universe(); +const viewer = createViewer("cesiumContainer", universe); +``` + +index.html + +```html + + + + + + +
+ + +``` + +## Example Webpack Application + +````sh +git clone https://github.com/mixxen/satsimjs-example.git +cd satsimjs-example +npm install +npm start +```` \ No newline at end of file diff --git a/app/index.css b/app/index.css new file mode 100644 index 0000000..9f98daa --- /dev/null +++ b/app/index.css @@ -0,0 +1,17 @@ +html, body, #cesiumContainer { + width: 100%; + height: 100%; + margin: 0; + padding: 0; + overflow: hidden; +} +.cesium-widget-credits { + display:none !important; +} + +.cesium-viewer-cesiumInspectorContainer { + display: block; + position: absolute; + top: 500px; + right: 10px; +} \ No newline at end of file diff --git a/app/index.html b/app/index.html new file mode 100644 index 0000000..99ad7ab --- /dev/null +++ b/app/index.html @@ -0,0 +1,9 @@ + + + + + + +
+ + \ No newline at end of file diff --git a/app/index.js b/app/index.js new file mode 100644 index 0000000..c63424b --- /dev/null +++ b/app/index.js @@ -0,0 +1,20 @@ +import { Universe, createViewer } from "../src/index.js"; +import { Math as CMath, JulianDate, ClockStep } from "cesium"; +import "cesium/Build/Cesium/Widgets/widgets.css"; +import "./index.css"; + +CMath.setRandomNumberSeed(42); + +const universe = new Universe(); +const viewer = createViewer("cesiumContainer", universe, { + showWeatherLayer: false, + showNightLayer: false, +}); +const start = JulianDate.now(); +viewer.clock.startTime = start.clone(); +viewer.clock.clockStep = ClockStep.SYSTEM_CLOCK; + + +viewer.scene.preUpdate.addEventListener((scene, time) => { + // do something +}); \ No newline at end of file diff --git a/package.json b/package.json new file mode 100755 index 0000000..1e591b1 --- /dev/null +++ b/package.json @@ -0,0 +1,52 @@ +{ + "name": "satsim", + "version": "0.2.0", + "description": "SatSim for JavaScript.", + "homepage": "https://github.com/ssc-ai/satsimjs/", + "license": "MIT", + "author": { + "name": "SatSim JS.", + "url": "https://github.com/ssc-ai/satsimjs/" + }, + "keywords": [ + "cesium", + "satsim" + ], + "repository": { + "type": "git", + "url": "https://github.com/ssc-ai/satsimjs/" + }, + "main": "src/index.js", + "scripts": { + "build": "webpack --config webpack.config.js", + "watch": "webpack --config webpack.config.js --watch", + "start": "webpack serve --config webpack.config.js --open", + "test": "jest --verbose --runInBand", + "coverage": "jest --verbose --runInBand --collectCoverage" + }, + "jest": { + "testEnvironment": "node", + "collectCoverageFrom": [ + "src/**/*.js" + ] + }, + "devDependencies": { + "@babel/core": "^7.23.2", + "babel-loader": "^9.1.3", + "copy-webpack-plugin": "^9.0.1", + "css-loader": "^6.2.0", + "eslint": "^8.50.0", + "html-webpack-plugin": "^5.3.2", + "jest": "^29.7.0", + "style-loader": "^3.2.1", + "url-loader": "^4.1.1", + "webpack": "^5.51.1", + "webpack-cli": "^4.9.1", + "webpack-dev-server": "^4.3.1" + }, + "dependencies": { + "cesium": "^1.111.0", + "mathjs": "^11.11.1", + "satellite.js": "^5.0.0" + } +} diff --git a/screenshot.jpg b/screenshot.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8b1aeada83016da1413a952b5508371b4df28d7d GIT binary patch literal 185488 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
>8(!tTg>wS^G_#g(H&g+A^wSARHb2 z0Af_I?JH#Fy)BIiyZcq8voyvTq!KH9r{^tqbm?pjSU#@6G{!pbuXCfpyC=YTlt)|8~B8#7vQC!0<3Q;<(GmWw^R&CQv4rX$?koHUANFwt-ascZSET3A9c1En{$%5kTBrmk%D^~(9wJ|bEs+w z4(L*OvZ$=;gM}oG&A{MSGpcJBcTj}3g{^H1l@Fi3!vu``WPWtzqPrs3I#a1f;oUA9 z%Ohv2v65I`)!CV$0Y}ctxWeT8q@GIB{7AfQZr@DQ?e48*&|#7Mod!KdNzI!+9zsI^ zP)OPPykvbVGefx3G@I-FH^nk*`faV;HtI!|&Ewil$A~U$Ebp|t zEK8u=l9qE_+M--Fy3EduO7voS@_jm2CEIp(dfxt8?f3oPag1R3l)1IOo}I0={Po+- zl`mENN%m{2Ehf&wJ7}V_c&syb&nHVD4qzFrJmz#_m>V8nmKnj`j%h084RNy*sN~lB%0Z$^8I`C=yXSxa^;fI zCx2aeY}539Pr0G-6IX`cQoGPJ>2%FYMYd;>+G`mlk|k+Mxhl_sO8vk{=)L;a0Uj1j zuhO37iNN_mp;-i&Bd2_3y4>87i|A6(DE1_h-cK{j+_^m|t8UR;svb=`Nh6G}my-Cy zH(Z?c_NChr+BC*j`&Rq}+w$$lT<5NPVx$W4F)YB~b5^YFX1OwLEAk;EvF%K=nmBGe z$*tDqSy^^5AOc1K-4K9O}bq! zJKrRWSCGWAp6mul{HOv;8=FXMZz7u6Zf2Es#_a5-7RWosI-F!+@y$}V+_Dy29lo@q z3nGI|kyIAL8dmB9)MshW&6W99XsK=Mgp_|pLaU6>1#>zY8HF5}an zrB)j;w<4@vP3A=!EPE7mGywv}Llwf7^))KnI}NG>H*zYw4Xw%Mgpi@gE&0#|ow37l zjAaL1rk3DGHe3cp>eS0%R-+L+F=9+)(+j2+{u^Mgd0 zkZ)m*dr$%_dt0tC%}e&8&JHjKdTgZ`w#UH5Br%DbX*h1efHbt*?==0IX22Qp-1g`0 zpK8{(QlzV{Q=_l>?^yTNEo*xsmWE}IZeA1e9E!E4-mTE`ByZm@+;8uXPpxrQr}mNO z*Q55A%)50TmPW?P9#y(${{XL2q-xg8&2o_u(l%0AsuYo7CMF2_Uow)}DRC}`7H2(mdqW}+T|>7ViFYgJ;FU_zld zpGqV7Tr%==(t+Kf1{a1L(NoPB`|JpBN+=Q1YKqXtkh`R+6fk8u>s97vYk2&)xE<p&Ab-KwK=Fp53M#PVw7HgEGP zBzyOjc?*xk)aj)n&oPSQd{L2;j!5>PqKc&OJ|iw&xGjwF=~HcJl}sqXqC1#L$ok^6 zp7K2~W(H#eFW&FF(vwS=O)Qd3it8jyn|Ev{^PrYMu%@8Wz0@I6L%2L`!r*#UUC`OV zFt?x1mO>CN(bLwU`L1RVz^@A9=0|VqQ(MCkmPsx*!c<`rATyc(W!0=JCA)p;8~tKY zG;m%xFd#u0@7z?d*j(F}xR`?b3$E_|)md!HITG%KFb<`%0SC1MEX`$O zpDsSN9_wi%8>L~6I2a4`#X&1ZtQ#Z8$E7-3t8&{st&(GhkEuTN43@^+$gLr9 z8Y>L$>md5l-%n>1$Mq*9djt@>cQ+~rG^k^U%ll|eEW6aAOFv<@_x%^CsgIZ?ZK*WL}y7E<59pMlSPGc zOXd?O*#SsXX(3iS8IH)~59Goxh}3RH@8o1>R^nH#a{qM>ZbAuKR^Z9Gr}vHkYkrc;c4Dr*?x zTZW2ue=UYWsP9P)!&|GQk>haA{{ZDs{h`-p2vP=j5mR!kTl-{6-)D5&1bp=6 `- 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 MW#8!-9%J?0e<&zi%m4rY literal 0 HcmV?d00001 diff --git a/src/engine/Universe.js b/src/engine/Universe.js new file mode 100644 index 0000000..7b397d5 --- /dev/null +++ b/src/engine/Universe.js @@ -0,0 +1,248 @@ +import Earth from "./objects/Earth"; +import SGP4Satellite from "./objects/SGP4Satellite"; +import EarthGroundStation from "./objects/EarthGroundStation"; +import AzElGimbal from "./objects/AzElGimbal"; +import ElectroOpicalSensor from "./objects/ElectroOpticalSensor"; +import LagrangeInterpolatedObject from "./objects/LagrangeInterpolatedObject"; +import TwoBodySatellite from "./objects/TwoBodySatellite"; +import SimObject from "./objects/SimObject"; +import { JulianDate } from "cesium"; +import Gimbal from "./objects/Gimbal"; + +/** + * Represents a universe containing ECI objects, ground stations, sensors, and gimbals. + */ +class Universe { + constructor() { + /** + * The Earth object in the universe. + * @type {Earth} + * @private + */ + this._earth = new Earth(); + /** + * The objects in the universe. + * @type {Object.} + * @private + */ + this._objects = {} + /** + * The gimbals in the universe. + * @type {Array.} + * @private + */ + this._gimbals = [] + /** + * The sensors in the universe. + * @type {Array.} + * @private + */ + this._sensors = [] + /** + * The trackable objects in the universe. + * @type {Array.} + * @private + */ + this._trackables = [] + /** + * The non-trackable objects in the universe. + * @type {Array.} + * @private + */ + this._nontrackables = [] + /** + * The observatories in the universe. + * @type {Array.<{site: SimObject, gimbal: AzElGimbal, sensor: ElectroOpicalSensor}>} + * @private + */ + this._observatories = [] + } + + /** + * Checks if an object with the given name exists in the universe. + * @param {string} name - The name of the object to check. + * @returns {boolean} - True if an object with the given name exists in the universe, false otherwise. + */ + hasObject(name) { + return name in this._objects; + } + + /** + * Gets the object with the given name from the universe. + * @param {string} name - The name of the object to get. + * @returns {SimObject} - The object with the given name. + */ + getObject(name) { + return this._objects[name]; + } + + /** + * Adds an object to the universe. + * @param {SimObject} object - The object to add. + * @param {boolean} [trackable=true] - Whether the object is trackable or not. + * @returns {SimObject} - The added object. + */ + addObject(object, trackable=true) { + if (object.name in this._objects) { + console.warn(`Object with name ${object.name} already exists in universe`); + } + this._objects[object.name] = object; + if (trackable) { + this._trackables.push(object); + } else { + this._nontrackables.push(object); + } + return object; + } + + /** + * Adds a ground station to the universe. + * @param {string} name - The name of the ground station. + * @param {number} latitude - The latitude of the ground station in degrees. + * @param {number} longitude - The longitude of the ground station in degrees. + * @param {number} altitude - The altitude of the ground station in meters. + * @param {boolean} [trackable=false] - Whether the ground station is trackable or not. + * @returns {EarthGroundStation} - The added ground station. + */ + addGroundSite(name, latitude, longitude, altitude, trackable=false) { + const site = new EarthGroundStation(latitude, longitude, altitude, name) + site.attach(this.earth) + this.addObject(site, trackable) + return site + } + + /** + * Adds an SGP4 satellite to the universe. + * @param {string} name - The name of the satellite. + * @param {string} line1 - The first line of the TLE for the satellite. + * @param {string} line2 - The second line of the TLE for the satellite. + * @param {string} orientation - The orientation of the satellite. + * @param {boolean} [lagrangeInterpolated=false] - Whether the satellite is lagrange interpolated or not. + * @param {boolean} [trackable=true] - Whether the satellite is trackable or not. + * @returns {SGP4Satellite|LagrangeInterpolatedObject} - The added satellite. + */ + addSGP4Satellite(name, line1, line2, orientation, lagrangeInterpolated=false, trackable=true) { + let satellite = new SGP4Satellite(line1, line2, orientation, name); + if (lagrangeInterpolated) + satellite = this.addObject(new LagrangeInterpolatedObject(satellite), trackable); + else + satellite = this.addObject(satellite, trackable); + return satellite; + } + + /** + * Adds a two-body satellite to the universe. + * @param {string} name - The name of the satellite. + * @param {Vector3} r0 - The initial position vector of the satellite in meters. + * @param {Vector3} v0 - The initial velocity vector of the satellite in meters per second. + * @param {JulianDate} t0 - The initial time of the satellite. + * @param {string} orientation - The orientation of the satellite. + * @param {boolean} [lagrangeInterpolated=false] - Whether the satellite is lagrange interpolated or not. + * @param {boolean} [trackable=true] - Whether the satellite is trackable or not. + * @returns {TwoBodySatellite|LagrangeInterpolatedObject} - The added satellite. + */ + addTwoBodySatellite(name, r0, v0, t0, orientation, lagrangeInterpolated=false, trackable=true) { + let satellite = new TwoBodySatellite(r0, v0, t0, orientation, name); + if (lagrangeInterpolated) + satellite = this.addObject(new LagrangeInterpolatedObject(satellite), trackable); + else + satellite = this.addObject(satellite, trackable); + return satellite; + } + + /** + * Adds a ground electro-optical observatory to the universe. + * @param {string} name - The name of the observatory. + * @param {number} latitude - The latitude of the observatory in degrees. + * @param {number} longitude - The longitude of the observatory in degrees. + * @param {number} altitude - The altitude of the observatory in meters. + * @param {string} gimbalType - The type of gimbal used by the observatory. + * @param {number} height - The height of the sensor in pixels. + * @param {number} width - The width of the sensor in pixels. + * @param {number} y_fov - The vertical field of view of the sensor in degrees. + * @param {number} x_fov - The horizontal field of view of the sensor in degrees. + * @param {number} field_of_regard - The field of regard of the sensor. + * @returns {{site: EarthGroundStation, gimbal: AzElGimbal, sensor: ElectroOpicalSensor}} - The added observatory. + */ + addGroundElectroOpticalObservatory(name, latitude, longitude, altitude, gimbalType, height, width, y_fov, x_fov, field_of_regard) { + const site = new EarthGroundStation(latitude, longitude, altitude, name) + site.attach(this.earth) + + const gimbal = new AzElGimbal(name + ' Gimbal') + gimbal.attach(site) + + const sensor = new ElectroOpicalSensor(height, width, y_fov, x_fov, field_of_regard, name + ' Sensor') + sensor.attach(gimbal) + + this._objects[name] = site + this._gimbals.push(gimbal) + this._sensors.push(sensor) + + const observatory = { site, gimbal, sensor } + this._observatories.push(observatory) + + return observatory + } + + /** + * Gets the Earth object in the universe. + * @type {Earth} + */ + get earth() { + return this._earth; + } + + /** + * Gets the gimbals in the universe. + * @type {Array.} + */ + get gimbals() { + return this._gimbals; + } + + /** + * Gets the sensors in the universe. + * @type {Array.} + */ + get sensors() { + return this._sensors; + } + + /** + * Gets the objects in the universe. + * @type {Object.} + */ + get objects() { + return this._objects + } + + /** + * Gets the trackable objects in the universe. + * @type {Array.} + */ + get trackables() { + return this._trackables + } + + /** + * Updates the universe to the given time. + * @param {JulianDate} time - The time to update the universe to. + */ + update(time) { + // TODO replace this with graph traversal + this._earth.update(time, this) + this._nontrackables.forEach((o) => { + o.update(time, this) + }) + this._trackables.forEach((o) => { + o.update(time, this) + }) + this._observatories.forEach((o) => { + o.site.update(time, this) + o.gimbal.update(time, this) + o.sensor.update(time, this) + }) + } +} + +export default Universe; diff --git a/src/engine/cesium/CallbackPositionProperty.js b/src/engine/cesium/CallbackPositionProperty.js new file mode 100644 index 0000000..753b731 --- /dev/null +++ b/src/engine/cesium/CallbackPositionProperty.js @@ -0,0 +1,155 @@ +import { defined, DeveloperError, Event, PositionProperty, ReferenceFrame, defaultValue } from "cesium"; + +/** + * A {@link Property} whose value is lazily evaluated by a callback function. + * + * @alias CallbackPositionProperty + * @constructor + * + * @param {CallbackPositionProperty.Callback} callback The function to be called when the property is evaluated. + * @param {boolean} isConstant true when the callback function returns the same value every time, false if the value will change. + */ +function CallbackPositionProperty(callback, isConstant, referenceFrame) { + + this._callback = undefined; + this._isConstant = undefined; + this._definitionChanged = new Event(); + this._referenceFrame = defaultValue(referenceFrame, () => ReferenceFrame.FIXED); + this.setCallback(callback, isConstant); +} + +Object.defineProperties(CallbackPositionProperty.prototype, { + /** + * Gets a value indicating if this property is constant. + * @memberof CallbackPositionProperty.prototype + * + * @type {boolean} + * @readonly + */ + isConstant: { + get: function () { + return this._isConstant; + }, + }, + /** + * Gets the event that is raised whenever the definition of this property changes. + * The definition is changed whenever setCallback is called. + * @memberof CallbackPositionProperty.prototype + * + * @type {Event} + * @readonly + */ + definitionChanged: { + get: function () { + return this._definitionChanged; + }, + }, + + /** + * Gets the reference frame that the position is defined in. + * @memberof PositionProperty.prototype + * @type {ReferenceFrame} + */ + referenceFrame: { + get: function () { + return this._referenceFrame(); + }, + }, +}); + +/** + * Gets the value of the property. + * + * @param {JulianDate} time The time for which to retrieve the value. + * @param {object} [result] The object to store the value into, if omitted, a new instance is created and returned. + * @returns {object} The modified result parameter or a new instance if the result parameter was not supplied or is unsupported. + */ +CallbackPositionProperty.prototype.getValue = function (time, result) { +// return this._callback(time, result); + return this.getValueInReferenceFrame(time, ReferenceFrame.FIXED, result) +}; + +/** + * Sets the callback to be used. + * + * @param {CallbackPositionProperty.Callback} callback The function to be called when the property is evaluated. + * @param {boolean} isConstant true when the callback function returns the same value every time, false if the value will change. + */ +CallbackPositionProperty.prototype.setCallback = function (callback, isConstant) { + //>>includeStart('debug', pragmas.debug); + if (!defined(callback)) { + throw new DeveloperError("callback is required."); + } + if (!defined(isConstant)) { + throw new DeveloperError("isConstant is required."); + } + //>>includeEnd('debug'); + + const changed = + this._callback !== callback || this._isConstant !== isConstant; + + this._callback = callback; + this._isConstant = isConstant; + + if (changed) { + this._definitionChanged.raiseEvent(this); + } +}; + +/** + * Compares this property to the provided property and returns + * true if they are equal, false otherwise. + * + * @param {Property} [other] The other property. + * @returns {boolean} true if left and right are equal, false otherwise. + */ +CallbackPositionProperty.prototype.equals = function (other) { + return ( + this === other || + (other instanceof CallbackPositionProperty && + this._callback === other._callback && + this._isConstant === other._isConstant) + ); +}; + +/** + * Gets the value of the property at the provided time and in the provided reference frame. + * + * @param {JulianDate} time The time for which to retrieve the value. + * @param {ReferenceFrame} referenceFrame The desired referenceFrame of the result. + * @param {Cartesian3} [result] The object to store the value into, if omitted, a new instance is created and returned. + * @returns {Cartesian3} The modified result parameter or a new instance if the result parameter was not supplied. + */ +CallbackPositionProperty.prototype.getValueInReferenceFrame = function ( + time, + referenceFrame, + result +) { + //>>includeStart('debug', pragmas.debug); + if (!defined(time)) { + throw new DeveloperError("time is required."); + } + if (!defined(referenceFrame)) { + return this._callback(time, result); + } + //>>includeEnd('debug'); + + let p = this._callback(time, result); + return PositionProperty.convertToReferenceFrame( + time, + p, + this._referenceFrame(), + referenceFrame, + result + ); +}; + +/** + * A function that returns the value of the property. + * @callback CallbackPositionProperty.Callback + * + * @param {JulianDate} time The time for which to retrieve the value. + * @param {object} [result] The object to store the value into. If omitted, the function must create and return a new instance. + * @returns {object} The modified result parameter, or a new instance if the result parameter was not supplied or is unsupported. + */ +export default CallbackPositionProperty; \ No newline at end of file diff --git a/src/engine/cesium/CompoundElementVisualizer.js b/src/engine/cesium/CompoundElementVisualizer.js new file mode 100644 index 0000000..c9e2c70 --- /dev/null +++ b/src/engine/cesium/CompoundElementVisualizer.js @@ -0,0 +1,48 @@ +import { defaultValue, Color } from "cesium" + +class CompountElementVisualizer { + constructor(color, materialAlpha, outlineAlpha) { + this._entities = [] + this._show = true + this._outline = true + this._color = defaultValue(color, Color.WHITE) + this._materialAlpha = defaultValue(materialAlpha, 0.25) + this._outlineAlpha = defaultValue(outlineAlpha, 0.5) + } + + get show() { + return this._show + } + + set show(value) { + this._show = value + this._entities.forEach(entity => { + entity.show = value + }) + } + + get outline() { + return this._outline + } + + set outline(value) { + this._entities.forEach(entity => { + entity.outline = value + }) + } + + get color() { + return this._color + } + + set color(value) { + this._color = value + this._entities.forEach(entity => { + entity.material = this._color.withAlpha(this._materialAlpha) + entity.outlineColor = this._color.withAlpha(this._outlineAlpha) + }) + } + +} + +export default CompountElementVisualizer diff --git a/src/engine/cesium/CoverageGridVisualizer.js b/src/engine/cesium/CoverageGridVisualizer.js new file mode 100644 index 0000000..c4da36d --- /dev/null +++ b/src/engine/cesium/CoverageGridVisualizer.js @@ -0,0 +1,100 @@ +import { PointPrimitiveCollection, Color } from 'cesium'; +import CompoundElementVisualizer from './CompoundElementVisualizer'; +import { colorVisibleSatellites } from "./utils.js"; + +class CoverageGridVisualizer extends CompoundElementVisualizer { + constructor(viewer, universe, orbit='GEO', alpha=0.3) { + super() + this._objects = [] + this._show = false + this.viewer = viewer + this.universe = universe + this._altitude = 384400.0e3 + this.orbit = orbit + this._alpha = alpha + + this._points = viewer.scene.primitives.add(new PointPrimitiveCollection()); + } + + initOrShowGridOfObjects() { + + if(this._objects.length === 0) { + for(let lat = -90; lat < 90; lat += 1) { + for(let lon = -179.5; lon <= 179.5; lon += 1) { // don't start at -180, weird aliasing in 2D + const g = this.universe.addGroundSite('grid ' + lat + ':' + lon, lat, lon, this._altitude); + const description = undefined; + const color = Color.RED.withAlpha(this._alpha); + this._objects.push(g); + const e = this._points.add({ + position: g.position, + pixelSize: 5, + color: color, + outlineColor: color, + show: true + }) + this._entities.push(e) + // // console.log(e) + g.visualizer = { + point: e + } + } + } + } + this._points.show = true + } + + set alpha(value) { + if(this._alpha === value) + return; + + this._alpha = value; + this._entities.forEach(e => { + e.color.alpha = this._alpha; + e.outlineColor.alpha = this._alpha; + }); + } + + set orbit(value) { + if(this._orbit === value) + return; + + this._orbit = value; + if (this._orbit === 'LEO') + this._altitude = 600e3; + else if(this._orbit === 'MEO') + this._altitude = 42164.0e3 / 2 - 6378.1e3; + else if(this._orbit === 'GEO') + this._altitude = 42164.0e3 - 6378.1e3; + else if(this._orbit === 'LUNAR') + this._altitude = 384400.0e3; + + this._objects.forEach(obj => { + obj.altitude = this._altitude; + obj.visualizer.point.position = obj.position; + }); + } + + set show(value) { + if(this._show === value) + return; + + this._show = value; + + const ec = this.viewer.entities + ec.suspendEvents() + if(this._show) { + this.initOrShowGridOfObjects() + } else { + this._points.show = false + } + ec.resumeEvents() + } + + update(time) { + colorVisibleSatellites(this.universe, this.universe._observatories, time, this._objects, this.alpha, false); + } + + +} + +export default CoverageGridVisualizer \ No newline at end of file diff --git a/src/engine/cesium/GeoBeltVisualizer.js b/src/engine/cesium/GeoBeltVisualizer.js new file mode 100644 index 0000000..6bc653a --- /dev/null +++ b/src/engine/cesium/GeoBeltVisualizer.js @@ -0,0 +1,30 @@ +import { defaultValue, Color, Cartesian3, Math as CMath } from 'cesium'; +import CompoundElementVisualizer from './CompoundElementVisualizer'; + +class GeoBeltVisualizer extends CompoundElementVisualizer { + constructor(viewer, color) { + super(defaultValue(color, Color.WHITE), 0.1, 0.2) + + const e = viewer.entities.add({ + name: 'GEO Belt', + position: Cartesian3.ZERO.clone(), + ellipsoid: { + radii: new Cartesian3(42164000.0, 42164000.0, 42164000.0), + minimumClock: CMath.toRadians(0), + maximumClock: CMath.toRadians(360), + minimumCone: CMath.toRadians(90.0-20.0), + maximumCone: CMath.toRadians(90.0+20.0), + material: Color.WHITE.withAlpha(0.1), + outlineColor: Color.WHITE.withAlpha(0.2), + outline: true, + slicePartitions: 36, + stackPartitions: 20 + }, + allowPicking: false + }) + + this._entities.push(e) + } +} + +export default GeoBeltVisualizer \ No newline at end of file diff --git a/src/engine/cesium/SensorFieldOfRegardVisualizer.js b/src/engine/cesium/SensorFieldOfRegardVisualizer.js new file mode 100644 index 0000000..d6da705 --- /dev/null +++ b/src/engine/cesium/SensorFieldOfRegardVisualizer.js @@ -0,0 +1,48 @@ +import { defined, Math as CMath, Color, Cartesian3 } from 'cesium' +import { createObjectPositionProperty, createObjectOrientationProperty } from './utils.js' +import CompountElementVisualizer from './CompoundElementVisualizer.js' + +class SensorFieldOfRegardVisualizer extends CompountElementVisualizer { + constructor(viewer, site, sensor, universe) { + super(Color.PURPLE, 0.1, 0.5) + + if (defined(sensor.field_of_regard)) { + for (let i = 0; i < sensor.field_of_regard.length; i++) { + let fofr = sensor.field_of_regard[i] + let ent = createFieldofRegardSection(fofr.clock[0], fofr.clock[1], fofr.elevation[0], fofr.elevation[1], fofr.range) + this._entities.push(ent) + } + } + + function createFieldofRegardSection(minClock, maxClock, minEl, maxEl, range) { + if(range === undefined) + range = 45000000.0 + + // TODO ellipsoid is buggy in Cesium (i.e., outline doesn't match, 2D view crashes), replace with a polygon + let ent = viewer.entities.add({ + name: sensor.name + ' Field of Regard', + position: createObjectPositionProperty(site, universe, viewer), + orientation: createObjectOrientationProperty(site, universe), + ellipsoid: { + radii: new Cartesian3(range, range, range), + innerRadii: new Cartesian3(CMath.EPSILON1, CMath.EPSILON1, CMath.EPSILON1), + minimumClock: CMath.toRadians(minClock - 180.0), + maximumClock: CMath.toRadians(maxClock - 180.0), + minimumCone: CMath.toRadians(90.0-minEl), + maximumCone: CMath.toRadians(90.0-maxEl), + material: Color.PURPLE.withAlpha(0.1), + outlineColor: Color.PURPLE.withAlpha(0.5), + outline: true, + slicePartitions: 36, + stackPartitions: 20 + }, + simObjectRef: sensor, + allowPicking: false + }); + + return ent.ellipsoid + } + } +} + +export default SensorFieldOfRegardVisualizer diff --git a/src/engine/cesium/SensorFieldOfVIewVisualizer.js b/src/engine/cesium/SensorFieldOfVIewVisualizer.js new file mode 100644 index 0000000..6b8343d --- /dev/null +++ b/src/engine/cesium/SensorFieldOfVIewVisualizer.js @@ -0,0 +1,37 @@ +import { defaultValue, Color, CallbackProperty, Cartesian3, Math as CMath } from 'cesium' +import { createObjectPositionProperty, createObjectOrientationProperty } from './utils.js' +import CompountElementVisualizer from './CompoundElementVisualizer.js' + +class SensorFieldOfViewVisualizer extends CompountElementVisualizer { + constructor(viewer, site, gimbal, sensor, universe, color) { + super(defaultValue(color, Color.GREEN), 0.25, 0.5) + const e = viewer.entities.add({ + name: sensor.name + ' Field of View', + position: createObjectPositionProperty(sensor, universe, viewer), + orientation: createObjectOrientationProperty(sensor, universe), + ellipsoid: { + radii: new CallbackProperty(function(time, result) { + gimbal.update(time, universe) + let range = gimbal.range <= 0 ? 1.0 : gimbal.range // Cesium will crash if range is less than 0 + return Cartesian3.clone(new Cartesian3(range, range, range), result) + }, false), + innerRadii: new Cartesian3(CMath.EPSILON1, CMath.EPSILON1, CMath.EPSILON1), // Cesium will crash if innerRadii is small and radii is large + minimumClock: CMath.toRadians(-sensor.x_fov / 2), + maximumClock: CMath.toRadians(sensor.x_fov / 2), + minimumCone: CMath.toRadians(90 - sensor.y_fov / 2), + maximumCone: CMath.toRadians(90 + sensor.y_fov / 2), + material: this._color.withAlpha(0.25), + outlineColor: this._color.withAlpha(0.5), + outline: true, + slicePartitions: 3, + stackPartitions: 3 + }, + simObjectRef: sensor, + allowPicking: false + }) + + this._entities.push(e.ellipsoid) + } +} + +export default SensorFieldOfViewVisualizer diff --git a/src/engine/cesium/utils.js b/src/engine/cesium/utils.js new file mode 100644 index 0000000..e130a15 --- /dev/null +++ b/src/engine/cesium/utils.js @@ -0,0 +1,173 @@ +import { Color, defaultValue, SampledPositionProperty, JulianDate, Cartesian3, LagrangePolynomialApproximation, defined, ReferenceFrame, Matrix3, Quaternion, CallbackProperty } from 'cesium' +import { CallbackPositionProperty, ElectroOpicalSensor } from '../../index.js' +import { southEastZenithToAzEl } from '../dynamics/gimbal.js' + +function toSampledPositionProperty(object, context, start, stop, step) { + start = JulianDate.addSeconds(start, -step, new JulianDate()) + stop = JulianDate.addSeconds(stop, step, new JulianDate()) + const prop = new SampledPositionProperty() + let current = JulianDate.clone(start) + let i = 0 + while(JulianDate.lessThan(current, stop)) { + object.update(current, context) + prop.addSample(current.clone(), Cartesian3.clone(object.position)) + JulianDate.addSeconds(current, step, current) + i = i + 1 + } + prop.setInterpolationOptions({ + interpolationDegree : 3, + interpolationAlgorithm : LagrangePolynomialApproximation + }); + return prop +} + + +function createObjectPositionProperty(object, universe, viewer) { + return new CallbackPositionProperty(function(time, result) { + if(!defined(this.lastReferenceFrameView)) { + this.lastReferenceFrameView = viewer.referenceFrameView + } else if(this.lastReferenceFrameView !== viewer.referenceFrameView) { + this.lastReferenceFrameView = viewer.referenceFrameView + } + + object.update(time, universe) + result = Cartesian3.clone(object.position, result) + if (viewer.referenceFrameView === ReferenceFrame.FIXED && object.referenceFrame === ReferenceFrame.INERTIAL) { + universe.earth.update(time, universe) + universe.earth.transformPointFromWorld(result, result) + } else if (viewer.referenceFrameView === ReferenceFrame.INERTIAL && object.referenceFrame === ReferenceFrame.FIXED) { + universe.earth.update(time, universe) + universe.earth.transformPointToWorld(result, result) + } + return result + }, false, () => viewer.referenceFrameView) +} + + +function getObjectPositionInCesiumFrame(viewer, universe, object, time, result) { + result = Cartesian3.clone(object.position, result) + if (object.referenceFrame === ReferenceFrame.INERTIAL) { + universe.earth.update(time, universe) + universe.earth.transformPointFromWorld(result, result) + } + // TODO - handle other reference frames + return result +} + + +function createObjectOrientationProperty(object, universe) { + return new CallbackProperty(function(time, result) { + object.update(time, universe) + let m; + if (object instanceof ElectroOpicalSensor) { + universe.earth.update(time, universe) + let x = object.transformVectorTo(universe.earth, new Cartesian3(0, 0, -1)) + let y = object.transformVectorTo(universe.earth, Cartesian3.UNIT_Y) + let z = object.transformVectorTo(universe.earth, Cartesian3.UNIT_X) + m = new Matrix3(x.x, y.x, z.x, x.y, y.y, z.y, x.z, y.z, z.z) + } else if (object.referenceFrame === ReferenceFrame.INERTIAL) { + universe.earth.update(time, universe) + let x = universe.earth.transformVectorFromWorld(Cartesian3.UNIT_Y) + let y = universe.earth.transformVectorFromWorld(Cartesian3.UNIT_X) + let z = universe.earth.transformVectorFromWorld(new Cartesian3(0, 0, -1)) + m = new Matrix3(x.x, y.x, z.x, x.y, y.y, z.y, x.z, y.z, z.z) + } else { + universe.earth.update(time, universe) + let x = object.transformVectorTo(universe.earth, Cartesian3.UNIT_X) + let y = object.transformVectorTo(universe.earth, Cartesian3.UNIT_Y) + let z = object.transformVectorTo(universe.earth, Cartesian3.UNIT_Z) + m = new Matrix3(x.x, y.x, z.x, x.y, y.y, z.y, x.z, y.z, z.z) + } + Quaternion.fromRotationMatrix(m, result) + return result + }, false) +} + + +function colorVisibleSatellites(universe, observatories, time, objects=undefined, alpha=0.5, showNonVisible=false) { + + function getPoint(o) { + if(defined(o.visualizer.point)) { + return o.visualizer.point + } else if(defined(o.visualizer.point2)) { + return o.visualizer.point2 + } else { + return undefined + } + } + + const [R, O, Y, G] = [Color.RED.withAlpha(alpha), Color.ORANGE.withAlpha(alpha), Color.YELLOW.withAlpha(alpha), Color.GREEN.withAlpha(alpha)] + const trackables = defaultValue(objects, universe._trackables) + trackables.forEach((o) => { + const point = getPoint(o) + if(showNonVisible) { + if(!Color.equals(point.color._value, R)) { + point.color = R + point.outlineColor = R + } + } else { + if(defined(point.show)) { + if(point.show._value !== false) { + point.show = false; + } + } else { + point.show = false; + } + } + }); + + const counts = {} + observatories.forEach((o) => { + applyToVisible(universe, o, time, objects, (sat) => { + const point = getPoint(sat) + if(point.show._value !== true) + point.show = true; + + if(counts[sat.name] === undefined) { + counts[sat.name] = 0 + } + counts[sat.name] += 1 + const count = counts[sat.name] + if(count == 1) { + point.color = showNonVisible ? O : R + point.outlineColor = showNonVisible ? O : R + } else if(count == 2) { + point.color = Y + point.outlineColor = Y + } else if(count > 2) { + point.color = G + point.outlineColor = G + } + }); + }); +} + + + +function applyToVisible(universe, observatory, time, objects, callback) { + const field_of_regard = observatory.sensor.field_of_regard; + observatory.site.update(time, universe) + const localPos = new Cartesian3(); + objects.forEach((sat) => { + sat.update(time, universe) + observatory.site.transformPointFromWorld(sat.worldPosition, localPos); + let [az, el, r] = southEastZenithToAzEl(localPos) + if(defined(field_of_regard)) { + for(let i = 0; i < field_of_regard.length; i ++) { + const f = field_of_regard[i]; + if(az > f.clock[0] && az < f.clock[1] && el > f.elevation[0] && el < f.elevation[1]) { + callback(sat); + break; + } + } + } + }); +} + +export { + toSampledPositionProperty, + createObjectPositionProperty, + createObjectOrientationProperty, + colorVisibleSatellites, + getObjectPositionInCesiumFrame +} \ No newline at end of file diff --git a/src/engine/dynamics/gimbal.js b/src/engine/dynamics/gimbal.js new file mode 100644 index 0000000..2f7f227 --- /dev/null +++ b/src/engine/dynamics/gimbal.js @@ -0,0 +1,49 @@ +import { Cartesian3, Math as CMath } from 'cesium'; + +function southEastZenithToAzEl(cartesian3) { + let az, el; + if (cartesian3.x === 0.0 && cartesian3.y === 0.0) { + az = 0.0; + } else { + az = Math.atan2(cartesian3.y, -cartesian3.x); + if (az < 0.0) { + az += CMath.TWO_PI; + } + } + + const mag = Cartesian3.magnitude(cartesian3); + if (mag < 1e-9) { + el = 0.0; + } else { + el = Math.asin(cartesian3.z / mag); + } + + return [az * CMath.DEGREES_PER_RADIAN, el * CMath.DEGREES_PER_RADIAN, mag]; +} + +function spaceBasedToAzEl(cartesian3) { + let az, el; + if (cartesian3.x === 0 && cartesian3.y === 0) { + az = 0.0; + } else { + az = Math.atan2(cartesian3.y, -cartesian3.x); + if (az < 0.0) { + az += CMath.TWO_PI; + } + } + + const mag = Cartesian3.magnitude(cartesian3); + const r = Math.sqrt(cartesian3.x * cartesian3.x + cartesian3.y * cartesian3.y); + if (Math.abs(r) < 1e-9 && Math.abs(cartesian3.z) < 1e-9) { + el = 0.0; + } else { + el = Math.atan2(r, -cartesian3.z); + } + + return [az * CMath.DEGREES_PER_RADIAN, el * CMath.DEGREES_PER_RADIAN, mag]; +} + +export { + southEastZenithToAzEl, + spaceBasedToAzEl +} \ No newline at end of file diff --git a/src/engine/dynamics/lagrange.js b/src/engine/dynamics/lagrange.js new file mode 100644 index 0000000..7226a3c --- /dev/null +++ b/src/engine/dynamics/lagrange.js @@ -0,0 +1,50 @@ +import { JulianDate, LagrangePolynomialApproximation, Cartesian3 } from 'cesium' + +const _r = [] +function lagrange_fast(times, positions, t, result) { + + _r.length = 0 + LagrangePolynomialApproximation.interpolateOrderZero(t, times, positions, 3, _r) + return Cartesian3.fromArray(_r, 0, result) +} + + +function lagrange(times, positions, epoch, time, update, result, interval = 180) { + + let delta = JulianDate.secondsDifference(time, epoch) + let numPoints = 7 + if (times.length < numPoints || delta < times[0] || delta > times[times.length - 1]) { + initialize(times, positions, epoch, time, update, interval, numPoints) + delta = JulianDate.secondsDifference(time, epoch) + } + + return lagrange_fast(times, positions, delta, result) +} + + +function initialize(times, positions, epoch, time, update, interval, numPoints) { + + times.length = 0 + positions.length = 0 + + let t1 = JulianDate.clone(time) + JulianDate.addSeconds(t1, -(numPoints - 1) / 2 * interval, t1) + for (let i = 0; i < numPoints; i++) { + JulianDate.addSeconds(t1, interval, t1) + + if (i === 0) + JulianDate.clone(t1, epoch) + + const p = update(t1) + positions.push(p.x) + positions.push(p.y) + positions.push(p.z) + times.push(interval * i) + } +} + +export { + lagrange_fast, + lagrange, + initialize +} \ No newline at end of file diff --git a/src/engine/dynamics/math.js b/src/engine/dynamics/math.js new file mode 100644 index 0000000..2bc28b6 --- /dev/null +++ b/src/engine/dynamics/math.js @@ -0,0 +1,70 @@ +import { gamma } from "mathjs" + + +function hyp2f1b(x) { + if (x >= 1.0) { + return Math.inf + } else { + let res = 1.0 + let term = 1.0 + let ii = 0 + while (true) { + term = term * (3 + ii) * (1 + ii) / (5 / 2 + ii) * x / (ii + 1) + let res_old = res + res += term + if (res_old == res) { + return res + } + ii += 1 + } + } +} + + +function stumpff_c2(psi) { + let eps = 1.0 + let res + if (psi > eps) { + res = (1 - Math.cos(Math.sqrt(psi))) / psi + } else if (psi < -eps) { + res = (Math.cosh(Math.sqrt(-psi)) - 1) / (-psi) + } else { + res = 1.0 / 2.0 + let delta = (-psi) / gamma(2 + 2 + 1) + let k = 1 + while (res + delta != res) { + res = res + delta + k += 1 + delta = (-psi) ** k / gamma(2 * k + 2 + 1) + } + } + return res +} + + +function stumpff_c3(psi) { + let eps = 1.0 + let res + if (psi > eps) { + res = (Math.sqrt(psi) - Math.sin(Math.sqrt(psi))) / (psi * Math.sqrt(psi)) + } else if (psi < -eps) { + res = (Math.sinh(Math.sqrt(-psi)) - Math.sqrt(-psi)) / (-psi * Math.sqrt(-psi)) + } else { + res = 1.0 / 6.0 + let delta = (-psi) / gamma(2 + 3 + 1) + let k = 1 + while (res + delta != res) { + res = res + delta + k += 1 + delta = (-psi) ** k / gamma(2 * k + 3 + 1) + } + } + + return res +} + +export { + hyp2f1b, + stumpff_c2, + stumpff_c3 +} \ No newline at end of file diff --git a/src/engine/dynamics/twobody.js b/src/engine/dynamics/twobody.js new file mode 100644 index 0000000..69f0e09 --- /dev/null +++ b/src/engine/dynamics/twobody.js @@ -0,0 +1,130 @@ +import { + stumpff_c2 as c2, + stumpff_c3 as c3 +} from './math.js' +import { Cartesian3 } from 'cesium' + +const _scratch = new Cartesian3() +function cross(a, b) { + return Cartesian3.cross(a, b, _scratch) +} + +function dot(a, b) { + return Cartesian3.dot(a, b) +} + +function mult(a, b) { + return new Cartesian3(a.x * b, a.y * b, a.z * b) +} + +function sub(a, b) { + return new Cartesian3(a.x - b.x, a.y - b.y, a.z - b.z) +} + +function mag(a) { + return Cartesian3.magnitude(a) +} + +function vallado_fast(k, r0, v0, tof, numiter) { + const dot_r0v0 = dot(r0, v0) + const norm_r0 = mag(r0) + const sqrt_mu = Math.pow(k, 0.5) + const alpha = -dot(v0, v0) / k + 2 / norm_r0 + + let xi_new + // First guess + if (alpha > 0) { + // Elliptic orbit + xi_new = sqrt_mu * tof * alpha + } else if (alpha < 0) { + // Hyperbolic orbit + xi_new = ( + Math.sign(tof) + * Math.pow((-1 / alpha), 0.5) + * Math.log( + (-2 * k * alpha * tof) + / ( + dot_r0v0 + + Math.sign(tof) + * Math.sqrt(-k / alpha) + * (1 - norm_r0 * alpha) + ) + ) + ) + } else { + // Parabolic orbit + // (Conservative initial guess) + xi_new = sqrt_mu * tof / norm_r0 + } + + // Newton-Raphson iteration on the Kepler equation + let xi, psi, c2_psi, c3_psi, norm_r + let count = 0 + while (count < numiter) { + xi = xi_new + psi = xi * xi * alpha + c2_psi = c2(psi) + c3_psi = c3(psi) + norm_r = ( + xi * xi * c2_psi + + dot_r0v0 / sqrt_mu * xi * (1 - psi * c3_psi) + + norm_r0 * (1 - psi * c2_psi) + ) + xi_new = ( + xi + + ( + sqrt_mu * tof + - xi * xi * xi * c3_psi + - dot_r0v0 / sqrt_mu * xi * xi * c2_psi + - norm_r0 * xi * (1 - psi * c3_psi) + ) + / norm_r + ) + if (Math.abs(xi_new - xi) < 1e-7) { + break + } else { + count += 1 + } + } + + // Compute Lagrange coefficients + const f = 1 - Math.pow(xi, 2) / norm_r0 * c2_psi + const g = tof - Math.pow(xi, 3) / sqrt_mu * c3_psi + + const gdot = 1 - Math.pow(xi, 2) / norm_r * c2_psi + const fdot = sqrt_mu / (norm_r * norm_r0) * xi * (psi * c3_psi - 1) + + return [f, g, fdot, gdot] +} + + +function vallado(k, r0, v0, tof, numiter) { + // Compute Lagrange coefficients + let f, g, fdot, gdot + [f, g, fdot, gdot] = vallado_fast(k, r0, v0, tof, numiter) + + // Return position and velocity vectors + return { + "position": { "x": f * r0.x + g * v0.x, "y": f * r0.y + g * v0.y, "z": f * r0.z + g * v0.z}, + "velocity": { "x": fdot * r0.x + gdot * v0.x, "y": fdot * r0.y + gdot * v0.y, "z": fdot * r0.z + gdot * v0.z} + } +} + +function rv2ecc(k, r, v) { + const e = mult(sub(mult(r, dot(v, v) - k / mag(r)), mult(v, dot(r, v))), 1/k) + const ecc = mag(e) + return ecc +} + + +function rv2p(k, r, v) { + const h = cross(r, v) + const p = dot(h, h) / k + const ecc = rv2ecc(k, r, v) + const a = p / (1 - ecc * ecc) + const mm = Math.sqrt(k / Math.abs(a*a*a)) + + return 2 * Math.PI / mm +} + +export { vallado, rv2p, rv2ecc } \ No newline at end of file diff --git a/src/engine/graph/Group.js b/src/engine/graph/Group.js new file mode 100644 index 0000000..1b2bcfb --- /dev/null +++ b/src/engine/graph/Group.js @@ -0,0 +1,68 @@ +import Node from './Node'; + +/** + * A group of nodes that can have children added and removed. + * @extends Node + */ +class Group extends Node { + + /** + * Creates a new group. + */ + constructor() { + super(); + /** + * The children of this group. + * @type {Node[]} + */ + this.children = []; + } + + /** + * Adds a child node to this group. + * @param {Node} child - The child node to add. + */ + addChild(child) { + child.parent = this; + this.children.push(child); + } + + /** + * Removes a child node from this group. + * @param {Node} child - The child node to remove. + */ + removeChild(child) { + const index = this.children.indexOf(child); + if (index !== -1) { + child.parent = null; + this.children.splice(index, 1); + } + } + + /** + * Removes all children from this group. + */ + removeAll() { + this.children.forEach(child => { + this.removeChild(child); + }); + } + + /** + * Checks if this group has any children. + * @returns {boolean} - True if this group has children, false otherwise. + */ + hasChildren() { + return this.children.length > 0; + } + + /** + * The number of children in this group. + * @type {number} + */ + get length() { + return this.children.length; + } +} + +export default Group; diff --git a/src/engine/graph/Node.js b/src/engine/graph/Node.js new file mode 100644 index 0000000..1ee5d17 --- /dev/null +++ b/src/engine/graph/Node.js @@ -0,0 +1,175 @@ +import { Matrix4, Cartesian3, defined } from 'cesium'; + +/** + * A node in a scene graph. + */ +class Node { + + /** + * Creates a new node. + */ + constructor() { + /** + * The parent node. + * @type {Node|null} + */ + this.parent = null; + } + + /** + * Attaches this node to a parent node. + * @param {Node} parent - The parent node to attach to. + */ + attach(parent) { + if (this.parent) { + this.parent.removeChild(this); + } + parent.addChild(this); + } + + /** + * Detaches this node from its parent node. + */ + detach() { + if (this.parent) { + this.parent.removeChild(this); + } + } + + /** + * Transforms a local point to world coordinates. + * @param {Cartesian3} localPoint - The local point to transform. + * @param {Cartesian3} [result] - The object to store the result in. + * @returns {Cartesian3} The transformed point in world coordinates. + */ + transformPointToWorld(localPoint, result) { + const localToWorldTransform = this.localToWorldTransform; + if (!defined(result)) { + result = new Cartesian3(); + } + Matrix4.multiplyByPoint(localToWorldTransform, localPoint, result); + return result; + } + + /** + * Transforms a world point to local coordinates. + * @param {Cartesian3} worldPoint - The world point to transform. + * @param {Cartesian3} [result] - The object to store the result in. + * @returns {Cartesian3} The transformed point in local coordinates. + */ + transformPointFromWorld(worldPoint, result) { + // const worldToLocalTransform = new Matrix4(); + // Matrix4.inverseTransformation(this.localToWorldTransform, worldToLocalTransform); + const worldToLocalTransform = this.worldToLocalTransform; + if (!defined(result)) { + result = new Cartesian3(); + } + Matrix4.multiplyByPoint(worldToLocalTransform, worldPoint, result); + return result; + } + + /** + * Transforms a local point from the this node's coordinate system to the destination node's coordinate system. + * @param {Node} destinationNode - The node that the local point should be transformed to. + * @param {Cartesian3} localPoint - The local point to transform. + * @param {Cartesian3} [result] - The object to store the result in. + * @returns {Cartesian3} The transformed point in the destination node's coordinate system. + */ + transformPointTo(destinationNode, localPoint, result) { + const worldPoint = this.transformPointToWorld(localPoint); + const localResult = destinationNode.transformPointFromWorld(worldPoint, result); + return localResult; + } + + /** + * Transforms a local vector to world coordinates. + * @param {Cartesian3} localVector - The local vector to transform. + * @param {Cartesian3} [result] - The object to store the result in. + * @returns {Cartesian3} The transformed vector in world coordinates. + */ + transformVectorToWorld(localVector, result) { + if (!defined(result)) { + result = new Cartesian3(); + } + Matrix4.multiplyByPointAsVector(this.localToWorldTransform, localVector, result); + return result; + } + + /** + * Transforms a world vector to local coordinates. + * @param {Cartesian3} worldVector - The world vector to transform. + * @param {Cartesian3} [result] - The object to store the result in. + * @returns {Cartesian3} The transformed vector in local coordinates. + */ + transformVectorFromWorld(worldVector, result) { + if (!defined(result)) { + result = new Cartesian3(); + } + Matrix4.multiplyByPointAsVector(this.worldToLocalTransform, worldVector, result); + return result; + } + + /** + * Transforms a local vector from the this node's coordinate system to the destination node's coordinate system. + * @param {Node} destinationNode - The node that the local vector should be transformed to. + * @param {Cartesian3} localVector - The local vector to transform. + * @param {Cartesian3} [result] - The object to store the result in. + * @returns {Cartesian3} The transformed vector in the destination node's coordinate system. + */ + transformVectorTo(destinationNode, localVector, result) { + const worldVector = this.transformVectorToWorld(localVector); + const localResult = destinationNode.transformVectorFromWorld(worldVector, result); + return localResult; + } + + /** + * Gets the local-to-world transformation matrix for the node. + * @returns {Matrix4} The local-to-world transformation matrix. + */ + get localToWorldTransform() { + let transform = new Matrix4(); + if (defined(this.parent)) { + Matrix4.multiply(this.parent.localToWorldTransform, this.transform, transform); + } else { + Matrix4.clone(this.transform, transform); + } + return transform; + } + + /** + * Gets the world-to-local transformation matrix for the node. + * @returns {Matrix4} The world-to-local transformation matrix. + */ + get worldToLocalTransform() { + const worldToLocalTransform = new Matrix4(); + Matrix4.inverseTransformation(this.localToWorldTransform, worldToLocalTransform); + return worldToLocalTransform; + } + + /** + * Returns the transform of the node. + * @returns {Matrix4} The identity matrix cloned. + */ + get transform() { + return Matrix4.IDENTITY.clone() + } + + + /** + * Returns the world position of the node. + * @returns {Cartesian3} The world position of the node. + */ + get worldPosition() { + let localToWorldTransform = this.localToWorldTransform + return Cartesian3.fromElements(localToWorldTransform[12], localToWorldTransform[13], localToWorldTransform[14]); + } + + /** + * Returns zero. + */ + get length() { + return 0; + } +} + +export default Node; diff --git a/src/engine/graph/TransformGroup.js b/src/engine/graph/TransformGroup.js new file mode 100644 index 0000000..898529c --- /dev/null +++ b/src/engine/graph/TransformGroup.js @@ -0,0 +1,130 @@ +import { Matrix3, Matrix4, Cartesian3 } from 'cesium'; +import Group from './Group'; + +const _scratchMatrix3 = new Matrix3(); + +/** + * A group note that contains a transform. This transform is applied to all children + * of this group. The effects of transformations in the scene graph are cumulative. + * @extends Group + */ +class TransformGroup extends Group { + + /** + * Creates a new transform group. + */ + constructor() { + super(); + /** + * The transformation matrix of this group. + * @type {Matrix4} + * @private + */ + this._transform = Matrix4.IDENTITY.clone(); + } + + /** + * Rotates this group around the X axis. + * @param {Number} angle - The angle to rotate, in radians. + */ + rotateX(angle) { + Matrix3.fromRotationX(angle, _scratchMatrix3); + Matrix4.multiplyByMatrix3(this._transform, _scratchMatrix3, this._transform); + } + + /** + * Rotates this group around the Y axis. + * @param {Number} angle - The angle to rotate, in radians. + */ + rotateY(angle) { + Matrix3.fromRotationY(angle, _scratchMatrix3); + Matrix4.multiplyByMatrix3(this._transform, _scratchMatrix3, this._transform); + } + + /** + * Rotates this group around the Z axis. + * @param {Number} angle - The angle to rotate, in radians. + */ + rotateZ(angle) { + Matrix3.fromRotationZ(angle, _scratchMatrix3); + Matrix4.multiplyByMatrix3(this._transform, _scratchMatrix3, this._transform); + } + + /** + * Translates this group. + * @param {Cartesian3} cartesian3 - The translation vector. + */ + translate(cartesian3) { + Matrix4.multiplyByTranslation(this._transform, cartesian3, this._transform); + } + + /** + * Sets the translation of this group. + * @param {Cartesian3} cartesian3 - The translation vector. + */ + setTranslation(cartesian3) { + this._transform[12] = cartesian3.x; + this._transform[13] = cartesian3.y; + this._transform[14] = cartesian3.z; + } + + /** + * Sets the rotation of this group. + * @param {Matrix3} matrix3 - The rotation matrix. + */ + setRotation(matrix3) { + this._transform[0] = matrix3[0]; + this._transform[1] = matrix3[1]; + this._transform[2] = matrix3[2]; + this._transform[4] = matrix3[3]; + this._transform[5] = matrix3[4]; + this._transform[6] = matrix3[5]; + this._transform[8] = matrix3[6]; + this._transform[9] = matrix3[7]; + this._transform[10] = matrix3[8]; + } + + /** + * Sets the columns of this group's transformation matrix. + * @param {Cartesian3} x - The X column. + * @param {Cartesian3} y - The Y column. + * @param {Cartesian3} z - The Z column. + */ + setColumns(x, y, z) { + this._transform[0] = x.x; + this._transform[1] = x.y; + this._transform[2] = x.z; + this._transform[4] = y.x; + this._transform[5] = y.y; + this._transform[6] = y.z; + this._transform[8] = z.x; + this._transform[9] = z.y; + this._transform[10] = z.z; + } + + /** + * Resets this group's transformation matrix to the identity matrix. + */ + reset() { + Matrix4.clone(Matrix4.IDENTITY, this._transform); + } + + /** + * Returns this group's transformation matrix. + * @type {Matrix4} + */ + get transform() { + return this._transform; + } + + /** + * Sets this group's transformation matrix. + * @param {Matrix4} value - The new transformation matrix. + */ + set transform(value) { + Matrix4.clone(value, this._transform); + } + +} + +export default TransformGroup; diff --git a/src/engine/objects/AzElGimbal.js b/src/engine/objects/AzElGimbal.js new file mode 100644 index 0000000..7bebfe3 --- /dev/null +++ b/src/engine/objects/AzElGimbal.js @@ -0,0 +1,42 @@ +import Gimbal from './Gimbal.js'; +import { southEastZenithToAzEl } from '../dynamics/gimbal.js'; +import { Math as CMath, JulianDate } from 'cesium'; + +/** + * Represents an Azimuth-Elevation Gimbal object that extends the Gimbal class. + * @extends Gimbal + */ +class AzElGimbal extends Gimbal { + /** + * Creates an instance of AzElGimbal. + * @param {string} [name='AzElGimbal'] - The name of the AzElGimbal object. + */ + constructor(name='AzElGimbal') { + super(name) + this.az = 0.0 + this.el = 90.0 + } + + /** + * Updates the AzElGimbal object's position and orientation based on the current time and universe. + * @param {JulianDate} time - The current time. + * @param {Universe} universe - The universe object. + * @override + */ + _update(time, universe) { + const localVector = this._trackToLocalVector(time, universe) + if (localVector !== null) + [this.az, this.el, this._range] = southEastZenithToAzEl(localVector) + + // setup reference transform + this.reset() + this.rotateY(CMath.PI_OVER_TWO) + this.rotateZ(CMath.PI_OVER_TWO) + + // move gimbals to position + this.rotateY(-this.az * CMath.RADIANS_PER_DEGREE) + this.rotateX(this.el * CMath.RADIANS_PER_DEGREE) + } +} + +export default AzElGimbal; diff --git a/src/engine/objects/Earth.js b/src/engine/objects/Earth.js new file mode 100644 index 0000000..4e5169e --- /dev/null +++ b/src/engine/objects/Earth.js @@ -0,0 +1,31 @@ +import { Transforms, Matrix3, defined, Matrix4, ReferenceFrame } from 'cesium'; +import SimObject from './SimObject.js'; + +/** + * Represents the Earth object in the simulation. + * @extends SimObject + */ +class Earth extends SimObject { + constructor() { + super('Earth', ReferenceFrame.FIXED); + this._teme2ecef = new Matrix3(); + this._ecef2teme = new Matrix3(); + } + + /** + * Updates the Earth object's position and orientation based on the current time and universe. + * @param {JulianDate} time - The current time in Julian Date format. + * @param {Universe} universe - The universe object containing information about the simulation. + * @override + */ + _update(time, universe) { + const transform = Transforms.computeIcrfToFixedMatrix(time, this._teme2ecef); + if (!defined(transform)) { + Transforms.computeTemeToPseudoFixedMatrix(time, this._teme2ecef); + } + Matrix3.transpose(this._teme2ecef, this._ecef2teme); + Matrix4.fromRotation(this._ecef2teme, this.transform); + } +} + +export default Earth; diff --git a/src/engine/objects/EarthGroundStation.js b/src/engine/objects/EarthGroundStation.js new file mode 100644 index 0000000..acc6659 --- /dev/null +++ b/src/engine/objects/EarthGroundStation.js @@ -0,0 +1,102 @@ +import { Math as CMath, Cartesian3, ReferenceFrame, defaultValue } from 'cesium' +import SimObject from './SimObject.js' + +/** + * Represents a ground station on Earth. + * @extends SimObject + */ +class EarthGroundStation extends SimObject { + /** + * Creates a new EarthGroundStation object. + * @param {Number} latitude - The latitude of the ground station in degrees. + * @param {Number} longitude - The longitude of the ground station in degrees. + * @param {Number} [altitude=0.0] - The altitude of the ground station in meters. + * @param {String} [name='EarthGroundStation'] - The name of the ground station. + */ + constructor(latitude, longitude, altitude, name='EarthGroundStation') { + super(name, ReferenceFrame.FIXED) + this._latitude = latitude + this._longitude = longitude + this._altitude = defaultValue(altitude, 0.0) // meters + this._period = 86400 + + this._initialize() + } + + /** + * Initializes the ground station's position and orientation. + * @private + */ + _initialize() { + this._position = Cartesian3.fromDegrees(this._longitude, this._latitude, this._altitude) + this._velocity = new Cartesian3() + + this.reset() + this.rotateZ(this._longitude * CMath.RADIANS_PER_DEGREE) + this.rotateY(CMath.PI_OVER_TWO - this._latitude * CMath.RADIANS_PER_DEGREE) + this.setTranslation(this._position) + } + + /** + * Gets the latitude of the ground station. + * @returns {Number} The latitude of the ground station in degrees. + */ + get latitude() { + return this._latitude + } + + /** + * Sets the latitude of the ground station. + * @param {Number} latitude - The latitude of the ground station in degrees. + */ + set latitude(latitude) { + this._latitude = latitude + this._initialize() + } + + /** + * Gets the longitude of the ground station. + * @returns {Number} The longitude of the ground station in degrees. + */ + get longitude() { + return this._longitude + } + + /** + * Sets the longitude of the ground station. + * @param {Number} longitude - The longitude of the ground station in degrees. + */ + set longitude(longitude) { + this._longitude = longitude + this._initialize() + } + + /** + * Gets the altitude of the ground station. + * @returns {Number} The altitude of the ground station in meters. + */ + get altitude() { + return this._altitude + } + + /** + * Sets the altitude of the ground station. + * @param {Number} altitude - The altitude of the ground station in meters. + */ + set altitude(altitude) { + this._altitude = altitude + this._initialize() + } + + /** + * Updates the ground station's state. + * @param {Number} time - The current simulation time in seconds. + * @param {Universe} universe - The universe in which the ground station exists. + * @override + */ + _update(time, universe) { + // do nothing since this is a fixed object and _initialize() sets the position and orientation + } +} + +export default EarthGroundStation \ No newline at end of file diff --git a/src/engine/objects/ElectroOpticalSensor.js b/src/engine/objects/ElectroOpticalSensor.js new file mode 100644 index 0000000..7c80c33 --- /dev/null +++ b/src/engine/objects/ElectroOpticalSensor.js @@ -0,0 +1,41 @@ +import { JulianDate } from 'cesium' +import SimObject from './SimObject.js' + +/** + * A class representing an electro-optical sensor. + * @extends SimObject + */ +class ElectroOpicalSensor extends SimObject { + /** + * Create an electro-optical sensor. + * @param {number} height - The height of the sensor in pixels. + * @param {number} width - The width of the sensor in pixels. + * @param {number} y_fov - The vertical field of view of the sensor in degrees. + * @param {number} x_fov - The horizontal field of view of the sensor in degrees. + * @param {Array} field_of_regard - An array of objects representing the field of regard of the sensor. + * @param {string} name - The name of the sensor. + */ + constructor(height, width, y_fov, x_fov, field_of_regard=[], name='ElectroOpticalSensor') { + super(name) + this.height = height + this.width = width + this.y_fov = y_fov + this.x_fov = x_fov + this.y_ifov = this.y_fov / this.height + this.x_ifov = this.x_fov / this.width + this.field_of_regard = field_of_regard + } + + /** + * Update the state of the sensor. + * @param {JulianDate} time - The current simulation time. + * @param {Universe} universe - The universe in which the sensor exists. + * @override + */ + _update(time, universe) { + // TODO do nothing for now + } + +} + +export default ElectroOpicalSensor diff --git a/src/engine/objects/EphemerisObject.js b/src/engine/objects/EphemerisObject.js new file mode 100644 index 0000000..ac4bb2c --- /dev/null +++ b/src/engine/objects/EphemerisObject.js @@ -0,0 +1,59 @@ +import { JulianDate, ReferenceFrame, Math as CMath} from 'cesium' +import { lagrange_fast } from '../dynamics/lagrange' +import SimObject from './SimObject' + +const K_KM = CMath.GRAVITATIONALPARAMETER / 1e9 + +/** + * Represents an object with ephemeris data. + * @extends SimObject + */ +class EphemerisObject extends SimObject { + /** + * Creates an instance of EphemerisObject. + * @param {JulianDate[]} times - Array of JulianDate objects representing the times of the state vectors. + * @param {Cartesian3[]} positions - Array of Cartesian3 objects representing the positions of the state vectors. + * @param {Cartesian3[]} velocities - Array of Cartesian3 objects representing the velocities of the state vectors. + * @param {string} [name='EphemerisObject'] - Name of the object. + * @param {ReferenceFrame} [referenceFrame=ReferenceFrame.INERTIAL] - Reference frame of the object. + */ + constructor(times, positions, velocities, name='EphemerisObject', referenceFrame=ReferenceFrame.INERTIAL) { + super(name, referenceFrame) + + this._stateVectors = [] + for(let i = 0; i < times.length; i++) { + this._stateVectors.push({ + time: times[i], + position: positions[i], + velocity: velocities[i] + }) + } + this._stateVectors.sort((a, b) => JulianDate.compare(a.time, b.time)) + + this._times = [] + this._positions = [] + this._epoch = new JulianDate(this._stateVectors[0].time) + + for(let i = 0; i < this._stateVectors.length; i++) { + this._times.push(JulianDate.secondsDifference(this._stateVectors[0].time, this._stateVectors[i].time)) + this._positions.push(this._stateVectors[i].position.x) + this._positions.push(this._stateVectors[i].position.y) + this._positions.push(this._stateVectors[i].position.z) + } + + this._period = rv2p(K_KM, this._stateVectors[0].position, this._stateVectors[0].velocity) + } + + /** + * Updates the position of the object at the given time. + * @param {JulianDate} time - The time to update the position to. + * @param {Universe} universe - The universe object containing gravitational constants and other data. + * @override + */ + _update(time, universe) { + const delta = JulianDate.secondsDifference(time, this._epoch) + lagrange_fast(this._times, this._positions, delta, this._position) + } +} + +export default EphemerisObject diff --git a/src/engine/objects/Gimbal.js b/src/engine/objects/Gimbal.js new file mode 100644 index 0000000..139c668 --- /dev/null +++ b/src/engine/objects/Gimbal.js @@ -0,0 +1,113 @@ +import { Cartesian3, JulianDate, defined } from "cesium"; +import SimObject from "./SimObject"; +import Universe from "../Universe"; + +/** + * Represents a base gimbal object that can track another object. + * @extends SimObject + * + */ +class Gimbal extends SimObject { + /** + * Creates a new Gimbal object. + * @param {string} [name='Gimbal'] - The name of the Gimbal object. + */ + constructor(name='Gimbal') { + super(name); + this._trackObject = null; + this._trackMode = 'fixed'; + this._sidereal = null; + this._range = 0 + } + + /** + * Gets the range to the track object. + * @returns {number} - The range to the tracked object. + */ + get range() { + if (this.trackMode === 'rate') + return this._range + else { + return 45000000.0 + } + } + + /** + * Gets the track mode of the Gimbal object. + * @returns {string} - The track mode of the Gimbal object. + */ + get trackMode() { + return this._trackMode; + } + + /** + * Sets the track mode of the Gimbal object. + * @param {string} value - The track mode to set. + */ + set trackMode(value) { + this._trackMode = value; + } + + /** + * Gets the object that the Gimbal object is tracking. + * @returns {SimObject} - The object that the Gimbal object is tracking. + */ + get trackObject() { + return this._trackObject; + } + + /** + * Sets the object that the Gimbal object is tracking. + * @param {SimObject} value - The object to track. + */ + set trackObject(value) { + if(value === this || value.parent === this) { + console.warn('Gimbal.trackObject cannot be set to self or child.'); + return; + } + + this._trackObject = value; + } + + /** + * Updates the Gimbal object. + * @param {JulianDate} time - The current time. + * @param {Universe} universe - The universe object. + * @override + */ + update(time, universe) { + super.update(time, universe, true, true) // always force update for now since gimbal can moved when time stops + } + + /** + * Gets the local vector of the Gimbal object based on tracking mode and track object. + * @param {JulianDate} time - The current time. + * @param {Universe} universe - The universe object. + * @returns {Cartesian3|null} - The local vector of the Gimbal object. + * @private + */ + _trackToLocalVector(time, universe) { + let localVector = new Cartesian3(); + if (defined(this._trackObject) && this._trackMode === 'rate') { + this._trackObject.update(time, universe); + localVector = this._trackObject.transformPointTo(this.parent, Cartesian3.ZERO, localVector); + } else if (this._trackMode === 'sidereal') { + console.log('sidereal not implemented'); + } else { + return null; // fixed mode, do nothing + } + return localVector; + } + + /** + * Override this function to update gimbal orientation. + * @param {JulianDate} time - The time to update the object to. + * @param {Universe} universe - The universe object. + * @abstract + */ + _update(time, universe) { + throw new Error('Gimbal._update must be implemented in derived classes.'); + } +} + +export default Gimbal; \ No newline at end of file diff --git a/src/engine/objects/LagrangeInterpolatedObject.js b/src/engine/objects/LagrangeInterpolatedObject.js new file mode 100644 index 0000000..a39273e --- /dev/null +++ b/src/engine/objects/LagrangeInterpolatedObject.js @@ -0,0 +1,57 @@ +import { JulianDate, defaultValue} from 'cesium' +import { lagrange } from '../dynamics/lagrange' +import SimObject from './SimObject' +import Universe from '../Universe' + +/** + * A class representing a Lagrange interpolated object. + * @extends SimObject + */ +class LagrangeInterpolatedObject extends SimObject { + /** + * Create a LagrangeInterpolatedObject. + * @param {Object} object - The object to interpolate. + */ + constructor(object) { + super(object.name, object.referenceFrame) + this._object = object + this._times = [] + this._positions = [] + this._interval = defaultValue(this.period / 60.0, 100) + this._epoch = new JulianDate() + } + + /** + * The period of the object. + * @type {Number} + */ + get period() { + return this._object.period + } + + /** + * The eccentricity of the object. + * @type {Number} + */ + get eccentricity() { + return this._object.eccentricity + } + + /** + * Update the object's position. + * @param {JulianDate} time - The time to update the position for. + * @param {Universe} universe - The universe object. + * @override + */ + _update(time, universe) { + const that = this + const f = function(t) { + that._object.update(t, universe) + return that._object.position + } + + lagrange(this._times, this._positions, this._epoch, time, f, this._position, this._interval) + } +} + +export default LagrangeInterpolatedObject diff --git a/src/engine/objects/SGP4Satellite.js b/src/engine/objects/SGP4Satellite.js new file mode 100644 index 0000000..f9cd0fb --- /dev/null +++ b/src/engine/objects/SGP4Satellite.js @@ -0,0 +1,47 @@ +import { sgp4, twoline2satrec } from 'satellite.js' +import { Cartesian3, JulianDate, defined, ReferenceFrame, Math as CMath} from 'cesium' +import SimObject from './SimObject.js' + +/** + * Represents a satellite object that uses the SGP4 model for propagation. + * @extends SimObject + */ +class SGP4Satellite extends SimObject { + /** + * Creates a new SGP4Satellite object. + * @param {string} tle1 - The first line of the TLE (Two-Line Element) set. + * @param {string} tle2 - The second line of the TLE (Two-Line Element) set. + * @param {string} orientation - The orientation of the satellite. + * @param {string} name - The name of the satellite. + */ + constructor(tle1, tle2, orientation, name='SGP4Satellite') { + super(name, ReferenceFrame.INERTIAL) + this._satrec = twoline2satrec(tle1, tle2) + this._epoch = new JulianDate(this._satrec.jdsatepoch) + this._period = CMath.TWO_PI / this._satrec.no * 60 + this._eccentricity = this._satrec.ecco + this.orientation = orientation //TODO + } + + /** + * Updates the position and velocity of the satellite based on the current time and universe. + * @param {JulianDate} time - The current time. + * @param {Universe} universe - The universe object. + * @override + */ + _update(time, universe) { + const deltaMin = JulianDate.secondsDifference(time, this._epoch) / 60.0 + const positionAndVelocity = sgp4(this._satrec, deltaMin) + + // check for bad sgp4 propagations + if(!defined(positionAndVelocity.position) || positionAndVelocity.position === false) { + positionAndVelocity.position = new Cartesian3() + positionAndVelocity.velocity = new Cartesian3() + } else { + Cartesian3.multiplyByScalar(positionAndVelocity.position, 1000.0, this._position) + Cartesian3.multiplyByScalar(positionAndVelocity.velocity, 1000.0, this._velocity) + } + } +} + +export default SGP4Satellite diff --git a/src/engine/objects/SimObject.js b/src/engine/objects/SimObject.js new file mode 100644 index 0000000..7573d2a --- /dev/null +++ b/src/engine/objects/SimObject.js @@ -0,0 +1,211 @@ +import { ReferenceFrame, Cartesian3, JulianDate, Matrix4, defined, defaultValue, Entity } from "cesium"; +import TransformGroup from "../graph/TransformGroup"; +import Universe from "../Universe"; + +/** + * A base class for all simulation objects. + * @extends TransformGroup + */ +class SimObject extends TransformGroup { + /** + * Creates a new SimObject. + * @param {string} [name='undefined'] - The name of the object. + * @param {ReferenceFrame} [referenceFrame=undefined] - The reference frame of the object. + */ + constructor(name='undefined', referenceFrame=undefined) { + super(); + this._name = name; + this._referenceFrame = referenceFrame; + + this._position = new Cartesian3(); + this._velocity = new Cartesian3(); + + this._localToWorldTransform = new Matrix4(); + this._worldToLocalTransform = new Matrix4(); + + this._lastUpdate = new JulianDate(); + this._lastUniverse = undefined; + this._transformDirty = true; + + this._visualizer = {}; + this._updateListeners = []; + } + + /** + * The eccentricity of the object's orbit. + * @type {number} + * @readonly + */ + get eccentricity() { + return this._eccentricity; + } + + /** + * The period of the object's orbit. + * @type {number} + * @readonly + */ + get period() { + return this._period; + } + + /** + * Sets the visualizer for the object. + * @type {Entity} + */ + set visualizer(visualizer) { + this._visualizer = visualizer; + } + + /** + * Gets the visualizer for the object. + * @type {Entity} + * @readonly + */ + get visualizer() { + return this._visualizer; + } + + /** + * Gets the update listeners for the object. + * @type {Array} + * @readonly + */ + get updateListeners() { + return this._updateListeners; + } + + /** + * Gets the reference frame of the object. + * @type {ReferenceFrame} + * @readonly + */ + get referenceFrame() { + return defaultValue(this._referenceFrame, defined(this.parent) ? this.parent.referenceFrame : undefined); + } + + /** + * Gets the position of the object in the Cesium world fixed reference frame. + * @type {Cartesian3} + * @readonly + */ + get position() { + return defined(this._referenceFrame) ? this._position : defined(this.parent) ? this.parent.position : undefined; + } + + /** + * Gets the velocity of the object in the Cesium world fixed reference frame. + * @type {Cartesian3} + * @readonly + */ + get velocity() { + return defined(this._referenceFrame) ? this._velocity : defined(this._velocity) ? this.parent._velocity : undefined; + } + + /** + * Gets the time of the last update for the object. + * @type {JulianDate} + * @readonly + */ + get time() { + return this._lastUpdate; + } + + /** + * Gets the name of the object. + * @type {string} + * @readonly + */ + get name() { + return this._name; + } + + /** + * Gets the world to local transform matrix for the object. + * @type {Matrix4} + * @readonly + */ + get worldToLocalTransform() { + this._updateTransformsIfDirty() + return this._worldToLocalTransform; + } + + /** + * Gets the local to world transform matrix for the object. + * @type {Matrix4} + * @readonly + */ + get localToWorldTransform() { + this._updateTransformsIfDirty() + return this._localToWorldTransform; + } + + /** + * Gets the world position (ECI) of the object. + * @type {Cartesian3} + * @readonly + */ + get worldPosition() { + if(this._referenceFrame === ReferenceFrame.INERTIAL) + return this._position; + else + return super.worldPosition + } + + /** + * Updates the object's position, velocity, and orientation. + * @param {JulianDate} time - The time to update the object to. + * @param {Universe} universe - The universe object. + * @param {boolean} [forceUpdate=false] - Whether to force an update. + * @param {boolean} [updateParent=true] - Whether to update the parent object. + */ + update(time, universe, forceUpdate = false, updateParent = true) { + if (!forceUpdate && JulianDate.equals(time, this._lastUpdate)) + return; + + if(updateParent && defined(this.parent)) + this.parent.update(time, universe, forceUpdate, updateParent); + + // override this function to update the position, velocity, and orientation + this._update(time, universe); + + // update position (for cesium) + this.setTranslation(this._position) + + // mark transforms dirty + this._transformDirty = true; + JulianDate.clone(time, this._lastUpdate); + this._lastUniverse = universe; + + // update any listeners + this._updateListeners.forEach(ul => ul.update(time, universe)); + } + + /** + * Updates the object's world to local and local to world transform matrices if they are dirty. + * @private + */ + _updateTransformsIfDirty() { + if(this._transformDirty) { + if(defined(this.parent) && !this.parent._lastUpdate.equals(this._lastUpdate)) { + this.parent.update(this._lastUpdate, this._lastUniverse, true, false); + } + this._localToWorldTransform = super.localToWorldTransform; + Matrix4.inverseTransformation(this._localToWorldTransform, this._worldToLocalTransform); + this._transformDirty = false; + } + } + + /** + * Override this function to update the position, velocity, and orientation of the object. + * @param {JulianDate} time - The time to update the object to. + * @param {Universe} universe - The universe object. + * @abstract + */ + _update(time, universe) { + throw new Error('SimObject._update must be implemented in derived classes.'); + } + +} + +export default SimObject; diff --git a/src/engine/objects/TwoBodySatellite.js b/src/engine/objects/TwoBodySatellite.js new file mode 100644 index 0000000..4f58d0c --- /dev/null +++ b/src/engine/objects/TwoBodySatellite.js @@ -0,0 +1,46 @@ +import { vallado, rv2p, rv2ecc } from '../dynamics/twobody.js' +import { Cartesian3, JulianDate, ReferenceFrame } from 'cesium' +import { Math as CMath } from 'cesium' +import SimObject from './SimObject.js' + +const K_KM = CMath.GRAVITATIONALPARAMETER / 1e9 + +/** + * Represents a two-body satellite object. + * @extends SimObject + */ +class TwoBodySatellite extends SimObject { + /** + * Creates a new TwoBodySatellite object. + * @param {Cartesian3} position - The initial position of the satellite in meters. + * @param {Cartesian3} velocity - The initial velocity of the satellite in meters per second. + * @param {JulianDate} time - The initial time of the satellite. + * @param {string} orientation - The orientation of the satellite. + * @param {string} name - The name of the satellite. + */ + constructor(position, velocity, time, orientation, name='TwoBodySatellite') { + super(name, ReferenceFrame.INERTIAL) + const positionKm = Cartesian3.multiplyByScalar(position, 1e-3, new Cartesian3()) + const velocityKm = Cartesian3.multiplyByScalar(velocity, 1e-3, new Cartesian3()) + this._epoch = { positionKm, velocityKm, time } + this._period = rv2p(K_KM, positionKm, velocityKm) + this._eccentricity = rv2ecc(K_KM, positionKm, velocityKm) + this.orientation = orientation //TODO + } + + /** + * Updates the position and velocity of the satellite at the given time. + * @param {JulianDate} time - The time to update the satellite to. + * @param {Universe} universe - The universe object containing gravitational constants and other parameters. + * @override + */ + _update(time, universe) { + let deltaSec = JulianDate.secondsDifference(time, this._epoch.time) + let positionAndVelocity = vallado(K_KM, this._epoch.positionKm, this._epoch.velocityKm, deltaSec, 350) + + Cartesian3.multiplyByScalar(positionAndVelocity.position, 1000.0, this._position) + Cartesian3.multiplyByScalar(positionAndVelocity.velocity, 1000.0, this._velocity) + } +} + +export default TwoBodySatellite diff --git a/src/index.js b/src/index.js new file mode 100755 index 0000000..8f28979 --- /dev/null +++ b/src/index.js @@ -0,0 +1,32 @@ +globalThis.SATSIM_VERSION = "0.2.0-alpha" + +export { default as Universe } from './engine/Universe.js' + +export { default as Node } from './engine/graph/Node.js' +export { default as Group } from './engine/graph/Group.js' +export { default as TransformGroup } from './engine/graph/TransformGroup.js' + +export { default as AzElGimbal } from './engine/objects/AzElGimbal.js' +export { default as Earth } from './engine/objects/Earth.js' +export { default as EarthGroundStation } from './engine/objects/EarthGroundStation.js' +export { default as ElectroOpicalSensor } from './engine/objects/ElectroOpticalSensor.js' +export { default as EphemerisObject } from './engine/objects/EphemerisObject.js' +export { default as Gimbal } from './engine/objects/Gimbal.js' +export { default as LagrangeInterpolatedObject } from './engine/objects/LagrangeInterpolatedObject.js' +export { default as SGP4Satellite } from './engine/objects/SGP4Satellite.js' +export { default as SimObject } from './engine/objects/SimObject.js' +export { default as TwoBodySatellite } from './engine/objects/TwoBodySatellite.js' + +export { default as CallbackPositionProperty } from './engine/cesium/CallbackPositionProperty.js' +export { default as CompoundElementVisualizer } from './engine/cesium/CompoundElementVisualizer.js' +export { default as CoverageGridVisualizer } from './engine/cesium/CoverageGridVisualizer.js' +export { default as GeoBeltVisualizer } from './engine/cesium/GeoBeltVisualizer.js' +export { default as SensorFieldOfRegardVisualizer } from './engine/cesium/SensorFieldOfRegardVisualizer.js' +export { default as SensorFieldOfVIewVisualizer } from './engine/cesium/SensorFieldOfVIewVisualizer.js' + +export { default as InfoBox } from './widgets/InfoBox.js' +export { default as Toolbar } from './widgets/Toolbar.js' +export { createViewer, mixinViewer } from './widgets/Viewer.js' + +export { fetchTle } from './io/tle.js' +export { southEastZenithToAzEl, spaceBasedToAzEl } from './engine/dynamics/gimbal.js' \ No newline at end of file diff --git a/src/io/tle.js b/src/io/tle.js new file mode 100644 index 0000000..6b94f31 --- /dev/null +++ b/src/io/tle.js @@ -0,0 +1,26 @@ +async function fetchTle(url, linesPerSatellite, callback) { + const response = await fetch(url); + const text = await response.text(); + + const lines = text.split('\n'); + const count = lines.length - 1; + + for(let i = 0; i < count; i+=linesPerSatellite) { + let line1, line2, line3 = ''; + if(linesPerSatellite === 2) { + line1 = lines[i].slice(2, 8); + line2 = lines[i]; + line3 = lines[i+1]; + } else { + line1 = lines[i].trim(); + line2 = lines[i+1]; + line3 = lines[i+2]; + } + + callback(line1, line2, line3) + } +} + +export { + fetchTle +} \ No newline at end of file diff --git a/src/widgets/InfoBox.js b/src/widgets/InfoBox.js new file mode 100644 index 0000000..608e2f1 --- /dev/null +++ b/src/widgets/InfoBox.js @@ -0,0 +1,198 @@ +import { + Check, + Color, + defined, + destroyObject, + getElement, + knockout, + subscribeAndEvaluate +} from "cesium"; +import InfoBoxViewModel from "./InfoBoxViewModel.js"; +import "cesium/Build/Cesium/Widgets/InfoBox/InfoBoxDescription.css" + +/** + * A widget for displaying information or a description. + * + * @alias InfoBox + * @constructor + * + * @param {Element|string} container The DOM element or ID that will contain the widget. + * + * @exception {DeveloperError} Element with id "container" does not exist in the document. + */ +function InfoBox(container) { + //>>includeStart('debug', pragmas.debug); + Check.defined("container", container); + //>>includeEnd('debug') + + container = getElement(container); + + const infoElement = document.createElement("div"); + infoElement.className = "cesium-infoBox"; + infoElement.setAttribute( + "data-bind", + '\ +css: { "cesium-infoBox-visible" : showInfo, "cesium-infoBox-bodyless" : _bodyless }' + ); + container.appendChild(infoElement); + + const titleElement = document.createElement("div"); + titleElement.className = "cesium-infoBox-title"; + titleElement.setAttribute("data-bind", "text: titleText"); + infoElement.appendChild(titleElement); + + const cameraElement = document.createElement("button"); + cameraElement.type = "button"; + cameraElement.className = "cesium-button cesium-infoBox-camera"; + cameraElement.setAttribute( + "data-bind", + '\ +attr: { title: "Focus camera on object" },\ +click: function () { cameraClicked.raiseEvent(this); },\ +enable: enableCamera,\ +cesiumSvgPath: { path: cameraIconPath, width: 32, height: 32 }' + ); + infoElement.appendChild(cameraElement); + + const closeElement = document.createElement("button"); + closeElement.type = "button"; + closeElement.className = "cesium-infoBox-close"; + closeElement.setAttribute( + "data-bind", + "\ +click: function () { closeClicked.raiseEvent(this); }" + ); + closeElement.innerHTML = "×"; + infoElement.appendChild(closeElement); + + const frame = document.createElement("div"); + frame.className = "cesium-infoBox-iframe"; + frame.setAttribute("sandbox", "allow-same-origin allow-popups allow-forms"); //allow-pointer-lock allow-scripts allow-top-navigation + frame.setAttribute( + "data-bind", + "style : { maxHeight : maxHeightOffset(40) }" + ); + frame.setAttribute("allowfullscreen", true); + infoElement.appendChild(frame); + + const viewModel = new InfoBoxViewModel(); + knockout.applyBindings(viewModel, infoElement); + + this._container = container; + this._element = infoElement; + this._frame = frame; + this._viewModel = viewModel; + this._descriptionSubscription = undefined; + + const that = this; + //We can't actually add anything into the frame until the load event is fired + + const frameDocument = document; + + //div to use for description content. + const frameContent = frameDocument.createElement("div"); + frame.appendChild(frameContent); + frameContent.className = "cesium-infoBox-description"; + + //We manually subscribe to the description event rather than through a binding for two reasons. + //1. It's an easy way to ensure order of operation so that we can adjust the height. + //2. Knockout does not bind to elements inside of an iFrame, so we would have to apply a second binding + // model anyway. + that._descriptionSubscription = subscribeAndEvaluate( + viewModel, + "description", + function (value) { + // Set the frame to small height, force vertical scroll bar to appear, and text to wrap accordingly. + frame.style.height = "5px"; + frameContent.innerHTML = value; + + //If the snippet is a single element, then use its background + //color for the body of the InfoBox. This makes the padding match + //the content and produces much nicer results. + let background = null; + const firstElementChild = frameContent.firstElementChild; + if ( + firstElementChild !== null && + frameContent.childNodes.length === 1 + ) { + const style = window.getComputedStyle(firstElementChild); + if (style !== null) { + const backgroundColor = style["background-color"]; + const color = Color.fromCssColorString(backgroundColor); + if (defined(color) && color.alpha !== 0) { + background = style["background-color"]; + } + } + } + infoElement.style["background-color"] = background; + + // Measure and set the new custom height, based on text wrapped above. + const height = frameContent.getBoundingClientRect().height; + frame.style.height = `${height}px`; + } + ); + +} + +Object.defineProperties(InfoBox.prototype, { + /** + * Gets the parent container. + * @memberof InfoBox.prototype + * + * @type {Element} + */ + container: { + get: function () { + return this._container; + }, + }, + + /** + * Gets the view model. + * @memberof InfoBox.prototype + * + * @type {InfoBoxViewModel} + */ + viewModel: { + get: function () { + return this._viewModel; + }, + }, + + /** + * Gets the iframe used to display the description. + * @memberof InfoBox.prototype + * + * @type {HTMLIFrameElement} + */ + frame: { + get: function () { + return this._frame; + }, + }, +}); + +/** + * @returns {boolean} true if the object has been destroyed, false otherwise. + */ +InfoBox.prototype.isDestroyed = function () { + return false; +}; + +/** + * Destroys the widget. Should be called if permanently + * removing the widget from layout. + */ +InfoBox.prototype.destroy = function () { + const container = this._container; + knockout.cleanNode(this._element); + container.removeChild(this._element); + + if (defined(this._descriptionSubscription)) { + this._descriptionSubscription.dispose(); + } + + return destroyObject(this); +}; + +export default InfoBox; \ No newline at end of file diff --git a/src/widgets/InfoBoxViewModel.js b/src/widgets/InfoBoxViewModel.js new file mode 100644 index 0000000..d84c70a --- /dev/null +++ b/src/widgets/InfoBoxViewModel.js @@ -0,0 +1,116 @@ +import { defined, Event, knockout } from "cesium"; + +const cameraEnabledPath = + "M 13.84375 7.03125 C 11.412798 7.03125 9.46875 8.975298 9.46875 11.40625 L 9.46875 11.59375 L 2.53125 7.21875 L 2.53125 24.0625 L 9.46875 19.6875 C 9.4853444 22.104033 11.423165 24.0625 13.84375 24.0625 L 25.875 24.0625 C 28.305952 24.0625 30.28125 22.087202 30.28125 19.65625 L 30.28125 11.40625 C 30.28125 8.975298 28.305952 7.03125 25.875 7.03125 L 13.84375 7.03125 z"; +const cameraDisabledPath = + "M 27.34375 1.65625 L 5.28125 27.9375 L 8.09375 30.3125 L 30.15625 4.03125 L 27.34375 1.65625 z M 13.84375 7.03125 C 11.412798 7.03125 9.46875 8.975298 9.46875 11.40625 L 9.46875 11.59375 L 2.53125 7.21875 L 2.53125 24.0625 L 9.46875 19.6875 C 9.4724893 20.232036 9.5676108 20.7379 9.75 21.21875 L 21.65625 7.03125 L 13.84375 7.03125 z M 28.21875 7.71875 L 14.53125 24.0625 L 25.875 24.0625 C 28.305952 24.0625 30.28125 22.087202 30.28125 19.65625 L 30.28125 11.40625 C 30.28125 9.8371439 29.456025 8.4902779 28.21875 7.71875 z"; + +/** + * The view model for {@link InfoBox}. + * @alias InfoBoxViewModel + * @constructor + */ +function InfoBoxViewModel() { + this._cameraClicked = new Event(); + this._closeClicked = new Event(); + + /** + * Gets or sets the maximum height of the info box in pixels. This property is observable. + * @type {number} + */ + this.maxHeight = 500; + + /** + * Gets or sets whether the camera tracking icon is enabled. + * @type {boolean} + */ + this.enableCamera = false; + + /** + * Gets or sets the status of current camera tracking of the selected object. + * @type {boolean} + */ + this.isCameraTracking = false; + + /** + * Gets or sets the visibility of the info box. + * @type {boolean} + */ + this.showInfo = false; + + /** + * Gets or sets the title text in the info box. + * @type {string} + */ + this.titleText = ""; + + /** + * Gets or sets the description HTML for the info box. + * @type {string} + */ + this.description = ""; + + knockout.track(this, [ + "showInfo", + "titleText", + "description", + "maxHeight", + "enableCamera", + "isCameraTracking", + ]); + + this._loadingIndicatorHtml = + '
'; + + /** + * Gets the SVG path of the camera icon, which can change to be "crossed out" or not. + * @type {string} + */ + this.cameraIconPath = undefined; + knockout.defineProperty(this, "cameraIconPath", { + get: function () { + return !this.enableCamera || this.isCameraTracking + ? cameraDisabledPath + : cameraEnabledPath; + }, + }); + + knockout.defineProperty(this, "_bodyless", { + get: function () { + return !defined(this.description) || this.description.length === 0; + }, + }); +} + +/** + * Gets the maximum height of sections within the info box, minus an offset, in CSS-ready form. + * @param {number} offset The offset in pixels. + * @returns {string} + */ +InfoBoxViewModel.prototype.maxHeightOffset = function (offset) { + return `${this.maxHeight - offset}px`; +}; + +Object.defineProperties(InfoBoxViewModel.prototype, { + /** + * Gets an {@link Event} that is fired when the user clicks the camera icon. + * @memberof InfoBoxViewModel.prototype + * @type {Event} + */ + cameraClicked: { + get: function () { + return this._cameraClicked; + }, + }, + /** + * Gets an {@link Event} that is fired when the user closes the info box. + * @memberof InfoBoxViewModel.prototype + * @type {Event} + */ + closeClicked: { + get: function () { + return this._closeClicked; + }, + }, +}); +export default InfoBoxViewModel; \ No newline at end of file diff --git a/src/widgets/Toolbar.js b/src/widgets/Toolbar.js new file mode 100644 index 0000000..06dab13 --- /dev/null +++ b/src/widgets/Toolbar.js @@ -0,0 +1,122 @@ +import { defined, getElement } from "cesium" + +/** + * A toolbar widget for adding buttons, menus, and separators. + * + * @constructor + * @param {Element|String} container The DOM element or ID that will contain the toolbar. + */ +function Toolbar(container) { + this._container = getElement(container) +} + +/** + * Adds a separator to the toolbar. + */ +Toolbar.prototype.addSeparator = function () { + const separator = document.createElement("br"); + this._container.appendChild(separator); +} + +/** + * Adds a toggle button to the toolbar. + * + * @param {String} text The text label for the button. + * @param {Boolean} checked Whether the button is initially checked. + * @param {Function} onchange The function to call when the button is toggled. + * @returns {HTMLInputElement} The input element for the toggle button. + */ +Toolbar.prototype.addToggleButton = function (text, checked, onchange) { + const input = document.createElement("input"); + input.checked = checked; + input.type = "checkbox"; + input.style.pointerEvents = "none"; + const label = document.createElement("label"); + label.appendChild(input); + label.appendChild(document.createTextNode(text)); + label.style.pointerEvents = "none"; + const button = document.createElement("button"); + button.type = "button"; + button.className = "cesium-button"; + button.appendChild(label); + button.onclick = function () { + input.checked = !input.checked; + onchange(input.checked); + }; + this._container.appendChild(button); + + input.enable = function(value) { + input.disabled = !value; + button.disabled = !value; + } + + return input; +} + +/** + * Adds a button to the toolbar. + * + * @param {String} text The text label for the button. + * @param {Function} onclick The function to call when the button is clicked. + * @returns {HTMLButtonElement} The button element. + */ +Toolbar.prototype.addToolbarButton = function (text, onclick) { + const button = document.createElement("button"); + button.type = "button"; + button.className = "cesium-button"; + button.onclick = function () { + onclick(); + }; + button.textContent = text; + this._container.appendChild(button); + + button.enable = function(value) { + button.disabled = !value; + } + + return button; +} + +/** + * Adds a menu to the toolbar. + * + * @param {Object[]} options The menu options. + * @param {String} options[].text The text label for the menu option. + * @param {String} options[].value The value for the menu option. + * @param {Object} [menu] The existing menu element to add options to. + * @returns {HTMLSelectElement} The menu element. + */ +Toolbar.prototype.addToolbarMenu = function (options, menu) { + if (!defined(menu)) { + menu = document.createElement("select"); + menu.className = "cesium-button"; + menu.userOptions = []; + menu.enable = function(value) { + menu.disabled = !value; + } + menu.onchange = function () { + const item = menu.userOptions[menu.selectedIndex]; + if (item && typeof item.onselect === "function") { + item.onselect(); + } + }; + this._container.appendChild(menu); + } + menu.userOptions.push(...options) + + for (let i = 0, len = options.length; i < len; ++i) { + const option = document.createElement("option"); + option.textContent = options[i].text; + option.value = options[i].value; + menu.appendChild(option); + } + + return menu; +} + +/** + * Resets the toolbar. + */ +Toolbar.prototype.reset = function () { } + +export default Toolbar; \ No newline at end of file diff --git a/src/widgets/Viewer.js b/src/widgets/Viewer.js new file mode 100644 index 0000000..4b7de8f --- /dev/null +++ b/src/widgets/Viewer.js @@ -0,0 +1,755 @@ +import { JulianDate, PointPrimitiveCollection, BillboardCollection, Viewer, ImageryLayer, UrlTemplateImageryProvider, IonImageryProvider, defined, Math as CMath, Cartesian2, Cartesian3, Matrix4, Color, SceneMode, ReferenceFrame, defaultValue, PostProcessStage, EntityView, Entity, Clock } from "cesium" +import InfoBox from "./InfoBox.js" +import Toolbar from "./Toolbar.js" +import SensorFieldOfRegardVisualizer from "../engine/cesium/SensorFieldOfRegardVisualizer.js" +import SensorFieldOfViewVisualizer from "../engine/cesium/SensorFieldOfVIewVisualizer.js" +import GeoBeltVisualizer from "../engine/cesium/GeoBeltVisualizer.js" +import { createObjectPositionProperty, createObjectOrientationProperty, getObjectPositionInCesiumFrame } from "../engine/cesium/utils.js" +import ElectroOpicalSensor from "../engine/objects/ElectroOpticalSensor.js" +import CoverageGridVisualizer from "../engine/cesium/CoverageGridVisualizer.js" +import SimObject from "../engine/objects/SimObject.js" +import { CompoundElementVisualizer } from "../index.js" + + +/** + * Create a new Cesium Viewer and mixin new capabilities. + * + * @param {string | Element} container - The HTML container. + * @param {Universe} universe - The SatSim Universe. + * @param {*} options - Options + * @returns {Viewer} - The new viewer instance. + */ +function createViewer(container, universe, options) { + + options = defaultValue(options, {}) + if (!defined(options.infoBox)) + options.infoBox = false + + // Create Baseline Cesium Viewer + const viewer = new Viewer(container, options) + + // Mixin new capabilities + return mixinViewer(viewer, universe, options) +} + +/** + * Mixin new capabilities into the viewer. + * + * @param {Viewer} viewer - The viewer to upgrade. + * @param {Universe} universe - The universe. + * @param {*} options - Options + * @returns {Viewer} - The upgraded viewer instance. + */ +function mixinViewer(viewer, universe, options) { + + // Default options + options = defaultValue(options, {}) + options.showNightLayer = defaultValue(options.showNightLayer, true) + options.showWeatherLayer = defaultValue(options.showWeatherLayer, true) + options.weatherApiKey = defaultValue(options.weatherApiKey, 'YOUR_API_KEY') + options.infoBox2 = defaultValue(options.infoBox2, true) + options.infoBox2Container = defaultValue(viewer._element, undefined) + options.toolbar2 = defaultValue(options.toolbar2, true) + options.toolbar2Container = defaultValue(viewer._element, undefined) + + // Viewer variables + const scene = viewer.scene + const layers = scene.imageryLayers + const controller = scene.screenSpaceCameraController + const camera = viewer.camera + const selectionIndicator = viewer._selectionIndicator + + // Mixin variables + viewer.referenceFrameView = ReferenceFrame.FIXED + viewer.trackedSensor = null + viewer.cameraMode = "world" + viewer.sensorFovVisualizers = [] + viewer.sensorForVisualizers = [] + viewer.geoBeltVisualizer = new GeoBeltVisualizer(viewer) + viewer.sensorGrids = [] + viewer.lastPicked = undefined + viewer.billboards = scene.primitives.add(new BillboardCollection()); + viewer.points = scene.primitives.add(new PointPrimitiveCollection()); + viewer.coverageVisualizer = new CoverageGridVisualizer(viewer, universe); + + viewer.BILLBOARD_SATELLITE = viewer.billboards.add({ + show: false, + image: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADJSURBVDhPnZHRDcMgEEMZjVEYpaNklIzSEfLfD4qNnXAJSFWfhO7w2Zc0Tf9QG2rXrEzSUeZLOGm47WoH95x3Hl3jEgilvDgsOQUTqsNl68ezEwn1vae6lceSEEYvvWNT/Rxc4CXQNGadho1NXoJ+9iaqc2xi2xbt23PJCDIB6TQjOC6Bho/sDy3fBQT8PrVhibU7yBFcEPaRxOoeTwbwByCOYf9VGp1BYI1BA+EeHhmfzKbBoJEQwn1yzUZtyspIQUha85MpkNIXB7GizqDEECsAAAAASUVORK5CYII=" + }).image + viewer.BILLBOARD_GROUNDSTATION = viewer.billboards.add({ + show: false, + image: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACvSURBVDhPrZDRDcMgDAU9GqN0lIzijw6SUbJJygUeNQgSqepJTyHG91LVVpwDdfxM3T9TSl1EXZvDwii471fivK73cBFFQNTT/d2KoGpfGOpSIkhUpgUMxq9DFEsWv4IXhlyCnhBFnZcFEEuYqbiUlNwWgMTdrZ3JbQFoEVG53rd8ztG9aPJMnBUQf/VFraBJeWnLS0RfjbKyLJA8FkT5seDYS1Qwyv8t0B/5C2ZmH2/eTGNNBgMmAAAAAElFTkSuQmCC" + }).image + + // Improved globe settings + scene.globe.enableLighting = true + scene.globe.lightingFadeInDistance = 0.0 + scene.globe.lightingFadeOutDistance = 0.0 + scene.globe.nightFadeInDistance = 10e10 + scene.globe.nightFadeOutDistance = 0 + scene.globe.brightness = 0.0 + scene.globe.dayAlpha = 0.0 + scene.globe.nightAlpha = 0.0 + scene.globe.dynamicAtmosphereLightingFromSun = true + scene.globe.atmosphereLightIntensity = 3.0 + + // Night layer + if (options.showNightLayer) { + const blackMarble = ImageryLayer.fromProviderAsync( + IonImageryProvider.fromAssetId(3812) + ) + blackMarble.dayAlpha = 0.0 + blackMarble.nightAlpha = 0.9 + blackMarble.brightness = 1.5 + layers.add(blackMarble) + } + + // Openweathermap layer + if (options.showWeatherLayer) { + const weatherLayer = new ImageryLayer(new UrlTemplateImageryProvider({ + url: `https://tile.openweathermap.org/map/clouds_new/{z}/{x}/{y}.png?appid=${options.weatherApiKey}`, + maximumLevel: 3, + })) + weatherLayer.dayAlpha = 0.8 + weatherLayer.nightAlpha = 0.5 + layers.add(weatherLayer) + } + + + /////////////////// + // Viewer Overrides + /////////////////// + + /** + * Add an updatePost listener to fix point primitive tracking. + * @param {Clock} clock + */ + const updatePost = function (clock) { + if (defined(viewer._entityView && defined(viewer._trackedEntity)) && defined(viewer._trackedEntity.point2) ) { // fix for point primitive not tracking + viewer._entityView.update(clock.currentTime, viewer.boundingSphereScratch) + } + } + viewer._eventHelper.add(viewer.clock.onTick, updatePost, viewer); + + /** + * Override the scene pick function. + * @param {Cartesian2} windowPosition - The window position. + * @param {number} [width] - Seems to always be undefined. + * @param {number} [height] - Seems to always be undefined. + * @returns {Entity} - The picked entity. + */ + scene.pick = function (windowPosition, width, height) { + + if(viewer.cameraMode === "up") { + //note: don't override width and height, makes drillPick super slow + const bwidth = scene.context.drawingBufferWidth; + const bheight = scene.context.drawingBufferHeight; + let uv = new Cartesian2(windowPosition.x / bwidth * 2.0 - 1.0, windowPosition.y / bheight * 2.0 - 1.0); + + if(Cartesian2.magnitude(uv) >= 1.0) { //don't pick outside the sphere + return undefined + } + + let z = Math.sqrt(1.0 - uv.x * uv.x - uv.y * uv.y); // sphere eq: r^2 = x^2 + y^2 + z^2 + let k = 1.0 / (z * Math.tan(camera.frustum.fov * 0.5)); + uv.x = (uv.x * k) + 0.5; + uv.y = (uv.y * k) + 0.5; + windowPosition = new Cartesian2(bwidth * uv.x, bheight * uv.y) + } + + let e = this._picking.drillPick(this, windowPosition, 100, width, height) + + for (let i = 0; i < e.length; i++) { + if (defined(e[i]) && defined(e[i].id)) { + if (e[i].id.allowPicking !== false) { + viewer.objectPickListener(e[i].id.simObjectRef, viewer.lastPicked) + viewer.lastPicked = e[i].id.simObjectRef + return e[i] + } else if (e[i].collection === viewer.points) { + viewer.objectPickListener(e[i].primitive.id.simObjectRef, viewer.lastPicked) + viewer.lastPicked = e[i].primitive.id.simObjectRef + return e[i].primitive + } + } + } + + viewer.objectPickListener(undefined, viewer.lastPicked) + viewer.lastPicked = undefined + + return undefined + } + + // Setup new morph to 2D, mainly to disable visualizers that don't work in 2D + const origMorphTo2D = scene.morphTo2D + scene.morphTo2D = function (duration) { + ecrButton.enable(false) + sensorFieldOfRegardButton.enable(false) + sensorFieldOfViewButton.enable(false) + geoButton.enable(false) + cameraViewMenu.enable(false) + viewer.showVisual(viewer.geoBeltVisualizer, false) + viewer.showVisual(viewer.sensorForVisualizers, false) + viewer.showVisual(viewer.sensorFovVisualizers, false) + viewer.setCameraMode("world") + cameraViewMenu.selectedIndex = 0 + origMorphTo2D.call(scene, duration) + } + + const origMorphToColumbusView = scene.morphToColumbusView + scene.morphToColumbusView = function (duration) { + ecrButton.enable(false) + sensorFieldOfRegardButton.enable(false) + sensorFieldOfViewButton.enable(false) + geoButton.enable(false) + cameraViewMenu.enable(false) + viewer.showVisual(viewer.geoBeltVisualizer, false) + viewer.showVisual(viewer.sensorForVisualizers, false) + viewer.showVisual(viewer.sensorFovVisualizers, false) + viewer.setCameraMode("world") + cameraViewMenu.selectedIndex = 0 + origMorphToColumbusView.call(scene, duration) + } + + /////////////////// + // New Listeners + /////////////////// + + /** + * Post update listener which adds new 3D perspectives and ECI mode. + */ + scene.postUpdate.addEventListener((scene, time) => { + const camera = viewer.camera + ecrButton.checked = viewer.referenceFrameView === ReferenceFrame.FIXED + universe.earth.update(time, universe) + viewer._selectionIndicator = selectionIndicator + + // senor perspective + if (viewer.cameraMode === "sensor") { + viewer.trackedSensor.update(time, universe) + camera.direction = new Cartesian3(0, 0, -1); + camera.right = new Cartesian3(1, 0, 0); + camera.up = new Cartesian3(0, 1, 0); + camera.position = new Cartesian3(CMath.EPSILON19, 0, 0) // if 0,0,0, cesium will crash + + universe.earth.update(time, universe) + const transform = new Matrix4() + Matrix4.multiply(universe.earth.worldToLocalTransform, viewer.trackedSensor.localToWorldTransform, transform) + Matrix4.clone(transform, camera.transform); + camera.frustum.fov = CMath.toRadians(viewer.trackedSensor.x_fov + viewer.trackedSensor.x_fov * 0.2) + // sensor what's up + } else if (viewer.cameraMode === "up") { + viewer._selectionIndicator = undefined //doesn't work in this mode + viewer.trackedSensor.update(time, universe) + camera.direction = new Cartesian3(0, 0, 1); + camera.right = new Cartesian3(0, 1, 0); + camera.up = new Cartesian3(-1, 0, 0); + camera.position = new Cartesian3(CMath.EPSILON19, 0, 0) // if 0,0,0, cesium will crash + + universe.earth.update(time, universe) + const transform = new Matrix4() + Matrix4.multiply(universe.earth.worldToLocalTransform, viewer.trackedSensor.parent.parent.localToWorldTransform, transform) + Matrix4.clone(transform, camera.transform); + camera.frustum.fov = CMath.toRadians(160) + // world view + } else { + // 2D modes + if (scene.mode !== SceneMode.SCENE3D) { + return + // 3D mode + } else { + // ECI mode + if (viewer.referenceFrameView === ReferenceFrame.INERTIAL) { + viewer.trackedEntity = undefined // disable tracking, doesn't work in ECI + const offset = Cartesian3.clone(camera.position) + camera.lookAtTransform(universe.earth.worldToLocalTransform, offset) + } + } + } + }) + + /** + * Morph complete listener which enables/disables UI elements based on the mode. + */ + scene.morphComplete.addEventListener(function () { + // 2D mode + if (scene.mode !== SceneMode.SCENE3D) { + ecrButton.enable(false) + sensorFieldOfRegardButton.enable(false) + sensorFieldOfViewButton.enable(false) + geoButton.enable(false) + cameraViewMenu.enable(false) + viewer.showVisual(viewer.geoBeltVisualizer, false) + viewer.showVisual(viewer.sensorForVisualizers, false) + viewer.showVisual(viewer.sensorFovVisualizers, false) + // 3D mode + } else { + ecrButton.enable(true) + sensorFieldOfRegardButton.enable(true) + sensorFieldOfViewButton.enable(true) + geoButton.enable(true) + cameraViewMenu.enable(true) + viewer.showVisual(viewer.geoBeltVisualizer, geoButton.checked) + viewer.showVisual(viewer.sensorForVisualizers, sensorFieldOfRegardButton.checked) + viewer.showVisual(viewer.sensorFovVisualizers, sensorFieldOfViewButton.checked) + } + + }) + + const lastUniverseUpdate = new JulianDate(); + let lastPickedUpdate = undefined + + /** + * Check if the universe needs to be updated. + */ + function isDirty(time, picked) { + + let dirty = false; + if(Math.abs(JulianDate.secondsDifference(lastUniverseUpdate, time)) != 0) { + JulianDate.clone(time, lastUniverseUpdate); + dirty = true; + } + + if(picked !== lastPickedUpdate) { + lastPickedUpdate = picked; + dirty = true; + } + + return dirty; + } + + /** + * Pre update listener which updates the universe. + */ + viewer.scene.preUpdate.addEventListener((scene, time) => { + + if(!isDirty(time, viewer.pickedObject)) { + return; + } + + universe.update(time) + }); + + + /////////////////// + // Viewer Mixins + /////////////////// + + /** + * Object pick listener. This should not be called directly. + * + * @param {Element} picked - The picked element. + * @param {Element} lastPicked - The last picked element. + */ + viewer.objectPickListener = function (picked, lastPicked) { + if (defined(lastPicked) && defined(lastPicked.visualizer) && defined(lastPicked.visualizer._path)) + lastPicked.visualizer._path.show = false + + if (defined(picked) && defined(picked.visualizer) && defined(picked.visualizer._path)) { + picked.visualizer._path.show = true + } + + } + + /** + * Add a sensor visualizer to the viewer. + * + * @param {Site} site - The site. + * @param {Gimbal} gimbal - The gimbal. + * @param {ElectroOpicalSensor} sensor - The sensor. + */ + viewer.addSensorVisualizer = function (site, gimbal, sensor) { + const forViz = new SensorFieldOfRegardVisualizer(viewer, site, sensor, universe) + forViz.show = false + viewer.sensorForVisualizers.push(forViz) + const fovViz = new SensorFieldOfViewVisualizer(viewer, site, gimbal, sensor, universe) + viewer.sensorFovVisualizers.push(fovViz) + + toolbar.addToolbarMenu([ + { + text: "Sensor View @ " + sensor.name, + onselect: function () { + viewer.setCameraMode("sensor", sensor) + }, + }, + { + text: "What's Up View @ " + sensor.name, + onselect: function () { + viewer.setCameraMode("up", sensor) + }, + }, + ], cameraViewMenu) + + // Grid for what's up view + viewer.sensorGrids.push(viewer.entities.add({ + name: sensor.name + ' grid ', + position: createObjectPositionProperty(site, universe, viewer), + orientation: createObjectOrientationProperty(site, universe), + ellipsoid: { + radii: new Cartesian3(1000000, 1000000, 1000000), + material: Color.WHITE.withAlpha(0.0), + outlineColor: Color.WHEAT.withAlpha(0.25), + outline: true, + slicePartitions: 36, + stackPartitions: 18 + }, + show: false, + allowPicking: false + })) + }; + + /** + * Add a site visualizer to the viewer. + * @param {SimObject} site + * @param {string} description + * @param {Entity} options + */ + viewer.addSiteVisualizer = function (site, description, options) { + const base = { + name: site.name, + description: description, + position: createObjectPositionProperty(site, universe, viewer), + orientation: createObjectOrientationProperty(site, universe), + simObjectRef: site + } + + const entity = viewer.entities.add(Object.assign(base, options)) + site.visualizer = entity + } + + /** + * Add an observatory visualizer to the viewer. + * @param {Observatory} observatory + * @param {string} description + */ + viewer.addObservatoryVisualizer = function (observatory, description) { + viewer.addSiteVisualizer(observatory.site, description, { + billboard: { + image: viewer.BILLBOARD_GROUNDSTATION, + disableDepthTestDistance: 2000000, + show: true + }, + simObjectRef: observatory + }) + viewer.addSensorVisualizer(observatory.site, observatory.gimbal, observatory.sensor) + } + + /** + * Add a satellite visualizer to the viewer. + * @param {SimObject} object + * @param {string} description + * @param {Entity} options + * @param {boolean} isStatic + */ + viewer.addObjectVisualizer = function (object, description, options, isStatic = false) { + + // clear point object to be added to point collection for 2-3x better performance + const point = options.point + options.point = undefined + + // create base entity which uses the traditional object position callback + const base = { + name: object.name, + description: description, + position: isStatic ? object.position : createObjectPositionProperty(object, universe, viewer), + simObjectRef: object, + allowPicking: true + } + const entity = viewer.entities.add(Object.assign(base, options)) + + // create new point primitive + if(defined(point)) { + entity.point2 = viewer.points.add(point) + entity.point2.id = entity // required for picking + entity.update = function(time, universe) { + entity.point2.position = getObjectPositionInCesiumFrame(viewer, universe, object, time) + } + object.visualizer = entity + object.updateListeners.push(entity); + } + } + + /** + * Set the camera mode. + * + * @param {string} mode - The camera mode. "world", "sensor", "up". + * @param {ElectroOpicalSensor} sensor - The sensor. + */ + viewer.setCameraMode = function (mode, sensor) { + + // save camera position + if (viewer.cameraMode === "world") { + originalCameraState = getCameraState(); + } + + if (mode === "sensor" || mode === "up") { + viewer.selectedEntity = undefined; + viewer.trackedEntity = undefined; + + controller.update = function () { }; + controller.enableTranslate = false; + controller.enableZoom = false; + controller.enableRotate = false; + controller.enableTilt = false; + controller.enableLook = false; + viewer.trackedSensor = sensor; + + } else if (mode === "world") { + setCameraState(originalCameraState); + camera.flyHome(); + } else { + console.log('unknown camera mode: ' + mode); + return; + } + + viewer.sensorGrids.forEach(function (v) { + v.show = mode === "up"; + }); + + viewer.sensorForVisualizers.forEach(function (v) { + v.outline = mode === "world"; + }); + + viewer.sensorFovVisualizers.forEach(function (v) { + v.outline = mode === "world"; + }); + + viewer.cameraMode = mode; + }; + + /** + * Shows or hides the visualizer(s). + * + * @param {CompoundElementVisualizer} visualizer - The visualizer or array of visualizers. + * @param {boolean} show - True to show, false to hide. + */ + viewer.showVisual = function (visualizer, show) { + + if (defined(visualizer.length)) { + visualizer.forEach(v => { + v.show = show; + }); + } else { + visualizer.show = show; + } + }; + + + + + /////////////////// + // UI Widgets + /////////////////// + + // Improved Infobox + if (options.infoBox2) { + const infoBoxContainer = document.createElement("div"); + infoBoxContainer.className = "cesium-viewer-infoBoxContainer"; + options.infoBox2Container.appendChild(infoBoxContainer); + const infoBox = new InfoBox(infoBoxContainer); + + function trackCallback(infoBoxViewModel) { + viewer.referenceFrameView = ReferenceFrame.FIXED; + if ( + infoBoxViewModel.isCameraTracking && + viewer.trackedEntity === viewer.selectedEntity + ) { + viewer.trackedEntity = undefined; + } else { + const selectedEntity = viewer.selectedEntity; + const position = selectedEntity.position; + if (defined(position)) { + viewer.trackedEntity = viewer.selectedEntity; + } else { + viewer.zoomTo(viewer.selectedEntity); + } + } + } + + function closeInfoBox(infoBoxViewModel) { + viewer.selectedEntity = undefined; + }; + + const infoBoxViewModel = infoBox.viewModel; + viewer._eventHelper.add( + infoBoxViewModel.cameraClicked, + trackCallback, + this + ); + viewer._eventHelper.add( + infoBoxViewModel.closeClicked, + closeInfoBox, + this + ); + viewer._infoBox = infoBox; + } + + // Toolbar + const toolbarContainer = document.createElement('div'); + toolbarContainer.classname = "cesium-viewer-actionbar"; + toolbarContainer.style.position = 'absolute'; + toolbarContainer.style.top = '10px'; + toolbarContainer.style.left = '10px'; + if (options.toolbar2) { + options.toolbar2Container.appendChild(toolbarContainer); + } + const toolbar = new Toolbar(toolbarContainer); + viewer.toolbar = toolbar; + const ecrButton = toolbar.addToggleButton('ECR', true, (checked) => { + if (!checked) { + viewer.referenceFrameView = ReferenceFrame.INERTIAL; + } else { + viewer.referenceFrameView = ReferenceFrame.FIXED; + } + }); + const sensorFieldOfRegardButton = toolbar.addToggleButton('FoR', false, (checked) => { + viewer.showVisual(viewer.sensorForVisualizers, checked); + }); + const sensorFieldOfViewButton = toolbar.addToggleButton('FoV', true, (checked) => { + viewer.showVisual(viewer.sensorFovVisualizers, checked); + }); + const geoButton = toolbar.addToggleButton('GEO', true, (checked) => { + viewer.showVisual(viewer.geoBeltVisualizer, checked); + }); + const satButton = toolbar.addToggleButton('Satellites', true, (checked) => { + viewer.points.show = checked; + }); + toolbar.addSeparator(); + const cameraViewMenu = toolbar.addToolbarMenu([ + { + text: "World View", + onselect: function () { + viewer.setCameraMode("world"); + } + } + ]); + const coverageMenu = toolbar.addToolbarMenu([ + { + text: "No Coverage Map", + onselect: function () { + viewer.coverageVisualizer.show = false; + }, + }, + { + text: "LEO Coverage Map", + onselect: function () { + viewer.coverageVisualizer.orbit = 'LEO'; + viewer.coverageVisualizer.show = true; + viewer.coverageVisualizer.update(viewer.clock.currentTime) + }, + }, + { + text: "MEO Coverage Map", + onselect: function () { + viewer.coverageVisualizer.orbit = 'MEO'; + viewer.coverageVisualizer.show = true; + viewer.coverageVisualizer.update(viewer.clock.currentTime) + }, + }, + { + text: "GEO Coverage Map", + onselect: function () { + viewer.coverageVisualizer.orbit = 'GEO'; + viewer.coverageVisualizer.show = true; + viewer.coverageVisualizer.update(viewer.clock.currentTime) + }, + }, + { + text: "Lunar Coverage Map", + onselect: function () { + viewer.coverageVisualizer.orbit = 'LUNAR'; + viewer.coverageVisualizer.show = true; + viewer.coverageVisualizer.update(viewer.clock.currentTime) + }, + }, + ]); + + + /////////////////// + // Misc + /////////////////// + let originalCameraState = getCameraState(); + function getCameraState() { + return { + update: scene.screenSpaceCameraController.update, + enableTranslate: controller.enableTranslate, + enableZoom: controller.enableZoom, + enableRotate: controller.enableRotate, + enableTilt: controller.enableTilt, + enableLook: controller.enableLook, + fov: camera.frustum.fov, + direction: Cartesian3.clone(camera.direction), + right: Cartesian3.clone(camera.right), + up: Cartesian3.clone(camera.up), + position: Cartesian3.clone(camera.position), + transform: Matrix4.clone(camera.transform) + }; + } + + function setCameraState(state) { + camera.frustum.fov = state.fov; + camera.frustum = camera.frustum; + Cartesian3.clone(state.direction, camera.direction); + Cartesian3.clone(state.right, camera.right); + Cartesian3.clone(state.up, camera.up); + Cartesian3.clone(state.position, camera.position); + Matrix4.clone(state.transform, camera.transform); + controller.update = state.update; + controller.enableTranslate = state.enableTranslate; + controller.enableZoom = state.enableZoom; + controller.enableRotate = state.enableRotate; + controller.enableTilt = state.enableTilt; + controller.enableLook = state.enableLook; + } + + // Add post process stage to fix wide fov distortion + const fs = ` +uniform sampler2D colorTexture; +uniform float fov; +uniform int mode; +in vec2 v_textureCoordinates; // input coord is 0 to +1 +//const float fovTheta = 160.0 * 3.1415926535 / 180.0; // FOV's theta +const float PI = 3.1415926535; + +void main (void) +{ + if (mode == 1) { + out_FragColor = texture(colorTexture, v_textureCoordinates); + return; + } + + vec2 uv = 2.0 * v_textureCoordinates - 1.0; // between -1 and +1 + float d = length(uv); + + if (d < 0.95) { + float z = sqrt(1.0 - uv.x * uv.x - uv.y * uv.y); // sphere eq: r^2 = x^2 + y^2 + z^2 + float k = 1.0 / (z * tan(fov * 0.5)); + vec4 c = texture(colorTexture, (uv * k) + 0.5); // between 0 and +1 + out_FragColor = c; + } else { + uv = v_textureCoordinates; + vec4 c = texture(colorTexture, uv); + out_FragColor = vec4(c.rgb * 0.0, 1.0); + } + } +`; + + scene.postProcessStages.add(new PostProcessStage({ + fragmentShader : fs, + uniforms : { + fov : function() { + return defined(camera.frustum.fov) ? camera.frustum.fov : 0.0; + }, + mode : function() { + return viewer.cameraMode === "up" ? 0 : 1; + } + } + })); + + return viewer; +}; + + +export { + createViewer, + mixinViewer +} diff --git a/webpack.config.js b/webpack.config.js new file mode 100755 index 0000000..0bde6f3 --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,68 @@ +// The path to the CesiumJS source code +const cesiumSource = 'node_modules/cesium/Source'; +const cesiumWorkers = '../Build/Cesium/Workers'; +const CopyWebpackPlugin = require('copy-webpack-plugin'); +const path = require('path'); +const webpack = require('webpack'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); + +module.exports = { + context: __dirname, + entry: { + app: './app/index.js' + }, + output: { + filename: 'app.js', + path: path.resolve(__dirname, 'dist'), + sourcePrefix: '' + }, + resolve: { + fallback: { "https": false, "zlib": false, "http": false, "url": false }, + mainFiles: ['index', 'Cesium'], + // add satsim as an alias to the root directory + alias: { + "satsim": path.resolve(__dirname, ".") + } + }, + module: { + rules: [{ + test: /\.css$/, + use: [ 'style-loader', 'css-loader' ] + }, { + test: /\.(png|gif|jpg|jpeg|svg|xml|json)$/, + use: [ 'url-loader' ] + }] + }, + plugins: [ + new HtmlWebpackPlugin({ + template: './app/index.html' + }), + // Copy Cesium Assets, Widgets, and Workers to a static directory + new CopyWebpackPlugin({ + patterns: [ + { from: path.join(cesiumSource, cesiumWorkers), to: 'Workers' }, + { from: path.join(cesiumSource, 'Assets'), to: 'Assets' }, + { from: path.join(cesiumSource, 'Widgets'), to: 'Widgets' }, + { from: path.join(cesiumSource, 'ThirdParty'), to: 'ThirdParty' } + ] + }), + new webpack.DefinePlugin({ + // Define relative base path in cesium for loading assets + CESIUM_BASE_URL: JSON.stringify('') + }) + ], + mode: 'development', + devtool: 'source-map', + devServer: { + watchFiles: { + paths: ['./src/**/*.js', './src/**/*.css', './src/**/*.html', './app/**/*'], + options: { + usePolling: false, + }, + }, + static: { + directory: path.resolve(__dirname, './app/assets'), + publicPath: '/assets' + } + } +};