Skip to content

Commit 46f3de1

Browse files
committed
GDscript builtin functions to rust
code cleanup added range for lerp functions, replaced fmod fm with rem fixed fmt fixed some tests added Range and RangeInclusive added wrapi and wrapf tests added wrapi and wrapf tests repaced tests with doc tests repaced tests with doc tests
1 parent 549af60 commit 46f3de1

File tree

3 files changed

+381
-1
lines changed

3 files changed

+381
-1
lines changed

gdnative-core/src/globalscope.rs

+379
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,379 @@
1+
//! `fmod` is `%` operator on `f32`;
2+
//! ```rust
3+
//! use std::ops::Rem;
4+
//! assert_eq!(f32::rem(12.0, 10.0), 2.0);
5+
//! ```
6+
7+
use std::f32::consts::TAU;
8+
use std::ops::Rem;
9+
use std::ops::{Range, RangeInclusive};
10+
11+
const CMP_EPSILON: f32 = 0.00001;
12+
13+
/// Converts a 2D point expressed in the `cartesian` coordinate system (X and Y axis)
14+
/// to the `polar` coordinate system (a distance from the origin and an angle `(in radians)`).
15+
#[inline]
16+
pub fn cartasian2polar(x: f32, y: f32) -> (f32, f32) {
17+
((x * x + y * y).sqrt(), y.atan2(x))
18+
}
19+
20+
/// Converts from `decibels` to linear energy (audio).
21+
#[inline]
22+
pub fn db2linear(db: f32) -> f32 {
23+
f32::exp(db * 0.115_129_255)
24+
}
25+
26+
/// Returns the `position` of the first `non-zero` digit, after the decimal point.
27+
/// Note that the `maximum` return `value` is `10`, which is a design decision in the implementation.
28+
/// # Examples:
29+
/// ```
30+
/// use gdnative_core::globalscope::step_decimals;
31+
/// assert_eq!(step_decimals(5.0), 0);
32+
/// assert_eq!(step_decimals(12.0004), 4);
33+
/// assert_eq!(step_decimals(0.000000004), 9);
34+
/// ```
35+
#[inline]
36+
pub fn step_decimals(step: f32) -> i32 {
37+
const MAXN: usize = 10;
38+
const SD: [f32; MAXN] = [
39+
0.9999, // somehow compensate for floating point error
40+
0.09999,
41+
0.009999,
42+
0.0009999,
43+
0.00009999,
44+
0.000009999,
45+
0.0000009999,
46+
0.00000009999,
47+
0.000000009999,
48+
0.0000000009999,
49+
];
50+
51+
let abs = step.abs();
52+
let int_abs: i32 = step as i32;
53+
let decs: f32 = abs - (int_abs as f32); // strip away integer part;
54+
for (i, item) in SD.iter().enumerate().take(MAXN) {
55+
if decs >= *item {
56+
return i.try_into().unwrap();
57+
}
58+
}
59+
0
60+
}
61+
62+
/// Moves `range.start()` toward `range.end()` by the `delta` `value`.
63+
/// Use a negative `delta` value `range.end()` move away.
64+
/// # Examples:
65+
/// ```
66+
/// use gdnative_core::globalscope::move_toward;
67+
/// assert_eq!(move_toward(10.0..=5.0, 4.), 6.);
68+
/// assert_eq!(move_toward(10.0..=5.0, -1.5), 11.5);
69+
/// assert_eq!(move_toward(4.0..=8.0, 1.0), 5.0);
70+
/// assert_eq!(move_toward(4.0..=8.0, 5.0), 8.0);
71+
/// assert_eq!(move_toward(8.0..=4.0, 1.0), 7.0);
72+
/// assert_eq!(move_toward(8.0..=4.0, 5.0), 4.0);
73+
/// ```
74+
#[inline]
75+
pub fn move_toward(range: RangeInclusive<f32>, delta: f32) -> f32 {
76+
if (range.end() - range.start()).abs() <= delta {
77+
*range.end()
78+
} else {
79+
range.start() + (range.end() - range.start()).signum() * delta
80+
}
81+
}
82+
83+
/// Returns an "eased" value of x based on an easing function defined with `curve`.
84+
/// This easing function is based on an `exponent`. The curve can be any floating-point number,
85+
/// with specific values leading to the following behaviors:
86+
#[inline]
87+
pub fn ease(mut s: f32, curve: f32) -> f32 {
88+
if s < 0.0 {
89+
s = 0.0;
90+
} else if s > 1.0 {
91+
s = 1.0;
92+
}
93+
if curve > 0.0 {
94+
if curve < 1.0 {
95+
1.0 - (1.0 - s).powf(1.0 / curve)
96+
} else {
97+
s.powf(curve)
98+
}
99+
} else if curve < 0.0 {
100+
//inout ease
101+
102+
if s < 0.5 {
103+
(s * 2.0).powf(-curve) * 0.5
104+
} else {
105+
(1.0 - (1.0 - (s - 0.5) * 2.0).powf(-curve)) * 0.5 + 0.5
106+
}
107+
} else {
108+
0.0 // no ease (raw)
109+
}
110+
}
111+
112+
/// Linearly interpolates between two values by the factor defined in weight.
113+
/// To perform interpolation, weight should be between 0.0 and 1.0 (inclusive).
114+
/// However, values outside this range are allowed and can be used to perform extrapolation.
115+
/// ```
116+
/// use gdnative_core::globalscope::lerp;
117+
/// assert_eq!(lerp(0.0..=4.0, 0.75), 3.0);
118+
/// ```
119+
#[inline]
120+
pub fn lerp(range: RangeInclusive<f32>, weight: f32) -> f32 {
121+
range.start() + (range.end() - range.start()) * weight
122+
}
123+
124+
/// Linearly interpolates between two angles (in radians) by a normalized value.
125+
/// Similar to lerp, but interpolates correctly when the angles wrap around `TAU`.
126+
/// To perform eased interpolation with `lerp_angle`, combine it with `ease` or `smoothstep`
127+
/// use std::f32::consts::{PI, TAU};
128+
/// use gdnative::globalscope::lerp_angle;
129+
///
130+
/// assert_eq!(lerp_angle(-PI..PI, 0.0), -PI);
131+
/// assert_eq!(lerp_angle(-PI..PI, 1.0), -PI);
132+
/// assert_eq!(lerp_angle(PI..-PI, 0.0), PI);
133+
/// assert_eq!(lerp_angle(PI..-PI, 1.0), PI);
134+
/// assert_eq!(lerp_angle(0.0..TAU, 0.0), 0.0);
135+
/// assert_eq!(lerp_angle(0.0..TAU, 1.0), 0.0);
136+
/// assert_eq!(lerp_angle(TAU..0, 0.0), TAU);
137+
/// assert_eq!(lerp_angle(TAU..0, 1.0), TAU);
138+
#[inline]
139+
pub fn lerp_angle(range: Range<f32>, amount: f32) -> f32 {
140+
let difference = f32::rem(range.end - range.start, TAU);
141+
142+
let distance = f32::rem(2.0 * difference, TAU) - difference;
143+
range.start + distance * amount
144+
}
145+
146+
/// Returns the floating-point modulus of `a/b` that wraps equally in `positive` and `negative`.
147+
/// # Examples:
148+
/// ```rust
149+
/// use gdnative_core::globalscope::fposmod;
150+
/// assert_eq!(fposmod(-1.5, 1.5), 0.0);
151+
/// assert_eq!(fposmod(-1.0, 1.5), 0.5);
152+
/// assert_eq!(fposmod(-0.5, 1.5), 1.0);
153+
/// assert_eq!(fposmod(0.0, 1.5), 0.0);
154+
/// ```
155+
#[inline]
156+
pub fn fposmod(x: f32, y: f32) -> f32 {
157+
let mut value = f32::rem(x, y);
158+
if ((value < 0.0) && (y > 0.0)) || ((value > 0.0) && (y < 0.0)) {
159+
value += y;
160+
}
161+
162+
value += 0.0;
163+
value
164+
}
165+
166+
/// Returns an interpolation or extrapolation factor considering the range specified in `range.start()` and `range.end()`,
167+
/// and the interpolated value specified in `weight`.
168+
/// The returned value will be between `0.0` and `1.0` if `weight` is between `range.start()` and `range.end()` (inclusive).
169+
/// If `weight` is located outside this range,
170+
/// then an extrapolation factor will be returned (return value lower than `0.0` or greater than `1.0`).
171+
/// # Examples:
172+
/// ```rust
173+
/// use gdnative_core::globalscope::inverse_lerp;
174+
/// assert_eq!(inverse_lerp(20.0..=30.0, 27.5), 0.75);
175+
/// ```
176+
#[inline]
177+
pub fn inverse_lerp(range: RangeInclusive<f32>, value: f32) -> f32 {
178+
(value - range.start()) / (range.end() - range.start())
179+
}
180+
181+
/// Returns the result of smoothly interpolating the value of `s` between `0` and `1`, based on the where `s` lies with respect to the edges `from` and `to`.
182+
/// The return value is `0` if `s <= from`, and `1` if `s >= to`. If `s` lies between `from` and `to`, the returned value follows an S-shaped curve that maps `s` between `0` and `1`.
183+
/// This S-shaped curve is the cubic Hermite interpolator, given by `f(y) = 3*y^2 - 2*y^3` where `y = (x-from) / (to-from)`.
184+
/// Compared to ease with a curve value of `-1.6521`, smoothstep returns the smoothest possible curve with no sudden changes in the derivative.
185+
/// If you need to perform more advanced transitions, use Tween or AnimationPlayer.
186+
/// # Examples:
187+
/// ```rust
188+
/// use gdnative_core::globalscope::smoothstep;
189+
/// assert_eq!(smoothstep(0.0, 2.0, -5.0), 0.0);
190+
/// assert_eq!(smoothstep(0.0, 2.0, 0.5), 0.15625);
191+
/// assert_eq!(smoothstep(0.0, 2.0, 1.0), 0.5);
192+
/// assert_eq!(smoothstep(0.0, 2.0, 2.0), 1.0);
193+
/// ```
194+
195+
#[inline]
196+
pub fn smoothstep(from: f32, to: f32, s: f32) -> f32 {
197+
if is_equal_approx(from, to) {
198+
return from;
199+
}
200+
let s = ((s - from) / (to - from)).clamp(0.0, 1.0);
201+
s * s * (3.0 - 2.0 * s)
202+
}
203+
204+
/// Returns `true` if `a` and `b` are approximately equal to each other.
205+
/// Here, approximately equal means that `a` and `sb` are within a small internal epsilon of each other,
206+
/// which scales with the magnitude of the numbers.
207+
/// Infinity values of the same sign are considered equal.
208+
#[inline]
209+
pub fn is_equal_approx(a: f32, b: f32) -> bool {
210+
if a == b {
211+
return true;
212+
}
213+
let mut tolerance = CMP_EPSILON * a.abs();
214+
if tolerance < CMP_EPSILON {
215+
tolerance = CMP_EPSILON;
216+
}
217+
(a - b).abs() < tolerance
218+
}
219+
220+
/// Returns true if s is zero or almost zero.
221+
/// This method is faster than using is_equal_approx with one value as zero.
222+
#[inline]
223+
pub fn is_zero_approx(s: f32) -> bool {
224+
s.abs() < CMP_EPSILON
225+
}
226+
227+
/// Converts from linear energy to decibels (audio).
228+
/// This can be used to implement volume sliders that behave as expected (since volume isn't linear).
229+
#[inline]
230+
pub fn linear2db(nrg: f32) -> f32 {
231+
nrg.ln() * 0.115_129_255
232+
}
233+
234+
/// Returns the nearest equal or larger power of 2 for integer value.
235+
/// In other words, returns the smallest value a where `a = pow(2, n)` such that `value <= a` for some non-negative integer `n`.
236+
/// # Examples:
237+
/// ```rust
238+
/// use gdnative_core::globalscope::nearest_po2;
239+
/// assert_eq!(nearest_po2(3), 4);
240+
/// assert_eq!(nearest_po2(4), 4);
241+
/// assert_eq!(nearest_po2(5), 8);
242+
/// assert_eq!(nearest_po2(0), 0);
243+
/// assert_eq!(nearest_po2(-1), 0);
244+
/// ```
245+
#[inline]
246+
pub fn nearest_po2(value: i32) -> u32 {
247+
if value <= 0 {
248+
return 0;
249+
}
250+
(value as u32).next_power_of_two()
251+
}
252+
253+
/// Converts a 2D point expressed in the polar coordinate system
254+
/// (a distance from the origin r and an angle th (radians)) to the cartesian coordinate system (X and Y axis).
255+
#[inline]
256+
pub fn cartesian2polar(r: f32, th: f32) -> (f32, f32) {
257+
(r * th.cos(), r * th.sin())
258+
}
259+
260+
/// Returns the integer modulus of a/b that wraps equally in positive and negative.
261+
/// # Examples:
262+
/// ```rust
263+
/// use gdnative_core::globalscope::posmod;
264+
/// const VALS: [i32; 7] = [0, 1, 2, 0, 1, 2, 0];
265+
/// for i in (-3..4).enumerate() {
266+
/// assert_eq!(posmod(i.1, 3), VALS[i.0]);
267+
/// }
268+
/// ```
269+
#[inline]
270+
pub fn posmod(a: i32, b: i32) -> i32 {
271+
let mut value = a % b;
272+
if ((value < 0) && (b > 0)) || ((value > 0) && (b < 0)) {
273+
value += b;
274+
}
275+
value
276+
}
277+
278+
/// Maps a value from range `range.from` to `range_to`.
279+
/// # Example:
280+
/// ```rust
281+
/// use gdnative_core::globalscope::range_lerp;
282+
/// assert_eq!(range_lerp(75.0, 0.0..=100.0, -1.0..=1.0), 0.5);
283+
/// ```
284+
#[inline]
285+
pub fn range_lerp(
286+
value: f32,
287+
range_from: RangeInclusive<f32>,
288+
range_to: RangeInclusive<f32>,
289+
) -> f32 {
290+
lerp(range_to, inverse_lerp(range_from, value))
291+
}
292+
293+
/// Snaps float value s to a given step.
294+
/// This can also be used to round a floating point number to an arbitrary number of decimals.
295+
/// ```rust
296+
/// use gdnative_core::globalscope::stepify;
297+
/// assert_eq!(stepify(100.0, 32.0), 96.0);
298+
/// assert_eq!(stepify(3.14159, 0.01), 3.1399999);
299+
/// ```
300+
#[inline]
301+
pub fn stepify(mut value: f32, step: f32) -> f32 {
302+
if step != 0.0 {
303+
value = (value / step + 0.5).floor() * step;
304+
}
305+
value
306+
}
307+
308+
/// Wraps float value between min and max.
309+
/// Usable for creating loop-alike behavior or infinite surfaces.
310+
/// # Examples :
311+
/// ```rust
312+
/// use gdnative_core::globalscope::wrapf;
313+
/// use std::f32::consts::{TAU, PI};
314+
///
315+
/// //Infinite loop between 5.0 and 9.9
316+
/// let value = 1.5;
317+
/// let angle = 0.70707;
318+
/// let value = wrapf(value + 0.1, 5.0..10.0);
319+
/// //Infinite rotation (in radians)
320+
/// let angle = wrapf(angle + 0.1, 0.0..TAU);
321+
/// //Infinite rotation (in radians)
322+
/// let angle = wrapf(angle + 0.1, -PI..PI);
323+
/// ```
324+
/// # Tests :
325+
/// ```rust
326+
/// use gdnative_core::globalscope::wrapf;
327+
/// use std::f32::consts::{TAU, PI};
328+
///
329+
/// let value = 0.5;
330+
/// assert_eq!(wrapf(value + 0.1, 5.0..0.0), 5.6);
331+
/// let angle = PI/4.0;
332+
/// assert_eq!(wrapf(angle, 0.0..TAU), 0.7853982);
333+
/// assert_eq!(wrapf(1.0, 0.5..1.5), 1.0);
334+
/// assert_eq!(wrapf(0.75, -0.5..0.5), -0.25);
335+
/// ```
336+
///
337+
/// # Note:
338+
/// If min is 0, this is equivalent to fposmod, so prefer using that instead.
339+
/// wrapf is more flexible than using the fposmod approach by giving the user control over the minimum value.
340+
#[inline]
341+
pub fn wrapf(value: f32, range: Range<f32>) -> f32 {
342+
let range_diff: f32 = range.end - range.start;
343+
if is_zero_approx(range_diff) {
344+
return range.start;
345+
}
346+
value - (range_diff * ((value - range.start / range_diff).floor()))
347+
}
348+
349+
/// Wraps integer value between min and max.
350+
/// Usable for creating loop-alike behavior or infinite surfaces.
351+
/// # Example :
352+
/// ```rust
353+
/// use gdnative_core::globalscope::wrapi;
354+
///
355+
/// //Infinite loop between 5 and 9
356+
/// let frame = 10;
357+
/// let frame = wrapi(frame + 1, 5..10);
358+
/// //result is -2
359+
/// let result = wrapi(-6, -5..-1);
360+
/// ```
361+
/// # Tests :
362+
/// ```rust
363+
/// use gdnative_core::globalscope::wrapi;
364+
///
365+
/// assert_eq!(wrapi(1, -1..2), 1);
366+
/// assert_eq!(wrapi(-1, 2..4), 3);
367+
/// assert_eq!(wrapi(1, 2..-1), 1);
368+
/// ```
369+
/// # Note:
370+
/// If min is 0, this is equivalent to posmod, so prefer using that instead.
371+
/// wrapi is more flexible than using the posmod approach by giving the user control over the minimum value.
372+
#[inline]
373+
pub fn wrapi(value: i32, range: Range<i32>) -> i32 {
374+
let range_diff = range.end - range.start;
375+
if range_diff == 0 {
376+
return range.start;
377+
}
378+
range.start + (((value - range.start % range_diff) + range_diff) % range_diff)
379+
}

gdnative-core/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ mod macros;
4343
pub mod core_types;
4444

4545
pub mod export;
46+
pub mod globalscope;
4647
pub mod init;
4748
pub mod log;
4849
pub mod object;

gdnative/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@
8181
// Items, which are #[doc(hidden)] in their original crate and re-exported with a wildcard, lose
8282
// their hidden status. Re-exporting them manually and hiding the wildcard solves this.
8383
#[doc(inline)]
84-
pub use gdnative_core::{core_types, export, init, log, object, profiler};
84+
pub use gdnative_core::{core_types, export, globalscope, init, log, object, profiler};
8585

8686
// Implementation details (e.g. used by macros).
8787
// However, do not re-export macros (on crate level), thus no wildcard

0 commit comments

Comments
 (0)