Skip to content

Commit db7b3cb

Browse files
authored
Merge pull request #69 from ichiro-its/feature/update-curve-function
[Feature] Add exponential mapping, sinusoidal mapping, lerp, and curve function
2 parents c7473d8 + 1664c6d commit db7b3cb

File tree

3 files changed

+214
-0
lines changed

3 files changed

+214
-0
lines changed

include/keisan/number.hpp

+16
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,16 @@ T map(
4949
const T & value, const T & source_min, const T & source_max,
5050
const T & target_min, const T & target_max);
5151

52+
template<typename T>
53+
T exponentialmap(
54+
const T & value, const T & source_min, const T & source_max,
55+
const T & target_min, const T & target_max);
56+
57+
template<typename T>
58+
T sinusoidalmap(
59+
const T & value, const T & source_min, const T & source_max,
60+
const T & target_min, const T & target_max);
61+
5262
template<typename T>
5363
T clamp(const T & value, const T & min, const T & max);
5464

@@ -64,6 +74,12 @@ T wrap(const T & value, const T & min, const T & max);
6474
template<typename T>
6575
T smooth(T value, T target, T ratio);
6676

77+
template<typename T>
78+
T curve(const T & value, const T & min, const T & max, const T & exponential);
79+
80+
template<typename T>
81+
T lerp(const T & start, const T & end, const T & rate);
82+
6783
} // namespace keisan
6884

6985
#include "keisan/number.impl.hpp"

include/keisan/number.impl.hpp

+42
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,32 @@ T map(
6161
scale(source_val - source_min, source_max - source_min, target_max - target_min);
6262
}
6363

64+
template<typename T>
65+
T exponentialmap(
66+
const T & value, const T & source_min, const T & source_max,
67+
const T & target_min, const T & target_max)
68+
{
69+
auto source_val = value;
70+
source_val = std::min(source_val, std::max(source_min, source_max));
71+
source_val = std::max(source_val, std::min(source_min, source_max));
72+
auto map_coeff = (target_min <= target_max) ? 1 : -1;
73+
auto normalized_val = (source_val - source_min) / (source_max - source_min);
74+
return map_coeff * std::pow(std::abs(target_max - target_min + map_coeff), normalized_val) + target_min - map_coeff;
75+
}
76+
77+
template<typename T>
78+
T sinusoidalmap(
79+
const T & value, const T & source_min, const T & source_max,
80+
const T & target_min, const T & target_max)
81+
{
82+
auto source_val = value;
83+
source_val = std::min(source_val, std::max(source_min, source_max));
84+
source_val = std::max(source_val, std::min(source_min, source_max));
85+
auto coeff = -(target_max - target_min) / 2;
86+
auto angle = (M_PI * (source_min - source_val)) / (source_min - source_max);
87+
return coeff * std::cos(angle) + (target_max + target_min) / 2;
88+
}
89+
6490
template<typename T>
6591
T clamp(const T & value, const T & min, const T & max)
6692
{
@@ -105,6 +131,22 @@ T smooth(T value, T target, T ratio)
105131
return ((1.0 - ratio) * value) + (ratio * target);
106132
}
107133

134+
template<typename T>
135+
T curve(const T & value, const T & min, const T & max, const T & exponential)
136+
{
137+
if (min == max) {
138+
return min;
139+
}
140+
auto val = clamp(value, min, max);
141+
return min + ((max - min) * (std::pow(val - min, exponential) / std::pow(max - min, exponential)));
142+
}
143+
144+
template<typename T>
145+
T lerp(const T & start, const T & end, const T & rate)
146+
{
147+
return (start + ((end - start) * rate));
148+
}
149+
108150
} // namespace keisan
109151

110152
#endif // KEISAN__NUMBER_IMPL_HPP_

test/number_test.cpp

+156
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,114 @@ TEST(NumberTest, MapFloatingPoint)
120120
"5.2 + 7.0 = 12.2";
121121
}
122122

123+
TEST(NumberTest, ExponentialMapIntegral)
124+
{
125+
EXPECT_EQ(ksn::exponentialmap(5, 2, 4, 0, 1), 1) <<
126+
"clamp(5, 2, 4) = 4\n"
127+
"map_coeff (0 <= 1) = 1\n"
128+
"normalized_val = (4 - 2)/(4 - 2) = 1\n"
129+
"1 * pow(|1 - 0 + 1|, 1) + 0 - 1 = 1\n";
130+
131+
EXPECT_EQ(ksn::exponentialmap(-5, -4, -2, 0, 1), 0) <<
132+
"clamp(-5, -4, -2) = -4\n"
133+
"map_coeff (0 <= 1) = 1\n"
134+
"normalized_val = (-4 - (-4))/( -2 - (-4)) = 0\n"
135+
"1 * pow(|1 - 0 + 1|, 0) + 0 - 1 = 0\n";
136+
}
137+
138+
TEST(NumberTest, ExponentialMapFloatingPoint)
139+
{
140+
EXPECT_DOUBLE_EQ(ksn::exponentialmap(5.2, 1.8, 4.9, 0.4, 2.4), 2.4) <<
141+
"clamp(5.2, 1.8, 4.9) = 4.9\n"
142+
"map_coeff (0.4 <= 2.4) = 1\n"
143+
"normalized_val = (4.9 - 1.8) / (4.9 - 1.8) = 1\n"
144+
"1 * pow(|2.4 - 0.4 + 1|, 1) + 0.4 - 1 = 2.4\n";
145+
146+
EXPECT_DOUBLE_EQ(ksn::exponentialmap(-6.9, -4.2, -2.0, 0.0, 1.3), 0) <<
147+
"clamp(-6.9, -4.2, -2.0) = -4.2\n"
148+
"map_coeff (0.0 <= 1.3) = 1\n"
149+
"normalized_val = (-4.2 - (-4.2)) / (-2.0 - (-4.2)) = 0\n"
150+
"1 * pow(|1.3 - 0.0 + 1|, 0) + 0.0 - 1 = 0\n";
151+
152+
EXPECT_NEAR(ksn::exponentialmap(10.2, 3.1, 14.7, -2.2, 2.2), -0.393, 0.001) <<
153+
"clamp(10.2, 3.1, 14.7) = 10.2\n"
154+
"map_coeff (-2.2 <= 2.2) = 1\n"
155+
"normalized_val = (10.2 - 3.1) / (14.7 - 3.1) = 0.6121\n"
156+
"1 * pow(|2.2 - (-2.2) + 1|, 0.6121) + (-2.2) - 1 ~ -0.393\n";
157+
158+
EXPECT_NEAR(ksn::exponentialmap(10.2, 3.1, 14.7, 2.2, -2.2), 0.393, 0.001) <<
159+
"clamp(10.2, 3.1, 14.7) = 10.2\n"
160+
"map_coeff (2.2 <= -2.2) = -1\n"
161+
"normalized_val = (10.2 - 3.1) / (14.7 - 3.1) = 0.6121\n"
162+
"-1 * pow(|-2.2 - 2.2 + (-1)|, 0.6121) + 2.2 - (-1) ~ 0.393\n";
163+
164+
EXPECT_NEAR(ksn::exponentialmap(10.2, 14.7, 3.1, 2.2, -2.2), 1.276, 0.001) <<
165+
"clamp(10.2, 14.7, 3.1) = 10.2\n"
166+
"map_coeff (2.2 <= -2.2) = -1\n"
167+
"normalized_val = (10.2 - 14.7) / (3.1 - 14.7) = 0.3879\n"
168+
"-1 * pow(|-2.2 - 2.2 + (-1)|, 0.3879) + 2.2 - (-1) ~ 1.276\n";
169+
170+
EXPECT_NEAR(ksn::exponentialmap(10.2, 14.7, 3.1, -2.2, 2.2), -1.276, 0.001) <<
171+
"clamp(10.2, 14.7, 3.1) = 10.2\n"
172+
"map_coeff (-2.2 <= 2.2) = 1\n"
173+
"normalized_val = (10.2 - 14.7) / (3.1 - 14.7) = 0.3879\n"
174+
"1 * pow(|2.2 - (-2.2) + 1|, 0.3879) + (-2.2) - 1 ~ -1.276\n";
175+
}
176+
177+
TEST(NumberTest, SinusoidalMapIntegral)
178+
{
179+
EXPECT_EQ(ksn::sinusoidalmap(10, 2, 4, 0, 2), 2) <<
180+
"clamp(10, 2, 4) = 4\n"
181+
"coeff = -(2 - 0) / 2 = -1\n"
182+
"angle = (M_PI * (2 - 4)) / (2 - 4) = M_PI\n"
183+
"-1 * cos(M_PI) + (2 + 0) / 2 = 2\n";
184+
185+
EXPECT_EQ(ksn::sinusoidalmap(-5, -4, -2, 0, 1), 0) <<
186+
"clamp(-5, -4, -2) = -4\n"
187+
"coeff = -(1 - 0) / 2 = -0.5\n"
188+
"angle = (M_PI * (-4 - (-4))) / (-4 - (-2)) = 0\n"
189+
"-0.5 * cos(0) + (1 + 0) / 2 = 0\n";
190+
}
191+
192+
TEST(NumberTest, SinusoidalMapFloatingPoint)
193+
{
194+
EXPECT_DOUBLE_EQ(ksn::sinusoidalmap(5.2, 1.8, 4.9, 0.4, 2.4), 2.4) <<
195+
"clamp(5.2, 1.8, 4.9) = 4.9\n"
196+
"coeff = -(2.4 - 0.4) / 2 = -1\n"
197+
"angle = (M_PI * (1.8 - 4.9)) / (1.8 - 4.9) = M_PI\n"
198+
"-1 * cos(M_PI) + (2.4 + 0.4) / 2 = 2.4\n";
199+
200+
EXPECT_DOUBLE_EQ(ksn::sinusoidalmap(-6.9, -4.2, -2.0, 0.0, 1.3), 0) <<
201+
"clamp(-6.9, -4.2, -2.0) = -4.2\n"
202+
"coeff = -(1.3 - 0.0) / 2 = -0.65\n"
203+
"angle = (M_PI * (-4.2 - (-4.2))) / (-4.2 - (-2.0)) = 0\n"
204+
"-0.65 * cos(0) + (1.3 + 0.0) / 2 = 0.0\n";
205+
206+
EXPECT_NEAR(ksn::sinusoidalmap(10.2, 3.1, 14.7, -2.2, 2.2), 0.759, 0.001) <<
207+
"clamp(10.2, 3.1, 14.7) = 10.2\n"
208+
"coeff = -(2.2 - (-2.2)) / 2 = -2.2\n"
209+
"angle = (M_PI * (3.1 - 10.2)) / (3.1 - 14.7) ~ 1.923\n"
210+
"-2.2 * cos(1.923) + (2.2 + (-2.2)) / 2 ~ 0.759\n";
211+
212+
EXPECT_NEAR(ksn::sinusoidalmap(10.2, 3.1, 14.7, 2.2, -2.2), -0.759, 0.001) <<
213+
"clamp(10.2, 3.1, 14.7) = 10.2\n"
214+
"coeff = -(-2.2 - 2.2) / 2 = 2.2\n"
215+
"angle = (M_PI * (3.1 - 10.2)) / (3.1 - 14.7) ~ 1.923\n"
216+
"2.2 * cos(1.923) + (-2.2 + 2.2) / 2 ~ -0.759\n";
217+
218+
EXPECT_NEAR(ksn::sinusoidalmap(10.2, 14.7, 3.1, 2.2, -2.2), 0.759, 0.001) <<
219+
"clamp(10.2, 14.7, 3.1) = 10.2\n"
220+
"coeff = -(-2.2 - 2.2) / 2 = 2.2\n"
221+
"angle = (M_PI * (14.7 - 10.2)) / (14.7 - 3.1) = 1.219\n"
222+
"2.2 * cos(1.219) + (2.2 + (-2.2)) / 2 ~ 0.759\n";
223+
224+
EXPECT_NEAR(ksn::sinusoidalmap(10.2, 14.7, 3.1, -2.2, 2.2), -0.759, 0.001) <<
225+
"clamp(10.2, 14.7, 3.1) = 10.2\n"
226+
"coeff = -(2.2 - (-2.2)) / 2 = -2.2\n"
227+
"angle = (M_PI * (14.7 - 10.2)) / (14.7 - 3.1) = 1.219\n"
228+
"-2.2 * cos(1.219) + (-2.2 + 2.2) / 2 ~ -0.759\n";
229+
}
230+
123231
TEST(NumberTest, ClampIntegral)
124232
{
125233
EXPECT_EQ(ksn::clamp(5, 0, 10), 5) << "0 <= 5 <= 10";
@@ -183,6 +291,54 @@ TEST(NumberTest, WrapFloatingPoint)
183291
"0.2 + (1.1) = 1.3";
184292
}
185293

294+
TEST(NumberTest, CurveIntegral)
295+
{
296+
EXPECT_EQ(ksn::curve(13, 10, 15, 2), 11) <<
297+
"clamp(13, 10, 15) = 13\n"
298+
"10 + (15 - 10) * ((13 - 10) / (15-10))^2 = 11";
299+
300+
EXPECT_EQ(ksn::curve(31, 10, 15, 2), 15) <<
301+
"clamp(31, 10, 15) = 15\n"
302+
"10 + (15 - 10) * ((15 - 10) / (15-10))^2 = 15";
303+
304+
EXPECT_EQ(ksn::curve(4, 10, 15, 2), 10) <<
305+
"clamp(4, 10, 15) = 10\n"
306+
"10 + (15 - 10) * ((10 - 10) / (15-10))^2 = 10";
307+
}
308+
309+
TEST(NumberTest, CurveFloatingPoint)
310+
{
311+
EXPECT_EQ(ksn::curve(13.0, 10.0, 15.0, 2.0), 11.8) <<
312+
"clamp(13, 10, 15) = 13\n"
313+
"10 + (15 - 10) * ((13 - 10) / (15-10))^2 = 11.8";
314+
315+
EXPECT_EQ(ksn::curve(31.0, 10.0, 15.0, 2.0), 15.0) <<
316+
"clamp(31, 10, 15) = 15\n"
317+
"10 + (15 - 10) * ((15 - 10) / (15-10))^2 = 15";
318+
319+
EXPECT_EQ(ksn::curve(4.0, 10.0, 15.0, 2.0), 10.0) <<
320+
"clamp(4, 10, 15) = 10\n"
321+
"10 + (15 - 10) * ((10 - 10) / (15-10))^2 = 10";
322+
}
323+
324+
TEST(NumberTest, LerpIntegral)
325+
{
326+
EXPECT_EQ(ksn::lerp(0, 10, 0), 0) <<
327+
"0 + (10 - 0) * 0 = 0";
328+
329+
EXPECT_EQ(ksn::lerp(5, 15, 1), 15) <<
330+
"5 + (15 - 5) * 1 = 15";
331+
}
332+
333+
TEST(NumberTest, LerpFloatingPoint)
334+
{
335+
EXPECT_DOUBLE_EQ(ksn::lerp(0.0, 10.0, 0.5), 5.0) <<
336+
"0 + (10 - 0) * 0.5 = 0";
337+
338+
EXPECT_DOUBLE_EQ(ksn::lerp(5.0, 15.0, 0.25), 7.5) <<
339+
"5 + (15 - 5) * 0.25 = 15";
340+
}
341+
186342
#define ASSERT_SMOOTH_NEAR(SOURCE, TARGET, RATIO, ...) \
187343
{ \
188344
double _source = SOURCE; \

0 commit comments

Comments
 (0)