Skip to content

Commit f0fb23a

Browse files
ajsampathkmsz-rai
andauthored
Add Laserscan support (#28)
* LaserScan support update * readme update * fix no intensity segfault * add lidar2d pattern loader + utils * bug fix * Hacky metadata * YieldNode switching * fix node for visualize * clean up + get min max angles from sdf * minor cleanup * Readme Update * Apply suggestions from code review Co-authored-by: Mateusz Szczygielski <[email protected]> * clear vector before resereving --------- Co-authored-by: Mateusz Szczygielski <[email protected]>
1 parent 2be4728 commit f0fb23a

File tree

5 files changed

+160
-10
lines changed

5 files changed

+160
-10
lines changed

README.md

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,29 @@ Inside the link entity in your model, add a custom sensor:
185185
<pattern_preset_path>/home/some1/your-preset.mat3x4f</pattern_preset_path>
186186
```
187187

188+
- **pattern_lidar2d**\
189+
Almost the same as `pattern_uniform` but only has
190+
the `horizontal` element and publishes a
191+
`LaserScan` message instead of a point cloud
192+
```xml
193+
<sensor name="Pattern2DLidar" type="custom">
194+
<plugin filename="RGLServerPluginInstance" name="rgl::RGLServerPluginInstance">
195+
<range>100</range>
196+
<update_rate>10</update_rate>
197+
<update_on_paused_sim>false</update_on_paused_sim>
198+
<topic>rgl_lidar</topic>
199+
<frame>RGLLidar</frame>
200+
<pattern_lidar2d>
201+
<horizontal>
202+
<samples>1800</samples>
203+
<min_angle>-3.14159</min_angle>
204+
<max_angle>3.14159</max_angle>
205+
</horizontal>
206+
</pattern_lidar2d>
207+
</plugin>
208+
</sensor>
209+
```
210+
188211
## How to visualize in Gazebo
189212
To enable non-uniform point clouds visualization in Gazebo Fortress that are produced by `RGLServerPlugin`, we port [PointCloud gui plugin](https://github.com/gazebosim/gz-gui/tree/gz-gui7/src/plugins/point_cloud) from Gazebo Garden and create `RGLVisualize`. It reimplements the minimal functionality of receiving PointCloudPacked messages (in a world coordinate frame) and rendering them in the scene.
190213

@@ -202,4 +225,4 @@ At [Robotec.AI](https://robotec.ai/) we care about every little detail of our pr
202225
## Ray pattern comparison
203226
| **RGL uniform pattern** | **gpu_lidar uniform pattern** |
204227
|---|---|
205-
| ![](docs/images/RGL_uniform.png) | ![](docs/images/GPU_lidar_uniform.png) |
228+
| ![](docs/images/RGL_uniform.png) | ![](docs/images/GPU_lidar_uniform.png) |

RGLServerPlugin/include/LidarPatternLoader.hh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,12 @@ private:
4242
static bool LoadPatternFromCustom(const sdf::ElementConstPtr& sdf, std::vector<rgl_mat3x4f>& outPattern);
4343
static bool LoadPatternFromPreset(const sdf::ElementConstPtr& sdf, std::vector<rgl_mat3x4f>& outPattern);
4444
static bool LoadPatternFromPresetPath(const sdf::ElementConstPtr& sdf, std::vector<rgl_mat3x4f>& outPattern);
45+
static bool LoadPatternFromLidar2d(const sdf::ElementConstPtr& sdf, std::vector<rgl_mat3x4f>& outPattern);
4546

4647
static rgl_mat3x4f AnglesToRglMat3x4f(const ignition::math::Angle& roll,
4748
const ignition::math::Angle& pitch,
4849
const ignition::math::Angle& yaw);
49-
50+
5051
template<typename T>
5152
static std::vector<T> LoadVector(const std::filesystem::path& path);
5253

RGLServerPlugin/include/RGLServerPluginInstance.hh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,26 +70,34 @@ private:
7070
void RayTrace(std::chrono::steady_clock::duration sim_time);
7171

7272
ignition::msgs::PointCloudPacked CreatePointCloudMsg(std::chrono::steady_clock::duration sim_time, std::string frame, int hitpointCount);
73+
ignition::msgs::LaserScan CreateLaserScanMsg(std::chrono::steady_clock::duration sim_time, std::string frame, int hitpointCount);
7374

7475
void DestroyLidar();
7576

7677
std::string topicName;
7778
std::string frameId;
7879
float lidarRange;
80+
ignition::math::Angle scanHMin;
81+
ignition::math::Angle scanHMax;
82+
int scanHSamples;
7983
std::vector<rgl_mat3x4f> lidarPattern;
8084
std::vector<rgl_vec3f> resultPointCloud;
85+
std::vector<float> resultDistances;
8186

8287
bool updateOnPausedSim = false;
88+
bool publishLaserScan = false;
8389

8490
ignition::gazebo::Entity thisLidarEntity;
8591
ignition::transport::Node::Publisher pointCloudPublisher;
92+
ignition::transport::Node::Publisher laserScanPublisher;
8693
ignition::transport::Node::Publisher pointCloudWorldPublisher;
8794
ignition::transport::Node gazeboNode;
8895

8996
rgl_node_t rglNodeUseRays = nullptr;
9097
rgl_node_t rglNodeLidarPose = nullptr;
9198
rgl_node_t rglNodeRaytrace = nullptr;
9299
rgl_node_t rglNodeCompact = nullptr;
100+
rgl_node_t rglNodeYield = nullptr;
93101
rgl_node_t rglNodeToLidarFrame = nullptr;
94102

95103
std::chrono::steady_clock::duration raytraceIntervalTime;

RGLServerPlugin/src/Lidar.cc

Lines changed: 92 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,17 @@ bool RGLServerPluginInstance::LoadConfiguration(const std::shared_ptr<const sdf:
6767
if (!LidarPatternLoader::Load(sdf, lidarPattern)) {
6868
return false;
6969
}
70+
71+
// Check for 2d pattern and get LaserScan parameters
72+
if (sdf->HasElement("pattern_lidar2d")) {
73+
ignmsg << "Lidar is 2D, switching to publish LaserScan messages";
74+
publishLaserScan = true;
75+
resultDistances.resize(lidarPattern.size());
76+
scanHMin = sdf->FindElement("pattern_lidar2d")->FindElement("horizontal")->Get<float>("min_angle");
77+
scanHMax = sdf->FindElement("pattern_lidar2d")->FindElement("horizontal")->Get<float>("max_angle");
78+
scanHSamples = lidarPattern.size();
79+
}
80+
7081
// Resize container for result point cloud
7182
resultPointCloud.resize(lidarPattern.size());
7283

@@ -84,10 +95,19 @@ void RGLServerPluginInstance::CreateLidar(ignition::gazebo::Entity entity,
8495
0, 0, 1, 0
8596
};
8697

98+
// set desired fields for API call
99+
std::vector<rgl_field_t> yieldFields;
100+
yieldFields.push_back(RGL_FIELD_XYZ_F32);
101+
102+
if (publishLaserScan) {
103+
yieldFields.push_back(RGL_FIELD_DISTANCE_F32);
104+
}
105+
87106
if (!CheckRGL(rgl_node_rays_from_mat3x4f(&rglNodeUseRays, lidarPattern.data(), lidarPattern.size())) ||
88107
!CheckRGL(rgl_node_rays_transform(&rglNodeLidarPose, &identity)) ||
89108
!CheckRGL(rgl_node_raytrace(&rglNodeRaytrace, nullptr, lidarRange)) ||
90109
!CheckRGL(rgl_node_points_compact(&rglNodeCompact)) ||
110+
!CheckRGL(rgl_node_points_yield(&rglNodeYield, yieldFields.data(), yieldFields.size())) ||
91111
!CheckRGL(rgl_node_points_transform(&rglNodeToLidarFrame, &identity))) {
92112

93113
ignerr << "Failed to create RGL nodes when initializing lidar. Disabling plugin.\n";
@@ -103,7 +123,23 @@ void RGLServerPluginInstance::CreateLidar(ignition::gazebo::Entity entity,
103123
return;
104124
}
105125

106-
pointCloudPublisher = gazeboNode.Advertise<ignition::msgs::PointCloudPacked>(topicName);
126+
if (!publishLaserScan) {
127+
128+
if(!CheckRGL(rgl_graph_node_add_child(rglNodeToLidarFrame, rglNodeYield))) {
129+
ignerr << "Failed to connect RGL nodes when initializing lidar. Disabling plugin.\n";
130+
}
131+
ignmsg << "Start publishing PointCloudPacked messages on topic '" << topicName << "'\n";
132+
pointCloudPublisher = gazeboNode.Advertise<ignition::msgs::PointCloudPacked>(topicName);
133+
134+
} else {
135+
136+
if(!CheckRGL(rgl_graph_node_add_child(rglNodeRaytrace, rglNodeYield))) {
137+
ignerr << "Failed to connect RGL nodes when initializing lidar. Disabling plugin.\n";
138+
}
139+
ignmsg << "Start publishing LaserScan messages on topic '" << topicName << "'\n";
140+
laserScanPublisher = gazeboNode.Advertise<ignition::msgs::LaserScan>(topicName);
141+
142+
}
107143
pointCloudWorldPublisher = gazeboNode.Advertise<ignition::msgs::PointCloudPacked>(topicName + worldTopicPostfix);
108144

109145
isLidarInitialized = true;
@@ -155,15 +191,30 @@ void RGLServerPluginInstance::RayTrace(std::chrono::steady_clock::duration simTi
155191
}
156192

157193
int32_t hitpointCount = 0;
158-
if (!CheckRGL(rgl_graph_get_result_size(rglNodeToLidarFrame, RGL_FIELD_XYZ_F32, &hitpointCount, nullptr)) ||
159-
!CheckRGL(rgl_graph_get_result_data(rglNodeToLidarFrame, RGL_FIELD_XYZ_F32, resultPointCloud.data()))) {
160194

161-
ignerr << "Failed to get result data from RGL lidar.\n";
195+
if (!publishLaserScan) {
196+
if (!CheckRGL(rgl_graph_get_result_size(rglNodeYield, RGL_FIELD_XYZ_F32, &hitpointCount, nullptr)) ||
197+
!CheckRGL(rgl_graph_get_result_data(rglNodeYield, RGL_FIELD_XYZ_F32, resultPointCloud.data()))) {
198+
199+
ignerr << "Failed to get result data from RGL lidar.\n";
200+
return;
201+
}
202+
} else {
203+
if (!CheckRGL(rgl_graph_get_result_size(rglNodeYield, RGL_FIELD_DISTANCE_F32, &hitpointCount, nullptr)) ||
204+
!CheckRGL(rgl_graph_get_result_data(rglNodeYield, RGL_FIELD_DISTANCE_F32, resultDistances.data()))) {
205+
206+
ignerr << "Failed to get result distances from RGL lidar.\n";
162207
return;
208+
}
163209
}
164210

165-
auto msg = CreatePointCloudMsg(simTime, frameId, hitpointCount);
166-
pointCloudPublisher.Publish(msg);
211+
if (!publishLaserScan) {
212+
auto msg = CreatePointCloudMsg(simTime, frameId, hitpointCount);
213+
pointCloudPublisher.Publish(msg);
214+
} else {
215+
auto msg = CreateLaserScanMsg(simTime, frameId, hitpointCount);
216+
laserScanPublisher.Publish(msg);
217+
}
167218

168219
if (pointCloudWorldPublisher.HasConnections()) {
169220
if (!CheckRGL(rgl_graph_get_result_size(rglNodeToLidarFrame, RGL_FIELD_XYZ_F32, &hitpointCount, nullptr)) ||
@@ -177,6 +228,36 @@ void RGLServerPluginInstance::RayTrace(std::chrono::steady_clock::duration simTi
177228
}
178229
}
179230

231+
ignition::msgs::LaserScan RGLServerPluginInstance::CreateLaserScanMsg(std::chrono::steady_clock::duration simTime, std::string frame, int hitpointCount)
232+
{
233+
ignition::msgs::LaserScan outMsg;
234+
*outMsg.mutable_header()->mutable_stamp() = ignition::msgs::Convert(simTime);
235+
auto _frame = outMsg.mutable_header()->add_data();
236+
_frame->set_key("frame_id");
237+
_frame->add_value(frame);
238+
239+
outMsg.set_frame(frame);
240+
outMsg.set_count(hitpointCount);
241+
242+
outMsg.set_range_min(0.0);
243+
outMsg.set_range_max(lidarRange);
244+
245+
ignition::math::Angle hStep((scanHMax-scanHMin)/scanHSamples);
246+
247+
outMsg.set_angle_min(scanHMin.Radian());
248+
outMsg.set_angle_max(scanHMax.Radian());
249+
outMsg.set_angle_step(hStep.Radian());
250+
251+
if (outMsg.ranges_size() != hitpointCount) {
252+
for (int i=0; i < hitpointCount; ++i) {
253+
outMsg.add_ranges(resultDistances[i]);
254+
outMsg.add_intensities(100.0);
255+
}
256+
}
257+
258+
return outMsg;
259+
}
260+
180261
ignition::msgs::PointCloudPacked RGLServerPluginInstance::CreatePointCloudMsg(std::chrono::steady_clock::duration simTime, std::string frame, int hitpointCount)
181262
{
182263
ignition::msgs::PointCloudPacked outMsg;
@@ -202,7 +283,11 @@ void RGLServerPluginInstance::DestroyLidar()
202283
ignerr << "Failed to destroy RGL lidar.\n";
203284
}
204285
// Reset publishers
205-
pointCloudPublisher = ignition::transport::Node::Publisher();
286+
if (!publishLaserScan) {
287+
pointCloudPublisher = ignition::transport::Node::Publisher();
288+
} else {
289+
laserScanPublisher = ignition::transport::Node::Publisher();
290+
}
206291
pointCloudWorldPublisher = ignition::transport::Node::Publisher();
207292
isLidarInitialized = false;
208293
}

RGLServerPlugin/src/LidarPatternLoader.cc

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ std::map<std::string, LidarPatternLoader::LoadFuncType> LidarPatternLoader::patt
3737
{"pattern_uniform", std::bind(&LidarPatternLoader::LoadPatternFromUniform, _1, _2)},
3838
{"pattern_custom", std::bind(&LidarPatternLoader::LoadPatternFromCustom, _1, _2)},
3939
{"pattern_preset", std::bind(&LidarPatternLoader::LoadPatternFromPreset, _1, _2)},
40-
{"pattern_preset_path", std::bind(&LidarPatternLoader::LoadPatternFromPresetPath, _1, _2)}
40+
{"pattern_preset_path", std::bind(&LidarPatternLoader::LoadPatternFromPresetPath, _1, _2)},
41+
{"pattern_lidar2d", std::bind(&LidarPatternLoader::LoadPatternFromLidar2d, _1, _2)}
4142
};
4243

4344
bool LidarPatternLoader::Load(const sdf::ElementConstPtr& sdf, std::vector<rgl_mat3x4f>& outPattern)
@@ -213,6 +214,38 @@ bool LidarPatternLoader::LoadPatternFromPresetPath(const sdf::ElementConstPtr& s
213214
return true;
214215
}
215216

217+
bool LidarPatternLoader::LoadPatternFromLidar2d(const sdf::ElementConstPtr& sdf, std::vector<rgl_mat3x4f>& outPattern)
218+
{
219+
if (!sdf->HasElement("horizontal")) {
220+
ignerr << "Failed to load uniform pattern. A horizontal element is required, but it is not set.\n";
221+
return false;
222+
}
223+
224+
ignition::math::Angle hMin, hMax;
225+
int hSamples;
226+
227+
if (!LoadAnglesAndSamplesElement(sdf->FindElement("horizontal"), hMin, hMax, hSamples)) {
228+
return false;
229+
}
230+
231+
outPattern.clear();
232+
outPattern.reserve(hSamples);
233+
234+
ignition::math::Angle hStep((hMax - hMin) / static_cast<double>(hSamples));
235+
236+
auto hAngle = hMin;
237+
for (int i = 0; i < hSamples; ++i) {
238+
outPattern.push_back(
239+
AnglesToRglMat3x4f(ignition::math::Angle::Zero,
240+
// Inverse and shift 90deg pitch to match uniform pattern from Gazebo
241+
ignition::math::Angle::HalfPi,
242+
hAngle));
243+
hAngle += hStep;
244+
}
245+
246+
return true;
247+
}
248+
216249
template<typename T>
217250
std::vector<T> LidarPatternLoader::LoadVector(const fs::path& path)
218251
{

0 commit comments

Comments
 (0)