Skip to content

Commit

Permalink
Merge branch 'feature/custom-reference-frames' (Fix #252) (#351, #352)
Browse files Browse the repository at this point in the history
  • Loading branch information
djungelorm committed Sep 30, 2016
2 parents a686409 + e741763 commit 0baa9d6
Show file tree
Hide file tree
Showing 6 changed files with 224 additions and 3 deletions.
2 changes: 2 additions & 0 deletions doc/order.txt
Expand Up @@ -679,6 +679,8 @@ SpaceCenter.Node.OrbitalReferenceFrame
SpaceCenter.Node.Position
SpaceCenter.Node.Direction
SpaceCenter.ReferenceFrame
SpaceCenter.ReferenceFrame.CreateRelative
SpaceCenter.ReferenceFrame.CreateHybrid
SpaceCenter.AutoPilot
SpaceCenter.AutoPilot.Engage
SpaceCenter.AutoPilot.Disengage
Expand Down
32 changes: 32 additions & 0 deletions doc/src/tutorials/reference-frames.rst
Expand Up @@ -153,6 +153,38 @@ kRPC provides the following reference frames:
* :attr:`DockingPort.reference_frame`
* :attr:`Thruster.thrust_reference_frame`

Relative and hybrid reference frames can also be constructed from the above.

Custom Reference Frames
-----------------------

Custom reference frames can be constructed from the built in frames listed
above. They come in two varieties: 'relative' and 'hybrid'.

A relative reference frame is constructed from a parent reference frame, a fixed
position offset and a fixed rotation offset. For example, this could be used to
construct a reference frame whose origin is 10m below the vessel as follows, by
applying a position offset of 10 along the z-axis to
:attr:`Vessel.reference_frame`. Relative reference frames can be constructed by
calling `:meth:ReferenceFrame.CreateRelative`.

A hybrid reference frame inherits its components (position, rotation, velocity
and angular velocity) from the components of other reference frames. Note that
these components need not be fixed. For example, you could construct a reference
frame whose position is the center of mass of the vessel (inherited from
:attr:`Vessel.reference_frame`) and whose rotation is that of the planet being
orbited (inherited from :attr:`CelestialBody.reference_frame`). Relative
reference frames can be constructed by calling
`:meth:ReferenceFrame.CreateHybrid`.

The parent reference frame(s) of a custom reference frame can also be other
custom reference frames. For example, you could combine the two example frames
from above: construct a hybrid reference frame, centered on the vessel and
rotated with the planet being orbited, and then create a relative reference that
offsets the position of this 10m along the z-axis. The resulting frame will have
its origin 10m below the vessel, and will be rotated with the planet being
orbited.

Converting Between Reference Frames
-----------------------------------

Expand Down
3 changes: 3 additions & 0 deletions service/SpaceCenter/CHANGES.txt
@@ -1,3 +1,6 @@
v0.3.7
* Add custom reference frames (ReferenceFrame.CreateRelative and CreateHybrid) (#252)

v0.3.6
* Add Part.Tag which can be used to get/set the name tag set via the NameTag mod, or kOS (#297)
* Add Parts.WithTag to get a list of all parts with the given tag value
Expand Down
125 changes: 123 additions & 2 deletions service/SpaceCenter/src/Services/ReferenceFrame.cs
Expand Up @@ -5,6 +5,8 @@
using KRPC.SpaceCenter.Services.Parts;
using KRPC.Utils;
using UnityEngine;
using Tuple3 = KRPC.Utils.Tuple<double, double, double>;
using Tuple4 = KRPC.Utils.Tuple<double, double, double, double>;

namespace KRPC.SpaceCenter.Services
{
Expand All @@ -25,6 +27,7 @@ namespace KRPC.SpaceCenter.Services
[KRPCClass (Service = "SpaceCenter")]
[SuppressMessage ("Gendarme.Rules.Maintainability", "AvoidLackOfCohesionOfMethodsRule")]
[SuppressMessage ("Gendarme.Rules.Maintainability", "VariableNamesShouldNotMatchFieldNamesRule")]
[SuppressMessage ("Gendarme.Rules.Smells", "AvoidLargeClassesRule")]
public class ReferenceFrame : Equatable<ReferenceFrame>
{
readonly ReferenceFrameType type;
Expand All @@ -34,11 +37,23 @@ public class ReferenceFrame : Equatable<ReferenceFrame>
readonly uint partId;
readonly ModuleDockingNode dockingPort;
readonly Thruster thruster;
readonly ReferenceFrame parent;
readonly Vector3d relativePosition;
readonly QuaternionD relativeRotation;
readonly Vector3d relativeVelocity;
readonly Vector3d relativeAngularVelocity;
readonly ReferenceFrame hybridPosition;
readonly ReferenceFrame hybridRotation;
readonly ReferenceFrame hybridVelocity;
readonly ReferenceFrame hybridAngularVelocity;

[SuppressMessage ("Gendarme.Rules.Smells", "AvoidLongParameterListsRule")]
ReferenceFrame (
ReferenceFrameType type, global::CelestialBody body = null, global::Vessel vessel = null,
ManeuverNode node = null, Part part = null, ModuleDockingNode dockingPort = null, Thruster thruster = null)
ManeuverNode node = null, Part part = null, ModuleDockingNode dockingPort = null,
Thruster thruster = null, ReferenceFrame parent = null,
ReferenceFrame hybridPosition = null, ReferenceFrame hybridRotation = null,
ReferenceFrame hybridVelocity = null, ReferenceFrame hybridAngularVelocity = null)
{
this.type = type;
this.body = body;
Expand All @@ -49,6 +64,22 @@ public class ReferenceFrame : Equatable<ReferenceFrame>
partId = part.flightID;
this.dockingPort = dockingPort;
this.thruster = thruster;
this.parent = parent;
this.hybridPosition = hybridPosition;
this.hybridRotation = hybridRotation;
this.hybridVelocity = hybridVelocity;
this.hybridAngularVelocity = hybridAngularVelocity;
}

[SuppressMessage ("Gendarme.Rules.Smells", "AvoidLongParameterListsRule")]
ReferenceFrame (ReferenceFrame parent, Vector3d relativePosition, QuaternionD relativeRotation, Vector3d relativeVelocity, Vector3d relativeAngularVelocity)
{
type = ReferenceFrameType.Relative;
this.parent = parent;
this.relativePosition = relativePosition;
this.relativeRotation = relativeRotation;
this.relativeVelocity = relativeVelocity;
this.relativeAngularVelocity = relativeAngularVelocity;
}

/// <summary>
Expand All @@ -64,7 +95,16 @@ public override bool Equals (ReferenceFrame other)
node == other.node &&
partId == other.partId &&
dockingPort == other.dockingPort &&
thruster == other.thruster;
thruster == other.thruster &&
parent == other.parent &&
relativePosition == other.relativePosition &&
relativeRotation == other.relativeRotation &&
relativeVelocity == other.relativeVelocity &&
relativeAngularVelocity == other.relativeAngularVelocity &&
hybridPosition == other.hybridPosition &&
hybridRotation == other.hybridRotation &&
hybridVelocity == other.hybridVelocity &&
hybridAngularVelocity == other.hybridAngularVelocity;
}

/// <summary>
Expand All @@ -83,6 +123,20 @@ public override int GetHashCode ()
hash ^= dockingPort.GetHashCode ();
if (thruster != null)
hash ^= thruster.GetHashCode ();
if (parent != null)
hash ^= parent.GetHashCode ();
hash ^= relativePosition.GetHashCode ();
hash ^= relativeRotation.GetHashCode ();
hash ^= relativeVelocity.GetHashCode ();
hash ^= relativeAngularVelocity.GetHashCode ();
if (hybridPosition != null)
hash ^= hybridPosition.GetHashCode ();
if (hybridRotation != null)
hash ^= hybridRotation.GetHashCode ();
if (hybridVelocity != null)
hash ^= hybridVelocity.GetHashCode ();
if (hybridAngularVelocity != null)
hash ^= hybridAngularVelocity.GetHashCode ();
return hash;
}

Expand Down Expand Up @@ -177,6 +231,9 @@ public override int GetHashCode ()
return dockingPort.nodeTransform;
case ReferenceFrameType.Thrust:
return thruster.WorldTransform;
case ReferenceFrameType.Relative:
case ReferenceFrameType.Hybrid:
throw new InvalidOperationException ("Transform not available for relative or hybrid frames");
default:
throw new InvalidOperationException ("No such reference frame");
}
Expand Down Expand Up @@ -266,6 +323,50 @@ internal static ReferenceFrame Thrust (Thruster thruster)
return new ReferenceFrame (ReferenceFrameType.Thrust, part: thruster.Part.InternalPart, thruster: thruster);
}

/// <summary>
/// Create a relative reference frame.
/// </summary>
/// <param name="referenceFrame">The parent reference frame.</param>
/// <param name="position">The offset of the position of the origin.</param>
/// <param name="rotation">The rotation to apply to the parent frames rotation, as a quaternion. Defaults to zero.</param>
/// <param name="velocity">The linear velocity to offset the parent frame by. Defaults to zero.</param>
/// <param name="angularVelocity">The angular velocity to offset the parent frame by. Defaults to zero.</param>
//FIXME: make rotation, velocity and angularVelocity default to null when null tuple bug is fixed
[KRPCMethod]
public static ReferenceFrame CreateRelative (ReferenceFrame referenceFrame, Tuple3 position, Tuple4 rotation, Tuple3 velocity, Tuple3 angularVelocity)
{
return new ReferenceFrame (
referenceFrame,
position.ToVector (),
rotation != null ? rotation.ToQuaternion () : QuaternionD.identity,
velocity != null ? velocity.ToVector () : Vector3d.zero,
angularVelocity != null ? angularVelocity.ToVector () : Vector3d.zero);
}

/// <summary>
/// Create a hybrid reference frame, which is a custom reference frame
/// whose components are inherited from other reference frames.
/// </summary>
/// <param name="position">The reference frame providing the position of the origin.</param>
/// <param name="rotation">The reference frame providing the orientation of the frame.</param>
/// <param name="velocity">The reference frame providing the linear velocity of the frame.</param>
/// <param name="angularVelocity">The reference frame providing the angular velocity of the frame.</param>
/// <remarks>
/// The <paramref name="position"/> is required but all other reference frames are optional.
/// If omitted, they are set to the <paramref name="position"/> reference frame.
/// </remarks>
[KRPCMethod]
public static ReferenceFrame CreateHybrid (ReferenceFrame position, ReferenceFrame rotation = null, ReferenceFrame velocity = null, ReferenceFrame angularVelocity = null)
{
if (rotation == null)
rotation = position;
if (velocity == null)
velocity = position;
if (angularVelocity == null)
angularVelocity = position;
return new ReferenceFrame (ReferenceFrameType.Hybrid, hybridPosition: position, hybridRotation: rotation, hybridVelocity: velocity, hybridAngularVelocity: angularVelocity);
}

/// <summary>
/// Returns the position of the origin of the reference frame in world-space.
/// </summary>
Expand Down Expand Up @@ -302,6 +403,10 @@ internal static ReferenceFrame Thrust (Thruster thruster)
return dockingPort.nodeTransform.position;
case ReferenceFrameType.Thrust:
return thruster.WorldTransform.position;
case ReferenceFrameType.Relative:
return parent.PositionToWorldSpace (relativePosition);
case ReferenceFrameType.Hybrid:
return hybridPosition.Position;
default:
throw new InvalidOperationException ();
}
Expand Down Expand Up @@ -388,6 +493,10 @@ static Vector3d ToNorthPole (global::Vessel vessel)
return dockingPort.nodeTransform.forward;
case ReferenceFrameType.Thrust:
return thruster.WorldThrustDirection;
case ReferenceFrameType.Relative:
return parent.DirectionToWorldSpace (relativeRotation * Vector3d.up);
case ReferenceFrameType.Hybrid:
return hybridRotation.UpNotNormalized;
default:
throw new InvalidOperationException ();
}
Expand Down Expand Up @@ -452,6 +561,10 @@ static Vector3d ToNorthPole (global::Vessel vessel)
return -dockingPort.nodeTransform.up;
case ReferenceFrameType.Thrust:
return thruster.WorldThrustPerpendicularDirection;
case ReferenceFrameType.Relative:
return parent.DirectionToWorldSpace (relativeRotation * Vector3d.forward);
case ReferenceFrameType.Hybrid:
return hybridRotation.ForwardNotNormalized;
default:
throw new InvalidOperationException ();
}
Expand Down Expand Up @@ -482,6 +595,10 @@ static Vector3d ToNorthPole (global::Vessel vessel)
return InternalPart.vessel.GetOrbit ().GetVel ();
case ReferenceFrameType.DockingPort:
return dockingPort.vessel.GetOrbit ().GetVel ();
case ReferenceFrameType.Relative:
return parent.VelocityToWorldSpace (Vector3d.zero, relativeVelocity);
case ReferenceFrameType.Hybrid:
return hybridVelocity.Velocity;
default:
throw new InvalidOperationException ();
}
Expand Down Expand Up @@ -514,6 +631,10 @@ static Vector3d ToNorthPole (global::Vessel vessel)
case ReferenceFrameType.DockingPort:
case ReferenceFrameType.Thrust:
return Vector3d.zero; //TODO: check this
case ReferenceFrameType.Relative:
return parent.AngularVelocityToWorldSpace (relativeAngularVelocity);
case ReferenceFrameType.Hybrid:
return hybridVelocity.AngularVelocity;
default:
throw new InvalidOperationException ();
}
Expand Down
10 changes: 9 additions & 1 deletion service/SpaceCenter/src/Services/ReferenceFrameType.cs
Expand Up @@ -63,6 +63,14 @@ public enum ReferenceFrameType
/// Centered on a thruster (e.g. an engine nozzle) and
/// oriented with the direction of thrust.
/// </summary>
Thrust
Thrust,
/// <summary>
/// Centered and rotated by fixed quantities relative to another reference frame.
/// </summary>
Relative,
/// <summary>
/// Centered and rotated based on quantities from other reference frames.
/// </summary>
Hybrid
}
}
55 changes: 55 additions & 0 deletions service/SpaceCenter/test/test_referenceframe.py
@@ -1,4 +1,5 @@
import unittest
import math
import krpctest
from krpctest.geometry import compute_position, norm, dot
import krpc
Expand Down Expand Up @@ -173,5 +174,59 @@ def test_node_orbital_direction(self):
# TODO: implement
pass

def test_relative_position(self):
position = (1, 2, 3)
#FIXME: remove default arguments when they are fixed on the server
ref = self.space_center.ReferenceFrame.create_relative(
self.vessel.reference_frame, position, (0, 0, 0, 1), (0, 0, 0), (0, 0, 0))
self.assertAlmostEqual(tuple(-x for x in position), self.vessel.position(ref))

def test_relative_rotation(self):
cases = [
# 90 degrees rotation around x
# vessel points along -z
{'rot': (math.sin(math.pi/4), 0, 0, math.cos(math.pi/4)), 'dir': (0, 0, -1)},
# 90 degrees rotation around y
# vessel points along y
{'rot': (0, math.sin(math.pi/4), 0, math.cos(math.pi/4)), 'dir': (0, 1, 0)},
# 90 degrees rotation around z
# vessel points along x
{'rot': (0, 0, math.sin(math.pi/4), math.cos(math.pi/4)), 'dir': (1, 0, 0)}
]
self.assertAlmostEqual((0, 1, 0), self.vessel.direction(self.vessel.reference_frame), places=4)
for case in cases:
#FIXME: remove default arguments when they are fixed on the server
ref = self.space_center.ReferenceFrame.create_relative(
self.vessel.reference_frame, (0, 0, 0), case['rot'], (0, 0, 0), (0, 0, 0))
self.assertAlmostEqual(case['dir'], self.vessel.direction(ref), places=4)

def test_relative_velocity(self):
velocity = (1, 2, 3)
#FIXME: remove default arguments when they are fixed on the server
ref = self.space_center.ReferenceFrame.create_relative(
self.vessel.reference_frame, (0, 0, 0), (0, 0, 0, 1), velocity, (0, 0, 0))
self.assertAlmostEqual(tuple(-x for x in velocity), self.vessel.velocity(ref), places=4)

def test_relative_angular_velocity(self):
angular_velocity = (1, 2, 3)
ref = self.space_center.ReferenceFrame.create_relative(
self.vessel.reference_frame, (0, 0, 0), (0, 0, 0, 1), (0, 0, 0), angular_velocity)
self.assertAlmostEqual(tuple(-x for x in angular_velocity), self.vessel.angular_velocity(ref), places=4)

def test_hybrid(self):
position = self.vessel.reference_frame
direction = self.vessel.orbital_reference_frame
velocity = self.vessel.surface_reference_frame
angular_velocity = self.vessel.parts.root.reference_frame
ref = self.space_center.ReferenceFrame.create_hybrid(position, direction, velocity, angular_velocity)
self.assertAlmostEqual(
self.vessel.position(position), self.vessel.position(ref), places=3)
self.assertAlmostEqual(
self.vessel.direction(direction), self.vessel.direction(ref), places=4)
self.assertAlmostEqual(
self.vessel.velocity(velocity), self.vessel.velocity(ref), places=4)
self.assertAlmostEqual(
self.vessel.angular_velocity(angular_velocity), self.vessel.angular_velocity(ref), places=4)

if __name__ == '__main__':
unittest.main()

0 comments on commit 0baa9d6

Please sign in to comment.