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