这段代码实现了在李群(Lie Group)上进行样条插值的数学工具,基于 Lovegrove 等人在 2013 年的论文提出的算法。以下是代码的关键点总结:
1. 核心概念
- 样条函数
:利用 B 样条基函数(B-Spline Basis Functions)构建插值函数,适用于连续变换的平滑插值。
- 李群和李代数
:基于李群(如旋转、位移等连续变换的集合)和其对应的李代数,完成插值和微分操作。
2. 主要功能
(1)B 样条基函数实现
类 SplineBasisFunction:
提供了三次 B 样条的基函数
B(u)和其一阶、二阶导数Dt_B(u, delta_t)与Dt2_B(u, delta_t)的实现。使用矩阵
C定义 B 样条的系数矩阵。
(2)样条插值函数
类 BasisSplineFn:
在李群中通过控制点计算样条插值值(
parent_T_spline)、一阶导数(Dt_parent_T_spline)和二阶导数(Dt2_parent_T_spline)。核心方法:
A(...):利用基函数值计算控制点的李群变换。Dt_A(...) 和Dt2_A(...):分别计算插值的一阶和二阶导数。使用李群的指数映射
exp和对数映射log处理插值。
(3)样条段的分段计算
类 BasisSplineSegment:
处理样条插值的单个段,根据分段类型(
SegmentCase)计算插值值及导数。分段类型:
first:样条段起始。normal:普通样条段。last:样条段末尾。
使用控制点的相对位姿(通过李代数对数计算)定义控制矢量。
(4)完整样条实现
类 BasisSplineImpl:
提供多段样条的整体实现。
核心功能:
parent_T_spline:计算第i段样条的插值值。Dt_parent_T_spline 和Dt2_parent_T_spline:计算插值值的一阶和二阶导数。管理控制点并提供分段数量、时间步长等信息。
3. 应用场景
- 运动插值
:用于机器人学、计算机视觉等领域中平滑表示连续的位姿(如旋转和位移)。
- 时间微分计算
:支持计算位姿的速度和加速度。
- 关键帧控制
:利用离散控制点生成连续轨迹。
4. 总结
该代码结合了数学理论(B 样条)与几何表示(李群与李代数),提供了高效且精确的插值工具,适用于对连续变换(如旋转和平移)的光滑插值和导数计算。
/// @file// Basis spline implementation on Lie Group following:// S. Lovegrove, A. Patron-Perez, G. Sibley, BMVC 2013// http://www.bmva.org/bmvc/2013/Papers/paper0093/paper0093.pdf
#pragma once // 确保该文件只被编译一次
#include "types.hpp" // 包含头文件 types.hpp
namespace Sophus { // 定义命名空间 Sophus
template <class Scalar>class SplineBasisFunction { // 定义模板类 SplineBasisFunction public: static SOPHUS_FUNC Eigen::Matrix<Scalar, 3, 4> C() { // 定义静态成员函数 C 返回一个 Eigen 矩阵 Eigen::Matrix<Scalar, 3, 4> C; // 定义 3x4 大小的矩阵 Scalar const o(0); // 定义常量 o 值为 0
// clang-format off C << Scalar(5./6), Scalar(3./6), -Scalar(3./6), Scalar(1./6), // 填充矩阵 C 的值 Scalar(1./6), Scalar(3./6), Scalar(3./6), -Scalar(2./6), o, o, o, Scalar(1./6); // clang-format on return C; // 返回填充好的矩阵 C }
static SOPHUS_FUNC Vector3<Scalar> B(Scalar const& u) { // 定义静态成员函数 B 返回一个 3 维向量 // SOPHUS_ENSURE(u >= Scalar(0), "but %", u); // 确保 u >= 0 // SOPHUS_ENSURE(u < Scalar(1), "but %", u); // 确保 u < 1 Scalar u_square(u * u); // 计算 u 的平方 return C() * Vector4<Scalar>(Scalar(1), u, u_square, u * u_square); // 计算并返回向量 B }
static SOPHUS_FUNC Vector3<Scalar> Dt_B(Scalar const& u, Scalar const& delta_t) { // 定义静态成员函数 Dt_B 返回一个 3 维向量 // SOPHUS_ENSURE(u >= Scalar(0), "but %", u); // 确保 u >= 0 // SOPHUS_ENSURE(u < Scalar(1), "but %", u); // 确保 u < 1 return (Scalar(1) / delta_t) * C() * Vector4<Scalar>(Scalar(0), Scalar(1), Scalar(2) * u, Scalar(3) * u * u); // 计算并返回向量 Dt_B }
static SOPHUS_FUNC Vector3<Scalar> Dt2_B(Scalar const& u, Scalar const& delta_t) { // 定义静态成员函数 Dt2_B 返回一个 3 维向量 // SOPHUS_ENSURE(u >= Scalar(0), "but %", u); // 确保 u >= 0 // SOPHUS_ENSURE(u < Scalar(1), "but %", u); // 确保 u < 1 return (Scalar(1) / (delta_t * delta_t)) * C() * Vector4<Scalar>(Scalar(0), Scalar(0), Scalar(2), Scalar(6) * u); // 计算并返回向量 Dt2_B }};
template <class LieGroup_>class BasisSplineFn { // 定义模板类 BasisSplineFn public: using LieGroup = LieGroup_; // 定义类型别名 LieGroup using Scalar = typename LieGroup::Scalar; // 定义标量类型 using Transformation = typename LieGroup::Transformation; // 定义变换矩阵类型 using Tangent = typename LieGroup::Tangent; // 定义切向量类型
static LieGroup parent_T_spline( const LieGroup& parent_Ts_control_point, std::tuple<Tangent, Tangent, Tangent> const& control_tagent_vectors, double u) { // 定义静态成员函数 parent_T_spline auto AA = A(control_tagent_vectors, u); // 计算 A 矩阵 return parent_Ts_control_point * std::get<0>(AA) * std::get<1>(AA) * std::get<2>(AA); // 计算并返回 spline 变换矩阵 }
static Transformation Dt_parent_T_spline( const LieGroup& parent_Ts_control_point, std::tuple<Tangent, Tangent, Tangent> const& control_tagent_vectors, double u, double delta_t) { // 定义静态成员函数 Dt_parent_T_spline auto AA = A(control_tagent_vectors, u); // 计算 A 矩阵 auto Dt_AA = Dt_A(AA, control_tagent_vectors, u, delta_t); // 计算 Dt_A 矩阵 return parent_Ts_control_point.matrix() * ((std::get<0>(Dt_AA) * std::get<1>(AA).matrix() * std::get<2>(AA).matrix()) + (std::get<0>(AA).matrix() * std::get<1>(Dt_AA) * std::get<2>(AA).matrix()) + (std::get<0>(AA).matrix() * std::get<1>(AA).matrix() * std::get<2>(Dt_AA))); // 计算并返回 spline 的导数 }
static Transformation Dt2_parent_T_spline( const LieGroup& parent_Ts_control_point, std::tuple<Tangent, Tangent, Tangent> const& control_tagent_vectors, double u, double delta_t) { // 定义静态成员函数 Dt2_parent_T_spline using Scalar = typename LieGroup::Scalar; // 定义标量类型 auto AA = A(control_tagent_vectors, u); // 计算 A 矩阵 auto Dt_AA = Dt_A(AA, control_tagent_vectors, u, delta_t); // 计算 Dt_A 矩阵 auto Dt2_AA = Dt2_A(AA, Dt_AA, control_tagent_vectors, u, delta_t); // 计算 Dt2_A 矩阵
return parent_Ts_control_point.matrix() * ((std::get<0>(Dt2_AA) * std::get<1>(AA).matrix() * std::get<2>(AA).matrix()) + (std::get<0>(AA).matrix() * std::get<1>(Dt2_AA) * std::get<2>(AA).matrix()) + (std::get<0>(AA).matrix() * std::get<1>(AA).matrix() * std::get<2>(Dt2_AA)) + Scalar(2) * ((std::get<0>(Dt_AA) * std::get<1>(Dt_AA) * std::get<2>(AA).matrix()) + (std::get<0>(Dt_AA) * std::get<1>(AA).matrix() * std::get<2>(Dt_AA)) + (std::get<0>(AA).matrix() * std::get<1>(Dt_AA) * std::get<2>(Dt_AA)))); // 计算并返回 spline 的二阶导数 }
private: static std::tuple<LieGroup, LieGroup, LieGroup> A( std::tuple<Tangent, Tangent, Tangent> const& control_tagent_vectors, double u) { // 定义静态成员函数 A Eigen::Vector3d B = SplineBasisFunction<double>::B(u); // 计算基函数 B return std::make_tuple( LieGroup::exp(B[0] * std::get<0>(control_tagent_vectors)), LieGroup::exp(B[1] * std::get<1>(control_tagent_vectors)), LieGroup::exp(B[2] * std::get<2>(control_tagent_vectors))); // 计算并返回 A 矩阵 }
static std::tuple<Transformation, Transformation, Transformation> Dt_A( std::tuple<LieGroup, LieGroup, LieGroup> const& AA, const std::tuple<Tangent, Tangent, Tangent>& control_tagent_vectors, double u, double delta_t) { // 定义静态成员函数 Dt_A Eigen::Vector3d Dt_B = SplineBasisFunction<double>::Dt_B(u, delta_t); // 计算 Dt_B return std::make_tuple( Dt_B[0] * std::get<0>(AA).matrix() * LieGroup::hat(std::get<0>(control_tagent_vectors)), Dt_B[1] * std::get<1>(AA).matrix() * LieGroup::hat(std::get<1>(control_tagent_vectors)), Dt_B[2] * std::get<2>(AA).matrix() * LieGroup::hat(std::get<2>(control_tagent_vectors))); // 计算并返回 Dt_A 矩阵 }
static std::tuple<Transformation, Transformation, Transformation> Dt2_A( std::tuple<LieGroup, LieGroup, LieGroup> const& AA, std::tuple<Transformation, Transformation, Transformation> const& Dt_AA, std::tuple<Tangent, Tangent, Tangent> const& control_tagent_vectors, double u, double delta_t) { // 定义静态成员函数 Dt2_A Eigen::Vector3d Dt_B = SplineBasisFunction<double>::Dt_B(u, delta_t); // 计算 Dt_B Eigen::Vector3d Dt2_B = SplineBasisFunction<double>::Dt2_B(u, delta_t); // 计算 Dt2_B
return std::make_tuple( (Dt_B[0] * std::get<0>(Dt_AA).matrix() + Dt2_B[0] * std::get<0>(AA).matrix()) * LieGroup::hat(std::get<0>(control_tagent_vectors)), (Dt_B[1] * std::get<1>(Dt_AA).matrix() + Dt2_B[1] * std::get<1>(AA).matrix()) * LieGroup::hat(std::get<1>(control_tagent_vectors)), (Dt_B[2] * std::get<2>(Dt_AA).matrix() + Dt2_B[2] * std::get<2>(AA).matrix()) * LieGroup::hat(std::get<2>(control_tagent_vectors))); // 计算并返回 Dt2_A 矩阵 } enum class SegmentCase { first, normal, last }; // 枚举类型 SegmentCase 定义了三种状态:first(第一段),normal(正常段),last(最后一段)
template <class LieGroup>struct BasisSplineSegment { // 模板结构体 BasisSplineSegment public: using T = typename LieGroup::Scalar; // 定义标量类型别名 T using Transformation = typename LieGroup::Transformation; // 定义变换矩阵类型别名
BasisSplineSegment(SegmentCase segment_case, T const* const raw_ptr0, T const* const raw_ptr1, T const* const raw_ptr2, T const* const raw_ptr3) // 构造函数,接受段类型和四个指针参数 : segment_case_(segment_case), // 初始化段类型 raw_params0_(raw_ptr0), // 初始化指针参数 raw_params1_(raw_ptr1), raw_params2_(raw_ptr2), raw_params3_(raw_ptr3) {}
Eigen::Map<LieGroup const> const world_pose_foo_prev() const { // 返回第一个指针指向的 LieGroup 对象 return Eigen::Map<LieGroup const>(raw_params0_); } Eigen::Map<LieGroup const> const world_pose_foo_0() const { // 返回第二个指针指向的 LieGroup 对象 return Eigen::Map<LieGroup const>(raw_params1_); }
Eigen::Map<LieGroup const> const world_pose_foo_1() const { // 返回第三个指针指向的 LieGroup 对象 return Eigen::Map<LieGroup const>(raw_params2_); }
Eigen::Map<LieGroup const> const world_pose_foo_2() const { // 返回第四个指针指向的 LieGroup 对象 return Eigen::Map<LieGroup const>(raw_params3_); } LieGroup parent_T_spline(double u) { // 计算父节点的样条曲线 switch (segment_case_) { case SegmentCase::first: // 如果是第一段 return BasisSplineFn<LieGroup>::parent_T_spline( world_pose_foo_0(), std::make_tuple( (world_pose_foo_0().inverse() * world_pose_foo_0()).log(), (world_pose_foo_0().inverse() * world_pose_foo_1()).log(), (world_pose_foo_1().inverse() * world_pose_foo_2()).log()), u); case SegmentCase::normal: // 如果是正常段 return BasisSplineFn<LieGroup>::parent_T_spline( world_pose_foo_prev(), std::make_tuple( (world_pose_foo_prev().inverse() * world_pose_foo_0()).log(), (world_pose_foo_0().inverse() * world_pose_foo_1()).log(), (world_pose_foo_1().inverse() * world_pose_foo_2()).log()), u); case SegmentCase::last: // 如果是最后一段 return BasisSplineFn<LieGroup>::parent_T_spline( world_pose_foo_prev(), std::make_tuple( (world_pose_foo_prev().inverse() * world_pose_foo_0()).log(), (world_pose_foo_0().inverse() * world_pose_foo_1()).log(), (world_pose_foo_1().inverse() * world_pose_foo_1()).log()), u); } SOPHUS_ENSURE(false, "logic error"); // 逻辑错误处理 }
Transformation Dt_parent_T_spline(double u, double delta_t) { // 计算样条曲线的一阶导数 switch (segment_case_) { case SegmentCase::first: // 如果是第一段 return BasisSplineFn<LieGroup>::Dt_parent_T_spline( world_pose_foo_0(), std::make_tuple( (world_pose_foo_0().inverse() * world_pose_foo_0()).log(), (world_pose_foo_0().inverse() * world_pose_foo_1()).log(), (world_pose_foo_1().inverse() * world_pose_foo_2()).log()), u, delta_t); case SegmentCase::normal: // 如果是正常段 return BasisSplineFn<LieGroup>::Dt_parent_T_spline( world_pose_foo_prev(), std::make_tuple( (world_pose_foo_prev().inverse() * world_pose_foo_0()).log(), (world_pose_foo_0().inverse() * world_pose_foo_1()).log(), (world_pose_foo_1().inverse() * world_pose_foo_2()).log()), u, delta_t); case SegmentCase::last: // 如果是最后一段 return BasisSplineFn<LieGroup>::Dt_parent_T_spline( world_pose_foo_prev(), std::make_tuple( (world_pose_foo_prev().inverse() * world_pose_foo_0()).log(), (world_pose_foo_0().inverse() * world_pose_foo_1()).log(), (world_pose_foo_1().inverse() * world_pose_foo_1()).log()), u, delta_t); } SOPHUS_ENSURE(false, "logic error"); // 逻辑错误处理 }
Transformation Dt2_parent_T_spline(double u, double delta_t) { // 计算样条曲线的二阶导数 switch (segment_case_) { case SegmentCase::first: // 如果是第一段 return BasisSplineFn<LieGroup>::Dt2_parent_T_spline( world_pose_foo_0(), std::make_tuple( (world_pose_foo_0().inverse() * world_pose_foo_0()).log(), (world_pose_foo_0().inverse() * world_pose_foo_1()).log(), (world_pose_foo_1().inverse() * world_pose_foo_2()).log()), u, delta_t); case SegmentCase::normal: // 如果是正常段 return BasisSplineFn<LieGroup>::Dt2_parent_T_spline( world_pose_foo_prev(), std::make_tuple( (world_pose_foo_prev().inverse() * world_pose_foo_0()).log(), (world_pose_foo_0().inverse() * world_pose_foo_1()).log(), (world_pose_foo_1().inverse() * world_pose_foo_2()).log()), u, delta_t); case SegmentCase::last: // 如果是最后一段 return BasisSplineFn<LieGroup>::Dt2_parent_T_spline( world_pose_foo_prev(), std::make_tuple( (world_pose_foo_prev().inverse() * world_pose_foo_0()).log(), (world_pose_foo_0().inverse() * world_pose_foo_1()).log(), (world_pose_foo_1().inverse() * world_pose_foo_1()).log()), u, delta_t); } SOPHUS_ENSURE(false, "logic error"); // 逻辑错误处理 }
private: SegmentCase segment_case_; // 段的类型 T const* raw_params0_; // 原始参数指针 0 T const* raw_params1_; // 原始参数指针 1 T const* raw_params2_; // 原始参数指针 2 T const* raw_params3_; // 原始参数指针 3};
template <class LieGroup_>class BasisSplineImpl { // 定义模板类 BasisSplineImpl public: using LieGroup = LieGroup_; // 定义 LieGroup 类型别名 using Scalar = typename LieGroup::Scalar; // 定义标量类型别名 using Transformation = typename LieGroup::Transformation; // 定义变换矩阵类型别名 using Tangent = typename LieGroup::Tangent; // 定义切向量类型别名
BasisSplineImpl(const std::vector<LieGroup>& parent_Ts_control_point, double delta_t) // 构造函数,接受控制点和时间间隔 : parent_Ts_control_point_(parent_Ts_control_point), delta_t_(delta_t) { SOPHUS_ENSURE(parent_Ts_control_point_.size() >= 2u, ", but {}", parent_Ts_control_point_.size()); // 确保控制点数量不少于2个 }
LieGroup parent_T_spline(int i, double u) const { // 计算样条曲线 SOPHUS_ENSURE(i >= 0, "i = {}", i); // 确保 i 大于等于0 SOPHUS_ENSURE(i < this->getNumSegments(), "i = {}; this->getNumSegments() = {}; " "parent_Ts_control_point_.size() = {}", i, this->getNumSegments(), parent_Ts_control_point_.size()); // 确保 i 小于段数
SegmentCase segment_case = i == 0 ? SegmentCase::first : (i == this->getNumSegments() - 1 ? SegmentCase::last : SegmentCase::normal); // 确定段的类型
int idx_prev = std::max(0, i - 1); // 确定前一个索引 int idx_0 = i; // 当前索引 int idx_1 = i + 1; // 下一个索引 int idx_2 = std::min(i + 2, int(this->parent_Ts_control_point_.size()) - 1); // 再下一个索引
return BasisSplineSegment<LieGroup>( segment_case, parent_Ts_control_point_[idx_prev].data(), parent_Ts_control_point_[idx_0].data(), parent_Ts_control_point_[idx_1].data(), parent_Ts_control_point_[idx_2].data()) .parent_T_spline(u); // 返回样条曲线 }
Transformation Dt_parent_T_spline(int i, double u) const { // 计算样条曲线的一阶导数 SOPHUS_ENSURE(i >= 0, "i = {}", i); // 确保 i 大于等于0 SOPHUS_ENSURE(i < this->getNumSegments(), "i = {}; this->getNumSegments() = {}; " "parent_Ts_control_point_.size() = {}", i, this->getNumSegments(), parent_Ts_control_point_.size()); // 确保 i 小于段数
SegmentCase segment_case = i == 0 ? SegmentCase::first : (i == this->getNumSegments() - 1 ? SegmentCase::last : SegmentCase::normal); // 确定段的类型
int idx_prev = std::max(0, i - 1); // 确定前一个索引 int idx_0 = i; // 当前索引 int idx_1 = i + 1; // 下一个索引 int idx_2 = std::min(i + 2, int(this->parent_Ts_control_point_.size()) - 1); // 再下一个索引
return BasisSplineSegment<LieGroup>( segment_case, parent_Ts_control_point_[idx_prev].data(), parent_Ts_control_point_[idx_0].data(), parent_Ts_control_point_[idx_1].data(), parent_Ts_control_point_[idx_2].data()) .Dt_parent_T_spline(u, delta_t_); // 返回样条曲线的一阶导数 }
Transformation Dt2_parent_T_spline(int i, double u) const { // 计算样条曲线的二阶导数 SOPHUS_ENSURE(i >= 0, "i = {}", i); // 确保 i 大于等于0 SOPHUS_ENSURE(i < this->getNumSegments(), "i = {}; this->getNumSegments() = {}; " "parent_Ts_control_point_.size() = {}", i, this->getNumSegments(), parent_Ts_control_point_.size()); // 确保 i 小于段数
SegmentCase segment_case = i == 0 ? SegmentCase::first : (i == this->getNumSegments() - 1 ? SegmentCase::last : SegmentCase::normal); // 确定段的类型
int idx_prev = std::max(0, i - 1); // 确定前一个索引 int idx_0 = i; // 当前索引 int idx_1 = i + 1; // 下一个索引 int idx_2 = std::min(i + 2, int(this->parent_Ts_control_point_.size()) - 1); // 再下一个索引
return BasisSplineSegment<LieGroup>( segment_case, parent_Ts_control_point_[idx_prev].data(), parent_Ts_control_point_[idx_0].data(), parent_Ts_control_point_[idx_1].data(), parent_Ts_control_point_[idx_2].data()) .Dt2_parent_T_spline(u, delta_t_); // 返回样条曲线的二阶导数 }
const std::vector<LieGroup>& parent_Ts_control_point() const { // 获取控制点的常量引用 return parent_Ts_control_point_; }
std::vector<LieGroup>& parent_Ts_control_point() { // 获取控制点的引用 return parent_Ts_control_point_; }
int getNumSegments() const { // 获取段数 return int(parent_Ts_control_point_.size()) - 1; }
double delta_t() const { return delta_t_; } // 获取时间间隔
private: std::vector<LieGroup> parent_Ts_control_point_; // 控制点向量 double delta_t_; // 时间间隔};
struct IndexAndU { // 定义结构体 IndexAndU int i; // 段索引 double u; // 参数 u};
template <class LieGroup_>class BasisSpline { // 定义模板类 BasisSpline public: using LieGroup = LieGroup_; // 定义 LieGroup 类型别名 using Scalar = typename LieGroup::Scalar; // 定义标量类型别名 using Transformation = typename LieGroup::Transformation; // 定义变换矩阵类型别名 using Tangent = typename LieGroup::Tangent; // 定义切向量类型别名
BasisSpline(std::vector<LieGroup> parent_Ts_control_point, double t0, double delta_t) // 构造函数,接受控制点、起始时间 t0 和时间间隔 delta_t : impl_(std::move(parent_Ts_control_point), delta_t), t0_(t0) {}
LieGroup parent_T_spline(double t) const { // 计算样条曲线 IndexAndU index_and_u = this->index_and_u(t); // 获取索引和参数 u
return impl_.parent_T_spline(index_and_u.i, index_and_u.u); // 返回样条曲线 }
Transformation Dt_parent_T_spline(double t) const { // 计算样条曲线的一阶导数 IndexAndU index_and_u = this->index_and_u(t); // 获取索引和参数 u return impl_.Dt_parent_T_spline(index_and_u.i, index_and_u.u); // 返回样条曲线的一阶导数 }
Transformation Dt2_parent_T_spline(double t) const { // 计算样条曲线的二阶导数 IndexAndU index_and_u = this->index_and_u(t); // 获取索引和参数 u return impl_.Dt2_parent_T_spline(index_and_u.i, index_and_u.u); // 返回样条曲线的二阶导数 }
double t0() const { return t0_; } // 返回起始时间 t0
double tmax() const { return t0_ + impl_.delta_t() * getNumSegments(); } // 返回最大时间 tmax
const std::vector<LieGroup>& parent_Ts_control_point() const { // 获取控制点的常量引用 return impl_.parent_Ts_control_point(); }
std::vector<LieGroup>& parent_Ts_control_point() { // 获取控制点的引用 return impl_.parent_Ts_control_point(); }
int getNumSegments() const { return impl_.getNumSegments(); } // 获取段数
double s(double t) const { return (t - t0_) / impl_.delta_t(); } // 计算归一化时间 s
double delta_t() const { return impl_.delta_t(); } // 返回时间间隔
IndexAndU index_and_u(double t) const { // 计算索引和参数 u SOPHUS_ENSURE(t >= t0_, "{} vs. {}", t, t0_); // 确保 t 大于等于 t0 SOPHUS_ENSURE(t <= this->tmax(), "{} vs. {}", t, this->tmax()); // 确保 t 小于等于 tmax
double s = this->s(t); // 计算归一化时间 s double i; IndexAndU index_and_u; index_and_u.u = std::modf(s, &i); // 分离整数部分和小数部分 index_and_u.i = int(i); if (index_and_u.u > Sophus::Constants<double>::epsilon()) { // 判断小数部分是否大于精度阈值 return index_and_u; }
// u ~=~ 0.0 if (t < 0.5 * this->tmax()) { // 样条曲线的前半段,保持为 (i, 0.0) return index_and_u; } // 样条曲线的后半段,使用 (i-1, 1.0) 来表示 t == tmax(而不是 t < tmax) index_and_u.u += 1.0; --index_and_u.i;
return index_and_u; }
private: BasisSplineImpl<LieGroup> impl_; // 样条曲线的实现
double t0_; // 起始时间};
} // namespace Sophus
1859





