All Downloads are FREE. Search and download functionalities are using the official Maven repository.

us.ihmc.euclid.referenceFrame.ReferenceFrame Maven / Gradle / Ivy

package us.ihmc.euclid.referenceFrame;

import us.ihmc.euclid.exceptions.NotARotationMatrixException;
import us.ihmc.euclid.interfaces.Transformable;
import us.ihmc.euclid.referenceFrame.ReferenceFrameChangedListener.Change;
import us.ihmc.euclid.referenceFrame.exceptions.ReferenceFrameMismatchException;
import us.ihmc.euclid.referenceFrame.interfaces.ReferenceFrameHolder;
import us.ihmc.euclid.referenceFrame.tools.ReferenceFrameTools;
import us.ihmc.euclid.transform.RigidBodyTransform;
import us.ihmc.euclid.transform.interfaces.RigidBodyTransformBasics;
import us.ihmc.euclid.transform.interfaces.RigidBodyTransformReadOnly;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;

/**
 * {@code ReferenceFrame} represents a reference coordinate frame.
 * 

* {@code ReferenceFrame}s are organized as a tree structure. A root reference frame such * {@link #getWorldFrame()} represents a global coordinate system with not parent. From this root * frame, children frame, with constant or variable transforms to the root, can be added recursively * to form the tree structure. This structure allows to define each reference frame with a * unmodifiable parent frame and their transform, i.e, a {@code RigidBodyTransform} providing both * position and orientation, with respect to that same parent frame. The transform to the root * reference frame is then internally computed by computing the path from each reference frame to * the root. *

*

* The most common ways to create a new {@code ReferenceFrame} are: *

    *
  • Using one of the constructors to create either a root reference frame or a child reference * frame. In both cases, the method {@link #updateTransformToParent(RigidBodyTransform)} has to be * overrided. When creating a root reference frame, the method should be empty, while creating a * child reference frame the implementation of the method is used to update the transform of the * child frame with respect to its parent. *
  • Using one of the static methods to create a reference frame with an unchanging transform with * respect to its parent. *
  • Look for extensions of {@code ReferenceFrame}. *
*

*

* Using {@code ReferenceFrame}, a geometry, such as a point, a line, or a convex polygon, can * easily be expressed in its local coordinate system and then transformed to know its coordinates * from another coordinate system perspective. For instance, the location of the toes of a bipedal * robot can be easily determined in the local foot reference frame, then can be easily transformed * to the world frame or pelvis frame to know its location from the perspective of world or the * pelvis. *

*

* {@code ReferenceFrame} is only the base class of the reference frame framework. Several classes * allows for avoiding the burden of tracking to what frame a geometry is attached and how to * express a geometry in a different frame. *

*/ public abstract class ReferenceFrame { /** A string used to separate frame names in the {@link #nameId} of the reference frame */ public static final String SEPARATOR = ":"; public static FrameNameRestrictionLevel DEFAULT_RESTRICTION_LEVEL = FrameNameRestrictionLevel.loadFromEnvironment("euclid.referenceFrame.restrictionLevel", "FrameNameRestrictionLevel", FrameNameRestrictionLevel.NONE); /** The name of this reference frame. The name should preferably be unique. */ private final String frameName; /** * A string that can be used to identify the {@link ReferenceFrame}. It contains the name of the * frame itself and all parents up to the root frame and is used for the {@link #hashCode()} and * {@link #equals()} methods. *

* In contrast to the {@link #frameIndex} this is a name based identifier that is only dependent on * names of frames. Note, that this means that two frames with the same name will be considered * equal even though they might be in different locations. *

*/ private final String nameId; /** * An ID that can be used to identify a frame inside a tree of reference frames. Each frame inside a * tree will have a frame ID that is different from all other frames inside the tree. *

* It is more reliably then a hash code since there will be no collisions within a single frame * tree. The disadvantage is that it is dependent on the order of construction of frames such that * this index can change from run to run even if the name of the frame remains unchanged. *

*/ private final long frameIndex; /** * A counter for the number of frames in the reference frame tree that starts at this frame. Note, * that this counter does not account for frames that might be removed from the frame tree. It is * meant to account all frames that were ever added to this frame tree to provide a unique number * Identifier for each frame. The counter is rest to zero if the root frame is cleared. */ private long framesAddedToTree = 0L; /** * Additional custom hash code representing this frame. *

* Somewhat of a hack that allows to enforce two frames that are physically the same but with * different names to have the same hash code or to enforce a common frame to have a specific hash * code that can be known without holding on its actual instance. *

*/ private long additionalNameBasedHashCode; /** * The reference to which this frame is attached to. *

* The {@link #transformToParent} of this describes the pose of this reference frame with respect to * {@link #parentFrame}. *

*/ private final ReferenceFrame parentFrame; /** * A collection of all children of this reference frame. The use of {@code WeakReference} allows the * garbage collector to dispose of the children that are not referenced outside this class. */ private final List> children = new ArrayList<>(); /** * List containing the {@link #frameName} of this frame's {@link #children}. * This is only used when {@link #nameRestrictionLevel} is set to {@link FrameNameRestrictionLevel#NAME_ID}. */ private final List childrenNames = new ArrayList<>(); /** * If this frame has restriction level {@link FrameNameRestrictionLevel#FRAME_NAME}, this object stores the highest * frame up the tree with this same restriction level. */ private ReferenceFrame topFrameNameRestriction = null; /** * Indicated if a frame is deactivated. This happens if the frame is removed from the frame tree. In * this case all references to the frame should be dropped so it can be garbage collected. */ private boolean hasBeenRemoved = false; /** * Entire from the root frame to this used to efficiently compute the pose of this reference frame * with respect to the root frame. */ private final ReferenceFrame[] framesStartingWithRootEndingWithThis; /** * The pose of this transform with respect to its parent. *

* Notes: *

    * The root frame has no parent such that {@code transformToParent == null}. *
      * The transform can be constant over time or can change depending on the final implementation of * {@code ReferenceFrame}. *

      */ private final RigidBodyTransform transformToParent; // These need to be longs instead of integers or they'll role over too soon. With longs, you get at least 100 years of runtime. static long nextTransformToRootID = 1; long transformToRootID = Long.MIN_VALUE; /** * The current transform from this reference frame to the root frame. *

      * For instance, one can calculate the coordinates in the root frame Proot of a point P * expressed in this frame as follows:
      * {@code transformToRoot.transform}(P, Proot) *

      */ private final RigidBodyTransform transformToRoot; private boolean accessingTransformToRoot = false; /** Condition for the root frame only. */ private Predicate treeUpdateCondition = null; /** * Field initialized at construction time that specifies if this reference frame represents a * stationary frame, i.e. a non-moving frame, with respect to the root reference frame. */ private final boolean isAStationaryFrame; // TODO when isAStationaryFrame == true, transformToParent should be immutable. /** * Field initialized at construction time that specifies if at all time the z-axis of this reference * frame remains aligned with the z-axis of the root frame. */ private final boolean isZupFrame; /** * Whether this frame is rigid attached to its parent such that its transform is immutable. */ private final boolean isFixedInParent; /** * List of active listeners currently attached to this frame. Only instantiated when adding the * first listener. */ private List changedListeners = null; /** * Restriction level for the reference frame's name. */ private FrameNameRestrictionLevel nameRestrictionLevel; /** * Creates a new root reference frame. *

      * Please use the method {@link ReferenceFrameTools#constructARootFrame(String)} instead. This is to * use only when extending this class. *

      *

      * Most of the time, {@link #getWorldFrame()} is the only root frame from which children reference * frames are added. *

      *

      * Note that frames added as children of this root frame belongs to a different reference frame tree * than the tree starting off of {@link #getWorldFrame()}. Transformation across two different trees * of reference frames is forbidden as the transformation between them is undefined. *

      *

      * The parent frame and transforms of a root frame are all {@code null}. *

      * * @param frameName the name of the new world frame. */ public ReferenceFrame(String frameName) { this(frameName, null, null, true, true); } /** * Creates a new reference frame defined as being a child of the given {@code parentFrame}. *

      * This new reference frame defined in the {@code parentFrame} and moves with it. *

      *

      * Its pose with respect to the {@code parentFrame} can be modified at runtime by changing the * transform in the method {@link #updateTransformToParent(RigidBodyTransform)} when overriding it. *

      *

      * This new reference frame is not a stationary frame, i.e. it is assumed to be potentially moving * with respect to the root frame. It is also not expected to have its z-axis aligned at all time * with the z-axis of the root frame. *

      * * @param frameName the name of the new frame. * @param parentFrame the parent frame of the new reference frame. */ public ReferenceFrame(String frameName, ReferenceFrame parentFrame) { this(frameName, parentFrame, null, false, false); } /** * Creates a new reference frame defined as being a child of the given {@code parentFrame}. *

      * This new reference frame defined in the {@code parentFrame} and moves with it. *

      *

      * Its pose with respect to the {@code parentFrame} can be modified at runtime by changing the * transform in the method {@link #updateTransformToParent(RigidBodyTransform)} when overriding it. *

      * * @param frameName the name of the new frame. * @param parentFrame the parent frame of the new reference frame. * @param isAStationaryFrame refers to whether this new frame is stationary with respect to the root * frame or moving. If {@code true}, the {@code parentFrame} has to also * be stationary. * @param isZupFrame refers to whether this new frame has its z-axis aligned with the root * frame at all time or not. * @throws IllegalArgumentException if {@code isAStationaryFrame} is {@code true} and the * {@code parentFrame} is not a stationary frame. */ public ReferenceFrame(String frameName, ReferenceFrame parentFrame, boolean isAStationaryFrame, boolean isZupFrame) { this(frameName, parentFrame, null, isAStationaryFrame, isZupFrame); } /** * Creates a new reference frame defined as being a child of the given {@code parentFrame} and * initializes the transform to its parent. *

      * The {@code transformFromParent} should describe the pose of the new frame expressed in its parent * frame. *

      *

      * This new reference frame defined in the {@code parentFrame} and moves with it. *

      *

      * Its pose with respect to the {@code parentFrame} can be modified at runtime by changing the * transform in the method {@link #updateTransformToParent(RigidBodyTransform)} when overriding it. *

      *

      * This new reference frame is not a stationary frame, i.e. it is assumed to be potentially moving * with respect to the root frame. It is also not expected to have its z-axis aligned at all time * with the z-axis of the root frame. *

      * * @param frameName the name of the new frame. * @param parentFrame the parent frame of the new reference frame. * @param transformToParent the transform that can be used to transform a geometry object the new * frame to its parent frame. Not modified. */ public ReferenceFrame(String frameName, ReferenceFrame parentFrame, RigidBodyTransformReadOnly transformToParent) { this(frameName, parentFrame, transformToParent, false, false); } /** * Creates a new reference frame defined as being a child of the given {@code parentFrame} and * initializes the transform to its parent. *

      * The {@code transformFromParent} should describe the pose of the new frame expressed in its parent * frame. *

      *

      * This new reference frame defined in the {@code parentFrame} and moves with it. *

      *

      * Its pose with respect to the {@code parentFrame} can be modified at runtime by changing the * transform in the method {@link #updateTransformToParent(RigidBodyTransform)} when overriding it. *

      * * @param frameName the name of the new frame. * @param parentFrame the parent frame of the new reference frame. * @param transformToParent the transform that can be used to transform a geometry object the new * frame to its parent frame. Not modified. * @param isAStationaryFrame refers to whether this new frame is stationary with respect to the root * frame or moving. If {@code true}, the {@code parentFrame} has to also * be stationary. * @param isZupFrame refers to whether this new frame has its z-axis aligned with the root * frame at all time or not. * @throws IllegalArgumentException if {@code isAStationaryFrame} is {@code true} and the * {@code parentFrame} is not a stationary frame. */ public ReferenceFrame(String frameName, ReferenceFrame parentFrame, RigidBodyTransformReadOnly transformToParent, boolean isAStationaryFrame, boolean isZupFrame) { this(frameName, parentFrame, transformToParent, isAStationaryFrame, isZupFrame, false); } /** * Creates a new reference frame defined as being a child of the given {@code parentFrame} and * initializes the transform to its parent. *

      * The {@code transformFromParent} should describe the pose of the new frame expressed in its parent * frame. *

      *

      * This new reference frame defined in the {@code parentFrame} and moves with it. *

      *

      * Its pose with respect to the {@code parentFrame} can be modified at runtime by changing the * transform in the method {@link #updateTransformToParent(RigidBodyTransform)} when overriding it. *

      * * @param frameName the name of the new frame. * @param parentFrame the parent frame of the new reference frame. * @param transformToParent the transform that can be used to transform a geometry object the new * frame to its parent frame. Not modified. * @param isAStationaryFrame refers to whether this new frame is stationary with respect to the root * frame or moving. If {@code true}, the {@code parentFrame} has to also * be stationary. * @param isZupFrame refers to whether this new frame has its z-axis aligned with the root * frame at all time or not. * @param isFixedInParent refers to whether this new frame should be considered as rigidly * attached to its parent, its pose is constant. This class does not use * this flag internally, it can be later accessed via * {@link #isFixedInParent()}. * @throws IllegalArgumentException if {@code isAStationaryFrame} is {@code true} and the * {@code parentFrame} is not a stationary frame. */ public ReferenceFrame(String frameName, ReferenceFrame parentFrame, RigidBodyTransformReadOnly transformToParent, boolean isAStationaryFrame, boolean isZupFrame, boolean isFixedInParent) { if (frameName.contains(SEPARATOR)) { throw new RuntimeException("A reference frame name can not contain '" + SEPARATOR + "'. Tried to construct a frame with name " + frameName + "."); } this.frameName = frameName; this.parentFrame = parentFrame; framesStartingWithRootEndingWithThis = ReferenceFrameTools.createPathFromRoot(this); if (parentFrame == null) { // Setting up this ReferenceFrame as a root frame. transformToRootID = 0; nameId = frameName; frameIndex = 0L; transformToRoot = null; this.transformToParent = null; this.isAStationaryFrame = true; this.isZupFrame = true; this.isFixedInParent = true; this.nameRestrictionLevel = DEFAULT_RESTRICTION_LEVEL; if (nameRestrictionLevel == FrameNameRestrictionLevel.FRAME_NAME) topFrameNameRestriction = this; } else { parentFrame.checkIfRemoved(); nameId = parentFrame.nameId + SEPARATOR + frameName; nameRestrictionLevel = parentFrame.nameRestrictionLevel; if (nameRestrictionLevel == FrameNameRestrictionLevel.FRAME_NAME) topFrameNameRestriction = parentFrame.topFrameNameRestriction; checkAndProcessNewFrameName(); frameIndex = parentFrame.incrementFramesAdded(); parentFrame.children.add(new WeakReference<>(this)); transformToRoot = new RigidBodyTransform(); this.transformToParent = new RigidBodyTransform(); if (transformToParent != null) { this.transformToParent.set(transformToParent); this.transformToParent.normalizeRotationPart(); } if (isAStationaryFrame && !parentFrame.isAStationaryFrame) throw new IllegalArgumentException("The child of a non-stationary frame cannot be stationary."); this.isAStationaryFrame = isAStationaryFrame; this.isZupFrame = isZupFrame; this.isFixedInParent = isFixedInParent; notifyListeners(ChangeType.FRAME_ADDED, this, parentFrame); } } /** * When a new reference frame is created, this checks the validity of the frame's name given the {@link #nameRestrictionLevel}. */ private void checkAndProcessNewFrameName() { if (parentFrame.nameRestrictionLevel == FrameNameRestrictionLevel.NAME_ID) { if (parentFrame.childrenNames.contains(frameName)) throw new RuntimeException("Duplicate reference frame: " + nameId); parentFrame.childrenNames.add(frameName); } else if (nameRestrictionLevel == FrameNameRestrictionLevel.FRAME_NAME) { topFrameNameRestriction.checkUniqueFrameNameInSubtree(this); } } /** * When increasing the name restriction level to {@link FrameNameRestrictionLevel#FRAME_NAME}, * this will check that frameToCheck has a distinct name from this frame and it's subtree. */ private void checkUniqueFrameNameInSubtree(ReferenceFrame frameToCheck) { checkUniqueFrameNames(this, frameToCheck); for (int i = 0; i < children.size(); i++) { ReferenceFrame childFrame = children.get(i).get(); if (childFrame != null) childFrame.checkUniqueFrameNameInSubtree(frameToCheck); } } /** * Performs a check that the two given frames have unique {@link #frameName}'s unless they are the same object. */ private static void checkUniqueFrameNames(ReferenceFrame frameA, ReferenceFrame frameB) { if (frameA == frameB) return; if (frameA.frameName.hashCode() != frameB.frameName.hashCode()) return; if (frameA.frameName.equals(frameB.frameName)) throw new RuntimeException("Duplicate reference frame names detected: " + frameA.frameName); } /** *

      * Sets the level of naming restriction for the sub-tree of reference frames at and below this frame. *

      *

      * Three levels of restriction are possible. Restriction is only allowed to increase. *

      *
        *
      • NONE: no restrictions on reference frame names are imposed
      • *
      • NAME_ID: each fully qualified nameId must be unique, e.g. "World:elevator:pelvis"
      • *
      • FRAME_NAME: each frame name must be unique, e.g. "afterKneePitchFrame"
      • *
      */ public void setNameRestrictionLevel(FrameNameRestrictionLevel nameRestrictionLevel) { if (this.nameRestrictionLevel == nameRestrictionLevel) return; if (nameRestrictionLevel.ordinal() < this.nameRestrictionLevel.ordinal()) { if (parentFrame == null && children.isEmpty()) { // allow decreased restriction for root frames without children topFrameNameRestriction = null; this.nameRestrictionLevel = nameRestrictionLevel; } else { // otherwise decreasing restriction level is not allowed throw new IllegalArgumentException("Cannot reduce name restriction level. Current mode: " + this.nameRestrictionLevel + ", tried to set to: " + nameRestrictionLevel); } } if (nameRestrictionLevel == FrameNameRestrictionLevel.NAME_ID) { setAndCheckNameIdRestrictionRecursively(); } else if (nameRestrictionLevel == FrameNameRestrictionLevel.FRAME_NAME) { topFrameNameRestriction = this; checkUniqueFrameNameInSubtree(this); setAndCheckFrameNameRestrictionRecursively(topFrameNameRestriction); } } /** * Increases the name restriction level of this frame to {@link FrameNameRestrictionLevel#NAME_ID}.
      * This will check that this frame has a distinct name from its sibling frames. */ private void setAndCheckNameIdRestrictionRecursively() { this.nameRestrictionLevel = FrameNameRestrictionLevel.NAME_ID; for (int i = 0; i < children.size(); i++) { ReferenceFrame childFrame = children.get(i).get(); if (childFrame == null) { // children and childrenNames are kept in same order. This will be cleared on the next updateChildren childrenNames.add(null); continue; } if (childrenNames.contains(childFrame.frameName)) { throw new RuntimeException("Duplicate ReferenceFrame nameId's detected: " + childFrame.nameId); } childrenNames.add(childFrame.frameName); childFrame.setAndCheckNameIdRestrictionRecursively(); } } /** * Increases the name restriction level of this frame to {@link FrameNameRestrictionLevel#FRAME_NAME}.
      * This will check that this frame has a distinct name from the subtree of {@link #topFrameNameRestriction} */ private void setAndCheckFrameNameRestrictionRecursively(ReferenceFrame topFrameNameRestriction) { this.nameRestrictionLevel = FrameNameRestrictionLevel.FRAME_NAME; this.topFrameNameRestriction = topFrameNameRestriction; for (int i = 0; i < children.size(); i++) { ReferenceFrame childFrame = children.get(i).get(); if (childFrame == null) continue; topFrameNameRestriction.checkUniqueFrameNameInSubtree(childFrame); childFrame.setAndCheckFrameNameRestrictionRecursively(topFrameNameRestriction); } } /** * Returns the name restriction level for this reference frame, see {@link #setNameRestrictionLevel(FrameNameRestrictionLevel)} */ public FrameNameRestrictionLevel getNameRestrictionLevel() { checkIfRemoved(); return nameRestrictionLevel; } private long incrementFramesAdded() { framesAddedToTree++; if (parentFrame == null) { return framesAddedToTree; } else { return parentFrame.incrementFramesAdded(); } } /** * Tests if this reference frame is {@link #getWorldFrame()}. * * @return {@code true} if this is {@link #getWorldFrame()}, {@code false} otherwise. */ public boolean isWorldFrame() { checkIfRemoved(); return this == ReferenceFrameTools.getWorldFrame(); } /** * Tests if this reference frame is the root, i.e. no parent frame, of its reference frame tree. * * @return {@code true} if this is a root frame, {@code false} otherwise. */ public boolean isRootFrame() { checkIfRemoved(); return parentFrame == null; } /** * Tests if this reference frame is to be considered as stationary frame, i.e. not moving with * respect to its root frame. * * @return {@code true} if this is a stationary frame, {@code false} other. */ public boolean isAStationaryFrame() { checkIfRemoved(); return isAStationaryFrame; } /** * Tests if this reference frame is considered to have its z-axis aligned with the root frame. * * @return {@code true} if this is a z-up frame, {@code false} otherwise. */ public boolean isZupFrame() { checkIfRemoved(); return isZupFrame; } /** * Returns whether this reference frame should be considered as rigidly attached to its parent, i.e. * its transform relative to its parent can be considered as constant. * * @return {@code true} if this frame can be considered as fixed in parent. */ public boolean isFixedInParent() { checkIfRemoved(); return isFixedInParent; } /** * The user must call update each tick. It will then call * {@link #updateTransformToParent(RigidBodyTransform)} which should be overridden to indicate how * the transform to each frame's parent should be updated. *

      * Note that it is not necessary to call update on reference frames with an unchanging transform to * parent, even if the parent frame is moving. *

      */ public void update() { checkIfRemoved(); if (parentFrame == null) { return; } updateTransformToParent(transformToParent); transformToRootID = Long.MIN_VALUE; } /** * Override this method to define how this reference frame should be located with respect to its * parent frame over time by setting the argument {@code transformToParent}. *

      * The {@code transformFromParent} should describe the pose of this frame expressed in its parent * frame. *

      * * @param transformToParent the transform to updated according to how this reference frame should * now positioned with respect to its parent frame. Modified. */ protected abstract void updateTransformToParent(RigidBodyTransform transformToParent); /** * Returns the parent frame of this reference frame. *

      * Note that a root frame has no parent frame, such that this method returns {@code null} if this is * a root frame. *

      * * @return the parent frame of this reference frame. */ public ReferenceFrame getParent() { checkIfRemoved(); return parentFrame; } /** * Retrieves the root frame of the tree of reference frame that this frame belongs to. * * @return the root frame. */ public ReferenceFrame getRootFrame() { checkIfRemoved(); return framesStartingWithRootEndingWithThis[0]; } /** * Returns a copy of this reference frame's transform to parent. *

      * WARNING: This method generates garbage. *

      *

      * This transform can be applied to a vector defined in this frame in order to obtain the equivalent * vector in the parent frame. *

      * * @return a copy of the transform to the parent frame. */ public RigidBodyTransform getTransformToParent() { RigidBodyTransform transformToReturn = new RigidBodyTransform(); getTransformToParent(transformToReturn); return transformToReturn; } /** * packs this reference frame's transform to parent into the given transform * {@code transformToPack}. *

      * This transform can be applied to a vector defined in this frame in order to obtain the equivalent * vector in the parent frame. *

      * * @param transformToPack the transform in which this frame's transform to its parent frame is * stored. Modified. */ // TODO For backward compatibility. Remove me in future release. public void getTransformToParent(RigidBodyTransform transformToPack) { checkIfRemoved(); transformToPack.set(transformToParent); } /** * packs this reference frame's transform to parent into the given transform * {@code transformToPack}. *

      * This transform can be applied to a vector defined in this frame in order to obtain the equivalent * vector in the parent frame. *

      * * @param transformToPack the transform in which this frame's transform to its parent frame is * stored. Modified. */ public void getTransformToParent(RigidBodyTransformBasics transformToPack) { checkIfRemoved(); transformToPack.set(transformToParent); } /** * Gets the name of this reference frame. *

      * Reference frames usually have a unique name among the reference frames in the same tree but this * is not guaranteed. *

      * * @return this frame's name. */ public String getName() { checkIfRemoved(); return frameName; } /** * A string that can be used to identify this reference frame. *

      * It contains the name of the frame itself and all parents up to the root frame and is used for the * {@link #hashCode()} and {@link #equals(Object)} methods. *

      *

      * In contrast to the {@link #frameIndex} this is a name based identifier that is only dependent on * names of frames. Note, that this means that two frames with the same name will be considered * equal even though they might be in different locations. *

      * * @return the name identifier for this reference frame. */ public String getNameId() { checkIfRemoved(); return nameId; } /** * Returns the transform that can be used to transform a geometry object defined in this frame to * obtain its equivalent expressed in the {@code desiredFrame}. *

      * WARNING: This method generates garbage. *

      * * @param desiredFrame the goal frame. * @return the transform from this frame to the {@code desiredFrame}. */ public RigidBodyTransform getTransformToDesiredFrame(ReferenceFrame desiredFrame) { RigidBodyTransform ret = new RigidBodyTransform(); getTransformToDesiredFrame(ret, desiredFrame); return ret; } /** * Returns the transform that can be used to transform a geometry object defined in this frame to * obtain its equivalent expressed in {@link #getWorldFrame()}. *

      * WARNING: This method generates garbage. *

      * * @return the transform from this frame to the {@link #getWorldFrame()}. */ public RigidBodyTransform getTransformToWorldFrame() { RigidBodyTransform ret = new RigidBodyTransform(); getTransformToDesiredFrame(ret, ReferenceFrameTools.getWorldFrame()); return ret; } /** * Packs the transform that can be used to transform a geometry object defined in this frame to * obtain its equivalent expressed in the {@code desiredFrame} into {@code transformToPack}. * * @param transformToPack the transform in which this frame's transform to the {@code desiredFrame} * is stored. Modified. * @param desiredFrame the goal frame. */ // TODO For backward compatibility. Remove me in future release. public void getTransformToDesiredFrame(RigidBodyTransform transformToPack, ReferenceFrame desiredFrame) { getTransformToDesiredFrame((RigidBodyTransformBasics) transformToPack, desiredFrame); } /** * Packs the transform that can be used to transform a geometry object defined in this frame to * obtain its equivalent expressed in the {@code desiredFrame} into {@code transformToPack}. * * @param transformToPack the transform in which this frame's transform to the {@code desiredFrame} * is stored. Modified. * @param desiredFrame the goal frame. */ public void getTransformToDesiredFrame(RigidBodyTransformBasics transformToPack, ReferenceFrame desiredFrame) { checkIfRemoved(); try { if (this == desiredFrame) { // Check for trivial case transformToPack.setToZero(); return; } verifySameRoots(desiredFrame); // The general approach is: // transformToPack = (desiredFrame.transformToRoot)^-1 * this.transformToRoot // As this requires a transform multiplication, we first check for simpler cases: if (isRootFrame()) { /* * If this is the root frame, desiredFrame cannot be the root frame, i.e. it would have triggered * the previous condition as there can be only one root per frame tree. Thus: this.transformToRoot * is the identity, no need for a multiplication here. */ transformToPack.setAndInvert(desiredFrame.getTransformToRoot()); } else if (desiredFrame.isRootFrame()) { /* * If desiredFrame is the root frame, this cannot be the root frame, i.e. it would have triggered * the previous condition as there can be only one root per frame tree. Thus: * desiredFrame.transformToRoot is the identity, no need for a multiplication here. */ transformToPack.set(getTransformToRoot()); } else if (isParentFrame(desiredFrame)) { // Test direct connection between the frames: transformToPack.set(transformToParent); } else if (desiredFrame.isParentFrame(this)) { // Test direct connection between the frames: transformToPack.setAndInvert(desiredFrame.transformToParent); } else if (parentFrame == desiredFrame.parentFrame) { /* * Common parentFrame. Here the multiplication is needed but the transforms involved will often be * simple (rotation only or translation only) whereas the transformToRoot of most frame is a complex * transform. */ transformToPack.setAndInvert(desiredFrame.transformToParent); transformToPack.multiply(transformToParent); } else if (parentFrame.parentFrame == desiredFrame) { // Look at a distance of 2, which would involve the multiplication of 2 transforms that will often be simple (rotation only or translation only). transformToPack.set(transformToParent); if (!parentFrame.isRootFrame()) // If it is the root, then parentFrame.transformToParent is identity. transformToPack.preMultiply(parentFrame.transformToParent); } else if (this == desiredFrame.parentFrame.parentFrame) { // Look at a distance of 2, which would involve the multiplication of 2 transforms that will often be simple (rotation only or translation only). transformToPack.setAndInvert(desiredFrame.transformToParent); if (!desiredFrame.parentFrame.isRootFrame()) // If it is the root, then desiredFrame.parentFrame.transformToParent is identity. transformToPack.multiplyInvertOther(desiredFrame.parentFrame.transformToParent); } else { // This is the general scenario: transformToPack.setAndInvert(desiredFrame.getTransformToRoot()); transformToPack.multiply(getTransformToRoot()); } } catch (NotARotationMatrixException e) { throw new NotARotationMatrixException("Caught exception, this frame: " + frameName + ", other frame: " + desiredFrame.getName() + ", exception:\n" + e.getMessage()); } } /** * Test whether the given frame is the parent of this frame. * * @param frame the query. * @return {@code true} if the query is the parent of this frame, {@code false} otherwise. */ public boolean isParentFrame(ReferenceFrame frame) { checkIfRemoved(); return frame == parentFrame; } /** * Test whether the given frame is a child of this frame. * * @param frame the query. * @return {@code true} if the query is a child of this frame, {@code false} otherwise. */ public boolean isChildFrame(ReferenceFrame frame) { checkIfRemoved(); return frame.isParentFrame(this); } /** * Asserts that this frame and {@code referenceFrame} share the same root frame. * * @param referenceFrame the query. * @throws RuntimeException if this frame and the query do not share the same root frame. */ public void verifySameRoots(ReferenceFrame referenceFrame) { if (getRootFrame() != referenceFrame.getRootFrame()) { throw new RuntimeException("Frames do not have same roots. this = " + this + ", referenceFrame = " + referenceFrame); } } /** * Asserts that {@code referenceFrame} is an ancestor of this frame. * * @param referenceFrame the query. * @throws RuntimeException if this frame and the query do not share the same root frame. */ public void verifyIsAncestor(ReferenceFrame referenceFrame) { if (isRootFrame() || !parentFrame.checkIsAncestorRecursively(referenceFrame)) { throw new RuntimeException(referenceFrame.getNameId() + " is not an ancestor of " + getNameId()); } } private boolean checkIsAncestorRecursively(ReferenceFrame referenceFrame) { if (this == referenceFrame) { return true; } else if (isRootFrame()) { return false; } else { return parentFrame.checkIsAncestorRecursively(referenceFrame); } } /** * Transforms the given {@code objectToTransform} by the transform from this reference frame to the * given {@code desiredFrame}. *

      * This method can be used to change the reference frame in which {@code objectToTransform} is * expressed from {@code this} to {@code desiredFrame}. *

      *

      * The given implementation of the given {@code Transformable} should check for * {@link RigidBodyTransform#hasRotation()} and {@link RigidBodyTransform#hasTranslation()} to * perform the transformation efficiently. *

      * * @param desiredFrame the target frame for the transformation. * @param objectToTransform the object to apply the transformation on. Modified. */ public void transformFromThisToDesiredFrame(ReferenceFrame desiredFrame, Transformable objectToTransform) { checkIfRemoved(); if (this == desiredFrame) { // Check for trivial case return; } verifySameRoots(desiredFrame); // The general approach is: // objectToTransform = (desired.transformToRoot)^-1 * this.transformToRoot * objectToTransform // Or code-wise: // 1- objectToTransform.applyTransform(transformToRoot); // 2- objectToTransform.applyInverseTransform(desiredFrame.transformToRoot); // As this requires 2 transformations, we first check for simpler cases: if (isRootFrame()) { /* * If this is the root frame, desiredFrame cannot be the root frame, i.e. it would have triggered * the previous condition as there can be only one root per frame tree. Thus: this.transformToRoot * is the identity, only 1 transformation here. */ objectToTransform.applyInverseTransform(desiredFrame.getTransformToRoot()); } else if (desiredFrame.isRootFrame()) { /* * If desiredFrame is the root frame, this cannot be the root frame, i.e. it would have triggered * the previous condition as there can be only one root per frame tree. Thus: * desiredFrame.transformToRoot is the identity, only 1 transformation here. */ objectToTransform.applyTransform(getTransformToRoot()); } else if (isParentFrame(desiredFrame)) { // Test direct connection between the frames: objectToTransform.applyTransform(transformToParent); } else if (desiredFrame.isParentFrame(this)) { // Test direct connection between the frames: objectToTransform.applyInverseTransform(desiredFrame.transformToParent); } else if (parentFrame == desiredFrame.parentFrame) { /* * Common parentFrame. Here 2 transformations are needed but the transforms involved will often be * simple (rotation only or translation only) whereas the transformToRoot of most frame is a complex * transform. */ objectToTransform.applyTransform(transformToParent); objectToTransform.applyInverseTransform(desiredFrame.transformToParent); } else if (parentFrame.parentFrame == desiredFrame) { // Look at a distance of 2, which involves 2 transformations with transforms that will often be simple (rotation only or translation only). objectToTransform.applyTransform(transformToParent); if (!parentFrame.isRootFrame()) // If it is the root, then parentFrame.transformToParent is identity. objectToTransform.applyTransform(parentFrame.transformToParent); } else if (this == desiredFrame.parentFrame.parentFrame) { // Look at a distance of 2, which involves 2 transformations with transforms that will often be simple (rotation only or translation only). if (!desiredFrame.parentFrame.isRootFrame()) // If it is the root, then desiredFrame.parentFrame.transformToParent is identity. objectToTransform.applyInverseTransform(desiredFrame.parentFrame.transformToParent); objectToTransform.applyInverseTransform(desiredFrame.transformToParent); } else { // This is the general scenario: objectToTransform.applyTransform(getTransformToRoot()); objectToTransform.applyInverseTransform(desiredFrame.getTransformToRoot()); } } /** * Returns the internal reference to this frame's transform to the root frame. *

      * The transform can be used to transform a geometry object defined in this frame to obtain its * equivalent expressed in the root frame. *

      * * @return the internal reference to the transform from this frame to the root frame. */ public RigidBodyTransform getTransformToRoot() { efficientComputeTransform(); return transformToRoot; } private void efficientComputeTransform() { Predicate treeUpdateCondition = framesStartingWithRootEndingWithThis[0].treeUpdateCondition; if (treeUpdateCondition != null && !treeUpdateCondition.test(this)) return; checkIfRemoved(); int chainLength = framesStartingWithRootEndingWithThis.length; boolean updateFromHereOnOut = false; long previousUpdateId = 0; for (int i = 0; i < chainLength; i++) { ReferenceFrame referenceFrame = framesStartingWithRootEndingWithThis[i]; if (!updateFromHereOnOut) { if (referenceFrame.transformToRootID < previousUpdateId) { updateFromHereOnOut = true; nextTransformToRootID++; } } if (updateFromHereOnOut) { if (referenceFrame.parentFrame != null) { if (referenceFrame.accessingTransformToRoot) { // We have concurrent access of the transform to root, let's abort the update. return; } try { referenceFrame.accessingTransformToRoot = true; RigidBodyTransform parentsTransformToRoot = referenceFrame.parentFrame.transformToRoot; if (parentsTransformToRoot != null) { if (referenceFrame.parentFrame.accessingTransformToRoot) { // We have concurrent access of the transform to root, let's abort the update. return; } referenceFrame.parentFrame.accessingTransformToRoot = true; try { referenceFrame.transformToRoot.set(parentsTransformToRoot); } finally { referenceFrame.parentFrame.accessingTransformToRoot = false; } referenceFrame.transformToRoot.multiply(referenceFrame.transformToParent); referenceFrame.transformToRoot.normalizeRotationPart(); } else { referenceFrame.transformToRoot.set(referenceFrame.transformToParent); } referenceFrame.transformToRootID = nextTransformToRootID; } finally { referenceFrame.accessingTransformToRoot = false; } } } previousUpdateId = referenceFrame.transformToRootID; } } /** * Overrides the {@link Object#toString()} method to print this reference frame's name. * * @return this frame's name. */ @Override public String toString() { checkIfRemoved(); return frameName; // + "\nTransform to Parent = " + this.transformToParent; } /** * Checks if the query holds onto this reference frame. *

      * This is usually used from verifying that a geometry is expressed in a specific frame. *

      * * @param referenceFrameHolder the query holding a reference frame. * @throws ReferenceFrameMismatchException if the query holds onto a different frame than this. */ public void checkReferenceFrameMatch(ReferenceFrameHolder referenceFrameHolder) throws ReferenceFrameMismatchException { checkReferenceFrameMatch(referenceFrameHolder.getReferenceFrame()); } /** * Check if this frame and the query are the same. * * @param referenceFrame the query. * @throws ReferenceFrameMismatchException if the query and this are two different frame. */ public void checkReferenceFrameMatch(ReferenceFrame referenceFrame) throws ReferenceFrameMismatchException { checkIfRemoved(); if (this != referenceFrame) { String msg = "Argument's frame " + referenceFrame + " does not match " + this; throw new ReferenceFrameMismatchException(msg); } } /** * Checks if this frame is equal to {@link #getWorldFrame()}. * * @throws RuntimeException if this is not {@link #getWorldFrame()}. */ public void checkIsWorldFrame() throws RuntimeException { checkIfRemoved(); if (!isWorldFrame()) { throw new RuntimeException("Frame " + this + " is not world frame."); } } /** * Checks if this is a stationary frame, i.e. not moving with respect to the root frame. * * @throws RuntimeException if this is not a stationary frame. */ public void checkIsAStationaryFrame() throws RuntimeException { checkIfRemoved(); if (!isAStationaryFrame()) { throw new RuntimeException("Frame " + this + " is not a stationary frame."); } } /** * Checks if this is a z-up frame, i.e. its z-axis is aligned with the root frame's z-axis. * * @throws RuntimeException if this is not a z-up frame. */ public void checkIsAZUpFrame() throws RuntimeException { checkIfRemoved(); if (!isZupFrame()) { throw new RuntimeException("Frame " + this + " is not a z-up frame."); } } /** * The hash code of a reference frame is based on the {@link #nameId} of the frame. This means that * the hash code will be equal for two distinct frames that have the same name. To differentiate all * frames in a tree regardless of their name use the {@link #getFrameIndex()} method. * * @return the hash code of the {@link #nameId} of this frame. */ @Override public int hashCode() { checkIfRemoved(); return nameId.hashCode(); } /** * A hash code computed from the {@link #frameName}, which is only guarunteed to be unique within subtrees that have * {@link FrameNameRestrictionLevel#FRAME_NAME}. * * @return the hash code of the {@link #frameName} of this frame. */ public int getFrameNameHashCode() { checkIfRemoved(); return frameName.hashCode(); } /** * This method will return true if the provided object is a reference frame with the same * {@link #nameId} as this frame. This means that two distinct frames with the same name are * considered equal. To differentiate all frames in a tree regardless of their name use the * {@link #getFrameIndex()} method. * * @return {@code true} if the provided object is of type {@link ReferenceFrame} and its * {@link #nameId} matches this. */ @Override public boolean equals(Object other) { checkIfRemoved(); if (other instanceof ReferenceFrame) { return ((ReferenceFrame) other).nameId.equals(nameId); } return false; } /** * Gets the {@link #frameIndex} of this reference frame. The frame index is a unique number that * identifies each reference frame within a reference frame tree. No two frames inside a frame tree * can have the same index. * * @return the frame index that is unique in the frame tree that this frame is part of. */ public long getFrameIndex() { checkIfRemoved(); return frameIndex; } /** * Gets the value of this frame's custom hash code. *

      * Somewhat of a hack that allows to enforce two frames that are physically the same but with * different names to have the same hash code or to enforce a common frame to have a specific hash * code that can be known without holding on its actual instance. *

      * * @return the name based hash code for this reference frame. */ public long getAdditionalNameBasedHashCode() { checkIfRemoved(); return additionalNameBasedHashCode; } /** * Sets this frame's custom hash code's value. *

      * Somewhat of a hack that allows to enforce two frames that are physically the same but with * different names to have the same hash code or to enforce a common frame to have a specific hash * code that can be known without holding on its actual instance. *

      * * @param additionalNameBasedHashCode the new value of this frame's custom hash code. */ public void setAdditionalNameBasedHashCode(long additionalNameBasedHashCode) { checkIfRemoved(); this.additionalNameBasedHashCode = additionalNameBasedHashCode; } protected void checkIfRemoved() { if (hasBeenRemoved) { throw new RuntimeException("Can not use frame that was removed from the frame tree."); } } /** * Will remove this frame from the frame tree. *

      * This recursively disables all children of this frame also. *

      *

      * Note that reference frames are automatically disposed of by the GC when no external reference * exists. *

      */ public void remove() { if (!hasBeenRemoved && parentFrame != null) { parentFrame.updateChildren(); for (int i = 0; i < parentFrame.children.size(); i++) { if (parentFrame.children.get(i).get() == this) { parentFrame.children.remove(i); if (parentFrame.nameRestrictionLevel == FrameNameRestrictionLevel.NAME_ID) parentFrame.childrenNames.remove(i); break; } } notifyListeners(ChangeType.FRAME_REMOVED, this, parentFrame); disableRecursivly(); } } private void updateChildren() { boolean hasChildBeenGCed = false; for (int i = children.size() - 1; i >= 0; i--) { if (children.get(i).get() == null) { children.remove(i); if (nameRestrictionLevel == FrameNameRestrictionLevel.NAME_ID) childrenNames.remove(i); hasChildBeenGCed = true; } } if (hasChildBeenGCed) notifyListeners(ChangeType.FRAME_GCED, null, this); } /** * Removes and disables all the children of {@code this}. *

      * Note that reference frames are automatically disposed of by the GC when no external reference * exists. *

      * * @see #remove() */ public void clearChildren() { checkIfRemoved(); children.stream().map(WeakReference::get).filter(child -> child != null).forEach(child -> child.disableRecursivly()); children.clear(); childrenNames.clear(); if (isRootFrame()) framesAddedToTree = 0L; } private void disableRecursivly() { hasBeenRemoved = true; children.stream().map(WeakReference::get).filter(child -> child != null).forEach(child -> child.disableRecursivly()); changedListeners = null; } /** * Gets the number of children attached to this frame. * * @return the number of children. */ public int getNumberOfChildren() { checkIfRemoved(); updateChildren(); return children.size(); } /** * Gets the indexth child attached to this frame. *

      * Although very unlikely, note that it is possible that the return frame is {@code null}. *

      * * @param index the index of the frame to retrieve. * @return the child frame attached to {@code this}. * @see #getNumberOfChildren() */ public ReferenceFrame getChild(int index) { checkIfRemoved(); return children.get(index).get(); } /** * Getter for the read only view of all reference frames starting with the root frame of this frame * tree all the way to this frame. * * @return the list of frames from the root frame to this. */ public ReferenceFrame[] getFramesStartingWithRootEndingWithThis() { checkIfRemoved(); return framesStartingWithRootEndingWithThis; } /** * @return the root frame of the world reference frame tree. * @see ReferenceFrameTools#getWorldFrame() */ public static ReferenceFrame getWorldFrame() { return ReferenceFrameTools.getWorldFrame(); } /** * Sets a predicate that restricts when the transform to root can be updated. *

      * The condition applies to all reference frame that belongs the same tree as this reference frame. *

      *

      * This predicate can be used to restrict the update to be done only by one thread for instance. *

      * * @param treeUpdateCondition the filtering condition for the update of the transform to root. */ public void setTreeUpdateCondition(Predicate treeUpdateCondition) { checkIfRemoved(); getRootFrame().treeUpdateCondition = treeUpdateCondition; } /** * Adds a listener to this reference frame. * * @param listener the listener for listening to changes done on this frame and its descendants. */ public void addListener(ReferenceFrameChangedListener listener) { checkIfRemoved(); if (changedListeners == null) changedListeners = new ArrayList<>(); changedListeners.add(listener); } /** * Removes all listeners previously added to this reference frame. */ public void removeListeners() { checkIfRemoved(); changedListeners = null; } /** * Tries to remove a listener from this reference frame. If the listener could not be found and * removed, nothing happens. * * @param listener the listener to remove. * @return {@code true} if the listener was removed, {@code false} if the listener was not found and * nothing happened. */ public boolean removeListener(ReferenceFrameChangedListener listener) { checkIfRemoved(); if (changedListeners == null) return false; return changedListeners.remove(listener); } private void notifyListeners(ChangeType type, ReferenceFrame target, ReferenceFrame targetParent) { if (changedListeners != null) { FrameChange change = new FrameChange(type, target, targetParent); for (int i = 0; i < changedListeners.size(); i++) changedListeners.get(i).changed(change); } if (parentFrame != null) parentFrame.notifyListeners(type, target, targetParent); } private enum ChangeType { FRAME_ADDED, FRAME_REMOVED, FRAME_GCED } private class FrameChange implements Change { private final ChangeType type; private final ReferenceFrame target, targetParent; public FrameChange(ChangeType type, ReferenceFrame target, ReferenceFrame targetParent) { this.type = type; this.target = target; this.targetParent = targetParent; } @Override public boolean wasAdded() { return type == ChangeType.FRAME_ADDED; } @Override public boolean wasRemoved() { return type == ChangeType.FRAME_REMOVED; } @Override public boolean wasGarbageCollected() { return type == ChangeType.FRAME_GCED; } @Override public ReferenceFrame getSource() { return ReferenceFrame.this; } @Override public ReferenceFrame getTarget() { return target; } @Override public ReferenceFrame getTargetParent() { return targetParent; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy