Program Listing for File fluent_rviz.hpp

Return to documentation for file (include/fluent_rviz/fluent_rviz.hpp)

// Copyright (c) 2022 ForteFibre
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef FLUENT_RVIZ__FLUENT_RVIZ_HPP_
#define FLUENT_RVIZ__FLUENT_RVIZ_HPP_

#include <array>
#include <geometry_msgs/msg/point.hpp>
#include <geometry_msgs/msg/quaternion.hpp>
#include <geometry_msgs/msg/vector3.hpp>
#include <numeric>
#include <rclcpp/rclcpp.hpp>
#include <string>
#include <type_traits>
#include <vector>
#include <visualization_msgs/msg/marker.hpp>

namespace flrv
{

namespace traits
{

template <class From, class To, class Enabler = void>
struct converter;

template <class From, class To>
struct converter<From, To, std::enable_if_t<std::is_convertible_v<From, To>>>
{
  static constexpr To convert(const From & value) { return value; }
};

}  // namespace traits

namespace util
{

namespace detail
{
template <class Derived, class Base, template <class, class> class... Features>
struct chain_impl
{
  using type = Base;
};

template <
  class Derived, class Base, template <class, class> class Feature,
  template <class, class> class... Features>
struct chain_impl<Derived, Base, Feature, Features...>
: chain_impl<Derived, Feature<Derived, Base>, Features...>
{
};
}  // namespace detail

template <class Derived, class Base, template <class, class> class... Features>
using chain = typename detail::chain_impl<Derived, Base, Features...>::type;

namespace detail
{
template <class AlwaysVoid, template <class...> class Op, class... Args>
constexpr inline bool is_detected_impl_v = false;

template <template <class...> class Op, class... Args>
constexpr inline bool is_detected_impl_v<std::void_t<Op<Args...>>, Op, Args...> = true;
}  // namespace detail

template <template <class...> class Op, class... Args>
constexpr inline bool is_detected_v = detail::is_detected_impl_v<void, Op, Args...>;

template <class From, class To>
using convert_type = decltype(traits::converter<From, To>::convert(std::declval<From>()));

template <class From, class To>
constexpr inline bool is_convertible_v = is_detected_v<convert_type, From, To>;

template <class To, class From>
constexpr auto convert(const From & from)
  -> std::enable_if_t<is_convertible_v<From, To>, convert_type<From, To>>
{
  return traits::converter<From, To>::convert(from);
}

template <class T>
using size_type = decltype(std::declval<T>().size());

template <class T>
constexpr inline bool has_size_v = is_detected_v<size_type, T>;

}  // namespace util

namespace param
{

namespace detail
{
template <class T, class Enabler = void>
struct access
{
  static constexpr size_t size = 0;
};
}  // namespace detail

template <class T, size_t I>
using elem_type = std::remove_const_t<
  std::remove_reference_t<decltype(detail::access<T>::template get<I>(std::declval<T &>()))>>;

template <class T>
constexpr inline bool is_accessible_v = detail::access<T>::size > 0;

template <class T>
constexpr inline bool is_vec3_compat_v = detail::access<T>::size == 3;

template <class T>
constexpr inline bool is_quat_compat_v = detail::access<T>::size == 4;

template <class T>
constexpr inline bool is_rgba_compat_v = detail::access<T>::size == 4;

template <class S, class T>
constexpr inline bool is_same_size_v =
  is_accessible_v<S> && is_accessible_v<T> && detail::access<S>::size == detail::access<T>::size;

template <size_t I, class T>
auto get(const T & t) -> std::enable_if_t<is_accessible_v<T>, elem_type<T, I>>
{
  return detail::access<T>::template get<I>(t);
}

template <size_t I, class T>
auto set(T & t, const elem_type<T, I> & value) -> std::enable_if_t<is_accessible_v<T>>
{
  return detail::access<T>::template set<I>(t, value);
}

namespace detail
{
template <class T, class Enabler = void>
struct construct;

template <class T, class Seq>
struct construct_accessible;

template <class T, size_t... Is>
struct construct_accessible<T, std::index_sequence<Is...>>
{
  static T from(const elem_type<T, Is> &... args)
  {
    T ret;
    (set<Is>(ret, args), ...);
    return ret;
  }

  template <class From>
  static auto from(const From & arg) -> std::enable_if_t<is_same_size_v<T, From>, T>
  {
    return from(get<Is>(arg)...);
  }
};

template <class T>
struct construct<T, std::enable_if_t<is_accessible_v<T> && std::is_default_constructible_v<T>>>
: construct_accessible<T, std::make_index_sequence<detail::access<T>::size>>
{
};
}  // namespace detail

struct Vector3 : geometry_msgs::msg::Vector3
{
  using geometry_msgs::msg::Vector3::Vector3;

  Vector3(const double x, const double y, const double z) noexcept
  {
    this->x = x, this->y = y, this->z = z;
  }

  template <class Return = Vector3>
  static auto UnitX() noexcept -> std::enable_if_t<is_vec3_compat_v<Return>, Return>
  {
    return detail::construct<Return>::from(1, 0, 0);
  }

  template <class Return = Vector3>
  static auto UnitY() noexcept -> std::enable_if_t<is_vec3_compat_v<Return>, Return>
  {
    return detail::construct<Return>::from(0, 1, 0);
  }

  template <class Return = Vector3>
  static auto UnitZ() noexcept -> std::enable_if_t<is_vec3_compat_v<Return>, Return>
  {
    return detail::construct<Return>::from(0, 0, 1);
  }

  template <class T>
  operator T() const
  {
    return util::convert<T, geometry_msgs::msg::Vector3>(*this);
  }
};

namespace detail
{
template <class T>
struct access<T, std::enable_if_t<std::is_base_of_v<geometry_msgs::msg::Vector3, T>>>
{
  static constexpr size_t size = 3;

  template <size_t I>
  static double get(const geometry_msgs::msg::Vector3 & t) noexcept
  {
    if constexpr (I == 0) return t.x;
    if constexpr (I == 1) return t.y;
    if constexpr (I == 2) return t.z;
  }
  template <size_t I>
  static void set(geometry_msgs::msg::Vector3 & t, double value) noexcept
  {
    if constexpr (I == 0) t.x = value;
    if constexpr (I == 1) t.y = value;
    if constexpr (I == 2) t.z = value;
  }
};
}  // namespace detail

struct Point : geometry_msgs::msg::Point
{
  using geometry_msgs::msg::Point::Point;

  Point(const double x, const double y, const double z) noexcept
  {
    this->x = x, this->y = y, this->z = z;
  }

  template <class T>
  operator T() const
  {
    return util::convert<T, geometry_msgs::msg::Point>(*this);
  }
};

namespace detail
{
template <class T>
struct access<T, std::enable_if_t<std::is_base_of_v<geometry_msgs::msg::Point, T>>>
{
  static constexpr size_t size = 3;

  template <size_t I>
  static double get(const geometry_msgs::msg::Point & t) noexcept
  {
    if constexpr (I == 0) return t.x;
    if constexpr (I == 1) return t.y;
    if constexpr (I == 2) return t.z;
  }
  template <size_t I>
  static void set(geometry_msgs::msg::Point & t, double value) noexcept
  {
    if constexpr (I == 0) t.x = value;
    if constexpr (I == 1) t.y = value;
    if constexpr (I == 2) t.z = value;
  }
};
}  // namespace detail

namespace detail
{
template <class S, class T, class F, size_t... Is>
auto apply(S & lhs, const T & rhs, const F & f, std::index_sequence<Is...>)
  -> std::enable_if_t<is_same_size_v<S, T>, S &>
{
  return (set<Is>(lhs, f(get<Is>(lhs), get<Is>(rhs))), ..., lhs);
}

template <class S, class T, class F, size_t... Is>
auto apply(S & lhs, T rhs, const F & f, std::index_sequence<Is...>)
  -> std::enable_if_t<is_accessible_v<S> && std::is_scalar_v<T>, S &>
{
  return (set<Is>(lhs, f(get<Is>(lhs), rhs)), ..., lhs);
}
}  // namespace detail

template <class S, class T, class F>
auto apply(S & lhs, const T & rhs, const F & f) noexcept
  -> std::enable_if_t<is_same_size_v<S, T>, S &>
{
  return detail::apply(lhs, rhs, f, std::make_index_sequence<detail::access<S>::size>());
}

template <class S, class T, class F>
auto apply(S & lhs, T rhs, const F & f) noexcept
  -> std::enable_if_t<is_accessible_v<S> && std::is_scalar_v<T>, S &>
{
  return detail::apply(lhs, rhs, f, std::make_index_sequence<detail::access<S>::size>());
}

template <class S, class T>
auto operator+=(S & lhs, const T & rhs) noexcept -> std::enable_if_t<is_same_size_v<S, T>, S &>
{
  return apply(lhs, rhs, std::plus<>());
}

template <class S, class T>
auto operator-=(S & lhs, const T & rhs) noexcept -> std::enable_if_t<is_same_size_v<S, T>, S &>
{
  return apply(lhs, rhs, std::minus<>());
}

template <class S, class T>
auto operator*=(S & lhs, T rhs) noexcept
  -> std::enable_if_t<is_accessible_v<S> && std::is_scalar_v<T>, S &>
{
  return apply(lhs, rhs, std::multiplies<>());
}

template <class S, class T>
auto operator/=(S & lhs, T rhs) noexcept
  -> std::enable_if_t<is_accessible_v<S> && std::is_scalar_v<T>, S &>
{
  return apply(lhs, rhs, std::divides<>());
}

template <class S, class T>
auto operator+(S lhs, const T & rhs) noexcept -> std::enable_if_t<is_same_size_v<S, T>, S>
{
  return lhs += rhs;
}

template <class S, class T>
auto operator-(S lhs, const T & rhs) noexcept -> std::enable_if_t<is_same_size_v<S, T>, S>
{
  return lhs -= rhs;
}

template <class S, class T>
auto operator*(S lhs, T rhs) noexcept
  -> std::enable_if_t<is_accessible_v<S> && std::is_scalar_v<T>, S>
{
  return lhs *= rhs;
}

template <class S, class T>
auto operator*(S lhs, T rhs) noexcept
  -> std::enable_if_t<std::is_scalar_v<S> && is_accessible_v<T>, T>
{
  return rhs *= lhs;
}

template <class S, class T>
auto operator/(S lhs, T rhs) noexcept
  -> std::enable_if_t<is_accessible_v<S> && std::is_scalar_v<T>, S>
{
  return lhs /= rhs;
}

template <class T>
auto operator+(const T & s) noexcept -> std::enable_if_t<is_accessible_v<T>, T>
{
  return s;
}

template <class T>
auto operator-(const T & s) noexcept -> std::enable_if_t<is_accessible_v<T>, T>
{
  return -1 * s;
}

namespace detail
{
template <class S, class T, size_t... Is>
auto dot(const S & lhs, const T & rhs, std::index_sequence<Is...>) noexcept
  -> std::enable_if_t<is_same_size_v<S, T>, double>
{
  return ((get<Is>(lhs) * get<Is>(rhs)) + ...);
}
}  // namespace detail

template <class S, class T>
auto dot(const S & lhs, const T & rhs) noexcept -> std::enable_if_t<is_same_size_v<S, T>, double>
{
  return detail::dot(lhs, rhs, std::make_index_sequence<detail::access<S>::size>());
}

template <class T>
auto squared_norm(const T & s) noexcept -> std::enable_if_t<is_accessible_v<T>, double>
{
  return dot(s, s);
}

template <class T>
auto norm(const T & s) noexcept -> std::enable_if_t<is_accessible_v<T>, double>
{
  return std::sqrt(squared_norm(s));
}

template <class S, class T>
auto cross(const S & lhs, const T & rhs) noexcept
  -> std::enable_if_t<is_vec3_compat_v<S> && is_vec3_compat_v<T>, S>
{
  return detail::construct<S>::from(
    get<1>(lhs) * get<2>(rhs) - get<2>(lhs) * get<1>(rhs),
    get<2>(lhs) * get<0>(rhs) - get<0>(lhs) * get<2>(rhs),
    get<0>(lhs) * get<1>(rhs) - get<1>(lhs) * get<0>(rhs));
}

namespace detail
{
template <class T, size_t... Is>
auto print_impl(std::ostream & os, const T & s, std::index_sequence<Is...>) noexcept
  -> std::enable_if_t<is_accessible_v<T>, std::ostream &>
{
  return ((os << (Is != 0 ? ", " : "") << get<Is>(s)), ...);
}
}  // namespace detail

template <class T>
auto operator<<(std::ostream & os, const T & s) noexcept
  -> std::enable_if_t<is_accessible_v<T>, std::ostream &>
{
  return detail::print_impl(os, s, std::make_index_sequence<detail::access<T>::size>());
}

struct Quaternion : geometry_msgs::msg::Quaternion
{
  using geometry_msgs::msg::Quaternion::Quaternion;

  Quaternion(const double w, const double x, const double y, const double z) noexcept
  {
    this->w = w, this->x = x, this->y = y, this->z = z;
  }

  template <class Return = Quaternion, class T>
  static auto ScalarVector(const double scalar, const T & vector) noexcept
    -> std::enable_if_t<is_vec3_compat_v<T> && is_quat_compat_v<Return>, Return>
  {
    return detail::construct<Return>::from(scalar, get<0>(vector), get<1>(vector), get<2>(vector));
  }

  template <class Return = Quaternion, class T = Vector3>
  static auto AngleAxis(const double angle, const T & axis = Vector3::UnitZ<T>()) noexcept
    -> std::enable_if_t<is_vec3_compat_v<T> && is_quat_compat_v<Return>, Return>
  {
    return ScalarVector<Return>(std::cos(angle / 2.0), axis * std::sin(angle / 2.0));
  }

  template <class T>
  operator T() const
  {
    return util::convert<T, geometry_msgs::msg::Quaternion>(*this);
  }
};

namespace detail
{
template <class T>
struct access<T, std::enable_if_t<std::is_base_of_v<geometry_msgs::msg::Quaternion, T>>>
{
  static constexpr size_t size = 4;

  template <size_t I>
  static double get(const geometry_msgs::msg::Quaternion & t) noexcept
  {
    if constexpr (I == 0) return t.w;
    if constexpr (I == 1) return t.x;
    if constexpr (I == 2) return t.y;
    if constexpr (I == 3) return t.z;
  }
  template <size_t I>
  static void set(geometry_msgs::msg::Quaternion & t, double value) noexcept
  {
    if constexpr (I == 0) t.w = value;
    if constexpr (I == 1) t.x = value;
    if constexpr (I == 2) t.y = value;
    if constexpr (I == 3) t.z = value;
  }
};
}  // namespace detail

template <class T>
auto scalar(const T & t) noexcept -> std::enable_if_t<is_quat_compat_v<T>, elem_type<T, 0>>
{
  return get<0>(t);
}

template <class Return = Vector3, class T>
auto vector(const T & t) noexcept
  -> std::enable_if_t<is_quat_compat_v<T> && is_vec3_compat_v<Return>, Return>
{
  return detail::construct<Return>::from(get<1>(t), get<2>(t), get<3>(t));
}

template <class T>
auto conjugate(const T & t) noexcept -> std::enable_if_t<is_quat_compat_v<T>, T>
{
  return Quaternion::ScalarVector<T>(scalar(t), -vector(t));
}

template <class T>
auto inverse(const T & t) noexcept -> std::enable_if_t<is_quat_compat_v<T>, T>
{
  return conjugate(t) / squared_norm(t);
}

template <class S, class T>
auto operator*(const S & lhs, const T & rhs) noexcept
  -> std::enable_if_t<is_quat_compat_v<S> && is_quat_compat_v<T>, S>
{
  return Quaternion::ScalarVector<T>(
    scalar(lhs) * scalar(rhs) - dot(vector(lhs), vector(rhs)),
    scalar(lhs) * vector(rhs) + scalar(rhs) * vector(lhs) + cross(vector(lhs), vector(rhs)));
}

template <class S, class T>
auto operator*(const S & lhs, const T & rhs) noexcept
  -> std::enable_if_t<is_quat_compat_v<S> && is_vec3_compat_v<T>, T>
{
  return vector<T>(lhs * Quaternion::ScalarVector(0, rhs) * inverse(lhs));
}

template <class S, class T>
auto lerp(const S & a, const T & b, double t) noexcept -> std::enable_if_t<is_same_size_v<S, T>, S>
{
  return a + t * (b - a);
}

template <class S, class T>
auto slerp(const S & a, const T & b, double t) noexcept
  -> std::enable_if_t<is_quat_compat_v<S> && is_quat_compat_v<T>, S>
{
  double dot = dot(a, b);
  double theta = std::acos(dot);
  return std::sin((1 - t) * theta) / std::sin(theta) * a +
         std::sin(t * theta) / std::sin(theta) * (dot < 0 ? -b : b);
}

struct Color : std_msgs::msg::ColorRGBA
{
  using std_msgs::msg::ColorRGBA::ColorRGBA;

  Color(const float r, const float g, const float b, const float a = 1.0f) noexcept
  {
    this->r = r, this->g = g, this->b = b, this->a = a;
  }

  template <class Return = Color>
  static auto White(const float alpha = 1.0f) noexcept
    -> std::enable_if_t<is_rgba_compat_v<Return>, Return>
  {
    return detail::construct<Return>::from(1.00, 1.00, 1.00, alpha);
  }

  template <class Return = Color>
  static auto Silver(const float alpha = 1.0f) noexcept
    -> std::enable_if_t<is_rgba_compat_v<Return>, Return>
  {
    return detail::construct<Return>::from(0.75, 0.75, 0.75, alpha);
  }

  template <class Return = Color>
  static auto Gray(const float alpha = 1.0f) noexcept
    -> std::enable_if_t<is_rgba_compat_v<Return>, Return>
  {
    return detail::construct<Return>::from(0.50, 0.50, 0.50, alpha);
  }

  template <class Return = Color>
  static auto Black(const float alpha = 1.0f) noexcept
    -> std::enable_if_t<is_rgba_compat_v<Return>, Return>
  {
    return detail::construct<Return>::from(0.00, 0.00, 0.00, alpha);
  }

  template <class Return = Color>
  static auto Red(const float alpha = 1.0f) noexcept
    -> std::enable_if_t<is_rgba_compat_v<Return>, Return>
  {
    return detail::construct<Return>::from(1.00, 0.00, 0.00, alpha);
  }

  template <class Return = Color>
  static auto Maroon(const float alpha = 1.0f) noexcept
    -> std::enable_if_t<is_rgba_compat_v<Return>, Return>
  {
    return detail::construct<Return>::from(0.50, 0.00, 0.00, alpha);
  }

  template <class Return = Color>
  static auto Yellow(const float alpha = 1.0f) noexcept
    -> std::enable_if_t<is_rgba_compat_v<Return>, Return>
  {
    return detail::construct<Return>::from(1.00, 1.00, 0.00, alpha);
  }

  template <class Return = Color>
  static auto Olive(const float alpha = 1.0f) noexcept
    -> std::enable_if_t<is_rgba_compat_v<Return>, Return>
  {
    return detail::construct<Return>::from(0.50, 0.50, 0.00, alpha);
  }

  template <class Return = Color>
  static auto Lime(const float alpha = 1.0f) noexcept
    -> std::enable_if_t<is_rgba_compat_v<Return>, Return>
  {
    return detail::construct<Return>::from(0.00, 1.00, 0.00, alpha);
  }

  template <class Return = Color>
  static auto Green(const float alpha = 1.0f) noexcept
    -> std::enable_if_t<is_rgba_compat_v<Return>, Return>
  {
    return detail::construct<Return>::from(0.00, 0.50, 0.00, alpha);
  }

  template <class Return = Color>
  static auto Aqua(const float alpha = 1.0f) noexcept
    -> std::enable_if_t<is_rgba_compat_v<Return>, Return>
  {
    return detail::construct<Return>::from(0.00, 1.00, 1.00, alpha);
  }

  template <class Return = Color>
  static auto Teal(const float alpha = 1.0f) noexcept
    -> std::enable_if_t<is_rgba_compat_v<Return>, Return>
  {
    return detail::construct<Return>::from(0.00, 0.50, 0.50, alpha);
  }

  template <class Return = Color>
  static auto Blue(const float alpha = 1.0f) noexcept
    -> std::enable_if_t<is_rgba_compat_v<Return>, Return>
  {
    return detail::construct<Return>::from(0.00, 0.00, 1.00, alpha);
  }

  template <class Return = Color>
  static auto Navy(const float alpha = 1.0f) noexcept
    -> std::enable_if_t<is_rgba_compat_v<Return>, Return>
  {
    return detail::construct<Return>::from(0.00, 0.00, 0.50, alpha);
  }

  template <class Return = Color>
  static auto Fuchsia(const float alpha = 1.0f) noexcept
    -> std::enable_if_t<is_rgba_compat_v<Return>, Return>
  {
    return detail::construct<Return>::from(1.00, 0.00, 1.00, alpha);
  }

  template <class Return = Color>
  static auto Purple(const float alpha = 1.0f) noexcept
    -> std::enable_if_t<is_rgba_compat_v<Return>, Return>
  {
    return detail::construct<Return>::from(0.50, 0.00, 0.50, alpha);
  }

  template <class T>
  operator T() const
  {
    return util::convert<T, std_msgs::msg::ColorRGBA>(*this);
  }
};

namespace detail
{
template <class T>
struct access<T, std::enable_if_t<std::is_base_of_v<std_msgs::msg::ColorRGBA, T>>>
{
  static constexpr size_t size = 4;

  template <size_t I>
  static float get(const std_msgs::msg::ColorRGBA & t) noexcept
  {
    if constexpr (I == 0) return t.r;
    if constexpr (I == 1) return t.g;
    if constexpr (I == 2) return t.b;
    if constexpr (I == 3) return t.a;
  }
  template <size_t I>
  static void set(std_msgs::msg::ColorRGBA & t, float value) noexcept
  {
    if constexpr (I == 0) t.r = value;
    if constexpr (I == 1) t.g = value;
    if constexpr (I == 2) t.b = value;
    if constexpr (I == 3) t.a = value;
  }
};
}  // namespace detail

}  // namespace param

namespace traits
{
template <class From, class To>
struct converter<
  From, To, std::enable_if_t<!std::is_convertible_v<From, To> && param::is_same_size_v<From, To>>>
{
  static constexpr To convert(const From & arg) { return param::detail::construct<To>::from(arg); }
};
}  // namespace traits

namespace marker
{

namespace detail
{
template <class Dest, class Src, class Enabler = void>
struct merger;
}

namespace attr
{
template <class Derived, class Base>
struct CRTP : Base
{
protected:
  Derived & derived() noexcept { return static_cast<Derived &>(*this); }
  const Derived & derived() const noexcept { return static_cast<const Derived &>(*this); }
};

template <class Derived, class Base>
struct Position : Base
{
  template <class T>
  Derived & position(const T & position) noexcept
  {
    this->message.pose.position = util::convert<geometry_msgs::msg::Point>(position);
    return this->derived();
  }

  Derived & position(const double x, const double y, const double z) noexcept
  {
    this->message.pose.position.x = x;
    this->message.pose.position.y = y;
    this->message.pose.position.z = z;
    return this->derived();
  }
};

template <class Derived, class Base>
struct Orientation : Base
{
  Orientation() noexcept { orientation(1, 0, 0, 0); }

  template <class T>
  Derived & orientation(const T & orientation) noexcept
  {
    this->message.pose.orientation = util::convert<geometry_msgs::msg::Quaternion>(orientation);
    return this->derived();
  }

  Derived & orientation(const double w, const double x, const double y, const double z) noexcept
  {
    this->message.pose.orientation.w = w;
    this->message.pose.orientation.x = x;
    this->message.pose.orientation.y = y;
    this->message.pose.orientation.z = z;
    return this->derived();
  }
};

template <class Derived, class Base>
struct Scale : Base
{
  Scale() noexcept { scale(1, 1, 1); }

  Derived & scale(const double x, const double y, const double z) noexcept
  {
    this->message.scale.x = x;
    this->message.scale.y = y;
    this->message.scale.z = z;
    return this->derived();
  }
};

template <class Derived, class Base>
struct PoseArrowScale : Base
{
  PoseArrowScale() noexcept { scale(0.2, 0.2, 1); }

  Derived & scale(const double length, const double width, const double height) noexcept
  {
    this->message.scale.x = length;
    this->message.scale.y = width;
    this->message.scale.z = height;
    return this->derived();
  }
};

template <class Derived, class Base>
struct VectorArrowScale : Base
{
  VectorArrowScale() noexcept { scale(0.2, 0.4, 0.4); }

  Derived & scale(
    const double shaft_diameter, const double head_diameter, const double head_length) noexcept
  {
    this->message.scale.x = shaft_diameter;
    this->message.scale.y = head_diameter;
    this->message.scale.z = head_length;
    return this->derived();
  }
};

template <class Derived, class Base>
struct PointScale : Base
{
  PointScale() noexcept { scale(0.05); }

  Derived & scale(const double width) noexcept { return scale(width, width); }

  Derived & scale(const double width, const double height) noexcept
  {
    this->message.scale.x = width;
    this->message.scale.y = height;
    return this->derived();
  }
};

template <class Derived, class Base>
struct LineScale : Base
{
  LineScale() noexcept { scale(0.05); }

  Derived & scale(const double width) noexcept
  {
    this->message.scale.x = width;
    return this->derived();
  }
};

template <class Derived, class Base>
struct TextScale : Base
{
  TextScale() noexcept { scale(0.05); }

  Derived & scale(const double height) noexcept
  {
    this->message.scale.z = height;
    return this->derived();
  }
};

template <class Derived, class Base>
struct Color : Base
{
  Color() noexcept { color(1, 1, 1, 1); }

  template <class T>
  Derived & color(const T & color) noexcept
  {
    this->message.color = util::convert<std_msgs::msg::ColorRGBA>(color);
    return this->derived();
  }

  Derived & color(const double r, const double g, const double b, const double a = 1) noexcept
  {
    this->message.color.r = r;
    this->message.color.g = g;
    this->message.color.b = b;
    this->message.color.a = a;
    return this->derived();
  }
};

namespace internal
{
template <class To>
struct convert
{
  template <class From>
  decltype(auto) operator()(const From & from) const noexcept
  {
    return util::convert<To>(from);
  }
};
}  // namespace internal

template <class Derived, class Base>
struct Colors : Color<Derived, Base>
{
  Colors() noexcept { color(1, 1, 1, 1); }

  template <class T>
  Derived & color(const T & color) noexcept
  {
    this->message.colors.clear();
    return Color<Derived, Base>::color(color);
  }

  Derived & color(const double r, const double g, const double b, const double a = 1) noexcept
  {
    this->message.colors.clear();
    return Color<Derived, Base>::color(r, g, b, a);
  }

  template <
    class Iterable = std::initializer_list<param::Color>,
    class Func = internal::convert<std_msgs::msg::ColorRGBA>>
  Derived & colors(const Iterable & iterable, const Func & func = Func()) noexcept
  {
    if constexpr (util::has_size_v<Iterable>) {
      this->message.colors.reserve(iterable.size());
    }
    for (const auto & e : iterable) {
      this->message.colors.push_back(func(e));
    }
    return this->derived();
  }
};

template <class Derived, class Base>
struct Points : Base
{
  template <
    class Iterable = std::initializer_list<param::Point>,
    class Func = internal::convert<geometry_msgs::msg::Point>>
  Derived & points(const Iterable & iterable, const Func & func = Func()) noexcept
  {
    if constexpr (util::has_size_v<Iterable>) {
      this->message.points.reserve(iterable.size());
    }
    for (const auto & e : iterable) {
      this->message.points.push_back(func(e));
    }
    return this->derived();
  }
};

template <class Derived, class Base>
struct LinePoints : Base
{
  LinePoints() noexcept
  {
    this->message.points.resize(2);
    end(1, 0, 0);
  }

  template <class T>
  Derived & start(const T & point) noexcept
  {
    return this->point(0, point);
  }
  Derived & start(const double x, const double y, const double z) noexcept
  {
    return this->point(0, x, y, z);
  }

  template <class T>
  Derived & end(const T & point) noexcept
  {
    return this->point(1, point);
  }
  Derived & end(const double x, const double y, const double z) noexcept
  {
    return this->point(1, x, y, z);
  }

private:
  template <class T>
  Derived & point(size_t index, const T & point) noexcept
  {
    this->message.points[index] = util::convert<geometry_msgs::msg::Point>(point);
    return this->derived();
  }

  Derived & point(size_t index, const double x, const double y, const double z) noexcept
  {
    this->message.points[index].x = x;
    this->message.points[index].y = y;
    this->message.points[index].z = z;
    return this->derived();
  }
};

template <class Derived, class Base>
struct Text : Base
{
  Text() noexcept { text("visualization_msgs::msg::Marker"); }

  Derived & text(const std::string & text) noexcept
  {
    this->message.text = text;
    return this->derived();
  }
};

template <class Derived, class Base>
struct MeshResource : Base
{
  Derived & mesh_resource(const std::string & mesh_resource) noexcept
  {
    this->message.mesh_resource = mesh_resource;
    return this->derived();
  }

  Derived & mesh_use_embedded_materials(const uint8_t mesh_use_embedded_materials) noexcept
  {
    this->message.mesh_use_embedded_materials = mesh_use_embedded_materials;
    return this->derived();
  }
};

}  // namespace attr

struct MarkerWrapper
{
protected:
  visualization_msgs::msg::Marker message;

public:
  operator visualization_msgs::msg::Marker &() noexcept { return message; }
  operator const visualization_msgs::msg::Marker &() const noexcept { return message; }
};

struct DeleteAll : MarkerWrapper
{
  DeleteAll(const std::string & ns = "") noexcept
  {
    this->message.action = visualization_msgs::msg::Marker::DELETEALL;
    this->message.ns = ns;
  }
};

struct Delete : MarkerWrapper
{
  Delete(const int32_t id = 0, const std::string & ns = "") noexcept
  {
    this->message.action = visualization_msgs::msg::Marker::DELETE;
    this->message.id = id;
    this->message.ns = ns;
  }
};

template <int32_t Type, template <class, class> class... Features>
struct Add : util::chain<Add<Type, Features...>, MarkerWrapper, attr::CRTP, Features...>
{
  Add(const int32_t id = 0, const std::string & ns = "") noexcept
  {
    this->message.action = visualization_msgs::msg::Marker::ADD;
    this->message.type = Type;
    this->message.id = id;
    this->message.ns = ns;
  }

  Add & lifetime(const double lifetime) noexcept
  {
    this->message.lifetime = lifetime;
    return *this;
  }

  Add & frame_locked(const uint8_t frame_locked) noexcept
  {
    this->message.frame_locked = frame_locked;
    return *this;
  }
};

using PoseArrow = Add<
  visualization_msgs::msg::Marker::ARROW, attr::Position, attr::Orientation, attr::PoseArrowScale,
  attr::Color>;

using LineArrow = Add<
  visualization_msgs::msg::Marker::ARROW, attr::VectorArrowScale, attr::Color, attr::LinePoints>;

using Cube = Add<
  visualization_msgs::msg::Marker::CUBE, attr::Position, attr::Orientation, attr::Scale,
  attr::Color>;

using Sphere = Add<
  visualization_msgs::msg::Marker::SPHERE, attr::Position, attr::Orientation, attr::Scale,
  attr::Color>;

using Cylinder = Add<
  visualization_msgs::msg::Marker::CYLINDER, attr::Position, attr::Orientation, attr::Scale,
  attr::Color>;

using LineStrip = Add<
  visualization_msgs::msg::Marker::LINE_STRIP, attr::Position, attr::Orientation, attr::LineScale,
  attr::Colors, attr::Points>;

using LineList = Add<
  visualization_msgs::msg::Marker::LINE_LIST, attr::Position, attr::Orientation, attr::LineScale,
  attr::Colors, attr::Points>;

using CubeList = Add<
  visualization_msgs::msg::Marker::CUBE_LIST, attr::Position, attr::Orientation, attr::Scale,
  attr::Colors, attr::Points>;

using SphereList = Add<
  visualization_msgs::msg::Marker::SPHERE_LIST, attr::Position, attr::Orientation, attr::Scale,
  attr::Colors, attr::Points>;

using Points = Add<
  visualization_msgs::msg::Marker::POINTS, attr::Position, attr::Orientation, attr::PointScale,
  attr::Colors, attr::Points>;

using TextViewFacing = Add<
  visualization_msgs::msg::Marker::TEXT_VIEW_FACING, attr::Position, attr::TextScale, attr::Color,
  attr::Text>;

using MeshResource = Add<
  visualization_msgs::msg::Marker::MESH_RESOURCE, attr::Position, attr::Orientation, attr::Scale,
  attr::Color, attr::MeshResource>;

using TriangleList = Add<
  visualization_msgs::msg::Marker::TRIANGLE_LIST, attr::Position, attr::Orientation, attr::Scale,
  attr::Colors, attr::Points>;

}  // namespace marker

class Rviz
{
private:
  rclcpp::Publisher<visualization_msgs::msg::Marker>::SharedPtr publisher_;
  rclcpp::Clock::SharedPtr clock_;
  std::string frame_id_;

public:
  Rviz() {}
  void init(
    rclcpp::Node & node, const std::string & frame_id = "map",
    const std::string & topic = "visualization_marker")
  {
    frame_id_ = frame_id;
    publisher_ = node.create_publisher<visualization_msgs::msg::Marker>(topic, 1);
    clock_ = node.get_clock();
  }

  inline const Rviz & operator<<(const visualization_msgs::msg::Marker & marker) const
  {
    publish(marker);
    return *this;
  }

  inline const Rviz & operator<<(visualization_msgs::msg::Marker && marker) const
  {
    publish(marker);
    return *this;
  }

  void publish(const visualization_msgs::msg::Marker & marker) const
  {
    publish(visualization_msgs::msg::Marker(marker));
  }

  void publish(visualization_msgs::msg::Marker && marker) const
  {
    marker.header.frame_id = frame_id_;
    marker.header.stamp = clock_->now();
    publisher_->publish(marker);
  }
};

}  // namespace flrv

#endif  // FLUENT_RVIZ__FLUENT_RVIZ_HPP_