├── 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 | 
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 | }
--------------------------------------------------------------------------------