Lightweight 2D robotics simulator (C++) with a small, pragmatic core and clear Agent ↔ Simulator split.
Key libraries:
- datapod — geographic types & serialization (WGS/ENU, Pose, Geo, etc.)
- flywheel — 2D rigid-body physics and collisions
- drivekit — path following controllers (PID, MPPI, Stanley, Pure Pursuit, MPC)
- agent47 — IPC/TCP communication protocol between Agent and Simulator
- rerun — optional visualization / recording stream
- robomod — URDF machine loader
#include "flatsim/agent.hpp"
#include "flatsim/simulator.hpp"
#include "flatsim/utils.hpp"
#include <drivekit.hpp>
int main() {
// Create simulator with datum (required for GPS conversions)
datapod::Geo datum{51.989, 5.658, 53.8};
simulator::Simulator sim(500.0f, 500.0f, datum);
// Spawn a robot from URDF file
datapod::Pose pose = utils::make_pose_2d(0.0, 0.0, 0.0);
auto &robot = sim.spawn_agent("machines/urdf/tractor.urdf", pose, "my_robot");
// Set up path following with Pure Pursuit controller
robot.tracker()->set_controller_type(drivekit::TrackerType::PURE_PURSUIT);
robot.set_tracker_enabled(true);
// Define a path
std::vector<datapod::Point> waypoints = {{10, 0}, {20, 5}, {30, 10}};
drivekit::PathGoal path(waypoints, 2.0f, 3.0f, false);
robot.tracker()->set_path(path);
// Run simulation loop
const float dt = 0.016f; // ~60 Hz
while (!robot.tracker()->is_path_completed()) {
sim.tick(dt);
sim.tock();
}
}| Mode | Description |
|---|---|
| LOCAL | Simulator and Agents in same process (tests, debugging) |
| IPC | Agents connect via Unix sockets (same machine) |
| TCP | Agents connect over network (different machines) |
The Agent class provides a simplified interface for robot control:
// Control (Twist commands - Simulator handles wheel conversion)
agent.set_velocity(linear, angular); // Set both at once
agent.set_linear(1.0f); // Forward/backward
agent.set_angular(0.5f); // Rotation
// Speed scaling and braking
agent.set_speed(0.8f); // Scale factor (0.0 - 1.0)
agent.speed_up(0.1f); // Increase speed
agent.slow_down(0.1f); // Decrease speed
agent.brake(); // Stop immediately
// Path following (via drivekit::Tracker)
agent.tracker()->set_controller_type(drivekit::TrackerType::PID);
agent.tracker()->set_path(path);
agent.set_tracker_enabled(true); // Enable autonomous navigation
// State accessors
agent.get_position(); // Current pose
agent.get_linear_velocity(); // Current linear velocity
agent.get_angular_velocity(); // Current angular velocity
agent.get_sensor_data(); // Sensor readings (lidar, etc.)Available controller types in drivekit::TrackerType:
PID— Classic PID controlMPPI— Model Predictive Path IntegralSTANLEY— Stanley lateral controllerPURE_PURSUIT— Carrot-following controllerMPC— Model Predictive Control
Server (Simulator):
#include "flatsim/simulator.hpp"
int main() {
datapod::Geo datum{51.989, 5.658, 53.8};
// IPC mode (Unix sockets)
simulator::Simulator sim(simulator::Conn::IPC, "", 500.0f, 500.0f, datum);
// Or TCP mode (network)
// simulator::Simulator sim(simulator::Conn::TCP, "0.0.0.0", 500.0f, 500.0f, datum);
while (true) {
sim.tick(0.016f);
sim.tock();
}
}Client (Agent via agent47):
#include "flatsim/agent.hpp"
int main() {
types::Machine config = /* load from URDF */;
// Connect via IPC (empty address) or TCP (server IP)
agent::Agent client(config, "", rec); // IPC
// agent::Agent client(config, "192.168.1.10", rec); // TCP
client.spawn();
while (running) {
client.set_velocity(1.0f, 0.0f);
client.tick(0.016f, 100);
client.tock();
}
client.despawn();
}flatsim/
├── include/flatsim/
│ ├── agent.hpp # Agent class
│ ├── simulator.hpp # Simulator class
│ ├── types.hpp # Core types (Machine, SensorData, etc.)
│ └── utils.hpp # Utility functions
├── src/
│ ├── agent.cpp
│ ├── simulator.cpp
│ └── simulator/ # Physics, machine handling
├── machines/
│ └── urdf/ # Robot URDF definitions
│ ├── tractor.urdf
│ ├── harvester.urdf
│ └── ...
└── examples/
├── pipe_server.cpp # IPC/TCP server example
├── pipe_agent.cpp # IPC/TCP agent example
├── pipe_one_binary.cpp # Single binary with both
└── complex_examples/ # Path following demos
├── test_pid.cpp
├── test_mppi.cpp
├── test_stanley.cpp
├── test_pure_pursuit.cpp
└── ...
# xmake (recommended)
xmake build
# Run examples
xmake run pipe_one_binary
xmake run test_pid┌─────────────────────────────────────────────────────────────┐
│ Simulator │
│ ┌─────────┐ ┌─────────┐ ┌──────────────────────────────┐ │
│ │ World │ │ Machine │ │ twist_to_wheel_control() │ │
│ │(physics)│ │ (URDF) │ │ (Ackermann/Differential) │ │
│ └─────────┘ └─────────┘ └──────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
▲ │
│ Commands (Twist) │ State (Pose, Sensors)
│ ▼
┌─────────────────────────────────────────────────────────────┐
│ Agent │
│ ┌────────────────┐ ┌────────────────────────────────────┐ │
│ │ drivekit │ │ set_velocity() / get_twist() │ │
│ │ ::Tracker │ │ (Twist commands) │ │
│ └────────────────┘ └────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Key design decisions:
- Agent sends Twist commands (linear/angular velocity)
- Simulator converts Twist → WheelControl (Ackermann steering or differential drive)
- Path following is handled by drivekit::Tracker on the Agent side
- In networked mode, agent47 protocol handles communication (COMMAND, FEEDBACK, SENSOR, HEARTBEAT)
- URDF paths are resolved relative to the working directory or project root
- Rerun visualization is optional (pass
nullptrforrecparameter) - The datum (GPS reference point) is required for coordinate conversions
