Skip to content

Commit b0e52f7

Browse files
Feature/improve spacecraft attitude (#280)
* Feature/improve spacecraft attitude computation by introducing GetBodyFront method and updating attitude classes to use it * Feature/add validation for orbital normal computation in attitude classes
1 parent 2707313 commit b0e52f7

29 files changed

Lines changed: 1817 additions & 91 deletions

DEVELOPER_GUIDE.md

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ IO.Astrodynamics.Net is a .NET 8/10 astrodynamics framework for orbital mechanic
1010
- **Ephemeris Computation**: Read and write SPICE kernel files for celestial bodies and spacecraft
1111
- **Time Systems**: Handle multiple time frames (UTC, TDB, TAI, TDT, GPS, Local)
1212
- **Reference Frames**: Transform between inertial and body-fixed frames (ICRF, ECLIPTIC, IAU frames)
13-
- **Spacecraft Modeling**: Define spacecraft with instruments, engines, fuel tanks, and payloads
13+
- **Spacecraft Modeling**: Define spacecraft with instruments, engines, fuel tanks, payloads, and configurable body axes
1414
- **Orbital Propagation**: Propagate orbits using numerical integration with perturbation models
1515
- **Maneuver Planning**: Compute delta-V requirements, launch windows, Lambert transfers
1616
- **Geometry Finding**: Search for occultations, eclipses, visibility windows, illumination conditions
@@ -26,7 +26,7 @@ IO.Astrodynamics.Net is a .NET 8/10 astrodynamics framework for orbital mechanic
2626
- Thermal analysis
2727
- Power system modeling
2828
- Communication link budgets (beyond basic geometry)
29-
- Detailed attitude control system design
29+
- Detailed attitude control system design (attitude determination/planning is supported; closed-loop control is not)
3030

3131
## Standards and Units
3232

@@ -1226,7 +1226,7 @@ Represents a spacecraft with components.
12261226

12271227
| Constructor | Description |
12281228
|------------|-------------|
1229-
| `Spacecraft(int naifId, string name, double dryMass, double maxMass, Clock clock, OrbitalParameters orbit, double sectionalArea = 1.0, double dragCoeff = 2.2, string cosparId = null, double solarRadiationCoeff = 1.0)` | Create spacecraft |
1229+
| `Spacecraft(int naifId, string name, double dryMass, double maxMass, Clock clock, OrbitalParameters orbit, double sectionalArea = 1.0, double dragCoeff = 2.2, string cosparId = null, double solarRadiationCoeff = 1.0, Vector3? bodyFront = null, Vector3? bodyRight = null, Vector3? bodyUp = null)` | Create spacecraft |
12301230

12311231
| Property | Description |
12321232
|----------|-------------|
@@ -1240,6 +1240,12 @@ Represents a spacecraft with components.
12401240
| `SolarRadiationCoeff` | Solar radiation pressure coefficient Cr (default 1.0, range 1.0–2.0) |
12411241
| `InitialOrbitalParameters` | Initial orbit |
12421242
| `StateVectorsRelativeToICRF` | Propagated states |
1243+
| `BodyFront` | Instance body front axis (default: +Y, same as `Spacecraft.Front`) |
1244+
| `BodyRight` | Instance body right axis (default: +X, same as `Spacecraft.Right`) |
1245+
| `BodyUp` | Instance body up axis (default: +Z, same as `Spacecraft.Up`) |
1246+
| `BodyBack` / `BodyLeft` / `BodyDown` | Computed inverses of body axes |
1247+
1248+
**Configurable body axes**: Pass `bodyFront`, `bodyRight`, `bodyUp` to the constructor to override the default body frame. Custom axes must be orthogonal and right-handed (`bodyRight.Cross(bodyFront) == bodyUp`). All attitude maneuvers automatically use per-instance body axes via `GetBodyFront()`.
12431249

12441250
| Method | Description |
12451251
|--------|-------------|
@@ -1385,9 +1391,13 @@ Base class for orbital maneuvers.
13851391
- `RetrogradeAttitude`: Orient opposite to velocity
13861392
- `ZenithAttitude`: Orient away from central body
13871393
- `NadirAttitude`: Orient toward central body
1394+
- `NormalAttitude`: Orient along orbital normal (h = r × v)
1395+
- `AntiNormalAttitude`: Orient opposite to orbital normal (−h)
13881396
- `InstrumentPointingAttitude`: Point instrument at target (single-vector, roll unconstrained)
13891397
- `TriadAttitude`: Fully-constrained 3-DOF attitude using TRIAD algorithm (eliminates roll ambiguity)
13901398

1399+
All attitude maneuvers support configurable body axes via `GetBodyFront()`, which reads per-instance axes from `Spacecraft.BodyFront` with fallback to the static default `Spacecraft.Front` (+Y).
1400+
13911401
**TRIAD Attitude Determination**
13921402

13931403
The `TriadAttitude` class uses two non-collinear observation vectors to fully constrain spacecraft attitude. Unlike single-vector pointing, TRIAD eliminates roll ambiguity by constraining all three degrees of freedom.
@@ -1406,12 +1416,29 @@ var attitude = new TriadAttitude(
14061416
camera, // Uses camera boresight (primary) and refVector (secondary)
14071417
earth, sun, // Primary target, secondary target
14081418
engine);
1419+
1420+
// Using IAttitudeTarget (orbital directions and/or celestial bodies)
1421+
var attitude = new TriadAttitude(
1422+
earth, epoch, duration,
1423+
Spacecraft.Front, OrbitalDirectionTarget.Prograde, // Primary: prograde pointing
1424+
Spacecraft.Up, new CelestialAttitudeTarget(sun), // Secondary: solar panel toward Sun
1425+
engine);
1426+
1427+
// Factory methods for common operational attitudes
1428+
var lvlh = TriadAttitude.CreateLVLH(earth, epoch, duration, engine);
1429+
var sunTrack = TriadAttitude.CreateProgradeWithSunTracking(earth, epoch, duration, sun, engine);
14091430
```
14101431

1432+
**IAttitudeTarget system**: The `IAttitudeTarget` interface enables orbital directions and celestial bodies to be used interchangeably as TRIAD targets:
1433+
- `OrbitalDirectionTarget` — predefined static instances: `.Prograde`, `.Retrograde`, `.Nadir`, `.Zenith`, `.Normal`, `.AntiNormal`
1434+
- `CelestialAttitudeTarget` — wraps `ILocalizable`, computes direction via ephemeris with light-time correction
1435+
- `OrbitalDirection` enum: `Prograde`, `Retrograde`, `Nadir`, `Zenith`, `Normal`, `AntiNormal`
1436+
14111437
Key features:
14121438
- Minimum 5-degree separation required between body vectors and reference vectors
1413-
- Uses spacecraft body frame: Front (+Y), Right (+X), Up (+Z)
1414-
- Three constructor overloads: single instrument, dual instruments, explicit vectors
1439+
- Uses spacecraft body frame: Front (+Y), Right (+X), Up (+Z) — or per-instance `BodyFront`, `BodyRight`, `BodyUp`
1440+
- Four constructor overloads: single instrument, dual instruments, explicit vectors, IAttitudeTarget
1441+
- Factory methods: `CreateLVLH()`, `CreateProgradeWithSunTracking()`
14151442

14161443
#### Impulse maneuvers
14171444

@@ -2712,8 +2739,25 @@ Console.WriteLine("Spacecraft maintains nadir pointing with roll constrained tow
27122739
```
27132740

27142741
**When to use TRIAD vs single-vector attitude:**
2715-
- Use `InstrumentPointingToAttitude`, `NadirAttitude`, etc. when roll is unconstrained (simpler, faster)
2742+
- Use `InstrumentPointingToAttitude`, `NadirAttitude`, `NormalAttitude`, etc. when roll is unconstrained (simpler, faster)
2743+
- Use `NormalAttitude` / `AntiNormalAttitude` for plane-change burns (h = r × v)
27162744
- Use `TriadAttitude` when you need specific roll orientation (solar panels, thermal control, dual instruments)
2745+
- Use `TriadAttitude` with `IAttitudeTarget` to mix orbital directions and celestial targets:
2746+
2747+
```csharp
2748+
// LVLH attitude using factory method
2749+
var lvlh = TriadAttitude.CreateLVLH(earth, epoch, duration, engine);
2750+
2751+
// Prograde with sun tracking using factory method
2752+
var sunTrack = TriadAttitude.CreateProgradeWithSunTracking(earth, epoch, duration, sun, engine);
2753+
2754+
// Custom: nadir pointing with normal constraint (using OrbitalDirectionTarget)
2755+
var customLvlh = new TriadAttitude(
2756+
earth, epoch, duration,
2757+
Spacecraft.Down, OrbitalDirectionTarget.Nadir,
2758+
Spacecraft.Front, OrbitalDirectionTarget.Prograde,
2759+
engine);
2760+
```
27172761

27182762
---
27192763

IO.Astrodynamics.Net/.claude/settings.local.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@
3939
"Bash(dotnet run:*)",
4040
"Bash(echo:*)",
4141
"WebFetch(domain:docs.json-everything.net)",
42-
"Bash(dotnet restore:*)"
42+
"Bash(dotnet restore:*)",
43+
"Bash(/home/sw/Applications/Cspice/exe/brief IO.Astrodynamics.Tests/Data/SolarSystem/earth_latest_high_prec.bpc)",
44+
"Bash(git -C /home/sw/Sources/Astrodynamics/IO.Astrodynamics.Net stash)"
4345
],
4446
"deny": [],
4547
"ask": []

IO.Astrodynamics.Net/CLAUDE.md

Lines changed: 76 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ dotnet tool install --global --add-source ./IO.Astrodynamics.CLI/bin/Debug IO.As
6868
- `IO.Astrodynamics.OrbitalParameters.TLE`: Two-Line Element sets and OMM support
6969
- `IO.Astrodynamics.CCSDS.OMM`: CCSDS Orbit Mean-elements Message support (read/write/validate/convert)
7070
- `IO.Astrodynamics.CCSDS.OPM`: CCSDS Orbit Parameter Message support (read/write/validate/convert)
71-
- `IO.Astrodynamics.Maneuver`: Lambert solvers, launch windows, maneuver planning, attitude maneuvers
71+
- `IO.Astrodynamics.Maneuver`: Lambert solvers, launch windows, maneuver planning, attitude maneuvers, IAttitudeTarget system (orbital direction targets, celestial attitude targets)
7272
- `IO.Astrodynamics.Frames`: Reference frames and transformations
7373
- `IO.Astrodynamics.TimeSystem`: Time frames (UTC, TDB, TAI, etc.)
7474
- `IO.Astrodynamics.Propagator`: Orbital propagation and integration (Velocity-Verlet symplectic integrator)
@@ -480,6 +480,8 @@ The framework provides a family of attitude maneuvers for spacecraft orientation
480480
- `ZenithAttitude`: Points toward zenith (away from central body)
481481
- `ProgradeAttitude`: Velocity-direction pointing
482482
- `RetrogradeAttitude`: Anti-velocity pointing
483+
- `NormalAttitude`: Orbital normal pointing (h = r × v)
484+
- `AntiNormalAttitude`: Anti-normal pointing (−h)
483485
- `InstrumentPointingToAttitude`: Single-vector instrument pointing at a target
484486
- `TriadAttitude`: Two-vector fully-constrained attitude (eliminates roll ambiguity)
485487

@@ -493,11 +495,30 @@ The `TriadAttitude` class implements the TRIAD algorithm for fully-constrained 3
493495
- **Minimum vector separation**: Default 5 degrees; prevents numerical instability from near-collinear vectors
494496

495497
**Spacecraft Body Frame Convention**
498+
499+
Default body axes (static fields, backward-compatible):
496500
- Front (+Y): `Spacecraft.Front`
497501
- Right (+X): `Spacecraft.Right`
498502
- Up (+Z): `Spacecraft.Up`
499503
- Down (-Z): `Spacecraft.Down`
500504

505+
**Configurable Body Axes** — per-instance overrides for non-standard body frames (e.g., +X forward):
506+
- `BodyFront`, `BodyRight`, `BodyUp` — instance properties (default to static values)
507+
- `BodyBack`, `BodyLeft`, `BodyDown` — computed inverses
508+
- Passed via optional constructor parameters: `bodyFront`, `bodyRight`, `bodyUp`
509+
- Validation enforces orthogonality and right-handedness when custom axes are provided
510+
- All attitude maneuvers use `GetBodyFront()` (protected helper on `Maneuver` base class) which reads per-instance axes with static fallback
511+
512+
```csharp
513+
// Spacecraft with +X as front (e.g., matching a SPICE FK convention)
514+
var spacecraft = new Spacecraft(-1001, "Sat", 1000, 5000, clock, orbit,
515+
bodyFront: Vector3.VectorX, bodyRight: Vector3.VectorY.Inverse(), bodyUp: Vector3.VectorZ);
516+
517+
// All attitude maneuvers automatically use BodyFront instead of Spacecraft.Front
518+
var prograde = new ProgradeAttitude(earth, epoch, duration, engine);
519+
// → orients spacecraft.BodyFront (+X) along velocity vector
520+
```
521+
501522
**Constructor Options**
502523

503524
1. **Single Instrument**: Uses instrument's boresight (primary) and refVector (secondary)
@@ -529,6 +550,47 @@ var attitude = new TriadAttitude(
529550
engine);
530551
```
531552

553+
4. **IAttitudeTarget (orbital directions and/or celestial bodies)**: Uses `IAttitudeTarget` interface for polymorphic targets
554+
```csharp
555+
// Point prograde with solar panels toward Sun
556+
var attitude = new TriadAttitude(
557+
earth, epoch, duration,
558+
Spacecraft.Front, OrbitalDirectionTarget.Prograde,
559+
Spacecraft.Up, new CelestialAttitudeTarget(sun),
560+
engine);
561+
562+
// Full LVLH attitude (nadir pointing with prograde constraint)
563+
var lvlh = new TriadAttitude(
564+
earth, epoch, duration,
565+
Spacecraft.Down, OrbitalDirectionTarget.Nadir,
566+
Spacecraft.Front, OrbitalDirectionTarget.Prograde,
567+
engine);
568+
```
569+
570+
**IAttitudeTarget System**
571+
572+
The `IAttitudeTarget` interface enables orbital directions and celestial bodies to be used interchangeably as TRIAD targets:
573+
- `IAttitudeTarget.GetDirection(StateVector)` — returns a unit vector in the inertial frame
574+
- `IAttitudeTarget.Name` — human-readable target name
575+
576+
**Implementations:**
577+
- `OrbitalDirectionTarget` — computes direction from state vector (prograde, nadir, normal, etc.)
578+
- Predefined static instances: `OrbitalDirectionTarget.Prograde`, `.Retrograde`, `.Nadir`, `.Zenith`, `.Normal`, `.AntiNormal`
579+
- `CelestialAttitudeTarget` — wraps `ILocalizable`, computes direction via ephemeris with `Aberration.LT`
580+
581+
**`OrbitalDirection` enum**: `Prograde`, `Retrograde`, `Nadir`, `Zenith`, `Normal`, `AntiNormal`
582+
583+
**Factory Methods**
584+
585+
Convenience factories for common operational attitudes:
586+
```csharp
587+
// LVLH: Front→Prograde, Down→Nadir
588+
var lvlh = TriadAttitude.CreateLVLH(earth, epoch, duration, engine);
589+
590+
// Prograde with sun tracking: Front→Prograde, Up→Sun
591+
var sunTrack = TriadAttitude.CreateProgradeWithSunTracking(earth, epoch, duration, sun, engine);
592+
```
593+
532594
**Use Case Examples**
533595

534596
*Earth Observation with Sun Tracking*
@@ -551,6 +613,12 @@ var observation = new TriadAttitude(
551613
engine);
552614
```
553615

616+
*Plane-Change Attitude (Normal Pointing)*
617+
```csharp
618+
// Orient for plane-change burn: front along orbital normal
619+
var normalAttitude = new NormalAttitude(earth, epoch, duration, engine);
620+
```
621+
554622
**Instrument Enhancements**
555623

556624
The `Instrument` class now provides:
@@ -602,9 +670,14 @@ Test data files are in `Data/SolarSystem/` and copied to output directory.
602670
- User-defined parameters support custom mission-specific data
603671
9. **Attitude Maneuvers**: When implementing attitude control:
604672
- Use `TriadAttitude` for fully-constrained orientation (eliminates roll ambiguity)
605-
- Use single-vector attitudes (`NadirAttitude`, `InstrumentPointingToAttitude`) only when roll is unconstrained
673+
- Use single-vector attitudes (`NadirAttitude`, `ProgradeAttitude`, `NormalAttitude`, etc.) only when roll is unconstrained
674+
- Use `NormalAttitude` / `AntiNormalAttitude` for plane-change burns (h = r × v)
606675
- Ensure body vectors and reference vectors are not collinear (minimum 5 degrees separation)
607-
- Use `Spacecraft.Front`, `Spacecraft.Up`, etc. for standard body frame directions
676+
- Use `Spacecraft.Front`, `Spacecraft.Up`, etc. for standard body frame directions; use instance `BodyFront`, `BodyRight`, `BodyUp` for non-standard frames
677+
- Custom body axes must be orthogonal and right-handed (`BodyRight.Cross(BodyFront) == BodyUp`)
678+
- All attitude maneuvers use `GetBodyFront()` which reads per-instance axes with static fallback
679+
- Use `IAttitudeTarget` with `TriadAttitude` to mix orbital directions (`OrbitalDirectionTarget.Prograde`, etc.) and celestial targets (`CelestialAttitudeTarget`)
680+
- Use factory methods `TriadAttitude.CreateLVLH()` and `CreateProgradeWithSunTracking()` for common operational attitudes
608681
- Use `Instrument.GetBoresightInSpacecraftFrame()` and `GetRefVectorInSpacecraftFrame()` for instrument-based pointing
609682
10. **Geopotential Gravity**: When working with spherical harmonic gravity models:
610683
- Pass `GeopotentialModelParameters` to `CelestialBody` constructor to enable geopotential

IO.Astrodynamics.Net/IO.Astrodynamics.CLI/IO.Astrodynamics.CLI.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<FileVersion>0.0.1</FileVersion>
1010
<PackAsTool>true</PackAsTool>
1111
<ToolCommandName>astro</ToolCommandName>
12-
<Version>0.8.3.2</Version>
12+
<Version>0.8.4.0-preview</Version>
1313
<Title>Astrodynamics command line interface</Title>
1414
<Authors>Sylvain Guillet</Authors>
1515
<Description>This CLI allows end user to exploit IO.Astrodynamics framework </Description>

IO.Astrodynamics.Net/IO.Astrodynamics.ConformanceRunner/Properties/launchSettings.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22
"profiles": {
33
"ConformanceRunner": {
44
"commandName": "Project",
5-
"commandLineArgs": "/home/sw/CLionProjects/conformance-tests /home/sw/CLionProjects/Astrodynamics/IO.Astrodynamics.Net/IO.Astrodynamics.Tests/Data/SolarSystem /tmp/conformance-report.json"
5+
"commandLineArgs": "/home/sw/Sources/conformance-tests /home/sw/Sources/Astrodynamics/IO.Astrodynamics.Net/IO.Astrodynamics.Tests/Data/SolarSystem /tmp/conformance-report.json"
66
},
77
"SmokeTest": {
88
"commandName": "Project",
9-
"commandLineArgs": "--smoke-test /home/sw/CLionProjects/Astrodynamics/IO.Astrodynamics.Net/IO.Astrodynamics.Tests/Data/SolarSystem"
9+
"commandLineArgs": "--smoke-test /home/sw/Sources/Astrodynamics/IO.Astrodynamics.Net/IO.Astrodynamics.Tests/Data/SolarSystem"
1010
}
1111
}
1212
}

IO.Astrodynamics.Net/IO.Astrodynamics.ConformanceRunner/Solvers/EclipseSolver.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using IO.Astrodynamics.Frames;
99
using IO.Astrodynamics.Math;
1010
using IO.Astrodynamics.OrbitalParameters;
11+
using IO.Astrodynamics.SolarSystemObjects;
1112
using IO.Astrodynamics.TimeSystem;
1213

1314
namespace IO.Astrodynamics.ConformanceRunner.Solvers;
@@ -36,7 +37,10 @@ public Dictionary<string, object> Solve(CaseInput caseInput)
3637
new Clock("ConfClk", 65536), sv);
3738

3839
spacecraft.Propagate(searchWindow,
39-
[occultingBody],
40+
[
41+
occultingBody, lightSource, PlanetsAndMoons.MOON_BODY, Barycenters.JUPITER_BARYCENTER, Barycenters.MARS_BARYCENTER, Barycenters.MERCURY_BARYCENTER,
42+
Barycenters.SATURN_BARYCENTER, Barycenters.URANUS_BARYCENTER, Barycenters.VENUS_BARYCENTER
43+
],
4044
false, false, TimeSpan.FromSeconds(1.0));
4145

4246
var stepSize = TimeSpan.FromSeconds(60.0);
@@ -128,4 +132,4 @@ private static StateVector BuildStateVector(object orbit, CelestialBody observer
128132

129133
throw new ArgumentException("Unknown orbit type");
130134
}
131-
}
135+
}

0 commit comments

Comments
 (0)