Store node initial pose in constant data. (#568)
parent
cae9c02e05
commit
bd8a2e6a92
|
@ -51,9 +51,8 @@ class GlobalTrajectoryBuilder
|
||||||
if (insertion_result == nullptr) {
|
if (insertion_result == nullptr) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
sparse_pose_graph_->AddScan(
|
sparse_pose_graph_->AddScan(insertion_result->constant_data, trajectory_id_,
|
||||||
insertion_result->constant_data, insertion_result->pose_observation,
|
insertion_result->insertion_submaps);
|
||||||
trajectory_id_, insertion_result->insertion_submaps);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddSensorData(const sensor::ImuData& imu_data) override {
|
void AddSensorData(const sensor::ImuData& imu_data) override {
|
||||||
|
|
|
@ -28,4 +28,5 @@ message TrajectoryNodeData {
|
||||||
optional sensor.proto.CompressedPointCloud high_resolution_point_cloud = 4;
|
optional sensor.proto.CompressedPointCloud high_resolution_point_cloud = 4;
|
||||||
optional sensor.proto.CompressedPointCloud low_resolution_point_cloud = 5;
|
optional sensor.proto.CompressedPointCloud low_resolution_point_cloud = 5;
|
||||||
repeated float rotational_scan_matcher_histogram = 6;
|
repeated float rotational_scan_matcher_histogram = 6;
|
||||||
|
optional transform.proto.Rigid3d initial_pose = 7;
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,8 @@ proto::TrajectoryNodeData ToProto(const TrajectoryNode::Data& constant_data) {
|
||||||
proto.add_rotational_scan_matcher_histogram(
|
proto.add_rotational_scan_matcher_histogram(
|
||||||
constant_data.rotational_scan_matcher_histogram(i));
|
constant_data.rotational_scan_matcher_histogram(i));
|
||||||
}
|
}
|
||||||
|
*proto.mutable_initial_pose() =
|
||||||
|
transform::ToProto(constant_data.initial_pose);
|
||||||
return proto;
|
return proto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,7 +65,8 @@ TrajectoryNode::Data FromProto(const proto::TrajectoryNodeData& proto) {
|
||||||
.Decompress(),
|
.Decompress(),
|
||||||
sensor::CompressedPointCloud(proto.low_resolution_point_cloud())
|
sensor::CompressedPointCloud(proto.low_resolution_point_cloud())
|
||||||
.Decompress(),
|
.Decompress(),
|
||||||
rotational_scan_matcher_histogram};
|
rotational_scan_matcher_histogram,
|
||||||
|
transform::ToRigid3(proto.initial_pose())};
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace mapping
|
} // namespace mapping
|
||||||
|
|
|
@ -45,6 +45,9 @@ struct TrajectoryNode {
|
||||||
sensor::PointCloud high_resolution_point_cloud;
|
sensor::PointCloud high_resolution_point_cloud;
|
||||||
sensor::PointCloud low_resolution_point_cloud;
|
sensor::PointCloud low_resolution_point_cloud;
|
||||||
Eigen::VectorXf rotational_scan_matcher_histogram;
|
Eigen::VectorXf rotational_scan_matcher_histogram;
|
||||||
|
|
||||||
|
// The initial unoptimized node pose.
|
||||||
|
transform::Rigid3d initial_pose;
|
||||||
};
|
};
|
||||||
|
|
||||||
common::Time time() const { return constant_data->time; }
|
common::Time time() const { return constant_data->time; }
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
#include "Eigen/Core"
|
#include "Eigen/Core"
|
||||||
#include "cartographer/common/time.h"
|
#include "cartographer/common/time.h"
|
||||||
#include "cartographer/mapping/proto/trajectory_node_data.pb.h"
|
#include "cartographer/mapping/proto/trajectory_node_data.pb.h"
|
||||||
|
#include "cartographer/transform/rigid_transform_test_helpers.h"
|
||||||
#include "gtest/gtest.h"
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
namespace cartographer {
|
namespace cartographer {
|
||||||
|
@ -36,7 +37,8 @@ TEST(TrajectoryNodeTest, ToAndFromProto) {
|
||||||
sensor::CompressedPointCloud({{2.f, 3.f, 4.f}}).Decompress(),
|
sensor::CompressedPointCloud({{2.f, 3.f, 4.f}}).Decompress(),
|
||||||
sensor::CompressedPointCloud({{-1.f, 2.f, 0.f}}).Decompress(),
|
sensor::CompressedPointCloud({{-1.f, 2.f, 0.f}}).Decompress(),
|
||||||
Eigen::VectorXf::Unit(20, 4),
|
Eigen::VectorXf::Unit(20, 4),
|
||||||
};
|
transform::Rigid3d({1., 2., 3.},
|
||||||
|
Eigen::Quaterniond(4., 5., -6., -7.).normalized())};
|
||||||
const proto::TrajectoryNodeData proto = ToProto(expected);
|
const proto::TrajectoryNodeData proto = ToProto(expected);
|
||||||
const TrajectoryNode::Data actual = FromProto(proto);
|
const TrajectoryNode::Data actual = FromProto(proto);
|
||||||
EXPECT_EQ(expected.time, actual.time);
|
EXPECT_EQ(expected.time, actual.time);
|
||||||
|
@ -49,6 +51,8 @@ TEST(TrajectoryNodeTest, ToAndFromProto) {
|
||||||
actual.low_resolution_point_cloud);
|
actual.low_resolution_point_cloud);
|
||||||
EXPECT_EQ(expected.rotational_scan_matcher_histogram,
|
EXPECT_EQ(expected.rotational_scan_matcher_histogram,
|
||||||
actual.rotational_scan_matcher_histogram);
|
actual.rotational_scan_matcher_histogram);
|
||||||
|
EXPECT_THAT(actual.initial_pose,
|
||||||
|
transform::IsNearly(expected.initial_pose, 1e-9));
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
|
@ -184,8 +184,9 @@ LocalTrajectoryBuilder::AddAccumulatedRangeData(
|
||||||
filtered_gravity_aligned_point_cloud,
|
filtered_gravity_aligned_point_cloud,
|
||||||
{}, // 'high_resolution_point_cloud' is only used in 3D.
|
{}, // 'high_resolution_point_cloud' is only used in 3D.
|
||||||
{}, // 'low_resolution_point_cloud' is only used in 3D.
|
{}, // 'low_resolution_point_cloud' is only used in 3D.
|
||||||
}),
|
{}, // 'rotational_scan_matcher_histogram' is only used in 3D.
|
||||||
pose_estimate, std::move(insertion_submaps)});
|
pose_estimate}),
|
||||||
|
std::move(insertion_submaps)});
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapping::PoseEstimate& LocalTrajectoryBuilder::pose_estimate() const {
|
const mapping::PoseEstimate& LocalTrajectoryBuilder::pose_estimate() const {
|
||||||
|
|
|
@ -42,7 +42,6 @@ class LocalTrajectoryBuilder {
|
||||||
public:
|
public:
|
||||||
struct InsertionResult {
|
struct InsertionResult {
|
||||||
std::shared_ptr<const mapping::TrajectoryNode::Data> constant_data;
|
std::shared_ptr<const mapping::TrajectoryNode::Data> constant_data;
|
||||||
transform::Rigid3d pose_observation;
|
|
||||||
std::vector<std::shared_ptr<const Submap>> insertion_submaps;
|
std::vector<std::shared_ptr<const Submap>> insertion_submaps;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -96,10 +96,10 @@ std::vector<mapping::SubmapId> SparsePoseGraph::GrowSubmapTransformsAsNeeded(
|
||||||
|
|
||||||
void SparsePoseGraph::AddScan(
|
void SparsePoseGraph::AddScan(
|
||||||
std::shared_ptr<const mapping::TrajectoryNode::Data> constant_data,
|
std::shared_ptr<const mapping::TrajectoryNode::Data> constant_data,
|
||||||
const transform::Rigid3d& pose, const int trajectory_id,
|
const int trajectory_id,
|
||||||
const std::vector<std::shared_ptr<const Submap>>& insertion_submaps) {
|
const std::vector<std::shared_ptr<const Submap>>& insertion_submaps) {
|
||||||
const transform::Rigid3d optimized_pose(
|
const transform::Rigid3d optimized_pose(
|
||||||
GetLocalToGlobalTransform(trajectory_id) * pose);
|
GetLocalToGlobalTransform(trajectory_id) * constant_data->initial_pose);
|
||||||
|
|
||||||
common::MutexLocker locker(&mutex_);
|
common::MutexLocker locker(&mutex_);
|
||||||
trajectory_nodes_.Append(
|
trajectory_nodes_.Append(
|
||||||
|
@ -132,11 +132,8 @@ void SparsePoseGraph::AddScan(
|
||||||
// execute the lambda.
|
// execute the lambda.
|
||||||
const bool newly_finished_submap = insertion_submaps.front()->finished();
|
const bool newly_finished_submap = insertion_submaps.front()->finished();
|
||||||
AddWorkItem([=]() REQUIRES(mutex_) {
|
AddWorkItem([=]() REQUIRES(mutex_) {
|
||||||
ComputeConstraintsForScan(
|
ComputeConstraintsForScan(trajectory_id, insertion_submaps,
|
||||||
trajectory_id, insertion_submaps, newly_finished_submap,
|
newly_finished_submap);
|
||||||
transform::Project2D(pose *
|
|
||||||
transform::Rigid3d::Rotation(
|
|
||||||
constant_data->gravity_alignment.inverse())));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,19 +224,11 @@ void SparsePoseGraph::ComputeConstraintsForOldScans(
|
||||||
void SparsePoseGraph::ComputeConstraintsForScan(
|
void SparsePoseGraph::ComputeConstraintsForScan(
|
||||||
const int trajectory_id,
|
const int trajectory_id,
|
||||||
std::vector<std::shared_ptr<const Submap>> insertion_submaps,
|
std::vector<std::shared_ptr<const Submap>> insertion_submaps,
|
||||||
const bool newly_finished_submap, const transform::Rigid2d& pose) {
|
const bool newly_finished_submap) {
|
||||||
const std::vector<mapping::SubmapId> submap_ids =
|
const std::vector<mapping::SubmapId> submap_ids =
|
||||||
GrowSubmapTransformsAsNeeded(trajectory_id, insertion_submaps);
|
GrowSubmapTransformsAsNeeded(trajectory_id, insertion_submaps);
|
||||||
CHECK_EQ(submap_ids.size(), insertion_submaps.size());
|
CHECK_EQ(submap_ids.size(), insertion_submaps.size());
|
||||||
const mapping::SubmapId matching_id = submap_ids.front();
|
const mapping::SubmapId matching_id = submap_ids.front();
|
||||||
const transform::Rigid2d optimized_pose =
|
|
||||||
optimization_problem_.submap_data()
|
|
||||||
.at(matching_id.trajectory_id)
|
|
||||||
.at(matching_id.submap_index)
|
|
||||||
.pose *
|
|
||||||
sparse_pose_graph::ComputeSubmapPose(*insertion_submaps.front())
|
|
||||||
.inverse() *
|
|
||||||
pose;
|
|
||||||
const mapping::NodeId node_id{
|
const mapping::NodeId node_id{
|
||||||
matching_id.trajectory_id,
|
matching_id.trajectory_id,
|
||||||
static_cast<size_t>(matching_id.trajectory_id) <
|
static_cast<size_t>(matching_id.trajectory_id) <
|
||||||
|
@ -252,10 +241,21 @@ void SparsePoseGraph::ComputeConstraintsForScan(
|
||||||
->first +
|
->first +
|
||||||
1)
|
1)
|
||||||
: 0};
|
: 0};
|
||||||
const auto& node_data = trajectory_nodes_.at(node_id).constant_data;
|
const auto& constant_data = trajectory_nodes_.at(node_id).constant_data;
|
||||||
optimization_problem_.AddTrajectoryNode(matching_id.trajectory_id,
|
const transform::Rigid2d pose = transform::Project2D(
|
||||||
node_data->time, pose, optimized_pose,
|
constant_data->initial_pose *
|
||||||
node_data->gravity_alignment);
|
transform::Rigid3d::Rotation(constant_data->gravity_alignment.inverse()));
|
||||||
|
const transform::Rigid2d optimized_pose =
|
||||||
|
optimization_problem_.submap_data()
|
||||||
|
.at(matching_id.trajectory_id)
|
||||||
|
.at(matching_id.submap_index)
|
||||||
|
.pose *
|
||||||
|
sparse_pose_graph::ComputeSubmapPose(*insertion_submaps.front())
|
||||||
|
.inverse() *
|
||||||
|
pose;
|
||||||
|
optimization_problem_.AddTrajectoryNode(
|
||||||
|
matching_id.trajectory_id, constant_data->time, pose, optimized_pose,
|
||||||
|
constant_data->gravity_alignment);
|
||||||
for (size_t i = 0; i < insertion_submaps.size(); ++i) {
|
for (size_t i = 0; i < insertion_submaps.size(); ++i) {
|
||||||
const mapping::SubmapId submap_id = submap_ids[i];
|
const mapping::SubmapId submap_id = submap_ids[i];
|
||||||
// Even if this was the last scan added to 'submap_id', the submap will only
|
// Even if this was the last scan added to 'submap_id', the submap will only
|
||||||
|
|
|
@ -71,7 +71,7 @@ class SparsePoseGraph : public mapping::SparsePoseGraph {
|
||||||
// 'true', this submap was inserted into for the last time.
|
// 'true', this submap was inserted into for the last time.
|
||||||
void AddScan(
|
void AddScan(
|
||||||
std::shared_ptr<const mapping::TrajectoryNode::Data> constant_data,
|
std::shared_ptr<const mapping::TrajectoryNode::Data> constant_data,
|
||||||
const transform::Rigid3d& pose, int trajectory_id,
|
int trajectory_id,
|
||||||
const std::vector<std::shared_ptr<const Submap>>& insertion_submaps)
|
const std::vector<std::shared_ptr<const Submap>>& insertion_submaps)
|
||||||
EXCLUDES(mutex_);
|
EXCLUDES(mutex_);
|
||||||
|
|
||||||
|
@ -130,8 +130,7 @@ class SparsePoseGraph : public mapping::SparsePoseGraph {
|
||||||
void ComputeConstraintsForScan(
|
void ComputeConstraintsForScan(
|
||||||
int trajectory_id,
|
int trajectory_id,
|
||||||
std::vector<std::shared_ptr<const Submap>> insertion_submaps,
|
std::vector<std::shared_ptr<const Submap>> insertion_submaps,
|
||||||
bool newly_finished_submap, const transform::Rigid2d& pose)
|
bool newly_finished_submap) REQUIRES(mutex_);
|
||||||
REQUIRES(mutex_);
|
|
||||||
|
|
||||||
// Computes constraints for a scan and submap pair.
|
// Computes constraints for a scan and submap pair.
|
||||||
void ComputeConstraint(const mapping::NodeId& node_id,
|
void ComputeConstraint(const mapping::NodeId& node_id,
|
||||||
|
|
|
@ -164,8 +164,9 @@ class SparsePoseGraphTest : public ::testing::Test {
|
||||||
range_data.returns,
|
range_data.returns,
|
||||||
{},
|
{},
|
||||||
{},
|
{},
|
||||||
{}}),
|
{},
|
||||||
transform::Embed3D(pose_estimate), kTrajectoryId, insertion_submaps);
|
transform::Embed3D(pose_estimate)}),
|
||||||
|
kTrajectoryId, insertion_submaps);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MoveRelative(const transform::Rigid2d& movement) {
|
void MoveRelative(const transform::Rigid2d& movement) {
|
||||||
|
|
|
@ -221,8 +221,9 @@ LocalTrajectoryBuilder::InsertIntoSubmap(
|
||||||
{}, // 'filtered_point_cloud' is only used in 2D.
|
{}, // 'filtered_point_cloud' is only used in 2D.
|
||||||
high_resolution_point_cloud,
|
high_resolution_point_cloud,
|
||||||
low_resolution_point_cloud,
|
low_resolution_point_cloud,
|
||||||
rotational_scan_matcher_histogram}),
|
rotational_scan_matcher_histogram,
|
||||||
pose_observation, std::move(insertion_submaps)});
|
pose_observation}),
|
||||||
|
std::move(insertion_submaps)});
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace mapping_3d
|
} // namespace mapping_3d
|
||||||
|
|
|
@ -42,7 +42,6 @@ class LocalTrajectoryBuilder {
|
||||||
public:
|
public:
|
||||||
struct InsertionResult {
|
struct InsertionResult {
|
||||||
std::shared_ptr<const mapping::TrajectoryNode::Data> constant_data;
|
std::shared_ptr<const mapping::TrajectoryNode::Data> constant_data;
|
||||||
transform::Rigid3d pose_observation;
|
|
||||||
std::vector<std::shared_ptr<const Submap>> insertion_submaps;
|
std::vector<std::shared_ptr<const Submap>> insertion_submaps;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -94,10 +94,10 @@ std::vector<mapping::SubmapId> SparsePoseGraph::GrowSubmapTransformsAsNeeded(
|
||||||
|
|
||||||
void SparsePoseGraph::AddScan(
|
void SparsePoseGraph::AddScan(
|
||||||
std::shared_ptr<const mapping::TrajectoryNode::Data> constant_data,
|
std::shared_ptr<const mapping::TrajectoryNode::Data> constant_data,
|
||||||
const transform::Rigid3d& pose, const int trajectory_id,
|
const int trajectory_id,
|
||||||
const std::vector<std::shared_ptr<const Submap>>& insertion_submaps) {
|
const std::vector<std::shared_ptr<const Submap>>& insertion_submaps) {
|
||||||
const transform::Rigid3d optimized_pose(
|
const transform::Rigid3d optimized_pose(
|
||||||
GetLocalToGlobalTransform(trajectory_id) * pose);
|
GetLocalToGlobalTransform(trajectory_id) * constant_data->initial_pose);
|
||||||
|
|
||||||
common::MutexLocker locker(&mutex_);
|
common::MutexLocker locker(&mutex_);
|
||||||
trajectory_nodes_.Append(
|
trajectory_nodes_.Append(
|
||||||
|
@ -131,7 +131,7 @@ void SparsePoseGraph::AddScan(
|
||||||
const bool newly_finished_submap = insertion_submaps.front()->finished();
|
const bool newly_finished_submap = insertion_submaps.front()->finished();
|
||||||
AddWorkItem([=]() REQUIRES(mutex_) {
|
AddWorkItem([=]() REQUIRES(mutex_) {
|
||||||
ComputeConstraintsForScan(trajectory_id, insertion_submaps,
|
ComputeConstraintsForScan(trajectory_id, insertion_submaps,
|
||||||
newly_finished_submap, pose);
|
newly_finished_submap);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,17 +250,11 @@ void SparsePoseGraph::ComputeConstraintsForOldScans(
|
||||||
void SparsePoseGraph::ComputeConstraintsForScan(
|
void SparsePoseGraph::ComputeConstraintsForScan(
|
||||||
const int trajectory_id,
|
const int trajectory_id,
|
||||||
std::vector<std::shared_ptr<const Submap>> insertion_submaps,
|
std::vector<std::shared_ptr<const Submap>> insertion_submaps,
|
||||||
const bool newly_finished_submap, const transform::Rigid3d& pose) {
|
const bool newly_finished_submap) {
|
||||||
const std::vector<mapping::SubmapId> submap_ids =
|
const std::vector<mapping::SubmapId> submap_ids =
|
||||||
GrowSubmapTransformsAsNeeded(trajectory_id, insertion_submaps);
|
GrowSubmapTransformsAsNeeded(trajectory_id, insertion_submaps);
|
||||||
CHECK_EQ(submap_ids.size(), insertion_submaps.size());
|
CHECK_EQ(submap_ids.size(), insertion_submaps.size());
|
||||||
const mapping::SubmapId matching_id = submap_ids.front();
|
const mapping::SubmapId matching_id = submap_ids.front();
|
||||||
const transform::Rigid3d optimized_pose =
|
|
||||||
optimization_problem_.submap_data()
|
|
||||||
.at(matching_id.trajectory_id)
|
|
||||||
.at(matching_id.submap_index)
|
|
||||||
.pose *
|
|
||||||
insertion_submaps.front()->local_pose().inverse() * pose;
|
|
||||||
const mapping::NodeId node_id{
|
const mapping::NodeId node_id{
|
||||||
matching_id.trajectory_id,
|
matching_id.trajectory_id,
|
||||||
static_cast<size_t>(matching_id.trajectory_id) <
|
static_cast<size_t>(matching_id.trajectory_id) <
|
||||||
|
@ -273,9 +267,16 @@ void SparsePoseGraph::ComputeConstraintsForScan(
|
||||||
->first +
|
->first +
|
||||||
1)
|
1)
|
||||||
: 0};
|
: 0};
|
||||||
const auto& scan_data = trajectory_nodes_.at(node_id).constant_data;
|
const auto& constant_data = trajectory_nodes_.at(node_id).constant_data;
|
||||||
|
const transform::Rigid3d& pose = constant_data->initial_pose;
|
||||||
|
const transform::Rigid3d optimized_pose =
|
||||||
|
optimization_problem_.submap_data()
|
||||||
|
.at(matching_id.trajectory_id)
|
||||||
|
.at(matching_id.submap_index)
|
||||||
|
.pose *
|
||||||
|
insertion_submaps.front()->local_pose().inverse() * pose;
|
||||||
optimization_problem_.AddTrajectoryNode(
|
optimization_problem_.AddTrajectoryNode(
|
||||||
matching_id.trajectory_id, scan_data->time, pose, optimized_pose);
|
matching_id.trajectory_id, constant_data->time, pose, optimized_pose);
|
||||||
for (size_t i = 0; i < insertion_submaps.size(); ++i) {
|
for (size_t i = 0; i < insertion_submaps.size(); ++i) {
|
||||||
const mapping::SubmapId submap_id = submap_ids[i];
|
const mapping::SubmapId submap_id = submap_ids[i];
|
||||||
// Even if this was the last scan added to 'submap_id', the submap will only
|
// Even if this was the last scan added to 'submap_id', the submap will only
|
||||||
|
|
|
@ -72,7 +72,7 @@ class SparsePoseGraph : public mapping::SparsePoseGraph {
|
||||||
// 'true', this submap was inserted into for the last time.
|
// 'true', this submap was inserted into for the last time.
|
||||||
void AddScan(
|
void AddScan(
|
||||||
std::shared_ptr<const mapping::TrajectoryNode::Data> constant_data,
|
std::shared_ptr<const mapping::TrajectoryNode::Data> constant_data,
|
||||||
const transform::Rigid3d& pose, int trajectory_id,
|
int trajectory_id,
|
||||||
const std::vector<std::shared_ptr<const Submap>>& insertion_submaps)
|
const std::vector<std::shared_ptr<const Submap>>& insertion_submaps)
|
||||||
EXCLUDES(mutex_);
|
EXCLUDES(mutex_);
|
||||||
|
|
||||||
|
@ -131,8 +131,7 @@ class SparsePoseGraph : public mapping::SparsePoseGraph {
|
||||||
void ComputeConstraintsForScan(
|
void ComputeConstraintsForScan(
|
||||||
int trajectory_id,
|
int trajectory_id,
|
||||||
std::vector<std::shared_ptr<const Submap>> insertion_submaps,
|
std::vector<std::shared_ptr<const Submap>> insertion_submaps,
|
||||||
bool newly_finished_submap, const transform::Rigid3d& pose)
|
bool newly_finished_submap) REQUIRES(mutex_);
|
||||||
REQUIRES(mutex_);
|
|
||||||
|
|
||||||
// Computes constraints for a scan and submap pair.
|
// Computes constraints for a scan and submap pair.
|
||||||
void ComputeConstraint(const mapping::NodeId& node_id,
|
void ComputeConstraint(const mapping::NodeId& node_id,
|
||||||
|
|
Loading…
Reference in New Issue