MeshLib C++ Docs
Loading...
Searching...
No Matches
MRUnits.h
Go to the documentation of this file.
1#pragma once
2
3#include "MRMesh/MRMacros.h"
4#include "MRViewer/exports.h"
6
7#include <cassert>
8#include <optional>
9#include <string>
10#include <variant>
11
12// Read the manual at: docs/measurement_units.md
13
14namespace MR
15{
16
17// A stub measurement unit representing no unit.
18enum class NoUnit
19{
20 _count [[maybe_unused]]
21};
22
23// Measurement units of length.
24enum class LengthUnit
25{
26 mm,
27 meters,
28 inches,
29 _count [[maybe_unused]],
30};
31
32// Measurement units of angle.
33enum class AngleUnit
34{
35 radians,
36 degrees,
37 _count [[maybe_unused]],
38};
39
40// Measurement units of screen sizes.
41enum class PixelSizeUnit
42{
43 pixels,
44 _count [[maybe_unused]],
45};
46
47// Measurement units for factors / ratios.
48enum class RatioUnit
49{
50 factor, // 0..1 x
51 percents, // 0..100 %
52 _count [[maybe_unused]],
53};
54
55// Measurement units for time.
56enum class TimeUnit
57{
58 seconds,
60 _count [[maybe_unused]],
61};
62
63// Measurement units for movement speed.
65{
69 _count [[maybe_unused]],
70};
71
72// Measurement units for surface area.
73enum class AreaUnit
74{
75 mm2,
76 meters2,
77 inches2,
78 _count [[maybe_unused]],
79};
80
81// Measurement units for body volume.
82enum class VolumeUnit
83{
84 mm3,
85 meters3,
86 inches3,
87 _count [[maybe_unused]],
88};
89
90// Measurement units for 1/length.
91enum class InvLengthUnit
92{
93 inv_mm, // mm^-1
94 inv_meters, // meters^-1
95 inv_inches, // inches^-1
96 _count [[maybe_unused]],
97};
98
99// A list of all unit enums, for internal use.
100#define DETAIL_MR_UNIT_ENUMS(X) X(NoUnit) X(LengthUnit) X(AngleUnit) X(PixelSizeUnit) X(RatioUnit) X(TimeUnit) X(MovementSpeedUnit) X(AreaUnit) X(VolumeUnit) X(InvLengthUnit)
101
102// All supported value types for `valueToString()`.
103// Not using `__VA_OPT__(,)` here to support legacy MSVC preprocessor.
104#ifdef __clang__
105#pragma clang diagnostic push
106#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
107#endif
108#define DETAIL_MR_UNIT_VALUE_TYPES(X, ...) \
109 X(float ,__VA_ARGS__) X(double ,__VA_ARGS__) X(long double ,__VA_ARGS__) \
110 X(signed char ,__VA_ARGS__) X(unsigned char ,__VA_ARGS__) \
111 X(short ,__VA_ARGS__) X(unsigned short ,__VA_ARGS__) \
112 X(int ,__VA_ARGS__) X(unsigned int ,__VA_ARGS__) \
113 X(long ,__VA_ARGS__) X(unsigned long ,__VA_ARGS__) \
114 X(long long ,__VA_ARGS__) X(unsigned long long ,__VA_ARGS__)
115#ifdef __clang__
116#pragma clang diagnostic pop
117#endif
118
119// Whether `E` is one of the unit enums: NoUnit, LengthUnit, AngleUnit, ...
120template <typename T>
121concept UnitEnum =
122 #define MR_X(E) || std::same_as<T, E>
124 #undef MR_X
125
126// ---
127
128// Information about a single measurement unit.
130{
131 // This is used to convert between units.
132 // To convert from A to B, multiply by A's factor and divide by B's.
134
135 std::string_view prettyName;
136
137 // The short unit name that's placed after values.
138 // This may or may not start with a space.
139 std::string_view unitSuffix;
140};
141
142// Returns information about a single measurement unit.
143template <UnitEnum E>
144[[nodiscard]] const UnitInfo& getUnitInfo( E unit ) = delete;
145
146#define MR_X(E) template <> [[nodiscard]] MRVIEWER_API const UnitInfo& getUnitInfo( E unit );
148#undef MR_X
149
150// Returns true if converting a value between units `a` and `b` doesn't change its value.
151template <UnitEnum E>
152[[nodiscard]] bool unitsAreEquivalent( E a, E b )
153{
154 return a == b || getUnitInfo( a ).conversionFactor == getUnitInfo( b ).conversionFactor;
155}
156// This version also returns true if `a` or `b` is null.
157template <UnitEnum E>
158[[nodiscard]] bool unitsAreEquivalent( const std::optional<E> &a, const std::optional<E> &b )
159{
160 return !a || !b || unitsAreEquivalent( *a, *b );
161}
162
163namespace detail::Units
164{
165 struct Empty {};
166
167 template <typename T>
168 concept Scalar = std::is_arithmetic_v<T> && !std::is_same_v<T, bool>;
169
170 template <typename T>
171 using MakeFloatingPoint = std::conditional_t<std::is_integral_v<typename VectorTraits<T>::BaseType>, typename VectorTraits<T>::template ChangeBaseType<float>, T>;
172}
173
174// Converts `value` from unit `from` to unit `to`. `value` is a scalar of a Vector2/3/4 or ImVec2/4 of them.
175// The return type matches `T` if it's not integral. If it's integral, its element type type is changed to `float`.
176// Returns min/max floating-point values unchanged.
177template <UnitEnum E, typename T>
178[[nodiscard]] detail::Units::MakeFloatingPoint<T> convertUnits( E from, E to, const T& value )
179{
180 using ReturnType = detail::Units::MakeFloatingPoint<T>;
181
182 bool needConversion = !unitsAreEquivalent( from, to );
183
184 if constexpr ( std::is_same_v<T, ReturnType> )
185 {
186 if ( !needConversion )
187 return value;
188 }
189
190 ReturnType ret{};
191
192 for ( int i = 0; i < VectorTraits<T>::size; i++ )
193 {
195
196 // Don't touch min/max floating-point values.
197 bool needElemConversion = needConversion;
198 if constexpr ( std::is_floating_point_v<typename VectorTraits<T>::BaseType> )
199 {
200 if ( needElemConversion &&
201 (
202 target <= std::numeric_limits<typename VectorTraits<T>::BaseType>::lowest() ||
203 target >= std::numeric_limits<typename VectorTraits<T>::BaseType>::max()
204 )
205 )
206 needElemConversion = false;
207 }
208
209 if ( needElemConversion )
210 target = target * getUnitInfo( from ).conversionFactor / getUnitInfo( to ).conversionFactor;
211 }
212
213 return ret;
214}
215
216// This version is a no-op if `from` or `to` is null.
217template <UnitEnum E, typename T>
218[[nodiscard]] detail::Units::MakeFloatingPoint<T> convertUnits( const std::optional<E> &from, const std::optional<E> &to, const T& value )
219{
220 if ( from && to )
221 return convertUnits( *from, *to, value );
222 else
224}
225
226// ---
227
228template <UnitEnum E>
229struct UnitToStringParams;
230
231// Returns the default parameters for converting a specific unit type to a string.
232// You can modify those with `setDefaultUnitParams()`.
233template <UnitEnum E>
235
236#define MR_X(E) extern template MRVIEWER_API const UnitToStringParams<E>& getDefaultUnitParams();
238#undef MR_X
239
240// Modifies the default parameters for converting a specific unit type to a string.
241template <UnitEnum E>
243
244#define MR_X(E) extern template MRVIEWER_API void setDefaultUnitParams( const UnitToStringParams<E>& newParams );
246#undef MR_X
247
248enum class NumberStyle
249{
250 normal, // Like %f.
251 distributePrecision, // Like %f, but the precision digits are spread across both decimal and integral parts.
252 exponential, // Like %e.
253 maybeExponential, // Like %g.
254};
255
256// This controls how the degrees are printed.
257enum class DegreesMode
258{
259 degrees, // Fractional degrees.
260 degreesMinutes, // Integral degrees, fractional arcminutes.
261 degreesMinutesSeconds, // Integral degrees and minutes, fractional arcseconds.
262 _count [[maybe_unused]],
263};
264[[nodiscard]] MRVIEWER_API std::string_view toString( DegreesMode mode );
265
266// Controls how a value with a unit is converted to a string.
267template <UnitEnum E>
269{
270 // The resulting string is wrapped in this.
271 // Do NOT use this for custom unit suffixes! Add them as actual units instead.
272 std::string_view decorationFormatString = "{}";
273
274 // --- Units:
275
276 // The measurement unit of the input value. If null, no conversion is performed.
278 // The measurement unit of the resulting string. If null, no conversion is performed, and the unit name is taken from `sourceUnit` if any.
279 std::optional<E> targetUnit = getDefaultUnitParams<E>().targetUnit;
280
281 // Whether to show the unit suffix.
283
284 // --- Precision:
285
286 // The output style. (Scientific notation or not, fixed-precision or not.)
288
289 // How many digits of precision.
291
292 // --- Other:
293
294 // If false, silently remove the minus sign before negative zeroes
295 // (including numbers that get rounded into negative zeroes with the current `precision` setting).
296 bool allowNegativeZero = getDefaultUnitParams<E>().allowNegativeZero;
297
298 // Use a pretty Unicode minus sign instead of the ASCII `-`.
299 bool unicodeMinusSign = getDefaultUnitParams<E>().unicodeMinusSign;
300
301 // If non-zero, this character is inserted between every three digits to the left of the decimal point.
302 char thousandsSeparator = getDefaultUnitParams<E>().thousandsSeparator;
303 // If non-zero, this character is inserted between every three digits to the right of the decimal point.
304 char thousandsSeparatorFrac = getDefaultUnitParams<E>().thousandsSeparatorFrac;
305
306 // If false, remove zero before the fractional point (`.5` instead of `0.5`).
308
309 // Remove trailing zeroes after the fractional point. If the point becomes the last symbol, remove the point too.
310 bool stripTrailingZeroes = getDefaultUnitParams<E>().stripTrailingZeroes;
311
312 // When printing degrees, this lets you display arcminutes and possibly arcseconds. Ignored for everything else.
313 std::conditional_t<std::is_same_v<E, AngleUnit>, DegreesMode, detail::Units::Empty> degreesMode = getDefaultUnitParams<E>().degreesMode;
314
315 // If you add new fields there, update the initializer for `defaultUnitToStringParams` in `MRUnits.cpp`.
316
317 friend bool operator==( const UnitToStringParams&, const UnitToStringParams& ) = default;
318};
319
320// The `std::variant` of `UnitToStringParams<E>` for all known `E`s (unit kinds).
321using VarUnitToStringParams = std::variant<
322 #define MR_X(E) , UnitToStringParams<E>
324 #undef MR_X
325>;
326
327// Converts value to a string, possibly converting it to a different unit.
328// By default, length is kept as is, while angles are converted from radians to the current UI unit.
329template <UnitEnum E, detail::Units::Scalar T>
330[[nodiscard]] MRVIEWER_API std::string valueToString( T value, const UnitToStringParams<E>& params = getDefaultUnitParams<E>() );
331
332#define MR_Y(T, E) extern template MRVIEWER_API std::string valueToString<E, T>( T value, const UnitToStringParams<E>& params );
333#define MR_X(E) DETAIL_MR_UNIT_VALUE_TYPES(MR_Y, E)
335#undef MR_X
336#undef MR_Y
337
338// This overload lets you select the unit kind at runtime.
339template <detail::Units::Scalar T>
340[[nodiscard]] MRVIEWER_API std::string valueToString( T value, const VarUnitToStringParams& params );
341
342#define MR_X(T, unused) extern template MRVIEWER_API std::string valueToString( T value, const VarUnitToStringParams& params );
344#undef MR_X
345
346// Guesses the number of digits of precision for fixed-point formatting of `value`.
347// Mostly for internal use.
348template <detail::Units::Scalar T>
349[[nodiscard]] MRVIEWER_API int guessPrecision( T value );
350
351// Guesses the number of digits of precision for fixed-point formatting of the min-max range.
352// If `min >= max`, always returns zero. Ignores min and/or max if they are the smallest of the largest representable value respectively.
353// Mostly for internal use.
354template <detail::Units::Scalar T>
355[[nodiscard]] MRVIEWER_API int guessPrecision( T min, T max );
356
357// Same but for vectors.
358template <typename T>
360[[nodiscard]] int guessPrecision( T value )
361{
362 int ret = 0;
363 for ( int i = 0; i < VectorTraits<T>::size; i++ )
364 ret = std::max( ret, guessPrecision( VectorTraits<T>::getElem( i, value ) ) );
365 return ret;
366}
367template <typename T>
368requires (VectorTraits<T>::size > 1 && detail::Units::Scalar<typename VectorTraits<T>::BaseType>)
369[[nodiscard]] int guessPrecision( T min, T max )
370{
371 int ret = 0;
372 for ( int i = 0; i < VectorTraits<T>::size; i++ )
373 ret = std::max( ret, guessPrecision( VectorTraits<T>::getElem( i, min ), VectorTraits<T>::getElem( i, max ) ) );
374 return ret;
375}
376
377#define MR_X(T, unused) \
378 extern template MRVIEWER_API int guessPrecision( T value ); \
379 extern template MRVIEWER_API int guessPrecision( T min, T max );
381#undef MR_X
382
383// Generates a printf-style format string for `value`, for use with ImGui widgets.
384// It has form "123.45 mm##%.6f" (the baked number, then `##` and some format string).
385// The `##...` part isn't printed, but we need it when ctrl+clicking the number, to show the correct number of digits.
386template <UnitEnum E, detail::Units::Scalar T>
387[[nodiscard]] MRVIEWER_API std::string valueToImGuiFormatString( T value, const UnitToStringParams<E>& params = getDefaultUnitParams<E>() );
388
389#define MR_Y(T, E) extern template MRVIEWER_API std::string valueToImGuiFormatString( T value, const UnitToStringParams<E>& params );
390#define MR_X(E) DETAIL_MR_UNIT_VALUE_TYPES(MR_Y, E)
392#undef MR_X
393#undef MR_Y
394
395// This overload lets you select the unit kind at runtime.
396template <detail::Units::Scalar T>
397[[nodiscard]] MRVIEWER_API std::string valueToImGuiFormatString( T value, const VarUnitToStringParams& params );
398
399#define MR_X(T, unused) extern template MRVIEWER_API std::string valueToImGuiFormatString( T value, const VarUnitToStringParams& params );
401#undef MR_X
402
403}
#define MR_TRIM_LEADING_COMMA(...)
Definition MRMacros.h:53
#define MR_X(E)
Definition MRUnits.h:146
#define DETAIL_MR_UNIT_VALUE_TYPES(X,...)
Definition MRUnits.h:108
#define DETAIL_MR_UNIT_ENUMS(X)
Definition MRUnits.h:100
Definition MRUnits.h:121
Definition MRUnits.h:168
std::conditional_t< std::is_integral_v< typename VectorTraits< T >::BaseType >, typename VectorTraits< T >::template ChangeBaseType< float >, T > MakeFloatingPoint
Definition MRUnits.h:171
PixelSizeUnit
Definition MRUnits.h:42
NumberStyle
Definition MRUnits.h:249
MRVIEWER_API int guessPrecision(T value)
Definition MRUnits.h:360
MRVIEWER_API std::string valueToString(T value, const UnitToStringParams< E > &params=getDefaultUnitParams< E >())
const UnitInfo & getUnitInfo(E unit)=delete
NoUnit
Definition MRUnits.h:19
bool unitsAreEquivalent(E a, E b)
Definition MRUnits.h:152
LengthUnit
Definition MRUnits.h:25
const UnitToStringParams< E > & getDefaultUnitParams()
void setDefaultUnitParams(const UnitToStringParams< E > &newParams)
MRVIEWER_API std::string valueToImGuiFormatString(T value, const UnitToStringParams< E > &params=getDefaultUnitParams< E >())
RatioUnit
Definition MRUnits.h:49
DegreesMode
Definition MRUnits.h:258
MovementSpeedUnit
Definition MRUnits.h:65
detail::Units::MakeFloatingPoint< T > convertUnits(E from, E to, const T &value)
Definition MRUnits.h:178
VolumeUnit
Definition MRUnits.h:83
AreaUnit
Definition MRUnits.h:74
MRMESH_API std::string_view toString(DimensionsVisualizePropertyType value)
TimeUnit
Definition MRUnits.h:57
InvLengthUnit
Definition MRUnits.h:92
AngleUnit
Definition MRUnits.h:34
std::variant< > VarUnitToStringParams
Definition MRUnits.h:321
Definition MRUnits.h:130
std::string_view unitSuffix
Definition MRUnits.h:139
std::string_view prettyName
Definition MRUnits.h:135
float conversionFactor
Definition MRUnits.h:133
Definition MRUnits.h:269
bool leadingZero
Definition MRUnits.h:307
int precision
Definition MRUnits.h:290
std::conditional_t< std::is_same_v< E, AngleUnit >, DegreesMode, detail::Units::Empty > degreesMode
Definition MRUnits.h:313
NumberStyle style
Definition MRUnits.h:287
std::optional< E > sourceUnit
Definition MRUnits.h:277
std::string_view decorationFormatString
Definition MRUnits.h:272
char thousandsSeparator
Definition MRUnits.h:302
bool unitSuffix
Definition MRUnits.h:282
bool allowNegativeZero
Definition MRUnits.h:296
char thousandsSeparatorFrac
Definition MRUnits.h:304
std::optional< E > targetUnit
Definition MRUnits.h:279
friend bool operator==(const UnitToStringParams &, const UnitToStringParams &)=default
bool stripTrailingZeroes
Definition MRUnits.h:310
bool unicodeMinusSign
Definition MRUnits.h:299
Definition MRMesh/MRVectorTraits.h:14
static constexpr int size
Definition MRMesh/MRVectorTraits.h:18
static constexpr auto && getElem(int i, U &&value)
Definition MRMesh/MRVectorTraits.h:28
T BaseType
Definition MRMesh/MRVectorTraits.h:17
Definition MRUnits.h:165