Commit e82d680c authored by Philip Trettner's avatar Philip Trettner
Browse files

subscript, working on quadrics, angle rename

parent 4d89e687
......@@ -30,7 +30,17 @@ std::map<p, int> v_cnts; // std::less
std::unordered_map<p, int> v_cnts; // std::hash
```
Most functionality is implemented as free functions, overloaded by type and with consistent vocabulary:
Most functionality is implemented as free functions, overloaded by type and with consistent vocabulary.
### Object Types
Basically all `tg` provided types are _regular_, i.e. have value semantics like `int` or `vector`.
The following categories exist:
* _POD_ types like `vec` and most fixed-size objects
* dynamically sized non-_POD_ values like `polygon` (similar to `vector`)
* _transparent_ types like `vec` where `.x` is part of the interface
* _opaque_ types like `angle` and `quadric` where the representation is implementation detail
### Vectors
......
......@@ -10,10 +10,10 @@
namespace tg
{
template <class T>
constexpr angle<T> tau = angle<T>::from_radians(static_cast<T>(6.28318530717958647693));
constexpr angle_t<T> tau = angle_t<T>::from_radians(static_cast<T>(6.28318530717958647693));
template <class T>
constexpr angle<T> pi = angle<T>::from_radians(static_cast<T>(3.14159265358979323846));
constexpr angle_t<T> pi = angle_t<T>::from_radians(static_cast<T>(3.14159265358979323846));
template <class T>
constexpr T tau_scalar = static_cast<T>(6.28318530717958647693);
......
......@@ -93,7 +93,7 @@ TG_NODISCARD inline f32 abs(f32 v) { return std::abs(v); }
TG_NODISCARD inline f64 abs(f64 v) { return std::abs(v); }
template <class T>
TG_NODISCARD angle<T> abs(angle<T> a)
TG_NODISCARD angle_t<T> abs(angle_t<T> a)
{
return radians(abs(a.radians()));
}
......@@ -209,22 +209,22 @@ TG_NODISCARD inline f64 log10(f64 v) { return std::log10(v); }
// ==================================================================
// Trigonometry
TG_NODISCARD inline f32 sin(angle<f32> v) { return std::sin(v.radians()); }
TG_NODISCARD inline f64 sin(angle<f64> v) { return std::sin(v.radians()); }
TG_NODISCARD inline f32 cos(angle<f32> v) { return std::cos(v.radians()); }
TG_NODISCARD inline f64 cos(angle<f64> v) { return std::cos(v.radians()); }
TG_NODISCARD inline f32 tan(angle<f32> v) { return std::tan(v.radians()); }
TG_NODISCARD inline f64 tan(angle<f64> v) { return std::tan(v.radians()); }
TG_NODISCARD inline angle<f32> asin(f32 v) { return radians(std::asin(v)); }
TG_NODISCARD inline angle<f64> asin(f64 v) { return radians(std::asin(v)); }
TG_NODISCARD inline angle<f32> acos(f32 v) { return radians(std::acos(v)); }
TG_NODISCARD inline angle<f64> acos(f64 v) { return radians(std::acos(v)); }
TG_NODISCARD inline angle<f32> atan(f32 v) { return radians(std::atan(v)); }
TG_NODISCARD inline angle<f64> atan(f64 v) { return radians(std::atan(v)); }
TG_NODISCARD inline angle<f32> atan2(f32 y, f32 x) { return radians(std::atan2(y, x)); }
TG_NODISCARD inline angle<f64> atan2(f64 y, f64 x) { return radians(std::atan2(y, x)); }
TG_NODISCARD inline f32 sin(angle_t<f32> v) { return std::sin(v.radians()); }
TG_NODISCARD inline f64 sin(angle_t<f64> v) { return std::sin(v.radians()); }
TG_NODISCARD inline f32 cos(angle_t<f32> v) { return std::cos(v.radians()); }
TG_NODISCARD inline f64 cos(angle_t<f64> v) { return std::cos(v.radians()); }
TG_NODISCARD inline f32 tan(angle_t<f32> v) { return std::tan(v.radians()); }
TG_NODISCARD inline f64 tan(angle_t<f64> v) { return std::tan(v.radians()); }
TG_NODISCARD inline angle_t<f32> asin(f32 v) { return radians(std::asin(v)); }
TG_NODISCARD inline angle_t<f64> asin(f64 v) { return radians(std::asin(v)); }
TG_NODISCARD inline angle_t<f32> acos(f32 v) { return radians(std::acos(v)); }
TG_NODISCARD inline angle_t<f64> acos(f64 v) { return radians(std::acos(v)); }
TG_NODISCARD inline angle_t<f32> atan(f32 v) { return radians(std::atan(v)); }
TG_NODISCARD inline angle_t<f64> atan(f64 v) { return radians(std::atan(v)); }
TG_NODISCARD inline angle_t<f32> atan2(f32 y, f32 x) { return radians(std::atan2(y, x)); }
TG_NODISCARD inline angle_t<f64> atan2(f64 y, f64 x) { return radians(std::atan2(y, x)); }
TG_NODISCARD inline f32 sinh(f32 v) { return std::sinh(v); }
TG_NODISCARD inline f64 sinh(f64 v) { return std::sinh(v); }
......
......@@ -10,6 +10,7 @@
#include <typed-geometry/types/objects/box.hh>
#include <typed-geometry/types/objects/halfspace.hh>
#include <typed-geometry/types/objects/hyperplane.hh>
#include <typed-geometry/types/objects/line.hh>
#include <typed-geometry/detail/operators/ops_pos.hh>
#include <typed-geometry/functions/dot.hh>
......@@ -82,4 +83,10 @@ template <int D, class ScalarT>
constexpr halfspace<D, ScalarT>::halfspace(dir_t n, pos_t p) : normal(n), dis(dot(vec<D, ScalarT>(p), n))
{
}
template <int D, class ScalarT>
constexpr line<D, ScalarT> line<D, ScalarT>::from_points(pos_t a, pos_t b)
{
return line(a, normalize(b - a));
}
}
#pragma once
#include "operators/common.hh"
#include "operators/subscript.hh"
#include "operators/ops_angle.hh"
#include "operators/ops_comp.hh"
#include "operators/ops_dir.hh"
#include "operators/ops_mat.hh"
#include "operators/ops_pos.hh"
#include "operators/ops_quadric.hh"
#include "operators/ops_size.hh"
#include "operators/ops_triangle.hh"
#include "operators/ops_vec.hh"
......@@ -6,46 +6,46 @@
namespace tg
{
template <class A>
TG_NODISCARD constexpr angle<A> operator-(angle<A> const& a)
TG_NODISCARD constexpr angle_t<A> operator-(angle_t<A> const& a)
{
return radians(-a.radians());
}
template <class A>
TG_NODISCARD constexpr angle<A> operator+(angle<A> const& a)
TG_NODISCARD constexpr angle_t<A> operator+(angle_t<A> const& a)
{
return a;
}
template <class A, class B, class R = promoted_scalar<A, B>>
TG_NODISCARD constexpr angle<R> operator+(angle<A> a, angle<B> b)
TG_NODISCARD constexpr angle_t<R> operator+(angle_t<A> a, angle_t<B> b)
{
return radians(a.radians() + b.radians());
}
template <class A, class B, class R = promoted_scalar<A, B>>
TG_NODISCARD constexpr angle<R> operator-(angle<A> a, angle<B> b)
TG_NODISCARD constexpr angle_t<R> operator-(angle_t<A> a, angle_t<B> b)
{
return radians(a.radians() - b.radians());
}
template <class A, class B, class R = promoted_scalar<A, B>>
TG_NODISCARD constexpr angle<R> operator*(angle<A> a, B b)
TG_NODISCARD constexpr angle_t<R> operator*(angle_t<A> a, B b)
{
return radians(a.radians() * b);
}
template <class A, class B, class R = promoted_scalar<A, B>>
TG_NODISCARD constexpr angle<R> operator/(angle<A> a, B b)
TG_NODISCARD constexpr angle_t<R> operator/(angle_t<A> a, B b)
{
return radians(a.radians() / b);
}
template <class A, class B, class R = promoted_scalar<A, B>>
TG_NODISCARD constexpr angle<R> operator*(A a, angle<B> b)
TG_NODISCARD constexpr angle_t<R> operator*(A a, angle_t<B> b)
{
return radians(a * b.radians());
}
template <class A, class B, class R = promoted_scalar<A, B>>
TG_NODISCARD constexpr R operator/(angle<A> a, angle<B> b)
TG_NODISCARD constexpr R operator/(angle_t<A> a, angle_t<B> b)
{
return a.radians() / b.radians();
}
......
#pragma once
#include <typed-geometry/types/quadric.hh>
namespace tg
{
template <class ScalarT>
TG_NODISCARD constexpr quadric<3, ScalarT> operator+(quadric<3, ScalarT> const& a, quadric<3, ScalarT> const& b)
{
quadric<3, ScalarT> r;
r.add(a);
r.add(b);
return r;
}
// TODO: scalar scaling + division?
}
#pragma once
#include <typed-geometry/types/comp.hh>
#include <typed-geometry/types/objects/line.hh>
#include <typed-geometry/types/objects/ray.hh>
#include <typed-geometry/types/objects/segment.hh>
#include <typed-geometry/types/objects/triangle.hh>
#include <typed-geometry/functions/interpolate.hh>
#include <typed-geometry/functions/mix.hh>
// defines operator[] for several object types
// obj[coord] indexes into objects that are "coordinate addressable" like triangle or segment
// respects the following identity:
// p == obj[coordinates(obj, p)]
namespace tg
{
template <int D, class ScalarT>
TG_NODISCARD constexpr pos<D, ScalarT> segment<D, ScalarT>::operator[](ScalarT t) const
{
return mix(this->pos0, this->pos1, t);
}
template <int D, class ScalarT>
TG_NODISCARD constexpr pos<D, ScalarT> ray<D, ScalarT>::operator[](ScalarT t) const
{
return this->origin + this->dir * t;
}
template <int D, class ScalarT>
TG_NODISCARD constexpr pos<D, ScalarT> line<D, ScalarT>::operator[](ScalarT t) const
{
return this->pos + this->dir * t;
}
template <int D, class ScalarT>
TG_NODISCARD constexpr pos<D, ScalarT> triangle<D, ScalarT>::operator[](comp<3, ScalarT> const& barycoords) const
{
return interpolate(*this, barycoords[0], barycoords[1], barycoords[2]);
}
template <int D, class ScalarT>
TG_NODISCARD constexpr pos<D, ScalarT> triangle<D, ScalarT>::operator[](comp<2, ScalarT> const& barycoords) const
{
return interpolate(*this, barycoords[0], barycoords[1], 1 - barycoords[0] - barycoords[1]);
}
}
......@@ -13,7 +13,7 @@ namespace tg
{
// returns the (smaller) angle between two vectors, i.e. the result is in 0..pi (0°..180°)
template <int D, class ScalarT>
TG_NODISCARD constexpr angle<fractional_result<ScalarT>> angle_between(vec<D, ScalarT> const& a, vec<D, ScalarT> const& b)
TG_NODISCARD constexpr angle_t<fractional_result<ScalarT>> angle_between(vec<D, ScalarT> const& a, vec<D, ScalarT> const& b)
{
auto a_unit = normalize_safe(a);
auto b_unit = normalize_safe(b);
......@@ -30,7 +30,7 @@ TG_NODISCARD constexpr auto angle_between(A const& a, B const& b) -> decltype(ac
// Returns the angle of a rotation of a towards b about the orthogonal_axis
// The orthogonal axis is important to determine the direction of orientation (axb vs -axb)
template <class ScalarT>
TG_NODISCARD constexpr angle<fractional_result<ScalarT>> angle_towards(vec<3, ScalarT> const& a, vec<3, ScalarT> const& b, vec<3, ScalarT> const& orthogonal_axis)
TG_NODISCARD constexpr angle_t<fractional_result<ScalarT>> angle_towards(vec<3, ScalarT> const& a, vec<3, ScalarT> const& b, vec<3, ScalarT> const& orthogonal_axis)
{
TG_CONTRACT(are_orthogonal(a, orthogonal_axis));
TG_CONTRACT(are_orthogonal(b, orthogonal_axis));
......
......@@ -5,6 +5,7 @@
#include <typed-geometry/types/objects/plane.hh>
#include <typed-geometry/types/objects/segment.hh>
#include <typed-geometry/types/pos.hh>
#include <typed-geometry/types/quadric.hh>
#include "coordinates.hh"
#include "mix.hh"
......@@ -37,4 +38,64 @@ TG_NODISCARD constexpr auto closest_points(pos<D, ScalarT> const& p, ObjectT con
// =========== Object Implementations ===========
// TODO
// =========== Other Implementations ===========
template <class ScalarT>
TG_NODISCARD constexpr pos<3, ScalarT> closest_point(quadric<3, ScalarT> const& q)
{
// Returns a point minimizing this quadric
// (Point is unique if any plane was added with sigma > 0)
// Solving Ax = r with some common subexpressions precomputed
auto a = q.A00;
auto b = q.A01;
auto c = q.A02;
auto d = q.A11;
auto e = q.A12;
auto f = q.A22;
auto r0 = q.r.x;
auto r1 = q.r.y;
auto r2 = q.r.z;
auto ad = a * d;
auto ae = a * e;
auto af = a * f;
auto bc = b * c;
auto be = b * e;
auto bf = b * f;
auto df = d * f;
auto ce = c * e;
auto cd = c * d;
auto be_cd = be - cd;
auto bc_ae = bc - ae;
auto ce_bf = ce - bf;
auto denom = 1 / (a * df + 2 * b * ce - ae * e - bf * b - cd * c);
auto nom0 = r0 * (df - e * e) + r1 * ce_bf + r2 * be_cd;
auto nom1 = r0 * ce_bf + r1 * (af - c * c) + r2 * bc_ae;
auto nom2 = r0 * be_cd + r1 * bc_ae + r2 * (ad - b * b);
return {nom0 * denom, nom1 * denom, nom2 * denom};
}
template <class ScalarT>
TG_NODISCARD constexpr pos<2, ScalarT> closest_point(quadric<2, ScalarT> const& q)
{
// Returns a point minimizing this quadric
// (Point is unique if any plane was added with sigma > 0)
// Solving Ax = r with some common subexpressions precomputed
auto a = q.A00;
auto b = q.A01;
auto c = q.A11;
auto denom = 1 / (a * c - b * b);
auto nom0 = q.r.x * c - q.r.y * b;
auto nom1 = q.r.y * a - q.r.x * b;
return {nom0 * denom, nom1 * denom};
}
} // namespace tg
......@@ -2,6 +2,8 @@
#include <typed-geometry/detail/operators/ops_vec.hh>
#include <typed-geometry/types/array.hh>
#include <typed-geometry/types/objects/line.hh>
#include <typed-geometry/types/objects/ray.hh>
#include <typed-geometry/types/objects/segment.hh>
#include <typed-geometry/types/objects/triangle.hh>
#include <typed-geometry/types/pos.hh>
......@@ -15,6 +17,9 @@
// - triangle (barycoords)
// - segment (t-parameter)
// The following identity is useful:
// p == obj[coordinates(obj, p)] (for all p in obj)
// Note: Passing a position that does not lie inside the given primitive,
// the behavior is (yet) undefined
namespace tg
......@@ -59,5 +64,4 @@ TG_NODISCARD constexpr ScalarT coordinates(segment<D, ScalarT> const& s, pos<D,
return t;
}
} // namespace tg
#pragma once
#include <typed-geometry/common/scalar_math.hh>
#include <typed-geometry/detail/operators/ops_pos.hh>
#include <typed-geometry/detail/operators/ops_vec.hh>
#include <typed-geometry/common/scalar_math.hh>
#include <typed-geometry/detail/special_values.hh>
#include <typed-geometry/detail/tg_traits.hh>
#include <typed-geometry/types/objects/line.hh>
#include <typed-geometry/types/objects/plane.hh>
#include <typed-geometry/types/objects/segment.hh>
#include <typed-geometry/types/pos.hh>
#include <typed-geometry/types/quadric.hh>
#include <typed-geometry/types/vec.hh>
#include "closest_points.hh"
......@@ -49,6 +50,7 @@ TG_NODISCARD constexpr auto distance2_to_origin(Obj const& o) -> decltype(distan
return distance2(o, pos_type_for<Obj>::zero);
}
// =========== Object Implementations ===========
// signed distance is positive if p lies above pl, 0 if it lies on the plane and negative if below pl
......@@ -63,4 +65,43 @@ TG_NODISCARD constexpr fractional_result<ScalarT> distance(pos<3, ScalarT> const
{
return abs(signed_distance(p, pl));
}
// =========== Other Implementations ===========
template <class ScalarT>
TG_NODISCARD constexpr ScalarT distance2(pos<2, ScalarT> const& p, quadric<2, ScalarT> const& q)
{
/// Residual L2 error as given by x^T A x - 2 r^T x + c
vec<2, ScalarT> Ax = {
q.A00 * p.x + q.A01 * p.y, //
q.A01 * p.x + q.A11 * p.y, //
};
return dot(vec<2, ScalarT>(p), Ax) // x^T A x
- 2 * dot(vec<2, ScalarT>(p), q.r) // - 2 r^T x
+ q.d_sqr; // + c
}
template <class ScalarT>
TG_NODISCARD constexpr ScalarT distance2(pos<3, ScalarT> const& p, quadric<3, ScalarT> const& q)
{
/// Residual L2 error as given by x^T A x - 2 r^T x + c
vec<3, ScalarT> Ax = {
q.A00 * p.x + q.A01 * p.y + q.A02 * p.z, //
q.A01 * p.x + q.A11 * p.y + q.A12 * p.z, //
q.A02 * p.x + q.A12 * p.y + q.A22 * p.z, //
};
return dot(vec<3, ScalarT>(p), Ax) // x^T A x
- 2 * dot(vec<3, ScalarT>(p), q.r) // - 2 r^T x
+ q.d_sqr; // + c
}
template <int D, class ScalarT>
TG_NODISCARD constexpr ScalarT distance2(quadric<D, ScalarT> const& q, pos<D, ScalarT> const& p)
{
return distance(p, q);
}
} // namespace tg
#pragma once
#include <typed-geometry/common/assert.hh>
#include <typed-geometry/detail/optional.hh>
#include <typed-geometry/types/objects/circle.hh>
#include <typed-geometry/types/objects/hyperplane.hh>
#include <typed-geometry/types/objects/ray.hh>
#include <typed-geometry/types/objects/segment.hh>
#include <typed-geometry/types/objects/sphere.hh>
#include <typed-geometry/types/objects/triangle.hh>
......@@ -17,6 +19,9 @@
// intersects(a, b) -> bool iff any point lies in a and in b
// intersection(a, b) -> ??? returns an object describing the intersection
// intersection_coordinate(a, b) -> coords? such that a[coords] == intersection(a, b)
// intersection_coordinates(a, b) -> pair<coords, coords>? such that a[coords.first] == intersection(a, b) == b[coords.second]
// related function:
// closest_hit(ray, b) -> optional<pos>
......@@ -268,4 +273,17 @@ TG_NODISCARD constexpr auto intersection(circle<2, ScalarT> const& a, circle<2,
return {false, p_above, p_below};
}
template <int D, class ScalarT>
TG_NODISCARD constexpr optional<ScalarT> intersection_coordinate(segment<D, ScalarT> const& a, hyperplane<D, ScalarT> const& p)
{
auto denom = dot(p.normal, a.pos1 - a.pos0);
if (denom == 0)
return {};
auto t = (p.dis - dot(p.normal, a.pos0)) / denom;
if (t < 0 || t > 1)
return {};
return t;
}
} // namespace tg
......@@ -101,7 +101,7 @@ TG_IMPL_DEFINE_COMPWISE_FUNC_TERNARY(vec, clamp);
TG_IMPL_DEFINE_COMPWISE_FUNC_TERNARY(size, clamp);
// saturate
TG_IMPL_DEFINE_COMPWISE_FUNC_TERNARY(pos, saturate);
TG_IMPL_DEFINE_COMPWISE_FUNC_TERNARY(vec, saturate);
TG_IMPL_DEFINE_COMPWISE_FUNC_TERNARY(size, saturate);
TG_IMPL_DEFINE_COMPWISE_FUNC_UNARY(pos, saturate);
TG_IMPL_DEFINE_COMPWISE_FUNC_UNARY(vec, saturate);
TG_IMPL_DEFINE_COMPWISE_FUNC_UNARY(size, saturate);
} // namespace tg
......@@ -15,7 +15,7 @@ TG_NODISCARD constexpr ScalarT mix(ScalarT v0, ScalarT v1, ScalarT t)
return v0 + t * (v1 - v0);
}
template <class ScalarT, class = enable_if<is_floating_point<ScalarT>>>
TG_NODISCARD constexpr angle<ScalarT> mix(angle<ScalarT> v0, angle<ScalarT> v1, ScalarT t)
TG_NODISCARD constexpr angle_t<ScalarT> mix(angle_t<ScalarT> v0, angle_t<ScalarT> v1, ScalarT t)
{
return tg::radians(tg::mix(v0.radians(), v1.radians(), t));
}
......
......@@ -12,7 +12,7 @@
namespace tg
{
template <class T>
TG_NODISCARD constexpr mat<4, 4, T> rotation_around(dir<3, T> const& axis, angle<T> angle)
TG_NODISCARD constexpr mat<4, 4, T> rotation_around(dir<3, T> const& axis, angle_t<T> angle)
{
auto ca = cos(angle);
auto sa = sin(angle);
......@@ -39,13 +39,13 @@ TG_NODISCARD constexpr mat<4, 4, T> rotation_around(dir<3, T> const& axis, angle
return m;
}
template <class T>
TG_NODISCARD constexpr mat<4, 4, T> rotation_around(angle<T> angle, dir<3, T> const& axis)
TG_NODISCARD constexpr mat<4, 4, T> rotation_around(angle_t<T> angle, dir<3, T> const& axis)
{
return rotation_around(axis, angle);
}
template <class T>
TG_NODISCARD constexpr mat<4, 4, T> rotation_x(angle<T> a)
TG_NODISCARD constexpr mat<4, 4, T> rotation_x(angle_t<T> a)
{
auto ca = cos(a);
auto sa = sin(a);
......@@ -58,7 +58,7 @@ TG_NODISCARD constexpr mat<4, 4, T> rotation_x(angle<T> a)
return m;
}
template <class T>
TG_NODISCARD constexpr mat<4, 4, T> rotation_y(angle<T> a)
TG_NODISCARD constexpr mat<4, 4, T> rotation_y(angle_t<T> a)
{
auto ca = cos(a);
auto sa = sin(a);
......@@ -71,7 +71,7 @@ TG_NODISCARD constexpr mat<4, 4, T> rotation_y(angle<T> a)
return m;
}
template <class T>
TG_NODISCARD constexpr mat<4, 4, T> rotation_z(angle<T> a)
TG_NODISCARD constexpr mat<4, 4, T> rotation_z(angle_t<T> a)
{
auto ca = cos(a);
auto sa = sin(a);
......@@ -85,7 +85,7 @@ TG_NODISCARD constexpr mat<4, 4, T> rotation_z(angle<T> a)
}
template <class ScalarT>
TG_NODISCARD constexpr mat<3, 3, ScalarT> rotation_around(pos<2, ScalarT> p, angle<ScalarT> a)
TG_NODISCARD constexpr mat<3, 3, ScalarT> rotation_around(pos<2, ScalarT> p, angle_t<ScalarT> a)
{
auto origin_to_p = p - pos<2, ScalarT>::zero;
......
......@@ -102,7 +102,7 @@ TG_NODISCARD constexpr u64 uniform(Rng& rng, u64 a, u64 b)
}
template <class T, class Rng>
TG_NODISCARD constexpr angle<T> uniform(Rng& rng, angle<T> a, angle<T> b)
TG_NODISCARD constexpr angle_t<T> uniform(Rng& rng, angle_t<T> a, angle_t<T> b)
{
return mix(a, b, detail::uniform01<T>(rng));
}
......@@ -211,10 +211,10 @@ struct sampler<bool>
}
};
template <class T>
struct sampler<angle<T>>
struct sampler<angle_t<T>>
{
template <class Rng>
constexpr static angle<T> uniform(Rng& rng)
constexpr static angle_t<T> uniform(Rng& rng)
{
return tg::uniform(rng, tg::radians(T(0)), 2 * tg::pi<T>);
}
......
......@@ -39,9 +39,9 @@ namespace std
{
// -- scalars
template <class T>
struct hash<tg::angle<T>>
struct hash<tg::angle_t<T>>
{
std::size_t operator()(tg::angle<T> const& v) const noexcept { return tg::detail::hash(v.radians()); }
std::size_t operator()(tg::angle_t<T> const& v) const noexcept { return tg::detail::hash(v.radians()); }
};
// -- comp
......
......@@ -42,7 +42,7 @@ std::basic_ostringstream<CharT, Traits> temp_sstream(std::basic_ostream<CharT, T
// };
template <class T, class CharT, class Traits>
std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& out, angle<T> const& val)
std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& out, angle_t<T> const& val)
{