Geometry Messages
HORUS provides fundamental geometric primitives used throughout robotics applications for representing position, orientation, and motion.
All geometry messages implement PodMessage for ultra-fast zero-serialization transfer (~50ns).
Twist
3D velocity command with linear and angular components.
Used for commanding robot motion in 3D space. For 2D robots, only x (forward) and yaw (rotation) are typically used.
use horus::prelude::*;
// Create 3D velocity command
let twist = Twist::new(
[1.0, 0.0, 0.0], // linear: [x, y, z] in m/s
[0.0, 0.0, 0.5] // angular: [roll, pitch, yaw] in rad/s
);
// For 2D robots (common case)
let cmd = Twist::new_2d(0.5, 0.3); // 0.5 m/s forward, 0.3 rad/s rotation
println!("Linear X: {}, Angular Z: {}", cmd.linear[0], cmd.angular[2]);
// Stop command (all zeros)
let stop = Twist::stop();
// Validate the message
if twist.is_valid() {
println!("Twist is valid");
}
Fields:
| Field | Type | Description |
|---|---|---|
linear | [f64; 3] | Linear velocity [x, y, z] in m/s |
angular | [f64; 3] | Angular velocity [roll, pitch, yaw] in rad/s |
timestamp_ns | u64 | Nanoseconds since epoch |
Methods:
| Method | Description |
|---|---|
new(linear, angular) | Create full 3D velocity command |
new_2d(linear_x, angular_z) | Create 2D velocity (forward + rotation) |
stop() | Create stop command (all zeros) |
is_valid() | Check if all values are finite |
Pose2D
2D pose representation (position and orientation).
Commonly used for mobile robots operating in planar environments.
use horus::prelude::*;
// Create 2D pose
let pose = Pose2D::new(5.0, 3.0, 1.57); // x, y, theta
println!("Position: ({}, {}), Orientation: {} rad", pose.x, pose.y, pose.theta);
// Create pose at origin
let origin = Pose2D::origin();
// Calculate distance between poses
let other = Pose2D::new(8.0, 7.0, 0.0);
let distance = pose.distance_to(&other);
println!("Distance: {:.2} m", distance);
// Normalize angle to [-π, π]
let mut pose_copy = pose;
pose_copy.normalize_angle();
// Check validity
assert!(pose.is_valid());
Fields:
| Field | Type | Description |
|---|---|---|
x | f64 | X position in meters |
y | f64 | Y position in meters |
theta | f64 | Orientation angle in radians |
timestamp_ns | u64 | Nanoseconds since epoch |
Methods:
| Method | Description |
|---|---|
new(x, y, theta) | Create a new 2D pose |
origin() | Create pose at origin (0, 0, 0) |
distance_to(&other) | Calculate euclidean distance to another pose |
normalize_angle() | Normalize theta to [-π, π] |
is_valid() | Check if all values are finite |
TransformStamped
3D transformation (translation and rotation).
Represents a full 3D transformation using translation vector and quaternion rotation. Used for coordinate frame transformations.
use horus::prelude::*;
// Create transform with translation and quaternion rotation
let transform = TransformStamped::new(
[1.0, 2.0, 0.5], // translation [x, y, z]
[0.0, 0.0, 0.0, 1.0] // rotation [x, y, z, w] quaternion
);
// Identity transform (no translation or rotation)
let identity = TransformStamped::identity();
// Create from 2D pose (z=0, only yaw rotation)
let pose = Pose2D::new(3.0, 4.0, 1.57);
let tf_from_pose = TransformStamped::from_pose_2d(&pose);
// Validate quaternion is normalized
if transform.is_valid() {
println!("Transform is valid (quaternion normalized)");
}
// Normalize quaternion if needed
let mut tf = TransformStamped::new([0.0; 3], [1.0, 1.0, 1.0, 1.0]);
tf.normalize_rotation();
Fields:
| Field | Type | Description |
|---|---|---|
translation | [f64; 3] | Translation [x, y, z] in meters |
rotation | [f64; 4] | Rotation as quaternion [x, y, z, w] |
timestamp_ns | u64 | Nanoseconds since epoch |
Methods:
| Method | Description |
|---|---|
new(translation, rotation) | Create new transform |
identity() | Identity transform (no change) |
from_pose_2d(&pose) | Create from 2D pose |
is_valid() | Check quaternion normalized and values finite |
normalize_rotation() | Normalize the quaternion component |
Point3
3D point representation.
use horus::prelude::*;
// Create 3D point
let point = Point3::new(1.0, 2.0, 3.0);
println!("Point: ({}, {}, {})", point.x, point.y, point.z);
// Create point at origin
let origin = Point3::origin();
// Calculate distance between points
let other = Point3::new(4.0, 6.0, 3.0);
let distance = point.distance_to(&other);
println!("Distance: {:.2} m", distance); // 5.0 m
Fields:
| Field | Type | Description |
|---|---|---|
x | f64 | X coordinate in meters |
y | f64 | Y coordinate in meters |
z | f64 | Z coordinate in meters |
Methods:
| Method | Description |
|---|---|
new(x, y, z) | Create new point |
origin() | Create point at origin (0, 0, 0) |
distance_to(&other) | Calculate euclidean distance |
Vector3
3D vector representation with mathematical operations.
use horus::prelude::*;
// Create 3D vector
let v = Vector3::new(3.0, 4.0, 0.0);
println!("Vector: ({}, {}, {})", v.x, v.y, v.z);
// Zero vector
let zero = Vector3::zero();
// Calculate magnitude
let mag = v.magnitude();
println!("Magnitude: {:.2}", mag); // 5.0
// Normalize vector
let mut unit = Vector3::new(3.0, 4.0, 0.0);
unit.normalize();
println!("Unit vector: ({:.2}, {:.2}, {})", unit.x, unit.y, unit.z); // (0.6, 0.8, 0)
// Dot product
let v1 = Vector3::new(1.0, 2.0, 3.0);
let v2 = Vector3::new(4.0, 5.0, 6.0);
let dot = v1.dot(&v2);
println!("Dot product: {}", dot); // 32.0
// Cross product
let i = Vector3::new(1.0, 0.0, 0.0);
let j = Vector3::new(0.0, 1.0, 0.0);
let k = i.cross(&j);
println!("i × j = ({}, {}, {})", k.x, k.y, k.z); // (0, 0, 1)
Fields:
| Field | Type | Description |
|---|---|---|
x | f64 | X component |
y | f64 | Y component |
z | f64 | Z component |
Methods:
| Method | Description |
|---|---|
new(x, y, z) | Create new vector |
zero() | Create zero vector |
magnitude() | Calculate vector length |
normalize() | Normalize to unit vector |
dot(&other) | Dot product with another vector |
cross(&other) | Cross product with another vector |
Quaternion
Quaternion for 3D rotation representation.
Quaternions avoid gimbal lock and provide smooth interpolation for rotations.
use horus::prelude::*;
// Create quaternion directly
let q = Quaternion::new(0.0, 0.0, 0.0, 1.0); // [x, y, z, w]
// Identity quaternion (no rotation)
let identity = Quaternion::identity();
assert_eq!(identity.w, 1.0);
// Create from Euler angles (roll, pitch, yaw)
let q_from_euler = Quaternion::from_euler(
0.0, // roll (rotation around X)
0.0, // pitch (rotation around Y)
1.57 // yaw (rotation around Z) - 90 degrees
);
// Normalize quaternion
let mut q_unnorm = Quaternion::new(1.0, 1.0, 1.0, 1.0);
q_unnorm.normalize();
// Validate
assert!(q.is_valid());
Fields:
| Field | Type | Description |
|---|---|---|
x | f64 | X component (imaginary i) |
y | f64 | Y component (imaginary j) |
z | f64 | Z component (imaginary k) |
w | f64 | W component (real/scalar) |
Methods:
| Method | Description |
|---|---|
new(x, y, z, w) | Create new quaternion |
identity() | Identity quaternion (no rotation) |
from_euler(roll, pitch, yaw) | Create from Euler angles |
normalize() | Normalize to unit quaternion |
is_valid() | Check all values are finite |
Zero-Copy Performance
All geometry messages are optimized for zero-copy shared memory transfer (~50ns):
use horus::prelude::*;
// HORUS automatically uses zero-copy for compatible types
let twist_topic: Topic<Twist> = Topic::new("velocity_cmd")?;
let pose_topic: Topic<Pose2D> = Topic::new("robot_pose")?;
// Send - automatically zero-copy for Pod types
twist_topic.send(Twist::new_2d(0.5, 0.1));
// Receive - zero-copy access
if let Some(pose) = pose_topic.recv() {
println!("Robot at ({:.2}, {:.2})", pose.x, pose.y);
}
Robot Control Example
use horus::prelude::*;
struct DifferentialDriveController {
pose_sub: Topic<Pose2D>,
goal_sub: Topic<Pose2D>,
cmd_pub: Topic<Twist>,
}
impl Node for DifferentialDriveController {
fn name(&self) -> &str { "DiffDriveController" }
fn tick(&mut self) {
// Get current pose and goal
let pose = match self.pose_sub.recv() {
Some(p) => p,
None => return,
};
let goal = match self.goal_sub.recv() {
Some(g) => g,
None => return,
};
// Calculate distance and angle to goal
let dx = goal.x - pose.x;
let dy = goal.y - pose.y;
let distance = (dx * dx + dy * dy).sqrt();
let angle_to_goal = dy.atan2(dx);
let angle_error = angle_to_goal - pose.theta;
// Simple proportional controller
let cmd = if distance > 0.1 {
// Move towards goal
Twist::new_2d(
0.3 * distance.min(1.0), // Linear velocity (capped)
1.0 * angle_error // Angular velocity
)
} else {
// Goal reached
Twist::stop()
};
self.cmd_pub.send(cmd);
}
}
Coordinate Frames
HORUS uses right-handed coordinate systems:
- X-axis: Forward (red)
- Y-axis: Left (green)
- Z-axis: Up (blue)
For 2D robots:
- X: Forward
- Y: Left
- Theta: Counter-clockwise rotation from X-axis
See Also
- Navigation Messages - Goals, paths, occupancy grids
- Sensor Messages - IMU, odometry
- Force Messages - Wrench, force vectors
- PodMessage - Zero-copy transfer