├── Universe ├── Data │ ├── Parse.cs.meta │ ├── ObjectBuilder.cs.meta │ ├── ObjectWriter.cs.meta │ ├── Serialize.cs.meta │ ├── StarConverter.cs.meta │ ├── ValueUnit.cs.meta │ ├── ConverterOptions.cs.meta │ ├── PlanetConverter.cs.meta │ ├── CelestialBodyConverter.cs.meta │ ├── ConverterExtensions.cs.meta │ ├── CoreObjectConverter.cs.meta │ ├── OrbitalDataConverter.cs.meta │ ├── PhysicalDataConverter.cs.meta │ ├── StellarDataConverter.cs.meta │ ├── ValueUnit.cs │ ├── CelestialBodyConverter.cs │ ├── VindemiatrixCollective.Universe.Data.asmdef.meta │ ├── GalaxyConverter.cs.meta │ ├── StarSystemConverter.cs.meta │ ├── ConverterOptions.cs │ ├── VindemiatrixCollective.Universe.Data.asmdef │ ├── ConverterExtensions.cs │ ├── StarSystemConverter.cs │ ├── GalaxyConverter.cs │ ├── Serialize.cs │ ├── PlanetConverter.cs │ ├── ObjectWriter.cs │ ├── PhysicalDataConverter.cs │ ├── StarConverter.cs │ ├── StellarDataConverter.cs │ ├── OrbitalDataConverter.cs │ ├── CoreObjectConverter.cs │ ├── ObjectBuilder.cs │ └── Parse.cs ├── Extensions.cs.meta ├── ITreeNode.cs.meta ├── Tests │ ├── Common.cs.meta │ ├── AngleTests.cs.meta │ ├── GalaxyTests.cs.meta │ ├── LambertTests.cs.meta │ ├── CoordinatesTests.cs.meta │ ├── ManoeuvreTests.cs.meta │ ├── OrbitStateTests.cs.meta │ ├── OrbitalDataTests.cs.meta │ ├── PhysicalDataTests.cs.meta │ ├── StarSystemTests.cs.meta │ ├── TextbookMethods.cs.meta │ ├── CelestialBodyTests.cs.meta │ ├── DeserializationHelper.cs.meta │ ├── DeserializationTests.cs.meta │ ├── RuntimeSystemTests.cs.meta │ ├── TransferPlannerTest.cs.meta │ ├── RelativisticRocketTests.cs.meta │ ├── GalaxyTests.cs │ ├── LambertTests.cs │ ├── StarSystemTests.cs │ ├── TextbookMethods.cs │ ├── CelestialBodyTests.cs │ ├── OrbitalDataTests.cs │ ├── TransferPlannerTest.cs │ ├── RelativisticRocketTests.cs │ ├── VindemiatrixCollective.Universe.Tests.asmdef.meta │ ├── VindemiatrixCollective.Universe.Tests.asmdef │ ├── RuntimeSystemTests.cs │ ├── DeserializationTests.cs │ ├── CoordinatesTests.cs │ ├── ManoeuvreTests.cs │ ├── DeserializationHelper.cs │ ├── AngleTests.cs │ ├── Common.cs │ ├── PhysicalDataTests.cs │ └── OrbitStateTests.cs ├── ICelestialBody.cs.meta ├── Model │ ├── Attributes.cs.meta │ ├── Barycentre.cs.meta │ ├── PhysicalData.cs.meta │ ├── StarSystem.cs.meta │ ├── StellarData.cs.meta │ ├── GeoCoordinates.cs.meta │ ├── Barycentre.cs │ ├── Star.asset.meta │ ├── Enums.cs.meta │ ├── Galaxy.cs.meta │ ├── Planet.cs.meta │ ├── Star.cs.meta │ ├── CelestialBody.cs.meta │ ├── LuminosityClass.cs.meta │ ├── SpectralClass.cs.meta │ ├── Enums.cs │ ├── Star.asset │ ├── LuminosityClass.cs │ ├── StellarData.cs │ ├── PhysicalData.cs │ ├── Attributes.cs │ ├── GeoCoordinates.cs │ ├── Galaxy.cs │ ├── Star.cs │ ├── StarSystem.cs │ ├── Planet.cs │ ├── SpectralClass.cs │ └── CelestialBody.cs ├── Rocketry │ ├── Rocket.cs.meta │ └── Rocket.cs ├── UniversalConstants.cs.meta ├── Extensions.cs ├── CelestialMechanics │ ├── Ellipse.cs.meta │ ├── Mathd.cs.meta │ ├── Quaterniond.cs.meta │ ├── Relativity.cs.meta │ ├── Orbits │ │ ├── IAttractor.cs.meta │ │ ├── OrbitState.cs.meta │ │ ├── SkyMapper.cs.meta │ │ ├── OrbitalMechanics.cs.meta │ │ ├── Propagation │ │ │ ├── Farnocchia.cs.meta │ │ │ ├── Kinematic.cs.meta │ │ │ ├── IPropagator.cs.meta │ │ │ ├── IPropagator.cs │ │ │ ├── Kinematic.cs │ │ │ └── Farnocchia.cs │ │ ├── IAttractor.cs │ │ ├── SkyMapper.cs │ │ ├── Propagation.meta │ │ ├── OrbitalData.cs.meta │ │ └── OrbitalData.cs │ ├── GravitationalParameter.cs.meta │ ├── Manoeuvres │ │ ├── Manoeuvre.cs.meta │ │ ├── TransferData.cs.meta │ │ ├── ILambertSolver.cs.meta │ │ ├── IzzoLambertSolver.cs.meta │ │ ├── TransferPlanner.cs.meta │ │ ├── Manoeuvre.cs │ │ ├── TransferData.cs │ │ ├── TransferPlanner.cs │ │ └── ILambertSolver.cs │ ├── Quaterniond.cs │ ├── Orbit.meta │ ├── Orbits.meta │ ├── Manoeuvres.meta │ ├── Distance.cs.meta │ ├── Vector3d.cs.meta │ ├── Extensions.cs.meta │ ├── Extensions.cs │ ├── Distance.cs │ ├── GravitationalParameter.cs │ ├── Ellipse.cs │ └── Relativity.cs ├── ICelestialBody.cs ├── package.json.meta ├── Data.meta ├── Model.meta ├── Tests.meta ├── Rocketry.meta ├── CelestialMechanics.meta ├── VindemiatrixCollective.Universe.asmdef.meta ├── package.json ├── VindemiatrixCollective.Universe.asmdef ├── UniversalConstants.cs └── ITreeNode.cs ├── Documentation └── Media │ └── SineFine_Transfer.png ├── .github └── workflows │ ├── stale.yml │ └── package.yml ├── LICENSE ├── THIRD-PARTY-LICENSES.txt └── README.md /Universe/Data/Parse.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8d7d86b91ee640445a5824d940cd7b05 -------------------------------------------------------------------------------- /Universe/Extensions.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0efb55da3b1fd5e4ab8f5ee7839711d9 -------------------------------------------------------------------------------- /Universe/ITreeNode.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8bf619794566fee468358b4fdfc02f86 -------------------------------------------------------------------------------- /Universe/Tests/Common.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2733a6d420db869468bd1a961ac4ebdc -------------------------------------------------------------------------------- /Universe/Data/ObjectBuilder.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 44fec2d3dda5caf4dae957773ef2de07 -------------------------------------------------------------------------------- /Universe/Data/ObjectWriter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 92dcdca49f0c42b4f82439af25278270 -------------------------------------------------------------------------------- /Universe/Data/Serialize.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2133d0edb0260804f939452f73a46f72 -------------------------------------------------------------------------------- /Universe/Data/StarConverter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 613770d8ec9fe5447939c9a7b9470ce4 -------------------------------------------------------------------------------- /Universe/Data/ValueUnit.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7b8623fe4b0310744ae7608994965069 -------------------------------------------------------------------------------- /Universe/ICelestialBody.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1a96e663d030d524096ca1f4f89dc6aa -------------------------------------------------------------------------------- /Universe/Model/Attributes.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7b79c04cb9f654840a9b94362c278b99 -------------------------------------------------------------------------------- /Universe/Model/Barycentre.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8919bb21d2310274fb4ecec46116c61a -------------------------------------------------------------------------------- /Universe/Model/PhysicalData.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: df384fa7047514748a6785568f651802 -------------------------------------------------------------------------------- /Universe/Model/StarSystem.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 82225ba1c84110f458f2db78a1586d76 -------------------------------------------------------------------------------- /Universe/Model/StellarData.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0ad35e4f7864ff146af0047f63e38874 -------------------------------------------------------------------------------- /Universe/Rocketry/Rocket.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 96c9132915da113489b3248f0c554db2 -------------------------------------------------------------------------------- /Universe/Tests/AngleTests.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: acb901f563b522c4db933b589f003459 -------------------------------------------------------------------------------- /Universe/Tests/GalaxyTests.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4040f44c5de56df4093104eb7f737f72 -------------------------------------------------------------------------------- /Universe/Tests/LambertTests.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f08cda93ed1d0844cb931566d8d1635a -------------------------------------------------------------------------------- /Universe/UniversalConstants.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 77c5803c3bebc554a8279f6a85eb96ea -------------------------------------------------------------------------------- /Universe/Data/ConverterOptions.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 89511f88f503c2d4cbf820a76219b277 -------------------------------------------------------------------------------- /Universe/Data/PlanetConverter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 08cb5ead0b2521d42804e5f80e7944d4 -------------------------------------------------------------------------------- /Universe/Extensions.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheWand3rer/Universe/HEAD/Universe/Extensions.cs -------------------------------------------------------------------------------- /Universe/Model/GeoCoordinates.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4854694e524a2ee4fa4f2b37e06e3bb1 -------------------------------------------------------------------------------- /Universe/Tests/CoordinatesTests.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b09ba8ebabcd6ba4387e765b2a1d7a14 -------------------------------------------------------------------------------- /Universe/Tests/ManoeuvreTests.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 76651701d25160a4691c1c07919837da -------------------------------------------------------------------------------- /Universe/Tests/OrbitStateTests.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 241e338d9325a384ab21cfbe339ffa16 -------------------------------------------------------------------------------- /Universe/Tests/OrbitalDataTests.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: feb059ee1e8d37541bd925a387561456 -------------------------------------------------------------------------------- /Universe/Tests/PhysicalDataTests.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: fccb77fed80bcb44cb149c822d6872ba -------------------------------------------------------------------------------- /Universe/Tests/StarSystemTests.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2f6aa6ed76ead324a9f48076ebd04eec -------------------------------------------------------------------------------- /Universe/Tests/TextbookMethods.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 58a5218e1bcae234a87174baa09e6df0 -------------------------------------------------------------------------------- /Universe/CelestialMechanics/Ellipse.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0089311dc66ee1948ad16c5bd65ea4fe -------------------------------------------------------------------------------- /Universe/CelestialMechanics/Mathd.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e0849ead523aaa248babc81c2668ebbe -------------------------------------------------------------------------------- /Universe/Data/CelestialBodyConverter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8cab912f500ce5c4790d6caa4ca34d47 -------------------------------------------------------------------------------- /Universe/Data/ConverterExtensions.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 110a5045ad15b9344b9f7abe5fd5521a -------------------------------------------------------------------------------- /Universe/Data/CoreObjectConverter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c8d300ad88428544db36f716bb27e66f -------------------------------------------------------------------------------- /Universe/Data/OrbitalDataConverter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e6036e7b64e54b94494c5987d30ad3c6 -------------------------------------------------------------------------------- /Universe/Data/PhysicalDataConverter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4cc1be207848dc743b6c14c8ed903698 -------------------------------------------------------------------------------- /Universe/Data/StellarDataConverter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9939d64160a20c54d820968485d922c6 -------------------------------------------------------------------------------- /Universe/Data/ValueUnit.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheWand3rer/Universe/HEAD/Universe/Data/ValueUnit.cs -------------------------------------------------------------------------------- /Universe/ICelestialBody.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheWand3rer/Universe/HEAD/Universe/ICelestialBody.cs -------------------------------------------------------------------------------- /Universe/Tests/CelestialBodyTests.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a4e11eb1543293f409c12b18bef77cb0 -------------------------------------------------------------------------------- /Universe/Tests/DeserializationHelper.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f340ce8eaad5cbc49808bcc74f7af63e -------------------------------------------------------------------------------- /Universe/Tests/DeserializationTests.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1eb36aee952b38d47888802692520c5a -------------------------------------------------------------------------------- /Universe/Tests/RuntimeSystemTests.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5ebbba0f82a5a3f4e9eaa9aa1fe64eed -------------------------------------------------------------------------------- /Universe/Tests/TransferPlannerTest.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d32060296910271448c01603232bb20c -------------------------------------------------------------------------------- /Universe/CelestialMechanics/Quaterniond.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4df90ea08a0806242ba4df2933d93749 -------------------------------------------------------------------------------- /Universe/CelestialMechanics/Relativity.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 19a8bb4cc1f367f429e1789b818dc3d8 -------------------------------------------------------------------------------- /Universe/Model/Barycentre.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheWand3rer/Universe/HEAD/Universe/Model/Barycentre.cs -------------------------------------------------------------------------------- /Universe/Tests/RelativisticRocketTests.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 01b5567591729154294b4ec9d62b87c9 -------------------------------------------------------------------------------- /Universe/CelestialMechanics/Orbits/IAttractor.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8bfc337782e679a45a8debe98726de15 -------------------------------------------------------------------------------- /Universe/CelestialMechanics/Orbits/OrbitState.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 46a38d4ef9e033a4e81082bcc8bb91d9 -------------------------------------------------------------------------------- /Universe/CelestialMechanics/Orbits/SkyMapper.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 938e212489df7fd49b9ed912f3ccda96 -------------------------------------------------------------------------------- /Universe/Tests/GalaxyTests.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheWand3rer/Universe/HEAD/Universe/Tests/GalaxyTests.cs -------------------------------------------------------------------------------- /Universe/Tests/LambertTests.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheWand3rer/Universe/HEAD/Universe/Tests/LambertTests.cs -------------------------------------------------------------------------------- /Universe/CelestialMechanics/GravitationalParameter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 54a4abf75671725408f9275ee115d945 -------------------------------------------------------------------------------- /Universe/CelestialMechanics/Manoeuvres/Manoeuvre.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2cc7ec1ce937f384f98200110fa6d439 -------------------------------------------------------------------------------- /Universe/CelestialMechanics/Manoeuvres/TransferData.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 28e99e28eccac7d459935f30f5af83bc -------------------------------------------------------------------------------- /Universe/CelestialMechanics/Orbits/OrbitalMechanics.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 21bc6f50adb003442bd93de6f8fede59 -------------------------------------------------------------------------------- /Universe/Tests/StarSystemTests.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheWand3rer/Universe/HEAD/Universe/Tests/StarSystemTests.cs -------------------------------------------------------------------------------- /Universe/Tests/TextbookMethods.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheWand3rer/Universe/HEAD/Universe/Tests/TextbookMethods.cs -------------------------------------------------------------------------------- /Universe/CelestialMechanics/Manoeuvres/ILambertSolver.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2f22d09eaad16d647a6a8d8c3a10d999 -------------------------------------------------------------------------------- /Universe/CelestialMechanics/Manoeuvres/IzzoLambertSolver.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: dabf5659f4eba934aaf852a7ec7ffec0 -------------------------------------------------------------------------------- /Universe/CelestialMechanics/Manoeuvres/TransferPlanner.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 420d6861b9628c64eba610b14c007b50 -------------------------------------------------------------------------------- /Universe/CelestialMechanics/Orbits/Propagation/Farnocchia.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f70032d7ce9df2b41a927ad70ddeb9f8 -------------------------------------------------------------------------------- /Universe/CelestialMechanics/Orbits/Propagation/Kinematic.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f105114139f35bd4aa39b59e26f9bb41 -------------------------------------------------------------------------------- /Universe/Tests/CelestialBodyTests.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheWand3rer/Universe/HEAD/Universe/Tests/CelestialBodyTests.cs -------------------------------------------------------------------------------- /Universe/Tests/OrbitalDataTests.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheWand3rer/Universe/HEAD/Universe/Tests/OrbitalDataTests.cs -------------------------------------------------------------------------------- /Universe/CelestialMechanics/Orbits/Propagation/IPropagator.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9d7406ca2ece75b4fa8d5ba68faade56 -------------------------------------------------------------------------------- /Universe/Tests/TransferPlannerTest.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheWand3rer/Universe/HEAD/Universe/Tests/TransferPlannerTest.cs -------------------------------------------------------------------------------- /Documentation/Media/SineFine_Transfer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheWand3rer/Universe/HEAD/Documentation/Media/SineFine_Transfer.png -------------------------------------------------------------------------------- /Universe/Data/CelestialBodyConverter.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheWand3rer/Universe/HEAD/Universe/Data/CelestialBodyConverter.cs -------------------------------------------------------------------------------- /Universe/Tests/RelativisticRocketTests.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheWand3rer/Universe/HEAD/Universe/Tests/RelativisticRocketTests.cs -------------------------------------------------------------------------------- /Universe/CelestialMechanics/Quaterniond.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheWand3rer/Universe/HEAD/Universe/CelestialMechanics/Quaterniond.cs -------------------------------------------------------------------------------- /Universe/CelestialMechanics/Orbits/IAttractor.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheWand3rer/Universe/HEAD/Universe/CelestialMechanics/Orbits/IAttractor.cs -------------------------------------------------------------------------------- /Universe/CelestialMechanics/Orbits/SkyMapper.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheWand3rer/Universe/HEAD/Universe/CelestialMechanics/Orbits/SkyMapper.cs -------------------------------------------------------------------------------- /Universe/CelestialMechanics/Manoeuvres/Manoeuvre.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheWand3rer/Universe/HEAD/Universe/CelestialMechanics/Manoeuvres/Manoeuvre.cs -------------------------------------------------------------------------------- /Universe/CelestialMechanics/Manoeuvres/TransferData.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheWand3rer/Universe/HEAD/Universe/CelestialMechanics/Manoeuvres/TransferData.cs -------------------------------------------------------------------------------- /Universe/CelestialMechanics/Manoeuvres/TransferPlanner.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheWand3rer/Universe/HEAD/Universe/CelestialMechanics/Manoeuvres/TransferPlanner.cs -------------------------------------------------------------------------------- /Universe/package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 22fdea1a793e39d458ed1917a62dd3c5 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Universe/Data.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b7be7f07ce7472640a9035007775118b 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Universe/Model.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 50073fb0341de1045937bd7e51bda44a 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Universe/Tests.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: efc16fcc4f7ec444b99c1d4eeb9d3335 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Universe/Rocketry.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 27589f076d37e7d478d12aff7d8e6242 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Universe/CelestialMechanics.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ea4b623f0c98f4941ba4227b20d52baa 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Universe/CelestialMechanics/Orbit.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 51c51dc968664614ba9ff033718818db 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Universe/CelestialMechanics/Orbits.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b67cd8c7ce8ddf448bb7bccaf2de7bd6 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Universe/CelestialMechanics/Manoeuvres.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6dda17fc00cdcf24e8d65e2a4e5aaf56 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Universe/Model/Star.asset.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 38c657adcd2bdfe4eb11ccff5259a036 3 | NativeFormatImporter: 4 | externalObjects: {} 5 | mainObjectFileID: 0 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Universe/VindemiatrixCollective.Universe.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7173e24e3974c4641a794a3e6509cc53 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Universe/CelestialMechanics/Orbits/Propagation.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: cf6c9fc4bae47f04faf36b3bcfa4c5a8 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Universe/Data/VindemiatrixCollective.Universe.Data.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 52e5845a56e038147a8467a448da3af2 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Universe/Tests/VindemiatrixCollective.Universe.Tests.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 436965913936b814b9eb1ad6367e51ec 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Universe/Model/Enums.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e67434f6b10965d4fb3c40f34ceee65b 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Universe/Model/Galaxy.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f49a383d48ccb09488fc885a3867460c 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Universe/Model/Planet.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 53e491d058c130949b7727c156ff7f3d 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Universe/Model/Star.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 617540c8dfde5e540b99a777f2b51f41 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Universe/Data/GalaxyConverter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7466f1ae035e40b458c35b82a0f0ad0f 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Universe/Model/CelestialBody.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 722142aed2c44db45bddb3868fb30089 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Universe/Model/LuminosityClass.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e8842ff988107614486d054f618880aa 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Universe/Model/SpectralClass.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8e2c08dc51384a340855a842d355d74e 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Universe/CelestialMechanics/Distance.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: eb7afffa1ef4a51488c14db44bb73584 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Universe/CelestialMechanics/Vector3d.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e22fb7ae529ad364390c454b6b1d8006 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Universe/Data/StarSystemConverter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 655f08e8b923aa14e87ab868b8753162 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Universe/CelestialMechanics/Extensions.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f26ae0bd84c8dea4f8b0c2ebfad7ce56 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Universe/CelestialMechanics/Orbits/OrbitalData.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 563fa5bea05b5904fbb68c6ce7158717 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Universe/Model/Enums.cs: -------------------------------------------------------------------------------- 1 | // VindemiatrixCollective.Universe © 2025 Vindemiatrix Collective 2 | // Website and Documentation: https://vindemiatrixcollective.com 3 | 4 | namespace VindemiatrixCollective.Universe.Model 5 | { 6 | public enum CelestialBodyType 7 | { 8 | Unknown, 9 | Planet, 10 | Star, 11 | StarSystem 12 | } 13 | } -------------------------------------------------------------------------------- /Universe/Data/ConverterOptions.cs: -------------------------------------------------------------------------------- 1 | // VindemiatrixCollective.Universe.Data © 2025 Vindemiatrix Collective 2 | // Website and Documentation: https://vindemiatrixcollective.com 3 | 4 | #if UNITY_EDITOR 5 | namespace VindemiatrixCollective.Universe.Data 6 | { 7 | internal static class ConverterOptions 8 | { 9 | internal static bool VerboseLog = false; 10 | } 11 | } 12 | #endif -------------------------------------------------------------------------------- /Universe/CelestialMechanics/Orbits/Propagation/IPropagator.cs: -------------------------------------------------------------------------------- 1 | // VindemiatrixCollective.Universe © 2025 Vindemiatrix Collective 2 | // Website and Documentation: https://vindemiatrixcollective.com 3 | 4 | #region 5 | 6 | using UnitsNet; 7 | 8 | #endregion 9 | 10 | namespace VindemiatrixCollective.Universe.CelestialMechanics.Orbits.Propagation 11 | { 12 | public interface IPropagator 13 | { 14 | (Angle nu, Angle E, Angle M) PropagateOrbit(OrbitState state, Duration tof); 15 | } 16 | } -------------------------------------------------------------------------------- /Universe/CelestialMechanics/Extensions.cs: -------------------------------------------------------------------------------- 1 | // VindemiatrixCollective.Universe © 2025 Vindemiatrix Collective 2 | // Website and Documentation: https://vindemiatrixcollective.com 3 | 4 | #region 5 | 6 | using System; 7 | 8 | #endregion 9 | 10 | namespace VindemiatrixCollective.Universe.CelestialMechanics 11 | { 12 | public static class Extensions 13 | { 14 | public static double ToDegrees(this double d) => d * (180 / Math.PI); 15 | 16 | public static double ToRadians(this double d) => d * (Math.PI / 180); 17 | } 18 | } -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Close stale issues 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' # runs daily at midnight 6 | 7 | jobs: 8 | stale: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/stale@v9 12 | with: 13 | days-before-stale: 30 14 | days-before-close: 7 15 | stale-issue-message: 'This issue has been automatically marked as stale due to inactivity.' 16 | close-issue-message: 'Closing this issue due to prolonged inactivity.' 17 | exempt-issue-labels: 'keep-open' 18 | -------------------------------------------------------------------------------- /.github/workflows/package.yml: -------------------------------------------------------------------------------- 1 | name: UnityPackage 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*.*.*' 7 | workflow_dispatch: 8 | 9 | jobs: 10 | echo: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - run: | 15 | find ./ -name \*.meta >> metaList 16 | 17 | - run: mkdir a 18 | 19 | - uses: pCYSl5EDgo/create-unitypackage@master 20 | with: 21 | package-path: 'a/Universe.${{ github.ref }}.unitypackage' 22 | include-files: metaList 23 | - uses: actions/upload-artifact@master 24 | with: 25 | path: a 26 | name: package 27 | -------------------------------------------------------------------------------- /Universe/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.vindemiatrixcollective.universe", 3 | "version": "1.2.0", 4 | "displayName": "Universe", 5 | "description": "A Unity library of space-related methods for hard sci-fi games.", 6 | "unity": "2023.1", 7 | "author": { 8 | "name": "TheWand3rer", 9 | "url": "https://github.com/TheWand3rer" 10 | }, 11 | "keywords": ["space", "astronomy", "orbit", "orbital", "mechanics"], 12 | "license": "MIT", 13 | "dependencies": { 14 | "com.unity.mathematics": "1.3.2", 15 | "org.nuget.system.text.json": "9.0.7", 16 | "org.nuget.unitsnet": "5.74.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Universe/VindemiatrixCollective.Universe.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "VindemiatrixCollective.Universe", 3 | "rootNamespace": "VindemiatrixCollective.Universe", 4 | "references": [ 5 | "GUID:d8b63aba1907145bea998dd612889d6b" 6 | ], 7 | "includePlatforms": [ 8 | "Editor", 9 | "LinuxStandalone64", 10 | "WindowsStandalone64" 11 | ], 12 | "excludePlatforms": [], 13 | "allowUnsafeCode": false, 14 | "overrideReferences": true, 15 | "precompiledReferences": [ 16 | "UnitsNet.dll" 17 | ], 18 | "autoReferenced": false, 19 | "defineConstraints": [], 20 | "versionDefines": [], 21 | "noEngineReferences": false 22 | } -------------------------------------------------------------------------------- /Universe/Model/Star.asset: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!114 &11400000 4 | MonoBehaviour: 5 | m_ObjectHideFlags: 0 6 | m_CorrespondingSourceObject: {fileID: 0} 7 | m_PrefabInstance: {fileID: 0} 8 | m_PrefabAsset: {fileID: 0} 9 | m_GameObject: {fileID: 0} 10 | m_Enabled: 1 11 | m_EditorHideFlags: 0 12 | m_Script: {fileID: 11500000, guid: 617540c8dfde5e540b99a777f2b51f41, type: 3} 13 | m_Name: Star 14 | m_EditorClassIdentifier: 15 | Mesh: {fileID: 0} 16 | Material: {fileID: 0} 17 | Scale: 0 18 | Name: 19 | Mass: 0 20 | Luminosity: 0 21 | Age: 0 22 | Temperature: 0 23 | SpectralClassSignature: 24 | Coordinates: {x: 0, y: 0, z: 0} 25 | Planets: [] 26 | -------------------------------------------------------------------------------- /Universe/Data/VindemiatrixCollective.Universe.Data.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "VindemiatrixCollective.Universe.Data", 3 | "rootNamespace": "VindemiatrixCollective.Universe.Data", 4 | "references": [ 5 | "GUID:7173e24e3974c4641a794a3e6509cc53" 6 | ], 7 | "includePlatforms": [ 8 | "Editor", 9 | "LinuxStandalone64", 10 | "WindowsStandalone64" 11 | ], 12 | "excludePlatforms": [], 13 | "allowUnsafeCode": false, 14 | "overrideReferences": true, 15 | "precompiledReferences": [ 16 | "UnitsNet.dll", 17 | "System.Text.Json.dll" 18 | ], 19 | "autoReferenced": false, 20 | "defineConstraints": [], 21 | "versionDefines": [], 22 | "noEngineReferences": false 23 | } -------------------------------------------------------------------------------- /Universe/Data/ConverterExtensions.cs: -------------------------------------------------------------------------------- 1 | // VindemiatrixCollective.Universe.Data © 2025 Vindemiatrix Collective 2 | // Website and Documentation: https://vindemiatrixcollective.com 3 | 4 | #region 5 | 6 | using System.Globalization; 7 | using UnityEngine; 8 | 9 | #endregion 10 | 11 | namespace VindemiatrixCollective.Universe.Data 12 | { 13 | public static class ConverterExtensions 14 | { 15 | public static Vector3 StringToVector3(string input, char delimiter = ',') 16 | { 17 | string[] array = input.Split(delimiter); 18 | return new Vector3(float.Parse(array[0], CultureInfo.InvariantCulture), float.Parse(array[1], CultureInfo.InvariantCulture), 19 | float.Parse(array[2], CultureInfo.InvariantCulture)); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /Universe/CelestialMechanics/Distance.cs: -------------------------------------------------------------------------------- 1 | // VindemiatrixCollective.Universe © 2025 Vindemiatrix Collective 2 | // Website and Documentation: https://vindemiatrixcollective.com 3 | 4 | #region 5 | 6 | using UnitsNet; 7 | using Unity.Mathematics; 8 | 9 | #endregion 10 | 11 | namespace VindemiatrixCollective.Universe.CelestialMechanics 12 | { 13 | public static class Distance 14 | { 15 | public static Angle AngularSize(Length radius, Length distance) 16 | { 17 | double delta = 2 * math.atan(radius / distance); 18 | return Angle.FromRadians(delta); 19 | } 20 | 21 | public static float InGameScaleFactor(Angle angularSize, float sceneDistance) 22 | { 23 | return sceneDistance * math.tan((float)angularSize.Radians / 2f); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /Universe/Tests/VindemiatrixCollective.Universe.Tests.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "VindemiatrixCollective.Universe.Tests", 3 | "rootNamespace": "VindemiatrixCollective.Universe.Tests", 4 | "references": [ 5 | "UnityEngine.TestRunner", 6 | "UnityEditor.TestRunner", 7 | "VindemiatrixCollective.Universe", 8 | "VindemiatrixCollective.Universe.Data" 9 | ], 10 | "includePlatforms": [ 11 | "Editor" 12 | ], 13 | "excludePlatforms": [], 14 | "allowUnsafeCode": false, 15 | "overrideReferences": true, 16 | "precompiledReferences": [ 17 | "nunit.framework.dll", 18 | "UnitsNet.dll", 19 | "System.Text.Json.dll" 20 | ], 21 | "autoReferenced": false, 22 | "defineConstraints": [ 23 | "UNITY_INCLUDE_TESTS" 24 | ], 25 | "versionDefines": [], 26 | "noEngineReferences": false 27 | } -------------------------------------------------------------------------------- /Universe/Rocketry/Rocket.cs: -------------------------------------------------------------------------------- 1 | // VindemiatrixCollective.Universe © 2025 Vindemiatrix Collective 2 | // Website and Documentation: https://vindemiatrixcollective.com 3 | 4 | #region 5 | 6 | using System; 7 | using UnitsNet; 8 | 9 | #endregion 10 | 11 | namespace VindemiatrixCollective.Universe.Rocketry 12 | { 13 | public static class Rocket 14 | { 15 | public static double CalculateMassRatio(Speed desiredDeltaV, Speed exhaustVelocity) 16 | { 17 | return Math.Exp(desiredDeltaV / exhaustVelocity); 18 | } 19 | 20 | public static Speed CalculateDeltaV(Speed exhaustVelocity, Mass payload, Mass fuelMass) 21 | { 22 | Speed c = Speed.FromMetersPerSecond(UniversalConstants.Celestial.LightSpeedMetresPerSecond); 23 | 24 | return c * Math.Tanh((exhaustVelocity / c) * Math.Log((payload + fuelMass) / payload)); 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /Universe/CelestialMechanics/Manoeuvres/ILambertSolver.cs: -------------------------------------------------------------------------------- 1 | // VindemiatrixCollective.Universe © 2025 Vindemiatrix Collective 2 | // Website and Documentation: https://vindemiatrixcollective.com 3 | 4 | #region 5 | 6 | using UnitsNet; 7 | 8 | #endregion 9 | 10 | namespace VindemiatrixCollective.Universe.CelestialMechanics.Manoeuvres 11 | { 12 | public interface ILambertSolver 13 | { 14 | bool LowPath { get; set; } 15 | bool Prograde { get; set; } 16 | int MaxIterations { get; } 17 | int Revolutions { get; set; } 18 | 19 | /// 20 | /// Solves Lambert's problem. 21 | /// 22 | /// Returns velocities in km/s. 23 | (Vector3d v1, Vector3d v2) Lambert( 24 | GravitationalParameter gravitationalParameter, Vector3d initialPosition, Vector3d finalPosition, Duration timeOfFlight); 25 | } 26 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 TheWand3rer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Universe/Tests/RuntimeSystemTests.cs: -------------------------------------------------------------------------------- 1 | // VindemiatrixCollective.Universe.Tests © 2025 Vindemiatrix Collective 2 | // Website and Documentation: https://vindemiatrixcollective.com 3 | 4 | #region 5 | 6 | using NUnit.Framework; 7 | using UnityEngine; 8 | using VindemiatrixCollective.Universe.Model; 9 | 10 | #endregion 11 | 12 | namespace VindemiatrixCollective.Universe.Tests 13 | { 14 | public class RuntimeSystemTests : ScriptableObject 15 | { 16 | [Test] 17 | public void BuildSolarSystem() 18 | { 19 | Galaxy galaxy = new("Milky Way"); 20 | StarSystem sol = new("Sol"); 21 | Star sun = Star.Sun; 22 | Planet earth = Planet.Earth; 23 | 24 | sun.AddOrbiter(earth); 25 | sol.AddOrbiter(sun); 26 | galaxy.AddSystem(sol); 27 | 28 | Assert.IsTrue(earth.ParentBody == sun, nameof(CelestialBody.ParentBody)); 29 | Assert.IsTrue(earth.ParentStar == sun, nameof(CelestialBody.ParentStar)); 30 | Assert.IsTrue(earth.StarSystem == sol, nameof(CelestialBody.StarSystem)); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /Universe/Tests/DeserializationTests.cs: -------------------------------------------------------------------------------- 1 | // VindemiatrixCollective.Universe.Tests © 2025 Vindemiatrix Collective 2 | // Website and Documentation: https://vindemiatrixcollective.com 3 | 4 | #region 5 | 6 | using NUnit.Framework; 7 | using VindemiatrixCollective.Universe.Model; 8 | 9 | #endregion 10 | 11 | namespace VindemiatrixCollective.Universe.Tests 12 | { 13 | public class DeserializationTests 14 | { 15 | private readonly DeserializationHelper dataHelper = new(); 16 | 17 | [TestCase("40.7128N,74.0060W", 40.7128, -74.0060)] 18 | [TestCase("40N, -74W", 40, -74)] 19 | [TestCase("0, -180", 0, -180)] // Test the (0, -180) coordinate 20 | [TestCase("0, 180", 0, 180)] // Test the (0, 180) coordinate 21 | public void DeserializeCoordinateString(string input, double expectedLatitude, double expectedLongitude) 22 | { 23 | GeoCoordinates coords = new(input); 24 | Assert.AreEqual(expectedLatitude, coords.Latitude, 1e-6, nameof(GeoCoordinates.Latitude)); 25 | Assert.AreEqual(expectedLongitude, coords.Longitude, 1e-6, nameof(GeoCoordinates.Longitude)); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /THIRD-PARTY-LICENSES.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Applies to: 4 | - James Newton-King for Newtonsoft.Json 5 | - Juan Luis Cano Rodríguez, Jorge Martínez Garrido, and the poliastro development team 6 | - Sebastian M. Ernst for the Hapsira fork 7 | - Andreas Gullberg Larsen for UnitsNet 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all 17 | copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | SOFTWARE. -------------------------------------------------------------------------------- /Universe/Model/LuminosityClass.cs: -------------------------------------------------------------------------------- 1 | // VindemiatrixCollective.Universe © 2025 Vindemiatrix Collective 2 | // Website and Documentation: https://vindemiatrixcollective.com 3 | 4 | namespace VindemiatrixCollective.Universe.Model 5 | { 6 | /// 7 | /// Luminosity classes enumeration. 8 | /// 9 | public enum LuminosityClass 10 | { 11 | Undefined = -1, 12 | 13 | /// 14 | /// Hypergiants. 15 | /// 16 | Ia0 = 0, 17 | 18 | /// 19 | /// Luminous supergiants. 20 | /// 21 | Ia = 1, 22 | 23 | /// 24 | /// Supergiants. 25 | /// 26 | Ib = 2, 27 | 28 | /// 29 | /// Bright ggiants. 30 | /// 31 | II = 3, 32 | 33 | /// 34 | /// Giants. 35 | /// 36 | III = 4, 37 | 38 | /// 39 | /// Subgiants. 40 | /// 41 | IV = 5, 42 | 43 | /// 44 | /// Main sequence stars (dwarf stars) 45 | /// 46 | V = 6, 47 | 48 | /// 49 | /// Subdwarf. 50 | /// 51 | VI = 7, 52 | 53 | /// 54 | /// White Dwarf. 55 | /// 56 | VII = 8 57 | } 58 | } -------------------------------------------------------------------------------- /Universe/Model/StellarData.cs: -------------------------------------------------------------------------------- 1 | // VindemiatrixCollective.Universe © 2025 Vindemiatrix Collective 2 | // Website and Documentation: https://vindemiatrixcollective.com 3 | 4 | #region 5 | 6 | using UnitsNet; 7 | using Unity.Properties; 8 | 9 | #endregion 10 | 11 | namespace VindemiatrixCollective.Universe.Model 12 | { 13 | public class StellarData : PhysicalData 14 | { 15 | [CreateProperty] public Duration Age { get; private set; } 16 | [CreateProperty] public Luminosity Luminosity { get; } 17 | 18 | public StellarData( 19 | Luminosity luminosity, Mass mass, Length radius = default, Acceleration gravity = default, Temperature temperature = default, 20 | Density density = default, Duration age = default) : base(mass, radius, gravity, density, temperature) 21 | { 22 | Luminosity = luminosity; 23 | Age = age; 24 | } 25 | 26 | public bool IsValid() 27 | { 28 | return Luminosity.SolarLuminosities > 0 && Mass.SolarMasses > 0; 29 | } 30 | 31 | internal static StellarData Null => new(Luminosity.Zero, Mass.Zero); 32 | 33 | private static Acceleration FromMassRadius(Mass m, Length r) 34 | { 35 | double G = UniversalConstants.Celestial.GravitationalConstant; 36 | double g = G * m.Kilograms / (r.Meters * r.Meters); 37 | 38 | return Acceleration.FromMetersPerSecondSquared(g); 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /Universe/Data/StarSystemConverter.cs: -------------------------------------------------------------------------------- 1 | // VindemiatrixCollective.Universe.Data © 2025 Vindemiatrix Collective 2 | // Website and Documentation: https://vindemiatrixcollective.com 3 | 4 | #region 5 | 6 | using System.Collections.Generic; 7 | using UnityEngine; 8 | using VindemiatrixCollective.Universe.Model; 9 | 10 | #endregion 11 | 12 | namespace VindemiatrixCollective.Universe.Data 13 | { 14 | public class StarSystemConverter : CoreObjectConverter 15 | { 16 | public StarSystemConverter() 17 | { 18 | Converter = new ObjectBuilder.Builder() 19 | .SetProperty(nameof(CelestialBody.Name), Parse.String, (state, value) => state.Name = value) 20 | .SetProperty(nameof(StarSystem.Orbiters), Parse.List, 21 | (state, value) => state.Orbiters = value) 22 | .SetProperty(nameof(StarSystem.Coordinates), Parse.Vector3, (state, value) => state.Coordinates = value, 23 | alternativeName: "c") 24 | .SetCreate(Creator) 25 | .Build(); 26 | } 27 | 28 | private StarSystem Creator(StarSystemState state) 29 | { 30 | StarSystem system = new(state.Name, state.Orbiters) 31 | { 32 | Coordinates = state.Coordinates 33 | }; 34 | return system; 35 | } 36 | 37 | public class StarSystemState 38 | { 39 | public List Orbiters; 40 | public string Name; 41 | public Vector3 Coordinates; 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /Universe/Model/PhysicalData.cs: -------------------------------------------------------------------------------- 1 | // VindemiatrixCollective.Universe © 2025 Vindemiatrix Collective 2 | // Website and Documentation: https://vindemiatrixcollective.com 3 | 4 | #region 5 | 6 | using System; 7 | using UnitsNet; 8 | using Unity.Properties; 9 | using VindemiatrixCollective.Universe.CelestialMechanics; 10 | 11 | #endregion 12 | 13 | namespace VindemiatrixCollective.Universe.Model 14 | { 15 | public class PhysicalData 16 | { 17 | [CreateProperty] public Acceleration Gravity { get; private set; } 18 | 19 | [CreateProperty] public Density Density { get; private set; } 20 | 21 | [CreateProperty] public Length Radius { get; private set; } 22 | 23 | [CreateProperty] public Mass Mass { get; private set; } 24 | 25 | [CreateProperty] public Temperature Temperature { get; private set; } 26 | 27 | public PhysicalData(Mass mass, Length radius, Acceleration gravity, Density density = default, Temperature temperature = default) 28 | { 29 | Mass = mass; 30 | Radius = radius; 31 | Gravity = gravity; 32 | Density = density; 33 | Temperature = temperature; 34 | } 35 | 36 | public PhysicalData(Density density, Length radius, GravitationalParameter gm, Temperature temperature = default) : 37 | this(Mass.FromKilograms(4 / 3d * UniversalConstants.Tri.Pi * Math.Pow(radius.Meters, 3) * density.KilogramsPerCubicMeter), 38 | radius, Acceleration.FromMetersPerSecondSquared(gm.M3S2 / Math.Pow(radius.Meters, 2)), density, temperature) { } 39 | 40 | internal static PhysicalData Null => new(Mass.Zero, Length.Zero, Acceleration.Zero); 41 | } 42 | } -------------------------------------------------------------------------------- /Universe/Tests/CoordinatesTests.cs: -------------------------------------------------------------------------------- 1 | // VindemiatrixCollective.Universe.Tests © 2025 Vindemiatrix Collective 2 | // Website and Documentation: https://vindemiatrixcollective.com 3 | 4 | #region 5 | 6 | using NUnit.Framework; 7 | using VindemiatrixCollective.Universe.CelestialMechanics; 8 | using VindemiatrixCollective.Universe.Model; 9 | 10 | #endregion 11 | 12 | namespace VindemiatrixCollective.Universe.Tests 13 | { 14 | public class CoordinatesTests 15 | { 16 | [Test] 17 | public void CoordinatesEquality() 18 | { 19 | GeoCoordinates coords1 = new(34.56700001, -118.123000009); 20 | GeoCoordinates coords2 = new(34.56700000, -118.123000001); 21 | Assert.IsTrue(coords1.Equals(coords2), nameof(Equals)); 22 | Assert.IsTrue(coords1 == coords2, "=="); 23 | } 24 | 25 | [Test] 26 | public void CoordinatesFromDouble() 27 | { 28 | GeoCoordinates coords = new(34.5, -118.0); 29 | Assert.AreEqual(34.5, coords.Latitude, nameof(GeoCoordinates.Latitude)); 30 | Assert.AreEqual(-118.0, coords.Longitude, nameof(GeoCoordinates.Longitude)); 31 | } 32 | 33 | [TestCase("90N,0E", 0, 1, 0)] 34 | [TestCase("0N,0E", 0, 0, 1)] 35 | [TestCase("0N,90E", 1, 0, 0)] 36 | public void GeoToCartesian(string input, double exp_x, double exp_y, double exp_z) 37 | { 38 | GeoCoordinates coords = new(input); 39 | Vector3d cartesian = coords.ToCartesian(1); 40 | Vector3d expected = new(exp_x, exp_y, exp_z); 41 | 42 | Common.VectorsAreEqual(expected, cartesian, 1e-6, nameof(cartesian)); 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /Universe/CelestialMechanics/GravitationalParameter.cs: -------------------------------------------------------------------------------- 1 | // VindemiatrixCollective.Universe © 2025 Vindemiatrix Collective 2 | // Website and Documentation: https://vindemiatrixcollective.com 3 | 4 | #region 5 | 6 | using System; 7 | using UnitsNet; 8 | 9 | #endregion 10 | 11 | namespace VindemiatrixCollective.Universe.CelestialMechanics 12 | { 13 | public readonly struct GravitationalParameter 14 | { 15 | public const double GravitationalConstant = UniversalConstants.Celestial.GravitationalConstant; 16 | 17 | public double Au3S2 => M3S2 / Math.Pow(UniversalConstants.Celestial.MetresPerAu, 3); 18 | 19 | public double Au3Y2 20 | { 21 | get 22 | { 23 | double timeScale = Math.Pow(UniversalConstants.Time.SecondsPerJulianYear, 2); 24 | double distanceScale = Math.Pow(UniversalConstants.Celestial.MetresPerAu, 3); 25 | 26 | return M3S2 * (timeScale / distanceScale); 27 | } 28 | } 29 | 30 | public double Km3S2 => M3S2 / Math.Pow(1000, 3); 31 | 32 | public double M3S2 { get; } 33 | 34 | public GravitationalParameter(double value) 35 | { 36 | M3S2 = value; 37 | } 38 | 39 | public static GravitationalParameter Earth => new(3.9860044188e14); 40 | public static GravitationalParameter Mars => new(4.2828372e13); 41 | 42 | public static GravitationalParameter Sun => new(1.32712440041279419e20); 43 | 44 | public static GravitationalParameter FromMass(Mass mass) => new(GravitationalConstant * mass.Kilograms); 45 | 46 | public static GravitationalParameter FromKm3S2(double value) => new(value * Math.Pow(1000, 3)); 47 | } 48 | } -------------------------------------------------------------------------------- /Universe/Data/GalaxyConverter.cs: -------------------------------------------------------------------------------- 1 | // VindemiatrixCollective.Universe.Data © 2025 Vindemiatrix Collective 2 | // Website and Documentation: https://vindemiatrixcollective.com 3 | 4 | #region 5 | 6 | using System.Collections.Generic; 7 | using System.Text.Json.Serialization; 8 | using VindemiatrixCollective.Universe.Model; 9 | using static VindemiatrixCollective.Universe.Data.GalaxyConverter; 10 | 11 | #endregion 12 | 13 | namespace VindemiatrixCollective.Universe.Data 14 | { 15 | public class GalaxyConverter : CoreObjectConverter 16 | { 17 | public GalaxyConverter() 18 | { 19 | Converter = new ObjectBuilder.Builder() 20 | .SetProperty(nameof(CelestialBody.Name), Parse.String, (state, value) => state.Name = value) 21 | .SetProperty(nameof(Galaxy.Systems), Parse.List, (state, value) => state.Systems = value) 22 | .SetCreate(Creator) 23 | .Build(); 24 | } 25 | 26 | private Galaxy Creator(GalaxyState state) 27 | { 28 | Galaxy galaxy = new(state.Name); 29 | galaxy.AddSystems(state.Systems); 30 | return galaxy; 31 | } 32 | 33 | public static JsonConverter[] Converters => new JsonConverter[] 34 | { 35 | new GalaxyConverter(), new StarSystemConverter(), 36 | new StarConverter(), new PlanetConverter(), new CelestialBodyConverter(), 37 | new PhysicalDataConverter(), new StellarDataConverter(), new OrbitalDataConverter() 38 | }; 39 | 40 | public class GalaxyState 41 | { 42 | public List Systems; 43 | public string Name; 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /Universe/CelestialMechanics/Orbits/Propagation/Kinematic.cs: -------------------------------------------------------------------------------- 1 | // VindemiatrixCollective.Universe © 2025 Vindemiatrix Collective 2 | // Website and Documentation: https://vindemiatrixcollective.com 3 | 4 | #region 5 | 6 | using UnitsNet; 7 | 8 | #endregion 9 | 10 | namespace VindemiatrixCollective.Universe.CelestialMechanics.Orbits.Propagation 11 | { 12 | public class Kinematic : IPropagator 13 | { 14 | private readonly double period; 15 | 16 | public Kinematic(OrbitalData data) 17 | { 18 | period = data.Period.Seconds; 19 | } 20 | 21 | public (Angle nu, Angle E, Angle M) PropagateOrbit(OrbitState state, Duration tof) 22 | { 23 | double e = state.Eccentricity.Value; 24 | double nu = state.TrueAnomaly.Radians; 25 | double P = period; 26 | double t0 = DeltaTFromNu(nu, e, P); 27 | double t = t0 + tof.Seconds; 28 | 29 | double E = TimeToEccentricAnomaly(t, P, e, out double M); 30 | nu = OrbitalMechanics.EccentricToTrueAnomaly(E, e); 31 | return (Angle.FromRadians(nu), Angle.FromRadians(E), Angle.FromRadians(M)); 32 | } 33 | 34 | private static double DeltaTFromNu(double nu, double e, double P) 35 | { 36 | double E = OrbitalMechanics.TrueToEccentricAnomaly(nu, e); 37 | double M = OrbitalMechanics.EccentricToMeanAnomaly(E, e); 38 | 39 | double t0 = M * P / UniversalConstants.Tri.Pi2; 40 | 41 | return t0; 42 | } 43 | 44 | private static double TimeToEccentricAnomaly(double t, double P, double e, out double M) 45 | { 46 | double Pi2 = UniversalConstants.Tri.Pi2; 47 | 48 | // Calculate Mean anomaly over one period 49 | M = Pi2 * t / P; 50 | M %= Pi2; 51 | 52 | double E = OrbitalMechanics.MeanToEccentricAnomaly(M, e); 53 | 54 | return E; 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /Universe/Data/Serialize.cs: -------------------------------------------------------------------------------- 1 | // VindemiatrixCollective.Universe.Data © 2025 Vindemiatrix Collective 2 | // Website and Documentation: https://vindemiatrixcollective.com 3 | 4 | #region 5 | 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Text.Json; 9 | using System.Text.RegularExpressions; 10 | using Object = UnityEngine.Object; 11 | 12 | #endregion 13 | 14 | namespace VindemiatrixCollective.Universe.Data 15 | { 16 | public static class Serialize 17 | { 18 | public static void String(Utf8JsonWriter writer, string value, JsonSerializerOptions options) 19 | { 20 | writer.WriteStringValue(value); 21 | } 22 | 23 | public static void String(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options) where TEnum : Enum 24 | { 25 | writer.WriteStringValue(value.ToString()); 26 | } 27 | 28 | public static void Type(Utf8JsonWriter writer, Type type, JsonSerializerOptions options) 29 | { 30 | string asmName = string.Join('.', type.Assembly.GetName().Name.Split('.')[1..]); 31 | string typeName; 32 | if (asmName.Contains("Module")) 33 | { 34 | asmName = Regex.Replace(asmName, "(.*)Module$", "Unity.$1"); 35 | } 36 | 37 | if (typeof(Object).IsAssignableFrom(type)) 38 | { 39 | List ns = new(); 40 | string[] nsParts = type.FullName.Split('.')[1..]; 41 | foreach (string s in nsParts) 42 | { 43 | if (asmName.Contains(s, StringComparison.InvariantCultureIgnoreCase)) 44 | continue; 45 | 46 | ns.Add(s); 47 | } 48 | 49 | typeName = string.Join('.', ns); 50 | } 51 | else 52 | { 53 | asmName = "Unity.Core"; 54 | typeName = "TextAsset"; 55 | } 56 | 57 | writer.WriteStringValue($"{asmName}:{typeName}"); 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /Universe/Tests/ManoeuvreTests.cs: -------------------------------------------------------------------------------- 1 | // VindemiatrixCollective.Universe.Tests © 2025 Vindemiatrix Collective 2 | // Website and Documentation: https://vindemiatrixcollective.com 3 | 4 | #region 5 | 6 | using System; 7 | using NUnit.Framework; 8 | using UnitsNet; 9 | using UnityEngine; 10 | using VindemiatrixCollective.Universe.CelestialMechanics; 11 | using VindemiatrixCollective.Universe.CelestialMechanics.Manoeuvres; 12 | using VindemiatrixCollective.Universe.CelestialMechanics.Orbits; 13 | using Impulse = VindemiatrixCollective.Universe.CelestialMechanics.Manoeuvres.Impulse; 14 | 15 | #endregion 16 | 17 | namespace VindemiatrixCollective.Universe.Tests 18 | { 19 | public class ManoeuvreTests 20 | { 21 | [Test] 22 | public void EarthToMarsManoeuvre() 23 | { 24 | // Values from Hapsira for the Mars Science Laboratory 25 | DateTime epochDeparture = new(2011, 11, 26, 15, 2, 0, DateTimeKind.Utc); 26 | 27 | Vector3d r = new(6.46006458e+10, 1.21424867e+11, 5.26400459e+10); // m 28 | Vector3d v = new(-27227.01764589, 11944.59987887, 5176.81664313); // m/s 29 | 30 | OrbitState state = OrbitState.FromVectors(r, v, Common.Sun, epochDeparture); 31 | 32 | 33 | Manoeuvre m = new(new[] 34 | { 35 | new Impulse(Duration.Zero, new Vector3d(-2064.20560746, 2587.9683653, 239.11542941)), // m/s 36 | new Impulse(Duration.FromSeconds(21910501), new Vector3d(3331.39946578, 682.12917585, -1089.77932679)) // m/s 37 | }); 38 | 39 | double deltaV = m.ComputeTotalCost().MetersPerSecond; 40 | double duration = m.ComputeTotalDuration().Days; 41 | 42 | Debug.Log($"DeltaV cost: {deltaV:f2} m/s\nDuration: {duration:f2} d"); 43 | 44 | Assert.AreEqual(6889.865, deltaV, 1e-3); 45 | Assert.AreEqual(253.55, duration, 1e-1); 46 | 47 | OrbitState newState = state.ApplyManoeuvre(m); 48 | 49 | Debug.Log(state.ToString()); 50 | Debug.Log(newState.ToString()); 51 | 52 | // TODO: see why there is a non-negligible difference in these results 53 | Assert.AreEqual(1.5307983220292651, newState.SemiMajorAxis.AstronomicalUnits, 0.01); 54 | Assert.AreEqual(691.791767927197, newState.Period.Days, 1); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /Universe/UniversalConstants.cs: -------------------------------------------------------------------------------- 1 | // VindemiatrixCollective.Universe © 2025 Vindemiatrix Collective 2 | // Website and Documentation: https://vindemiatrixcollective.com 3 | 4 | #region 5 | 6 | using System; 7 | using System.Runtime.CompilerServices; 8 | 9 | #endregion 10 | 11 | [assembly: InternalsVisibleTo("VindemiatrixCollective.Universe.Data")] 12 | 13 | namespace VindemiatrixCollective.Universe 14 | { 15 | public static class UniversalConstants 16 | { 17 | public struct Physical 18 | { 19 | /// 20 | /// Earth equatorial radius from https://arxiv.org/abs/1510.07674 21 | /// 22 | public const double EarthRadiusKm = 6378.1; 23 | } 24 | 25 | public struct Tri 26 | { 27 | public const double DegreeToRad = 0.017453292519943295; 28 | public const double Pi = 3.141592653589793d; 29 | public const double Pi2 = 2 * Pi; 30 | public const double RadToDegree = 180 / Pi; 31 | } 32 | 33 | public struct Time 34 | { 35 | public const int SecondsPerDay = SecondsPerHour * 24; 36 | public const int SecondsPerHour = 60 * 60; 37 | public const int SecondsPerJulianYear = 31557600; 38 | public static DateTime J2000 = new(2000, 1, 1, 11, 58, 55, 816, DateTimeKind.Utc); 39 | } 40 | 41 | public struct Celestial 42 | { 43 | public const double AuPerMetre = 6.6845871222684454959959533702106e-12; 44 | public const double GravitationalConstant = 6.67429E-11; // N⋅m²/kg² 45 | public const double KmPerAu = 149597870.700; 46 | public const double LightSpeedKilometresPerSecond = 299792.458; 47 | public const double LightSpeedMetresPerSecond = 299792458; 48 | public const double MetresPerAu = 149597870700; 49 | public const double SquareAuPerSquareKm = 4.4683704831421e-17; 50 | } 51 | 52 | public struct Gas 53 | { 54 | public const double AvogadroConstant = 6.02214076e23; // mol -1 55 | public const double BoltzmannConstant = 1.380649e-23; // J/K 56 | } 57 | 58 | public struct Chemistry 59 | { 60 | public const double H = 1.007825; 61 | } 62 | 63 | public struct Energy 64 | { 65 | public const double SolarConstantWm2 = 1361; 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /Universe/Data/PlanetConverter.cs: -------------------------------------------------------------------------------- 1 | // VindemiatrixCollective.Universe.Data © 2025 Vindemiatrix Collective 2 | // Website and Documentation: https://vindemiatrixcollective.com 3 | 4 | #region 5 | 6 | using System.Collections.Generic; 7 | using VindemiatrixCollective.Universe.CelestialMechanics.Orbits; 8 | using VindemiatrixCollective.Universe.Model; 9 | 10 | #endregion 11 | 12 | namespace VindemiatrixCollective.Universe.Data 13 | { 14 | public class PlanetConverter : CoreObjectConverter 15 | { 16 | public PlanetConverter() 17 | { 18 | Converter = new ObjectBuilder.Builder() 19 | .SetProperty(nameof(CelestialBody.Name), Parse.String, (state, value) => state.Name = value) 20 | .SetProperty(nameof(CelestialBody.Attributes), Parse.Dictionary, (state, value) => state.Attributes = value) 21 | .SetProperty(nameof(CelestialBody.PhysicalData), Parse.Object, (state, value) => state.PhysicalData = value) 22 | .SetProperty(nameof(CelestialBody.OrbitalData), Parse.Object, (state, value) => state.OrbitalData = value) 23 | .SetProperty(nameof(StarSystem.Orbiters), Parse.List, 24 | (state, value) => state.Orbiters = value, true) 25 | .SetCreate(Creator) 26 | .Build(); 27 | } 28 | 29 | private Planet Creator(PlanetState state) 30 | { 31 | if (state.PhysicalData == null) 32 | { 33 | state.PhysicalData = PhysicalData.Null; 34 | } 35 | 36 | Planet planet = new(state.Name, state.PhysicalData, state.OrbitalData); 37 | 38 | if (state.Attributes != null) 39 | { 40 | planet.Attributes.CopyFrom(state.Attributes); 41 | } 42 | 43 | if (state.Orbiters != null) 44 | { 45 | planet.AddOrbiters(state.Orbiters); 46 | } 47 | 48 | return planet; 49 | } 50 | 51 | public class PlanetState 52 | { 53 | public Dictionary Attributes; 54 | public List Orbiters; 55 | public OrbitalData OrbitalData; 56 | public PhysicalData PhysicalData; 57 | public string Name; 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /Universe/Data/ObjectWriter.cs: -------------------------------------------------------------------------------- 1 | // VindemiatrixCollective.Universe.Data © 2025 Vindemiatrix Collective 2 | // Website and Documentation: https://vindemiatrixcollective.com 3 | 4 | #region 5 | 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Text.Json; 9 | using UnityEngine; 10 | 11 | #endregion 12 | 13 | namespace VindemiatrixCollective.Universe.Data 14 | { 15 | public class ObjectWriter : ISerializerImplementation where T : class 16 | { 17 | private readonly Dictionary> predicates = new(); 18 | private readonly List> writers = new(); 19 | 20 | public void Serialize(Utf8JsonWriter writer, T value, JsonSerializerOptions options) 21 | { 22 | foreach (Action action in writers) 23 | { 24 | action(writer, value, options); 25 | } 26 | } 27 | 28 | public class Builder 29 | { 30 | private readonly ObjectWriter objectWriter = new(); 31 | 32 | public ObjectWriter Build() 33 | { 34 | return objectWriter; 35 | } 36 | 37 | public Builder SetPredicate(string propertyName, Func predicate) 38 | { 39 | objectWriter.predicates.Add(propertyName, predicate); 40 | return this; 41 | } 42 | 43 | public Builder SetProperty(string propertyName, Func accessor, PropertyWriter writer) 44 | { 45 | objectWriter.writers.Add((w, value, o) => 46 | { 47 | TValue propValue = accessor(value); 48 | 49 | if (propValue == null || string.IsNullOrEmpty(propValue.ToString())) 50 | { 51 | Debug.LogWarning($"[{typeof(T).Name}.{propertyName}]: attempted to serialize null or empty property"); 52 | return; 53 | } 54 | 55 | if (objectWriter.predicates.TryGetValue(propertyName, out Func predicate)) 56 | { 57 | if (!predicate(value)) 58 | return; 59 | } 60 | 61 | w.WritePropertyName(propertyName); 62 | writer(w, propValue, o); 63 | }); 64 | 65 | return this; 66 | } 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /Universe/Model/Attributes.cs: -------------------------------------------------------------------------------- 1 | // VindemiatrixCollective.Universe © 2025 Vindemiatrix Collective 2 | // Website and Documentation: https://vindemiatrixcollective.com 3 | 4 | #region 5 | 6 | using System; 7 | using System.Collections; 8 | using System.Collections.Generic; 9 | 10 | #endregion 11 | 12 | namespace VindemiatrixCollective.Universe.Model 13 | { 14 | public class Attributes : IEnumerable> 15 | { 16 | private readonly Dictionary data; 17 | 18 | public CelestialBodyType Type => TryGet(nameof(Type)); 19 | 20 | public string this[string key] 21 | { 22 | get => data[key]; 23 | set => data[key] = value; 24 | } 25 | 26 | public Attributes() 27 | { 28 | data = new Dictionary(); 29 | } 30 | 31 | public Attributes(Dictionary data) 32 | { 33 | this.data = data; 34 | } 35 | 36 | public bool ContainsKey(string key) 37 | { 38 | return data.ContainsKey(key); 39 | } 40 | 41 | public IEnumerator> GetEnumerator() 42 | { 43 | return data.GetEnumerator(); 44 | } 45 | 46 | public string TryGet(string key) 47 | { 48 | data.TryGetValue(key, out string value); 49 | return value; 50 | } 51 | 52 | public TEnum TryGet(string alternativeKey = null) where TEnum : struct, Enum 53 | { 54 | string value = TryGet(typeof(TEnum).Name); 55 | if (string.IsNullOrEmpty(value) && !string.IsNullOrEmpty(alternativeKey)) 56 | { 57 | value = TryGet(alternativeKey); 58 | } 59 | 60 | if (string.IsNullOrEmpty(value)) 61 | return default(TEnum); 62 | return Enum.Parse(value); 63 | } 64 | 65 | public void CopyFrom(Attributes attributes) 66 | { 67 | CopyFrom(attributes.data); 68 | } 69 | 70 | public void CopyFrom(IDictionary attributes) 71 | { 72 | foreach (var kvp in attributes) 73 | { 74 | data[kvp.Key] = kvp.Value; 75 | } 76 | } 77 | 78 | public void Set(TEnum value) where TEnum : struct, Enum 79 | { 80 | data[typeof(TEnum).Name] = value.ToString(); 81 | } 82 | 83 | 84 | IEnumerator IEnumerable.GetEnumerator() 85 | { 86 | return GetEnumerator(); 87 | } 88 | } 89 | } -------------------------------------------------------------------------------- /Universe/Tests/DeserializationHelper.cs: -------------------------------------------------------------------------------- 1 | // VindemiatrixCollective.Universe.Tests © 2025 Vindemiatrix Collective 2 | // Website and Documentation: https://vindemiatrixcollective.com 3 | 4 | #region 5 | 6 | using System.Text.Json; 7 | using UnityEngine; 8 | using UnityEngine.Assertions; 9 | using VindemiatrixCollective.Universe.Data; 10 | using VindemiatrixCollective.Universe.Model; 11 | using JsonConverter = System.Text.Json.Serialization.JsonConverter; 12 | using JsonSerializer = System.Text.Json.JsonSerializer; 13 | 14 | #endregion 15 | 16 | namespace VindemiatrixCollective.Universe.Tests 17 | { 18 | public class DeserializationHelper 19 | { 20 | public static readonly JsonConverter[] Converters = 21 | { 22 | new GalaxyConverter(), new StarSystemConverter(), 23 | new StarConverter(), new PlanetConverter(), new CelestialBodyConverter(), 24 | new PhysicalDataConverter(), new StellarDataConverter(), new OrbitalDataConverter() 25 | }; 26 | 27 | public Galaxy LoadGalaxy(string path = "Data/galaxy") 28 | { 29 | Galaxy galaxy = DeserializeFile(path, Converters); 30 | return galaxy; 31 | } 32 | 33 | public Galaxy LoadSol(string path = "Data/SolarSystem") 34 | { 35 | Galaxy galaxy = new("Milky Way"); 36 | LoadSol(ref galaxy); 37 | return galaxy; 38 | } 39 | 40 | public T DeserializeFile(string filename, params JsonConverter[] converters) 41 | { 42 | TextAsset text = Resources.Load(filename); 43 | Assert.IsNotNull(text, filename); 44 | JsonSerializerOptions options = new() { PropertyNameCaseInsensitive = true }; 45 | 46 | foreach (JsonConverter converter in converters) 47 | { 48 | options.Converters.Add(converter); 49 | } 50 | 51 | return JsonSerializer.Deserialize(text.text, options); 52 | } 53 | 54 | public T DeserializeObject(string data, params JsonConverter[] converters) 55 | { 56 | JsonSerializerOptions options = new() { PropertyNameCaseInsensitive = true }; 57 | 58 | foreach (JsonConverter converter in converters) 59 | { 60 | options.Converters.Add(converter); 61 | } 62 | 63 | return JsonSerializer.Deserialize(data, options); 64 | } 65 | 66 | public void LoadSol(ref Galaxy galaxy, string path = "Data/SolarSystem") 67 | { 68 | Galaxy additionalData = DeserializeFile(path, Converters); 69 | foreach (StarSystem system in additionalData.Systems) 70 | { 71 | galaxy.AddSystem(system); 72 | } 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /Universe/Data/PhysicalDataConverter.cs: -------------------------------------------------------------------------------- 1 | // VindemiatrixCollective.Universe.Data © 2025 Vindemiatrix Collective 2 | // Website and Documentation: https://vindemiatrixcollective.com 3 | 4 | #region 5 | 6 | using UnitsNet; 7 | using UnitsNet.Units; 8 | using VindemiatrixCollective.Universe.CelestialMechanics; 9 | using VindemiatrixCollective.Universe.Model; 10 | using static VindemiatrixCollective.Universe.Data.PhysicalDataConverter; 11 | 12 | #endregion 13 | 14 | namespace VindemiatrixCollective.Universe.Data 15 | { 16 | public class PhysicalDataConverter : CoreObjectConverter 17 | { 18 | public PhysicalDataConverter() 19 | { 20 | Converter = new ObjectBuilder.Builder() 21 | .SetProperty(nameof(PhysicalData.Mass), Parse.ValueUnit, (state, value) => state.m = value, true, "m") 22 | .SetProperty(nameof(PhysicalData.Density), Parse.ValueUnit, (state, value) => state.d = value, true, "d") 23 | .SetProperty(nameof(PhysicalData.Gravity), Parse.ValueUnit, (state, value) => state.g = value, true, "g") 24 | .SetProperty(nameof(PhysicalData.Radius), Parse.ValueUnit, (state, value) => state.r = value, true, "r") 25 | .SetProperty(nameof(PhysicalData.Temperature), Parse.ValueUnit, (state, value) => state.t = value, true, "t") 26 | .SkipProperty("HillSphereRadius") 27 | .SetProperty(nameof(GravitationalParameter), Parse.Double, (state, value) => state.GmKm3S2 = value, true, "gm") 28 | .SetCreate(Creator) 29 | .Build(); 30 | } 31 | 32 | private PhysicalData Creator(PhysicalDataState state) 33 | { 34 | Mass mass = state.m.ToQuantity(MassUnit.Kilogram); 35 | Length radius = state.r.ToQuantity(LengthUnit.Kilometer); 36 | Acceleration gravity = state.g.ToQuantity(AccelerationUnit.MeterPerSecondSquared); 37 | Density density = state.d.ToQuantity(DensityUnit.GramPerCubicCentimeter); 38 | Temperature temp = state.t.ToQuantity(TemperatureUnit.Kelvin); 39 | 40 | GravitationalParameter gm = GravitationalParameter.FromKm3S2(state.GmKm3S2); 41 | 42 | PhysicalData data = state.m.v > 0 43 | ? new PhysicalData(mass, radius, gravity, density, temp) 44 | : new PhysicalData(density, radius, gm, temp); 45 | 46 | return data; 47 | } 48 | 49 | public class PhysicalDataState 50 | { 51 | public double GmKm3S2; 52 | 53 | public ValueUnit m, d, r, g, t; 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /Universe/Data/StarConverter.cs: -------------------------------------------------------------------------------- 1 | // VindemiatrixCollective.Universe.Data © 2025 Vindemiatrix Collective 2 | // Website and Documentation: https://vindemiatrixcollective.com 3 | 4 | #region 5 | 6 | using System.Collections.Generic; 7 | using VindemiatrixCollective.Universe.CelestialMechanics.Orbits; 8 | using VindemiatrixCollective.Universe.Model; 9 | 10 | #endregion 11 | 12 | namespace VindemiatrixCollective.Universe.Data 13 | { 14 | public class StarConverter : CoreObjectConverter 15 | { 16 | public StarConverter() 17 | { 18 | Converter = new ObjectBuilder.Builder() 19 | .SetProperty("Id", Parse.String, (state, value) => state.Id = value, true) 20 | .SetProperty(nameof(CelestialBody.Name), Parse.String, (state, value) => state.Name = value) 21 | .SetProperty(nameof(CelestialBody.Attributes), Parse.Dictionary, (state, value) => state.Attributes = value, true) 22 | .SetProperty(nameof(CelestialBody.OrbitalData), Parse.Object, (state, value) => state.OrbitalData = value, true) 23 | .SetProperty(nameof(Star.StellarData), Parse.Object, (state, value) => state.StellarData = value, true, 24 | alternativeName: nameof(CelestialBody.PhysicalData)) 25 | .SetProperty(nameof(Star.SpectralClass), Parse.String, (state, value) => state.SpectralClass = value, true, 26 | alternativeName: "SC") 27 | .SetProperty(nameof(StarSystem.Orbiters), Parse.List, 28 | (state, value) => state.Orbiters = value, true) 29 | .SetCreate(Creator) 30 | .Build(); 31 | } 32 | 33 | private Star Creator(StarState state) 34 | { 35 | if (state.StellarData == null) 36 | { 37 | state.StellarData = StellarData.Null; 38 | } 39 | 40 | Star star = new(state.Name, state.StellarData); 41 | 42 | if (state.Attributes != null) 43 | { 44 | star.Attributes.CopyFrom(state.Attributes); 45 | } 46 | 47 | star.OrbitalData = state.OrbitalData; 48 | if (!string.IsNullOrEmpty(state.SpectralClass)) 49 | { 50 | star.SpectralClass = new SpectralClass(state.SpectralClass); 51 | } 52 | 53 | if (state.Orbiters != null) 54 | { 55 | star.AddOrbiters(state.Orbiters); 56 | } 57 | 58 | return star; 59 | } 60 | 61 | public class StarState 62 | { 63 | public Dictionary Attributes; 64 | public List Orbiters; 65 | public OrbitalData OrbitalData; 66 | public StellarData StellarData; 67 | public string Name, Id; 68 | public string SpectralClass; 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /Universe/Tests/AngleTests.cs: -------------------------------------------------------------------------------- 1 | // VindemiatrixCollective.Universe.Tests © 2025 Vindemiatrix Collective 2 | // Website and Documentation: https://vindemiatrixcollective.com 3 | 4 | #region 5 | 6 | using NUnit.Framework; 7 | using VindemiatrixCollective.Universe.CelestialMechanics.Orbits; 8 | 9 | #endregion 10 | 11 | namespace VindemiatrixCollective.Universe.Tests 12 | { 13 | public class AngleTests 14 | { 15 | // Data from Schlesinger & Udick, 1912 16 | private readonly double[][] ellipticAngles = 17 | { 18 | // ecc,E (deg), nu(deg) 19 | new[] { 0.0, 0.0, 0.0 }, 20 | new[] { 0.05, 10.0, 11.06 }, 21 | new[] { 0.06, 30.0, 33.67 }, 22 | new[] { 0.04, 120.0, 123.87 }, 23 | new[] { 0.14, 65.0, 80.50 }, 24 | new[] { 0.19, 21.0, 30.94 }, 25 | new[] { 0.35, 65.0, 105.71 }, 26 | new[] { 0.48, 180.0, 180.0 }, 27 | new[] { 0.75, 125.0, 167.57 }, 28 | }; 29 | 30 | [Test] 31 | public void MeanToTrueAnomaly() 32 | { 33 | foreach (double[] row in ellipticAngles) 34 | { 35 | double e = row[0]; 36 | double M = row[1] * UniversalConstants.Tri.DegreeToRad; 37 | double expectedNu = row[2]; 38 | 39 | double nu = OrbitalMechanics.EccentricToTrueAnomaly(OrbitalMechanics.MeanToEccentricAnomaly(M, e), e); 40 | Assert.AreEqual(expectedNu, nu * UniversalConstants.Tri.RadToDegree, 1e-2, nameof(OrbitState.TrueAnomaly)); 41 | } 42 | } 43 | 44 | [Test] 45 | public void TrueToEccentric() 46 | { 47 | double[][] data = 48 | { 49 | //# ecc,E (deg), nu (deg) 50 | new[] { 0.0, 0.0, 0.0 }, 51 | new[] { 0.05, 10.52321, 11.05994 }, 52 | new[] { 0.10, 54.67466, 59.49810 }, 53 | new[] { 0.35, 142.27123, 153.32411 }, 54 | new[] { 0.61, 161.87359, 171.02189 }, 55 | }; 56 | 57 | foreach (double[] row in data) 58 | { 59 | double e = row[0]; 60 | double expectedE = row[1]; 61 | double nu = row[2] * UniversalConstants.Tri.DegreeToRad; 62 | 63 | double E = OrbitalMechanics.TrueToEccentricAnomaly(nu, e); 64 | Assert.AreEqual(expectedE, E * UniversalConstants.Tri.RadToDegree, 1e-2, nameof(OrbitState.EccentricAnomaly)); 65 | } 66 | } 67 | 68 | [Test] 69 | public void TrueToMeanAnomaly() 70 | { 71 | foreach (double[] row in ellipticAngles) 72 | { 73 | double e = row[0]; 74 | double expectedM = row[1]; 75 | double nu = row[2] * UniversalConstants.Tri.DegreeToRad; 76 | 77 | double M = OrbitalMechanics.EccentricToMeanAnomaly(OrbitalMechanics.TrueToEccentricAnomaly(nu, e), e); 78 | Assert.AreEqual(expectedM, M * UniversalConstants.Tri.RadToDegree, 1e-2, "MeanAnomaly"); 79 | } 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /Universe/Data/StellarDataConverter.cs: -------------------------------------------------------------------------------- 1 | // VindemiatrixCollective.Universe.Data © 2025 Vindemiatrix Collective 2 | // Website and Documentation: https://vindemiatrixcollective.com 3 | 4 | #region 5 | 6 | using UnitsNet; 7 | using UnitsNet.Units; 8 | using VindemiatrixCollective.Universe.Model; 9 | using static VindemiatrixCollective.Universe.Data.StellarDataConverter; 10 | 11 | #endregion 12 | 13 | namespace VindemiatrixCollective.Universe.Data 14 | { 15 | public class StellarDataConverter : CoreObjectConverter 16 | { 17 | public StellarDataConverter() 18 | { 19 | Converter = new ObjectBuilder.Builder() 20 | .SetProperty(nameof(StellarData.Luminosity), Parse.ValueUnit, (state, value) => state.l = value, alternativeName: "l") 21 | .SetProperty(nameof(StellarData.Mass), Parse.ValueUnit, (state, value) => state.m = value, true, "m") 22 | .SetProperty(nameof(StellarData.Density), Parse.ValueUnit, (state, value) => state.d = value, true, "d") 23 | .SetProperty(nameof(StellarData.Gravity), Parse.ValueUnit, (state, value) => state.g = value, true, "g") 24 | .SetProperty(nameof(StellarData.Radius), Parse.ValueUnit, (state, value) => state.r = value, true, "r") 25 | .SetProperty(nameof(StellarData.Temperature), Parse.ValueUnit, (state, value) => state.t = value, true, "t") 26 | .SetProperty(nameof(StellarData.Age), Parse.ValueUnit, (state, value) => state.y = value, true, "age") 27 | .SetCreate(Creator) 28 | .Build(); 29 | } 30 | 31 | private StellarData Creator(StellarDataState state) 32 | { 33 | if (state.l.u == "sl") 34 | { 35 | state.l = new ValueUnit(state.l.v, "L⊙"); 36 | } 37 | 38 | if (state.m.u == "sm") 39 | { 40 | state.m = new ValueUnit(state.m.v, "M☉"); 41 | } 42 | 43 | if (state.r.u == "sr") 44 | { 45 | state.r = new ValueUnit(state.r.v, "R⊙"); 46 | } 47 | 48 | Luminosity lum = state.l.ToQuantity(LuminosityUnit.SolarLuminosity); 49 | Mass mass = state.m.ToQuantity(MassUnit.SolarMass); 50 | Length radius = state.r.ToQuantity(LengthUnit.SolarRadius); 51 | Acceleration gravity = state.g.ToQuantity(AccelerationUnit.MeterPerSecondSquared); 52 | Density density = state.d.ToQuantity(DensityUnit.GramPerCubicCentimeter); 53 | Temperature temp = state.t.ToQuantity(TemperatureUnit.Kelvin); 54 | Duration age = (state.y * 1E9).ToQuantity(DurationUnit.Year365); 55 | 56 | 57 | return new StellarData(lum, mass, radius, gravity, temp, density, age); 58 | } 59 | 60 | public class StellarDataState 61 | { 62 | public ValueUnit l, m, d, r, g, t, y; 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /Universe/Data/OrbitalDataConverter.cs: -------------------------------------------------------------------------------- 1 | // VindemiatrixCollective.Universe.Data © 2025 Vindemiatrix Collective 2 | // Website and Documentation: https://vindemiatrixcollective.com 3 | 4 | #region 5 | 6 | using UnitsNet; 7 | using UnitsNet.Units; 8 | using VindemiatrixCollective.Universe.CelestialMechanics.Orbits; 9 | 10 | #endregion 11 | 12 | namespace VindemiatrixCollective.Universe.Data 13 | { 14 | public class OrbitalDataConverter : CoreObjectConverter 15 | { 16 | public OrbitalDataConverter() 17 | { 18 | Converter = new ObjectBuilder.Builder() 19 | .SetProperty(nameof(OrbitalData.SemiMajorAxis), Parse.ValueUnit, (state, value) => state.a = value, alternativeName: "a") 20 | .SetProperty(nameof(OrbitalData.Eccentricity), Parse.ValueUnit, (state, value) => state.e = value, alternativeName: "e") 21 | .SetProperty(nameof(OrbitalData.Inclination), Parse.ValueUnit, (state, value) => state.i = value, alternativeName: "i") 22 | .SetProperty(nameof(OrbitalData.LongitudeAscendingNode), Parse.ValueUnit, (state, value) => state.lan = value, 23 | alternativeName: "lan") 24 | .SetProperty(nameof(OrbitalData.ArgumentPeriapsis), Parse.ValueUnit, (state, value) => state.argp = value, 25 | alternativeName: "argp") 26 | .SetProperty(nameof(OrbitalData.TrueAnomaly), Parse.ValueUnit, (state, value) => state.ta = value, alternativeName: "ta") 27 | .SetProperty(nameof(OrbitalData.MeanAnomaly), Parse.ValueUnit, (state, value) => state.ma = value, true, "ma") 28 | .SetProperty(nameof(OrbitalData.Period), Parse.ValueUnit, (state, value) => state.p = value, true, "p") 29 | .SetProperty(nameof(OrbitalData.AxialTilt), Parse.ValueUnit, (state, value) => state.tilt = value, true, "tilt") 30 | .SetProperty(nameof(OrbitalData.SiderealRotationPeriod), Parse.ValueUnit, (state, value) => state.srp = value, true, "srp") 31 | .SetCreate(Creator) 32 | .Build(); 33 | } 34 | 35 | private OrbitalData Creator(OrbitalDataState state) 36 | { 37 | Length sma = state.a.ToQuantity(LengthUnit.AstronomicalUnit); 38 | Ratio ecc = state.e.ToQuantity(RatioUnit.DecimalFraction); 39 | Angle inc = state.i.ToQuantity(AngleUnit.Degree); 40 | Duration period = state.p.ToQuantity(DurationUnit.Day); 41 | Angle laNode = state.lan.ToQuantity(AngleUnit.Degree); 42 | Angle argPer = state.argp.ToQuantity(AngleUnit.Degree); 43 | Angle trueAn = state.ta.ToQuantity(AngleUnit.Degree); 44 | Angle meanAn = state.ma.ToQuantity(AngleUnit.Degree); 45 | Angle axial = state.tilt.ToQuantity(AngleUnit.Degree); 46 | Duration sidereal = state.srp.ToQuantity(DurationUnit.Hour); 47 | 48 | 49 | return new OrbitalData(sma, ecc, inc, laNode, argPer, trueAn, period, sidereal, 50 | axial, meanAn); 51 | } 52 | 53 | public class OrbitalDataState 54 | { 55 | public ValueUnit a, e, p, i, lan, argp, ma, ta, tilt, srp; 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /Universe/Data/CoreObjectConverter.cs: -------------------------------------------------------------------------------- 1 | // VindemiatrixCollective.Universe.Data © 2025 Vindemiatrix Collective 2 | // Website and Documentation: https://vindemiatrixcollective.com 3 | 4 | #region 5 | 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Text.Json; 9 | using System.Text.Json.Serialization; 10 | 11 | #endregion 12 | 13 | namespace VindemiatrixCollective.Universe.Data 14 | { 15 | public interface IConverterImplementation 16 | { 17 | Type Type { get; } 18 | bool ReadProperty(string propertyName, ref Utf8JsonReader reader, JsonSerializerOptions options, TState state); 19 | bool Validate(TState state, out IEnumerable missingProperties); 20 | T Create(TState state); 21 | } 22 | 23 | public interface ISerializerImplementation 24 | { 25 | void Serialize(Utf8JsonWriter writer, T value, JsonSerializerOptions options); 26 | } 27 | 28 | public delegate TProperty PropertyReader(ref Utf8JsonReader reader, JsonSerializerOptions options); 29 | 30 | public delegate void PropertySetter(ref Utf8JsonReader reader, JsonSerializerOptions options, TState state) 31 | where TState : class, new(); 32 | 33 | public delegate void PropertyWriter(Utf8JsonWriter writer, T value, JsonSerializerOptions options); 34 | 35 | public class CoreObjectConverter : JsonConverter where T : class where TState : class, new() 36 | { 37 | protected IConverterImplementation Converter { get; set; } 38 | protected ISerializerImplementation Serializer { get; set; } 39 | 40 | public CoreObjectConverter( 41 | IConverterImplementation readerImplementation, ISerializerImplementation serializerImplementation = null) 42 | { 43 | Converter = readerImplementation; 44 | Serializer = serializerImplementation; 45 | } 46 | 47 | protected CoreObjectConverter() { } 48 | 49 | public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 50 | { 51 | if (reader.TokenType != JsonTokenType.StartObject) 52 | { 53 | throw new JsonException($"{Type.Name}: Expected start of object but found {reader.TokenType}"); 54 | } 55 | 56 | TState state = new(); 57 | 58 | while (reader.Read()) 59 | { 60 | if (reader.TokenType == JsonTokenType.EndObject) 61 | { 62 | break; 63 | } 64 | 65 | if (reader.TokenType == JsonTokenType.PropertyName) 66 | { 67 | string propertyName = reader.GetString(); 68 | 69 | reader.Read(); 70 | 71 | bool result = Converter.ReadProperty(propertyName, ref reader, options, state); 72 | if (!result) 73 | { 74 | throw new JsonException($"{Type.Name}: Property not recognised: <{propertyName}> | {reader.TokenType}"); 75 | } 76 | } 77 | else 78 | { 79 | throw new JsonException($"{Type.Name}: Unexpected token: <{reader.TokenType}> Value: {reader.GetString()}"); 80 | } 81 | } 82 | 83 | if (!Converter.Validate(state, out IEnumerable missingProperties)) 84 | throw new JsonException($"{Type.Name}: Missing required properties {string.Join(", ", missingProperties)}"); 85 | return Converter.Create(state); 86 | } 87 | 88 | public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) 89 | { 90 | if (Serializer == null) 91 | throw new NotImplementedException($"No serializer for {typeof(T).Name}"); 92 | 93 | writer.WriteStartObject(); 94 | Serializer.Serialize(writer, value, options); 95 | writer.WriteEndObject(); 96 | } 97 | } 98 | } -------------------------------------------------------------------------------- /Universe/Model/GeoCoordinates.cs: -------------------------------------------------------------------------------- 1 | // VindemiatrixCollective.Universe © 2025 Vindemiatrix Collective 2 | // Website and Documentation: https://vindemiatrixcollective.com 3 | 4 | #region 5 | 6 | using System; 7 | using System.Globalization; 8 | using System.Text.RegularExpressions; 9 | using UnityEngine.Assertions; 10 | using VindemiatrixCollective.Universe.CelestialMechanics; 11 | using VindemiatrixCollective.Universe.CelestialMechanics.Orbits; 12 | 13 | #endregion 14 | 15 | namespace VindemiatrixCollective.Universe.Model 16 | { 17 | public readonly struct GeoCoordinates : IEquatable 18 | { 19 | public static GeoCoordinates North = new(90, 0); 20 | public static GeoCoordinates South = new(-90, 0); 21 | 22 | public double Latitude { get; } 23 | 24 | public double Longitude { get; } 25 | 26 | public GeoCoordinates(double latitude, double longitude) 27 | { 28 | Assert.IsFalse(latitude is < -90 or > 90, $"{nameof(latitude)} not in range: {latitude}"); 29 | Assert.IsFalse(longitude is < -180 or > 180, $"{nameof(longitude)} not in range: {longitude}"); 30 | Latitude = latitude; 31 | Longitude = longitude; 32 | } 33 | 34 | public GeoCoordinates(string coordinates) 35 | { 36 | const string pattern = @"^(?-?\d+[.0-9]*)(?[NS]*),\s*(?-?\d+[.0-9]*)(?[EW]*)$"; 37 | Regex re = new(pattern, RegexOptions.Compiled); 38 | Match match = re.Match(coordinates); 39 | if (match.Success) 40 | { 41 | double latitude = double.Parse(match.Groups["latNum"].Value, CultureInfo.InvariantCulture); 42 | char latDir = 'N'; 43 | if (match.Groups["latDir"].Success && !string.IsNullOrEmpty(match.Groups["latDir"].Value)) 44 | { 45 | latDir = char.ToUpper(match.Groups["latDir"].Value[0]); 46 | } 47 | 48 | char lonDir = 'E'; 49 | if (match.Groups["lonDir"].Success && !string.IsNullOrEmpty(match.Groups["lonDir"].Value)) 50 | { 51 | lonDir = char.ToUpper(match.Groups["lonDir"].Value[0]); 52 | } 53 | 54 | double longitude = double.Parse(match.Groups["lonNum"].Value, CultureInfo.InvariantCulture); 55 | 56 | // Apply the sign based on direction 57 | if (char.ToUpper(latDir) == 'S' && latitude > 0) 58 | { 59 | latitude *= -1; 60 | } 61 | 62 | if (char.ToUpper(lonDir) == 'W' && longitude > 0) 63 | { 64 | longitude *= -1; 65 | } 66 | 67 | this = new GeoCoordinates(latitude, longitude); 68 | } 69 | else 70 | { 71 | throw new InvalidOperationException($"{nameof(GeoCoordinates)}: cannot parse <{coordinates}>"); 72 | } 73 | } 74 | 75 | 76 | // Equality members 77 | public bool Equals(GeoCoordinates other) 78 | { 79 | const double epsilon = 1e-5; 80 | return Math.Abs(Latitude - other.Latitude) < epsilon && Math.Abs(Longitude - other.Longitude) < epsilon; 81 | } 82 | 83 | public override bool Equals(object? obj) => obj is GeoCoordinates other && Equals(other); 84 | 85 | public override int GetHashCode() => HashCode.Combine(Latitude.GetHashCode(), Longitude.GetHashCode()); 86 | 87 | public override string ToString() 88 | { 89 | char latDir = Latitude >= 0 ? 'N' : 'S'; 90 | char lonDir = Longitude >= 0 ? 'E' : 'W'; 91 | return $"{Math.Abs(Latitude):F6}{latDir}, {Math.Abs(Longitude):F6}{lonDir}"; 92 | } 93 | 94 | public Vector3d ToCartesian(double radius) => OrbitalMechanics.GeoToCartesian(Latitude, Longitude, radius); 95 | 96 | public static GeoCoordinates East => new(0, 0); 97 | public static GeoCoordinates West => new(0, 180); 98 | 99 | 100 | public static bool operator ==(GeoCoordinates left, GeoCoordinates right) => left.Equals(right); 101 | 102 | public static bool operator !=(GeoCoordinates left, GeoCoordinates right) => !(left == right); 103 | } 104 | } -------------------------------------------------------------------------------- /Universe/ITreeNode.cs: -------------------------------------------------------------------------------- 1 | // VindemiatrixCollective.Universe © 2025 Vindemiatrix Collective 2 | // Website and Documentation: https://vindemiatrixcollective.com 3 | 4 | #region 5 | 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Text; 10 | 11 | #endregion 12 | 13 | namespace VindemiatrixCollective.Universe 14 | { 15 | public interface ITreeNode 16 | { 17 | IEnumerable Children { get; } 18 | 19 | ITreeNode this[string name] { get; } 20 | 21 | ITreeNode Parent { get; } 22 | string Name { get; } 23 | } 24 | 25 | public static class Tree 26 | { 27 | public static string RenderTree(ITreeNode root, int indentLength = 2) 28 | { 29 | string indent = string.Empty; 30 | for (int i = 0; i < indentLength; i++) 31 | { 32 | indent += " "; 33 | } 34 | 35 | StringBuilder sb = new(); 36 | 37 | PrintNode(sb, root, string.Empty, true); 38 | 39 | return sb.ToString(); 40 | 41 | void PrintNode(StringBuilder sb, ITreeNode node, string currentIndent, bool isLast) 42 | { 43 | sb.Append(currentIndent); 44 | if (isLast) 45 | { 46 | sb.Append("└── "); 47 | } 48 | else 49 | { 50 | sb.Append("├── "); 51 | } 52 | 53 | sb.AppendLine(node.Name); 54 | 55 | ITreeNode[] orbiters = node.Children.ToArray(); 56 | for (int i = 0; i < orbiters.Length; i++) 57 | { 58 | ITreeNode treeNode = orbiters[i]; 59 | bool lastChild = i == orbiters.Length - 1; 60 | PrintNode(sb, treeNode, currentIndent + (isLast ? $"{indent} " : $"│{indent} "), lastChild); 61 | } 62 | } 63 | } 64 | 65 | public static IEnumerable PreOrderVisit(ITreeNode root) 66 | { 67 | yield return root; 68 | 69 | foreach (ITreeNode orbiter in root.Children) 70 | { 71 | foreach (ITreeNode child in PreOrderVisit(orbiter)) 72 | { 73 | yield return child; 74 | } 75 | } 76 | } 77 | 78 | public static IEnumerable LevelOrderVisit(ITreeNode root) 79 | { 80 | Queue queue = new(); 81 | queue.Enqueue(root); 82 | 83 | while (queue.Count > 0) 84 | { 85 | ITreeNode body = queue.Dequeue(); 86 | yield return body; 87 | 88 | foreach (ITreeNode orbiter in body.Children) 89 | { 90 | queue.Enqueue(orbiter); 91 | } 92 | } 93 | } 94 | 95 | public static void VisitHierarchy( 96 | TOrbiter root, Action callback = null, Func> visitAlgorithm = null) 97 | where TOrbiter : ITreeNode 98 | { 99 | visitAlgorithm ??= PreOrderVisit; 100 | 101 | foreach (ITreeNode body in visitAlgorithm(root)) 102 | { 103 | if (body is TOrbiter orbiter) 104 | { 105 | callback?.Invoke(orbiter); 106 | } 107 | } 108 | } 109 | 110 | public static TOrbiter FindAncestor(ITreeNode treeNode) where TOrbiter : class, ITreeNode 111 | { 112 | foreach (ITreeNode ancestor in Ancestors(treeNode)) 113 | { 114 | if (ancestor is TOrbiter ancestorOrbiter) 115 | { 116 | return ancestorOrbiter; 117 | } 118 | } 119 | 120 | return null; 121 | } 122 | 123 | public static IEnumerable Ancestors(ITreeNode treeNode) where TOrbiter : ITreeNode 124 | { 125 | ITreeNode current = treeNode; 126 | 127 | while (current.Parent != null) 128 | { 129 | current = current.Parent; 130 | yield return (TOrbiter)current; 131 | } 132 | } 133 | } 134 | } -------------------------------------------------------------------------------- /Universe/CelestialMechanics/Orbits/OrbitalData.cs: -------------------------------------------------------------------------------- 1 | // VindemiatrixCollective.Universe © 2025 Vindemiatrix Collective 2 | // Website and Documentation: https://vindemiatrixcollective.com 3 | 4 | #region 5 | 6 | using UnitsNet; 7 | using Unity.Properties; 8 | 9 | #endregion 10 | 11 | namespace VindemiatrixCollective.Universe.CelestialMechanics.Orbits 12 | { 13 | public class OrbitalData 14 | { 15 | public bool Retrograde; 16 | 17 | private readonly double argumentPeriapsis; 18 | private readonly double axialTilt; 19 | private readonly double eccentricity; 20 | private readonly double inclination; 21 | private readonly double longitudeAscendingNode; 22 | private readonly double meanAnomalyAtEpoch; 23 | private readonly double periodS; 24 | private readonly double semiMajorAxisM; 25 | private readonly double siderealRotationPeriodS; 26 | private readonly double trueAnomalyAtEpoch; 27 | public Angle ArgumentPeriapsis => Angle.FromDegrees(argumentPeriapsis); 28 | public Angle AxialTilt => Angle.FromDegrees(axialTilt); 29 | public Angle Inclination => Angle.FromDegrees(inclination); 30 | public Angle LongitudeAscendingNode => Angle.FromDegrees(longitudeAscendingNode); 31 | public Angle MeanAnomaly => Angle.FromDegrees(meanAnomalyAtEpoch); 32 | public Angle TrueAnomaly => Angle.FromDegrees(trueAnomalyAtEpoch); 33 | 34 | [CreateProperty] public Duration Period => Duration.FromSeconds(periodS); 35 | 36 | public Duration SiderealRotationPeriod => Duration.FromSeconds(siderealRotationPeriodS); 37 | 38 | /// 39 | /// Semi-major axis of the orbit. 40 | /// 41 | [CreateProperty] 42 | public Length SemiMajorAxis => Length.FromMeters(semiMajorAxisM); 43 | 44 | public Ratio Eccentricity => Ratio.FromDecimalFractions(eccentricity); 45 | 46 | private OrbitalData( 47 | double semiMajorAxisMetres, double eccentricity, double orbitalInclination, double lan, double argp, double trueAnomaly, 48 | double? orbitalPeriodSeconds = null, double? siderealRotationSeconds = null, double? axialTilt = null, 49 | double? meanAnomaly = null) 50 | { 51 | semiMajorAxisM = semiMajorAxisMetres; 52 | this.eccentricity = eccentricity; 53 | siderealRotationPeriodS = siderealRotationSeconds ?? 0; 54 | inclination = orbitalInclination; 55 | longitudeAscendingNode = lan; 56 | argumentPeriapsis = argp; 57 | periodS = orbitalPeriodSeconds ?? 0; 58 | 59 | this.axialTilt = axialTilt ?? 0; 60 | meanAnomalyAtEpoch = meanAnomaly ?? 0; 61 | trueAnomalyAtEpoch = trueAnomaly; 62 | 63 | if (inclination > 180) 64 | { 65 | inclination -= 180; 66 | } 67 | } 68 | 69 | public OrbitalData( 70 | Length semiMajorAxis, Ratio eccentricity, Angle inclination, Angle longitudeAscendingNode, Angle argumentPeriapsis, 71 | Angle trueAnomaly, Duration? orbitalPeriod = null, Duration? siderealPeriod = null, Angle? axialTilt = null, 72 | Angle? meanAnomaly = null) : this(semiMajorAxis.Meters, eccentricity.Value, inclination.Degrees, longitudeAscendingNode.Degrees, 73 | argumentPeriapsis.Degrees, trueAnomaly.Degrees, orbitalPeriod?.Seconds, 74 | siderealPeriod?.Seconds, axialTilt?.Degrees, meanAnomaly?.Degrees) { } 75 | 76 | internal static OrbitalData Empty => new(0, 0, 0, 0, 0, 0); 77 | 78 | 79 | /// 80 | /// Creates an OrbitalData object. 81 | /// 82 | /// Semi-major axis (m) 83 | /// Eccentricity 84 | /// Inclination (deg) 85 | /// Longitude of the Ascending Node (deg) 86 | /// Argument of Periapsis (deg) 87 | /// True Anomaly (deg) 88 | /// 89 | public static OrbitalData FromClassicElements(float a, float e, float i, float lan, float argP, float nu) => 90 | new(a, e, i, lan, argP, nu, 0, 0, 91 | 0, 0); 92 | } 93 | } -------------------------------------------------------------------------------- /Universe/CelestialMechanics/Ellipse.cs: -------------------------------------------------------------------------------- 1 | // VindemiatrixCollective.Universe © 2025 Vindemiatrix Collective 2 | // Website and Documentation: https://vindemiatrixcollective.com 3 | 4 | #region 5 | 6 | using System; 7 | using UnitsNet; 8 | using UnityEngine; 9 | using VindemiatrixCollective.Universe.CelestialMechanics.Orbits; 10 | using Angle = UnitsNet.Angle; 11 | 12 | #endregion 13 | 14 | namespace VindemiatrixCollective.Universe.CelestialMechanics 15 | { 16 | public class Ellipse 17 | { 18 | public double A; 19 | public double B; 20 | public Ratio Eccentricity; 21 | public Vector3d AxisMain; 22 | public Vector3d AxisSecondary; 23 | public Vector3d Center; 24 | public Vector3d Focus0; 25 | public Vector3d Focus1; 26 | public Vector3d FocusDistance; 27 | public Vector3d Normal => Vector3d.Cross(AxisMain, AxisSecondary).normalized; 28 | 29 | public Ellipse(Vector3d focus0, Vector3d focus1, Vector3d p0) 30 | { 31 | Focus0 = focus0; 32 | Focus1 = focus1; 33 | FocusDistance = Focus0 - Focus1; 34 | A = ((Focus0 - p0).magnitude + (focus1 - p0).magnitude) * 0.5; 35 | if (A < 0) 36 | { 37 | A = -A; 38 | } 39 | 40 | Eccentricity = Ratio.FromDecimalFractions((FocusDistance.magnitude * 0.5) / A); 41 | B = A * Math.Sqrt(1 - (Eccentricity.Value * Eccentricity.Value)); 42 | AxisMain = FocusDistance.normalized; 43 | Vector3d tempNorm = Vector3d.Cross(AxisMain, p0 - Focus0).normalized; 44 | AxisSecondary = Vector3d.Cross(AxisMain, tempNorm).normalized; 45 | Center = Focus1 + (FocusDistance * 0.5); 46 | 47 | if (Vector3d.Dot(Normal, Vector3d.Z) < 0) 48 | { 49 | AxisSecondary = -AxisSecondary; 50 | } 51 | } 52 | 53 | /// 54 | /// Calculate eccentric anomaly in radians for point. 55 | /// 56 | /// Point in plane of elliptic shape. 57 | /// Eccentric anomaly radians. 58 | public Angle GetEccentricAnomalyForPoint(Vector3d point) 59 | { 60 | Vector3d vector = point - Focus0; 61 | double trueAnomaly = Vector3d.Angle(vector, AxisMain) * UniversalConstants.Tri.DegreeToRad; 62 | 63 | if (Vector3d.Dot(vector, AxisSecondary) > 0) 64 | { 65 | trueAnomaly = UniversalConstants.Tri.Pi2 - trueAnomaly; 66 | } 67 | 68 | Angle result = OrbitalMechanics.TrueToEccentricAnomaly(Angle.FromRadians(trueAnomaly), Eccentricity); 69 | return result; 70 | } 71 | 72 | public Vector3[] CalculateEllipseArcPoints( 73 | Angle eStart, Angle eEnd, Vector3d startPosition, Vector3d endPosition, int steps = 64, float scale = 1, bool ccw = true) 74 | { 75 | Vector3[] points = new Vector3[steps]; 76 | Vector3d point = startPosition; 77 | float fSteps = steps; 78 | points[0] = scale * point.ToXZY(); 79 | 80 | double delta = eEnd.Radians - eStart.Radians; 81 | 82 | if (!ccw) 83 | { 84 | delta *= -1; 85 | } 86 | 87 | 88 | for (int i = 1; i <= steps - 1; i++) 89 | { 90 | float ratio = i / fSteps; 91 | point = scale * GetSamplePoint(eStart.Radians + (delta * ratio)); 92 | points[i] = point.ToXZY(); 93 | } 94 | 95 | points[steps - 1] = scale * endPosition.ToXZY(); 96 | 97 | return points; 98 | } 99 | 100 | /// 101 | /// Get point on ellipse at specified angle from center. 102 | /// 103 | /// Angle from center in radians 104 | /// 105 | public Vector3d GetSamplePoint(double eccentricAnomaly) 106 | { 107 | return Center + (AxisMain * (A * Math.Cos(eccentricAnomaly))) + (AxisSecondary * (B * Math.Sin(eccentricAnomaly))); 108 | } 109 | 110 | public void PrintOut() 111 | { 112 | Debug.Log($"Ellipse C({Center.x:F2},{Center.y:F2},{Center.z:F2} a:{A:F2} b:{B:F2}"); 113 | } 114 | } 115 | } -------------------------------------------------------------------------------- /Universe/Model/Galaxy.cs: -------------------------------------------------------------------------------- 1 | // VindemiatrixCollective.Universe © 2025 Vindemiatrix Collective 2 | // Website and Documentation: https://vindemiatrixcollective.com 3 | 4 | #region 5 | 6 | using System; 7 | using System.Collections; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using UnityEngine.Assertions; 11 | 12 | #endregion 13 | 14 | namespace VindemiatrixCollective.Universe.Model 15 | { 16 | [Serializable] 17 | public class Galaxy : IEnumerable 18 | { 19 | private string name; 20 | 21 | public IEnumerable Systems => _Systems.Values; 22 | 23 | public int SystemCount => _Systems.Count; 24 | 25 | public StarSystem this[string name] 26 | { 27 | get => _Systems[name]; 28 | set => _Systems[name] = value; 29 | } 30 | 31 | public string Name 32 | { 33 | get => name; 34 | set => name = value; 35 | } 36 | 37 | protected Dictionary _Systems { get; private set; } 38 | 39 | public Galaxy() 40 | { 41 | name = nameof(Galaxy); 42 | _Systems = new Dictionary(); 43 | } 44 | 45 | public Galaxy(string name) : this() 46 | { 47 | this.name = name; 48 | } 49 | 50 | public Galaxy(string name, IEnumerable stars) : this(name) 51 | { 52 | _Systems = new Dictionary(); 53 | 54 | foreach (Star star in stars) 55 | { 56 | if (!_Systems.ContainsKey(star.Name)) 57 | { 58 | _Systems.Add(star.Name, new StarSystem(star.Name)); 59 | } 60 | 61 | this[star.Name].AddOrbiter(star); 62 | } 63 | } 64 | 65 | public bool ContainsSystem(string name) => _Systems.ContainsKey(name); 66 | 67 | /// 68 | /// Traverses the Galaxy structure to retrieve the chosen body (Star or Planet/Satellite). 69 | /// 70 | /// 71 | /// Must be in the format "SystemName/StarName*/PlanetName*/SatelliteName*", e.g.: "Sol/Sun/Earth/Moon". 72 | /// Asterisks indicate optional parts. Only the SystemName is required. If no StarName is specified, 73 | /// it will return the primary object. 74 | /// 75 | /// The specified body 76 | /// 77 | public CelestialBody GetBody(string path) 78 | { 79 | Assert.IsFalse(string.IsNullOrEmpty(path), $"Path cannot be null or empty: {nameof(path)}"); 80 | string[] parts = path.Split('/', StringSplitOptions.RemoveEmptyEntries); 81 | Assert.IsTrue(parts.Length > 1, $"Invalid path format: [{path}]"); 82 | 83 | if (!_Systems.TryGetValue(parts[0], out StarSystem system)) 84 | { 85 | throw new InvalidOperationException($"System {parts[0]} not found: [{path}]"); 86 | } 87 | 88 | if (parts.Length == 1) 89 | { 90 | return system.Primary; 91 | } 92 | 93 | CelestialBody current = system[parts[1]]; 94 | for (int i = 2; i < parts.Length; i++) 95 | { 96 | current = current[parts[i]]; 97 | if (current == null) 98 | { 99 | throw new InvalidOperationException($"{parts[i]} not found: [{path}]"); 100 | } 101 | } 102 | 103 | return current; 104 | } 105 | 106 | public IEnumerator GetEnumerator() => 107 | _Systems?.Values.GetEnumerator() ?? Enumerable.Empty().GetEnumerator(); 108 | 109 | public TBody GetBody(string path) where TBody : CelestialBody 110 | { 111 | TBody body = (TBody)GetBody(path); 112 | return body; 113 | } 114 | 115 | public void AddSystem(StarSystem system) 116 | { 117 | system.Galaxy = this; 118 | system.Index = _Systems.Count; 119 | _Systems.Add(system.Name, system); 120 | } 121 | 122 | public void AddSystems(IEnumerable systems) 123 | { 124 | foreach (StarSystem system in systems) 125 | { 126 | AddSystem(system); 127 | } 128 | } 129 | 130 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 131 | } 132 | } -------------------------------------------------------------------------------- /Universe/Data/ObjectBuilder.cs: -------------------------------------------------------------------------------- 1 | // VindemiatrixCollective.Universe.Data © 2025 Vindemiatrix Collective 2 | // Website and Documentation: https://vindemiatrixcollective.com 3 | 4 | #region 5 | 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Reflection; 9 | using System.Text.Json; 10 | using UnityEngine.Assertions; 11 | 12 | #endregion 13 | 14 | namespace VindemiatrixCollective.Universe.Data 15 | { 16 | public class ObjectBuilder : IConverterImplementation where TState : class, new() 17 | { 18 | private readonly Dictionary> propertyReaders = new(); 19 | 20 | private readonly List optionalProperties = new(); 21 | private Func create; 22 | 23 | public Type Type => typeof(T); 24 | 25 | public bool ReadProperty(string propertyName, ref Utf8JsonReader reader, JsonSerializerOptions options, TState state) 26 | { 27 | if (!propertyReaders.TryGetValue(propertyName, out PropertySetter propertyReader)) 28 | { 29 | return optionalProperties.Contains(propertyName); 30 | } 31 | 32 | try 33 | { 34 | propertyReader(ref reader, options, state); 35 | } 36 | catch (JsonException ex) 37 | { 38 | throw new JsonException($"Error while reading property [{Type.Name}.{propertyName}]", ex); 39 | } 40 | 41 | return true; 42 | } 43 | 44 | public bool Validate(TState state, out IEnumerable missingProperties) 45 | { 46 | FieldInfo[] fields = state.GetType().GetFields(); 47 | List missing = new(); 48 | bool result = true; 49 | foreach (FieldInfo field in fields) 50 | { 51 | if (optionalProperties.Contains(field.Name)) 52 | continue; 53 | 54 | object value = field.GetValue(state); 55 | 56 | if (value == null || string.IsNullOrEmpty(value.ToString())) 57 | { 58 | result = false; 59 | missing.Add(field.Name); 60 | } 61 | } 62 | 63 | missingProperties = missing; 64 | return result; 65 | } 66 | 67 | public T Create(TState state) => create(state); 68 | 69 | public class Builder 70 | { 71 | private readonly ObjectBuilder objectBuilder = new(); 72 | 73 | public ObjectBuilder Build() 74 | { 75 | Assert.IsNotNull(objectBuilder.create, nameof(objectBuilder.create)); 76 | return objectBuilder; 77 | } 78 | 79 | public Builder SetCreate(Func creator) 80 | { 81 | objectBuilder.create = creator; 82 | return this; 83 | } 84 | 85 | public Builder SetProperty(string propertyName, PropertySetter propertyReader, bool optional) 86 | { 87 | objectBuilder.propertyReaders[propertyName] = propertyReader; 88 | if (optional) 89 | { 90 | objectBuilder.optionalProperties.Add(propertyName); 91 | } 92 | 93 | return this; 94 | } 95 | 96 | public Builder SetProperty( 97 | string propertyName, PropertyReader getter, Action setter, bool optional = false, 98 | string alternativeName = null) 99 | { 100 | SetProperty(propertyName, PropertySetter, optional); 101 | 102 | if (!string.IsNullOrEmpty(alternativeName)) 103 | { 104 | SetProperty(alternativeName, PropertySetter, optional); 105 | } 106 | 107 | return this; 108 | 109 | void PropertySetter(ref Utf8JsonReader reader, JsonSerializerOptions options, TState state) 110 | { 111 | TProperty value = getter(ref reader, options); 112 | setter(state, value); 113 | } 114 | } 115 | 116 | public Builder SkipProperty(string propertyName) 117 | { 118 | objectBuilder.propertyReaders[propertyName] = (ref Utf8JsonReader reader, JsonSerializerOptions options, TState state) => 119 | reader.Skip(); 120 | return this; 121 | } 122 | } 123 | } 124 | } -------------------------------------------------------------------------------- /Universe/Tests/Common.cs: -------------------------------------------------------------------------------- 1 | // VindemiatrixCollective.Universe.Tests © 2025 Vindemiatrix Collective 2 | // Website and Documentation: https://vindemiatrixcollective.com 3 | 4 | #region 5 | 6 | using System; 7 | using System.Diagnostics; 8 | using NUnit.Framework; 9 | using UnitsNet; 10 | using VindemiatrixCollective.Universe.CelestialMechanics; 11 | using VindemiatrixCollective.Universe.CelestialMechanics.Orbits; 12 | using VindemiatrixCollective.Universe.Model; 13 | using Debug = UnityEngine.Debug; 14 | 15 | #endregion 16 | 17 | namespace VindemiatrixCollective.Universe.Tests 18 | { 19 | public static class Common 20 | { 21 | public static Galaxy Galaxy; 22 | public static readonly DateTime J2000 = new(2000, 1, 1, 11, 58, 55, 816, DateTimeKind.Utc); 23 | public static Stopwatch timer = new(); 24 | 25 | internal static Planet Earth 26 | { 27 | get 28 | { 29 | Planet earth = Planet.Earth; 30 | earth.SetParentBody(Sun); 31 | return earth; 32 | } 33 | } 34 | 35 | internal static Planet Io 36 | { 37 | get 38 | { 39 | OrbitalData orbital = new(Length.FromKilometers(421700), Ratio.FromDecimalFractions(0.0041), Angle.FromDegrees(0.0375), 40 | Angle.FromDegrees(241.1210503807339), Angle.FromDegrees(127.39925384521484), 41 | Angle.FromDegrees(13.08436484643558f), Duration.FromSeconds(152853.5047), 42 | Duration.FromDays(1.77f), Angle.Zero, Angle.FromDegrees(33.54986953430662)); 43 | 44 | PhysicalData physical = new(Density.FromGramsPerCubicCentimeter(3.528), Length.FromKilometers(1821.49), 45 | GravitationalParameter.FromMass(Mass.FromKilograms(8.931938e22))); 46 | return new Planet("Io", physical, orbital); 47 | } 48 | } 49 | 50 | internal static Planet Mars 51 | { 52 | get 53 | { 54 | Planet mars = Planet.Mars; 55 | mars.SetParentBody(Sun); 56 | return mars; 57 | } 58 | } 59 | 60 | internal static OrbitalData MarsElements => Planet.Mars.OrbitalData; 61 | 62 | internal static Star Proxima 63 | { 64 | get 65 | { 66 | Luminosity luminosity = Luminosity.FromSolarLuminosities(0.0017); 67 | Mass mass = Mass.FromSolarMasses(0.12); 68 | Length radius = Length.FromSolarRadiuses(0.1542); 69 | Temperature temperature = Temperature.FromKelvins(3306); 70 | Acceleration gravity = Acceleration.FromMetersPerSecondSquared(112000); 71 | Duration age = Duration.FromYears365(4.85 * 1E9); 72 | 73 | Length sma = Length.FromAstronomicalUnits(14666.424758); 74 | Duration period = Duration.FromYears365(547000.0); 75 | Ratio eccentricity = Ratio.FromDecimalFractions(0.5); 76 | Angle inclination = Angle.FromDegrees(107.6); 77 | Angle lan = Angle.FromDegrees(126.0); 78 | Angle argp = Angle.FromDegrees(72.3); 79 | 80 | OrbitalData orbitalData = new(sma, eccentricity, inclination, lan, argp, Angle.Zero, period); 81 | Star proxima = new("Proxima", new StellarData(luminosity, mass, radius, gravity, temperature, age: age)); 82 | proxima.OrbitalData = orbitalData; 83 | return proxima; 84 | } 85 | } 86 | 87 | internal static Star Sun => Star.Sun; 88 | 89 | public static void ArrayAreEqual(double[] expected, double[] actual, double tolerance, string name) 90 | { 91 | for (int i = 0; i < expected.Length; i++) 92 | { 93 | Assert.AreEqual(expected[i], actual[i], tolerance, name); 94 | } 95 | } 96 | 97 | public static void CompareVector3d(Vector3d expected, Vector3d actual, double tolerance, string name) 98 | { 99 | ArrayAreEqual(expected.ToArray(), actual.ToArray(), tolerance, name); 100 | } 101 | 102 | public static void LoadData() 103 | { 104 | Debug.Log($"Loaded {Galaxy.SystemCount} systems"); 105 | } 106 | 107 | public static void VectorsAreEqual(Vector3d expected, Vector3d actual, double tolerance, string name = null) 108 | { 109 | Assert.AreEqual(expected.x, actual.x, tolerance, $"{name}.x" ?? string.Empty); 110 | Assert.AreEqual(expected.y, actual.y, tolerance, $"{name}.y" ?? string.Empty); 111 | Assert.AreEqual(expected.z, actual.z, tolerance, $"{name}.z" ?? string.Empty); 112 | } 113 | } 114 | } -------------------------------------------------------------------------------- /Universe/Model/Star.cs: -------------------------------------------------------------------------------- 1 | // VindemiatrixCollective.Universe © 2025 Vindemiatrix Collective 2 | // Website and Documentation: https://vindemiatrixcollective.com 3 | 4 | #region 5 | 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Diagnostics; 9 | using System.Linq; 10 | using UnitsNet; 11 | using UnitsNet.Units; 12 | using Unity.Properties; 13 | 14 | #endregion 15 | 16 | namespace VindemiatrixCollective.Universe.Model 17 | { 18 | [Serializable] 19 | [DebuggerDisplay("{Name}")] 20 | public class Star : CelestialBody 21 | { 22 | public bool HasPlanets => Planets.Any(); 23 | public float Distance => (float)StarSystem.DistanceFromSol.LightYears; 24 | 25 | 26 | /// 27 | /// Returns a sequence of Planet objects ordered by distance from this star. 28 | /// 29 | public IEnumerable Planets => _Orbiters.Values.OfType().OrderBy(o => o.OrbitalData.SemiMajorAxis); 30 | 31 | public int PlanetCount => Planets.Count(); 32 | 33 | public override PhysicalData PhysicalData 34 | { 35 | get => StellarData; 36 | set => StellarData = (StellarData)value; 37 | } 38 | 39 | [CreateProperty] public SpectralClass SpectralClass { get; set; } 40 | 41 | [CreateProperty] public StellarData StellarData { get; set; } 42 | public override string FullName => Name.Length > 2 ? Name : $"{StarSystem.Name} {Name}"; 43 | public Star() : base(nameof(Star), CelestialBodyType.Star) { } 44 | 45 | public Star(string name) : base(name, CelestialBodyType.Star) { } 46 | 47 | public Star(string name, StellarData data) : this(name) 48 | { 49 | StellarData = data; 50 | #if UNITY_EDITOR 51 | CopyPhysicalValues(); 52 | #endif 53 | } 54 | 55 | public Mass CalculatePlanetaryMass() 56 | { 57 | Mass sum = Mass.Zero; 58 | foreach (CelestialBody orbiter in _Orbiters.Values) 59 | { 60 | sum += orbiter.PhysicalData.Mass; 61 | } 62 | 63 | return sum; 64 | } 65 | 66 | public Planet GetPlanet(string key) 67 | { 68 | return Planets.FirstOrDefault(p => p.Name == key); 69 | } 70 | 71 | public Star Clone() 72 | { 73 | Star newStar = new(Name, StellarData) { SpectralClass = SpectralClass }; 74 | if (OrbitalData != null) 75 | { 76 | newStar.OrbitalData = OrbitalData; 77 | } 78 | 79 | return newStar; 80 | } 81 | 82 | /// 83 | /// Creates a new star with the physical characteristics of the Sun. 84 | /// 85 | public static Star Sun 86 | { 87 | get 88 | { 89 | Luminosity luminosity = Luminosity.FromSolarLuminosities(1); 90 | Mass mass = Mass.FromSolarMasses(1); 91 | Length radius = Length.FromSolarRadiuses(1); 92 | Density density = 93 | Density.FromGramsPerCubicCentimeter(3 * mass.Grams / (4 * UniversalConstants.Tri.Pi * Math.Pow(radius.Centimeters, 3))); 94 | Temperature temperature = Temperature.FromKelvins(5770); 95 | Acceleration gravity = 96 | Acceleration.FromMetersPerSecondSquared(UniversalConstants.Celestial.GravitationalConstant * mass.Kilograms 97 | / Math.Pow(radius.Meters, 2)); 98 | Duration age = Duration.FromYears365(4.6 * 1E9); 99 | 100 | return new Star("Sun", new StellarData(luminosity, mass, radius, gravity, temperature, density, age)); 101 | } 102 | } 103 | 104 | #region UnityEditor 105 | 106 | #if UNITY_EDITOR 107 | 108 | [Serializable] 109 | public struct StellarDataValues 110 | { 111 | public string Age; 112 | public string Luminosity; 113 | public string Mass; 114 | public string Radius; 115 | public string Temperature; 116 | } 117 | 118 | public StellarDataValues stellarDataValues; 119 | 120 | private void CopyPhysicalValues() 121 | { 122 | string massUnit = Mass.GetAbbreviation(MassUnit.SolarMass); 123 | string radiusUnit = Length.GetAbbreviation(LengthUnit.SolarRadius); 124 | string lumUnit = Luminosity.GetAbbreviation(LuminosityUnit.SolarLuminosity); 125 | 126 | stellarDataValues.Mass = StellarData.Mass.EarthMasses.ToString($"0.00 {massUnit}"); 127 | stellarDataValues.Radius = StellarData.Radius.SolarRadiuses.ToString($"0.00 {radiusUnit}"); 128 | stellarDataValues.Temperature = StellarData.Temperature.ToString("0.00 K"); 129 | stellarDataValues.Luminosity = StellarData.Luminosity.SolarLuminosities.ToString($"0.00 {lumUnit}"); 130 | stellarDataValues.Age = (StellarData.Age.Years365 / 1E9).ToString("0.00 Gy"); 131 | } 132 | 133 | #endif 134 | 135 | #endregion 136 | } 137 | } -------------------------------------------------------------------------------- /Universe/Tests/PhysicalDataTests.cs: -------------------------------------------------------------------------------- 1 | // VindemiatrixCollective.Universe.Tests © 2025 Vindemiatrix Collective 2 | // Website and Documentation: https://vindemiatrixcollective.com 3 | 4 | #region 5 | 6 | using NUnit.Framework; 7 | using UnitsNet; 8 | using VindemiatrixCollective.Universe.Data; 9 | using VindemiatrixCollective.Universe.Model; 10 | 11 | #endregion 12 | 13 | namespace VindemiatrixCollective.Universe.Tests 14 | { 15 | public class PhysicalDataTests 16 | { 17 | private readonly DeserializationHelper dataHelper = new(); 18 | 19 | [TestCase(@"{ 20 | ""Mass"": 3.302e+23, 21 | ""Density"": 5.427, 22 | ""Radius"": 2440.53, 23 | ""Gravity"": 3.701, 24 | ""Temperature"": 440.0, 25 | ""HillSphereRadius"": 94.4, 26 | ""GravitationalParameter"": 22031.86855 27 | }", 1e-6, 3.302e+23, 5.427, 2440.53, 3.701, 440.0, 94.4, 22031.86855)] 28 | [TestCase(@"{ 29 | ""Mass"": 3.302e+23, 30 | ""Radius"": 2440.53, 31 | ""Gravity"": 3.701 32 | }", 1e-6, 3.302e+23, 0, 2440.53, 3.701, 0, 0, 0)] 33 | [TestCase(@"{ 34 | ""Density"": 5.427, 35 | ""Radius"": 2440.53, 36 | ""GravitationalParameter"": 22031.86855 37 | }", 1e-2, 3.202e+23, 5.427, 2440.53, 3.701, 0, 0, 0)] 38 | [TestCase(@"{ 39 | ""Mass"": {""v"": 3.302e+23, ""u"": ""kg""}, 40 | ""Density"": {""v"": 5.427, ""u"": ""g/cm3""}, 41 | ""Radius"": {""v"": 2440.53, ""u"": ""km""}, 42 | ""Gravity"":{""v"": 3.701, ""u"": ""m/s2""}, 43 | ""Temperature"": {""v"": 440.0, ""u"": ""K""}, 44 | ""HillSphereRadius"": 94.4, 45 | ""GravitationalParameter"": 22031.86855 46 | }", 1e-6, 3.302e+23, 5.427, 2440.53, 3.701, 440.0, 94.4, 22031.86855)] 47 | public void DeserializeCompleteData( 48 | string input, double tol, double mass, double density, double radius, double g, double temp, double hsr, double gm) 49 | { 50 | PhysicalData physicalData = dataHelper.DeserializeObject(input, new PhysicalDataConverter()); 51 | PhysicalData expected = new(Mass.FromKilograms(mass), Length.FromKilometers(radius), Acceleration.FromMetersPerSecondSquared(g), 52 | Density.FromGramsPerCubicCentimeter(density), Temperature.FromKelvins(temp)); 53 | ComparePhysicalData(expected, physicalData, tol); 54 | } 55 | 56 | [TestCase(@"{ 57 | ""Mass"": 1.0, 58 | ""Luminosity"": 1.0, 59 | ""Age"": 4.6, 60 | ""Temperature"": 5770.0, 61 | ""Radius"": 1.0 62 | }", 1e-6, 1, 1, 4.6, 5770, 1.0, 0)] 63 | [TestCase(@"{ 64 | ""l"": 1.519, 65 | ""m"": 1.1, 66 | ""t"": 5271.0, 67 | ""g"": 10620.0 68 | }", 1e-6, 1.1, 1.519, 0.0, 5271, 0.0, 10620)] 69 | [TestCase(@"{ 70 | ""l"": { ""v"": 1.519, ""u"": ""sl"" }, 71 | ""m"": { ""v"": 1.1, ""u"": ""sm"" }, 72 | ""t"": { ""v"": 5271.0, ""u"": ""K"" }, 73 | ""g"": { ""v"": 10620.0, ""u"": ""m/s2"" } 74 | }", 1e-6, 1.1, 1.519, 0.0, 5271, 0.0, 10620)] 75 | public void DeserializeCompleteStellarData( 76 | string input, double tol, double mass, double lum, double age, double temp, double radius, double gravity) 77 | { 78 | StellarData stellarData = dataHelper.DeserializeObject(input, new StellarDataConverter()); 79 | StellarData expected = new(Luminosity.FromSolarLuminosities(lum), Mass.FromSolarMasses(mass), Length.FromSolarRadiuses(radius), 80 | Acceleration.FromMetersPerSecondSquared(gravity), Temperature.FromKelvins(temp), Density.Zero, 81 | Duration.FromYears365(age * 1E9)); 82 | CompareStellarData(expected, stellarData, tol); 83 | } 84 | 85 | public static void CompareStellarData(StellarData expected, StellarData actual, double tol) 86 | { 87 | Assert.IsNotNull(actual, nameof(StellarData)); 88 | Assert.AreEqual(expected.Luminosity.SolarLuminosities, actual.Luminosity.SolarLuminosities, tol, nameof(Luminosity)); 89 | Assert.AreEqual(expected.Mass.SolarMasses, actual.Mass.SolarMasses, tol, nameof(Mass)); 90 | Assert.AreEqual(expected.Temperature.Kelvins, actual.Temperature.Kelvins, tol, nameof(Temperature)); 91 | Assert.AreEqual(expected.Age.Years365, actual.Age.Years365, tol, nameof(StellarData.Age)); 92 | 93 | if (expected.Radius.Value > 0) 94 | { 95 | Assert.AreEqual(expected.Radius.SolarRadiuses, actual.Radius.SolarRadiuses, tol, nameof(PhysicalData.Radius)); 96 | } 97 | 98 | if (expected.Gravity.Value > 0) 99 | { 100 | Assert.AreEqual(expected.Gravity.StandardGravity, actual.Gravity.StandardGravity, 0.05, nameof(StellarData.Gravity)); 101 | } 102 | } 103 | 104 | public static void ComparePhysicalData(PhysicalData expected, PhysicalData actual, double tol) 105 | { 106 | Assert.IsNotNull(actual, nameof(PhysicalData)); 107 | Assert.IsNotNull(expected, nameof(PhysicalData)); 108 | Assert.AreEqual(expected.Mass.EarthMasses, actual.Mass.EarthMasses, tol, nameof(Mass)); 109 | Assert.AreEqual(expected.Density.GramsPerCubicCentimeter, actual.Density.GramsPerCubicCentimeter, 1e-3, nameof(Density)); 110 | Assert.AreEqual(expected.Radius.Kilometers, actual.Radius.Kilometers, tol, nameof(PhysicalData.Radius)); 111 | Assert.AreEqual(expected.Gravity.MetersPerSecondSquared, actual.Gravity.MetersPerSecondSquared, tol, 112 | nameof(PhysicalData.Gravity)); 113 | Assert.AreEqual(expected.Temperature.Kelvins, actual.Temperature.Kelvins, tol, nameof(Temperature)); 114 | } 115 | } 116 | } -------------------------------------------------------------------------------- /Universe/Model/StarSystem.cs: -------------------------------------------------------------------------------- 1 | // VindemiatrixCollective.Universe © 2025 Vindemiatrix Collective 2 | // Website and Documentation: https://vindemiatrixcollective.com 3 | 4 | #region 5 | 6 | using System; 7 | using System.Collections; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using UnitsNet; 11 | using Unity.Properties; 12 | using UnityEngine; 13 | 14 | #endregion 15 | 16 | namespace VindemiatrixCollective.Universe.Model 17 | { 18 | public class StarSystem : IEnumerable 19 | { 20 | private Barycentre barycentre; 21 | public Barycentre Barycentre => barycentre ??= new Barycentre(this); 22 | public CelestialBody this[string name] => _Orbiters[name]; 23 | public Galaxy Galaxy { get; internal set; } 24 | 25 | public IEnumerable Hierarchy 26 | { 27 | get 28 | { 29 | foreach (CelestialBody body in _Orbiters.Values) 30 | { 31 | foreach (ITreeNode orbiter in Tree.PreOrderVisit(body)) 32 | { 33 | yield return (CelestialBody)orbiter; 34 | } 35 | } 36 | } 37 | } 38 | 39 | public IEnumerable Orbiters => _Orbiters.Values; 40 | 41 | public IEnumerable Stars => _Orbiters.Values.OfType().OrderByDescending(o => o.PhysicalData.Mass); 42 | 43 | public int Index { get; internal set; } 44 | 45 | public int StarCount => Stars.Count(); 46 | 47 | [CreateProperty] public Length DistanceFromSol => Length.FromParsecs(Coordinates.magnitude); 48 | 49 | public Mass Mass => Mass.FromSolarMasses(Stars.Sum(star => star.CalculatePlanetaryMass().SolarMasses)); 50 | 51 | public Star this[int index] => Stars.ElementAt(index); 52 | 53 | public Star Primary => Stars.FirstOrDefault(); 54 | 55 | public string Id { get; set; } 56 | 57 | [CreateProperty] public string Name { get; set; } 58 | 59 | public Vector3 Coordinates { get; set; } 60 | 61 | protected Dictionary _Orbiters { get; } 62 | 63 | public StarSystem() 64 | { 65 | _Orbiters = new Dictionary(); 66 | } 67 | 68 | public StarSystem(string name) : this() 69 | { 70 | Name = name; 71 | Id = MakeId(name); 72 | } 73 | 74 | public StarSystem(string name, Star primary) : this(name, new[] { primary }) { } 75 | 76 | public StarSystem(string name, IEnumerable orbiters) : this(name) 77 | { 78 | foreach (CelestialBody body in orbiters) 79 | { 80 | AddOrbiter(body); 81 | } 82 | } 83 | 84 | public bool ContainsStar(string starName) => _Orbiters.ContainsKey(starName); 85 | 86 | public CelestialBody[] ToArray() 87 | { 88 | return _Orbiters.Values.OrderByDescending(star => star.PhysicalData.Mass.SolarMasses).ToArray(); 89 | } 90 | 91 | public IEnumerator GetEnumerator() => 92 | _Orbiters?.Values.GetEnumerator() ?? Enumerable.Empty().GetEnumerator(); 93 | 94 | public Length DistanceFrom(StarSystem system) => Length.FromParsecs((Coordinates - system.Coordinates).magnitude); 95 | 96 | public string SystemTree() 97 | { 98 | string tree = "*\n"; 99 | foreach (CelestialBody body in Orbiters) 100 | { 101 | tree += Tree.RenderTree(body); 102 | } 103 | 104 | return tree; 105 | } 106 | 107 | public void AddOrbiter(CelestialBody body) 108 | { 109 | body.StarSystem = this; 110 | body.Index = _Orbiters.Count; 111 | _Orbiters.Add(body.Name, body); 112 | 113 | foreach (CelestialBody orbiter in Hierarchy) 114 | { 115 | orbiter.StarSystem = this; 116 | } 117 | } 118 | 119 | public void SetBarycentre(Barycentre barycentre) 120 | { 121 | this.barycentre = barycentre; 122 | } 123 | 124 | public void VisitHierarchy(Action callback, Func> visitAlgorithm = null) 125 | where TBody : ITreeNode 126 | { 127 | visitAlgorithm ??= Tree.PreOrderVisit; 128 | 129 | foreach (ITreeNode body in _Orbiters.Values) 130 | { 131 | foreach (ITreeNode orbiter in visitAlgorithm(body)) 132 | { 133 | if (orbiter is TBody tBody) 134 | { 135 | callback.Invoke(tBody); 136 | } 137 | } 138 | } 139 | } 140 | 141 | IEnumerator IEnumerable.GetEnumerator() => _Orbiters?.Values.GetEnumerator() ?? Enumerable.Empty().GetEnumerator(); 142 | 143 | 144 | /// 145 | /// Returns a basic model containing the Sun, Earth, the Moon, and Mars. 146 | /// 147 | public static StarSystem Sol 148 | { 149 | get 150 | { 151 | Star sun = Star.Sun; 152 | Planet earth = Planet.Earth; 153 | Planet moon = Planet.Moon; 154 | Planet mars = Planet.Mars; 155 | 156 | sun.AddOrbiter(earth); 157 | sun.AddOrbiter(mars); 158 | earth.AddOrbiter(moon); 159 | 160 | StarSystem sol = new("Sol", sun); 161 | return sol; 162 | } 163 | } 164 | 165 | public static string MakeId(string name) 166 | { 167 | string[] words = name.Split(' '); 168 | string id = string.Empty; 169 | const int n = 3; 170 | 171 | foreach (string word in words) 172 | { 173 | id += word.Length <= n ? word : word[..n]; 174 | if (id.Length >= 6) 175 | { 176 | id = id[..6]; 177 | break; 178 | } 179 | } 180 | 181 | return id; 182 | } 183 | } 184 | } -------------------------------------------------------------------------------- /Universe/Model/Planet.cs: -------------------------------------------------------------------------------- 1 | // VindemiatrixCollective.Universe © 2025 Vindemiatrix Collective 2 | // Website and Documentation: https://vindemiatrixcollective.com 3 | 4 | #region 5 | 6 | using System; 7 | using System.Collections; 8 | using System.Collections.Generic; 9 | using System.Diagnostics; 10 | using System.Linq; 11 | using UnitsNet; 12 | using UnitsNet.Units; 13 | using VindemiatrixCollective.Universe.CelestialMechanics.Orbits; 14 | 15 | #endregion 16 | 17 | namespace VindemiatrixCollective.Universe.Model 18 | { 19 | [DebuggerDisplay("Planet {Name}")] 20 | public class Planet : CelestialBody, IEnumerable 21 | { 22 | public bool HasRings => !string.IsNullOrEmpty(Attributes.TryGet("Rings")); 23 | 24 | public bool IsSatellite => ParentBody is { Type: CelestialBodyType.Planet }; 25 | 26 | /// 27 | /// Returns a sequence of Planet (satellite) objects order by distance from this Planet. 28 | /// 29 | public IEnumerable Satellites => _Orbiters.Values.OfType().OrderBy(o => o.OrbitalData.SemiMajorAxis); 30 | 31 | public int SatelliteCount => Satellites.Count(); 32 | 33 | public Planet ClosestMoon => 34 | Satellites.Aggregate((m1, m2) => m2.OrbitalData.SemiMajorAxis < m1.OrbitalData.SemiMajorAxis ? m2 : m1); 35 | 36 | public Planet FarthestMoon => 37 | Satellites.Aggregate((m1, m2) => m2.OrbitalData.SemiMajorAxis > m1.OrbitalData.SemiMajorAxis ? m2 : m1); 38 | 39 | public override string FullName => $"{Name}"; 40 | 41 | 42 | public Planet() : base(nameof(Planet), CelestialBodyType.Planet) { } 43 | 44 | public Planet(string name) : base(name, CelestialBodyType.Planet) { } 45 | 46 | public Planet(string name, PhysicalData physical, OrbitalData orbital) : this(name) 47 | { 48 | PhysicalData = physical; 49 | OrbitalData = orbital; 50 | #if UNITY_EDITOR 51 | CopyPhysicalValues(); 52 | ; 53 | #endif 54 | } 55 | 56 | public new IEnumerator GetEnumerator() => 57 | Satellites?.GetEnumerator() ?? Enumerable.Empty().GetEnumerator(); 58 | 59 | public Planet Clone() 60 | { 61 | Planet newPlanet = new(Name, PhysicalData, OrbitalData); 62 | return newPlanet; 63 | } 64 | 65 | public Planet GetSatellite(string key) 66 | { 67 | return Satellites.FirstOrDefault(p => p.Name == key); 68 | } 69 | 70 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 71 | 72 | /// 73 | /// Creates a new planet with the physical and orbital characteristics of the Earth at J2000. 74 | /// It returns the planet only, it must then be parented to an attractor, like the Sun. 75 | /// 76 | public static Planet Earth 77 | { 78 | get 79 | { 80 | OrbitalData orbital = new(Length.FromAstronomicalUnits(1.000448828934185), Ratio.FromDecimalFractions(0.01711862906746885), 81 | Angle.FromDegrees(7.251513445651153), Angle.FromDegrees(241.097743921078), 82 | Angle.FromDegrees(206.0459434316863), Angle.FromDegrees(358.5688856532555), 83 | Duration.FromDays(365.5022838235192), Duration.FromHours(23.9344695944), 84 | Angle.FromDegrees(23.4392911), Angle.FromDegrees(358.6172562416435)); 85 | PhysicalData physical = new(Mass.FromEarthMasses(1), Length.FromKilometers(UniversalConstants.Physical.EarthRadiusKm), 86 | Acceleration.FromStandardGravity(1), Density.FromGramsPerCubicCentimeter(5.51), 87 | Temperature.FromKelvins(287.6)); 88 | 89 | return new Planet(nameof(Earth), physical, orbital); 90 | } 91 | } 92 | 93 | public static Planet Luna 94 | { 95 | get 96 | { 97 | OrbitalData orbital = new(Length.FromKilometers(384400.0), Ratio.FromDecimalFractions(0.0549), Angle.FromDegrees(5.145), 98 | Angle.FromDegrees(241.2713606974586), Angle.FromDegrees(328.980187284116), 99 | Angle.FromDegrees(235.5936224066131), Duration.FromDays(27.321582), Duration.FromDays(27.321661), 100 | Angle.FromDegrees(6.67), Angle.FromDegrees(238.5779166596645)); 101 | PhysicalData physical = new(Mass.FromKilograms(7.349e22), Length.FromKilometers(1738.0), 102 | Acceleration.FromMetersPerSecondSquared(1.62), Density.FromGramsPerCubicCentimeter(3.3437)); 103 | 104 | return new Planet(nameof(Luna), physical, orbital); 105 | } 106 | } 107 | 108 | /// 109 | /// Creates a new planet with the physical and orbital characteristics of the Earth at J2000. 110 | /// It returns the planet only, it must then be parented to an attractor, like the Sun. 111 | /// 112 | public static Planet Mars 113 | { 114 | get 115 | { 116 | OrbitalData orbital = new(Length.FromAstronomicalUnits(1.523679), Ratio.FromDecimalFractions(0.093315f), 117 | Angle.FromDegrees(5.65099), Angle.FromDegrees(249.4238472638976), 118 | Angle.FromDegrees(72.062034606933594d), Angle.FromDegrees(23.33319), 119 | Duration.FromSeconds(5.935431800266414e07), Duration.FromHours(24.622962), 120 | Angle.FromDegrees(25.19), Angle.FromDegrees(19.35648274725784)); 121 | PhysicalData physical = new(Mass.FromKilograms(0.64169e24), Length.FromKilometers(3396.19), 122 | Acceleration.FromMetersPerSecondSquared(3.71), Density.FromGramsPerCubicCentimeter(3.9335), 123 | Temperature.FromKelvins(210)); 124 | 125 | return new Planet(nameof(Mars), physical, orbital); 126 | } 127 | } 128 | 129 | public static Planet Moon 130 | { 131 | get 132 | { 133 | Planet moon = Luna; 134 | moon.Name = "Moon"; 135 | return moon; 136 | } 137 | } 138 | 139 | #if UNITY_EDITOR 140 | [Serializable] 141 | public struct PhysicalDataValues 142 | { 143 | public string Mass; 144 | public string Radius; 145 | public string Temperature; 146 | } 147 | 148 | public PhysicalDataValues physicalDataValues; 149 | 150 | private void CopyPhysicalValues() 151 | { 152 | string massUnit = Mass.GetAbbreviation(Type == CelestialBodyType.Planet ? MassUnit.EarthMass : MassUnit.SolarMass); 153 | physicalDataValues.Mass = PhysicalData.Mass.EarthMasses.ToString($"0.00 {massUnit}"); 154 | physicalDataValues.Radius = 155 | (PhysicalData.Radius.Kilometers / UniversalConstants.Physical.EarthRadiusKm).ToString("0.00 R\ud83d\udf28"); 156 | physicalDataValues.Temperature = PhysicalData.Temperature.Kelvins.ToString("0.00 K"); 157 | } 158 | #endif 159 | } 160 | } -------------------------------------------------------------------------------- /Universe/Model/SpectralClass.cs: -------------------------------------------------------------------------------- 1 | // VindemiatrixCollective.Universe © 2025 Vindemiatrix Collective 2 | // Website and Documentation: https://vindemiatrixcollective.com 3 | 4 | #region 5 | 6 | using System; 7 | using System.Text.RegularExpressions; 8 | using Unity.Properties; 9 | using UnityEngine; 10 | 11 | #endregion 12 | 13 | namespace VindemiatrixCollective.Universe.Model 14 | { 15 | public enum StarClass 16 | { 17 | Undefined, 18 | WhiteDwarf, 19 | SubDwarf, 20 | Dwarf, 21 | SubGiant, 22 | Giant, 23 | BrightGiant, 24 | SuperGiant, 25 | HyperGiant 26 | } 27 | 28 | public enum StarType 29 | { 30 | Undefined = 0, 31 | W = 1, 32 | O = 2, 33 | B = 3, 34 | A = 4, 35 | F = 5, 36 | G = 6, 37 | K = 7, 38 | M = 8, 39 | L = 9, 40 | T = 10 41 | } 42 | 43 | [Serializable] 44 | public struct SpectralClass 45 | { 46 | public int SubType { get; } 47 | 48 | public LuminosityClass Class { get; } 49 | 50 | public StarType Type1 { get; } 51 | 52 | [CreateProperty] 53 | public readonly string Description => $"{ColorFromStarType(Type1)} {FindStarClass(Class.ToString().SplitCamelCase())}"; 54 | 55 | public string Extra { get; } 56 | [CreateProperty] public readonly string Signature => $"{Type1}{SubType}{Class}"; 57 | 58 | public SpectralClass( 59 | StarType type = StarType.Undefined, int subType = 0, LuminosityClass luminosityClass = LuminosityClass.Undefined, 60 | string extra = null) 61 | { 62 | Type1 = type; 63 | SubType = subType; 64 | Class = luminosityClass; 65 | Extra = extra ?? string.Empty; 66 | } 67 | 68 | public SpectralClass(string spectralClass) 69 | { 70 | Type1 = StarType.Undefined; 71 | Class = LuminosityClass.Undefined; 72 | SubType = 0; 73 | Extra = null; 74 | 75 | Regex regex = new(@"(([OBAFGKM]+)([0-9]*)([IVX]*)(\w)*)[\/\-\+]*"); 76 | MatchCollection matches = regex.Matches(spectralClass); 77 | 78 | if (matches.Count == 0) 79 | { 80 | Debug.LogWarning($"Invalid Spectral Class: {spectralClass}"); 81 | return; 82 | } 83 | 84 | Match m = null; 85 | 86 | foreach (Match candidate in matches) 87 | { 88 | if (string.IsNullOrEmpty(candidate.Groups[3].Value)) 89 | { 90 | Debug.LogWarning($"Invalid Spectral Class: {spectralClass}"); 91 | } 92 | else 93 | { 94 | m = candidate; 95 | break; 96 | } 97 | } 98 | 99 | if (m == null) 100 | { 101 | Debug.LogWarning($"Invalid Spectral Class: {spectralClass}"); 102 | return; 103 | } 104 | 105 | char t = m.Groups[2].Value.Trim()[0]; 106 | Type1 = FindStarType(t); 107 | string sN = m.Groups[3].Value; 108 | bool result = float.TryParse(sN, out float fN); 109 | SubType = result ? (int)fN : 0; 110 | string sClass = m.Groups[4].Value; 111 | Class = FindLuminosityClass(sClass); 112 | Extra = m.Groups[5].Value; 113 | } 114 | 115 | public readonly override string ToString() => Signature; 116 | 117 | public static SpectralClass Sol => new(StarType.G, 2, LuminosityClass.V); 118 | public static SpectralClass Undefined => new(StarType.Undefined); 119 | 120 | public static StarType FindStarType(char t) 121 | { 122 | StarType starType = t switch 123 | { 124 | 'O' => StarType.O, 125 | 'B' => StarType.B, 126 | 'A' => StarType.A, 127 | 'F' => StarType.F, 128 | 'G' => StarType.G, 129 | 'K' => StarType.K, 130 | 'M' => StarType.M, 131 | _ => StarType.Undefined 132 | }; 133 | 134 | return starType; 135 | } 136 | 137 | public static string ColorFromStarType(StarType starType) 138 | { 139 | return starType switch 140 | { 141 | StarType.W => "Wolf-Rayet", 142 | StarType.O => "Blue", 143 | StarType.B => "Blue-White", 144 | StarType.A => "White", 145 | StarType.F => "Yellow-White", 146 | StarType.G => "Yellow", 147 | StarType.K => "Orange", 148 | StarType.M => "Red", 149 | StarType.L => "Brown", 150 | StarType.T => "Cool Brown", 151 | _ => "Error" 152 | }; 153 | } 154 | 155 | public static StarClass FindStarClass(string luminosityClass) 156 | { 157 | StarClass starClass = luminosityClass switch 158 | { 159 | "V" => StarClass.Dwarf, 160 | "IV" => StarClass.SubGiant, 161 | "III" or "IIIa" or "IIIb" => StarClass.Giant, 162 | "II" => StarClass.BrightGiant, 163 | "Ia" or "Ib" or "Iab" => StarClass.SuperGiant, 164 | "Ia0" => StarClass.HyperGiant, 165 | _ => StarClass.Undefined 166 | }; 167 | 168 | return starClass; 169 | } 170 | 171 | 172 | public static LuminosityClass FindLuminosityClass(string luminosityClass) 173 | { 174 | LuminosityClass luminosity = luminosityClass switch 175 | { 176 | "V" => LuminosityClass.V, 177 | "IV" => LuminosityClass.IV, 178 | "III" or "IIIa" or "IIIb" => LuminosityClass.III, 179 | "II" => LuminosityClass.II, 180 | "Ia" => LuminosityClass.Ia, 181 | "Ib" or "Iab" => LuminosityClass.Ib, 182 | _ => LuminosityClass.V 183 | }; 184 | 185 | return luminosity; 186 | } 187 | 188 | // Where 0 = Ia0, 1 = Ia, 2 = Ib, 3 = II, ..., 8 = VII 189 | public static LuminosityClass FindLuminosityClassByIndex(int index) 190 | { 191 | return index switch 192 | { 193 | 0 => LuminosityClass.Ia0, 194 | 1 => LuminosityClass.Ia, 195 | 2 => LuminosityClass.Ib, 196 | 3 => LuminosityClass.II, 197 | 4 => LuminosityClass.III, 198 | 5 => LuminosityClass.IV, 199 | 6 => LuminosityClass.V, 200 | 7 => LuminosityClass.VI, 201 | 8 => LuminosityClass.VII, 202 | _ => LuminosityClass.Undefined 203 | }; 204 | } 205 | } 206 | 207 | public static class SpectralClassExtensions 208 | { 209 | private static readonly Regex regex1 = new(@"(\P{Ll})(\P{Ll}\p{Ll})", RegexOptions.Compiled | RegexOptions.CultureInvariant); 210 | private static readonly Regex regex2 = new(@"(\p{Ll})(\P{Ll})", RegexOptions.Compiled | RegexOptions.CultureInvariant); 211 | 212 | public static string SplitCamelCase(this string str) => regex2.Replace(regex1.Replace(str, "$1 $2"), "$1 $2"); 213 | } 214 | } -------------------------------------------------------------------------------- /Universe/CelestialMechanics/Orbits/Propagation/Farnocchia.cs: -------------------------------------------------------------------------------- 1 | // VindemiatrixCollective.Universe © 2025 Vindemiatrix Collective 2 | // Website and Documentation: https://vindemiatrixcollective.com 3 | 4 | #region 5 | 6 | using System; 7 | using UnitsNet; 8 | using UnityEngine.Assertions; 9 | using Angle = UnitsNet.Angle; 10 | using Length = UnitsNet.Length; 11 | 12 | #endregion 13 | 14 | namespace VindemiatrixCollective.Universe.CelestialMechanics.Orbits.Propagation 15 | { 16 | /// 17 | /// Propagates orbit using the algorithm described by Davide Farnocchia in 18 | /// http://dx.doi.org/10.1007/s10569-013-9476-9. 19 | /// Implementation based on Poliastro & Hapsira 20 | /// https://github.com/pleiszenburg/hapsira 21 | /// 22 | public class Farnocchia : IPropagator 23 | { 24 | /// 25 | /// Propagates orbit using mean motion. 26 | /// 27 | /// Current orbit state 28 | /// Time of flight (s) 29 | public (Angle nu, Angle E, Angle M) PropagateOrbit(OrbitState state, Duration tof) 30 | { 31 | Assert.IsNotNull(state.Attractor, $"{nameof(OrbitState)}.{nameof(OrbitState.Attractor)} cannot be null"); 32 | 33 | double p = state.SemiLatusRectum.Meters; 34 | double q = p / (1 + state.Eccentricity.Value); 35 | double deltaT0 = DeltaTFromNu(state.TrueAnomaly.Radians, state.Eccentricity.Value, state.Attractor.Mu.M3S2, q); 36 | double deltaT = deltaT0 + tof.Seconds; 37 | 38 | (double nu, double E, double M) = NuFromDeltaT(deltaT, state.Eccentricity.Value, state.GravitationalParameter.M3S2, q); 39 | 40 | return (Angle.FromRadians(nu), Angle.FromRadians(E), Angle.FromRadians(M)); 41 | } 42 | 43 | /// 44 | /// Time elapsed since periapsis (q) for the given true anomaly. 45 | /// 46 | /// True anomaly (rad) 47 | /// Eccentricity 48 | /// Gravitational Parameter (M3/S2) 49 | /// 50 | /// Periapsis distance (m)/param> 51 | /// 52 | /// Time elapsed since periapsis (s) 53 | public static double DeltaTFromNu(double nu, double e, double muM3S2, double q, double delta = 1e-2) 54 | { 55 | const double pi = UniversalConstants.Tri.Pi; 56 | if (nu is < -pi or > pi) 57 | { 58 | throw new InvalidOperationException("nu must be between -pi <= nu < pi"); 59 | } 60 | 61 | double E; // Eccentric Anomaly 62 | double M; // Mean Anomaly 63 | double n; // Term of equation (4) in the paper 64 | 65 | if (e < 1 - delta) 66 | { 67 | E = OrbitalMechanics.TrueToEccentricAnomaly(nu, e); 68 | M = OrbitalMechanics.EccentricToMeanAnomaly(E, e); 69 | n = Math.Sqrt(muM3S2 * Math.Pow(1 - e, 3) / Math.Pow(q, 3)); 70 | } 71 | else if (e >= 1 - delta && e < 1) 72 | { 73 | E = OrbitalMechanics.TrueToEccentricAnomaly(nu, e); 74 | if (delta <= 1 - e * Math.Cos(E)) 75 | { 76 | M = OrbitalMechanics.EccentricToMeanAnomaly(E, e); 77 | n = Math.Sqrt(muM3S2 * Math.Pow(1 - e, 3) / Math.Pow(q, 3)); 78 | } 79 | else 80 | { 81 | throw new NotImplementedException("Near parabolic case not implemented"); 82 | } 83 | } 84 | else if (Mathd.Approximately(e, 1)) 85 | { 86 | throw new NotImplementedException("Parabolic case not implemented"); 87 | } 88 | else if (1 + e * Math.Cos(nu) < 0) 89 | { 90 | return double.NaN; 91 | } 92 | else if (e > 1 && e <= 1 + delta) 93 | { 94 | throw new NotImplementedException("Hyperbolic case not implemented"); 95 | } 96 | else if (e > 1 + delta) 97 | { 98 | throw new NotImplementedException("Strong Hyperbolic case not implemented"); 99 | } 100 | else 101 | { 102 | throw new InvalidOperationException("Invalid case"); 103 | } 104 | 105 | 106 | return M / n; 107 | } 108 | 109 | /// 110 | /// 111 | /// 112 | /// 113 | /// Gravitational parameter (M3/S2) 114 | /// 115 | /// 116 | /// A tuple with the three anomalies: true, eccentric, and mean anomaly 117 | public static (double nu, double E, double M) NuFromDeltaT(double dt, double e, double mu, double q, double delta = 1e-2) 118 | { 119 | const double pi = UniversalConstants.Tri.Pi; 120 | const double pi2 = UniversalConstants.Tri.Pi2; 121 | 122 | double E; // Eccentric Anomaly 123 | double M; // Mean Anomaly 124 | double n; 125 | double nu; 126 | if (e < 1 - delta) 127 | { 128 | n = Math.Sqrt(mu * Math.Pow(1 - e, 3) / Math.Pow(q, 3)); 129 | M = n * dt; 130 | E = OrbitalMechanics.MeanToEccentricAnomaly((M + pi) % pi2 - pi, e); 131 | nu = OrbitalMechanics.EccentricToTrueAnomaly(E, e); 132 | } 133 | else if (e > 1 - delta && e < 1) 134 | { 135 | double Edelta = Math.Acos((1 - delta) / e); 136 | n = Math.Sqrt(mu * Math.Pow(1 - e, 3) / Math.Pow(q, 3)); 137 | M = n * dt; 138 | if (OrbitalMechanics.EccentricToMeanAnomaly(Edelta, e) <= Math.Abs(M)) 139 | { 140 | // Strong elliptic 141 | E = OrbitalMechanics.MeanToEccentricAnomaly((M + pi) % pi2 - pi, e); 142 | nu = OrbitalMechanics.EccentricToMeanAnomaly(E, e); 143 | } 144 | else 145 | { 146 | throw new NotImplementedException("Near parabolic case not implemented"); 147 | } 148 | } 149 | else if (Mathd.Approximately(e, 1)) 150 | { 151 | throw new NotImplementedException("Parabolic case not implemented"); 152 | } 153 | else if (e > 1 && e <= 1 + delta) 154 | { 155 | throw new NotImplementedException("Hyperbolic case not implemented"); 156 | } 157 | else 158 | { 159 | throw new NotImplementedException("Strong Hyperbolic case not implemented"); 160 | } 161 | 162 | return (nu, E, M); 163 | } 164 | 165 | 166 | /// 167 | /// Time elapsed since periapsis (q) for the given true anomaly. 168 | /// 169 | /// True anomaly (rad) 170 | /// Eccentricity 171 | /// Gravitational Parameter (M3/S2) 172 | /// Periapsis distance (m) 173 | /// 174 | /// Time elapsed since periapsis (s) 175 | public static Duration DeltaTFromNu(Angle nu, Ratio e, GravitationalParameter mu, Length q, double delta = 1e-2) 176 | { 177 | double dt = DeltaTFromNu(nu.Radians, e.Value, mu.M3S2, q.Meters, delta); 178 | return Duration.FromSeconds(dt); 179 | } 180 | } 181 | } -------------------------------------------------------------------------------- /Universe/Model/CelestialBody.cs: -------------------------------------------------------------------------------- 1 | // VindemiatrixCollective.Universe © 2025 Vindemiatrix Collective 2 | // Website and Documentation: https://vindemiatrixcollective.com 3 | 4 | #region 5 | 6 | using System; 7 | using System.Collections; 8 | using System.Collections.Generic; 9 | using System.Linq; 10 | using UnitsNet; 11 | using Unity.Properties; 12 | using UnityEngine.Assertions; 13 | using VindemiatrixCollective.Universe.CelestialMechanics; 14 | using VindemiatrixCollective.Universe.CelestialMechanics.Orbits; 15 | 16 | #endregion 17 | 18 | namespace VindemiatrixCollective.Universe.Model 19 | { 20 | public abstract class CelestialBody : ICelestialBody, IAttractor, IEnumerable 21 | { 22 | private OrbitalData orbitalData; 23 | 24 | public Attributes Attributes { get; protected internal set; } 25 | 26 | public CelestialBody this[string key] => _Orbiters[key]; 27 | 28 | public CelestialBody this[int index] => _Orbiters.Values.ElementAt(index); 29 | 30 | public CelestialBody ParentBody { get; internal set; } 31 | 32 | [CreateProperty] public CelestialBodyType Type => Attributes.Type; 33 | public GravitationalParameter Mu => GravitationalParameter.FromMass(PhysicalData.Mass); 34 | 35 | public IEnumerable Hierarchy => Tree.PreOrderVisit(this).Cast(); 36 | 37 | public IEnumerable Orbiters => _Orbiters.Values; 38 | 39 | public IEnumerable Siblings 40 | { 41 | get 42 | { 43 | List siblings = ParentBody != null ? ParentBody.Orbiters.ToList() : StarSystem.Orbiters.ToList(); 44 | siblings.Remove(this); 45 | return siblings; 46 | } 47 | } 48 | 49 | public int Depth 50 | { 51 | get 52 | { 53 | int depth = 0; 54 | CelestialBody body = ParentBody; 55 | while (body != null) 56 | { 57 | body = body.ParentBody; 58 | depth++; 59 | } 60 | 61 | return depth; 62 | } 63 | } 64 | 65 | public int Index { get; protected internal set; } 66 | 67 | public int OrbiterCount => _Orbiters.Count; 68 | 69 | public int SiblingsCount => Siblings.Count(); 70 | 71 | [CreateProperty] 72 | public OrbitalData OrbitalData 73 | { 74 | get => orbitalData; 75 | set 76 | { 77 | if (value == null) 78 | { 79 | return; 80 | } 81 | 82 | orbitalData = value; 83 | if (ParentBody != null) 84 | { 85 | OrbitState = OrbitState.FromOrbitalElements(orbitalData, ParentBody); 86 | } 87 | else 88 | { 89 | OrbitState = OrbitState.FromOrbitalElements(orbitalData); 90 | } 91 | #if UNITY_EDITOR 92 | CopyOrbitalValues(); 93 | #endif 94 | } 95 | } 96 | 97 | [CreateProperty] public OrbitState OrbitState { get; private set; } 98 | 99 | [CreateProperty] public virtual PhysicalData PhysicalData { get; set; } 100 | 101 | public Star ParentStar => Tree.FindAncestor(this); 102 | 103 | [CreateProperty] public StarSystem StarSystem { get; internal set; } 104 | 105 | [CreateProperty] public virtual string FullName => Name; 106 | 107 | public string Name { get; set; } 108 | 109 | protected Dictionary _Orbiters { get; } 110 | 111 | protected CelestialBody(string name, CelestialBodyType type) 112 | { 113 | Assert.IsFalse(string.IsNullOrEmpty(name)); 114 | Name = name; 115 | Attributes = new Attributes { [nameof(Type)] = type.ToString() }; 116 | 117 | _Orbiters = new Dictionary(); 118 | } 119 | 120 | 121 | /// 122 | /// Searches in the orbiters of this body for . 123 | /// 124 | /// The name of the to find. 125 | /// If true, will also search descendants. 126 | /// 127 | public bool HasOrbiter(string orbiterName, bool recursive = false) 128 | { 129 | bool result = _Orbiters.ContainsKey(orbiterName); 130 | if (recursive && !result) 131 | { 132 | foreach (CelestialBody body in _Orbiters.Values) 133 | { 134 | result = body.HasOrbiter(orbiterName, true); 135 | if (result) 136 | { 137 | break; 138 | } 139 | } 140 | } 141 | 142 | return result; 143 | } 144 | 145 | public IEnumerator GetEnumerator() => Orbiters.GetEnumerator(); 146 | 147 | public Length DistanceTo(CelestialBody body) 148 | { 149 | Assert.IsNotNull(body, nameof(body)); 150 | Vector3d from = OrbitState?.Position ?? Vector3d.zero; 151 | Vector3d to = body.OrbitState.Position; 152 | double d = Vector3d.Distance(from, to); 153 | return Length.FromMeters(d); 154 | } 155 | 156 | /// 157 | /// Returns a string representing the location of this body in the Galaxy. 158 | /// The format used is StarSystemName/StarName/PlanetName. 159 | /// 160 | /// The path. 161 | public string GetPath() 162 | { 163 | Stack pathStack = new(); 164 | 165 | pathStack.Push(Name); 166 | 167 | CelestialBody body = ParentBody; 168 | while (body != null) 169 | { 170 | pathStack.Push(body.Name); 171 | body = body.ParentBody; 172 | } 173 | 174 | pathStack.Push(StarSystem.Name); 175 | 176 | string path = string.Empty; 177 | 178 | while (pathStack.Count > 0) 179 | { 180 | path += $"{pathStack.Pop()}/"; 181 | } 182 | 183 | return path[..^1]; 184 | } 185 | 186 | public virtual void AddOrbiter(CelestialBody orbiter) 187 | { 188 | orbiter.Index = _Orbiters.Count; 189 | orbiter.SetParentBody(this); 190 | 191 | _Orbiters.Add(orbiter.Name, orbiter); 192 | 193 | foreach (CelestialBody body in orbiter.Hierarchy) 194 | { 195 | body.StarSystem = StarSystem; 196 | } 197 | } 198 | 199 | public void AddOrbiters(IEnumerable newOrbiters) 200 | { 201 | Assert.IsNotNull(newOrbiters, nameof(Orbiters)); 202 | 203 | foreach (CelestialBody orbiter in newOrbiters) 204 | { 205 | AddOrbiter(orbiter); 206 | } 207 | } 208 | 209 | /// 210 | /// Sets the parent body of this . 211 | /// Also causes this parent body to become an attractor of this one. 212 | /// 213 | /// 214 | public virtual void SetParentBody(CelestialBody parentBody) 215 | { 216 | ParentBody = parentBody; 217 | if (ParentBody != null && OrbitState != null) 218 | { 219 | OrbitState.SetAttractor(parentBody); 220 | } 221 | } 222 | 223 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 224 | 225 | 226 | #if UNITY_EDITOR 227 | 228 | #region Unity_Editor 229 | 230 | #if UNITY_EDITOR 231 | 232 | [Serializable] 233 | public struct OrbitalDataValues 234 | { 235 | public string AxialTilt; 236 | public string Eccentricity; 237 | public string Inclination; 238 | public string OrbitalPeriod; 239 | public string SemiMajorAxis; 240 | public string SiderealRotation; 241 | } 242 | 243 | public OrbitalDataValues orbitalDataValues; 244 | 245 | protected void CopyOrbitalValues() 246 | { 247 | orbitalDataValues.SemiMajorAxis = OrbitalData.SemiMajorAxis.AstronomicalUnits.ToString("0.00 au"); 248 | orbitalDataValues.Eccentricity = OrbitalData.Eccentricity.Value.ToString("0.00"); 249 | orbitalDataValues.SiderealRotation = OrbitalData.SiderealRotationPeriod.Days.ToString("0.00 d"); 250 | orbitalDataValues.Inclination = OrbitalData.Inclination.Degrees.ToString("0.00 °"); 251 | orbitalDataValues.AxialTilt = OrbitalData.AxialTilt.Degrees.ToString("0.00 °"); 252 | orbitalDataValues.OrbitalPeriod = OrbitalData.Period.Years365 < 0.1f 253 | ? OrbitalData.Period.Days.ToString("0.00 d") 254 | : OrbitalData.Period.Years365.ToString("0.00 y"); 255 | } 256 | #endif 257 | 258 | #endregion 259 | 260 | #endif 261 | 262 | #region ITreeNode 263 | 264 | IEnumerable ITreeNode.Children => Orbiters; 265 | 266 | ITreeNode ITreeNode.Parent => ParentBody; 267 | ITreeNode ITreeNode.this[string name] => this[name]; 268 | 269 | #endregion 270 | 271 | #region IAttractor 272 | 273 | Mass IAttractor.Mass => PhysicalData.Mass; 274 | 275 | #endregion 276 | } 277 | } -------------------------------------------------------------------------------- /Universe/CelestialMechanics/Relativity.cs: -------------------------------------------------------------------------------- 1 | // VindemiatrixCollective.Universe © 2025 Vindemiatrix Collective 2 | // Website and Documentation: https://vindemiatrixcollective.com 3 | 4 | #region 5 | 6 | using System.Text; 7 | using UnitsNet; 8 | using UnityEngine.Assertions; 9 | using Math = System.Math; 10 | 11 | #endregion 12 | 13 | namespace VindemiatrixCollective.Universe.CelestialMechanics 14 | { 15 | public readonly struct RelativisticTravelData 16 | { 17 | public Acceleration Acceleration { get; } 18 | 19 | public bool Orbit { get; } 20 | 21 | public double Rapidity { get; } 22 | 23 | public Duration ObserverTimeAcceleration { get; } 24 | 25 | public Duration ObserverTimeCruise { get; } 26 | public Duration ShipTimeAcceleration { get; } 27 | 28 | public Duration ShipTimeCruise { get; } 29 | 30 | public Duration TotalObserverTime { get; } 31 | 32 | public Duration TotalShipTime { get; } 33 | public Length Distance { get; } 34 | public Length DistanceAcceleration { get; } 35 | 36 | public Length DistanceCruise { get; } 37 | public Speed MaxSpeed { get; } 38 | 39 | public RelativisticTravelData( 40 | Length distance, Speed maxSpeed, Acceleration acceleration, Duration shipTimeAcceleration, Duration observerTimeAcceleration, 41 | Duration shipTimeCruise, Duration observerTimeCruise, Duration totalShipTime, Duration totalObserverTime, 42 | Length distanceAcceleration, Length distanceCruise, double rapidity, bool orbit = true) 43 | { 44 | Distance = distance; 45 | MaxSpeed = maxSpeed; 46 | Acceleration = acceleration; 47 | ShipTimeAcceleration = shipTimeAcceleration; 48 | ObserverTimeAcceleration = observerTimeAcceleration; 49 | ShipTimeCruise = shipTimeCruise; 50 | ObserverTimeCruise = observerTimeCruise; 51 | TotalShipTime = totalShipTime; 52 | TotalObserverTime = totalObserverTime; 53 | DistanceAcceleration = distanceAcceleration; 54 | DistanceCruise = distanceCruise; 55 | Rapidity = rapidity; 56 | Orbit = orbit; 57 | } 58 | 59 | public override string ToString() 60 | { 61 | StringBuilder sb = new StringBuilder(); 62 | sb.AppendLine($"Distance: {Distance.LightYears:0.00} ly"); 63 | sb.AppendLine($"Maximum speed reached: {(MaxSpeed.KilometersPerSecond / UniversalConstants.Celestial.LightSpeedKilometresPerSecond):0.00} c"); 64 | sb.AppendLine($"Acceleration: {Acceleration.StandardGravity:0.00} g"); 65 | sb.AppendLine($"Total travel time (obs/ship): {TotalObserverTime.Years365:0.00} y / {TotalShipTime.Years365:0.00} y"); 66 | sb.AppendLine($"Acceleration/deceleration phase (obs/ship): {ObserverTimeAcceleration.Years365:0.00} y / {ShipTimeAcceleration.Years365:0.00} y"); 67 | sb.AppendLine($"Cruise phase (obs/ship): {ObserverTimeCruise.Years365:0.00} y / {ShipTimeCruise.Years365:0.00} y"); 68 | if (Orbit) 69 | sb.AppendLine($"Distance acceleration - cruise - deceleration: {DistanceAcceleration.LightYears:0.00} ly + {DistanceCruise.LightYears:0.00} ly + {DistanceAcceleration.LightYears:0.00} ly"); 70 | else 71 | sb.AppendLine($"Distance acceleration - cruise: {DistanceAcceleration.LightYears:0.00} ly + {DistanceCruise.LightYears:0.00} ly"); 72 | sb.AppendLine($"Total distance: {(DistanceAcceleration * (Orbit ? 2 : 1) + DistanceCruise).LightYears:0.00} ly"); 73 | sb.AppendLine($"Rapidity: {Rapidity:0.00}"); 74 | 75 | return sb.ToString(); 76 | } 77 | } 78 | 79 | public static class Relativity 80 | { 81 | /// 82 | /// Calculates the time a ship would take to travel a distance d, reaching a max speed of 83 | /// v, 84 | /// under an acceleration a. The ship accelerates until it reaches max speed, then cruises, 85 | /// and then decelerates (if decelerate is true). 86 | /// Formulas sourced from: https://math.ucr.edu/home/baez/physics/Relativity/SR/Rocket/rocket.html 87 | /// 88 | /// The distance to the destination star system. 89 | /// The maximum speed the ship will reach. 90 | /// The constant acceleration provided by the engines. 91 | /// If the ship should decelerate. 92 | /// A struct. 93 | public static RelativisticTravelData CalculateTravel( 94 | Length distance, Speed shipMaxSpeed, Acceleration acceleration, bool decelerate = true) 95 | { 96 | Assert.IsTrue(!double.IsNaN(distance.Value) && distance.Value > 0, nameof(distance)); 97 | Assert.IsTrue(!double.IsNaN(shipMaxSpeed.Value) && shipMaxSpeed.Value > 0, nameof(shipMaxSpeed)); 98 | Assert.IsTrue(!double.IsNaN(acceleration.Value) && acceleration.Value > 0, nameof(acceleration)); 99 | 100 | double gamma = CalculateTimeDilation(shipMaxSpeed); 101 | Duration shipTimeAcceleration = CalculateShipTimeAcceleration(acceleration, shipMaxSpeed); 102 | double rapidity = CalculateRapidity(acceleration, shipTimeAcceleration); 103 | Length distanceAcceleration = CalculateAccelerationDistance(acceleration, rapidity); 104 | Length distanceCruise = distance - (decelerate ? 2 : 1) * distanceAcceleration; 105 | 106 | Duration observerTimeAcceleration = CalculateObserverTimeAcceleration(acceleration, rapidity); 107 | Duration observerTimeCruise = distanceCruise / shipMaxSpeed; 108 | 109 | Duration shipTimeCruise = observerTimeCruise / gamma; 110 | Duration totalObserverTime = (observerTimeAcceleration * (decelerate ? 2 : 1)) + observerTimeCruise; 111 | Duration totalShipTime = (shipTimeAcceleration * (decelerate ? 2 : 1)) + shipTimeCruise; 112 | 113 | return new RelativisticTravelData(distance, shipMaxSpeed, acceleration, shipTimeAcceleration, observerTimeAcceleration, 114 | shipTimeCruise, observerTimeCruise, totalShipTime, totalObserverTime, distanceAcceleration, 115 | distanceCruise, rapidity, decelerate); 116 | } 117 | 118 | /// 119 | /// Calculates the time a ship would take to travel a distance d, reaching a max speed of 120 | /// v, 121 | /// under an acceleration a. The ship accelerates until it reaches max speed, then cruises, and then 122 | /// decelerates. 123 | /// Formulas sourced from: https://math.ucr.edu/home/baez/physics/Relativity/SR/Rocket/rocket.html 124 | /// 125 | /// The distance to the destination star system, in light years. 126 | /// The total deltaV budget in km/s. 127 | /// The constant acceleration provided by the engines, in standard g. 128 | /// If the ship should decelerate. 129 | /// A struct. 130 | public static RelativisticTravelData CalculateTravel(float distanceLY, float deltaV, float accelerationG, bool decelerate = true) 131 | { 132 | return CalculateTravel(Length.FromLightYears(distanceLY), Speed.FromKilometersPerSecond(deltaV / 2), 133 | Acceleration.FromStandardGravity(accelerationG), decelerate); 134 | } 135 | 136 | public static double CalculateRapidity(Acceleration acceleration, Duration shipTime) 137 | { 138 | double a = acceleration.MetersPerSecondSquared; 139 | double c = UniversalConstants.Celestial.LightSpeedMetresPerSecond; 140 | double t = shipTime.Seconds; 141 | 142 | return (a * t) / c; 143 | } 144 | 145 | public static double CalculateTimeDilation(Speed shipMaxSpeed) 146 | { 147 | double v = shipMaxSpeed.MetersPerSecond; 148 | double c = UniversalConstants.Celestial.LightSpeedMetresPerSecond; 149 | 150 | return 1 / Math.Sqrt(1 - ((v * v) / (c * c))); 151 | } 152 | 153 | public static Duration CalculateObserverTimeAcceleration(Acceleration acceleration, double rapidity) 154 | { 155 | double a = acceleration.MetersPerSecondSquared; 156 | double c = UniversalConstants.Celestial.LightSpeedMetresPerSecond; 157 | return Duration.FromSeconds((Math.Sinh(rapidity) * c) / a); 158 | } 159 | 160 | public static Duration CalculateShipTimeAcceleration(Acceleration acceleration, Speed shipMaxSpeed) 161 | { 162 | double a = acceleration.MetersPerSecondSquared; 163 | double v = shipMaxSpeed.MetersPerSecond; 164 | double c = UniversalConstants.Celestial.LightSpeedMetresPerSecond; 165 | double shipTime = ((c / a) * Math.Atanh(v / c)); 166 | 167 | return Duration.FromSeconds(shipTime); 168 | } 169 | 170 | public static Length CalculateAccelerationDistance(Acceleration acceleration, double rapidity) 171 | { 172 | double a = acceleration.MetersPerSecondSquared; 173 | double c = UniversalConstants.Celestial.LightSpeedMetresPerSecond; 174 | return Length.FromMeters(((Math.Cosh(rapidity) - 1) * c * c) / a); 175 | } 176 | 177 | public static Speed CalculateVelocity(double rapidity) 178 | { 179 | double c = UniversalConstants.Celestial.LightSpeedMetresPerSecond; 180 | 181 | return Speed.FromMetersPerSecond(Math.Tanh(rapidity) * c); 182 | } 183 | 184 | public static Speed SpeedFromFractionOfC(float fraction) 185 | { 186 | return Speed.FromMetersPerSecond(UniversalConstants.Celestial.LightSpeedMetresPerSecond * fraction); 187 | } 188 | } 189 | } -------------------------------------------------------------------------------- /Universe/Tests/OrbitStateTests.cs: -------------------------------------------------------------------------------- 1 | // VindemiatrixCollective.Universe.Tests © 2025 Vindemiatrix Collective 2 | // Website and Documentation: https://vindemiatrixcollective.com 3 | 4 | #region 5 | 6 | using System; 7 | using System.Linq; 8 | using NUnit.Framework; 9 | using UnitsNet; 10 | using UnityEngine; 11 | using VindemiatrixCollective.Universe.CelestialMechanics; 12 | using VindemiatrixCollective.Universe.CelestialMechanics.Orbits; 13 | using VindemiatrixCollective.Universe.Model; 14 | 15 | #endregion 16 | 17 | namespace VindemiatrixCollective.Universe.Tests 18 | { 19 | public class OrbitStateTests 20 | { 21 | private readonly DeserializationHelper dataHelper = new(); 22 | 23 | [Test] 24 | public void Clone() 25 | { 26 | OrbitState state = OrbitState.FromOrbitalElements(Common.MarsElements, Common.Sun); 27 | OrbitState state1 = state.Clone(); 28 | 29 | Duration week = Duration.FromDays(7); 30 | 31 | state.Propagate(week); 32 | state1.Propagate(week); 33 | 34 | Common.ArrayAreEqual(state.ToArrayElements(), state1.ToArrayElements(), 1e-3, "Elements"); 35 | } 36 | 37 | [Test] 38 | public void CoeToRV() 39 | { 40 | GravitationalParameter gm = GravitationalParameter.Earth; 41 | double h = 60000e6; 42 | double e = 0.3; 43 | double nu = UniversalConstants.Tri.DegreeToRad * 120; 44 | double p = h * h / gm.M3S2; 45 | (Vector3d r, Vector3d v) = OrbitalMechanics.RVinPerifocalFrame(gm.M3S2, p, e, nu); 46 | 47 | Common.VectorsAreEqual(new Vector3d(-5312706.25105345, 9201877.15251336, 0), r, 1e-2, nameof(OrbitState.LocalPosition)); 48 | Common.VectorsAreEqual(new Vector3d(-5753.30180931, -1328.66813933, 0), v, 1e-2, nameof(OrbitState.Velocity)); 49 | } 50 | 51 | [Test] 52 | public void CoeToRvTransitive() 53 | { 54 | Star sun = Common.Sun; 55 | OrbitalData marsElements = OrbitalData.FromClassicElements(1.523679f, 0.093315f, 1.85f, 49.562f, 286.537f, 23.33f); 56 | OrbitState stateExpected = OrbitState.FromOrbitalElements(marsElements, sun); 57 | 58 | (Vector3d r, Vector3d v) = stateExpected.ToVectors(); 59 | 60 | OrbitState stateResult = OrbitState.FromVectors(r, v, sun, stateExpected.Epoch); 61 | 62 | Assert.AreEqual(stateExpected.SemiMajorAxis.AstronomicalUnits, stateResult.SemiMajorAxis.AstronomicalUnits, 1e-3, 63 | nameof(OrbitState.SemiMajorAxis)); 64 | Assert.AreEqual(stateExpected.Eccentricity.Value, stateResult.Eccentricity.Value, 1e-3, nameof(OrbitState.Eccentricity)); 65 | Assert.AreEqual(stateExpected.SemiLatusRectum.AstronomicalUnits, stateResult.SemiLatusRectum.AstronomicalUnits, 1e-3, 66 | nameof(OrbitState.SemiLatusRectum)); 67 | 68 | Assert.AreEqual(stateExpected.TrueAnomaly.Degrees, stateResult.TrueAnomaly.Degrees, 0.5, nameof(OrbitState.TrueAnomaly)); 69 | Assert.AreEqual(stateExpected.LongitudeAscendingNode.Degrees, stateResult.LongitudeAscendingNode.Degrees, 1e-3, 70 | nameof(OrbitState.LongitudeAscendingNode)); 71 | Assert.AreEqual(stateExpected.ArgumentPeriapsis.Degrees, stateResult.ArgumentPeriapsis.Degrees, 1, 72 | nameof(OrbitState.ArgumentPeriapsis)); 73 | Assert.AreEqual(stateExpected.Inclination.Degrees, stateResult.Inclination.Degrees, 1e-3, nameof(OrbitState.Inclination)); 74 | } 75 | 76 | [Test] 77 | public void FromClassicElements() 78 | { 79 | double tol = 1e-6; 80 | Star sun = Common.Sun; 81 | 82 | OrbitState state = OrbitState.FromOrbitalElements(Common.MarsElements, sun); 83 | 84 | Assert.AreEqual(686.9713834767824, state.Period.Days, 1); 85 | Assert.AreEqual(1.6658611058850001, state.ApoapsisDistance.AstronomicalUnits, 1e-3); 86 | Assert.AreEqual(1.3814968941149999, state.PeriapsisDistance.AstronomicalUnits, 1e-3); 87 | } 88 | 89 | [Test] 90 | public void FromVectorsMars() 91 | { 92 | Star sun = Common.Sun; 93 | 94 | Vector3d r = new(2.08047627e+11, -2.02006193e+09, -5.15689300e+09); 95 | Vector3d v = new(1164.20212, 26296.03633, 522.29379); 96 | OrbitState state = OrbitState.FromVectors(r, v, sun, Common.J2000); 97 | 98 | Vector3d hExpected = new(1.34550780e14, -1.14665650e14, 5.47317972e15); 99 | Common.VectorsAreEqual(hExpected, state.AngularMomentum, 1e7); 100 | 101 | Vector3d EExpected = new(0.08527746, -0.03777703, -0.00288788); 102 | Common.VectorsAreEqual(EExpected, state.EccentricityVector, 1e-3); 103 | 104 | Assert.AreEqual(1.523679, state.SemiMajorAxis.AstronomicalUnits, 1e-3, nameof(OrbitState.SemiMajorAxis)); 105 | Assert.AreEqual(0.093315, state.Eccentricity.Value, 1e-3, nameof(OrbitState.Eccentricity)); 106 | Assert.AreEqual(1.85, state.Inclination.Degrees, 1e-3, nameof(OrbitState.Inclination)); 107 | Assert.AreEqual(1.5104112767893412, state.SemiLatusRectum.AstronomicalUnits, 1e-3, nameof(OrbitState.SemiLatusRectum)); 108 | Assert.AreEqual(23.33, state.TrueAnomaly.Degrees, 0.5, nameof(OrbitState.TrueAnomaly)); 109 | Assert.AreEqual(49.562, state.LongitudeAscendingNode.Degrees, 1e-3, nameof(OrbitState.LongitudeAscendingNode)); 110 | Assert.AreEqual(286.537, state.ArgumentPeriapsis.Degrees, 1, nameof(OrbitState.ArgumentPeriapsis)); 111 | } 112 | 113 | [Test] 114 | public void LunarPeriod() 115 | { 116 | Mass eMass = Mass.FromEarthMasses(1); 117 | Length eRadius = Length.FromKilometers(6371.0); 118 | 119 | double volume = 4 / 3d * Math.PI * Math.Pow(eRadius.Meters, 3); 120 | 121 | Planet earth = new() 122 | { 123 | Name = "Earth", 124 | PhysicalData = new PhysicalData(Density.FromKilogramsPerCubicMeter(eMass.Kilograms / volume), eRadius, 125 | GravitationalParameter.FromMass(eMass)), 126 | OrbitalData = Planet.Earth.OrbitalData 127 | }; 128 | 129 | Planet moon = new() 130 | { 131 | Name = "Luna", 132 | PhysicalData = new PhysicalData(Mass.FromKilograms(7.346e22), Length.FromKilometers(1737.4), 133 | Acceleration.FromMetersPerSecondSquared(1.622), Density.FromGramsPerCubicCentimeter(3.34)), 134 | OrbitalData = Planet.Moon.OrbitalData 135 | }; 136 | 137 | earth.AddOrbiters(new[] { moon }); 138 | moon.OrbitState.Propagate(Duration.FromDays(1)); 139 | 140 | // Half a day of tolerance 141 | Assert.AreEqual(27, moon.OrbitState.Period.Days, 5e-1, nameof(OrbitState.Period)); 142 | Debug.Log(moon.OrbitState.Period.Days); 143 | } 144 | 145 | 146 | [Test] 147 | public void PropagatedFromToVectors() 148 | { 149 | Planet earth = Planet.Earth; 150 | int altitude = 7000000; 151 | 152 | // this method uses Mu.M3S2 / (body.Radius + orbitHeight) 153 | Speed orbitSpeed1 = 154 | OrbitalMechanics.CalculateOrbitalVelocity(Planet.Earth, Length.FromMeters(7000000 - earth.PhysicalData.Radius.Meters)); 155 | 156 | Speed orbitSpeed = 157 | Speed.FromMetersPerSecond(Math.Sqrt(UniversalConstants.Celestial.GravitationalConstant * earth.PhysicalData.Mass.Kilograms 158 | / altitude)); 159 | 160 | Assert.AreEqual(orbitSpeed1.MetersPerSecond, orbitSpeed.MetersPerSecond, 1e-2, "Orbital Speed"); 161 | 162 | 163 | DateTime date = DateTime.Now; 164 | 165 | OrbitState orbit = OrbitState.FromVectors(new Vector3d(altitude, 0, 0), new Vector3d(0, orbitSpeed.MetersPerSecond, 0), earth, 166 | date); 167 | 168 | Assert.AreEqual(altitude, orbit.PeriapsisDistance.Meters, 5, "PeriapsisDistance Before"); 169 | Assert.AreEqual(altitude, orbit.ApoapsisDistance.Meters, 5, "ApoapsisDistance Before"); 170 | Assert.AreEqual(altitude, orbit.SemiMajorAxis.Meters, 5, "Semi Major Axis Before"); 171 | Assert.AreEqual(0, orbit.TrueAnomaly.Degrees, 0, "TrueAnomaly Before"); 172 | 173 | 174 | for (int i = 0; i < 1000; i++) 175 | { 176 | date += Duration.FromDays(0.016); 177 | orbit.Propagate(date); 178 | } 179 | 180 | Vector3d r, v; 181 | 182 | (r, v) = orbit.ToVectors(); 183 | 184 | OrbitState newOrbit = OrbitState.FromVectors(r, v, earth, date); 185 | 186 | Assert.AreEqual(altitude, newOrbit.PeriapsisDistance.Meters, 5, "PeriapsisDistance After"); 187 | Assert.AreEqual(altitude, newOrbit.ApoapsisDistance.Meters, 5, "ApoapsisDistance After"); 188 | Assert.AreEqual(altitude, newOrbit.SemiMajorAxis.Meters, 5, "Semi Major Axis After"); 189 | //Assert.AreEqual(0, newOrbit.TrueAnomaly.Degrees, 0, "TrueAnomaly After"); 190 | } 191 | 192 | [Test] 193 | public void PropagationElements() 194 | { 195 | Star sun = Common.Sun; 196 | 197 | OrbitState halley = OrbitState.FromVectors(new Vector3d(-9018878635.69932, -94116054798.39276, 22619058699.43215), 198 | new Vector3d(-49950.92305, -12948.43055, -4292.51577), sun, Common.J2000); 199 | 200 | double[] elementsExpected = halley.ToArrayElements().SkipLast(1).ToArray(); 201 | halley.Propagate(Duration.FromDays(1)); 202 | double[] elements = halley.ToArrayElements().SkipLast(1).ToArray(); 203 | 204 | Common.ArrayAreEqual(elementsExpected, elements, 1e-3, "Elements"); 205 | } 206 | 207 | [Test] 208 | public void PropagationKnownValues() 209 | { 210 | Galaxy galaxy = dataHelper.LoadSol(); 211 | Planet earth = (Planet)galaxy["Sol"][0]["Earth"]; 212 | 213 | // Data from Vallado, example 2.4 214 | Vector3d r0 = new(1131340, -2282343, 6672423); // m 215 | Vector3d v0 = new(-5643.05, 4303.33, 2428.79); // m / s 216 | Vector3d rExp = new(-4219752.7, 4363029.2, -3958766.6); // m 217 | Vector3d vExp = new(3689.866, -1916.735, -6112.511); // m / s 218 | 219 | OrbitState state0 = OrbitState.FromVectors(r0, v0, earth, earth.OrbitState.Epoch); 220 | Duration tof = Duration.FromMinutes(40); 221 | state0.Propagate(tof); 222 | 223 | (Vector3d r1, Vector3d v1) = state0.ToVectors(); 224 | 225 | Common.VectorsAreEqual(rExp, r1, 1e2, nameof(OrbitState.LocalPosition)); // 100 m precision 226 | Common.VectorsAreEqual(vExp, v1, 1e-1, nameof(OrbitState.Velocity)); // 10 cm / s 227 | } 228 | } 229 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Universe 2 | ![](Documentation/Media/SineFine_Transfer.png) 3 | 4 | `Universe` is a library used in the development of [Sine Fine](https://www.vindemiatrixcollective.com), a space exploration game. It provides methods and code to perform calculations useful in this genre of games. With code ported to C# from popular libraries like [poliastro](https://github.com/poliastro/poliastro), it can calculate the position of the planets given a certain date, or an interplanetary manoeuvre to travel to another planet! 5 | 6 | ## Table of Contents 7 | 8 | * [Features](#Features) 9 | * [Installation](#Installation) 10 | * [Creating a galaxy](#Creating-a-galaxy) 11 | * [Propagating orbits](#Propagating-orbits) 12 | * [Interplanetary transfer](#Interplanetary-transfers) 13 | * [Relativistic rockets](#Relativistic-rockets) 14 | * [Support](#Support) 15 | 16 | ## Features 17 | These are the features currently available in `Universe`: 18 | 19 | * A game agnostic "galactic" object model: for each celestial body, it only contains physical and orbital data, based on the [UnitsNet](https://github.com/angularsen/UnitsNet) library to avoid (minimise) confusion and mistakes. 20 | * Uses `double` for internal calculations and can output in `float` for Unity. 21 | * The model supports Galaxies / (n-ary) Star Systems / Stars / Planets (and moons). Each orbiter is a `CelestialBody`. 22 | * A Kepler simulation model that calculates the position of any object in the solar system given their orbital data and time from the [J2000 epoch](https://en.wikipedia.org/wiki/Epoch_(astronomy)#Julian_years_and_J2000). 23 | * Kinematic propagator for orbits of binary stars. 24 | * An interplanetary mission planner that calculates possible transfers given a set of launch windows. 25 | * A set of functions to calculate mission profiles for *relativistic rockets*, to obtain the acceleration and cruise time for both the observer and the ship, and the total duration of the mission given speeds that approach fractions of c. 26 | * (optional) custom deserialisation converters in the assembly `com.vindemiatrixcollective.universe.data`. 27 | * Unit tests based on results from [poliastro](https://github.com/poliastro/poliastro) and other sources. 28 | 29 | ## Installation 30 | 31 | 1. Open the Unity Package Manager from Window > Package Manager 32 | 2. Click on the "+" button > Install package from git URL 33 | 3. Enter the following URL: 34 | 35 | ``` 36 | https://github.com/TheWand3rer/Universe.git?path=/Universe 37 | ``` 38 | 39 | ## Creating a galaxy 40 | The easiest way is to use the `com.vindemiatrixcollective.universe.data` package and deserialise the Solar system data. To do so, move the file [SolarSystem.json](Documentation/Data/Systems.json) into your `Resources/Data` folder. Then, you can deserialise its contents by implementing a function like the following (you can also use the provided [DeserializationHelper](Universe/Tests/DeserializationHelper.cs)): 41 | 42 | ```cs 43 | public T DeserializeFile(string filename, params JsonConverter[] converters) 44 | { 45 | TextAsset text = Resources.Load(filename); 46 | T data = JsonConvert.DeserializeObject(text.ToString(), converters); 47 | return data; 48 | } 49 | 50 | public Galaxy LoadSol(ref Galaxy galaxy, string path = "Data/SolarSystem.json") 51 | { 52 | JsonSerializerSettings settings = new(); 53 | Galaxy additionalData = DeserializeFile(path, 54 | new CoreObjectConverter(new GalaxyConverter()), 55 | new CoreObjectConverter(new StarSystemConverter()), 56 | new CoreObjectConverter(new StarConverter()), 57 | new CoreObjectConverter(new PlanetConverter())); 58 | foreach ((string key, StarSystem system) in additionalData.Systems) 59 | { 60 | galaxy[key] = system; 61 | } 62 | } 63 | ``` 64 | then call it via: 65 | ```cs 66 | Galaxy galaxy = new Galaxy("Milky Way"); 67 | LoadSol(ref galaxy); 68 | ``` 69 | 70 | ### From code 71 | ```cs 72 | Galaxy galaxy = new Galaxy("Milky Way"); 73 | StarSystem sol = new StarSystem("Sol"); 74 | 75 | // Star.Sol and Planet.Earth are included as examples with hardcoded values at J2000. 76 | // You should create stars and planets with the appropriate constructors or factory methods 77 | Star sun = Star.Sun; 78 | Planet earth = Planet.Earth; 79 | 80 | galaxy.AddSystem(sol); 81 | sun.AddPlanet(earth); 82 | sol.AddStar(sun); 83 | ``` 84 | 85 | ## Propagating orbits 86 | Define a solar system, then in your `Start` or other initialisation method: 87 | ### Keplerian Orbits 88 | ```cs 89 | // iterate on every body in the Solar system or use the provided Hiearrchy enumerator, 90 | // or use CelestialBody.PreOrderVisit | LevelOrderVisit 91 | planet.OrbitState.Propagate(DateTime.Today); 92 | ``` 93 | Then, in your `Update` method: 94 | ```cs 95 | float delta = speed * 86400 * Time.deltaTime; 96 | planet.OrbitState.Propagate(delta); 97 | 98 | // When you need to use the calculated position, in the gameobject representing your Planet: 99 | transform.position = planet.OrbitState.Position.FromMetrestoAU(unitsPerAU).ToXZY(); 100 | ``` 101 | Remarks: 102 | * Set speed as a multiplier to define how many in-game seconds correspond to a day: 1 day = 86400 seconds. 103 | * Set unitsPerAU to define how many Unity units correspond to one Astronomical Unit (AU). 104 | * `.toXZY()` is necessary because the calculations are computed using Y as up, to adapt to the original libraries. 105 | 106 | ### Binary Orbits 107 | https://github.com/user-attachments/assets/de2b0dad-f1b2-437a-9703-2bb9262cb911 108 | 109 | The Keplerian propagator works best for two-body problems where one is massive (like a star) and the other is much smaller (a planet). For star systems that have two or more stars, the Keplerian propagator cannot be used for the orbits of the stars. There is a `Kinematic` propagator that you can assign to the `OrbitState` of both stars, which will make them orbit without being affected by each other. An n-body simulator will be added in the future. 110 | 111 | ```cs 112 | Star primary = stars[0]; 113 | Star companion = stars[1]; 114 | 115 | OrbitalData o = companion.OrbitalData; 116 | Barycentre barycentre = new(primary.StarSystem); 117 | (Length a1, Length a2) = barycentre.CalculateSemiMajorAxes(primary, companion, o.SemiMajorAxis); 118 | 119 | primary.Attractor = barycentre; 120 | companion.Attractor = barycentre; 121 | 122 | // Only a, e, i, Ω (longitude of ascending node), ω (argument of periastron, and P are necessary 123 | // i here is zero to make sure that your view is aligned to the orbital plane, 124 | // but in catalogues this value represents the inclination as seen from Earth 125 | OrbitalData o1 = new(a1, o.Eccentricity, Angle.Zero, o.LongitudeAscendingNode, o.ArgumentPeriapsis, o.Period, 126 | Duration.Zero, Angle.Zero, 127 | trueAnomaly: Angle.FromDegrees(0)); 128 | // ω is shifted 180° to place the other state at the opposite side 129 | // however you could calculate the "current position" from the time of periastron T0 if you have it 130 | OrbitalData o2 = new(a2, o.Eccentricity, Angle.Zero, 131 | o.LongitudeAscendingNode, //Angle.FromDegrees((o.LongitudeAscendingNode.Value + 180) % 360), 132 | Angle.FromDegrees((o.ArgumentPeriapsis.Value + 180) % 360), o.Period, 133 | Duration.Zero, Angle.Zero, 134 | trueAnomaly: Angle.FromDegrees(0)); 135 | 136 | primary.OrbitalData = o1; 137 | companion.OrbitalData = o2; 138 | 139 | IPropagator propagator = new Kinematic(); 140 | primary.OrbitState.SetPropagator(propagator); 141 | companion.OrbitState.SetPropagator(propagator); 142 | ``` 143 | 144 | 145 | ## Interplanetary Transfers 146 | [SineFine_transfer.webm](https://github.com/user-attachments/assets/df0ded8a-72e8-4578-8fe2-4075dfc0b5b2) 147 | 148 | ### Simple transfer 149 | You can calculate a transfer between two known planets at any moment in time (based on J2000). 150 | The full implementation is in [LambertTests](Universe/Tests/LambertTests.cs). 151 | 152 | ```cs 153 | CelestialBody earth = Common.Earth; 154 | CelestialBody mars = Common.Mars; 155 | 156 | DateTime epochDeparture = new DateTime(2018, 12, 1, 0, 0, 0, DateTimeKind.Utc); 157 | DateTime epochArrival = epochDeparture.AddYears(2); 158 | 159 | earth.OrbitState.SetAttractor(Common.Sun); 160 | mars.OrbitState.SetAttractor(Common.Sun); 161 | earth.OrbitState.Propagate(epochDeparture); 162 | mars.OrbitState.Propagate(epochArrival); 163 | 164 | IzzoLambertSolver solver = new IzzoLambertSolver(); 165 | Manoeuvre m = Manoeuvre.Lambert(earth.OrbitState, mars.OrbitState, solver); 166 | ``` 167 | 168 | If you print `m`, among other data it will output the manoeuvre's total DeltaV cost and transfer duration: 169 | ``` 170 | Total duration: 731 d 171 | Total cost: 56.031 km/s 172 | ``` 173 | ### Mission Planner 174 | You can also request a full analysis of the potential transfer windows given a certain launch and arrival time span. This is the so-called ["porkchop"](https://docs.poliastro.space/en/stable/examples/Porkchops%20with%20poliastro.html) chart. You can replicate that poliastro example with `Universe` (except for the plotting). Full details in the `PorkchopTest` 175 | in [TransferPlannerTest](Universe/Tests/TransferPlannerTest.cs). 176 | 177 | ```cs 178 | Planet earth = Common.Earth; 179 | Planet mars = Common.Mars; 180 | 181 | earth.OrbitState.SetAttractor(Common.Sun); 182 | mars.OrbitState.SetAttractor(Common.Sun); 183 | 184 | TransferPlanner tp = new CelestialMechanics.Manoeuvres.TransferPlanner(earth, mars); 185 | DateTime startDate = new DateTime(2025, 1, 1, 0, 0, 0, DateTimeKind.Utc); 186 | tp.CalculateTransferWindows(startDate, 200, 50); 187 | TransferData transfer = tp.OrderByDeltaV().First(); 188 | // you can also order the transfers in order of transfer duration 189 | // via tp.OrderByTransferTime() 190 | ``` 191 | 192 | You can then use the `TransferData` [struct](Universe/CelestialMechanics/Manoeuvres/TransferData) to access various properties. If you call `transfer.TransferOrbit(1)` you will obtain a new `OrbitState` object with the first impulse applied, which is the transfer orbit. If you call `transfer.TransferOrbit(0)` or `transfer.TransferOrbit(2)` it will return the circularised orbit around the destination Planet. 193 | 194 | ## Relativistic rockets 195 | These methods calculate interstellar missions based on relativistic rockets: how long would a trip to another star system take at speeds approaching `c`, the speed of light? Run the [RelativisticRocketTests](Universe/Tests/RelativisticRocketTests.cs). 196 | 197 | ```cs 198 | // Values from Project Longshot to Alpha Centauri Orbit 199 | Length distanceAlphaCentauri = Length.FromLightYears(4.344); 200 | Speed maxSpeed = Relativity.SpeedFromFractionOfC(0.048f); // page 20, 59 201 | Acceleration acceleration = Acceleration.FromMetersPerSecondSquared(0.429); // page 68 202 | 203 | RelativisticTravelData result = Relativity.CalculateTravel(distanceAlphaCentauri, maxSpeed, acceleration); 204 | ``` 205 | Example output: 206 | ``` 207 | Distance: 4.34 ly 208 | Maximum speed reached: 0.05 c 209 | Acceleration: 0.04 g 210 | Total travel time (obs/ship): 91.63 y / 91.52 y 211 | Acceleration/deceleration phase (obs/ship): 1.06 y / 1.06 y 212 | Cruise phase (obs/ship): 89.50 y / 89.39 y 213 | Distance acceleration - cruise - deceleration: 0.03 ly + 4.29 ly + 0.03 ly 214 | Total distance: 4.34 ly 215 | Rapidity: 0.05 216 | ``` 217 | 218 | ## Support 219 | If you have questions, please create an [issue](https://github.com/TheWand3rer/SineFine/issues) here or join the [Discord](https://discord.gg/qCX6XKvJ4f). 220 | 221 | ## See also: 222 | * [Sine Fine](https://vindemiatrixcollective.com): a space exploration game played at sublight speeds. 223 | * [astrolabium](https://github.com/TheWand3rer/astrolabium): a python project to combine data from stellar catalogues into the data structures used in the Universe library to represent the galaxy and its star systems. 224 | -------------------------------------------------------------------------------- /Universe/Data/Parse.cs: -------------------------------------------------------------------------------- 1 | // VindemiatrixCollective.Universe.Data © 2025 Vindemiatrix Collective 2 | // Website and Documentation: https://vindemiatrixcollective.com 3 | 4 | #region 5 | 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Globalization; 9 | using System.Text.Json; 10 | using System.Text.Json.Serialization; 11 | using System.Text.RegularExpressions; 12 | using UnityEditor; 13 | using UnityEngine; 14 | 15 | #endregion 16 | 17 | namespace VindemiatrixCollective.Universe.Data 18 | { 19 | public static class Parse 20 | { 21 | public static TEnum EnumFromInt(ref Utf8JsonReader reader, JsonSerializerOptions options) where TEnum : struct, Enum 22 | { 23 | int level = reader.GetInt32(); 24 | return (TEnum)Enum.ToObject(typeof(TEnum), level); 25 | } 26 | 27 | public static TEnum EnumFromString(ref Utf8JsonReader reader, JsonSerializerOptions options) where TEnum : struct, Enum 28 | { 29 | string value = reader.GetString(); 30 | return Enum.Parse(value); 31 | } 32 | 33 | public static bool Boolean(ref Utf8JsonReader reader, JsonSerializerOptions options) => reader.GetBoolean(); 34 | 35 | public static string String(ref Utf8JsonReader reader, JsonSerializerOptions options) => reader.GetString(); 36 | 37 | public static Guid Guid(ref Utf8JsonReader reader, JsonSerializerOptions options) => reader.GetGuid(); 38 | 39 | #if UNITY_EDITOR 40 | public static GUID GuidUnity(ref Utf8JsonReader reader, JsonSerializerOptions options) => new(reader.GetString()); 41 | #endif 42 | 43 | public static float Float(ref Utf8JsonReader reader, JsonSerializerOptions options) 44 | { 45 | float value; 46 | 47 | if (reader.TokenType == JsonTokenType.Number) 48 | { 49 | if (reader.TryGetInt32(out int numberInt)) 50 | { 51 | value = numberInt; 52 | } 53 | else if (reader.TryGetDouble(out double numberDouble)) 54 | { 55 | value = (float)numberDouble; 56 | } 57 | else 58 | { 59 | throw new JsonException("Invalid number format"); 60 | } 61 | } 62 | else if (reader.TokenType == JsonTokenType.String) 63 | { 64 | string text = reader.GetString(); 65 | if (!float.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out float result)) 66 | { 67 | throw new JsonException($"Encountered text while parsing string: \"{text}\", but it is not a number"); 68 | } 69 | 70 | value = result; 71 | Debug.LogWarning($"Encountered text while parsing number: \"{text}\": please change it to a number by removing the \" in the JSON file."); 72 | } 73 | else 74 | { 75 | throw new JsonException($"Invalid token type: {reader.TokenType}"); 76 | } 77 | 78 | try 79 | { 80 | return value; 81 | } 82 | catch (InvalidCastException ex) 83 | { 84 | throw new JsonException($"Value is <{value.GetType().Name}> instead of "); 85 | } 86 | } 87 | 88 | public static double Double(ref Utf8JsonReader reader, JsonSerializerOptions options) 89 | { 90 | double value; 91 | 92 | if (reader.TokenType == JsonTokenType.Number) 93 | { 94 | if (reader.TryGetInt32(out int numberInt)) 95 | { 96 | value = numberInt; 97 | } 98 | else if (reader.TryGetDouble(out double numberDouble)) 99 | { 100 | value = numberDouble; 101 | } 102 | else 103 | { 104 | throw new JsonException("Invalid number format"); 105 | } 106 | } 107 | else if (reader.TokenType == JsonTokenType.String) 108 | { 109 | string text = reader.GetString(); 110 | if (!double.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out double result)) 111 | { 112 | throw new JsonException($"Encountered text while parsing string: \"{text}\", but it is not a number"); 113 | } 114 | 115 | value = result; 116 | Debug.LogWarning($"Encountered text while parsing number: \"{text}\": please change it to a number by removing the \" in the JSON file."); 117 | } 118 | else 119 | { 120 | throw new JsonException($"Invalid token type: {reader.TokenType}"); 121 | } 122 | 123 | try 124 | { 125 | return value; 126 | } 127 | catch (InvalidCastException ex) 128 | { 129 | throw new JsonException($"Value is <{value.GetType().Name}> instead of "); 130 | } 131 | } 132 | 133 | public static T Object(ref Utf8JsonReader reader, JsonSerializerOptions options) => 134 | JsonSerializer.Deserialize(ref reader, options); 135 | 136 | public static ValueUnit ValueUnit(ref Utf8JsonReader reader, JsonSerializerOptions options) 137 | { 138 | double v = 0; 139 | string u = string.Empty; 140 | if (reader.TokenType == JsonTokenType.Number) 141 | { 142 | v = Double(ref reader, options); 143 | return new ValueUnit(v, u); 144 | } 145 | 146 | while (reader.Read() && reader.TokenType != JsonTokenType.EndObject) 147 | { 148 | if (reader.TokenType == JsonTokenType.PropertyName) 149 | { 150 | string innerPropertyName = reader.GetString(); 151 | reader.Read(); // Move to the value 152 | 153 | if (innerPropertyName == "v") 154 | { 155 | v = reader.GetDouble(); 156 | } 157 | else if (innerPropertyName == "u") 158 | { 159 | u = reader.GetString(); 160 | // superscripts 161 | u = u.Replace('2', '\u00B2'); 162 | u = u.Replace('3', '\u00B3'); 163 | } 164 | } 165 | } 166 | 167 | return new ValueUnit(v, u); 168 | } 169 | 170 | public static T[] Array(ref Utf8JsonReader reader, JsonSerializerOptions options) => 171 | JsonSerializer.Deserialize(ref reader, options); 172 | 173 | public static string[] Array(ref Utf8JsonReader reader, JsonSerializerOptions options) => 174 | JsonSerializer.Deserialize(ref reader, options); 175 | 176 | public static Vector2 Vector2(ref Utf8JsonReader reader, JsonSerializerOptions options) 177 | { 178 | if (reader.TokenType == JsonTokenType.StartArray) 179 | { 180 | float[] array = Array(ref reader, options); 181 | return new Vector2(array[0], array[1]); 182 | } 183 | 184 | if (reader.TokenType == JsonTokenType.StartObject) 185 | { 186 | float x = 0, y = 0; 187 | 188 | while (reader.Read()) 189 | { 190 | string propertyName = string.Empty; 191 | 192 | if (reader.TokenType == JsonTokenType.PropertyName) 193 | { 194 | propertyName = reader.GetString(); 195 | reader.Read(); 196 | } 197 | else if (reader.TokenType == JsonTokenType.EndObject) 198 | { 199 | break; 200 | } 201 | 202 | if (propertyName.ToLowerInvariant() == "x") 203 | { 204 | x = reader.GetSingle(); 205 | } 206 | else if (propertyName.ToLowerInvariant() == "y") 207 | { 208 | y = reader.GetSingle(); 209 | } 210 | } 211 | 212 | return new Vector2(x, y); 213 | } 214 | 215 | throw new JsonException($"Cannot parse {nameof(Vector2)}"); 216 | } 217 | 218 | public static Vector3 Vector3(ref Utf8JsonReader reader, JsonSerializerOptions options) 219 | { 220 | if (reader.TokenType == JsonTokenType.StartArray) 221 | { 222 | float[] array = Array(ref reader, options); 223 | return new Vector3(array[0], array[1], array[2]); 224 | } 225 | 226 | if (reader.TokenType == JsonTokenType.String) 227 | { 228 | string text = reader.GetString(); 229 | Vector3 vector = ConverterExtensions.StringToVector3(text); 230 | return vector; 231 | } 232 | 233 | throw new JsonException($"Cannot parse {nameof(Vector3)}"); 234 | } 235 | 236 | public static Dictionary Dictionary(ref Utf8JsonReader reader, JsonSerializerOptions options) 237 | { 238 | if (reader.TokenType != JsonTokenType.StartObject) 239 | { 240 | throw new JsonException($"Invalid Token type: <{reader.TokenType}>"); 241 | } 242 | 243 | Dictionary dict = new(); 244 | 245 | string tag = $"Dictionary"; 246 | 247 | while (reader.Read()) 248 | { 249 | if (reader.TokenType == JsonTokenType.EndObject) 250 | { 251 | return dict; 252 | } 253 | 254 | string key = reader.GetString(); 255 | if (string.IsNullOrEmpty(key)) 256 | { 257 | throw new JsonException($"{tag}: key cannot be null"); 258 | } 259 | 260 | reader.Read(); 261 | 262 | bool valueType = true; 263 | object value = null; 264 | switch (reader.TokenType) 265 | { 266 | case JsonTokenType.Number: 267 | value = typeof(TValue).Name switch 268 | { 269 | nameof(Int32) => reader.GetInt32(), 270 | nameof(Double) => reader.GetDouble(), 271 | nameof(Single) => Float(ref reader, options), 272 | _ => throw new JsonException($"{tag}.{key}: invalid number format <{typeof(TValue)}>") 273 | }; 274 | break; 275 | 276 | case JsonTokenType.String: 277 | value = reader.GetString(); 278 | break; 279 | case JsonTokenType.True: 280 | value = true; 281 | break; 282 | 283 | case JsonTokenType.False: 284 | value = false; 285 | break; 286 | 287 | case JsonTokenType.StartObject: 288 | value = JsonSerializer.Deserialize(ref reader, options); 289 | valueType = false; 290 | break; 291 | 292 | default: 293 | throw new JsonException($"{tag} {key}: Unsupported value type: {reader.TokenType}"); 294 | } 295 | 296 | try 297 | { 298 | TValue tValue; 299 | if (valueType) 300 | { 301 | tValue = (TValue)Convert.ChangeType(value, typeof(TValue)); 302 | } 303 | else 304 | { 305 | tValue = (TValue)value; 306 | } 307 | 308 | dict.Add(key, tValue); 309 | } 310 | catch (InvalidCastException ex) 311 | { 312 | throw new JsonException($"[{tag}] Value is <{value?.GetType().Name ?? "null"}> instead of <{typeof(TValue).Name}>"); 313 | } 314 | } 315 | 316 | return dict; 317 | } 318 | 319 | public static string UnityType(ref Utf8JsonReader reader, JsonSerializerOptions options) 320 | { 321 | string type = reader.GetString(); 322 | if (string.IsNullOrEmpty(type)) 323 | { 324 | return string.Empty; 325 | } 326 | 327 | if (type.Contains("Unity")) 328 | { 329 | type = Regex.Replace(type, @"^Unity\.(.*)$", "UnityEngine.$1Module"); 330 | } 331 | 332 | return type; 333 | } 334 | 335 | public static List List(ref Utf8JsonReader reader, JsonSerializerOptions options) 336 | where TConverter : JsonConverter, new() 337 | { 338 | if (reader.TokenType != JsonTokenType.StartArray) 339 | { 340 | throw new JsonException($"Invalid Token type: <{reader.TokenType}>"); 341 | } 342 | 343 | return JsonSerializer.Deserialize>(ref reader, options); 344 | } 345 | } 346 | } --------------------------------------------------------------------------------