Skip to main content

box2d_rust/
core.rs

1// Port of the surviving behavior from box2d-cpp-reference/src/core.c, core.h,
2// include/box2d/base.h, and src/ctz.h.
3//
4// core.c is largely an allocator / threading / timing shim (b2Alloc, b2Free,
5// aligned allocation, mutex/semaphore/thread wrappers). Rust covers all of that
6// natively through Vec, ownership, and std::thread, so those pieces are not
7// ported — they carry no algorithmic behavior. What remains here is the pieces
8// that do carry behavior: the runtime length-unit scale, the version/precision
9// query functions, the deterministic djb2 hash, and the bit-twiddling helpers.
10//
11// SPDX-FileCopyrightText: 2023 Erin Catto
12// SPDX-License-Identifier: MIT
13
14use core::sync::atomic::{AtomicU32, Ordering};
15
16/// Used to indicate an unset or invalid index value. (base.h: B2_NULL_INDEX)
17pub const NULL_INDEX: i32 = -1;
18
19/// Use to validate definitions. (core.h: B2_SECRET_COOKIE)
20pub const SECRET_COOKIE: i32 = 1152023;
21
22/// Initial value for the djb2 hash. (base.h: B2_HASH_INIT)
23pub const HASH_INIT: u32 = 5381;
24
25// The length-unit scale is a single global that the user sets once at startup.
26// C stores it as a plain `static float`; we store the bit pattern in an atomic
27// so the global is sound under Rust's threading rules. The observable value is
28// identical. 0x3F80_0000 is the bit pattern of 1.0f32.
29static LENGTH_UNITS_PER_METER_BITS: AtomicU32 = AtomicU32::new(0x3F80_0000);
30
31/// Box2D bases all length units on meters. Set this to use different units for
32/// all length values passed to and returned from Box2D. Must be set at
33/// application startup, before any other Box2D calls.
34///
35/// See the extended documentation on `b2SetLengthUnitsPerMeter` in
36/// math_functions.h.
37pub fn set_length_units_per_meter(length_units: f32) {
38    debug_assert!(crate::math_functions::is_valid_float(length_units) && length_units > 0.0);
39    LENGTH_UNITS_PER_METER_BITS.store(length_units.to_bits(), Ordering::Relaxed);
40}
41
42/// Get the current length units per meter.
43pub fn get_length_units_per_meter() -> f32 {
44    f32::from_bits(LENGTH_UNITS_PER_METER_BITS.load(Ordering::Relaxed))
45}
46
47/// Version numbering scheme. See <https://semver.org/>
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub struct Version {
50    /// Significant changes
51    pub major: i32,
52    /// Incremental changes
53    pub minor: i32,
54    /// Bug fixes
55    pub revision: i32,
56}
57
58/// Get the current version of Box2D.
59pub fn get_version() -> Version {
60    Version {
61        major: 3,
62        minor: 2,
63        revision: 0,
64    }
65}
66
67/// @return true if the library was built with the `double-precision` feature
68/// (large world mode), mirroring `BOX2D_DOUBLE_PRECISION`.
69pub fn is_double_precision() -> bool {
70    cfg!(feature = "double-precision")
71}
72
73/// Simple djb2 hash function for determinism testing. (timer.c)
74pub fn hash(hash: u32, data: &[u8]) -> u32 {
75    let mut result = hash;
76    for &byte in data {
77        // C: result = (result << 5) + result + data[i], all in wrapping uint32.
78        result = (result << 5).wrapping_add(result).wrapping_add(byte as u32);
79    }
80    result
81}
82
83// ---------------------------------------------------------------------------
84// Bit helpers (ctz.h). The C versions are thin wrappers over compiler
85// intrinsics (__builtin_ctz / _BitScanForward / __popcnt). The count-leading
86// and count-trailing intrinsics are undefined for a zero argument in C; every
87// caller guarantees a nonzero argument, and Rust's intrinsics are well defined
88// (returning the bit width) even for zero, so the ported callers behave
89// identically.
90// ---------------------------------------------------------------------------
91
92/// Count trailing zeros of a 32-bit block. (ctz.h: b2CTZ32)
93pub fn ctz32(block: u32) -> u32 {
94    block.trailing_zeros()
95}
96
97/// Count leading zeros of a 32-bit value. (ctz.h: b2CLZ32)
98pub fn clz32(value: u32) -> u32 {
99    value.leading_zeros()
100}
101
102/// Count trailing zeros of a 64-bit block. (ctz.h: b2CTZ64)
103pub fn ctz64(block: u64) -> u32 {
104    block.trailing_zeros()
105}
106
107/// Population count of a 64-bit block. (ctz.h: b2PopCount64)
108pub fn pop_count64(block: u64) -> i32 {
109    block.count_ones() as i32
110}
111
112/// (ctz.h: b2IsPowerOf2)
113pub fn is_power_of2(x: i32) -> bool {
114    (x & (x - 1)) == 0
115}
116
117/// (ctz.h: b2BoundingPowerOf2)
118pub fn bounding_power_of2(x: i32) -> i32 {
119    if x <= 1 {
120        return 1;
121    }
122
123    32 - clz32((x as u32) - 1) as i32
124}
125
126/// (ctz.h: b2RoundUpPowerOf2)
127pub fn round_up_power_of2(x: i32) -> i32 {
128    if x <= 1 {
129        return 1;
130    }
131
132    1 << (32 - clz32((x as u32) - 1))
133}
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138
139    #[test]
140    fn djb2_hash_matches_reference() {
141        // djb2("box2d") computed from the reference formula starting at HASH_INIT.
142        let mut expected: u32 = HASH_INIT;
143        for &b in b"box2d" {
144            expected = (expected << 5)
145                .wrapping_add(expected)
146                .wrapping_add(b as u32);
147        }
148        assert_eq!(hash(HASH_INIT, b"box2d"), expected);
149        // Empty input returns the seed unchanged.
150        assert_eq!(hash(HASH_INIT, b""), HASH_INIT);
151    }
152
153    #[test]
154    fn bit_helpers() {
155        assert_eq!(ctz32(0b1000), 3);
156        assert_eq!(clz32(1), 31);
157        assert_eq!(ctz64(1u64 << 40), 40);
158        assert_eq!(pop_count64(0xFFFF_FFFF_FFFF_FFFF), 64);
159        assert!(is_power_of2(8));
160        assert!(!is_power_of2(6));
161        assert_eq!(round_up_power_of2(5), 8);
162        assert_eq!(round_up_power_of2(1), 1);
163        assert_eq!(bounding_power_of2(5), 3);
164    }
165
166    #[test]
167    fn version_and_precision() {
168        let v = get_version();
169        assert_eq!((v.major, v.minor, v.revision), (3, 2, 0));
170        assert_eq!(is_double_precision(), cfg!(feature = "double-precision"));
171    }
172
173    // Runs the whole length-unit lifecycle in one test so it never races another
174    // test observing the shared global (default -> scaled -> reset).
175    #[test]
176    fn length_units_scale_constants() {
177        use crate::constants;
178
179        assert_eq!(get_length_units_per_meter(), 1.0);
180        assert_eq!(constants::linear_slop(), 0.005);
181
182        set_length_units_per_meter(100.0);
183        assert_eq!(get_length_units_per_meter(), 100.0);
184        assert_eq!(constants::linear_slop(), 0.5);
185        assert_eq!(constants::speculative_distance(), 4.0 * 0.5);
186        assert_eq!(constants::max_aabb_margin(), 0.05 * 100.0);
187
188        set_length_units_per_meter(1.0);
189        assert_eq!(constants::linear_slop(), 0.005);
190    }
191}