More actions
Guide to the KSP 2 Transform system
KSP 2 uses a 64-bt double-precision system for tracking object position and movement. This guide attempts to explain how it works and how to use it.
The system is used to keep track of all objects in game, is used for game save state, and dealing with position between objects very long distances away. It works in tandem with Unity's 32-bit space, but provides better accuracy when dealing with astronomical distances or sizes.
This guide will assume some knowledge of how Unity transforms works, and 3d programming concepts such as points, vectors, quaternions, etc.
Major classes / structs
TransformFrame
TransformFrame represents a reference frame (Cartesian coordinate system) that can contain points or other spatial objects. They can be nested, that is, a frame can be "inside" of another frame.
Moving points and vectors from one transform frame to another is similar to using Unity's Transform matricies to move between objects spaces. However, there is no "global" space. There is a galactic frame (GameInstance.UniverseModel.GalacticOrigin) that serves as the root frame that all bodies move inside of (its position is (0,0,0), but most calculations will try to find the closest common parent. For example, if the player is on the Mun, but the game needs to draw an icon for a vessel on Minmus, the calculation will go through Kerbin's reference frame. Calculations with no common parent frame may fail or be invalid.
TransformModel
TransformModel represents a particular object inside of a reference frame. It is similar to Unity's Transform class.
TransformModel has a 1:1 relationship with the simulation SimulationObjectModel.
A model can have other models as children, or directly "float" in the reference frame.
Primitive types
There are double-precision types comparable to common Unit types, but using double or other underlying storage. These are straight forward replacements for various unity single-precision types. They are castable too and from the corresponding Unity types.
All of these primitive types are value types (struct).
KSP2 primative | Unity Analog | Underlying storage |
---|---|---|
Matrix4x4D | Matrix4x4 | Unity.Mathematics.double4x4 |
Vector3d | Vector3 | double (3x) |
Vector4d | Vector4 | double (4x) |
QuaternionD | Quaternion | double (4x) |
Frame tracked types
There are certain types that are similar to primitive types, but include a reference to the coordinate system that they are created with respect to.
The transform system provides helpers to get relative position and rotations between points that are not in the same reference frame without having to explicitly pass around which frame they came from.
KSP2 class | Underlying storage | Unity Analog | Usage notes |
---|---|---|---|
TransformFrame | Vector3d/QuaternionD/Matrix4x4D | (Unity coordinate system) | |
TransformModel | Vector3d/QuaternionD | Transform | |
Position | Vector3d | Vector3 | A vector representing a position |
Rotation | QuaternionD | Quaternion | |
Vector | Vector3d | - | A vector representing a direction |
Velocity | Vector | - | Physics velocity vector |
AngularVelocity | Vector | - |
Interfaces
A number of interfaces denote which methods should be used on a given type.
The "Internal" interfaces are generally meant to be methods that a given type uses when calling between objects of its own type.
The "Driver" interfaces provide notification callbacks for specific value changes.
Name | Implemented by | Use |
---|---|---|
ITransformFrameInternal | TransformFrame | Calls between different kinds of TransformFrame subclasses |
ITransformFrame | TransformFrame | Consumers of TransformFrame |
ITransformModelInternal | TransformModel | Calls between different TransformModel objectss |
ITransformModel | TransformModel | Consumers of TransformModel |
ICoordinateSystem | TransformFrame,
TransformModel |
Translate a position, rotation, vector, or transform matrix that exists in some other ICoordinateSystem into the implementing coordinate system. |
IPositionDriver | (various) | Change or listen for changes to an object's position. |
IRotationDriver | (various) | Change or listen for changes to an object's rotation. |
ILinearMotionDriver | Change or listen for changes to an object's velocity. | |
IAngularMotionDriver | Change or listen for changes to an object's angular velocity. | |
IMotionFrame | Translate a velocity or angular velocity between moving reference frames. | |
IMotion | The velocity and angular velocity between two objects (or reference frames). | |
IMotionRelative | ||
IPhysicsSpaceProvider | PhysicsSpaceProvider | Translate between simulation space and Unity object space |
The relationship between the game simulation and PhysX simulation
Normally, simulation object positions are determined entirely by the game's internal simulation engine. That is, `SimulationObjectModel` (the main object for simulation objects) are moved around by classes that live on the object like `OrbiterComponent`. This allows the game to track and move objects that don't exist in the Unity engine.
In that mode, most entities are essentially abstract. For example, the position of a part on a vessel can be calculated relative to the vessel, and in turn relative the the body it is orbiting. However, the part is not individually simulated (except for components that might do background calculations.). The part's position is just implied based on its relative position to the vessel.
When a vessel gets close enough to the player to be visible in game, the relationship between the parts and the vessel changes.The parts are individually loaded into Unity, the simulation positions are copied to unity GameObjects, and then PhysX eventually becomes the "source of truth" as to where exactly the part is. The vessel position is then calculated as an average of the of all of the part positions.
The coordinate system in Unity is referred to as "physics space" by the simulation code. Physics space is a moving reference frame used for showing sim objects to the player, and leveraging PhysX for physics simulation. Typically, physics space follows a vessel's orbit around (or location on) a planet. This means that while a vessel/part may be moving very fast and traveling long distances in sim terms, it may be moving very slowly and traveling short distances in PhysX terms. This makes better use of PhysX's 32-bit precision by allowing calculations to be performed more precisely.
The game will periodically "reset" physics space according to what the vessel does; It will move and change velocity of the physics space frame, and subtract that movement/position change to keep them from moving too far away from the origin or too high a speed. This is how the "floating origin" concept often mentioned in KSP context is actually implemented.
While a sim object is in physics space, the code will hold one of two states:
- The game simulation position drives the unity position. That is, the unity position is set kinematically by the simulation layer based in the the physics space frame. PhysX is not allowed to move the objects.
- The unity position drives the game sim position. PhysX (or possibly future network sync code) does whatever it wants to set the position in Unity space, and then the resulting unity space position is used to set the simulation position.
When a vessel is being loaded into physics space, it starts out in the first state. It will stay in that state for some number of frames until all of the parts are loaded, unpacked, and initialized in Unity. Then it will switch over to the second state.
When a vessel is unloaded from physics space, it will save the last known sim positions and destroy all of the associated Unity GameObjects. The simulation objects' positions return to being controlled by the simulation engine.
Hand of Kraken
HandOfKraken is a special class for dealing with what players call the "orbital decay" problem. It intermediates and adjusts things between the simulation and physics space frames, so warrants some explanation here. It is only used for active vessels that are being simulated in physics space.
What the problem is
There are two main ways to calculate orbits and object motion:
- Parametric.A relatively simple equation is used to figure out an object's position at a given (possibly future) time. KSP players call this "on rails" orbit calculation.
- Iterative step solving. Based on the current position and forces, calculate the motion a small step at a time into the future.
Both approaches have their respective strengths and weaknesses. The main difference here is that iterative step solvers can suffer from "cumulative drift error." Small rounding errors each iteration can add up to large, unpredictable differences in motion outcome.
Drift error is an inherent problem in iterative step solvers, and PhysX is no exception. In most PhysX use cases, there is some kind of unmoving "ground" that objects sit on, which would prevent them from floating away due to minor velocity calculation errors. Any velocity calculation error would be pulled toward zero as the object encounters friction with the ground. This is actually true for landed vessels, so HandOfKraken is only needed in orbit.
But we don't have a "ground" when vessels are in orbit. PhysX objects are allowed to float freely inside physics space. So errors can accumulate for various difficult to fix reasons. Because the physic space frame is periodically reset to track the motion of the vessel, the errors trickle up into the simulation, eventually resulting in changes to the orbit the player can notice but can't account for. If the changes can't be explained by noticeable things like drag or thrust, it becomes a gameplay bug.
(Aside: There are orbital mechanics, such as tidal forces, that could cause an orbit or motion to change over time without forces like friction or thrust, but the game doesn't intend to simulate these, so in a sense they could be considered bugs even if that is what is causing the error.)
So, the purpose of HandOfKraken is to correct drift errors in situations the player would typically notice and break players' expectations. The player's expectation of a vessel in orbit under no thrust, drag, or other force is that it should follow a Parametric ("rails") style orbital path.
HandOfKraken guarantees that a vessel will follow a "rails" obit despite issues with iterative step solving, if and when it can.
How it works
HandOfKraken monitors the movement of the vessel and the forces it is under, and makes periodic correction.
- When the forces applied to the vessel are go some certain small threshold (close to zero),it will record the current rails orbit and go into "correcting" mode.
- While in correcting mode, it hides the actual orbit from the player, and presents the recorded rails orbit. Any discrepancy between the expected rails orbit and iterative physics orbit is calculated every frame.
- If the discrepancy gets too high, it quietly drags the entire vessel back the position and velocity it should have had as predicted by the recorded rails orbit. (This might be where it gets its name from.)
- If any significant force becomes active on the vessel, it goes out of correcting mode. The orbit shown to the player is then again (as normal) the one derived from the current vessel physics situation. (Normally, a vessel's position and velocity is calculated by averaging all of the individual parts.)