Photon Engine 2.0.0-beta
A physically based renderer.
Loading...
Searching...
No Matches
radical_inverse.h
Go to the documentation of this file.
1#pragma once
2
3#include "Math/math.h"
4
5#include <Common/assertion.h>
6
7#include <concepts>
8#include <type_traits>
9#include <climits>
10#include <limits>
11
12namespace ph::math
13{
14
15namespace radical_inverse_detail
16{
17
20template<std::integral Value, std::integral Base>
21inline constexpr Value reverse_limit(const Value nextDigit, const Base base)
22{
23 return (std::numeric_limits<Value>::max() - nextDigit) / base;
24}
25
30template<std::floating_point Result, std::integral Base>
31inline constexpr auto num_meaningful_digits(const Base base)
32{
33 const Result rcpBase = Result(1) / base;
34
35 Base numDigits = 0;
36 Result valueScaler = 1;
37 while(1 - (base - 1) * valueScaler < 1)
38 {
39 ++numDigits;
40 valueScaler *= rcpBase;
41 }
42 return numDigits;
43}
44
45template<std::floating_point Result, std::unsigned_integral Value>
46inline constexpr Result base_2_scaler()
47{
48 constexpr auto numBits = sizeof(Value) * CHAR_BIT;
49 if constexpr(numBits == 8)
50 {
51 return Result(0x1p-8);
52 }
53 else if constexpr(numBits == 16)
54 {
55 return Result(0x1p-16);
56 }
57 else if constexpr(numBits == 32)
58 {
59 return Result(0x1p-32);
60 }
61 else if constexpr(numBits == 64)
62 {
63 return Result(0x1p-64);
64 }
65 else
66 {
67 PH_STATIC_ASSERT_DEPENDENT_FALSE(Result,
68 "Unexpected size of `Value`.");
69 }
70}
71
72}// end namespace radical_inverse_detail
73
78template<auto BASE, std::floating_point Result, std::integral Value>
79inline Result radical_inverse(const Value value)
80{
81 static_assert(std::integral<decltype(BASE)>,
82 "`BASE` must be an integer.");
83 static_assert(BASE >= 2);
84
85 // Special case for unsigned value of base-2
86 if constexpr(BASE == 2 && std::unsigned_integral<Value>)
87 {
88 constexpr auto base2Scaler = radical_inverse_detail::base_2_scaler<Result, Value>();
89
90 return math::reverse_bits(value) * base2Scaler;
91 }
92 else
93 {
94 constexpr Result rcpBase = Result(1) / BASE;
95
96 // Being safe as we assume a max possible next digit `BASE - 1`, could potentially do better
97 // by checking overflow in the loop (just want to be faster here).
98 constexpr Value maxSafeValue = radical_inverse_detail::reverse_limit(BASE - 1, BASE);
99
100 // Extract digits from `value` and reassemble them reversely as `unscaledReversedValue`
101 Value currentValue = value;
102 Value unscaledReversedValue = 0;
103 Result scaler = 1;
104 while(currentValue > 0 && unscaledReversedValue <= maxSafeValue)
105 {
106 const Value quotient = currentValue / BASE;
107 const Value remainder = currentValue - quotient * BASE;
108
109 unscaledReversedValue = unscaledReversedValue * BASE + remainder;
110 scaler *= rcpBase;
111
112 currentValue = quotient;
113 }
114
115 return math::clamp<Result>(unscaledReversedValue * scaler, 0, 1);
116 }
117}
118
124template<auto BASE, std::floating_point Result, std::integral Value, typename DigitPermuter>
125inline Result radical_inverse_permuted(const Value dimIndex, const Value value, DigitPermuter permuter)
126{
127 static_assert(std::integral<decltype(BASE)>,
128 "`BASE` must be an integer.");
129 static_assert(BASE >= 2);
130 static_assert(requires (DigitPermuter p)
131 {
132 { p.template operator()<BASE>(Value{}, Value{})} -> std::same_as<Value>;
133 },
134 "`DigitPermuter` must be callable with `<BASE>(dimIndex, digit)` and return a permuted digit.");
135
136 constexpr Result rcpBase = Result(1) / BASE;
137 constexpr Value maxDigits = radical_inverse_detail::num_meaningful_digits<Result>(BASE);
138
139 // Being safe as we assume a max possible next digit `BASE - 1`, could potentially do better
140 // by checking overflow in the loop (just want to be faster here).
141 constexpr Value maxSafeValue = radical_inverse_detail::reverse_limit(BASE - 1, BASE);
142
143 // Extract digits from `value` and reassemble them reversely as `unscaledReversedValue`
144 // Cannot use the condition `currentValue > 0` to terminate the loop, since `permuter` may
145 // produce a non-zero digit from zero.
146 Value currentValue = value;
147 Value unscaledReversedValue = 0;
148 Value numReversedDigits = 0;
149 Result scaler = 1;
150 while(numReversedDigits < maxDigits && unscaledReversedValue <= maxSafeValue)
151 {
152 const Value quotient = currentValue / BASE;
153 const Value remainder = currentValue - quotient * BASE;
154 const Value permutedRemainder = permuter.template operator()<BASE>(dimIndex, remainder);
155
156 unscaledReversedValue = unscaledReversedValue * BASE + permutedRemainder;
157 scaler *= rcpBase;
158 ++numReversedDigits;
159
160 currentValue = quotient;
161 }
162
163 return math::clamp<Result>(unscaledReversedValue * scaler, 0, 1);
164}
165
166}// end namespace ph::math
Miscellaneous math utilities.
constexpr Value reverse_limit(const Value nextDigit, const Base base)
The max value x such that x * base + nextDigit will not overflow its type.
Definition radical_inverse.h:21
constexpr auto num_meaningful_digits(const Base base)
Number of reversed digits that will have effect on the type Result. Due to the limited precision of f...
Definition radical_inverse.h:31
constexpr Result base_2_scaler()
Definition radical_inverse.h:46
Math functions and utilities.
Definition TransformInfo.h:10
Result radical_inverse(const Value value)
Compute radical inverse of a value.
Definition radical_inverse.h:79
T reverse_bits(const T value)
Get an integral value with reversed bits.
Definition math.h:448
Result radical_inverse_permuted(const Value dimIndex, const Value value, DigitPermuter permuter)
Same as radical_inverse(), with permutation ability. It is guaranteed that no more than radical_inver...
Definition radical_inverse.h:125
T clamp(const T value, const T lowerBound, const T upperBound)
Clamps a value to [lowerBound, upperBound]. None of value, lowerBound and upperBound can be NaN,...
Definition math.h:77