Ark Server API (ASA) - Wiki
Loading...
Searching...
No Matches
UnitConversion.inl
Go to the documentation of this file.
1// Copyright Epic Games, Inc. All Rights Reserved.
2
3#pragma once
4
5/** Inline file for UnitConversion.h to separate the implementation from the header */
6
7
8#include "CoreTypes.h"
9#include "CoreFwd.h"
10#include "Misc/OptionalFwd.h"
11
12struct FMath;
13struct FUnitConversion;
14enum class EUnit : uint8;
15enum class EUnitType;
16template<typename NumericType> struct FNumericUnit;
17template<typename ValueType, typename ErrorType> class TValueOrError;
18
20{
21 /** Find the common quantization factor for the specified distance unit. Quantizes to Meters. */
23 /** Find the common quantization factor for the specified angular unit. Quantizes to Degrees. */
25 /** Find the common quantization factor for the specified speed unit. Quantizes to km/h. */
27 /** Find the common quantization factor for the specified temperature unit. Quantizes to Kelvin. */
29 /** Find the common quantization factor for the specified mass unit. Quantizes to Grams. */
31 /** Find the common quantization factor for the specified force unit. Quantizes to Newtons. */
33 /** Find the common quantization factor for the specified frequency unit. Quantizes to KHz. */
35 /** Find the common quantization factor for the specified data size unit. Quantizes to MB. */
37 /** Find the common quantization factor for the specified time unit. Quantizes to hours. */
39 /** Find the common quantization factor for the specified multiplier unit. Quantizes to 1.0 scale (where 1.0 == 100%). */
41
42 /** Attempt to parse an expression into a numeric unit */
44
45 /** Structure used to define the factor required to get from one unit type to the next. */
47 {
48 /** The unit to which this factor applies */
50 /** The factor by which to multiply to get to the next unit in this range */
51 float Factor;
52
53 /** Constructor */
54 FQuantizationInfo(EUnit InUnit, float InFactor) : Units(InUnit), Factor(InFactor) {}
55 };
56
57 /** Find the quantization bounds for the specified unit, if any */
59
60} // namespace UnitConversion
61
62/** Convert the specified number from one unit to another. Does nothing if the units are incompatible. */
63template<typename T>
64T FUnitConversion::Convert(T InValue, EUnit From, EUnit To)
65{
66 using namespace UnitConversion;
67
68 if (!AreUnitsCompatible(From, To))
69 {
70 return InValue;
71 }
72 else if (From == EUnit::Unspecified || To == EUnit::Unspecified)
73 {
74 return InValue;
75 }
76
77 switch(FUnitConversion::GetUnitType(From))
78 {
79 case EUnitType::Distance: return InValue * DistanceUnificationFactor(From) * (1.0 / DistanceUnificationFactor(To));
80 case EUnitType::Angle: return InValue * AngleUnificationFactor(From) * (1.0 / AngleUnificationFactor(To));
81 case EUnitType::Speed: return InValue * SpeedUnificationFactor(From) * (1.0 / SpeedUnificationFactor(To));
82 case EUnitType::Mass: return InValue * MassUnificationFactor(From) * (1.0 / MassUnificationFactor(To));
83 case EUnitType::Force: return InValue * ForceUnificationFactor(From) * (1.0 / ForceUnificationFactor(To));
84 case EUnitType::Frequency: return InValue * FrequencyUnificationFactor(From) * (1.0 / FrequencyUnificationFactor(To));
85 case EUnitType::DataSize: return InValue * DataSizeUnificationFactor(From) * (1.0 / DataSizeUnificationFactor(To));
86 case EUnitType::LuminousFlux: return InValue;
87 case EUnitType::Time: return InValue * TimeUnificationFactor(From) * (1.0 / TimeUnificationFactor(To));
88 case EUnitType::Multipliers: return InValue * MultiplierUnificationFactor(From) * (1.0 / MultiplierUnificationFactor(To));
89 // Temperature conversion is not just a simple multiplication, so needs special treatment
90 case EUnitType::Temperature:
91 {
92 double NewValue = InValue;
93 // Put it into kelvin
94 switch (From)
95 {
96 case EUnit::Celsius: NewValue = NewValue + 273.15f; break;
97 case EUnit::Farenheit: NewValue = (NewValue + 459.67f) * 5.f/9.f; break;
98 default: break;
99 }
100 // And out again
101 switch (To)
102 {
103 case EUnit::Celsius: return NewValue - 273.15f;
104 case EUnit::Farenheit: return NewValue * 9.f/5.f - 459.67f;
105 default: return NewValue;
106 }
107 }
108
109 default: return InValue;
110 }
111}
112
113template<typename T>
114FNumericUnit<T> FUnitConversion::QuantizeUnitsToBestFit(T Value, EUnit Units)
115{
116 auto OptionalBounds = UnitConversion::GetQuantizationBounds(Units);
117 if (!OptionalBounds.IsSet())
118 {
119 return FNumericUnit<T>(Value, Units);
120 }
121
122 const auto& Bounds = *OptionalBounds.GetValue();
123
124 const int32 CurrentUnitIndex = (uint8)Units - (uint8)Bounds[0].Units;
125
126 EUnit NewUnits = Units;
127 double NewValue = Value;
128
129 if (FMath::Abs(NewValue) > 1)
130 {
131 // Large number? Try larger units
132 for (int32 Index = CurrentUnitIndex; Index < Bounds.Num(); ++Index)
133 {
134 if (Bounds[Index].Factor == 0)
135 {
136 break;
137 }
138
139 const auto Tmp = NewValue / Bounds[Index].Factor;
140
141 if (FMath::Abs(Tmp) < 1)
142 {
143 break;
144 }
145
146 NewValue = Tmp;
147 NewUnits = Bounds[Index + 1].Units;
148 }
149 }
150 else if (NewValue != 0)
151 {
152 // Small number? Try smaller units
153 for (int32 Index = CurrentUnitIndex - 1; Index >= 0; --Index)
154 {
155 NewValue *= Bounds[Index].Factor;
156 NewUnits = Bounds[Index].Units;
157
158 if (FMath::Abs(NewValue) > 1)
159 {
160 break;
161 }
162 }
163 }
164
165 return FNumericUnit<T>(NewValue, NewUnits);
166}
167
168template<typename T>
169EUnit FUnitConversion::CalculateDisplayUnit(T Value, EUnit InUnits)
170{
171 if (InUnits == EUnit::Unspecified)
172 {
173 return EUnit::Unspecified;
174 }
175
176 const TArray<EUnit>& DisplayUnits = Settings().GetDisplayUnits(GetUnitType(InUnits));
177 if (DisplayUnits.Num() == 0)
178 {
179 return QuantizeUnitsToBestFit(Value, InUnits).Units;
180 }
181 else if (DisplayUnits.Num() == 1)
182 {
183 return DisplayUnits[0];
184 }
185
186 // If the value we were given was 0, change it to something we can actually work with
187 if (Value == 0)
188 {
189 Value = 1;
190 }
191
192 int32 BestIndex = 0;
193 for (int32 Index = 0; Index < DisplayUnits.Num() - 1; ++Index)
194 {
195 double This = Convert(Value, InUnits, DisplayUnits[Index]);
196 double Next = Convert(Value, InUnits, DisplayUnits[Index + 1]);
197
198 if (FMath::Abs(FMath::LogX(10.0f, (float)This)) < FMath::Abs(FMath::LogX(10.0f, (float)Next)))
199 {
200 BestIndex = Index;
201 }
202 else
203 {
204 BestIndex = Index + 1;
205 }
206 }
207
208 return DisplayUnits[BestIndex];
209}
210
211template<typename NumericType>
212FNumericUnit<NumericType>::FNumericUnit()
213 : Units(EUnit::Unspecified)
214{}
215
216template<typename NumericType>
217FNumericUnit<NumericType>::FNumericUnit(const NumericType& InValue, EUnit InUnits)
218 : Value(InValue), Units(InUnits)
219{}
220
221template<typename NumericType>
222FNumericUnit<NumericType>::FNumericUnit(const FNumericUnit& Other)
223 : Units(EUnit::Unspecified)
224{
225 (*this) = Other;
226}
227
228template<typename NumericType>
229FNumericUnit<NumericType>& FNumericUnit<NumericType>::operator=(const FNumericUnit& Other)
230{
231 CopyValueWithConversion(Other);
232 return *this;
233}
234
235/** Templated Copy construction/assignment from differing numeric types. Relies on implicit conversion of the two numeric types. */
236template<typename NumericType> template<typename OtherType>
237FNumericUnit<NumericType>::FNumericUnit(const FNumericUnit<OtherType>& Other)
238{
239 (*this) = Other;
240}
241
242template<typename NumericType> template<typename OtherType>
243FNumericUnit<NumericType>& FNumericUnit<NumericType>::operator=(const FNumericUnit<OtherType>& Other)
244{
245 CopyValueWithConversion(Other);
246 return *this;
247}
248
249/** Convert this quantity to a different unit */
250template<typename NumericType>
251TOptional<FNumericUnit<NumericType>> FNumericUnit<NumericType>::ConvertTo(EUnit ToUnits) const
252{
253 if (Units == EUnit::Unspecified)
254 {
255 return FNumericUnit(Value, ToUnits);
256 }
257 else if (FUnitConversion::AreUnitsCompatible(Units, ToUnits))
258 {
259 return FNumericUnit<NumericType>(FUnitConversion::Convert(Value, Units, ToUnits), ToUnits);
260 }
261
262 return TOptional<FNumericUnit<NumericType>>();
263}
264
265template<typename NumericType>
266FNumericUnit<NumericType> FNumericUnit<NumericType>::QuantizeUnitsToBestFit() const
267{
268 return FUnitConversion::QuantizeUnitsToBestFit(Value, Units);
269}
270
271template<typename NumericType>
272TValueOrError<FNumericUnit<NumericType>, FText> FNumericUnit<NumericType>::TryParseExpression(const TCHAR* InExpression, EUnit InDefaultUnit, const FNumericUnit<NumericType>& InExistingValue)
273{
274 TValueOrError<FNumericUnit<double>, FText> Result = UnitConversion::TryParseExpression(InExpression, InDefaultUnit, InExistingValue);
275 if (Result.IsValid())
276 {
277 const auto& Value = Result.GetValue();
278 return MakeValue(FNumericUnit<NumericType>((NumericType)Value.Value, Value.Units));
279 }
280
281 return MakeError(Result.GetError());
282}
283
284template<typename NumericType>
285TOptional<FNumericUnit<NumericType>> FNumericUnit<NumericType>::TryParseString(const TCHAR* InSource)
286{
287 TOptional<FNumericUnit<NumericType>> Result;
288 if (!InSource || !*InSource)
289 {
290 return Result;
291 }
292
293 const TCHAR* NumberEnd = nullptr;
294 if (!ExtractNumberBoundary(InSource, NumberEnd))
295 {
296 return Result;
297 }
298
299 NumericType NewValue;
300 LexFromString(NewValue, InSource);
301
302 // Now parse the units
303 while(FChar::IsWhitespace(*NumberEnd)) ++NumberEnd;
304
305 if (*NumberEnd == '\0')
306 {
307 // No units
308 Result.Emplace(NewValue);
309 }
310 else
311 {
312 // If the string specifies units, they must map to something that exists for this function to succeed
313 auto NewUnits = FUnitConversion::UnitFromString(NumberEnd);
314 if (NewUnits)
315 {
316 Result.Emplace(NewValue, NewUnits.GetValue());
317 }
318 }
319
320 return Result;
321}
322
323/** Copy another unit into this one, taking account of its units, and applying necessary conversion */
324template<typename NumericType> template<typename OtherType>
325void FNumericUnit<NumericType>::CopyValueWithConversion(const FNumericUnit<OtherType>& Other)
326{
327 if (Units != EUnit::Unspecified && Other.Units != EUnit::Unspecified)
328 {
329 if (Units == Other.Units)
330 {
331 Value = Other.Value;
332 }
333 else if (FUnitConversion::AreUnitsCompatible(Units, Other.Units))
334 {
335 Value = FUnitConversion::Convert(Other.Value, Other.Units, Units);
336 }
337 else
338 {
339 // Invalid conversion - assignment invalid
340 }
341 }
342 else
343 {
344 // If our units haven't been specified, we take on the units of the rhs
345 if (Units == EUnit::Unspecified)
346 {
347 // This is the only time we ever change units. Const_cast is 'acceptible' here since the units haven't been specified yet.
348 const_cast<EUnit&>(Units) = Other.Units;
349 }
350
351 Value = Other.Value;
352 }
353}
354
355template<typename NumericType>
356bool FNumericUnit<NumericType>::ExtractNumberBoundary(const TCHAR* Start, const TCHAR*& End)
357{
358 while(FChar::IsWhitespace(*Start)) ++Start;
359
360 End = Start;
361 if (*End == '-' || *End == '+')
362 {
363 End++;
364 }
365
366 bool bHasDot = false;
367 while (FChar::IsDigit(*End) || *End == '.')
368 {
369 if (*End == '.')
370 {
371 if (bHasDot)
372 {
373 return false;
374 }
375 bHasDot = true;
376 }
377 ++End;
378 }
379
380 return true;
381}
382
383template <typename NumericType>
384struct TNumericLimits<FNumericUnit<NumericType>> : public TNumericLimits<NumericType>
385{ };
386
387template<typename T>
388FString LexToString(const FNumericUnit<T>& NumericUnit)
389{
390 FString String = LexToString(NumericUnit.Value);
391 String += TEXT(" ");
392 String += FUnitConversion::GetUnitDisplayString(NumericUnit.Units);
393
394 return String;
395}
396
397template<typename T>
398FString LexToSanitizedString(const FNumericUnit<T>& NumericUnit)
399{
400 FString String = LexToSanitizedString(NumericUnit.Value);
401 String += TEXT(" ");
402 String += FUnitConversion::GetUnitDisplayString(NumericUnit.Units);
403
404 return String;
405}
406
407template<typename T>
408void LexFromString(FNumericUnit<T>& OutValue, const TCHAR* String)
409{
410 auto Parsed = FNumericUnit<T>::TryParseString(String);
411 if (Parsed)
412 {
413 OutValue = Parsed.GetValue();
414 }
415}
416
417template<typename T>
418bool LexTryParseString(FNumericUnit<T>& OutValue, const TCHAR* String)
419{
420 auto Parsed = FNumericUnit<T>::TryParseString(String);
421 if (Parsed)
422 {
423 OutValue = Parsed.GetValue();
424 return true;
425 }
426
427 return false;
428}
EUnitType
Definition Enums.h:23284
EUnit
Definition Enums.h:23304
#define TEXT(x)
Definition Platform.h:1108
FString LexToSanitizedString(const FNumericUnit< T > &NumericUnit)
bool LexTryParseString(FNumericUnit< T > &OutValue, const TCHAR *String)
void LexFromString(FNumericUnit< T > &OutValue, const TCHAR *String)
FString LexToString(const FNumericUnit< T > &NumericUnit)
CORE_API double TemperatureUnificationFactor(EUnit From)
CORE_API double TimeUnificationFactor(EUnit From)
CORE_API double MassUnificationFactor(EUnit From)
CORE_API double DataSizeUnificationFactor(EUnit From)
CORE_API double AngleUnificationFactor(EUnit From)
CORE_API double FrequencyUnificationFactor(EUnit From)
CORE_API double DistanceUnificationFactor(EUnit From)
CORE_API double SpeedUnificationFactor(EUnit From)
CORE_API double MultiplierUnificationFactor(EUnit From)
CORE_API double ForceUnificationFactor(EUnit From)
FQuantizationInfo(EUnit InUnit, float InFactor)