diff --git a/rosjava/src/main/java/org/ros/concurrent/EventDispatcher.java b/rosjava/src/main/java/org/ros/concurrent/EventDispatcher.java index 2475aeb1f92787db2ced5a50feb6e900e6ce4a21..428d92569f663b17eecd4533a875c41b840d58f6 100644 --- a/rosjava/src/main/java/org/ros/concurrent/EventDispatcher.java +++ b/rosjava/src/main/java/org/ros/concurrent/EventDispatcher.java @@ -26,20 +26,20 @@ package org.ros.concurrent; public class EventDispatcher<T> extends CancellableLoop { private final T listener; - private final CircularBlockingQueue<SignalRunnable<T>> events; + private final CircularBlockingDeque<SignalRunnable<T>> events; public EventDispatcher(T listener, int queueCapacity) { this.listener = listener; - events = new CircularBlockingQueue<SignalRunnable<T>>(queueCapacity); + events = new CircularBlockingDeque<SignalRunnable<T>>(queueCapacity); } public void signal(final SignalRunnable<T> signalRunnable) { - events.add(signalRunnable); + events.addLast(signalRunnable); } @Override public void loop() throws InterruptedException { - SignalRunnable<T> signalRunnable = events.take(); + SignalRunnable<T> signalRunnable = events.takeFirst(); signalRunnable.run(listener); } } \ No newline at end of file diff --git a/rosjava/src/main/java/org/ros/internal/transport/queue/IncomingMessageQueue.java b/rosjava/src/main/java/org/ros/internal/transport/queue/IncomingMessageQueue.java index 95bdb3039b17df377882ecb3f0c5f920bcee8f8f..f87672fc6dc90a934399c11c8e7fcc85b22129a2 100644 --- a/rosjava/src/main/java/org/ros/internal/transport/queue/IncomingMessageQueue.java +++ b/rosjava/src/main/java/org/ros/internal/transport/queue/IncomingMessageQueue.java @@ -16,7 +16,7 @@ package org.ros.internal.transport.queue; -import org.ros.concurrent.CircularBlockingQueue; +import org.ros.concurrent.CircularBlockingDeque; import org.ros.internal.transport.tcp.NamedChannelHandler; import org.ros.message.MessageDeserializer; import org.ros.message.MessageListener; @@ -43,8 +43,8 @@ public class IncomingMessageQueue<T> { private final MessageDispatcher<T> messageDispatcher; public IncomingMessageQueue(MessageDeserializer<T> deserializer, ExecutorService executorService) { - CircularBlockingQueue<LazyMessage<T>> lazyMessages = - new CircularBlockingQueue<LazyMessage<T>>(QUEUE_CAPACITY); + CircularBlockingDeque<LazyMessage<T>> lazyMessages = + new CircularBlockingDeque<LazyMessage<T>>(QUEUE_CAPACITY); messageReceiver = new MessageReceiver<T>(lazyMessages, deserializer); messageDispatcher = new MessageDispatcher<T>(lazyMessages, executorService); executorService.execute(messageDispatcher); diff --git a/rosjava/src/main/java/org/ros/internal/transport/queue/MessageDispatcher.java b/rosjava/src/main/java/org/ros/internal/transport/queue/MessageDispatcher.java index 7299281a5d9fdfe183b6e494855df06d1bf88d07..b44bf398f254833d2f4a65beec2b2e1ffb2880e1 100644 --- a/rosjava/src/main/java/org/ros/internal/transport/queue/MessageDispatcher.java +++ b/rosjava/src/main/java/org/ros/internal/transport/queue/MessageDispatcher.java @@ -19,7 +19,7 @@ package org.ros.internal.transport.queue; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.ros.concurrent.CancellableLoop; -import org.ros.concurrent.CircularBlockingQueue; +import org.ros.concurrent.CircularBlockingDeque; import org.ros.concurrent.EventDispatcher; import org.ros.concurrent.ListenerGroup; import org.ros.concurrent.SignalRunnable; @@ -38,7 +38,7 @@ public class MessageDispatcher<T> extends CancellableLoop { private static final boolean DEBUG = false; private static final Log log = LogFactory.getLog(MessageDispatcher.class); - private final CircularBlockingQueue<LazyMessage<T>> lazyMessages; + private final CircularBlockingDeque<LazyMessage<T>> lazyMessages; private final ListenerGroup<MessageListener<T>> messageListeners; /** @@ -50,7 +50,7 @@ public class MessageDispatcher<T> extends CancellableLoop { private boolean latchMode; private LazyMessage<T> latchedMessage; - public MessageDispatcher(CircularBlockingQueue<LazyMessage<T>> lazyMessages, + public MessageDispatcher(CircularBlockingDeque<LazyMessage<T>> lazyMessages, ExecutorService executorService) { this.lazyMessages = lazyMessages; messageListeners = new ListenerGroup<MessageListener<T>>(executorService); @@ -114,7 +114,7 @@ public class MessageDispatcher<T> extends CancellableLoop { @Override public void loop() throws InterruptedException { - LazyMessage<T> lazyMessage = lazyMessages.take(); + LazyMessage<T> lazyMessage = lazyMessages.takeFirst(); synchronized (mutex) { latchedMessage = lazyMessage; if (DEBUG) { diff --git a/rosjava/src/main/java/org/ros/internal/transport/queue/MessageReceiver.java b/rosjava/src/main/java/org/ros/internal/transport/queue/MessageReceiver.java index 2cd4f762e4f309dae189c38446919edc69b308df..33392eefed5ee11e9e5e666e792382b26f8aa154 100644 --- a/rosjava/src/main/java/org/ros/internal/transport/queue/MessageReceiver.java +++ b/rosjava/src/main/java/org/ros/internal/transport/queue/MessageReceiver.java @@ -16,7 +16,7 @@ package org.ros.internal.transport.queue; -import org.ros.concurrent.CircularBlockingQueue; +import org.ros.concurrent.CircularBlockingDeque; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -37,10 +37,10 @@ public class MessageReceiver<T> extends AbstractNamedChannelHandler { private static final boolean DEBUG = false; private static final Log log = LogFactory.getLog(MessageReceiver.class); - private final CircularBlockingQueue<LazyMessage<T>> lazyMessages; + private final CircularBlockingDeque<LazyMessage<T>> lazyMessages; private final MessageDeserializer<T> deserializer; - public MessageReceiver(CircularBlockingQueue<LazyMessage<T>> lazyMessages, + public MessageReceiver(CircularBlockingDeque<LazyMessage<T>> lazyMessages, MessageDeserializer<T> deserializer) { this.lazyMessages = lazyMessages; this.deserializer = deserializer; @@ -59,7 +59,7 @@ public class MessageReceiver<T> extends AbstractNamedChannelHandler { } // We have to make a defensive copy of the buffer here because Netty does // not guarantee that the returned ChannelBuffer will not be reused. - lazyMessages.add(new LazyMessage<T>(buffer.copy(), deserializer)); + lazyMessages.addLast(new LazyMessage<T>(buffer.copy(), deserializer)); super.messageReceived(ctx, e); } } \ No newline at end of file diff --git a/rosjava/src/main/java/org/ros/internal/transport/queue/OutgoingMessageQueue.java b/rosjava/src/main/java/org/ros/internal/transport/queue/OutgoingMessageQueue.java index c7ebe441212ccd11a7ebdf3124c3568008389f14..5163a17643f087dc61ace7f8057587dcc56043a9 100644 --- a/rosjava/src/main/java/org/ros/internal/transport/queue/OutgoingMessageQueue.java +++ b/rosjava/src/main/java/org/ros/internal/transport/queue/OutgoingMessageQueue.java @@ -27,7 +27,7 @@ import org.jboss.netty.channel.group.ChannelGroupFuture; import org.jboss.netty.channel.group.ChannelGroupFutureListener; import org.jboss.netty.channel.group.DefaultChannelGroup; import org.ros.concurrent.CancellableLoop; -import org.ros.concurrent.CircularBlockingQueue; +import org.ros.concurrent.CircularBlockingDeque; import org.ros.internal.message.MessageBufferPool; import org.ros.internal.message.MessageBuffers; import org.ros.message.MessageSerializer; @@ -45,7 +45,7 @@ public class OutgoingMessageQueue<T> { private static final int QUEUE_CAPACITY = 16; private final MessageSerializer<T> serializer; - private final CircularBlockingQueue<T> queue; + private final CircularBlockingDeque<T> queue; private final ChannelGroup channelGroup; private final Writer writer; private final MessageBufferPool messageBufferPool; @@ -57,7 +57,7 @@ public class OutgoingMessageQueue<T> { private final class Writer extends CancellableLoop { @Override public void loop() throws InterruptedException { - T message = queue.take(); + T message = queue.takeFirst(); final ChannelBuffer buffer = messageBufferPool.acquire(); serializer.serialize(message, buffer); if (DEBUG) { @@ -79,7 +79,7 @@ public class OutgoingMessageQueue<T> { public OutgoingMessageQueue(MessageSerializer<T> serializer, ExecutorService executorService) { this.serializer = serializer; - queue = new CircularBlockingQueue<T>(QUEUE_CAPACITY); + queue = new CircularBlockingDeque<T>(QUEUE_CAPACITY); channelGroup = new DefaultChannelGroup(); writer = new Writer(); messageBufferPool = new MessageBufferPool(); @@ -101,7 +101,7 @@ public class OutgoingMessageQueue<T> { * the message to add to the queue */ public void add(T message) { - queue.add(message); + queue.addLast(message); setLatchedMessage(message); } diff --git a/rosjava/src/main/java/org/ros/namespace/NameResolver.java b/rosjava/src/main/java/org/ros/namespace/NameResolver.java index 164851b255247091c6f53a2623f23f7183f74d9b..b12af1fe9dbd8432d103c8827c3161afc3adeab2 100644 --- a/rosjava/src/main/java/org/ros/namespace/NameResolver.java +++ b/rosjava/src/main/java/org/ros/namespace/NameResolver.java @@ -16,8 +16,6 @@ package org.ros.namespace; -import com.google.common.base.Preconditions; - import org.ros.exception.RosRuntimeException; import java.util.Collections; @@ -76,8 +74,10 @@ public class NameResolver { */ public GraphName resolve(GraphName namespace, GraphName name) { GraphName remappedNamespace = lookUpRemapping(namespace); - Preconditions.checkArgument(remappedNamespace.isGlobal(), - "Namespace must be global. Tried to resolve: " + remappedNamespace); + if (!remappedNamespace.isGlobal()) { + throw new IllegalStateException(String.format( + "Namespace %s (remapped from %s) must be global.", remappedNamespace, namespace)); + } GraphName remappedName = lookUpRemapping(name); if (remappedName.isGlobal()) { return remappedName; diff --git a/rosjava/src/test/java/org/ros/internal/transport/queue/MessageDispatcherTest.java b/rosjava/src/test/java/org/ros/internal/transport/queue/MessageDispatcherTest.java index b3f61d5e07a5cd2ae8e6f6ac75471fc278212274..5b5038b520c30821f5fd0877650cffbeea37ce80 100644 --- a/rosjava/src/test/java/org/ros/internal/transport/queue/MessageDispatcherTest.java +++ b/rosjava/src/test/java/org/ros/internal/transport/queue/MessageDispatcherTest.java @@ -21,7 +21,7 @@ import static org.junit.Assert.fail; import org.junit.Before; import org.junit.Test; -import org.ros.concurrent.CircularBlockingQueue; +import org.ros.concurrent.CircularBlockingDeque; import org.ros.internal.message.DefaultMessageFactory; import org.ros.internal.message.definition.MessageDefinitionReflectionProvider; import org.ros.message.MessageFactory; @@ -42,13 +42,13 @@ public class MessageDispatcherTest { private static final int QUEUE_CAPACITY = 128; private ExecutorService executorService; - private CircularBlockingQueue<LazyMessage<std_msgs.Int32>> lazyMessages; + private CircularBlockingDeque<LazyMessage<std_msgs.Int32>> lazyMessages; private MessageFactory messageFactory; @Before public void before() { executorService = Executors.newCachedThreadPool(); - lazyMessages = new CircularBlockingQueue<LazyMessage<std_msgs.Int32>>(128); + lazyMessages = new CircularBlockingDeque<LazyMessage<std_msgs.Int32>>(128); messageFactory = new DefaultMessageFactory(new MessageDefinitionReflectionProvider()); } @@ -84,7 +84,7 @@ public class MessageDispatcherTest { final int count = i; std_msgs.Int32 message = messageFactory.newFromType(std_msgs.Int32._TYPE); message.setData(count); - lazyMessages.add(new LazyMessage<std_msgs.Int32>(message)); + lazyMessages.addLast(new LazyMessage<std_msgs.Int32>(message)); } assertTrue(latch.await(1, TimeUnit.SECONDS)); diff --git a/rosjava_benchmarks/src/main/java/org/ros/rosjava_benchmarks/TransformBenchmark.java b/rosjava_benchmarks/src/main/java/org/ros/rosjava_benchmarks/TransformBenchmark.java index 9a38cb1fea4ae79c006c0e486140b142a711bf49..3867329ae842722e4caff3a11c756911b9fe113e 100644 --- a/rosjava_benchmarks/src/main/java/org/ros/rosjava_benchmarks/TransformBenchmark.java +++ b/rosjava_benchmarks/src/main/java/org/ros/rosjava_benchmarks/TransformBenchmark.java @@ -59,15 +59,13 @@ public class TransformBenchmark extends AbstractNodeMain { connectedNode.getTopicMessageFactory().newFromType(geometry_msgs.TransformStamped._TYPE); turtle2.getHeader().setFrameId("world"); turtle2.setChildFrameId("turtle2"); - final GraphName sourceFrame = GraphName.of("turtle1"); - final GraphName targetFrame = GraphName.of("turtle2"); final FrameTransformTree frameTransformTree = new FrameTransformTree(NameResolver.newRoot()); connectedNode.executeCancellableLoop(new CancellableLoop() { @Override protected void loop() throws InterruptedException { updateTransform(turtle1, connectedNode, frameTransformTree); updateTransform(turtle2, connectedNode, frameTransformTree); - frameTransformTree.newFrameTransform(sourceFrame, targetFrame); + frameTransformTree.transform("turtle1", "turtle2"); counter.incrementAndGet(); } }); @@ -103,6 +101,6 @@ public class TransformBenchmark extends AbstractNodeMain { transformStamped.getTransform().getTranslation().setX(Math.random()); transformStamped.getTransform().getTranslation().setY(Math.random()); transformStamped.getTransform().getTranslation().setZ(Math.random()); - frameTransformTree.updateTransform(transformStamped); + frameTransformTree.update(transformStamped); } } diff --git a/rosjava_geometry/src/main/java/org/ros/rosjava_geometry/FrameTransform.java b/rosjava_geometry/src/main/java/org/ros/rosjava_geometry/FrameTransform.java index a91536e027a08d0f25b67e0bcec14a0f253ee44b..1d0e99d4f26917fb69ee16a46d57a773a473225e 100644 --- a/rosjava_geometry/src/main/java/org/ros/rosjava_geometry/FrameTransform.java +++ b/rosjava_geometry/src/main/java/org/ros/rosjava_geometry/FrameTransform.java @@ -16,12 +16,14 @@ package org.ros.rosjava_geometry; +import com.google.common.base.Preconditions; + import org.ros.message.Time; import org.ros.namespace.GraphName; /** * Describes a {@link Transform} from data in the source frame to data in the - * target frame. + * target frame at a specified {@link Time}. * * @author damonkohler@google.com (Damon Kohler) */ @@ -30,19 +32,39 @@ public class FrameTransform { private final Transform transform; private final GraphName source; private final GraphName target; + private final Time time; - public static FrameTransform - fromTransformStamped(geometry_msgs.TransformStamped transformStamped) { + public static FrameTransform fromTransformStampedMessage( + geometry_msgs.TransformStamped transformStamped) { Transform transform = Transform.fromTransformMessage(transformStamped.getTransform()); String target = transformStamped.getHeader().getFrameId(); String source = transformStamped.getChildFrameId(); - return new FrameTransform(transform, GraphName.of(source), GraphName.of(target)); + Time stamp = transformStamped.getHeader().getStamp(); + return new FrameTransform(transform, GraphName.of(source), GraphName.of(target), stamp); } - public FrameTransform(Transform transform, GraphName source, GraphName target) { + /** + * Allocates a new {@link FrameTransform}. + * + * @param transform + * the {@link Transform} that transforms data in the {@code source} + * frame to data in the {@code target} frame + * @param source + * the source frame + * @param target + * the target frame + * @param time + * the time associated with this {@link FrameTransform}, can be + * {@null} + */ + public FrameTransform(Transform transform, GraphName source, GraphName target, Time time) { + Preconditions.checkNotNull(transform); + Preconditions.checkNotNull(source); + Preconditions.checkNotNull(target); this.transform = transform; this.source = source; this.target = target; + this.time = time; } public Transform getTransform() { @@ -57,10 +79,19 @@ public class FrameTransform { return target; } - public geometry_msgs.TransformStamped toTransformStampedMessage(Time stamp, + /** + * @return the time associated with the {@link FrameTransform} or {@code null} + * if there is no associated time + */ + public Time getTime() { + return time; + } + + public geometry_msgs.TransformStamped toTransformStampedMessage( geometry_msgs.TransformStamped result) { + Preconditions.checkNotNull(time); result.getHeader().setFrameId(target.toString()); - result.getHeader().setStamp(stamp); + result.getHeader().setStamp(time); result.setChildFrameId(source.toString()); transform.toTransformMessage(result.getTransform()); return result; @@ -68,7 +99,8 @@ public class FrameTransform { @Override public String toString() { - return String.format("FrameTransform<Source: %s, Target: %s, %s>", source, target, transform); + return String.format("FrameTransform<Source: %s, Target: %s, %s, Time: %s>", source, target, + transform, time); } @Override @@ -77,6 +109,7 @@ public class FrameTransform { int result = 1; result = prime * result + ((source == null) ? 0 : source.hashCode()); result = prime * result + ((target == null) ? 0 : target.hashCode()); + result = prime * result + ((time == null) ? 0 : time.hashCode()); result = prime * result + ((transform == null) ? 0 : transform.hashCode()); return result; } @@ -100,6 +133,11 @@ public class FrameTransform { return false; } else if (!target.equals(other.target)) return false; + if (time == null) { + if (other.time != null) + return false; + } else if (!time.equals(other.time)) + return false; if (transform == null) { if (other.transform != null) return false; diff --git a/rosjava_geometry/src/main/java/org/ros/rosjava_geometry/FrameTransformTree.java b/rosjava_geometry/src/main/java/org/ros/rosjava_geometry/FrameTransformTree.java index 83c27b3a8fb5a1e9a338ef33331bc25227bfed46..e4e9571de165f3330e635cb406154128975c4cac 100644 --- a/rosjava_geometry/src/main/java/org/ros/rosjava_geometry/FrameTransformTree.java +++ b/rosjava_geometry/src/main/java/org/ros/rosjava_geometry/FrameTransformTree.java @@ -16,10 +16,13 @@ package org.ros.rosjava_geometry; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.collect.Maps; import geometry_msgs.TransformStamped; +import org.ros.concurrent.CircularBlockingDeque; +import org.ros.message.Time; import org.ros.namespace.GraphName; import org.ros.namespace.NameResolver; @@ -36,16 +39,22 @@ import java.util.Map; */ public class FrameTransformTree { + private static final int TRANSFORM_QUEUE_CAPACITY = 16; + private final NameResolver nameResolver; + private final Object mutex; /** - * A {@link Map} of the most recent {@link LazyFrameTransform} by target - * frame. + * A {@link Map} of the most recent {@link LazyFrameTransform} by source + * frame. Lookups by target frame or by the pair of source and target are both + * unnecessary because every frame can only have exactly one target. */ - private final Map<GraphName, LazyFrameTransform> transforms; + private final Map<GraphName, CircularBlockingDeque<LazyFrameTransform>> transforms; public FrameTransformTree(NameResolver nameResolver) { + Preconditions.checkNotNull(nameResolver); this.nameResolver = nameResolver; + mutex = new Object(); transforms = Maps.newConcurrentMap(); } @@ -60,69 +69,185 @@ public class FrameTransformTree { * @param transformStamped * the {@link geometry_msgs.TransformStamped} message to update with */ - public void updateTransform(geometry_msgs.TransformStamped transformStamped) { - GraphName target = nameResolver.resolve(transformStamped.getChildFrameId()); - transforms.put(target, new LazyFrameTransform(transformStamped)); + public void update(geometry_msgs.TransformStamped transformStamped) { + Preconditions.checkNotNull(transformStamped); + GraphName resolvedSource = nameResolver.resolve(transformStamped.getChildFrameId()); + LazyFrameTransform lazyFrameTransform = new LazyFrameTransform(transformStamped); + add(resolvedSource, lazyFrameTransform); + } + + @VisibleForTesting + void update(FrameTransform frameTransform) { + Preconditions.checkNotNull(frameTransform); + GraphName resolvedSource = frameTransform.getSourceFrame(); + LazyFrameTransform lazyFrameTransform = new LazyFrameTransform(frameTransform); + add(resolvedSource, lazyFrameTransform); } - private FrameTransform getLatestTransform(GraphName frame) { - LazyFrameTransform lazyFrameTransform = transforms.get(nameResolver.resolve(frame)); - if (lazyFrameTransform != null) { - return lazyFrameTransform.get(); + private void add(GraphName resolvedSource, LazyFrameTransform lazyFrameTransform) { + if (!transforms.containsKey(resolvedSource)) { + transforms.put(resolvedSource, new CircularBlockingDeque<LazyFrameTransform>( + TRANSFORM_QUEUE_CAPACITY)); } - return null; + synchronized (mutex) { + transforms.get(resolvedSource).addFirst(lazyFrameTransform); + } + } + + /** + * Returns the most recent {@link FrameTransform} for target {@code source}. + * + * @param source + * the frame to look up + * @return the most recent {@link FrameTransform} for {@code source} or + * {@code null} if no transform for {@code source} is available + */ + public FrameTransform lookUp(GraphName source) { + Preconditions.checkNotNull(source); + GraphName resolvedSource = nameResolver.resolve(source); + return getLatest(resolvedSource); + } + + private FrameTransform getLatest(GraphName resolvedSource) { + CircularBlockingDeque<LazyFrameTransform> deque = transforms.get(resolvedSource); + if (deque == null) { + return null; + } + LazyFrameTransform lazyFrameTransform = deque.peekFirst(); + if (lazyFrameTransform == null) { + return null; + } + return lazyFrameTransform.get(); + } + + /** + * @see #lookUp(GraphName) + */ + public FrameTransform get(String source) { + Preconditions.checkNotNull(source); + return lookUp(GraphName.of(source)); + } + + /** + * Returns the {@link FrameTransform} for {@code source} closest to + * {@code time}. + * + * @param source + * the frame to look up + * @param time + * the transform for {@code frame} closest to this {@link Time} will + * be returned + * @return the most recent {@link FrameTransform} for {@code source} or + * {@code null} if no transform for {@code source} is available + */ + public FrameTransform lookUp(GraphName source, Time time) { + Preconditions.checkNotNull(source); + Preconditions.checkNotNull(time); + GraphName resolvedSource = nameResolver.resolve(source); + return get(resolvedSource, time); + } + + // TODO(damonkohler): Use an efficient search. + private FrameTransform get(GraphName resolvedSource, Time time) { + CircularBlockingDeque<LazyFrameTransform> deque = transforms.get(resolvedSource); + if (deque == null) { + return null; + } + LazyFrameTransform result = null; + synchronized (mutex) { + long offset = 0; + for (LazyFrameTransform lazyFrameTransform : deque) { + if (result == null) { + result = lazyFrameTransform; + offset = Math.abs(time.subtract(result.get().getTime()).totalNsecs()); + continue; + } + long newOffset = Math.abs(time.subtract(lazyFrameTransform.get().getTime()).totalNsecs()); + if (newOffset < offset) { + result = lazyFrameTransform; + offset = newOffset; + } + } + } + if (result == null) { + return null; + } + return result.get(); } /** - * @param sourceFrame - * the source frame - * @param targetFrame - * the target frame - * @return {@code true} if there exists a {@link FrameTransform} from - * {@code sourceFrame} to {@code targetFrame}, {@code false} otherwise + * @see #lookUp(GraphName, Time) */ - public boolean canTransform(GraphName sourceFrame, GraphName targetFrame) { - Preconditions.checkNotNull(sourceFrame); - Preconditions.checkNotNull(targetFrame); - FrameTransform source = newFrameTransformToRoot(sourceFrame); - FrameTransform target = newFrameTransformToRoot(targetFrame); - return source.getTargetFrame().equals(target.getTargetFrame()); + public FrameTransform get(String source, Time time) { + Preconditions.checkNotNull(source); + return lookUp(GraphName.of(source), time); } /** * @return the {@link FrameTransform} from source the frame to the target * frame, or {@code null} if no {@link FrameTransform} could be found */ - public FrameTransform newFrameTransform(GraphName sourceFrame, GraphName targetFrame) { - Preconditions.checkNotNull(sourceFrame); - Preconditions.checkNotNull(targetFrame); - FrameTransform source = newFrameTransformToRoot(sourceFrame); - FrameTransform target = newFrameTransformToRoot(targetFrame); - if (source.getTargetFrame().equals(target.getTargetFrame())) { - Transform transform = target.getTransform().invert().multiply(source.getTransform()); - return new FrameTransform(transform, source.getSourceFrame(), target.getSourceFrame()); + public FrameTransform transform(GraphName source, GraphName target) { + Preconditions.checkNotNull(source); + Preconditions.checkNotNull(target); + GraphName resolvedSource = nameResolver.resolve(source); + GraphName resolvedTarget = nameResolver.resolve(target); + if (resolvedSource.equals(resolvedTarget)) { + return new FrameTransform(Transform.identity(), resolvedSource, resolvedTarget, null); + } + FrameTransform sourceToRoot = transformToRoot(resolvedSource); + if (sourceToRoot != null && sourceToRoot.getTargetFrame().equals(resolvedTarget)) { + return sourceToRoot; + } + FrameTransform targetToRoot = transformToRoot(resolvedTarget); + if (targetToRoot != null) { + if (targetToRoot.getTargetFrame().equals(resolvedTarget)) { + return targetToRoot; + } + if (targetToRoot.getTargetFrame().equals(resolvedSource)) { + Transform transform = targetToRoot.getTransform().invert(); + return new FrameTransform(transform, resolvedSource, resolvedTarget, targetToRoot.getTime()); + } + } + if (sourceToRoot == null || targetToRoot == null) { + return null; + } + if (sourceToRoot.getTargetFrame().equals(targetToRoot.getTargetFrame())) { + Transform transform = + targetToRoot.getTransform().invert().multiply(sourceToRoot.getTransform()); + return new FrameTransform(transform, resolvedSource, resolvedTarget, sourceToRoot.getTime()); } return null; } /** - * @param frame - * the start frame - * @return the {@link Transform} from {@code frame} to root + * @see #transform(GraphName, GraphName) */ - private FrameTransform newFrameTransformToRoot(GraphName frame) { - GraphName sourceFrame = nameResolver.resolve(frame); - FrameTransform result = - new FrameTransform(Transform.identity(), sourceFrame, sourceFrame); + public FrameTransform transform(String source, String target) { + Preconditions.checkNotNull(source); + Preconditions.checkNotNull(target); + return transform(GraphName.of(source), GraphName.of(target)); + } + + /** + * @param resolvedSource + * the resolved source frame + * @return the {@link Transform} from {@code source} to root + */ + private FrameTransform transformToRoot(GraphName resolvedSource) { + FrameTransform result = getLatest(resolvedSource); + if (result == null) { + return null; + } while (true) { - FrameTransform resultToParent = getLatestTransform(result.getTargetFrame()); + FrameTransform resultToParent = lookUp(result.getTargetFrame(), result.getTime()); if (resultToParent == null) { return result; } // Now resultToParent.getSourceFrame() == result.getTargetFrame() Transform transform = resultToParent.getTransform().multiply(result.getTransform()); - GraphName targetFrame = nameResolver.resolve(resultToParent.getTargetFrame()); - result = new FrameTransform(transform, sourceFrame, targetFrame); + GraphName resolvedTarget = resultToParent.getTargetFrame(); + result = new FrameTransform(transform, resolvedSource, resolvedTarget, result.getTime()); } } } diff --git a/rosjava_geometry/src/main/java/org/ros/rosjava_geometry/LazyFrameTransform.java b/rosjava_geometry/src/main/java/org/ros/rosjava_geometry/LazyFrameTransform.java index a41b2dc7baa59af964a8cd0753192fa3f385ba63..41a4a981abb9638205a24c029e66c09996bb49a1 100644 --- a/rosjava_geometry/src/main/java/org/ros/rosjava_geometry/LazyFrameTransform.java +++ b/rosjava_geometry/src/main/java/org/ros/rosjava_geometry/LazyFrameTransform.java @@ -16,6 +16,8 @@ package org.ros.rosjava_geometry; +import com.google.common.annotations.VisibleForTesting; + /** * Lazily converts a {@link geometry_msgs.Transform} message to a * {@link Transform} on the first call to {@link #get()} and caches the result. @@ -36,6 +38,13 @@ public class LazyFrameTransform { mutex = new Object(); } + @VisibleForTesting + LazyFrameTransform(FrameTransform frameTransform) { + message = null; + mutex = null; + this.frameTransform = frameTransform; + } + /** * @return the {@link FrameTransform} for the wrapped * {@link geometry_msgs.TransformStamped} message @@ -46,7 +55,7 @@ public class LazyFrameTransform { } synchronized (mutex) { if (frameTransform == null) { - frameTransform = FrameTransform.fromTransformStamped(message); + frameTransform = FrameTransform.fromTransformStampedMessage(message); } } return frameTransform; diff --git a/rosjava_geometry/src/main/java/org/ros/rosjava_geometry/Quaternion.java b/rosjava_geometry/src/main/java/org/ros/rosjava_geometry/Quaternion.java index 0b4385f886ed6b6fff2f50b69149c152685ab31b..c9353a2696111ec84a1f560db8f2f4cc5b8fd3f6 100644 --- a/rosjava_geometry/src/main/java/org/ros/rosjava_geometry/Quaternion.java +++ b/rosjava_geometry/src/main/java/org/ros/rosjava_geometry/Quaternion.java @@ -131,6 +131,11 @@ public class Quaternion { @Override public int hashCode() { + // Ensure that -0 and 0 are considered equal. + double w = this.w == 0 ? 0 : this.w; + double x = this.x == 0 ? 0 : this.x; + double y = this.y == 0 ? 0 : this.y; + double z = this.z == 0 ? 0 : this.z; final int prime = 31; int result = 1; long temp; @@ -154,13 +159,22 @@ public class Quaternion { if (getClass() != obj.getClass()) return false; Quaternion other = (Quaternion) obj; - if (Double.doubleToLongBits(w) != Double.doubleToLongBits(other.w)) + // Ensure that -0 and 0 are considered equal. + double w = this.w == 0 ? 0 : this.w; + double x = this.x == 0 ? 0 : this.x; + double y = this.y == 0 ? 0 : this.y; + double z = this.z == 0 ? 0 : this.z; + double otherW = other.w == 0 ? 0 : other.w; + double otherX = other.x == 0 ? 0 : other.x; + double otherY = other.y == 0 ? 0 : other.y; + double otherZ = other.z == 0 ? 0 : other.z; + if (Double.doubleToLongBits(w) != Double.doubleToLongBits(otherW)) return false; - if (Double.doubleToLongBits(x) != Double.doubleToLongBits(other.x)) + if (Double.doubleToLongBits(x) != Double.doubleToLongBits(otherX)) return false; - if (Double.doubleToLongBits(y) != Double.doubleToLongBits(other.y)) + if (Double.doubleToLongBits(y) != Double.doubleToLongBits(otherY)) return false; - if (Double.doubleToLongBits(z) != Double.doubleToLongBits(other.z)) + if (Double.doubleToLongBits(z) != Double.doubleToLongBits(otherZ)) return false; return true; } diff --git a/rosjava_geometry/src/main/java/org/ros/rosjava_geometry/Vector3.java b/rosjava_geometry/src/main/java/org/ros/rosjava_geometry/Vector3.java index 32e40f3538141cbabdba75f3699d79d99cd3a339..cf8cec28aff86e7f61e19659281a9884bcd9ce53 100644 --- a/rosjava_geometry/src/main/java/org/ros/rosjava_geometry/Vector3.java +++ b/rosjava_geometry/src/main/java/org/ros/rosjava_geometry/Vector3.java @@ -126,6 +126,10 @@ public class Vector3 { @Override public int hashCode() { + // Ensure that -0 and 0 are considered equal. + double x = this.x == 0 ? 0 : this.x; + double y = this.y == 0 ? 0 : this.y; + double z = this.z == 0 ? 0 : this.z; final int prime = 31; int result = 1; long temp; @@ -147,11 +151,18 @@ public class Vector3 { if (getClass() != obj.getClass()) return false; Vector3 other = (Vector3) obj; - if (Double.doubleToLongBits(x) != Double.doubleToLongBits(other.x)) + // Ensure that -0 and 0 are considered equal. + double x = this.x == 0 ? 0 : this.x; + double y = this.y == 0 ? 0 : this.y; + double z = this.z == 0 ? 0 : this.z; + double otherX = other.x == 0 ? 0 : other.x; + double otherY = other.y == 0 ? 0 : other.y; + double otherZ = other.z == 0 ? 0 : other.z; + if (Double.doubleToLongBits(x) != Double.doubleToLongBits(otherX)) return false; - if (Double.doubleToLongBits(y) != Double.doubleToLongBits(other.y)) + if (Double.doubleToLongBits(y) != Double.doubleToLongBits(otherY)) return false; - if (Double.doubleToLongBits(z) != Double.doubleToLongBits(other.z)) + if (Double.doubleToLongBits(z) != Double.doubleToLongBits(otherZ)) return false; return true; } diff --git a/rosjava_geometry/src/test/java/org/ros/rosjava_geometry/FrameTransformTreeTest.java b/rosjava_geometry/src/test/java/org/ros/rosjava_geometry/FrameTransformTreeTest.java index 0510075a957c109e3455af78c805b42aa58c9e3e..13fcd367513da738d47e2f033dbf652871ae65b7 100644 --- a/rosjava_geometry/src/test/java/org/ros/rosjava_geometry/FrameTransformTreeTest.java +++ b/rosjava_geometry/src/test/java/org/ros/rosjava_geometry/FrameTransformTreeTest.java @@ -17,14 +17,15 @@ package org.ros.rosjava_geometry; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import org.junit.Before; import org.junit.Test; import org.ros.internal.message.DefaultMessageFactory; import org.ros.internal.message.definition.MessageDefinitionReflectionProvider; import org.ros.message.MessageDefinitionProvider; import org.ros.message.MessageFactory; import org.ros.message.Time; -import org.ros.namespace.GraphName; import org.ros.namespace.NameResolver; /** @@ -32,75 +33,119 @@ import org.ros.namespace.NameResolver; */ public class FrameTransformTreeTest { + private NameResolver nameResolver; + private FrameTransformTree frameTransformTree; + private MessageDefinitionProvider messageDefinitionProvider; + private MessageFactory messageFactory; + + @Before + public void before() { + nameResolver = NameResolver.newRoot(); + frameTransformTree = new FrameTransformTree(nameResolver); + messageDefinitionProvider = new MessageDefinitionReflectionProvider(); + messageFactory = new DefaultMessageFactory(messageDefinitionProvider); + } + + @Test + public void testUpdateAndGet() { + FrameTransform frameTransform = + new FrameTransform(Transform.identity(), nameResolver.resolve("foo"), + nameResolver.resolve("bar"), new Time()); + frameTransformTree.update(frameTransform); + assertEquals(frameTransform, frameTransformTree.get("foo")); + } + + @Test + public void testUpdateAndGetWithTransformStampedMessage() { + FrameTransform frameTransform = + new FrameTransform(Transform.identity(), nameResolver.resolve("foo"), + nameResolver.resolve("bar"), new Time()); + frameTransformTree.update(newTransformStampedMessage(Transform.identity(), "foo", "bar", + new Time())); + assertEquals(frameTransform, frameTransformTree.get("foo")); + } + + private geometry_msgs.TransformStamped newTransformStampedMessage(Transform transform, + String source, String target, Time time) { + geometry_msgs.TransformStamped message = + messageFactory.newFromType(geometry_msgs.TransformStamped._TYPE); + FrameTransform frameTransform = + new FrameTransform(transform, nameResolver.resolve(source), nameResolver.resolve(target), + time); + frameTransform.toTransformStampedMessage(message); + return message; + } + @Test public void testIdentityTransforms() { - MessageDefinitionProvider messageDefinitionProvider = new MessageDefinitionReflectionProvider(); - MessageFactory messageFactory = new DefaultMessageFactory(messageDefinitionProvider); - NameResolver nameResolver = NameResolver.newRoot(); - FrameTransformTree frameTransformTree = new FrameTransformTree(nameResolver); + frameTransformTree.update(newTransformStampedMessage(Transform.identity(), "baz", "bar", + new Time())); + frameTransformTree.update(newTransformStampedMessage(Transform.identity(), "bar", "foo", + new Time())); + // Full tree transform. { - geometry_msgs.TransformStamped message = - messageFactory.newFromType(geometry_msgs.TransformStamped._TYPE); - Transform transform = Transform.identity(); - FrameTransform frameTransform = - new FrameTransform(transform, GraphName.of("baz"), GraphName.of("bar")); - frameTransform.toTransformStampedMessage(new Time(), message); - frameTransformTree.updateTransform(message); + FrameTransform frameTransform = frameTransformTree.transform("baz", "foo"); + assertTrue(frameTransform != null); + assertEquals(nameResolver.resolve("baz"), frameTransform.getSourceFrame()); + assertEquals(nameResolver.resolve("foo"), frameTransform.getTargetFrame()); + assertEquals(Transform.identity(), frameTransform.getTransform()); } + // Same node transform. { - geometry_msgs.TransformStamped message = - messageFactory.newFromType(geometry_msgs.TransformStamped._TYPE); - Transform transform = Transform.identity(); - FrameTransform frameTransform = - new FrameTransform(transform, GraphName.of("bar"), GraphName.of("foo")); - frameTransform.toTransformStampedMessage(new Time(), message); - frameTransformTree.updateTransform(message); + FrameTransform frameTransform = frameTransformTree.transform("baz", "baz"); + assertTrue(frameTransform != null); + assertEquals(nameResolver.resolve("baz"), frameTransform.getSourceFrame()); + assertEquals(nameResolver.resolve("baz"), frameTransform.getTargetFrame()); + assertEquals(Transform.identity(), frameTransform.getTransform()); } - FrameTransform frameTransform = - frameTransformTree.newFrameTransform(GraphName.of("baz"), GraphName.of("foo")); - assertEquals(nameResolver.resolve("baz"), frameTransform.getSourceFrame()); - assertEquals(nameResolver.resolve("foo"), frameTransform.getTargetFrame()); - assertEquals(Transform.identity(), frameTransform.getTransform()); + // Same node transform. + { + FrameTransform frameTransform = frameTransformTree.transform("bar", "bar"); + assertTrue(frameTransform != null); + assertEquals(nameResolver.resolve("bar"), frameTransform.getSourceFrame()); + assertEquals(nameResolver.resolve("bar"), frameTransform.getTargetFrame()); + assertEquals(Transform.identity(), frameTransform.getTransform()); + } + + // Root-to-root transform. + { + FrameTransform frameTransform = frameTransformTree.transform("foo", "foo"); + assertTrue(frameTransform != null); + assertEquals(nameResolver.resolve("foo"), frameTransform.getSourceFrame()); + assertEquals(nameResolver.resolve("foo"), frameTransform.getTargetFrame()); + assertEquals(Transform.identity(), frameTransform.getTransform()); + } + + // Root-to-leaf transform. + { + FrameTransform frameTransform = frameTransformTree.transform("foo", "baz"); + assertTrue(frameTransform != null); + assertEquals(nameResolver.resolve("foo"), frameTransform.getSourceFrame()); + assertEquals(nameResolver.resolve("baz"), frameTransform.getTargetFrame()); + assertEquals(Transform.identity(), frameTransform.getTransform()); + } } @Test public void testTransformToRoot() { - MessageDefinitionProvider messageDefinitionProvider = new MessageDefinitionReflectionProvider(); - MessageFactory messageFactory = new DefaultMessageFactory(messageDefinitionProvider); - NameResolver nameResolver = NameResolver.newRoot(); - FrameTransformTree frameTransformTree = new FrameTransformTree(nameResolver); - { - geometry_msgs.TransformStamped message = - messageFactory.newFromType(geometry_msgs.TransformStamped._TYPE); Vector3 vector = Vector3.zero(); Quaternion quaternion = new Quaternion(Math.sqrt(0.5), 0, 0, Math.sqrt(0.5)); Transform transform = new Transform(vector, quaternion); - GraphName source = GraphName.of("baz"); - GraphName target = GraphName.of("bar"); - FrameTransform frameTransform = new FrameTransform(transform, source, target); - frameTransform.toTransformStampedMessage(new Time(), message); - frameTransformTree.updateTransform(message); + frameTransformTree.update(newTransformStampedMessage(transform, "baz", "bar", new Time())); } { - geometry_msgs.TransformStamped message = - messageFactory.newFromType(geometry_msgs.TransformStamped._TYPE); Vector3 vector = new Vector3(0, 1, 0); Quaternion quaternion = Quaternion.identity(); Transform transform = new Transform(vector, quaternion); - GraphName source = GraphName.of("bar"); - GraphName target = GraphName.of("foo"); - FrameTransform frameTransform = new FrameTransform(transform, source, target); - frameTransform.toTransformStampedMessage(new Time(), message); - frameTransformTree.updateTransform(message); + frameTransformTree.update(newTransformStampedMessage(transform, "bar", "foo", new Time())); } - FrameTransform frameTransform = - frameTransformTree.newFrameTransform(GraphName.of("baz"), GraphName.of("foo")); + FrameTransform frameTransform = frameTransformTree.transform("baz", "foo"); // If we were to reverse the order of the transforms in our implementation, // we would expect the translation vector to be <0, 0, 1> instead. Vector3 vector = new Vector3(0, 1, 0); @@ -110,4 +155,26 @@ public class FrameTransformTreeTest { assertEquals(nameResolver.resolve("foo"), frameTransform.getTargetFrame()); assertEquals(transform, frameTransform.getTransform()); } + + @Test + public void testTimeTravel() { + FrameTransform frameTransform1 = + new FrameTransform(Transform.identity(), nameResolver.resolve("foo"), + nameResolver.resolve("bar"), new Time()); + FrameTransform frameTransform2 = + new FrameTransform(Transform.identity(), nameResolver.resolve("foo"), + nameResolver.resolve("bar"), new Time(2)); + FrameTransform frameTransform3 = + new FrameTransform(Transform.identity(), nameResolver.resolve("foo"), + nameResolver.resolve("bar"), new Time(4)); + frameTransformTree.update(frameTransform1); + frameTransformTree.update(frameTransform2); + frameTransformTree.update(frameTransform3); + assertEquals(frameTransform3, frameTransformTree.get("foo")); + assertEquals(frameTransform1, frameTransformTree.get("foo", new Time())); + assertEquals(frameTransform1, frameTransformTree.get("foo", new Time(0.5))); + assertEquals(frameTransform2, frameTransformTree.get("foo", new Time(1.5))); + assertEquals(frameTransform2, frameTransformTree.get("foo", new Time(2))); + assertEquals(frameTransform3, frameTransformTree.get("foo", new Time(10))); + } }