Solar Panels sun tracking
Code side
Class hierarchy
Module_SolarPanel → Module_Deployable → PartBehaviourModule
The tracking logic lives almost entirely in Module_Deployable, with Module_SolarPanel overriding two hooks (CalculateTrackingLOS and PostCalculateTracking) to add occlusion testing and power computation.
Per-frame tick
OnModuleFixedUpdate (Module_Deployable.cs) calls UpdateDeployment, which dispatches to CalculateTracking only when the panel is in Extended state.
CalculateTracking:
Find the tracking target.
GetTrackingBodyTransformFrameresolves atrackingTransform. FortrackingMode = "Sun"it callsGame.UniverseModel.GetStar(transform.Position)to pick the nearest star dynamically.Compute tracking direction. Converts the star's position into the part's local body frame, then transforms it into world space and into the pivot's local space (
relativeDirection).LOS check. Calls
CalculateTrackingLOS(overridden inModule_SolarPanel).Rotate the pivot (if LOS is clear and
isTrackingis on). ForrotationMode = YAW:Computes a yaw delta:
Atan2(relativeDirection.x, z) * 57.3+TrackingAlignmentOffset(90° for the accordion).Applies a
Quaternion.Lerptoward the target usingtrackingSpeed * UniverseTimeDelta, storing the result indataDeployable.currentRotation.
Calls
PostCalculateTrackingonModule_SolarPanelto update exposure and power output.
The actual Transform.localRotation assignment happens in OnLateUpdate, which runs after all physics to avoid conflicts.
LOS / occlusion
Module_SolarPanel.CalculateTrackingLOS:
Checks in order:
SimBlockingBody— set by orbital simulation when the part is in a body's shadow.Incidence dot —
Dot(panelIncidenceDirection, sunDir). If the dot is ≤ 0, the sun is behind the panel and the ray isn't even cast.Physics.Raycast from
secondaryTransform.position + sunDir * RaycastOffsettoward the sun:Hit a
PQSRenderer→ blocked by terrain.Hit a
PartBehaviorthat is notthis.part→ blocked by another vessel part (self-hits filtered out as geometry artifacts, see the issue #684 comment).
Power output
Module_SolarPanel.PostCalculateTracking:
exposure = Dot(PanelIncidenceDirection, sunDir) // 0..1 flowRate = Rate * exposure * SimStarEnergyScale * EfficiencyMultiplier
SimStarEnergyScale is injected by the sim layer to account for distance from the star.
Retraction
On retract, HasTrackerReturnedToOrigin lerps the pivot back to originalRotation (captured at spawn) in sync with the retraction animation, so the panels don't snap.
Prefab side
Example with solararray_2v_accordion.
From the part JSON:
Field | Value | Meaning |
|---|---|---|
|
| The named transform in the mesh that physically rotates to face the sun |
|
| The named transform used as the LOS ray origin |
|
| Tracks the nearest star (resolved dynamically) |
|
| Single-axis rotation only |
|
| 90° correction because |
|
| Slew rate (fraction of remaining angle per |
|
| Incidence dot computed from |
|
| Local +X is the panel's active face axis |
|
| 25 cm offset to prevent the ray from hitting the panel's own collider |
|
| Full 360° rotation allowed |
|
| Enables the tracking behavior at all |
The mesh exposes two named transforms that must exist in the model hierarchy: bone_rotator (rotated each frame by the tracking math) and raycastTransform (a fixed reference point used for the sun-facing dot product and LOS ray origin).