From 8532c55ed0afcb7cba6c118f4f3a27a19b808aac Mon Sep 17 00:00:00 2001 From: Lukas Klingsbo Date: Sun, 19 Jul 2026 22:59:04 +0200 Subject: [PATCH 1/5] feat: Run on the web via a WebAssembly build of Box2D The same public API now works in the browser under both dart2js and dart2wasm. Box2D and a pointer-based C shim (native/wasm/f2d_shim.c) are compiled with Emscripten into a standalone ~220 KB box2d.wasm, shipped inside the package and loaded by initializeForge2D(). The shim flattens every struct-passing call into scalars and pointers (the wasm C ABI cannot pass structs by value across the JS boundary), copies events into float64 record buffers, and routes the synchronous callbacks (queries, custom filter, pre-solve, debug draw) through fixed host imports supplied at instantiation, so no function tables are needed. On the Dart side, RawBox2DWasm implements the backend seam over dart:js_interop with a scratch arena for arguments and results, and the conditional export picks it up automatically on the web. Dart web apps need no setup; Flutter web apps copy the module once with dart run forge2d:setup_web, or pass wasmUri to initializeForge2D. The API test suite now runs on vm, chrome/dart2js, and chrome/dart2wasm (new CI jobs), and build-wasm.yml rebuilds the module with a pinned emsdk to verify the committed artifact. Along the way chain creation gained proper validation: Box2D requires at least four points, which the native release build silently ignored with asserts compiled out. --- .github/workflows/build-wasm.yml | 34 + .github/workflows/cicd.yml | 15 + README.md | 13 +- packages/forge2d/bin/setup_web.dart | 32 + packages/forge2d/dart_test.yaml | 5 + packages/forge2d/lib/src/api/body.dart | 12 + packages/forge2d/lib/src/backend/backend.dart | 8 +- .../lib/src/backend/raw_box2d_ffi.dart | 4 +- .../src/backend/raw_box2d_unsupported.dart | 2 +- .../lib/src/backend/raw_box2d_wasm.dart | 2119 +++++++++++++++++ .../forge2d/lib/src/backend/wasm/box2d.wasm | Bin 0 -> 226900 bytes .../lib/src/backend/wasm/wasm_runtime.dart | 570 +++++ packages/forge2d/lib/src/initialize.dart | 15 +- packages/forge2d/native/wasm/f2d_shim.c | 1443 +++++++++++ packages/forge2d/test/api/body_test.dart | 3 - .../forge2d/test/api/debug_draw_test.dart | 3 - packages/forge2d/test/api/events_test.dart | 3 - packages/forge2d/test/api/joints_test.dart | 3 - packages/forge2d/test/api/shape_test.dart | 19 +- packages/forge2d/test/api/world_test.dart | 3 - .../test/backend/world_backend_test.dart | 3 - packages/forge2d/tool/build_wasm.sh | 33 + 22 files changed, 4304 insertions(+), 38 deletions(-) create mode 100644 .github/workflows/build-wasm.yml create mode 100644 packages/forge2d/bin/setup_web.dart create mode 100644 packages/forge2d/dart_test.yaml create mode 100644 packages/forge2d/lib/src/backend/raw_box2d_wasm.dart create mode 100755 packages/forge2d/lib/src/backend/wasm/box2d.wasm create mode 100644 packages/forge2d/lib/src/backend/wasm/wasm_runtime.dart create mode 100644 packages/forge2d/native/wasm/f2d_shim.c create mode 100755 packages/forge2d/tool/build_wasm.sh diff --git a/.github/workflows/build-wasm.yml b/.github/workflows/build-wasm.yml new file mode 100644 index 00000000..d6139c6a --- /dev/null +++ b/.github/workflows/build-wasm.yml @@ -0,0 +1,34 @@ +name: build-wasm + +on: + pull_request: + paths: + - "packages/forge2d/native/wasm/**" + - "packages/forge2d/third_party/box2d/**" + - "packages/forge2d/tool/build_wasm.sh" + - ".github/workflows/build-wasm.yml" + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: mymindstorm/setup-emsdk@v14 + with: + # Keep in sync with the version documented in tool/build_wasm.sh; + # the artifact check below relies on reproducible output. + version: 4.0.15 + - name: Rebuild box2d.wasm + run: packages/forge2d/tool/build_wasm.sh + - name: Verify the committed artifact is up to date + run: | + if ! git diff --exit-code --stat -- packages/forge2d/lib/src/backend/wasm/box2d.wasm; then + echo "The committed box2d.wasm differs from a fresh build." + echo "Rebuild it with tool/build_wasm.sh using emsdk 4.0.15." + exit 1 + fi + - uses: actions/upload-artifact@v4 + with: + name: box2d-wasm + path: packages/forge2d/lib/src/backend/wasm/box2d.wasm diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index a7c987e9..fd0947c6 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -68,4 +68,19 @@ jobs: - uses: subosito/flutter-action@v2 - uses: bluefireteam/melos-action@v3 - run: melos test + + test-web: + needs: [format, analyze] + strategy: + fail-fast: false + matrix: + compiler: [dart2js, dart2wasm] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: subosito/flutter-action@v2 + - uses: bluefireteam/melos-action@v3 + - name: Test the WebAssembly backend + working-directory: packages/forge2d + run: dart test -p chrome -c ${{ matrix.compiler }} # END TESTING STAGE diff --git a/README.md b/README.md index 5f5509e2..fddac7ff 100644 --- a/README.md +++ b/README.md @@ -29,10 +29,15 @@ You can use it independently in Dart or in your - Dart 3.12+ (Flutter 3.38+), which builds the bundled C library automatically through build hooks. -- A C toolchain: Xcode on macOS/iOS, Visual Studio Build Tools on Windows, - clang or gcc on Linux, and the NDK on Android. -- Supported platforms: Android, iOS, macOS, Windows, and Linux. Web support - is planned via a WebAssembly build of Box2D behind the same API. +- A C toolchain on native platforms: Xcode on macOS/iOS, Visual Studio + Build Tools on Windows, clang or gcc on Linux, and the NDK on Android. +- Supported platforms: Android, iOS, macOS, Windows, Linux, and the web. + +On the web the same API runs against a bundled WebAssembly build of Box2D +(about 300 KB). Dart web apps need no setup: `initializeForge2D()` finds +the module at the package asset path. Flutter web apps copy it next to the +page once with `dart run forge2d:setup_web`, or pass a custom location via +`initializeForge2D(wasmUri: ...)`. ## Getting started diff --git a/packages/forge2d/bin/setup_web.dart b/packages/forge2d/bin/setup_web.dart new file mode 100644 index 00000000..bd928e38 --- /dev/null +++ b/packages/forge2d/bin/setup_web.dart @@ -0,0 +1,32 @@ +// Copies the bundled box2d.wasm into a web app's web/ directory, where +// initializeForge2D() finds it at runtime. +// +// Run from the root of your app: +// dart run forge2d:setup_web +// ignore_for_file: avoid_print +import 'dart:io'; +import 'dart:isolate'; + +Future main() async { + final packageUri = await Isolate.resolvePackageUri( + Uri.parse('package:forge2d/src/backend/wasm/box2d.wasm'), + ); + if (packageUri == null) { + stderr.writeln('Could not resolve the forge2d package.'); + exitCode = 1; + return; + } + + final webDirectory = Directory('web'); + if (!webDirectory.existsSync()) { + stderr.writeln( + 'No web/ directory found. Run this from the root of a web app.', + ); + exitCode = 1; + return; + } + + final target = File('web/box2d.wasm'); + File.fromUri(packageUri).copySync(target.path); + print('Copied box2d.wasm to ${target.path}'); +} diff --git a/packages/forge2d/dart_test.yaml b/packages/forge2d/dart_test.yaml new file mode 100644 index 00000000..f4134dec --- /dev/null +++ b/packages/forge2d/dart_test.yaml @@ -0,0 +1,5 @@ +# The default run exercises the FFI backend on the VM. Run the same suite +# against the WebAssembly backend with: +# dart test -p chrome (dart2js) +# dart test -p chrome -c dart2wasm +platforms: [vm] diff --git a/packages/forge2d/lib/src/api/body.dart b/packages/forge2d/lib/src/api/body.dart index a67bba9f..f0112ddd 100644 --- a/packages/forge2d/lib/src/api/body.dart +++ b/packages/forge2d/lib/src/api/body.dart @@ -407,7 +407,19 @@ class Body { } /// Creates a chain of one-sided segments and attaches it to the body. + /// + /// Chains need at least four points: open chains use the first and last + /// point as invisible ghost anchors that smooth collisions at the ends. Chain createChain(ChainDef definition) { + if (definition.points.length < 4) { + throw ArgumentError('A chain needs at least four points'); + } + final materialCount = definition.materials.length; + if (materialCount != 1 && materialCount != definition.points.length) { + throw ArgumentError( + 'A chain needs one material, or one material per point', + ); + } final (chainIndex1, chainWorldAndGeneration) = rawBox2D.createChain( index1, worldAndGeneration, diff --git a/packages/forge2d/lib/src/backend/backend.dart b/packages/forge2d/lib/src/backend/backend.dart index 4cefdd17..6de61967 100644 --- a/packages/forge2d/lib/src/backend/backend.dart +++ b/packages/forge2d/lib/src/backend/backend.dart @@ -1,9 +1,11 @@ /// Compile-time selection of the platform backend. /// -/// Exports `createRawBox2D` and `initializeBackend`. The web backend will be -/// added here as a `dart.library.js_interop` branch. +/// Exports `createRawBox2D` and `initializeBackend`: dart:ffi against the +/// native library on native platforms, dart:js_interop against the bundled +/// box2d.wasm on the web. library; export 'package:forge2d/src/backend/raw_box2d.dart'; export 'package:forge2d/src/backend/raw_box2d_unsupported.dart' - if (dart.library.ffi) 'package:forge2d/src/backend/raw_box2d_ffi.dart'; + if (dart.library.ffi) 'package:forge2d/src/backend/raw_box2d_ffi.dart' + if (dart.library.js_interop) 'package:forge2d/src/backend/raw_box2d_wasm.dart'; diff --git a/packages/forge2d/lib/src/backend/raw_box2d_ffi.dart b/packages/forge2d/lib/src/backend/raw_box2d_ffi.dart index 26a1944b..5c0ef158 100644 --- a/packages/forge2d/lib/src/backend/raw_box2d_ffi.dart +++ b/packages/forge2d/lib/src/backend/raw_box2d_ffi.dart @@ -5,8 +5,8 @@ import 'package:forge2d/src/backend/raw_box2d.dart'; import 'package:forge2d/src/ffi/box2d.g.dart' as b2; /// Prepares the FFI backend. Nothing to do: the native library is bundled by -/// the build hook and loaded lazily. -Future initializeBackend() async {} +/// the build hook and loaded lazily. [wasmUri] only applies on the web. +Future initializeBackend({Uri? wasmUri}) async {} /// Creates the FFI backend. RawBox2D createRawBox2D() => RawBox2DFfi(); diff --git a/packages/forge2d/lib/src/backend/raw_box2d_unsupported.dart b/packages/forge2d/lib/src/backend/raw_box2d_unsupported.dart index ee0c40b2..5a7d03ff 100644 --- a/packages/forge2d/lib/src/backend/raw_box2d_unsupported.dart +++ b/packages/forge2d/lib/src/backend/raw_box2d_unsupported.dart @@ -1,7 +1,7 @@ import 'package:forge2d/src/backend/raw_box2d.dart'; /// Fails: this platform has no forge2d backend. -Future initializeBackend() async { +Future initializeBackend({Uri? wasmUri}) async { throw UnsupportedError(_message); } diff --git a/packages/forge2d/lib/src/backend/raw_box2d_wasm.dart b/packages/forge2d/lib/src/backend/raw_box2d_wasm.dart new file mode 100644 index 00000000..0b5f1204 --- /dev/null +++ b/packages/forge2d/lib/src/backend/raw_box2d_wasm.dart @@ -0,0 +1,2119 @@ +import 'package:forge2d/src/backend/raw_box2d.dart'; +import 'package:forge2d/src/backend/wasm/wasm_runtime.dart'; + +WasmRuntime? _runtime; + +/// Loads and instantiates the Box2D WebAssembly module. +/// +/// Called by `initializeForge2D`. [wasmUri] overrides the default lookup +/// locations: the package asset path used by Dart web tooling, then a +/// `box2d.wasm` next to the page for Flutter web apps. +Future initializeBackend({Uri? wasmUri}) async { + if (_runtime != null) { + return; + } + _runtime = await WasmRuntime.load([ + if (wasmUri != null) + wasmUri + else ...[ + Uri.parse('packages/forge2d/src/backend/wasm/box2d.wasm'), + Uri.parse('/packages/forge2d/src/backend/wasm/box2d.wasm'), + Uri.parse('box2d.wasm'), + ], + ]); +} + +/// Creates the WebAssembly backend. +RawBox2D createRawBox2D() { + final runtime = _runtime; + if (runtime == null) { + throw StateError( + 'forge2d is not initialized. On the web you must call and await ' + 'initializeForge2D() before creating a World.', + ); + } + return RawBox2DWasm(runtime); +} + +/// The dart:js_interop implementation of [RawBox2D], calling the `f2d_` +/// shim exports of box2d.wasm. See native/wasm/f2d_shim.c for the ABI. +final class RawBox2DWasm implements RawBox2D { + /// Creates the backend over a loaded [WasmRuntime]. + RawBox2DWasm(this._runtime); + + final WasmRuntime _runtime; + + // Scratch arena layout (see WasmRuntime.scratch, 4096 bytes): + // - byte 0: small out-parameters (up to 128 bytes) + // - byte 128: the f2dShapeDef struct (19 * 4 bytes) + // - byte 1024: string data + static const _outOffset = 0; + static const _shapeDefOffset = 128; + static const _stringOffset = 1024; + + int get _out => _runtime.scratch + _outOffset; + int get _shapeDefPointer => _runtime.scratch + _shapeDefOffset; + int get _string => _runtime.scratch + _stringOffset; + + num _call(String name, List arguments) => _runtime.call(name, arguments); + + double _callF(String name, List arguments) => + _call(name, arguments).toDouble(); + + int _callI(String name, List arguments) => + _call(name, arguments).toInt(); + + bool _callB(String name, List arguments) => _call(name, arguments) != 0; + + static int _b(bool value) => value ? 1 : 0; + + /// Splits a Dart int holding a 64-bit bit field into unsigned 32-bit + /// halves. Negative values mean "all bits set" (the seam contract). + static (int, int) _splitBits(int value) { + if (value < 0) { + return (0xFFFFFFFF, 0xFFFFFFFF); + } + return (value % 0x100000000, value ~/ 0x100000000); + } + + static int _joinBits(int low, int high) { + final unsignedLow = low < 0 ? low + 0x100000000 : low; + final unsignedHigh = high < 0 ? high + 0x100000000 : high; + if (unsignedLow == 0xFFFFFFFF && unsignedHigh == 0xFFFFFFFF) { + return -1; + } + return unsignedHigh * 0x100000000 + unsignedLow; + } + + (double, double) _outVec2() => + (_runtime.readF32(_out), _runtime.readF32(_out + 4)); + + (int, int) _outIdPair() => + (_runtime.readI32(_out), _runtime.readI32(_out + 4)); + + // World. + + @override + int createWorld({ + required double gravityX, + required double gravityY, + required double restitutionThreshold, + required double hitEventThreshold, + required double contactHertz, + required double contactDampingRatio, + required double maxContactPushSpeed, + required double maximumLinearSpeed, + required bool enableSleep, + required bool enableContinuous, + }) => _callI('f2d_create_world', [ + gravityX, + gravityY, + restitutionThreshold, + hitEventThreshold, + contactHertz, + contactDampingRatio, + maxContactPushSpeed, + maximumLinearSpeed, + _b(enableSleep), + _b(enableContinuous), + ]); + + @override + void destroyWorld(int worldId) => _call('f2d_destroy_world', [worldId]); + + @override + bool worldIsValid(int worldId) => _callB('f2d_world_is_valid', [worldId]); + + @override + void worldStep(int worldId, double timeStep, int subStepCount) => + _call('f2d_world_step', [worldId, timeStep, subStepCount]); + + @override + void worldSetGravity(int worldId, double x, double y) => + _call('f2d_world_set_gravity', [worldId, x, y]); + + @override + (double, double) worldGetGravity(int worldId) { + _call('f2d_world_get_gravity', [worldId, _out]); + return _outVec2(); + } + + @override + void worldEnableSleeping(int worldId, {required bool enabled}) => + _call('f2d_world_enable_sleeping', [worldId, _b(enabled)]); + + @override + bool worldIsSleepingEnabled(int worldId) => + _callB('f2d_world_is_sleeping_enabled', [worldId]); + + @override + void worldEnableContinuous(int worldId, {required bool enabled}) => + _call('f2d_world_enable_continuous', [worldId, _b(enabled)]); + + @override + bool worldIsContinuousEnabled(int worldId) => + _callB('f2d_world_is_continuous_enabled', [worldId]); + + // Bodies. + + @override + (int, int) createBody( + int worldId, { + required int type, + required double positionX, + required double positionY, + required double rotationCos, + required double rotationSin, + required double linearVelocityX, + required double linearVelocityY, + required double angularVelocity, + required double linearDamping, + required double angularDamping, + required double gravityScale, + required double sleepThreshold, + required String? name, + required bool enableSleep, + required bool isAwake, + required bool fixedRotation, + required bool isBullet, + required bool isEnabled, + required bool allowFastRotation, + }) { + final namePointer = _runtime.writeCString(_string, name); + _call('f2d_create_body', [ + worldId, + type, + positionX, + positionY, + rotationCos, + rotationSin, + linearVelocityX, + linearVelocityY, + angularVelocity, + linearDamping, + angularDamping, + gravityScale, + sleepThreshold, + namePointer, + _b(enableSleep), + _b(isAwake), + _b(fixedRotation), + _b(isBullet), + _b(isEnabled), + _b(allowFastRotation), + _out, + ]); + return _outIdPair(); + } + + @override + void destroyBody(int index1, int wg) => + _call('f2d_destroy_body', [index1, wg]); + + @override + bool bodyIsValid(int index1, int wg) => + _callB('f2d_body_is_valid', [index1, wg]); + + @override + (double, double) bodyGetPosition(int index1, int wg) { + _call('f2d_body_get_position', [index1, wg, _out]); + return _outVec2(); + } + + @override + (double, double) bodyGetRotation(int index1, int wg) { + _call('f2d_body_get_rotation', [index1, wg, _out]); + return _outVec2(); + } + + @override + void bodySetTransform( + int index1, + int wg, + double positionX, + double positionY, + double rotationCos, + double rotationSin, + ) => _call('f2d_body_set_transform', [ + index1, + wg, + positionX, + positionY, + rotationCos, + rotationSin, + ]); + + @override + (double, double) bodyGetLinearVelocity(int index1, int wg) { + _call('f2d_body_get_linear_velocity', [index1, wg, _out]); + return _outVec2(); + } + + @override + void bodySetLinearVelocity(int index1, int wg, double x, double y) => + _call('f2d_body_set_linear_velocity', [index1, wg, x, y]); + + @override + double bodyGetAngularVelocity(int index1, int wg) => + _callF('f2d_body_get_angular_velocity', [index1, wg]); + + @override + void bodySetAngularVelocity(int index1, int wg, double value) => + _call('f2d_body_set_angular_velocity', [index1, wg, value]); + + @override + void bodyApplyForce( + int index1, + int wg, + double forceX, + double forceY, + double pointX, + double pointY, { + required bool wake, + }) => _call('f2d_body_apply_force', [ + index1, + wg, + forceX, + forceY, + pointX, + pointY, + _b(wake), + ]); + + @override + void bodyApplyForceToCenter( + int index1, + int wg, + double forceX, + double forceY, { + required bool wake, + }) => _call('f2d_body_apply_force_to_center', [ + index1, + wg, + forceX, + forceY, + _b(wake), + ]); + + @override + void bodyApplyTorque( + int index1, + int wg, + double torque, { + required bool wake, + }) => _call('f2d_body_apply_torque', [index1, wg, torque, _b(wake)]); + + @override + void bodyApplyLinearImpulse( + int index1, + int wg, + double impulseX, + double impulseY, + double pointX, + double pointY, { + required bool wake, + }) => _call('f2d_body_apply_linear_impulse', [ + index1, + wg, + impulseX, + impulseY, + pointX, + pointY, + _b(wake), + ]); + + @override + void bodyApplyLinearImpulseToCenter( + int index1, + int wg, + double impulseX, + double impulseY, { + required bool wake, + }) => _call('f2d_body_apply_linear_impulse_to_center', [ + index1, + wg, + impulseX, + impulseY, + _b(wake), + ]); + + @override + void bodyApplyAngularImpulse( + int index1, + int wg, + double impulse, { + required bool wake, + }) => + _call('f2d_body_apply_angular_impulse', [index1, wg, impulse, _b(wake)]); + + @override + double bodyGetMass(int index1, int wg) => + _callF('f2d_body_get_mass', [index1, wg]); + + @override + double bodyGetRotationalInertia(int index1, int wg) => + _callF('f2d_body_get_rotational_inertia', [index1, wg]); + + @override + (double, double) bodyGetLocalCenterOfMass(int index1, int wg) { + _call('f2d_body_get_local_center', [index1, wg, _out]); + return _outVec2(); + } + + @override + (double, double) bodyGetWorldCenterOfMass(int index1, int wg) { + _call('f2d_body_get_world_center', [index1, wg, _out]); + return _outVec2(); + } + + @override + void bodySetMassData( + int index1, + int wg, + double mass, + double rotationalInertia, + double centerX, + double centerY, + ) => _call('f2d_body_set_mass_data', [ + index1, + wg, + mass, + rotationalInertia, + centerX, + centerY, + ]); + + @override + void bodyApplyMassFromShapes(int index1, int wg) => + _call('f2d_body_apply_mass_from_shapes', [index1, wg]); + + @override + int bodyGetType(int index1, int wg) => + _callI('f2d_body_get_type', [index1, wg]); + + @override + void bodySetType(int index1, int wg, int type) => + _call('f2d_body_set_type', [index1, wg, type]); + + @override + String bodyGetName(int index1, int wg) => + _runtime.readCString(_callI('f2d_body_get_name', [index1, wg])); + + @override + void bodySetName(int index1, int wg, String? name) => _call( + 'f2d_body_set_name', + [index1, wg, _runtime.writeCString(_string, name)], + ); + + @override + bool bodyIsAwake(int index1, int wg) => + _callB('f2d_body_is_awake', [index1, wg]); + + @override + void bodySetAwake(int index1, int wg, {required bool awake}) => + _call('f2d_body_set_awake', [index1, wg, _b(awake)]); + + @override + bool bodyIsSleepEnabled(int index1, int wg) => + _callB('f2d_body_is_sleep_enabled', [index1, wg]); + + @override + void bodyEnableSleep(int index1, int wg, {required bool enabled}) => + _call('f2d_body_enable_sleep', [index1, wg, _b(enabled)]); + + @override + double bodyGetSleepThreshold(int index1, int wg) => + _callF('f2d_body_get_sleep_threshold', [index1, wg]); + + @override + void bodySetSleepThreshold(int index1, int wg, double value) => + _call('f2d_body_set_sleep_threshold', [index1, wg, value]); + + @override + bool bodyIsEnabled(int index1, int wg) => + _callB('f2d_body_is_enabled', [index1, wg]); + + @override + void bodyDisable(int index1, int wg) => + _call('f2d_body_disable', [index1, wg]); + + @override + void bodyEnable(int index1, int wg) => _call('f2d_body_enable', [index1, wg]); + + @override + bool bodyIsFixedRotation(int index1, int wg) => + _callB('f2d_body_is_fixed_rotation', [index1, wg]); + + @override + void bodySetFixedRotation(int index1, int wg, {required bool flag}) => + _call('f2d_body_set_fixed_rotation', [index1, wg, _b(flag)]); + + @override + bool bodyIsBullet(int index1, int wg) => + _callB('f2d_body_is_bullet', [index1, wg]); + + @override + void bodySetBullet(int index1, int wg, {required bool flag}) => + _call('f2d_body_set_bullet', [index1, wg, _b(flag)]); + + @override + double bodyGetGravityScale(int index1, int wg) => + _callF('f2d_body_get_gravity_scale', [index1, wg]); + + @override + void bodySetGravityScale(int index1, int wg, double scale) => + _call('f2d_body_set_gravity_scale', [index1, wg, scale]); + + @override + double bodyGetLinearDamping(int index1, int wg) => + _callF('f2d_body_get_linear_damping', [index1, wg]); + + @override + void bodySetLinearDamping(int index1, int wg, double damping) => + _call('f2d_body_set_linear_damping', [index1, wg, damping]); + + @override + double bodyGetAngularDamping(int index1, int wg) => + _callF('f2d_body_get_angular_damping', [index1, wg]); + + @override + void bodySetAngularDamping(int index1, int wg, double damping) => + _call('f2d_body_set_angular_damping', [index1, wg, damping]); + + @override + (double, double) bodyGetWorldPoint(int index1, int wg, double x, double y) { + _call('f2d_body_get_world_point', [index1, wg, x, y, _out]); + return _outVec2(); + } + + @override + (double, double) bodyGetLocalPoint(int index1, int wg, double x, double y) { + _call('f2d_body_get_local_point', [index1, wg, x, y, _out]); + return _outVec2(); + } + + @override + List bodyGetShapes(int index1, int wg) { + final count = _callI('f2d_body_get_shape_count', [index1, wg]); + if (count == 0) { + return const []; + } + final buffer = _runtime.bulk(count * 8); + final written = _callI('f2d_body_get_shapes', [index1, wg, buffer, count]); + return _runtime.readI32List(buffer, written * 2); + } + + @override + List bodyGetJoints(int index1, int wg) { + final count = _callI('f2d_body_get_joint_count', [index1, wg]); + if (count == 0) { + return const []; + } + final buffer = _runtime.bulk(count * 8); + final written = _callI('f2d_body_get_joints', [index1, wg, buffer, count]); + return _runtime.readI32List(buffer, written * 2); + } + + // Shapes. + + void _writeShapeDef(RawShapeDef definition) { + final pointer = _shapeDefPointer; + final (categoryLow, categoryHigh) = _splitBits(definition.categoryBits); + final (maskLow, maskHigh) = _splitBits(definition.maskBits); + _runtime + ..writeF32(pointer, definition.friction) + ..writeF32(pointer + 4, definition.restitution) + ..writeF32(pointer + 8, definition.rollingResistance) + ..writeF32(pointer + 12, definition.tangentSpeed) + ..writeI32(pointer + 16, definition.userMaterialId) + ..writeI32(pointer + 20, definition.customColor) + ..writeF32(pointer + 24, definition.density) + ..writeI32(pointer + 28, categoryLow) + ..writeI32(pointer + 32, categoryHigh) + ..writeI32(pointer + 36, maskLow) + ..writeI32(pointer + 40, maskHigh) + ..writeI32(pointer + 44, definition.groupIndex) + ..writeI32(pointer + 48, _b(definition.isSensor)) + ..writeI32(pointer + 52, _b(definition.enableSensorEvents)) + ..writeI32(pointer + 56, _b(definition.enableContactEvents)) + ..writeI32(pointer + 60, _b(definition.enableHitEvents)) + ..writeI32(pointer + 64, _b(definition.enablePreSolveEvents)) + ..writeI32(pointer + 68, _b(definition.invokeContactCreation)) + ..writeI32(pointer + 72, _b(definition.updateBodyMass)); + } + + @override + (int, int) createCircleShape( + int bodyIndex1, + int bodyWg, { + required double centerX, + required double centerY, + required double radius, + required RawShapeDef definition, + }) { + _writeShapeDef(definition); + _call('f2d_create_circle_shape', [ + bodyIndex1, + bodyWg, + _shapeDefPointer, + centerX, + centerY, + radius, + _out, + ]); + return _outIdPair(); + } + + @override + (int, int) createCapsuleShape( + int bodyIndex1, + int bodyWg, { + required double center1X, + required double center1Y, + required double center2X, + required double center2Y, + required double radius, + required RawShapeDef definition, + }) { + _writeShapeDef(definition); + _call('f2d_create_capsule_shape', [ + bodyIndex1, + bodyWg, + _shapeDefPointer, + center1X, + center1Y, + center2X, + center2Y, + radius, + _out, + ]); + return _outIdPair(); + } + + @override + (int, int) createSegmentShape( + int bodyIndex1, + int bodyWg, { + required double point1X, + required double point1Y, + required double point2X, + required double point2Y, + required RawShapeDef definition, + }) { + _writeShapeDef(definition); + _call('f2d_create_segment_shape', [ + bodyIndex1, + bodyWg, + _shapeDefPointer, + point1X, + point1Y, + point2X, + point2Y, + _out, + ]); + return _outIdPair(); + } + + @override + (int, int) createBoxShape( + int bodyIndex1, + int bodyWg, { + required double halfWidth, + required double halfHeight, + required double centerX, + required double centerY, + required double rotationCos, + required double rotationSin, + required double radius, + required RawShapeDef definition, + }) { + _writeShapeDef(definition); + _call('f2d_create_box_shape', [ + bodyIndex1, + bodyWg, + _shapeDefPointer, + halfWidth, + halfHeight, + centerX, + centerY, + rotationCos, + rotationSin, + radius, + _out, + ]); + return _outIdPair(); + } + + @override + (int, int) createPolygonShape( + int bodyIndex1, + int bodyWg, { + required List points, + required double radius, + required RawShapeDef definition, + }) { + _writeShapeDef(definition); + final buffer = _runtime.bulk(points.length * 4); + _runtime.writeF32List(buffer, points); + final ok = _callB('f2d_create_polygon_shape', [ + bodyIndex1, + bodyWg, + _shapeDefPointer, + buffer, + points.length ~/ 2, + radius, + _out, + ]); + if (!ok) { + throw ArgumentError( + 'The points do not form a valid convex hull: at least three ' + 'distinct, non-collinear points are required', + ); + } + return _outIdPair(); + } + + @override + void destroyShape(int index1, int wg, {required bool updateBodyMass}) => + _call('f2d_destroy_shape', [index1, wg, _b(updateBodyMass)]); + + @override + bool shapeIsValid(int index1, int wg) => + _callB('f2d_shape_is_valid', [index1, wg]); + + @override + int shapeGetType(int index1, int wg) => + _callI('f2d_shape_get_type', [index1, wg]); + + @override + (int, int) shapeGetBody(int index1, int wg) { + _call('f2d_shape_get_body', [index1, wg, _out]); + return _outIdPair(); + } + + @override + bool shapeIsSensor(int index1, int wg) => + _callB('f2d_shape_is_sensor', [index1, wg]); + + @override + double shapeGetDensity(int index1, int wg) => + _callF('f2d_shape_get_density', [index1, wg]); + + @override + void shapeSetDensity( + int index1, + int wg, + double density, { + required bool updateBodyMass, + }) => _call('f2d_shape_set_density', [ + index1, + wg, + density, + _b(updateBodyMass), + ]); + + @override + double shapeGetFriction(int index1, int wg) => + _callF('f2d_shape_get_friction', [index1, wg]); + + @override + void shapeSetFriction(int index1, int wg, double friction) => + _call('f2d_shape_set_friction', [index1, wg, friction]); + + @override + double shapeGetRestitution(int index1, int wg) => + _callF('f2d_shape_get_restitution', [index1, wg]); + + @override + void shapeSetRestitution(int index1, int wg, double restitution) => + _call('f2d_shape_set_restitution', [index1, wg, restitution]); + + @override + (int, int, int) shapeGetFilter(int index1, int wg) { + _call('f2d_shape_get_filter', [index1, wg, _out]); + return ( + _joinBits(_runtime.readI32(_out), _runtime.readI32(_out + 4)), + _joinBits(_runtime.readI32(_out + 8), _runtime.readI32(_out + 12)), + _runtime.readI32(_out + 16), + ); + } + + @override + void shapeSetFilter( + int index1, + int wg, + int categoryBits, + int maskBits, + int groupIndex, + ) { + final (categoryLow, categoryHigh) = _splitBits(categoryBits); + final (maskLow, maskHigh) = _splitBits(maskBits); + _call('f2d_shape_set_filter', [ + index1, + wg, + categoryLow, + categoryHigh, + maskLow, + maskHigh, + groupIndex, + ]); + } + + @override + bool shapeAreSensorEventsEnabled(int index1, int wg) => + _callB('f2d_shape_are_sensor_events_enabled', [index1, wg]); + + @override + void shapeEnableSensorEvents(int index1, int wg, {required bool enabled}) => + _call('f2d_shape_enable_sensor_events', [index1, wg, _b(enabled)]); + + @override + bool shapeAreContactEventsEnabled(int index1, int wg) => + _callB('f2d_shape_are_contact_events_enabled', [index1, wg]); + + @override + void shapeEnableContactEvents(int index1, int wg, {required bool enabled}) => + _call('f2d_shape_enable_contact_events', [index1, wg, _b(enabled)]); + + @override + bool shapeAreHitEventsEnabled(int index1, int wg) => + _callB('f2d_shape_are_hit_events_enabled', [index1, wg]); + + @override + void shapeEnableHitEvents(int index1, int wg, {required bool enabled}) => + _call('f2d_shape_enable_hit_events', [index1, wg, _b(enabled)]); + + @override + bool shapeArePreSolveEventsEnabled(int index1, int wg) => + _callB('f2d_shape_are_pre_solve_events_enabled', [index1, wg]); + + @override + void shapeEnablePreSolveEvents(int index1, int wg, {required bool enabled}) => + _call('f2d_shape_enable_pre_solve_events', [index1, wg, _b(enabled)]); + + @override + bool shapeTestPoint(int index1, int wg, double x, double y) => + _callB('f2d_shape_test_point', [index1, wg, x, y]); + + @override + (double, double, double, double) shapeGetAabb(int index1, int wg) { + _call('f2d_shape_get_aabb', [index1, wg, _out]); + return ( + _runtime.readF32(_out), + _runtime.readF32(_out + 4), + _runtime.readF32(_out + 8), + _runtime.readF32(_out + 12), + ); + } + + // Chains. + + @override + (int, int) createChain( + int bodyIndex1, + int bodyWg, { + required List points, + required List materials, + required int categoryBits, + required int maskBits, + required int groupIndex, + required bool isLoop, + required bool enableSensorEvents, + }) { + final pointBytes = points.length * 4; + final buffer = _runtime.bulk(pointBytes + materials.length * 4); + _runtime + ..writeF32List(buffer, points) + ..writeF32List(buffer + pointBytes, materials); + final (categoryLow, categoryHigh) = _splitBits(categoryBits); + final (maskLow, maskHigh) = _splitBits(maskBits); + _call('f2d_create_chain', [ + bodyIndex1, + bodyWg, + buffer, + points.length ~/ 2, + buffer + pointBytes, + materials.length ~/ 6, + categoryLow, + categoryHigh, + maskLow, + maskHigh, + groupIndex, + _b(isLoop), + _b(enableSensorEvents), + _out, + ]); + return _outIdPair(); + } + + @override + void destroyChain(int index1, int wg) => + _call('f2d_destroy_chain', [index1, wg]); + + @override + bool chainIsValid(int index1, int wg) => + _callB('f2d_chain_is_valid', [index1, wg]); + + @override + void chainSetFriction(int index1, int wg, double friction) => + _call('f2d_chain_set_friction', [index1, wg, friction]); + + @override + double chainGetFriction(int index1, int wg) => + _callF('f2d_chain_get_friction', [index1, wg]); + + @override + void chainSetRestitution(int index1, int wg, double restitution) => + _call('f2d_chain_set_restitution', [index1, wg, restitution]); + + @override + double chainGetRestitution(int index1, int wg) => + _callF('f2d_chain_get_restitution', [index1, wg]); + + @override + List chainGetSegments(int index1, int wg) { + final count = _callI('f2d_chain_get_segment_count', [index1, wg]); + if (count == 0) { + return const []; + } + final buffer = _runtime.bulk(count * 8); + final written = _callI('f2d_chain_get_segments', [ + index1, + wg, + buffer, + count, + ]); + return _runtime.readI32List(buffer, written * 2); + } + + // Joints. + + @override + (int, int) createDistanceJoint( + int worldId, { + required (int, int) bodyA, + required (int, int) bodyB, + required (double, double) localAnchorA, + required (double, double) localAnchorB, + required double length, + required bool enableSpring, + required double hertz, + required double dampingRatio, + required bool enableLimit, + required double minLength, + required double maxLength, + required bool enableMotor, + required double maxMotorForce, + required double motorSpeed, + required bool collideConnected, + }) { + _call('f2d_create_distance_joint', [ + worldId, + bodyA.$1, + bodyA.$2, + bodyB.$1, + bodyB.$2, + localAnchorA.$1, + localAnchorA.$2, + localAnchorB.$1, + localAnchorB.$2, + length, + _b(enableSpring), + hertz, + dampingRatio, + _b(enableLimit), + minLength, + maxLength, + _b(enableMotor), + maxMotorForce, + motorSpeed, + _b(collideConnected), + _out, + ]); + return _outIdPair(); + } + + @override + (int, int) createFilterJoint( + int worldId, { + required (int, int) bodyA, + required (int, int) bodyB, + }) { + _call('f2d_create_filter_joint', [ + worldId, + bodyA.$1, + bodyA.$2, + bodyB.$1, + bodyB.$2, + _out, + ]); + return _outIdPair(); + } + + @override + (int, int) createMotorJoint( + int worldId, { + required (int, int) bodyA, + required (int, int) bodyB, + required (double, double) linearOffset, + required double angularOffset, + required double maxForce, + required double maxTorque, + required double correctionFactor, + required bool collideConnected, + }) { + _call('f2d_create_motor_joint', [ + worldId, + bodyA.$1, + bodyA.$2, + bodyB.$1, + bodyB.$2, + linearOffset.$1, + linearOffset.$2, + angularOffset, + maxForce, + maxTorque, + correctionFactor, + _b(collideConnected), + _out, + ]); + return _outIdPair(); + } + + @override + (int, int) createMouseJoint( + int worldId, { + required (int, int) bodyA, + required (int, int) bodyB, + required (double, double) target, + required double hertz, + required double dampingRatio, + required double maxForce, + required bool collideConnected, + }) { + _call('f2d_create_mouse_joint', [ + worldId, + bodyA.$1, + bodyA.$2, + bodyB.$1, + bodyB.$2, + target.$1, + target.$2, + hertz, + dampingRatio, + maxForce, + _b(collideConnected), + _out, + ]); + return _outIdPair(); + } + + @override + (int, int) createPrismaticJoint( + int worldId, { + required (int, int) bodyA, + required (int, int) bodyB, + required (double, double) localAnchorA, + required (double, double) localAnchorB, + required (double, double) localAxisA, + required double referenceAngle, + required double targetTranslation, + required bool enableSpring, + required double hertz, + required double dampingRatio, + required bool enableLimit, + required double lowerTranslation, + required double upperTranslation, + required bool enableMotor, + required double maxMotorForce, + required double motorSpeed, + required bool collideConnected, + }) { + _call('f2d_create_prismatic_joint', [ + worldId, + bodyA.$1, + bodyA.$2, + bodyB.$1, + bodyB.$2, + localAnchorA.$1, + localAnchorA.$2, + localAnchorB.$1, + localAnchorB.$2, + localAxisA.$1, + localAxisA.$2, + referenceAngle, + targetTranslation, + _b(enableSpring), + hertz, + dampingRatio, + _b(enableLimit), + lowerTranslation, + upperTranslation, + _b(enableMotor), + maxMotorForce, + motorSpeed, + _b(collideConnected), + _out, + ]); + return _outIdPair(); + } + + @override + (int, int) createRevoluteJoint( + int worldId, { + required (int, int) bodyA, + required (int, int) bodyB, + required (double, double) localAnchorA, + required (double, double) localAnchorB, + required double referenceAngle, + required double targetAngle, + required bool enableSpring, + required double hertz, + required double dampingRatio, + required bool enableLimit, + required double lowerAngle, + required double upperAngle, + required bool enableMotor, + required double maxMotorTorque, + required double motorSpeed, + required double drawSize, + required bool collideConnected, + }) { + _call('f2d_create_revolute_joint', [ + worldId, + bodyA.$1, + bodyA.$2, + bodyB.$1, + bodyB.$2, + localAnchorA.$1, + localAnchorA.$2, + localAnchorB.$1, + localAnchorB.$2, + referenceAngle, + targetAngle, + _b(enableSpring), + hertz, + dampingRatio, + _b(enableLimit), + lowerAngle, + upperAngle, + _b(enableMotor), + maxMotorTorque, + motorSpeed, + drawSize, + _b(collideConnected), + _out, + ]); + return _outIdPair(); + } + + @override + (int, int) createWeldJoint( + int worldId, { + required (int, int) bodyA, + required (int, int) bodyB, + required (double, double) localAnchorA, + required (double, double) localAnchorB, + required double referenceAngle, + required double linearHertz, + required double angularHertz, + required double linearDampingRatio, + required double angularDampingRatio, + required bool collideConnected, + }) { + _call('f2d_create_weld_joint', [ + worldId, + bodyA.$1, + bodyA.$2, + bodyB.$1, + bodyB.$2, + localAnchorA.$1, + localAnchorA.$2, + localAnchorB.$1, + localAnchorB.$2, + referenceAngle, + linearHertz, + angularHertz, + linearDampingRatio, + angularDampingRatio, + _b(collideConnected), + _out, + ]); + return _outIdPair(); + } + + @override + (int, int) createWheelJoint( + int worldId, { + required (int, int) bodyA, + required (int, int) bodyB, + required (double, double) localAnchorA, + required (double, double) localAnchorB, + required (double, double) localAxisA, + required bool enableSpring, + required double hertz, + required double dampingRatio, + required bool enableLimit, + required double lowerTranslation, + required double upperTranslation, + required bool enableMotor, + required double maxMotorTorque, + required double motorSpeed, + required bool collideConnected, + }) { + _call('f2d_create_wheel_joint', [ + worldId, + bodyA.$1, + bodyA.$2, + bodyB.$1, + bodyB.$2, + localAnchorA.$1, + localAnchorA.$2, + localAnchorB.$1, + localAnchorB.$2, + localAxisA.$1, + localAxisA.$2, + _b(enableSpring), + hertz, + dampingRatio, + _b(enableLimit), + lowerTranslation, + upperTranslation, + _b(enableMotor), + maxMotorTorque, + motorSpeed, + _b(collideConnected), + _out, + ]); + return _outIdPair(); + } + + @override + void destroyJoint(int index1, int wg) => + _call('f2d_destroy_joint', [index1, wg]); + + @override + bool jointIsValid(int index1, int wg) => + _callB('f2d_joint_is_valid', [index1, wg]); + + @override + int jointGetType(int index1, int wg) => + _callI('f2d_joint_get_type', [index1, wg]); + + @override + (int, int) jointGetBodyA(int index1, int wg) { + _call('f2d_joint_get_body_a', [index1, wg, _out]); + return _outIdPair(); + } + + @override + (int, int) jointGetBodyB(int index1, int wg) { + _call('f2d_joint_get_body_b', [index1, wg, _out]); + return _outIdPair(); + } + + @override + (double, double) jointGetLocalAnchorA(int index1, int wg) { + _call('f2d_joint_get_local_anchor_a', [index1, wg, _out]); + return _outVec2(); + } + + @override + (double, double) jointGetLocalAnchorB(int index1, int wg) { + _call('f2d_joint_get_local_anchor_b', [index1, wg, _out]); + return _outVec2(); + } + + @override + bool jointGetCollideConnected(int index1, int wg) => + _callB('f2d_joint_get_collide_connected', [index1, wg]); + + @override + void jointSetCollideConnected(int index1, int wg, {required bool value}) => + _call('f2d_joint_set_collide_connected', [index1, wg, _b(value)]); + + @override + void jointWakeBodies(int index1, int wg) => + _call('f2d_joint_wake_bodies', [index1, wg]); + + @override + (double, double) jointGetConstraintForce(int index1, int wg) { + _call('f2d_joint_get_constraint_force', [index1, wg, _out]); + return _outVec2(); + } + + @override + double jointGetConstraintTorque(int index1, int wg) => + _callF('f2d_joint_get_constraint_torque', [index1, wg]); + + // Distance joint. + + @override + double distanceJointGetLength(int index1, int wg) => + _callF('f2d_distance_joint_get_length', [index1, wg]); + + @override + void distanceJointSetLength(int index1, int wg, double length) => + _call('f2d_distance_joint_set_length', [index1, wg, length]); + + @override + double distanceJointGetCurrentLength(int index1, int wg) => + _callF('f2d_distance_joint_get_current_length', [index1, wg]); + + @override + bool distanceJointIsSpringEnabled(int index1, int wg) => + _callB('f2d_distance_joint_is_spring_enabled', [index1, wg]); + + @override + void distanceJointEnableSpring(int index1, int wg, {required bool enabled}) => + _call('f2d_distance_joint_enable_spring', [index1, wg, _b(enabled)]); + + @override + double distanceJointGetSpringHertz(int index1, int wg) => + _callF('f2d_distance_joint_get_spring_hertz', [index1, wg]); + + @override + void distanceJointSetSpringHertz(int index1, int wg, double hertz) => + _call('f2d_distance_joint_set_spring_hertz', [index1, wg, hertz]); + + @override + double distanceJointGetSpringDampingRatio(int index1, int wg) => + _callF('f2d_distance_joint_get_spring_damping_ratio', [index1, wg]); + + @override + void distanceJointSetSpringDampingRatio(int index1, int wg, double ratio) => + _call('f2d_distance_joint_set_spring_damping_ratio', [ + index1, + wg, + ratio, + ]); + + @override + bool distanceJointIsLimitEnabled(int index1, int wg) => + _callB('f2d_distance_joint_is_limit_enabled', [index1, wg]); + + @override + void distanceJointEnableLimit(int index1, int wg, {required bool enabled}) => + _call('f2d_distance_joint_enable_limit', [index1, wg, _b(enabled)]); + + @override + double distanceJointGetMinLength(int index1, int wg) => + _callF('f2d_distance_joint_get_min_length', [index1, wg]); + + @override + double distanceJointGetMaxLength(int index1, int wg) => + _callF('f2d_distance_joint_get_max_length', [index1, wg]); + + @override + void distanceJointSetLengthRange( + int index1, + int wg, + double minLength, + double maxLength, + ) => _call('f2d_distance_joint_set_length_range', [ + index1, + wg, + minLength, + maxLength, + ]); + + @override + bool distanceJointIsMotorEnabled(int index1, int wg) => + _callB('f2d_distance_joint_is_motor_enabled', [index1, wg]); + + @override + void distanceJointEnableMotor(int index1, int wg, {required bool enabled}) => + _call('f2d_distance_joint_enable_motor', [index1, wg, _b(enabled)]); + + @override + double distanceJointGetMotorSpeed(int index1, int wg) => + _callF('f2d_distance_joint_get_motor_speed', [index1, wg]); + + @override + void distanceJointSetMotorSpeed(int index1, int wg, double speed) => + _call('f2d_distance_joint_set_motor_speed', [index1, wg, speed]); + + @override + double distanceJointGetMaxMotorForce(int index1, int wg) => + _callF('f2d_distance_joint_get_max_motor_force', [index1, wg]); + + @override + void distanceJointSetMaxMotorForce(int index1, int wg, double force) => + _call('f2d_distance_joint_set_max_motor_force', [index1, wg, force]); + + @override + double distanceJointGetMotorForce(int index1, int wg) => + _callF('f2d_distance_joint_get_motor_force', [index1, wg]); + + // Motor joint. + + @override + (double, double) motorJointGetLinearOffset(int index1, int wg) { + _call('f2d_motor_joint_get_linear_offset', [index1, wg, _out]); + return _outVec2(); + } + + @override + void motorJointSetLinearOffset(int index1, int wg, double x, double y) => + _call('f2d_motor_joint_set_linear_offset', [index1, wg, x, y]); + + @override + double motorJointGetAngularOffset(int index1, int wg) => + _callF('f2d_motor_joint_get_angular_offset', [index1, wg]); + + @override + void motorJointSetAngularOffset(int index1, int wg, double offset) => + _call('f2d_motor_joint_set_angular_offset', [index1, wg, offset]); + + @override + double motorJointGetMaxForce(int index1, int wg) => + _callF('f2d_motor_joint_get_max_force', [index1, wg]); + + @override + void motorJointSetMaxForce(int index1, int wg, double force) => + _call('f2d_motor_joint_set_max_force', [index1, wg, force]); + + @override + double motorJointGetMaxTorque(int index1, int wg) => + _callF('f2d_motor_joint_get_max_torque', [index1, wg]); + + @override + void motorJointSetMaxTorque(int index1, int wg, double torque) => + _call('f2d_motor_joint_set_max_torque', [index1, wg, torque]); + + @override + double motorJointGetCorrectionFactor(int index1, int wg) => + _callF('f2d_motor_joint_get_correction_factor', [index1, wg]); + + @override + void motorJointSetCorrectionFactor(int index1, int wg, double factor) => + _call('f2d_motor_joint_set_correction_factor', [index1, wg, factor]); + + // Mouse joint. + + @override + (double, double) mouseJointGetTarget(int index1, int wg) { + _call('f2d_mouse_joint_get_target', [index1, wg, _out]); + return _outVec2(); + } + + @override + void mouseJointSetTarget(int index1, int wg, double x, double y) => + _call('f2d_mouse_joint_set_target', [index1, wg, x, y]); + + @override + double mouseJointGetSpringHertz(int index1, int wg) => + _callF('f2d_mouse_joint_get_spring_hertz', [index1, wg]); + + @override + void mouseJointSetSpringHertz(int index1, int wg, double hertz) => + _call('f2d_mouse_joint_set_spring_hertz', [index1, wg, hertz]); + + @override + double mouseJointGetSpringDampingRatio(int index1, int wg) => + _callF('f2d_mouse_joint_get_spring_damping_ratio', [index1, wg]); + + @override + void mouseJointSetSpringDampingRatio(int index1, int wg, double ratio) => + _call('f2d_mouse_joint_set_spring_damping_ratio', [index1, wg, ratio]); + + @override + double mouseJointGetMaxForce(int index1, int wg) => + _callF('f2d_mouse_joint_get_max_force', [index1, wg]); + + @override + void mouseJointSetMaxForce(int index1, int wg, double force) => + _call('f2d_mouse_joint_set_max_force', [index1, wg, force]); + + // Prismatic joint. + + @override + bool prismaticJointIsSpringEnabled(int index1, int wg) => + _callB('f2d_prismatic_joint_is_spring_enabled', [index1, wg]); + + @override + void prismaticJointEnableSpring( + int index1, + int wg, { + required bool enabled, + }) => _call('f2d_prismatic_joint_enable_spring', [index1, wg, _b(enabled)]); + + @override + double prismaticJointGetSpringHertz(int index1, int wg) => + _callF('f2d_prismatic_joint_get_spring_hertz', [index1, wg]); + + @override + void prismaticJointSetSpringHertz(int index1, int wg, double hertz) => + _call('f2d_prismatic_joint_set_spring_hertz', [index1, wg, hertz]); + + @override + double prismaticJointGetSpringDampingRatio(int index1, int wg) => + _callF('f2d_prismatic_joint_get_spring_damping_ratio', [index1, wg]); + + @override + void prismaticJointSetSpringDampingRatio(int index1, int wg, double ratio) => + _call('f2d_prismatic_joint_set_spring_damping_ratio', [ + index1, + wg, + ratio, + ]); + + @override + double prismaticJointGetTargetTranslation(int index1, int wg) => + _callF('f2d_prismatic_joint_get_target_translation', [index1, wg]); + + @override + void prismaticJointSetTargetTranslation(int index1, int wg, double value) => + _call('f2d_prismatic_joint_set_target_translation', [ + index1, + wg, + value, + ]); + + @override + bool prismaticJointIsLimitEnabled(int index1, int wg) => + _callB('f2d_prismatic_joint_is_limit_enabled', [index1, wg]); + + @override + void prismaticJointEnableLimit(int index1, int wg, {required bool enabled}) => + _call('f2d_prismatic_joint_enable_limit', [index1, wg, _b(enabled)]); + + @override + double prismaticJointGetLowerLimit(int index1, int wg) => + _callF('f2d_prismatic_joint_get_lower_limit', [index1, wg]); + + @override + double prismaticJointGetUpperLimit(int index1, int wg) => + _callF('f2d_prismatic_joint_get_upper_limit', [index1, wg]); + + @override + void prismaticJointSetLimits( + int index1, + int wg, + double lower, + double upper, + ) => _call('f2d_prismatic_joint_set_limits', [index1, wg, lower, upper]); + + @override + bool prismaticJointIsMotorEnabled(int index1, int wg) => + _callB('f2d_prismatic_joint_is_motor_enabled', [index1, wg]); + + @override + void prismaticJointEnableMotor(int index1, int wg, {required bool enabled}) => + _call('f2d_prismatic_joint_enable_motor', [index1, wg, _b(enabled)]); + + @override + double prismaticJointGetMotorSpeed(int index1, int wg) => + _callF('f2d_prismatic_joint_get_motor_speed', [index1, wg]); + + @override + void prismaticJointSetMotorSpeed(int index1, int wg, double speed) => + _call('f2d_prismatic_joint_set_motor_speed', [index1, wg, speed]); + + @override + double prismaticJointGetMaxMotorForce(int index1, int wg) => + _callF('f2d_prismatic_joint_get_max_motor_force', [index1, wg]); + + @override + void prismaticJointSetMaxMotorForce(int index1, int wg, double force) => + _call('f2d_prismatic_joint_set_max_motor_force', [index1, wg, force]); + + @override + double prismaticJointGetMotorForce(int index1, int wg) => + _callF('f2d_prismatic_joint_get_motor_force', [index1, wg]); + + @override + double prismaticJointGetTranslation(int index1, int wg) => + _callF('f2d_prismatic_joint_get_translation', [index1, wg]); + + @override + double prismaticJointGetSpeed(int index1, int wg) => + _callF('f2d_prismatic_joint_get_speed', [index1, wg]); + + // Revolute joint. + + @override + bool revoluteJointIsSpringEnabled(int index1, int wg) => + _callB('f2d_revolute_joint_is_spring_enabled', [index1, wg]); + + @override + void revoluteJointEnableSpring(int index1, int wg, {required bool enabled}) => + _call('f2d_revolute_joint_enable_spring', [index1, wg, _b(enabled)]); + + @override + double revoluteJointGetSpringHertz(int index1, int wg) => + _callF('f2d_revolute_joint_get_spring_hertz', [index1, wg]); + + @override + void revoluteJointSetSpringHertz(int index1, int wg, double hertz) => + _call('f2d_revolute_joint_set_spring_hertz', [index1, wg, hertz]); + + @override + double revoluteJointGetSpringDampingRatio(int index1, int wg) => + _callF('f2d_revolute_joint_get_spring_damping_ratio', [index1, wg]); + + @override + void revoluteJointSetSpringDampingRatio(int index1, int wg, double ratio) => + _call('f2d_revolute_joint_set_spring_damping_ratio', [ + index1, + wg, + ratio, + ]); + + @override + double revoluteJointGetTargetAngle(int index1, int wg) => + _callF('f2d_revolute_joint_get_target_angle', [index1, wg]); + + @override + void revoluteJointSetTargetAngle(int index1, int wg, double angle) => + _call('f2d_revolute_joint_set_target_angle', [index1, wg, angle]); + + @override + double revoluteJointGetAngle(int index1, int wg) => + _callF('f2d_revolute_joint_get_angle', [index1, wg]); + + @override + bool revoluteJointIsLimitEnabled(int index1, int wg) => + _callB('f2d_revolute_joint_is_limit_enabled', [index1, wg]); + + @override + void revoluteJointEnableLimit(int index1, int wg, {required bool enabled}) => + _call('f2d_revolute_joint_enable_limit', [index1, wg, _b(enabled)]); + + @override + double revoluteJointGetLowerLimit(int index1, int wg) => + _callF('f2d_revolute_joint_get_lower_limit', [index1, wg]); + + @override + double revoluteJointGetUpperLimit(int index1, int wg) => + _callF('f2d_revolute_joint_get_upper_limit', [index1, wg]); + + @override + void revoluteJointSetLimits(int index1, int wg, double lower, double upper) => + _call('f2d_revolute_joint_set_limits', [index1, wg, lower, upper]); + + @override + bool revoluteJointIsMotorEnabled(int index1, int wg) => + _callB('f2d_revolute_joint_is_motor_enabled', [index1, wg]); + + @override + void revoluteJointEnableMotor(int index1, int wg, {required bool enabled}) => + _call('f2d_revolute_joint_enable_motor', [index1, wg, _b(enabled)]); + + @override + double revoluteJointGetMotorSpeed(int index1, int wg) => + _callF('f2d_revolute_joint_get_motor_speed', [index1, wg]); + + @override + void revoluteJointSetMotorSpeed(int index1, int wg, double speed) => + _call('f2d_revolute_joint_set_motor_speed', [index1, wg, speed]); + + @override + double revoluteJointGetMaxMotorTorque(int index1, int wg) => + _callF('f2d_revolute_joint_get_max_motor_torque', [index1, wg]); + + @override + void revoluteJointSetMaxMotorTorque(int index1, int wg, double torque) => + _call('f2d_revolute_joint_set_max_motor_torque', [index1, wg, torque]); + + @override + double revoluteJointGetMotorTorque(int index1, int wg) => + _callF('f2d_revolute_joint_get_motor_torque', [index1, wg]); + + // Weld joint. + + @override + double weldJointGetLinearHertz(int index1, int wg) => + _callF('f2d_weld_joint_get_linear_hertz', [index1, wg]); + + @override + void weldJointSetLinearHertz(int index1, int wg, double hertz) => + _call('f2d_weld_joint_set_linear_hertz', [index1, wg, hertz]); + + @override + double weldJointGetAngularHertz(int index1, int wg) => + _callF('f2d_weld_joint_get_angular_hertz', [index1, wg]); + + @override + void weldJointSetAngularHertz(int index1, int wg, double hertz) => + _call('f2d_weld_joint_set_angular_hertz', [index1, wg, hertz]); + + @override + double weldJointGetLinearDampingRatio(int index1, int wg) => + _callF('f2d_weld_joint_get_linear_damping_ratio', [index1, wg]); + + @override + void weldJointSetLinearDampingRatio(int index1, int wg, double ratio) => + _call('f2d_weld_joint_set_linear_damping_ratio', [index1, wg, ratio]); + + @override + double weldJointGetAngularDampingRatio(int index1, int wg) => + _callF('f2d_weld_joint_get_angular_damping_ratio', [index1, wg]); + + @override + void weldJointSetAngularDampingRatio(int index1, int wg, double ratio) => + _call('f2d_weld_joint_set_angular_damping_ratio', [index1, wg, ratio]); + + // Wheel joint. + + @override + bool wheelJointIsSpringEnabled(int index1, int wg) => + _callB('f2d_wheel_joint_is_spring_enabled', [index1, wg]); + + @override + void wheelJointEnableSpring(int index1, int wg, {required bool enabled}) => + _call('f2d_wheel_joint_enable_spring', [index1, wg, _b(enabled)]); + + @override + double wheelJointGetSpringHertz(int index1, int wg) => + _callF('f2d_wheel_joint_get_spring_hertz', [index1, wg]); + + @override + void wheelJointSetSpringHertz(int index1, int wg, double hertz) => + _call('f2d_wheel_joint_set_spring_hertz', [index1, wg, hertz]); + + @override + double wheelJointGetSpringDampingRatio(int index1, int wg) => + _callF('f2d_wheel_joint_get_spring_damping_ratio', [index1, wg]); + + @override + void wheelJointSetSpringDampingRatio(int index1, int wg, double ratio) => + _call('f2d_wheel_joint_set_spring_damping_ratio', [index1, wg, ratio]); + + @override + bool wheelJointIsLimitEnabled(int index1, int wg) => + _callB('f2d_wheel_joint_is_limit_enabled', [index1, wg]); + + @override + void wheelJointEnableLimit(int index1, int wg, {required bool enabled}) => + _call('f2d_wheel_joint_enable_limit', [index1, wg, _b(enabled)]); + + @override + double wheelJointGetLowerLimit(int index1, int wg) => + _callF('f2d_wheel_joint_get_lower_limit', [index1, wg]); + + @override + double wheelJointGetUpperLimit(int index1, int wg) => + _callF('f2d_wheel_joint_get_upper_limit', [index1, wg]); + + @override + void wheelJointSetLimits(int index1, int wg, double lower, double upper) => + _call('f2d_wheel_joint_set_limits', [index1, wg, lower, upper]); + + @override + bool wheelJointIsMotorEnabled(int index1, int wg) => + _callB('f2d_wheel_joint_is_motor_enabled', [index1, wg]); + + @override + void wheelJointEnableMotor(int index1, int wg, {required bool enabled}) => + _call('f2d_wheel_joint_enable_motor', [index1, wg, _b(enabled)]); + + @override + double wheelJointGetMotorSpeed(int index1, int wg) => + _callF('f2d_wheel_joint_get_motor_speed', [index1, wg]); + + @override + void wheelJointSetMotorSpeed(int index1, int wg, double speed) => + _call('f2d_wheel_joint_set_motor_speed', [index1, wg, speed]); + + @override + double wheelJointGetMaxMotorTorque(int index1, int wg) => + _callF('f2d_wheel_joint_get_max_motor_torque', [index1, wg]); + + @override + void wheelJointSetMaxMotorTorque(int index1, int wg, double torque) => + _call('f2d_wheel_joint_set_max_motor_torque', [index1, wg, torque]); + + @override + double wheelJointGetMotorTorque(int index1, int wg) => + _callF('f2d_wheel_joint_get_motor_torque', [index1, wg]); + + // Events. + + @override + RawContactEvents worldGetContactEvents(int worldId) { + _call('f2d_world_get_contact_event_counts', [worldId, _out]); + final beginCount = _runtime.readI32(_out); + final endCount = _runtime.readI32(_out + 4); + final hitCount = _runtime.readI32(_out + 8); + + var begin = []; + if (beginCount > 0) { + final buffer = _runtime.bulk(beginCount * 13 * 8); + final written = _callI('f2d_world_get_contact_begin_events', [ + worldId, + buffer, + ]); + final data = _runtime.readF64List(buffer, written * 13); + begin = [ + for (var i = 0; i < written; i++) + ( + shapeAIndex1: data[i * 13].toInt(), + shapeAWorldAndGeneration: data[i * 13 + 1].toInt(), + shapeBIndex1: data[i * 13 + 2].toInt(), + shapeBWorldAndGeneration: data[i * 13 + 3].toInt(), + normalX: data[i * 13 + 4], + normalY: data[i * 13 + 5], + points: [ + for (var p = 0; p < data[i * 13 + 6].toInt(); p++) + ( + x: data[i * 13 + 7 + 3 * p], + y: data[i * 13 + 8 + 3 * p], + separation: data[i * 13 + 9 + 3 * p], + ), + ], + ), + ]; + } + + var end = []; + if (endCount > 0) { + final buffer = _runtime.bulk(endCount * 4 * 8); + final written = _callI('f2d_world_get_contact_end_events', [ + worldId, + buffer, + ]); + final data = _runtime.readF64List(buffer, written * 4); + end = [ + for (var i = 0; i < written; i++) + ( + shapeAIndex1: data[i * 4].toInt(), + shapeAWorldAndGeneration: data[i * 4 + 1].toInt(), + shapeBIndex1: data[i * 4 + 2].toInt(), + shapeBWorldAndGeneration: data[i * 4 + 3].toInt(), + ), + ]; + } + + var hit = []; + if (hitCount > 0) { + final buffer = _runtime.bulk(hitCount * 9 * 8); + final written = _callI('f2d_world_get_contact_hit_events', [ + worldId, + buffer, + ]); + final data = _runtime.readF64List(buffer, written * 9); + hit = [ + for (var i = 0; i < written; i++) + ( + shapeAIndex1: data[i * 9].toInt(), + shapeAWorldAndGeneration: data[i * 9 + 1].toInt(), + shapeBIndex1: data[i * 9 + 2].toInt(), + shapeBWorldAndGeneration: data[i * 9 + 3].toInt(), + pointX: data[i * 9 + 4], + pointY: data[i * 9 + 5], + normalX: data[i * 9 + 6], + normalY: data[i * 9 + 7], + approachSpeed: data[i * 9 + 8], + ), + ]; + } + + return (begin: begin, end: end, hit: hit); + } + + @override + RawSensorEvents worldGetSensorEvents(int worldId) { + _call('f2d_world_get_sensor_event_counts', [worldId, _out]); + final beginCount = _runtime.readI32(_out); + final endCount = _runtime.readI32(_out + 4); + + List read(String function, int count) { + if (count == 0) { + return const []; + } + final buffer = _runtime.bulk(count * 4 * 8); + final written = _callI(function, [worldId, buffer]); + final data = _runtime.readF64List(buffer, written * 4); + return [ + for (var i = 0; i < written; i++) + ( + sensorIndex1: data[i * 4].toInt(), + sensorWorldAndGeneration: data[i * 4 + 1].toInt(), + visitorIndex1: data[i * 4 + 2].toInt(), + visitorWorldAndGeneration: data[i * 4 + 3].toInt(), + ), + ]; + } + + return ( + begin: read('f2d_world_get_sensor_begin_events', beginCount), + end: read('f2d_world_get_sensor_end_events', endCount), + ); + } + + @override + List worldGetBodyEvents(int worldId) { + final count = _callI('f2d_world_get_body_event_count', [worldId]); + if (count == 0) { + return const []; + } + final buffer = _runtime.bulk(count * 7 * 8); + final written = _callI('f2d_world_get_body_events', [worldId, buffer]); + final data = _runtime.readF64List(buffer, written * 7); + return [ + for (var i = 0; i < written; i++) + ( + bodyIndex1: data[i * 7].toInt(), + bodyWorldAndGeneration: data[i * 7 + 1].toInt(), + x: data[i * 7 + 2], + y: data[i * 7 + 3], + rotationCos: data[i * 7 + 4], + rotationSin: data[i * 7 + 5], + fellAsleep: data[i * 7 + 6] != 0, + ), + ]; + } + + // Queries. + + @override + RawRayHit? worldCastRayClosest( + int worldId, + double originX, + double originY, + double translationX, + double translationY, + int categoryBits, + int maskBits, + ) { + final (categoryLow, categoryHigh) = _splitBits(categoryBits); + final (maskLow, maskHigh) = _splitBits(maskBits); + final hitSomething = _callB('f2d_world_cast_ray_closest', [ + worldId, + originX, + originY, + translationX, + translationY, + categoryLow, + categoryHigh, + maskLow, + maskHigh, + _out, + ]); + if (!hitSomething) { + return null; + } + final data = _runtime.readF64List(_out, 7); + return ( + shapeIndex1: data[0].toInt(), + shapeWorldAndGeneration: data[1].toInt(), + pointX: data[2], + pointY: data[3], + normalX: data[4], + normalY: data[5], + fraction: data[6], + ); + } + + @override + void worldCastRay( + int worldId, + double originX, + double originY, + double translationX, + double translationY, + int categoryBits, + int maskBits, + double Function(RawRayHit hit) callback, + ) { + final (categoryLow, categoryHigh) = _splitBits(categoryBits); + final (maskLow, maskHigh) = _splitBits(maskBits); + final previous = _runtime.callbacks.castRay; + _runtime.callbacks.castRay = + (index1, wg, pointX, pointY, normalX, normalY, fraction) => callback(( + shapeIndex1: index1, + shapeWorldAndGeneration: wg, + pointX: pointX, + pointY: pointY, + normalX: normalX, + normalY: normalY, + fraction: fraction, + )); + try { + _call('f2d_world_cast_ray', [ + worldId, + originX, + originY, + translationX, + translationY, + categoryLow, + categoryHigh, + maskLow, + maskHigh, + ]); + } finally { + _runtime.callbacks.castRay = previous; + } + } + + @override + List worldOverlapAabb( + int worldId, + double lowerX, + double lowerY, + double upperX, + double upperY, + int categoryBits, + int maskBits, + ) { + final (categoryLow, categoryHigh) = _splitBits(categoryBits); + final (maskLow, maskHigh) = _splitBits(maskBits); + final results = []; + final previous = _runtime.callbacks.overlap; + _runtime.callbacks.overlap = (index1, wg) { + results + ..add(index1) + ..add(wg); + return true; + }; + try { + _call('f2d_world_overlap_aabb', [ + worldId, + lowerX, + lowerY, + upperX, + upperY, + categoryLow, + categoryHigh, + maskLow, + maskHigh, + ]); + } finally { + _runtime.callbacks.overlap = previous; + } + return results; + } + + @override + void worldExplode( + int worldId, { + required int maskBits, + required double positionX, + required double positionY, + required double radius, + required double falloff, + required double impulsePerLength, + }) { + final (maskLow, maskHigh) = _splitBits(maskBits); + _call('f2d_world_explode', [ + worldId, + maskLow, + maskHigh, + positionX, + positionY, + radius, + falloff, + impulsePerLength, + ]); + } + + // Simulation callbacks, dispatched by the world index embedded in the + // shape ids (the low 16 bits of worldAndGeneration). Note the id + // conventions: a world id's index1 is 1-based while a shape id's world0 + // is 0-based, hence the -1 when registering. + + final Map _filterCallbacks = {}; + final Map + _preSolveCallbacks = {}; + + static int _world0(int worldId) => (worldId & 0xFFFF) - 1; + + @override + void worldSetCustomFilterCallback( + int worldId, + bool Function( + int shapeAIndex1, + int shapeAWorldAndGeneration, + int shapeBIndex1, + int shapeBWorldAndGeneration, + )? + callback, + ) { + if (callback == null) { + _filterCallbacks.remove(_world0(worldId)); + _call('f2d_world_set_custom_filter', [worldId, 0]); + } else { + _filterCallbacks[_world0(worldId)] = callback; + _runtime.callbacks.customFilter = (a1, awg, b1, bwg) => + _filterCallbacks[awg & 0xFFFF]?.call(a1, awg, b1, bwg) ?? true; + _call('f2d_world_set_custom_filter', [worldId, 1]); + } + } + + @override + void worldSetPreSolveCallback( + int worldId, + bool Function( + int shapeAIndex1, + int shapeAWorldAndGeneration, + int shapeBIndex1, + int shapeBWorldAndGeneration, + double normalX, + double normalY, + )? + callback, + ) { + if (callback == null) { + _preSolveCallbacks.remove(_world0(worldId)); + _call('f2d_world_set_pre_solve', [worldId, 0]); + } else { + _preSolveCallbacks[_world0(worldId)] = callback; + _runtime.callbacks.preSolve = (a1, awg, b1, bwg, normalX, normalY) => + _preSolveCallbacks[awg & 0xFFFF]?.call( + a1, + awg, + b1, + bwg, + normalX, + normalY, + ) ?? + true; + _call('f2d_world_set_pre_solve', [worldId, 1]); + } + } + + // Debug drawing. + + @override + void worldDraw(int worldId, RawDebugDraw draw) { + final previous = _runtime.callbacks.draw; + _runtime.callbacks.draw = WasmDrawTarget( + drawPolygon: draw.drawPolygon, + drawSolidPolygon: draw.drawSolidPolygon, + drawCircle: draw.drawCircle, + drawSolidCircle: draw.drawSolidCircle, + drawSolidCapsule: draw.drawSolidCapsule, + drawSegment: draw.drawSegment, + drawTransform: draw.drawTransform, + drawPoint: draw.drawPoint, + drawString: draw.drawString, + ); + try { + final bounds = draw.drawingBounds; + _call('f2d_world_draw', [ + worldId, + _b(bounds != null), + bounds?.$1 ?? 0, + bounds?.$2 ?? 0, + bounds?.$3 ?? 0, + bounds?.$4 ?? 0, + _b(draw.drawShapes), + _b(draw.drawJoints), + _b(draw.drawJointExtras), + _b(draw.drawBounds), + _b(draw.drawMass), + _b(draw.drawBodyNames), + _b(draw.drawContacts), + _b(draw.drawGraphColors), + _b(draw.drawContactNormals), + _b(draw.drawContactImpulses), + _b(draw.drawContactFeatures), + _b(draw.drawFrictionImpulses), + _b(draw.drawIslands), + ]); + } finally { + _runtime.callbacks.draw = previous; + } + } +} diff --git a/packages/forge2d/lib/src/backend/wasm/box2d.wasm b/packages/forge2d/lib/src/backend/wasm/box2d.wasm new file mode 100755 index 0000000000000000000000000000000000000000..8a1905346b9ed2398a4dc78b0db12944932c1579 GIT binary patch literal 226900 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 z
j>DM6_VNe2+j}^H_$VuFjq?*Vm)iJ_r`jn)4gPT}!!$GPyx=lz@km?PVEUDgT zX$|V9*MiazHH<MB0smJ47VX4(g$$ zs8z~CS8{*lM4Y@KB2N815fKt=b;XOW$tD~%MOn&Wo>z9(O^tPhcnm50P0VjX#O0No zzFR94n?wa6C`5ohFF}FUMdUbwKPEXSa!B=|=)91S-PCH2kVxA}^{o*SC3g!+#cf%! zU0H?lMxoBO72@A{+eW}4kn!30WF9Nz#iHx+N-DfOW5nC7 zDEm=z?jwjM470Hty)7lS&W+rbI$adK=BeaHxwj#X&UY|*I@W6+iXOV88}*N&$CBy8 zF_a%dM~hK(A&t*fNxPNsDnlTV_F5P!HLOY63B);&d{mVXb^MI3^x~tsTs}yk3Xac% zjE-D*JkGU@<IZi{R_MbPlW)(&q_CB#BtQzZ;i zofiFlIfJtxZ`PL{_9SLS+nNMyC+~cZn*Iu%cn&8WL45U7{+n?GaU8 z_ljze+vPLkC%}AbNurdQ`(i>0{E7;1q#R|}6BHyGT zjJp|B>N0~4w^dn>TpNTjk%%7-WrBr_31EZLD4klJoKy^A2I=K|W3ZX348_VdF1xvo zsfJM?gA0wjOg*4QSC?i2GwAZjD8uAs~-hTgcVi9m+Qhhl}xdJ_n{$6~uoeG%5H zQNr-BaN#ga4?RW88!|0qO)swkDN~TL5^W=qC019mF%z~Z^G!jl$9xm8J$n?Qvb7bW za-k4$2-HMgtWVTwrQiC*3hc}nH0Yo%MrG}fKyNg$W()+0iwhXIeh`IiUi=N)u9*NM z;E1!BckC`K4i)h^nIS*~tS1eX%pU|#Mj18Ao?-)lH=#6C5`pfg44NfuGAl|$<%G=_ zI!K$b z)+XkP01iV6t9n4Icql(zNg94Fk4Q#MYv%Iwqs*(go6Sof&6^5cbP(wMuK74KmC_rg z2^ik0_fr_IxF((}Gsohwp&8X&az|col^mwa(ZQP)J9er_nN$`ks?d`tT! zZR;XIhfIXEkZ7s1?N~=kWnQ6}L97{z9mJZ66l}<}iABrL4*^N*n?v$U;flg#g>CF$LBGk zYFbEj!$i@}C3{sN6U+nJ907K7$v$(P?ejRzK-~f-*WUL}MOsbwIC0m;$=x7Mtk8qg zP=N<@-m6f~B_TtMf+1sT#{n6kzdy7SL+nVP80$+#B6}d9DcP#c-4-0H09}(Jzp*yb z-d@Er&gH{e%dQz)btu7{vh!jV$_&vFpsq-y8-C<5^vs$wy3Dy@O>G0UDP?tnhw@9@ zv{^tcAYvhIT{MT}p^G{|z@84GXD_o*UTNM3ffIou2K5mG$x#i`eC4LA*PpB8wn7qhwKg4< zVZ*l=*xi7=r2q@Ou!Ql%?$ zi2%TbDr*7t{1wGkNIxnDgeMp}!L3m(KHwd-DbLNizBAjY%W+~nxE#$lUe4eY_H%Zt8@Y@EA@!ll*%bbo zJ-&hLWdzq--5!gC-vv@`mdPeJRXLi*b=SzJF=@th8|5<^47n@i+g4XrmgOqk;BF!# zxtnepV`|iqXUuJ5zN#I;d15tb_foFiblU@Exm>!_dH{aFP8vj44*%+A?dM9^K*rzw z)7f^{fD83HJlG{1rt*w8+{|9qC@T@}RM`u_qjx35RK4;<6RU!6s@vOdDw`57=FmWf(u=h8i9jYdJR!?Y*<+^H9QZ1PwOGUaKfuHOB#CWZ)sfGrYXw94QTUN8xb8^LBVz zf_B)bFhj$~NyP1oF+r{3C>ygxf=c=}x=Emf$GL%XItQbnF*Ps7yexT@;O$?~zA6b#u~l`7ZWXH_4}Kl#SH8!3YQ) zt(o8yx@)5z_EB5E!b(FdH2NY-j+mNdapN1~wUh z4GjX;8~|*SfHemL+Z2E`2LanK02nfgB{UdVBLLel2v~Ihuwen48VqbW0AtL?G2Xv{ zngb~;J&KdGd-HU*rMsen`>JSWa7E=%5iY7tZn(c9%wr-D2c&Q8Pea2QjI~Iyx?wQ- z(g(n0glxKMKWuibZ(t{4-%j#k7kdv8n7#@|UwzXaMqm1X9qQT4074EcP2H#Sm-UEA zAUNBO-S_}pE9mIS0qGO{Y1kX=i0)5kAe`z?#aOW1eJ#t~mI1l9IXkn6UitYL3xe5c z>G>F&MmI0Ue!1T66HYwX&cLXL&l3vB)YIi!KiO@UwW+{f;OU$GyEW0 zec8vX@~0Edir_Q@_Q`CQUsc5tDwy(`>zL$jbrLz3s}8$N!E#Y@nXuM z3R?=f({TV23%$9*R{({R7XX4JM}+}6dsaW4=Hq&eJ5utI=9B)l+U!{^CJ*^`vF@d} zFDSc}!o%Ye+Syg;g*dlK@!FvM_xwb(;O9$5(SQ9#gFXu_7QdUsyY4nQ$ zJA{~ zHe)FwMB!~7U?~vuS-eNq9d)3_4rQ3=rQs-|B-tFHLWpa6a(g!k5FovwXb~EvAwXMlX+^Jjy6}L@8Ni+#7i;oiZC?K}VJO%$$tly9n>g@R>iOOBqQn z8(qdkmr~F^PsamKKcXTNMr+_7#TYf>{1wF8ZrFjY<1@w~!kC*Dk%UG*qJ~XEKYKFG z+Qy|0k0f)J2gkFp^)G2)t#ivRJ7lpOaMs`}C0YjOE56b)n1u>;9pzk}z5#%V=w`$y zRKhqbm%eIN35=4p1BRxnU9$`_hcRF0^vK%sXZ4gKi@_^tUZV3W!lcA!*FdTxe{}SZ zKlVr24ZXEvEY%To&_DKqHpXjpE%>sGVxc?`o5sNGgR!Z>f*>#mhaFb#Vw`ORrK&apI~%(p&mnG$7k$#^BwEI(>~U)lv)r(|&E$x2Mk$;B4= zaAGXbF-8d~7WG`pO6SBs&Nc!yJlS?GpH|;3D2~w)*AQzFaut>3F5|AGRWTOT zN|uX7F_wSH#fA(Wl*Ia&ow9QWq;2UyfuvP^({xr!po=Gw_UGm1Nrp@=353$gLU#iZ z@Be)Lw_R!D#c|fr<9D;Jb1C7B6&7}){&7hMFChyA6u5O>+mh*r;%ri=<0qZg^}}(t zO;=8K*{N3;Q&29@}iKMImVlzX?^h$0iGj98e9H`X183$!309vi*IK-ralg|jlYIEzEc#%~i z%4!9xk-HM$biN|1J=8;mt7O}P7gNieTA)D;SRMER2;XDJVKJcJ8gul? z0UXMpZ-=Mk#MOS;KYfPNNyZN(U-D1;ko+&kFv}lXTh@sW3FpCPcGdz&rmZisI>w$v zJLiVF*z34>OT zyFpZcB-0sODGRYW_=vS@MCWE*5v85Ue2QX%64AZ+gpOdsH(Y6zbfjRV_2rjddTHnp zJIhxm+2}}RdUi_Oi&iOTAr@_w!>iVsf$$pb>fw%%v2aK4yn0_UbM^m8qF6tPep2fl zX{#X^tPl}%QP+=FO&PB5OWdIRRh_K|7t9L{p8jk-+6Z3Iyn|O+%SSm|MP^W@yr3F( ztMcsh93;`7{fo|tqRbzVnoTAUL;n$$K(3Rcd|f;yTpnW06PCf{IOZ);nv)jFdN-3j z(jH=xvPB(eIp1dBj{0pSRNTkiEtl!;n_1t`2AU~4Cf!5@{gMM=6^I3$WNpDyB+Z|+ z1CWkmJ<%q0r1J-oH|vfC>9>%&&uta-DG!~K2?U7XE%hz0TDQAxD^FpFZ@bKKrNCr< zF~i+G^j0**vhVv_mqBZVP7SPSa9su2B+y_|?M2~W|E{pQqNhLvyx&HJ?Wmd=2xvJ) zEi&$qN-;uBi%`=1nJ1JM>t;H)lUn#0NPv5sC?`BXO5o69%oew=I;W+Se0DX;V2wm_AK4rL0O{!fwEvZEiqil#XS0e^RyF{+@#@nRJRhW z!Q8n)@th7U>=4F|F!jW-+P1VQVx44gZK6f!4XIjQbO%u+^oJB8g#I9znQ~jz#?8nK zf+F$s6`6GS78*l5Z`P9z#!>4%lWm40R879wIxB}H+K@kkgymBv2YZtJjO!_xNt!e%!8E{hn0AqjgwomK-1DB70Mjro$M{1{AmWL z1gRmw&j*1K1F%CTDYl$Xt9^5aWe=OCH9d=(m5O0nx-YYs18&oM1{hUl2_b;k>rqsYl=&O<%4kcDt==XwwW@JcBbbJ)ti}Lr=1ej#({4WJEJBP3)l6|!#T|PS zvdt~2NzQdR$&kwsKf*mn{mnCsGVaSN7-GcM?q2MXQOw%Ke5?(FJMC|voxC6TO2#5> zJDiVAqYjaX!}+FZ3{b`6;DnfVJh5Ia>q}U09zMsB*_6qRNL46T0ld_%mpkfpn^`C4 zu!x!toS5N4oUpL)h7s+=QN;EtXcA|kq*PGqsWl_}=K}}T-8c&POh|3AT zQM*U1aiglAVq8m5ewp!4t#pRGC~O@w9Hj&e=5TeDXP)I3LCSg!-hNN zCGj?xy);>qmI>yiX1y6F+NK$D1V*46%&YJmHtiZ=u$?^8Hj;dZ{cwEHK!!qV0)7s+ zCOBlT?x$thR#VB@W`AM$`+#*}4zHGzMamOxH7z}bc9KPtZ4U99GLCcGE62l z5mhLsGeS}h(qd70owNfIBXA-;Dte38O3GQ7niwNqVJjzz()r{>i$_d}3AU4QSny0&_E*O@*e@ykX{*ku=4yO&y#b zEHZE@_b1DocQrHPYayqmZ;l7c+1mxHUYfzc!oNB-cIwkYa=Shr1O++0C3+U=SAUu5 ziRJy(Pk;JVZ2u!2>fg<97P(Bfn7OiEn0jeEQ~=F(JxKLW%G`4{{nZ2G`>sPQ3aBj$H+OGhuh{6a}yh35;ef*2kFnuOXR7B<|r`=>dReUmf< z$7H{JC3^)ybi0rAv~%T9+uhS>@9Fmdn{!DIv~py`1ecHe}mO!CXw|{o@87)jY!;g{rwjnO<#~w93i*X}3?Wyy=^XMp48(z3KLFUbNq*sO<*2a_v$_-f(yHHO<3 zT!4w@I` zZc5xi>LIS)Xoq+M7_>>I=eXO%W`Ke#+;0-;GbVMLl-TsFKo1%h*3wpe9CD3&$`~Dy zh85~_@ECfG8$GfO31il+@pFm)R#vLMd&=`wCNJzQMmm@=feSjlCHhsr*)G8hk(o&> z5_SS64nd1kAcswROZ3ZKd9VOkAXXk`pzO7iOu&zER5&1Iwj*6l8H$^jqb`$U6K!Wa zF?RS7+xp|3uK5&XV7S^qhHO&YV3zR^7wmXul5$twhXM~VZ(H#wt9?QzhwryUe1OzW zurd!rXlJ{D*0^8uh|Bb0yzFi-J6LGJ3JwGCmWh-*?P?)%6EBK{?*rZ+VhBS!I_P!` z3^$E0f>Ba7hGD#eeYYYlAL2l?kAnOR3OJ-}J(%3rZjLn$QPfhL=sJ+5MtAYXSRtE! zg$IU6c9JTjkYyf`nF&#c%0}}x2Q&~DdsYtnEh}jYab8QGF4I`F>B6UvE+!lQ=jkHZ zqbnBd_Vm$iL@%3N^g+Vb$|dws9IfcHNi^Zp6QWO3_v}1@KB5Q(yJV6wo6JyY(?@3p zgTUs?n%|n_UDlNHIBWc}avNCr**rYY?vyVeLm0>0PJJw=TX)l~!uB7wO9d5<#OG4k zAoNP9a6NN`zBIIT#C8v#LVI#N+yv^L0;aV=oW73b$9I#wE-p&*Y`R#_eBgxN;6o_Z zEw&T`!lp1F;9CLA9n|l2KQy6^39oLppm=#k1`m1pY>hz^%4RD@9%OFg z&1FJ~-`YD;4DN zGfvYN?13ZjSz6XBb{QDW}#ZZM=FJ&mWLEcl1KkB>`BqUurO}>h@ zGQ`Ep8%j-xmMl~$chH)g=D_@p*E~-+wkYe620vl-i&Z)_(%aQy&vxSCH&O|k9d(1% zyuqF(WR-UK!@3Heevn||Ht>R3kznehS`y5_9}W(K2}7b2;3gb*UN8$1_PuHg=1^NO ztJB!TieSgJLHr3TglqWYztiVW4OgxLxM=I^743symg<~%S-uvJ#*IfSYCAr^f(s}i zxFE`-CWGLjTA};~QQl-4$}2yV-e9^Fl_6Q+d84Pi>sAQNnYt8$2tQ$@-l!WY7q4Yg zKzNW38bR*4g75(FGdu_{Rnw&rx(E-r4IRQ4oz@87{pwnTCrH8wzg8pI$`9TX&CB{U zf&uR$JO}yesL?**N5+HjZxrEOAiPAFSA>^{@H-%U9hJQI{*iPmnq{MV1;d8hlx-6` zQWm06NbKThz7B%D;_yf!a$wnFYi1L)U&gK*&vv#_jyp@|f$;K9Damo^0#)tOVJ;JP z0tiFE%2UhwqT58UVCs1j=xp{_`4&~(#A>E$kfgWmx<5~*sfhSL$IUoNM8i(A;J1-m zZrD+4PMy{(%$4KD^fcCDqKspQ0oHqXKzJu7>7#NZr`e?{V2dbsy0UbHFM!#o=BIzA zjnCTIFoTi6WrAf#E>rYJF3qgL(2AXwuH-m_wdT*z&UG(}S4Ry%E1pO2X=fiDQ@Q5#5*OxvsB#pLw+{f=!I!1eDXIm6P)<*ceTFiRRNYN7XQ3fl!qV!z(LLfUgVbh2OVQ;d^jG zVxNvfV*KO-PI}zW_H0ITLB%HFlS@D$x<$=mJMT)Cno%N;p8rq#s~#FP2e<4aV6fYv zYXs01A3BD#yFOEd_JK4w`Ixaw`O$m%AeL}cKh^;}m}#JKbXC8{L5!tR?fj;XYg;+t zKZ?xIF+9jTK`U#jG7m&(QGfn(0;xKy@-(zGw}OSolPV+WGwb>&mj&*(aGAi1O~u^FFbPTS%` zJJ0G`JCHn~E0f*NCtPs^GtrJ)L?t|_D}GpFIrt>&f#iy=IQ!Tp4{r}7XSvWR@8QCv z!g+__97r-*<7vR!N1Yvf-x5b&J`j=yyL_50J+j=Py}kY;$@s?nBVU-dw?NZd|HWeM z%E^~KX-3mEowW37c3ZLj9BN?4pk!pD2kc|H2t%wq1LH4P&iKwEzDud4J6E%vjG)Dkl+DS#QzaLd zh=bX5ZQ$(VLd-4YnbOOV&bKG|Zxj3VCMnL};c1T5qK+xIjYO>@;@lB3n z=wSpgG1^-+YzSgaSAi~ADkkF^2Z18&RaVapeCL8X<6pBY%s}rl&<<}v=H!dTJq}Kr zTy}pxG)-L^ySXVbcVRV-bH=v&if5SGh%#j;lWB`=m76@(O2>A9;e!yA$@`hc07d#= z=Al>MyLIf?MGc|K$|{uttgz^I(MX)W%lU4czm~P_0bZ`-Sr%JBPmRoIg_y-mN{!6b zNS+%^IMv7<{&k6u8;X86StE_&v!GftBxhA~=5j?x+!Pp+@4?sNYxbAJ(`Hp_AL@2D zE3R!ioR>upPGe2*us|Ui=-ai&+Eq8MP9EXa)+3LM#E6j{4i5S5Lxggd1%YvM=Hp&< zH?e*#2pnCp(7=5Fv;0PZ=g!Nr>{7`J@?3B5s_dz3ydhZztTg zh>>=r_opiM+fIODv#aRAnfRU*9NTDpOCf->s4htJM!sDu%3zg@+#2uHo1MzFbE%a) zvc%`*z}%RL|BP9lD4FQ%^6aW%1ViUSK(nJR`lP8`NTEiVeY+&nn*jiP0_Ht3{WO8Is&qCxq=4|SN`H(F{j zT&D7khD*?zegOu5oY$L*?L>)k#vr(u=9E6x8jYswB(Pnc8)Z>5x6x1ao1;gD`P%Q{ zd|Y~)X}KGFy-_fL!GY{6mnhSe3y(f?J}f{Pt7pX;LD=zngbrL z=-VZQ*68RHCS-~|WlcwCp5QIjvMGu|6~v%h_Xxxuf!HHJc&kT{8F(83eaa97IM&@& zGK8jq$vfmWwo#B`gcQyz9Y-}RAPEH)q=f>@OG05+fr3?W4HOJTNYRf%1r(}=0!u;6 z4ccr_q^_~St2PkAlW)4$vw?MU=~{&waY3P6hYiN2fozb&Q(xT~&SI@cS)Wr^@{(sg zdNX}X9e7f233T?|T5&w;#9Kqp41HVS59-R>eMeo>!MBAD&J0x=Mn93)F1jtXd$sju z-^EP$XGoXLwnz`LHQW)oAkxDR8{Q!N4a&WkErB;y59o1WFk{U1^_jriBBtsyfoFJI zr#_RxUIrJOMaC`0N=l@yB>gzMhP~p(0QQpHfUKsvJM0y|ShnfW-(BpLAep>)@i1xV zWv>k!-o*XCz+-6byLpGlOg4jhYb%++V=O@5!;Qyyw0sXY9uw2C4$TMXHu2aTNNmq& z*Vfi2fy38UXL8cf!~`H(%C!7 zhbe)zAqi`n8l3onwWK4PScO1{BD{E*Lf(jsxWf=xYbTNPzi%gDmG#(3m^adJG*1_4 z-?FKDDf>vrYz?rJgj|7pyfRa%w~?Vnwe?JWa79l zOhuF-H+(l-Ww@Kkl3+dJ2gVpv@AMSGwQpuCnjYrM9<@6&Tg@)>W2%7r$v1HWpo5de z=}IG+s{0QJW8Ug`I#R<2gdu!Ujr0yLx(Pqzle!6y`;ZNp7AmUUX>9|Q^#0Vgfw~Ui zX@>~kJBWcpo4#r+Wqr_scRHAJ;P2Gddi{Hnq{K0NMirc%n$c@Rnwj22J4BG(YM1*& zCOjG{W6(zDFnyC(5Vmpg-g(WF0tAu{=NSCpT0wz>GkINEu4SeN^!gTBv#!s7s|HhH z7U9P&LjbGUWRtYD5?SH_2bbU;rv^~bri#P;=Hl*Z9)aM z91MTaMtZfTs=C}owjNmjagN@-Sh}FySMFq9oh7dvVAO-e&>9?{$hka5kfDM1mP-|@ zpGyg=p9>!kQ)5$Fnp`A*;@3Ec!Oi)KSd_DHU;;1B>hrnyxN)sWO6$fA)-pK4`Y`+Q zNaumGA;SZwV2jTF4KcY?S!7eEPWiAEg@$g4cD727fUA$^1PV3~-*1**VtgbTiMMLl zV&i4&rJ*;f1P3f5^=gx_)t9O@A-#*;XSA7D=GwMKd0(fR}nXzubELfxtq+o zu~Uoz&S-|?i8Gfpaby~(6R@f}0J@^XOW8rj$}Kb_O&($ngXg|R8fR@XwdL=y*5XLh zANL#?e;Hc&&{Uo`Lb38?CI)5mRZwzM7iTxO*iB^2MFe5)jF>y+nH!6_Tg6=D7jwz0 zl*ukGvHMwcz&0g^*+kS!`o3k@&$zOI042S-x?enIKjnFPC;!aEPo4z z9qT>eKu;~%)mQ`a9+TB&&JiWhr7b8-;uI39iA09?Oy6E6q*EeOYW^sj!>#$>fbWK3 z-IId;q?WO`&VB}N8G!@gjR}G9<{XFca=VKeBD}p1gtv4fd>PIf(NKtmSQ68yDKMC5 zl$Znt&OYlRvQ%Y@R*UvR$Fkh;vQPEX=y{=am4XLfY0<-ZJS~|I+*aq%kr6%-?)P?& zt-gCug6^@I?x4l3?-?bl8EFHIV5>y#;vk4m4heul8R5y^n+X-YAR|Sp9p;u$Q+cW%GQj$X?x@a zt=^FrQHVWG<|{TAn`|J_7-tmP8V<{$BE2Eeu|R$Wh>6Sm6kL{3iJ!Xt$qSo7nD3e* zAPD~8u5lZDPhx{mgUC|QBWCho>P?L`S~0#sn6e0g#FAH(^APn!(4mH9@d{H7H(&y6 zP=3<6p<+$vaqh*6OS&rTK5qWQ1IcB_Qm(#InQB0j{)BcD{u7a;XrCdIeO6;itp=;$ z%wZWtBqS-j1>=ZX*+7glt6i4%7y$eFcWzccBQ^2Qk>{=WZXRa(MX;Uh4%^8)^jVFN zI(PJD8-~29Dw{7(->5*_dh5py=tW&O*w5c@{gB?D+O~es6f$#gY3qWI!qwYe1Gpo- z$R~=-b%)cS)uic-jKzW&^0TvfX5aDe%=dT)bPi&DLVibGXOw7&swE zi`aoaF+&j>l_e9g3rUhu3x`;0!=#b$nxw*5x*og!oBz;hfu&(!VGti5bwRwZM_O$z zivIUVi;J_c;45tlo)FJ&(rN&gC28%wqVw_VSITMmV&wu0L1-5Ba48xrcN^DI#lW!A zFVDS#V~}wTLTts?4VFySKxP~8#zzOyC0QJXxjK*#vrYq?$qTB#fiBXkI-Uf5}Im{RzyDZ?MH7}>c~DdxX^j|{p(tg`JUZV2oL*#ciY!@1?k zm9tc{JbWZ?q6dp+v2+myw4g?jqi8@lluE2LMUt^3eQx^75+r#@mM)>Lyu!vNbR^1= z8`-IxiK0uWx(9k!{MUGCm{(bTAQlrbw!Y4$$MKY`Gb#=n$8)0~WO_kk7jSOS=L+9q8g_g+o+&7N^6g7QP6;sr#f1(d(sUF$Bye zx0Agj^z?g8S)`%esE^$gV`#?{e3Y%j%D_CR5a@wsH0FJuoi73vbWAZ)v_98}xD(en z?Ze{~xJFQoS(pudcg%AQth@}#sx_7=h6I-Bnf$3fhR|l2{G1WI6vt@i8g@y*uB352 z2pGpFfuX{o6cT~Gr)ek#)WA3llSR$V#a7V?{Joa0N?wf34P=z4oV@x0|Ct3Ju&?E1 zCR@W#5$=O-geiQCNdB%7Vuxr1CoXIGu*OWx>=65wK=ci;U(m>(5_eNZQzYsTrh}@I z2y3G!0ACXje-Sz&wFDEI*n|2+`F>mNtz4$c>dFWB#(+&Q%sDWu<`6Ykks>mX*QijM zarh~=y;=Fp&r_Jvscudsw#HlCE;WW|(mABOo(?Iu(1Fi9qnPo zxai{#ZX`$>GRpUEByi~MR{Y9ns$TyWY1}XlmS6+ummn(KkS;gg)VwukCN>IyUJHH2 zE8ReLbt|&$CU6L>-s_l#n?q#H3cZapsBc zO40~5o2@IS^pNK7b@jyxL1cx5kd?SA`yz%8FP0|fRATio96xjvV~9A%9NvEdBf6i- zoM-3oW#H<8(~vGzaQT!qJA0lH*<3#Aa`Oo)P5~@*e|=_-kCebE?E|pSp>62MjzkXG z+3*wrX~YdO5n0Vf=|ixcqKet&R%Lk+zF8K@u7GsvFzJ(VOQ(zSLkn9m-;;Brm$N3? zl-NWw5Ns>42W7j70i``*qi*be_^N~9BM-6lWQ-|l25H@3!wyY>__7F@T|;wlIXt*Q z&ZJGL(zGdt59^iP+Y|vmx-5=VTc2iHhfW8dnm1UmPV(9(nL04jDCPA}qLizuTccI; zt=!{6Pys3VkYqzgrKQj1sg*suIftpeOnd2K+Lx{^8ZdyzhBd__;7QR4c23C34uz1_ z_G3FHv1+U2L(Xe@d4OFtm zm=xYa9JBVW8Xd3GKDqsT%|=d7E)TcX+dPCK^QuU^W&vZA zXt^eb=1cG!_3b=2C)OKlzzK5#(v)zU8aug1(q+%K=U@p~Fn2=gwGN=%jhrq?W9X>$ zdo2pmd=h$9^{WOtGb?@!HwV{*Pj?V>OO_00rBy&;;TbT1az1D_^Xb&0{#wse7MtII zo+tAsDnRr|ULk5nvPSC1D-3Bq47qraaV;yh^TCijd6S%DWNaNg1tJGeH4jnDW=_CD z9S~B`8?h=jJ`|wZp9c{^Lw@!L+l6(RgqIp!Jx?U*bW3VO|Ug~F1 zml_~@_+G~TjNT~z?RRDSRMpwr*#B6*+E9?G6ojt&r(KOjx}J-7q@%Sp@kKEi^OAW* zvmE5SIta+!KJWPOcMuMiKju(4aQ&X?9ERK8Z9Sexl<-s?0&8fe&z@mp9)K2<1yKtq zvi9w_CWx`-R4>qJ z0^DqpD!2@l5Aq1(RJ+yp^ zCP;pUIOELRc1%eORm&4l4Iec}Ye=4aR=I!-h#!0uj^`;vGf+i1l+Y`rzIG&$aAW@( zSSZ7>$G&R5<*lkKs=8|Sz;GuILZY?~lLr;aOifk$tRk-gv8n++q;UaLSc`g45UN>| zWLI0&Qg$d6&(K0h=dvN?GP)JS=VRXbZjKQjM+C&jMiKET3-M{8PteDD46>)ELGEU& zk#t?Q3PC~UT0mA)EdjQ|5xoUyhqq+itB^xRuD8|ee^ZIaOp;$H=ivG=PLFQcP{jks z6_`$i2coG?r7yZTEH}*p{h@eF1JD5bOddy~RPurXLFAQ5UtlUex}@+EqZDF?pj%=4 zM3aj8T``Efh;MO65?O?jemo=wc?FV<$N-XP;0=?w$ruf)2a70l4lhtrY#LDoT}`%6 za3OqI3=F!c@1M9GEuMcad2@S#Q{|>;0bk1))g*+XA3_!(sftRqioY0k6h%d-jm6ri ziV`ZIqGO@xr3z+b*^1VBiqhMphucLBL);;vuTFO4Ev^$oQ%rs{CTYn(QSZHo9CfjL zPXLwxv7?F~l$PSlc`jDjOz-*3EJtvXrSdtP*P&Q{VbPyKkf~0Z zfpP%?S)GBQIwuh(Vb=FFL|gJ`x#zUVVt1(7y{d1_*LSJnRp%VcvHB>pAD)!`lO($b z0^pHbeyC6SUG*_2@?%OGYiYnh$ZNzQ_+aZk1!QtQV(#xae1_;wldB&l!AV&d(X;v&uU&M3GSZm%5$ z_jQee``e=+YP~ZG?$;=|zk3wqb>Nqb2pa|Gx<Nkd)O}ay* zmgv?(fg5TreU#2MiinE=G(l8Od?!s>oOSXjub8D_Evv7pgb1nn2#;y^aXPB^J$q8| zhN7kkjHvB0#FMnk&_h$x3EgE5nS6EXiPhe9tSyLL=$?+X)fPE0WCxn5DpB0VKNC6A zo9Jt6v4*ye$l)_G@|(5Q5Iv>LP2@~MCC+F&v9nL)+NKs$)$rkUIn{?DZaq|i+?vsn z+!8%1HHBh=;H{X!7Nsj%!_6k^y39Ll0fR7oq>Elt)DB?`0?XGyf7%!Sq-k+Rcr^tR z-wfIko;QO%-P*L4sVr^&@nfiXO~6U4PCWsK(O@=ebukF0>Kr2+)9g_~;hXwp`3tK{ zNsr_s`i`J*O_F(6x+R*2U|jCtooI*PAOyUUt83&nJ~K{N--HT8AQR?*?Zr_;C4y`p zlc`|Tpy}8(a<9=^Q~6SrHm=AWVLFVMJ9-_Xjn|z)X_AW0?NY}I%?qt{)><1{gD^8% z&v&&O!StiAwcBe)lvh(832S#{O}h^jjXgwD!K~d>KaAv9TeCDfW|V~*BfiHD`Sus> z2JuMxN>b3%ZX2)WFcPfYnjN%tcWC#12qc3CX3DlyyL*O((C%Q6Xn-#|F@kEnS#x>T zzQ%k%<-@H__L#a_bG%-^nU701zrz;V)H$wHo5$6cn%{{I-;+G6m5OJF!&%>m0;R;ij<`v}4)$LAc1%LjIZckVCW6sQ!*P#ZtO8F14Phooao4 zBZKoT(fev8jt9paFpFCNykOEZPx62)Qig9`bFXgWhnuK%hhExR`HS(QSwuEMrJAF1 zfNq_)5C$w3>-Eo%X|h3UXB9=Iiz&>9FwS^-NdC&!i)Bse>A`c!<6%nA?GyOc)fo*r z<`}h56C$O)Tw`X6hvhML-{_v1Y1O9myi+64Mf^f49DoWY1)8mZGsajob;~qN+yXbY z3)82YKtIlO+^WS!GiY{Xu%wD5eu)L5^UCNhE{NJ4T<{wol##ElRrM1tdm^eyPb}R? zi68P=CMZBB*wT_R?3_f|jO@OLl~HPG<}VZ|kKFGxEeK(yQoydpFq%~39N z&$kNBeHrs1)AlIb`-&1M7PKl{{kZmQN@SQ1Eop@4U`W4!BZ9$(pEicBjp0-qL+-vp z44uc2pk<(UW2n!V_)gJ>;ixt%xzT&HCWh7aI`mTVI*ZR?;aKrP%%J?NT3dG&B~fke z39~jMyW*{&jv6JYk*mnU!y%Q(&%I*~0wC_DeBhD4qj50sF~J_3RgP(hc+6lG~rH&c}8B|l9@2W5IK3bOiXpI}4GIo&~4DLt5Ceb8}a1GWxMOJd>3rwOW1G!LaxG*AmL z1Jeusw~w3Q>8V_2}Noyh5^Ef4@iR>sOj6aLM>=xLk-BPfcb_9%H3X% z8mE@7LG4z{*NvJrg|-=BPz#^Y?L%#*gW6ONYL^T(K9ePJ=tE75fFLv@)L71vuo>0* zQG3wxb)$yhAd;Xaz@YXi|ApT^)b@8!W6It|HSBk&w$Iyzo@h307dr#Zm@P^AsOaE& zh;h^f#%ecWcnAg7Tejkb!1bbK3I$zlp4ld!6-Dth*~biDn>ZC237c={vTqS5d))dA zCsNj$_qo)2i_>+y#e>rc`yH4+0vsP0xP%iJ|7|f(oE1tUD_*#UQPwMqLA_BEJK9E1 zQUzxZMNu@*AO9}hiK}>@U_b<~g;4IzV{-DLShQTqxEj^JvlI`TH*|(Rhz&tUv#Geb z54Bj0)Miy`;AVn;U-+G-+VORW7iL=cel>UP?^iP|@-EM=iH?mI8oafB5VFE+21BsZ z0k-Bel^Lp@%Z!7@1ve5y8MBeQJu0pXmD%f~!ffj+K*fwzw1w0#qz#|aW^6NL=rD6h zKt@#A8=8l6`2ibA8$}p~yijI#MaqYtgl?uEjD@DTBcQk3;$8;T2N|33i90J~&iEOP zfv8;e!~5|k$6!Lun_LwA%|`keBM&`N6Al2w7A$H?%Ki%!r-y@~3>^$NZ`#D^f?AV9&BdeHWs|S)L%rmHbIpljfWa=c16Qa}AGo0QgVR9`S&m?lL4=3lWaHb0J7YCE~Py#poD%9dM zmz;2icaj1cnq`ok6D|uF+z|VsL=NaO7z8BcEV^HcDS|8s2oX(r{UBd2Ru+9NoZ6bU zfX1`cJ+botv-ke7d0lCK-@W&JNxtMuQZGeHqC`qM_llB8{jg<^#?soF5no&OXx18A zJENJ^?np8>5L$uOHnP{58d!V9Y?RbUnXXJyg%LP_lDb%9FQ^avT>P8*7X6lQ6sRC;WuM;41DG_ygh-03EY}u2pZMt-M~C!-2ab_{r#D z)2;T&$5OBoiUBFK-c8fciD~Tsu2=Fxur5O^Bymi#PD;j|9$d<9S>2!FBv2i_L1W4@C(R^Yl*Gk+{z&t81-?*U;!A%6;huy zZx6M9IJst4)}HKrq0X`@T6Lo*HQyvDN=L$7vY=oRmkFqBT+149uBZ46~h3`1*5 zEHM;+wBtg4lvf`!1w$70(}ZC(K(dzD$$JuOg?(RQr+qK{?)?5`$IE`lOA9G<${fPl z=&6EFQx!N2iOC(O#?z8vwCc5%wrk{ZSm&X)t66FM6-m4}>~c67h-hUVM-i>8aH={( z;jgJGV`{HZLI0X4iC2ENtz(8N!~Lg$j= zk&)>cj~pwRp7F@Y^nlq3XIk9N8#6O}8gmw>K{F^@H;I;MEC^>PZsgNR-=}pC8{^z9 zwUyC<54uYlj(Y_}bt4k4I~kQ8VTLwm-1WLSw?c*a8Uz=%VC5&v}6_qi1jko zjD*mh4d?(zK48wkPZVH*{NrG17zfo^J6c}urV^S}hWnnQ%sD**kVcvDG}h~*+`)UO zP8{1{KOrE93L;MMbjlI63l|m~7ta6r`4O`3mywtQ1{^5Y_z{w=lOKtkIz^^YGNMf! z37IF3gann$M1lrm9g^4}7bDwCg5t{waO6x097_I#8)yWkqPH;sI5tLjp#!&>6YnC* zQUfS!*^JfWPbK2Ef_xnG-6@55vzkuJ)%19FTLacv2ZL#2HRQ}pSlDJ?1m{+Me719*pTr$Ry!IH1IO@+OBlLU%Ni z|BdG^r|@2kJgds~R!RUg|AMm(So=cEEB$Sy>qFYW|FBkx+)Ct)&Xgo_--)g96h?+` zFUM_Y61jbmWY_6~SE4=$QRz#l#4oN7ibDEsnO-0w{l3AJIOMv*K;G?Oay>=v8OQVs ze|}8=v+0JtCGQf=@5FR*PR8^8qSpXgDu*x+Lv3S>C2Hfb9)*(mHN)(L+BXC%5KS)j z3`r?`DM6&;61fz<_JQ$^RtZimWMzMk<1fh6g}6ooFhJ^BE4jCqZ3?n{vb}zze*DMr!auysn^g4io4dtar_go4o&b%9w%jbPkc zzX-!lIfkA4^J5se^2_j27c}>neJla8^%&MY{h+Ti-qpxT4OO*qJhIX*d_y-ed}FBE ztkhIBacvt(xD;F>;v4d)jcVpW&U1-nmd&sX-Xj)l1tH)J%h*RPWP_J-Fa2%uz_ zw&A4%hvgIwK^gYj_VubJxSE?AVjG>D(k>dS@ib26;9=CxS@Km~Dgv$s-W$T38KEIp z{|>y@q}!Ch5p66$?-8^0A!g}B;##i!Ovl;aAc7z&0J}ffORxPKysh&d-~CaZv6`uK zY<`e?KEC>cBo0N=7%~hLX^8Q)D}b~xn$vaw$p0)*IklnTNW0*FmC}pAtiUo5V%*qa zu(0(&VH&Rf8Knq3ay?V#^y;DB4#t&uo9^=PO`wedxKUy&DNb-t|4;T;RjKndF;scD8*1H)w z$Es^!RRoCfm$MzuQh+Aw2HTE;VtIuIDr9VmggRN-P5ycQEu0Be$%IQzTvD}!o>wo2>-G?m@Zo*Ax(yvqyT3^ zz({sN^ze!(pn#wm3rFw^ivHcvD)usU4X}*dqx4Hqlj1p`yJ;|rZA{wG4=_NyHF~*X z-)$Iw&h^sx?SH%n zLQD`JMU7_H4{dlObzHouqgZ%8>p{-nN{LcJaSdXBkh#Gj0K8mFiG8lFd+c+L@-q5V zEYnDJ)`n>}xiKLG9EKx?d(0RiVXRk_bWT6#4L2^>?}E)7js0tw5nal0#=fxA&qS9E1ot-4Rs(WY8|wvusIVN32~(c0Zs^(&JHIYpk{?`oF+o9=9fm=HRYAGlr-C< zyyN6W%4&BVM^JJYg7T_S4DTQ{w1#)3g~y{ht_qlxcs!~TRCq4&c&jywg)=oP79?58 zlkkLslzRi7+@DV@4R`Q~PuJlIc->?0W&388=H7S_83l-7qxD+qbaUGAg0b%873UpI z&Uq~SvyKmEKnDg5`s-#Sr#i-|7bDRLK;u{e#x&}YtDr?huEJ2N)YCeygBSr~Es?9p zCIX#B-c0nHu7N-zbPWVdc^ykRtwHEyDKzxYG#sCF4#!93LyK+yMGnVHJ%?ih$glkZ zkVhzkhTXjTrH#BB8?ukU%N?Np<#_i?>j6#kG^Ui;6Z!AYBwripK>nYtgSHMICJs5M z{&H_3X!FQk!{Rctn?-{9QQ7HYk#No)EE2NmWRXan`>{wYVGKc=MPgj*$s+$PbF9_t zKby~HUaeZVI9HSXS7Y71N}Ck>CH**JS2$p*a#Giumo~mBX5shw2(L|F=1H-sc!1raKkz(_0OA?JtzGY9LVolICA};R z;1P!yJ%W^EAY?KhnUY}ovYlqlTl8h7Tx*1zLUy2UYh3^dhH;{puuqHuo4(9~j`U?a zGOTMm`*vGw911v{u~)mUifZTrT&m8-Yp$JCT{|%ct86~(Kb+iVZy%1TYNl2~xST5{ zZC5@o|86SBKGIIx)J%Js_v>*1O9B^6+x)(nE}OEiNn=COS5j9zcRY-)@&E_7quo}F z#q$BP*X{c76q~$d|K|3M6aXo>!-B#q!@soDuUUhZZv{O{U7&$B?&|ZQVM?s-d)% zaxOYRV0=zKpot|MY?^{lt%%nRuVHP8DV8A+yotS~bZj2AmCFgs%FXe$I+mvWo{ML! z-FlB&Ma+2Mh*65RNN@IV5|$Ak2@DyR0fxE*NUSM>km4&?XIVR^Lz~;s11O-915Wch z`f9KveZtaZ0PubbZbDGZ0D|Q>+L2Tx#DRceaOmTNEoM7gmh&kjS7;BOTS&8&h$Vl|NCMn8Ii}$`&mlb2237KK)rlGt3lA3MC+$>+!UYQ*ne*x$l=*#Kknej#D9d6jS$I3jf7aqi35 zDu=!vZ;@>qsY#x%gBhJ{0mulIS(auLWo7-dllJH8CQ5K90bi|cx^zeAkt zH*sBsiTA+kZ>^#q>9$flMY(pr8ZabRAWA8@}pl* zHXrA463pXqS`4v6OxSV=8x{DB$n|t;p@ptIoaZAh3<|*!3ACYy*U}ZN*ha5fIOcL)q!VC#gMfX(2DAY|5|EY^pudi%13ruD(3 z(<{hK4hEnf!(bI0f!cR@=lYdh3aWSAT8}A<>>RBs9>?pY4w)&&)smSQJWUJb*iB?D zQ$0Qdi8xSA(iOhI5iOjf<)z$6AwOV3vnG^(!t--zut_8iz9pRXIXXE3jZ+E-B=fBd zwr81Sn8=n@i&*CHNtA*58WPgRymUeU`(JmV;o@2+w`jT2#I<>#TTGQ%7<+ZCtW7gX zt3;=lY=eRcM5zcdW~n&H3z##4WewGR^Dc24p;kVMA!kfhjzl$K=3;$*P$^(ZxmCq+9^4xm;OxI|FLR zt{~=)!58}?ZNlkM8PLqY8lFWcJjsk0PMdW;Fo)d%YFPrP0yDQT1v>_25v}UTvj=?JEX}PS z*|`;=)xr~3D1fDfLvkch#2I;hL?mJ3!|K#usjr@qZFKw!{Va`T7m2pJa`5~Aemsyw;ml=#iTtdDZN&aO>;Qt!pe~|+a+ox#9L<0$$S$T@$JLcuzE_uO zp6knNQi3Vy#Q9eXtF)pXhVt zAUK)cO~FI1SKE5^_oY|&G1lj>lFiTwF~fX?HcV3zk06MhZne+!U);)8X_!uHf!)wH z?YES#*~}lyX`a9`c~U}YDVX+ZWAS~ z2Hd|3?N)bnrn}Yu&|B?Hx*wItAodEB5;<1Xe;_YOfey{yd03}9Yucb(dOOuwb@~E* zH?{tqbXiE&HE`=XZ>p}iw^UtgLv-461yTB?^kpQfoCO(&5gSR4h4hh}vXSJ`M{+WF z=j8|p8!cgir8Ev6jaCyE3J!nP4U$xRqU?Ry?RXMrIj@P`jve)R_dIF=1)MO$=eWe- z9>fr5&4OrkOtKnZ_BBbw!x1?s=^kBNfG4#&X&AT{R@EBoRgjf~NlJ8G&4Xblal*h) zP1=MW=@W|g6d45XXGS%IFeh=q z5~q*^$Qv3yEt_=o`NS7mI)x9(x2Lp>%B;aoe}kC2-8w;>u$P0;IA~9ZwiQsqBep~y zv8P36Q^q5Pr3HD-S#)Jk zZU#++OF$DzoA)%EmqO{4d&(sAh5O4Sj29qpZmS#9SOz7b&{g2rGOTl%l!_v5ZY!yZ z;z*(*GIFV+A9DfsGFbZItn2t% zfXQ@StJV%|$rmblxrgOQ^_Ok#>`bS8p+&MBbvD(h^*jLa*0Cm~d|iA_Y`K zBkL71K@K;x6E?}-NKjX*Gu5~@B>eXAHkD|FIcZyG#s&^L$NXq|@Ee|EeyvTS0AX69 zA&3E0wZ#GJr&S^*jYkp;3g&BY5sTdxCgS{yvL-l|m$tkij@t)M-qNX%*{e0s9`q*iBC*bK~z&qi4@Yf1OZnjq(ziF?O3VY?8QAV1q+x$J} zT!M%wgtzF9dw}6WFJqr16^cCaX^0}2M=}GUzs&!5u?;$A=QhVUFT==__Zm92_ZdI)@c%ZQ&igBTcEA=?&8}(xjjweicPP zsXLXX7^a%(X%=W&f1A%nt9e`T@8+?lB?m@*w%M^3Hrwo23!B}*V>>MCuJ^C!vF!{| z@89VRlof8V%w57MSwFZ`;kaBl6977Do2H^7#?YTN1iPezDO$M92c6~;9($NejP@@( z3xJcZVV$M8E5cFK=X%M2S#44Rysr=Zkku2qDJQX80W2c6Bse!MZK!&TA}S(M(unRa z^d*S2(jqAB!*UNW)>WYrl%R4aE!O6YAAMZB^jWZ%eU|77Jz@|hhMqn>_>+y(cD>D<3 zz+~cu12SmWAg~=qFp9L{jR|$&tF37CyU5HJpqfU;^2XIM@~CViM{Dv4a)F|y)A&LU7`WxaSoZnj<}N)k%c5q{)sHCG0-kWn)zRQGhsbe=@rJt z*-g1Npi$^v^>UTAQ+Fu@*%LNk+;pf}Kv}P%dEjkuM!VO#JI`6MbidtsDL921-gAn4 z-a2E0p)GWb#)wT#*w6)@0+T5oiM?S{WMDGMtVl2+^D1~)!bA&c4hF6E(Zs7=6JtQY z{IS^3^}=A&*>#^4Th_Ac-e)%p6!dmNJWt_3G+W*crub}mv(1*(+k4NJFMx>}JnOFw zYd~FS;eZr8w$YV1Als9MTodG>5)*`%x%E}6Ukv*0WvI-CRMp&XnL^>YsYQ0v`r>>Z>cHwY9D+uP2X8M{s35WL4irH-+{_y!GyrIjU} z(9gvx7(N~s!Cc&S zCXmb($WRlHm$<{fg1c*;29dtvY22Yb69mINBPw&*%f~7Nb>bkS?Ao!)*-&YIL@fGB z^TTAZdGmk$NAmsd8%ybyR*i5N}u67=yp{4To1jL{mpP z_F8(Za@=p1xIIEG$JNIpi_N9K`uphpp`R`?l@VCFKYfGzv;OH#ZUbM<>eH~uIo&_U zU&KD`_)?)*tWtf6TM%qRxHI3*K9KavuF_$X#OK?P-O;Qy_b?Q@-9j-SathovJc?N6 zj+l)8YpGXdmuW+g2oA`xSu3kb12e6$0U%I;XjzTa+2*jXIZEDstnx{BDbR5s^%@a+ zK22f~OK=ZJjWW6_RMzSojO1A53fVuoZ}tghL&{El$0`f$FVFMk9gw0b%xwNIfA~N3 zTUBXdr;uvMtbOI@Fu-G#C1Yw{^UHj>lOe9uP04*n(nV%*Fa*lQ^q z?433TV^uV=Ft`&u<}JJnorz&UWDF+Y8YJINwS9;k7q+=YyobP5YY@kzNDXhpyTLlin89#tC za+(?NxM46-(mxQh93=@fh`C6jLnHxjAQ?ZTt0a|F5?di|5lLR$qLbu>=D3w~H3EiA z8REFe4(_h0N=p;RW$O!Rk>kb+;J7e`$ZL&t+Lc~>((AG0=tGEwWb%v)tM22T$&|(h4m=<%mJ?{+C*+a*WwXzGV5?eDN@6Ss{LU-OV zKEm=jEBcbz9fbD1E<$@-g!XBh(7q?7eLlK2B9b! zY9)WaL(hfQI+cY0SS>*U3$)&&j-oefVoo>$ez_gu7BhQMZ+s_IRI$0_$*Y7XW|p-4 z$KC=EQ%@hon(?xbIMW0WGEvRotlfXa{gPVo8m*{^&v{EQ92k}!PLBEHSGWTd&90Ax zIr_w!1`AA$!wC(YoO4>iIx~lf0lcS zgb0IA_f;Ligt)x4((3V$_Z4=f?Fpmp7knom$RVs0-GU67Tx6wEyWmg0?NLqa0EZ|7 z$V7H$;=&XeNJ>YCO1yU$Yv47ms$yF~Al4NNzf`e8^3!w+orqr)6sOc!u6MN6!4A6Z z8d+nEt;!HK2Ifew8q#^MEHXj%F;mxZ)C`EvSWYLoO4&oOLMs@s)x=LIV3u11v)JYl zHFUNm?Mk-U5|y!9eHFX_wLt1h(3g|<`;vO0tXQu;1ASGmI9Wqq*Sfz3uZ$P4X{Lh- zfZ83>_J`}F?MG=TA0Y^6^y9Rrw7v8Br0ow|rF~$f{V44zZ9n{1Ds62X4H18Vtc`Xc zfC}YY`8=}r>c17WKncNw#-s9mGZRaI*5*tzo<$FaJ5<e*q74a6)P0(P!)Ih1z#>vC$hEF{gM9S3>>%I^(73=!mMj?UWOO; zTys{7HuKvG=1CUTkW|S@@qDCwHQ0(K$#N?3WCXpj(M*@=T;I}9u{CbVbsO!Kfb7jr zhZIRMkjT^LW z(K&M{s7klp-$dDxdX6EI8Gur;RQAh*CP`qu4TUf;2TJ_VwBZv8*>$XC>1%TfqYe4B zKhT2ys#guQlb&)8E3NdoyBjMgerbN~l7-zVn6-WQ@2lzvR0C=}M z+7!AdV4jWhZu5^FW`Uc>Y7syIqBKUd(z+NK0?l1<4BNxozHs#M@EYZ6pk+j+bjq`D z6bnens6eWc>Do3hjFHun90mx(QlS}G#VRC_@$oPh0b!}>woKa;Wx~7Br{#OIwt;Qt zGc}+kcTjn?kk#6d1UGX>M-oBvT-$=_P%Z2RU2Gma9D-Vq^KvuQlBjTd3TZJc4PAjI zL0egzdGh_jc)!!?F>40Vr|ilP_BMhNX^DdQZWz0T|EcmUon$@8Gjn9^RvF>?WY%x2 zC_bgJh6IAJQq27IpIzbDnX+DdVNMbiO??@DoSwV1*EjF*~SuM~%P*0qR7p;E#onndy zRLi`9wo%};CGx_rVwddueNIaC*WK^iy_n@{2k0g&l7rlliR7?361Ml4QRBo>5a?C5OPA$ett;(x~mo$NaDmB&FnMz-@@b-u}d0PFN~<_kd3i3O-`V&R2) zo`!v|vukOd@sY1!$b~v_l%Jznpq?#5g2=1IgZ9#W2A_KYRz-lJuw?<9*|PA$i##{( zj)(1cLs~p^_kU0GFVLI^d=4KT3*`xWeur}Tr`yV8qmuiMkuWMZeY*v$r`xY!3Dg{f4M@*Rn57!vk zHhKz|FrhSPi_v3H&C<%as$9nv63dcksx2IpO%>NIo2n-Aoqf+I?e9q}yK3?UR*1)< zwM2ZKmgoT_?3X74%pcrgtt{HSr{GG@=>vah!GRWD^fQucVHf5QiInNE6Zu1L1>2Kr z@KA~p4?o3ASY??miH#t1*}#Gse2Iq%`arte5Syu)W2mlgIb>5>wr&qGxT&;>WgBH1 zM)q_GaFI!AupP74jm4NY;fygBL)MaH%^0sta1F*?;!eVO00UotK1vHH(#x@JqV4oD zi`Px)2D_m$EpwEZMF<#tDy=dw3t2H_xqyl`;!zDLtW|m$eUV=@90MRl8$qbCJ%S{b zMfR2LcQjJ_C5&wZ1v$R6@A;(tJ&Eo9qjGgL2`4%2C6(+jcR9%^Ih9!$6V|75yzW{_ zEUA#x13Bu#h?JpjW5mud-GvbYDpgj&4H$uhYP2wdOhTIp`b7GuFk&eahf8!Sgp`={ zv6N*E#k@StrEVyAPDA!;*r!35ZEZZUm^(?B5}=P>glmSq%5XAF-9olw7MX#31hmcE zZ(o)-32eq|!96Xawu`w7G^3fgT1>Fn7avQHQ~R{g_Hd~M9bz>YUvO1tinR0}oE_G} z!7|4Ia*TK6AfCHiWR2{$N%yYuyiLzLRFal^_k_7sm`xF8SK$;1(f9L8Gs&}xY#3Sd zh*mF2lv-oz0pu$622DXmViXNqh7*@(o0ZB%lT&1DlqFy+W9I~S@F4C`l&Mc?nyaui zf;%*MUlm5{zj|7S|J46+s7${)biJyD;iby+MLv_nRU^y})Jr?1KhbI~+Vm$)Ua?lt z-tvP=#9`PpH+&PMxj}lgHv#_A^xeu{&G8D+vMz2sGmm7SStL1Z`J*8xE?ec(l(*Q# ze}>^qt0(ivD(}!x+}iTyI~rP$(EoL>%%1cGISj?z7lp|wF;CW9V zg^gkn-lOnKK_CnJc^!&H9b>FXj^IG~voxldF;IkqrZ!U8!9V-~j@!8+$qJl@d-k58pG}7q8Vv+(28j8P#XyTY~V^w1! zVmKGs&_UHl0MWdiF6IT;QKi4uTN)t?o%*O4GfY4ii@j1~A^5()9S4rssz+4=Pm(E- zIG@?bg9G|f-vK5Kk$=aXeJ3d4FW>q|*wTj5Pk@6%sU8(9ZR$tC%6DY*3{O1sY>zQ; z;B$u2nrT*E$SY6${O8wyPQPZF)o1Ojl+05GbU0|*{FgF{zK|E(rZ42P@g3c+h$w83 zeQ+5r4Yi?T0$OQA3qwY;l{5R&x7Rto6p-<^S0xf>jACjIID?{ngF$pvR&QabpWQB# zBTpQARlZ;aSNu^Y4-+#FFDhLJFHM2poGzg=@%C~-`z*}EBFxg`4Ha!H4}F1uY^s)! zt~kUmp$z2)dn6*1RuOVm!i)+AZV^PY+ybJxh#LDMVCkx`qzhbzB?c~ATqAq};My0# z{^wOa+=-_I-sQL?;r`bRe!3EehfNR#lH3zshRD=j9 zD~jb2&5D0RCAbP6`VCxmCvb^k3|!i*P#-oiEFF?0j zcqjKd?5IjCI{K|+FygohyrF_X{CovKvns%tOT`+5NOF2;H^yp(CRDaWR@y-*oNbar zMWj`~$~?gbHjhdVt6D>m$BFKSjNF7#hg{M z4{{e)b5-<5ZBy2JwP4+=5~+)Q&IN`q6$gaabD&cZ_DuzzFk6T#WF=SF@37_MQur;o z+J%eFL~5em(8omDnm=P)leX^BFdM56MG25o$czlhz_%>(86yxZxY+;;`@F?9_IAT! z504sX_RRhQSm^yP35z`u7JEuqh*3gG9e4&T_QcBLP_Ku@o({W!q={kJ_PFrefm6A2 zse%I4&bdlb9MqzH&a?PIGhsMqI3P?y1d*o zheTmxQZGm6ow^JcyUgwb5~8JIRg(S3@;30Yl!b~+xRc8Ys#$CUh+GFIcJjz3^lDVX zClWN}i-47qWi5jCJV~_YZx>3-j_)Yc>yx3e9BMsnG({h(dIMfcz9fP4y}X4dM=F-$ zpVQ8sU%NHcwwIeLgM#1530kJyVBdFJtNLXiQCz##5SU=whBLEPxtlKZp&7Nf21YFi zhDxoJmbJ!#0l1^ zue%@ljM}DCo;#b-(FiNmWb0nh=-%LNts(LS=DYAw84RSl_l3j!)=GVTFdTNpepo;~ zo{t#)aC@}a?ATbOeCZR+3)~Tl)eIqyPy<3qgu^Pk3^jtxRuLL~JlY5*(#2($R8iR1 zhfym*bbqpuR1RtAWe(Qccy8yIfbaY~eIe{=)?Rv1IQjss-xMD3MnmtRY~Iqzdo<7H z^6lmkW$<3jlllMt&C_B>ck}jlH}8;v_>ebGeolfSP5U*>P%AnMrzA3gxXHKVnzB{%e!kJHd$HJ|reYzsuXsq1 z#PPd+tqu+#lV1&+V1QB7z=zlwjX1`_JE2w`mAu(uRAPG;^pA}@?vx2M~#+E_`&EPFlv~eNXi0Z4DE(X0Mtsjlj)_e<6O4bXy zvDL0D9gH)ufC0y6a&3z22#}+3V9*2!^%k3*LKd)uVZDcY2w%dB_whn;q=eUCraR=crIO32- zO>Qx)T0$mbhnRHUQ)yD9XjMefs)%Y;5!I@qk!9!LUa+e0%2dTL?^JzeC9~$PlCO|Z zv4m~0s(Ss+2dg;;1Xg~p|99o;T6wkY8a_i>6E+HazdP#TPzU< zWIC%!mwlTpS9)kjZ@pcSKmiXBii~rSv&I;O3+Upau^^;I5eltUo6hCSTZLnq1R!4f zA*Y#&Rk2C9l*DE3EqWl>hpC;fTPO+4vl@As+|e;v1RR!mR_m6m`H`3;ur9u0f24n> zhiS|QFeh6s->Xbsi1Jg&UTeZuvu*1TUnoPHbcm`XTttUr30BK&EZlBm83?#wKt+2F z+MUT#>~?L9+Ks+z`t=k$xbn@gWX=H83aIEu>!?HjWY38OS`IIuY1%Nvo;H?pt<4t| zq<(Ta--qbdh;lPmCl_CUtz=X1iWFCl`~XF`X>r!s zxwu?D2LsR8idlV@Cd`&53^tdr-WKkT-yKOf0kSc+dAz*lUkv?s$Mc6-jT_Iu5ZQk; z6z*@{$uJZi3}0kj&J2Zzw84g@Ib9p923m2o!J)c}oZP-+syM3Uzb^z<@9vIN`}zll zM@BbIY}vYPdgrd)wl=8Mz=z0T86URQz`Gx7*>FVVs0@9g>?GfmF=c-LdUm8T9@#H1^;M{ZS`DH)zYAzx4z}hzV%PrABsU`5>vFq z%?tZ&zDeM!%#f~eiZ=a(xh--Pf6LE?5>U>Y8D`r#-evC6dXKT>(d136sm zy0%H}XQj5DElWZpz#C6$$J~jc=8FShJ$q>U%(r>XZZ3h`TfhIuVe|L@`0?;JZiGSZ zhyA{IfR#0)t*Jtac?cuC+!ixFfDRvDKFG^mrLw*|Jm?zc-faF}|4Q(W#CvoB1J9)qv_!hOGYZ5BE%uHRag-$@7jZ1xOU)w&sU{|pb`yL~ z9&^&I@{G!$9Cpf^Bpo4(P$59jzyqOzII@ZdIq$IRkvlo>?65S9;D?jG99`{jlEJ0g znk@L}B6xTdXdX`bJyRckB&(+VhSu}NYojWXARU6Fp_ha z$XK(0;FL!+BDjb`)>g1G>xG$i5POrQG^>kEziI++U>Ork90Ai=lCBY8xdVv7y8%+y z2%rRNXv~$T903K>K#;R8i@L_5x_GQn8qVXQGvQd5rio4`Ei>Po$x4dcR4??6D4hu< z8kk9PG&$-gi((>}DrX6DPut}+?&Woj32r)!suMg7=0e&CaAzy*9712_D7(GQE$vh4 zq!zo7Ixk`OWN4&8HJAP+HUbS|TS;du60`v>6R6ebt%yY)8gh_bEhcqe#qA1tZCEU| zSFUbCIilHe_ztf&ql1!fm~ms}M7OmOEeMFldT|YV27TJ7Zj@NEonZsGQP!3X6hsBmBU_= zjA-hpe3JHK?#mCi)yd42Kuy^ohr`EAMYeoGwz@W(s5VCyf zm2^zYx&+Ch*y<~sD;RWo2DM3PHCH`tL_EcV)c%-B3x&3nD_s^p7Swh!X~9`jLZs6O z=>$v^xwn>{$Mr07Cu_oc7Q{8Ys4g%E*(Rm+pdUM{AbA`hcd!%kut6^7KCNR8#8}j_ z33;lB-hKC7f z9>s%JU|{5tcl)JZ`|W1S^)V?>C2!|8Lr!DmHX=O$31Ig>C}vR{0@oRlwZIK7vzpjw zj0T-N4=ff$R`m?rv@&Q~gW*S(e<64nxL6$0n$bZBzrq4Q;0e!jtpOk~W%tga>5JWR zEyh07yAQOklZE8jFgrzIoqc&lT@=eKyHYMuh=*7e;&%05I!X-cd(rSw=ji4C-( zy?<5sN-WO$>!V&sq~cQv@7J})%AiDuExp9$hCbJg=421tP}9Z& zq0+LTI_I$|YWBb-Jk}Uafq)wnh1Lj)&9`l?wjyV0Mjue%MyiA@L-%ihL$tWsiwFne zLam2`CLRU{pL;MZ-0EC=P+nGPQ!x9dNsg2-4l~lY8d<>YWvu@n2h@zT3Tgc z92hejo#Y+-n@! z@V$yd9l%GgEDJelpJSt=4+fcrkBMh*bty4R_*$k)(rTxCO^zKTgz|c9*T?yKp-FPc=WWH zSN97y)m2$mc+|2e(JsUhZGlZntdER1*0L#7qDTbyFZ z`G}c^_?YOPym47MPFqY0OU4$}Dd80raAT)zB-p#0%Hs4=R9NwpNUcsw`H0AqzdoZz zIet?QRyjqCHa%w6CFUGU*-VzfQgmC(`wuTQa8 z?6*eB9Iaxc+^an7ZXJb5zbpGSTmLv5njmxi;n2R?X}P zhsOztHHYDFk=1X&A6Cd9CotDT@3N6{vW3nf95V$@sC9*ka{KwI<*oS`|jjY$o6 zqSsFCU)1dMs(enb%IEZI+P}3>R%_5|yqw#^eVl2iO*8}E1JhvMh9zE0P~T0n^wkPW zP==h}4yj*XC=a)=-P=0c_+&_ysloGIEGkn4EN~gh^)E!oNPf~TPU%;Fmx?~TjrnGc zH>u7%{->}0;}dI796NLJ%PS;=?1|t0FW>tIYflV~{Lv2xg~lgDc1l)?WR-iCx9m!} zR&!;lUjHm5wrF}?%9G;8rfS%KD@m`rug-H)c*x?}%I~c`@z4Lo&gROplxxd)QXyn4 z<9qkc=wSj>rK zFq^FnK%~Q07oq8j;+eo2D$NhLjxrtL{bzE=o@U*2V3r`sG>}MD{Xhx>5tTloSQp%y z^YD^UyyLhVE>N`L?>VPVP=6#lr_7HaS?SOt*?BH3(OuxuhspgKHs1v$aest(RJ3WXas?fy zoivMHcAQqDF7_#?Pov3O?eQ;ajc3}~DXlV332c9b@cR_3LpgiqQJ4#y>{uLW60PT5Ob~CWFl6|7i^T+4ay*4lM%XRMSi)iv?RO^o#iX5MM)Sm3i_F;<4GRFxf*O zX+#@~jlZRtIU9Ae#(#wDXF|%SG*jIs95Kd7j( zj`FD4<3m`vY2#+a+hgAh=`j^CgAt+&>>KonU|f_~pl5#7mMnl`Gx?_p9CVSo<;1PZG<#~iUMc#mYoT9Vi!bQzP1$sn`&YG?^LpZ>#3HJ6_YQqHm84xeMy~EoWDf-x_L;% zBtFI*;k*OW(29<16Q7a82sx|dEZNMpqNbO4Dd`NsL3Mo)RTk?pnlD^xfyft?D}U`Q z-Qnqqo)+|^X5PdsLvI_qxc{+vNmd&Ffw;|tjQ4$h{}vMl1@y25Cb5bJZUS&!?iA8a z%SUwh(S+3_tbZi1fkJ#9tF*j8M1d?QVPHIjthoI}d2TUU8d7z-0h8l0A&$s(IIjxO ze%A!mn(23#L37Qpwu1@sqS3HaUILR4508T^AeGoJwLJnuR-w6R{Te(m3(qfh3J(Ot zS_!(z)CXgo27|$YxSk^9B4&<^wn_fbf4oT;3N}KhWRZUYT4-E#Nx$m*8|ZVYg+jOm zb!PQ{no19ePs5nov}?k-8>zCbv4}2-s9$>~YelX}_I>0zo8?R#JLh5|k84WKRxq&*# z8L=<`K-3!pTEbn$p@dDck7x*B@JEA{Dj00S_YMsiknnB3-@n-SHcSYpebNt@U?<%E z1gg@Qa*J%`LH%nzK;IdEj4h4hp0OQ#MxHce6xM^n{4%&m>Hw#sH0bmZQH51gw@m%m zQMn-~0dFnc+WrYGwwk;w*tuw>{6}Zf=SQnaC9BdRcoWb{z}h9bXF_=BA~#b-J>yA( z(RyKufu@!HC@$jTH&c;>$gHyz^|SNYOAU`?x2*!v54GL2ToehwIjjvN#_v{0%%^gY zvF+*8(!sgon=0QBUDQbB5f&@Co2vwcfV20NS;qT{zelxtS@|YasMdS`W0~D3^%cE8 zoczk8@Dsm%^wDHB-ahtdLVQhLpRmuaOIhHnN0VDw_PZtQviWt;yL%Dv*yQG9!o;ck zlXF2o?_TTu4u=2(@ZF2(PzF)mnaira`ZJa&h~B$Sdw27m?a{G!{k>*sl_P**h)v3x zg4tanLq$Jc9K~SI4`3$D6p^AL9}w~}E*j(++j673tAH3te@Vp8^|)-3hyh1L*7d~t z6$+#woP-IXqV~hJbLAS0xg@p{G+vk+?rP#`tbaH5FlBk-o$UYW0l)t~B^F zr|P8vn_yV(iYf8Pc-KBfOOp5JAAxMRo2#6aYre6DCH5yH3zp0;+7zL7tg285@SDLFUdro0Y~ZJT$}y|9W3qzb<%Ty~*ZvQ$vsB{4l#8X5j@~ zq(gA4su&dx7$)L@x@IKTTYQlRdkt|iE{MdVp--g%A;v_bN*d$v{3F?Ml|d-|__ap9 z(wG5OqE!3#P#kg;9O`~kz)Zi>d|#6eX{+fAni45$m_(dSA!ee`Maf6op~)*;d)Axo-pL{U!NO3Ks;R0!PF1m@>p%Wj7Idbz%AW`b}bFiTQqu`0vIkj z8W}jGlNcHrAGJI2Ye^Jo8sfu3NA7UZ>q18cb%~0lyit-%Hb}thRY4N^h8|!)*lLm% zrR;(adQ$(Jm1=supD@p+R7xOMTKjpy%g%qQVPdo|mDGdTpxdAN5ucc!PLqMD2ZpCp+%aS04hq9nua0b}I18yiV1tOk z8$xH|S6VGB0mHbcWb$g6I$8f`7_Qui@u3e}=U`DhNYFK0rEH)UMqlbjYpBXsVs!CU z$1s&|L&w|p5S4F_bi8d3Px)58O`F|A18!?*s=lJZ1*@D5Gwkv2r)lID>2pCzr2A49!rQ{ zE_HlamcoeARGJ?Pt@u@T#`;IqY|uQ~t<;Qu(~n*~F*<@{TlQAwl@+Y?)q4HgCAo)E!-zrxZBIG|+FmAY4@SXqQAnii$$Y3uw(M#~_rP2> zO37&Hd3>fL>FT@8uqx#ss(kbV%^-2jpuU_^MEl=5)&i7;3CLKZuEr@RNlQzW_FCGR z=2&FuS!a6F$SG+h!7RFxn5SVts3-M5+FRLrebSF6K-(He+d@RLA4Zd)&Dp%3AQjv4 zn^xwVf(CQgS%RJx3F<&a)5}V;|C#E%>ucCiLelt~6`M$7EaeZ!^fR zrj9~2*aI5>VH%`)Lm@2jt4cIxSCn+jv=xLMFqCBoJ=i)OYSu$J+{uWNoS9NZ>%QZS zzROfM=bo*rQ>JY!X21B$SYu{2Lgfg%P%*;`AQ(;N?r4i4bC3CPvVw3Itq-LNUGy-70IMPOZITQjQkKtZ;;NV zmRkUyi9G0Om_w-SgRYX+RMHxj?fi2y1rSCp zLszkuS^M+cFShL{$qQjXi$(mf!m5o=W{pow7=Mr*R(k|Z51OFwPRNo|Lh~FRYH}Oh z3S!FBuorU4L5%`T81ebKVTs{P;yAUbVhSaA$ZlpX^-DcbeVdKz2FbuS^R$!_84q`J7Y0Cd;FwE6e!- zdfpGz_i6FPuz!F$!96aM#SF)>>2A6>gm(aYS-2l2iS+%3WHUO;anA~E+^Kho_PcPc zl7ko1D|CI%_DHSJxjB7j2az36+LTVcI-oSD6O*q72`4y5AL|~0nAq($L)m0kVRRNq z${a8S6WRf^rt?C*O#?_>s&9a21DE=S`RV21rhAth3UQ|G- zcg-rrF>o|!FuSfyie(E@T`p|#5$+Az5$4Qo$;n?XOfyLtw%vavBrm`zO?^mUVW=`JSp=Zlf2icL{{NW&%*<7!JH)xj3cpn+x*s0G!bEGZVU36ZraeoyJB4isU6 z9FKGre!u)1>XJ%dEmem5ec4qLYKBw>TdNTxuqQ(Y=^tkM$b=)RR&gPG2TF*H4IGLE5CE@^-rW4NgtL#5|6Ar)h2 zUm0I(jUl|L977nwFBn5pMZg_a%SQy}p{H6S$bkr^=$ig#gDCkVMrMOJ*fEH$QuIwR zYIr<0F2QXh|x4w@AbwF!6Yy$N@; z9CQ)%Uo_!z9%)Iq9fLmNgZ{ZGSEWGtL!E;@bRWv?%0IN8OWcogyS|Kr?ltwK+^s&o z<)DWNJ>yg_w|#tiJL&cg$*iuPx^Xbwz&9N^`OPNMkkaRU3YGhB@udBu^-IwYp{rlkCkTeFo9H6WNUd$kTA~ zpd#_0Df3V*j_B~({cC6i)Be6z>_ihqG1U8{cnEcrZLa79hpq777kupxyT2e?I>Q5K z42&xZDgqoOc8xN^gD4K%?UH(6V*g-xNbNX6DoLgjAn>E^fHEA_3)ExwcT07tq;WqhO?tP}I56i7EPF`q^;&zLChj=VsJ*cV#B+J*E;= zgpkao9fKPv1Sw}6B1T}v2+DHh!{YIA>ZMo;L zLSYTUn0C?-Tj?n`PoaqCBNR5EDUoa*GF})C)lvR5xna)@%Ady2?og0s{BGiShGM8b zy$Ge0AJ>10kK?YX5$&47QQx6L3t!MeNZBG84ZTHBnY9Qw#unL~waD(&B6(5`%65F5 zrSYgMjCsizchCWlojx`R;-nU(NjV1J)fjwN(uB3NcR_}6Gac3c&QN9K`X;wkxjP4C zX|dAHtAzWoWdkCjPIN9wFkTq3j>@_4H7uzD4VH$An8vi;%wvDxBHIL6ZVzA=8L(wA zyr|_hh*g%%=2&@hEl0gRoc#U}CR(n49Ve+5@&-c(`+(obg~=xGn|+eK)hF58b(w%= zVCZOmtOY zOym~rqCu$Hs%-CSpql5kTO($*LB`s)OP531ZqFQAw8~?HsWR_%Lcl7i@;+vb?|gK* z4Co)1!QWw_dR)~Yk8|UJGo*=P8TCeM&T=%TW?=FW<>3sS#cxp%OJp%0U>fna%#{o{N1Mi^ zGtAZ_Fql^37r>?kL+bAV)7k7aa``yO->=s3+9PnCl*coEcX+m-TTyq1zbl4JOt_EB zkkje!O2(tk+t`1M1hdYdCWcJ9$5ax)W_4 zG-4MV0JKAn5Vl5bzP2GmCtI;ct;&io%cH6i%naXWrh1^Rg;|*l8^14SVJroZ3;mbp z7PrLrF$0f1OxOciJlizcPzKCRORhji>{|r_bPC}zk#7}(SV<^E4C6fE_`q1kGi-fM zw-$2dwA~s}o#Ai+2u~x7zfTq>ZP)M_yW32Sr@2!IDJ^4WAfxVV9LoNq^aFeEKLyEQ z0RTD^O(eqLef|`^!5&1bFm?#2z?`FwK1pJ zqnloY#o-kIpq(bVe@!PF9ZXm_R5+0u8^xxD3W)Vt&RR{V8{=TC6KnbNJW%P5uv2Z- z&RT9^*-$}guI0m{Zm~TlnKGMS)MX~Wq)W)3GeMIt=rWoAmM#bA6BNtlo2-(|fMk zwX6v$me%G?t_9amN#Dcu4%Zp3ACi7hS2XEET;Jn*nClYnAK-eE>w{cBCH)B3J6s>) zs<7HbhLNjd{speMedb5GE^+-LSE6a>k8r)i^%z$?YIFQeY-#XUxZdXZQ9V=tV_flP z%};P$;`$}7pW<5oWv+L)p5*!t>GND!_0OAJ33&537u1p`xP-<06c;AuPojtHt;}nQ zHGEaER9`9*xl9+VMF>e`SOvvc8xyC<-B|wQV%W`{*jkeRx?g_7FW>gdw?Yo(7yD$m z3Ea!Xoy(xc3u*A*)+Nb*11#4NC0Gp*2Y5aB49;D+!c@ClPe&u`(^j9X z*+%TM`O}nh5&(dp#Tozb%ZuS@zx$HC&~kyQJ7G_^F8?Qwj6=*L69Oe}iJa zXq6lFbD_jFfzBz>bJNkyHFMV;*`>fj=M`}J@>{2CLi-w59d-#|4{KQEB> zbtYBcCheOxpYks6Uo>>bDxTA=xy76bU^uXtKPB|PiQ24b3&4KKL4VCbe-+S^gE^tY zeV+q8cZlyU!98Q(e%XKxIM#!EPXzBP5xhlwUP@!aR14m_IzVLbeik)w9lYB~_lD1O zM>!7X<>G5yMH#4-^bA9}#=vnGLH$ezs6Q{1%>f|Hv^uh+Z{~yDzowPPFbGZQaec?tZ0p z$4QuxDvq`89%{T<7Duyxl-dF9(ft+xkScXO>f4$+IH z>~GzDzF}Zlq4h-sHPC&mm3GkY+(=Y6X8Eg;JMNVlu(xTUW4m;MIvx(DLa;ciX%<`W z*zkESoLhsNlTt$>E0sJM0kyTOX! z^DW+-T?0p#POjX_&)5yGNj=j_{TfR14wEES>9f|F{F}P8irygXv}{GiE?IH?~O7q zPTg9|&uDoDp(PUxLXR1}3$88=t)F5km<=7zk7jAY_F?Q|!#PTw>1_h|aVnXWJLHVC zBI#i%o6Uxkb^o!DSCl%KS@Pt3QS6b-PAMNP;BHdIXjbp>X+^c7jOvRS|6;oJMWxpl zX7#HTyY$6g|6*6`i~e3;up%R+dP-mH_AjPdU-b3*g2@+F^ddy-V+gJ61HH1_lFmf0 z?6qFm$9up5{}$ZfH-z{r2+qoUWn=oLjcJ%QtPH8_3O86r0mM1X%Gi)TxG`d&{GJ$1 z^M9%?U?$S@r&%$ojRm^DhE-12o0Vd-jxPH+E&6r+Q~BYCEd*|brEp@b;-4E?{`v_UrI{N=ktN-3XAZj2=i%p(T5R7(Xm6(&1 z?Ff|LN#=~}m}eI52?s3-WxblPGTcGjo=>n)C`*O3v~ZS|k0>7-!NJH`JJy2_l<#B# zlp7nnvHniN_k6wUC#WwyXBIyJZe!$#C?`Y*$pYiDz7gz3*)S$F%*K2DSmE$)x#z)eJ?B``!ptHUlc+*sqvP*cWXKYUQ7Mxi=qvg;L>ffWz8_B zwzP4zOK-ugw@Xsu9E^xWw9yzGXd5R>y#_gJGV`7Vlliy1i^ikhR3Z%O60g`Qqz9cr zE>AaWoGZX;i8*%diW*CzNU5xqXl+?CtVo$&bmU`qz20v~oiHoXVn{mjPT_m`^OH}v zE7?K0pRxVDn38kM8RFemu@fNjvGL;Iu_$N*eD?Jh1rk4LeqAk6Fy5`HAJ>RC?(Wp0lB>*7S3j~}=I7ez+oNwe$_;v~-oJ0Z%pNu5y|M~o z*nb!bf%%SQpMRd)z&AZUptUw=z9WQbzA`n|0oMW74S}<*E*)ugNgi|>-;ymk)Z{Pd zP&?Q9=4k61R?~#NEDp*-7^cGXQv}j79UgfRAejzt~#GHt+%xda*kyXwDiPuZx3387R^*zLv}_55nhO2UyO-KRZFrZ85+Rs zC5uMPO1+ElA=VDUPuhfU6Bf|p-h`j5C;Ut%W83PkukNLFGX)Y`u=0=I9b_#%AFs@3 zaEF$@Erq694EI3xW-)U2t+crbm+Oshg=sSQ=}_}xmt-@Q2>6tTe_AiK)kH^CYP31Z zg|-fTZ_U2eD|=719&`o}y|Uj=L+{a;`)TMsvSX+g8gG`F2*+XcMhA ze!Q_rO*MMxuTjZg%8G6NxwK=!;94Oy%5E1BFdXUHqi$A&Eyp`PFVeS?1{;RCAcDPR zyqOgZ)$^zJzqMC@b}`li7Z7C|ra%nB*O5?lM!FpGO0j)oKCAFHv`TN5svFW)eL4)exDCI!Ta;)nQ8MA)1)j9`b6a!F#dFl@pC7#INT zdw~wlOjAQa91XppWMXiW@=m1%zj?-M8)BYP2!i9eV&3w}iiMJi4`>@oP@CK`&bGAAcfN#$fO6Jvz4inw^MyY(~a{ZI1I5h zO5Sdwz_a((tO+r+u?bX%OIeR}Ky8k*-Noaz>qOJDHN)#$W7&JRhM3eKh(Qa3NgG-q z0Z|XJ4Qzx~FR*i@rZH^8706%66sJ|t$p-o^CsXU7X5vI*qi44!)_1EVeKyX8drtMJ zm9Q%tdsGULb*=dvlPMGgr2j8HnQGe!FKDq<3$weJE0n4UI*n#6+97Vs&M7(uob^EV z9=h})zx@=A`HbJ*MC(55w@cizLae+WR;7X4?Wd%^;!|NWM|~f+>%%L&?B5UD_5G|}-y7}vUTfF) zQMieW!AJkFS_d&b9&)W69*{*N7UEhz}^?lZ^@6C?-mOJYENvyAH z8eOmd-&tC_F3lK4)aZIB`t!09qXao|YiF>6N8p05lzf1^1DOS=kqbW9a=EA|{ulFz zX3KpW@$#1oA}tWtCsgEuU%>^x&s^~FE^!xpJRWLj=oZDscJNiBFE9K5%(+&rsK^B$ zZYOQNLLE>H-rHqK@Mjf*gB`bM-?ewYFS}|& zdy~I*Ca)(`^N6GS6`OOXnj|G!z-iR$|4+R#biMIvrXW^#XjwL6AIi^m3FP;+51iKm z9$s8#aE+sjlW6%|rt<+MHaPK!%LMMRTxQ+d?GXNL0tQ^gVkQh11jG3;L#HirAL&Y1 z*U)DNgKY}c9~2E1P74BI{z&f#b*h;8uwRx~3xZ{S2u$21J>E-)_0Cm zGN=ltAokm32&S!aFWW4yKGN2dCO_?UUjT95-qu@AL(&(+@e8(k5k#qS1orDrVjprK zPz8r083sV^jJ>~$@{_`1Brh%0+nwC)Y)jqJ_KH1gdSmL{C+>b7on}Er>1zr<0*L8m z14(XDL+J~orD=-SL^~jZl4KtbE2|RlzWyE2Y5Z}`IN&tU_~jE zA>M+7GPFW?iIbVt7q{2r`xu8(%kLkceLj(3(NTOeZjvM)x933E>j^pIjabN9oh2iX z>lu(1euS$>+H~ z-r_U3-QH5EaEW6$md&@~nmW(+F+5S-e~t(#a`Qz~l%0YgDl$^oI|WqZ9ea5hl)Bcn z<*n+|b1%hG4Z$^khgojv@`IJXvLU8XwbcvIak6g8OoW-z(G|SVhC#y*+&1xK0MD+_ z4Q>|*$%|8=KU}tl%RE@Nt7N+x_?iwen)uP_TNnwKtL*jVaycK)=0O#e#1aBlP4}bY z=2RFq=Q#^~;C^&=27&w0rCJ5Wk1mB0bVP@=16Dn_JI&p27^77(*o0 znyy6eUDj3ngJ@;h_sfT)JbYjebJ_H<>|ML1oyXaPK@0JgB$DY<8H0Er`)TUOASUw8 zfN02O9dVZwJB(IzM3?#+e@zGlF5CeZFw@G-5@jtxOf{G~Kwb2fZPZQfnR28K7EXB+ zYRB-Ksvih7p0biE3k5}enugT3w0otD2~1H;qOM!Rs>6iwlXaC?qPH@IPX*TC=Ldtr ze(7?;#pTLks5hSAPAyBC@0QOpgwSe}m}KX{B>W^CfqfAKopQ9Dy(+SpkT*5@;AW{P z_{bw=X*J&{M4?2B?k7zUc$Xo3I!MG|g*JgED_Wo#9IG*ixQt4}RPj!e=Xr3FBx1T2 zIU+?~a)QL(6nJq?FN`3yXb=hVTk7zKgUBN5I0_Oa&t>No=E3gHDq^XkhVUcSK(qSB zMvX8LIf}L}{Jf*U0~1HK)-eVgaa<3Qj0!b}*ktA|=z|v>oTQ!PLM%k_6=ytQ86& zZHZ5;8dR~5Jt8fsa7LRotm#A^_S3E}4!y?0aj@D^7KdI;+Omq%a$?rJJq%hVP(DCw z-Yg#s_3jf`kIt+MtmmjIAx8O^p#h>pGx|c|8aM~4&oYlF6!A~DOf4+5EJ4GBvfl|+ zG@~X_5gQMBPW+W5k6%ko}>Fg*sy^A8)r9xF~;nDVi0`&}5tW6!UO@azhhZsE@G))c#Q4WI8 zpm$*FxaZ6}(2E%)U%w;t(ry}+ZAhSg!d4t)G~wSUPVh&Y(?*PfIpPz0Ae0(RhMC1; zRG|CDl8Eun4436(uc)B@71ZC&Loz8Bap83N) z(h)ueB-z@B6i*dUPhdnHF&7JQeLx}3DJ>x4F%gtOGIfAUqE`%SZ>%+CZdJUXj5N1 z+%zIa*%U+6BC0X&&?RIFl(l1$YPd)pTjWN(HG}jel$^c72aOp>nBA~=A67eyMkr03 z=VgV&FHt4zOXILN@a8h5%X(=a@kuE&O*YDDix;^3r)XD1uc=zh5VU$3HB4M#-l*7J zSFBMam^PFVE-Zcx7zu-ltRtYlgf3gAC6WI>^4=hM-P1iYJs-dfFaz-Q z<4_(@19L3HAeMxzc@!kUHf+;b+M#2T<|Zmzi-F680;^bwNQ^8)9K%Uv@m?iNGA4$H-qnOatbmF3z@La!N{w&?_$RW>XaW$y2P&VAi)W*Q(!iIS`( z3-j)~ec$~!_uO;OJzp1iU1d*8VMubF(lNnlO6aMbD`EIhG&mm}Us^iQN<@$kv*#cEgbA$lV)+QmY zwJCX!EoM~vpUqL&|7efR)iywUf_;V<`D5h_U6fic%#gOFatkzoKPG^yaBqePqH80t z849}~)m3*vV0pVCsO^cYlGzenhv4XRk}qCj+ffX~rl4hUd9|JDm`bEO*?t*xYeg%7 zR*rY|Pa&SR^R?MuNp%tY6xp=);3N59R{P@5efH)Q=`0n^mY+0!5U9UhY0Uv;+HiBA z{9*jgE8z*UP=6=4=yoU~ zRV@n;8M1Z-HI0TAP6YXcaR=ozu(L|2r&-{*8J3A4#ursi#ODAsaJd2GGy zqyWwNavCz9hR&90_z3}hzB&!_%0WDaF<1 zG3^j)Z$$#rzO2G1it;` z!=kt*#L-z>zvd(fEpN+V;lIvk58m$a7V%Q__prp zEdJbW|MD0A^`HLbU;No)Cm+lcWhI}x?O(n04_^F(Fa5?JAL?a&;dmaIs-In1x8ktr znp1oHLx{wv6;NkG)iOq%^aJ(*?5bJ(L#;nflP00L%f8qVL}3bS{SD|%OTB4r=uOD_ zLf$;i<|ESs^@oFTtc};A#gyHG8Zt9WIE96 zEJ8qT5(a}AbC7EJ7NCnjpVdtnJ;dgqetnTg20y0Q8@M2f#*We=+M03A2 zKoNc#!ttzryjx3FfEl|Xp^<@q?FS?U&bYqFqIZx%N*Uz$+7`)9A>V4(n@8xUq zPq#i&`7GwQ*ghjHJffp4)@LaM#Xi&F)j!Lk2R{A39g6sG?bNpyzxq#G-#@SxYutpd z)RU>sve`Tpg!}bAu%Wgi^u7pjs!1Uqm0n*~^S|_)fAof!0h0gVvEKneE2KV?+!}q5 z=M!yOxH-*8KE+04;9LZv+^kml_kk;ji)*5xEQ0~!Vg%p-ZT+EE+Y)-YXRb9tANhpk zokbHdFRksQi%c#NXudm0yo)6stry4BNp6QoWvj7*piYyM`9+Ch!$vC6iELN76H5a! zk?~IGN#v7BP)jms=bgk9bC$D{1L32DZNPyrB;pgy?jXIf%xDKggd3B?iblo{hoqM} zq8FavXZVGeLwY%4nhH>)fj7wCK5jvp_S0O}(Y`!djS&M+qbZHat;UE^mxtIdI9G^} zm@2TPXx=?lAZb?xVoITM(85rzDJ5pF9O+OFTB0l87Q_LZRB90e08n0%mpbZN1?9EW zij?LS&-ys_8<3vfN2J0>qD6#u;iGv^@lmFn2I?ywZPZs7{7Ah=XdEnKa^eN?Y!J+Z z$C83D>F(5F^#1%ZE8`1!gA8LY!sm-Q76De#5+71>AHNXrWu2dQs3i9oiC07O4+p#_ zIdC(|m|U-S zCc+Tnm}q}V2>V)NVjQO!Ghd+y?3Yfo6v@;JEAqM*c_an{DYB5yepuAx9%;rX=z+2g z6A5wB9dZ;`Sw_K)VM!XNEo@1;LmZ!q(}|dPSgfNcYP&P7jh|@XSNH^0%|78`a#{Lp z&t|xE7R8u4q}N%ZMd{XZ;`I;>Gv-NO;br;5v|fPq>LStB=1tdFeP}GuKEzAVpnX(OijY^ZIBty(@))=B2S|mB z;J#bcqe0U=*7qbU(wtceTw+_7=8P*f(cQ7U#?YMQL!;0qQ4DP=+;~O@x$8}bfRVk? z=LsPRa-pa^T-7KuBbOAeiYc+N*-2H>Bcxa?eb6V^dRTf5rDSqD4WLI41|}M3OFO)s zcWQgN){M#k8Yfk~1ns9CjqpoZTe}o-2PG*=SYv9J*SFsK(#hlc!Mkmt^=z>}WrmK@ z(Ahi??T=f>w65Cr+$9m!quAuTyqry3V(|@zO{GgJj&_YQCLhO46B22=S6IE8qKSPw zhE;8$X!SovEYT-F-*bhZkARj~WU>40mMwXUgl8NmGY-%S!GEM}PNbY}yGDY}f-eea z<(){q6N6kf)QMo-GI(&IWr4I_JdFs9@Y7Z6t{xEP*=EWH-)_`lSo6JM-4ri%70$4k ztWeE1#5hd@Uz;BU_c7_y86g^Pf-7si5|y05hRl}C6tQiWC>c0wv1M6X=aG3ZMIZG7 zKbE-i)?0s9TN9nkT62ap4DEvMj|8(@d8%bM1Z&mFOOJHEnVBGrygBE_KL|kG$XfXa9$nq-OMNq7-$vas;%+3%~W*B-FDv zIzjQZzkDlHOsTub_)lwp>KVl&|K$&B601^C!_9(=KloRl^UsUl{mfg##oA|n7J(02 z`y8i@jE1xa8Y>Jk@}6}5M={F1d+?yYha|~{UZD-=u>wVjlsF;_y(QAv2rTm8ZGi_j zsAoL5YYHAxZ?a3_4VFqJQT#wLi(0Yv8*jnoFD8fdM?9UTUWO0Qv5Uh=da|MEUW-?C zH=RaQ{%TD*TrgRH>&9B4Q z|H%CFezFiFsq|?!mUVt5iSA*9~Xr(e41D}OG0ae6<1XRH+&A!4;`o8d$Aq(bZb}icpN0-oZn45qm_j(F6 zxsy)6A`4OT>{j0s`d;~L9%L(V>UN6ZOAOnv1~sy>!&Y{>3x&%@KKv6u@e|w~HjJ(A z^B)vne2m=DkUQ4P{e)NF$J)c*!Y5>`S2JV18Wh;*3OE$oGG*`pJNF{w>0RxzyMcW6Y@$*kaXl#5eD^q zuSXlZp~C@mU3cHa#ThTes^R4YdodZIb`Lm16*=ogzzV+x*SZXeR9`g!7{EsBA$1vA z=(2`_8{(1`odz-4p&}jPY~w>%=XUUW?51n#*dro@?I>oW;0BwvM6Ln|wqa`{x-XLU z+_n?bVkv*1PwG8HN=9N0m&}kkfrJ9lNDyhHtzX`r&OR{a*Lv#)#@I=;!r_ zK`vb88sk}|0tIZ4W=UuAY7_w_J)6&PN!_~MnF0B>&+3LcQVSTGvQqR7W<+vMsObdR zws(pne^MqX#N{g4*;I0x8Qpi3a7xVXY&LI51MfG8m5s{X%%2*0dGjF=E^>*ucKMX= z7HIW5`_lE#5EhjrJKJ^7gK$h)jkY?`A|0TFhgg|>;pWlNG+oYU3kaA z39DBO4v3x1?=pea%Q>$elG>1EZr@>K_wTw-`A_( z8%yruI>xR-azwXAI~1AYmZ%T;jQbTEJ`Y3&7E?bL1}HD6`nrbN_TD~}%WWZY6X1rA zdg@`QJ0^bwKwj_SA2f7^vh8(t1bHyjf8IZ!pI4Y-HQ7f5T4o;QqX`^q>?GRBaoAkX z=WyN;+`QHL!#Kp?-k_3#={aHD9Jg~aAr!)eBCiXKGOL?p;~s(5jCSpU-_{u{m1!iv zT8GIgjS*Yux`$1^)}C@6hj~yB+0?jgR71U{$pe3C*EV^S+%#)~)->}wq@Zc@ViuV- zpg;AnmYUBW83Le#ik3i3tzO)6YPvk?+|ysYE&IHI6#z~v7pV}rr@(|3NG{o`m@lq- z(wRLg*vdeAXq&CME^7{&oL}#YPQT_0#u7RRehiRy#Yxol=)gbmkRF{!1OAw@HGD95 ziwJ4O8%MN_XmBNDVcHgK9TRR*;p_?dd>(#<*xculOfIkqcNdG3=HtoT02mwR?>n0B zp@E#shceRd)3R-1gHE(f@@sh~BMaQXLY)AklIs8su&!a~+aJ%6Pc8>wi?w5Gp2^m! zZV3HvZ9ODw53?3_b$4qC`PaydR0c9pDMKhbu;%X3lwJb8UDEtk8mb6eo*&-G%4^6RMAfQTXe zA@UkWb7)>6lk$j58p(MCUUqkBRepW((unqn{6;qUSK{pYOo_`%&<=L=Yg~w6^NeFF zNfD4U2^bl)W*`Q$ z5V4v&EZMro2SJV&*$5;?w|p|2*)TU-44*S9!&~i~goD|2d!wfj7n!7lD$a9DNf9H< zsRt4>q$hqAfm+l zZU;DL+MZno&vHD>BC-_(NosU9AJKM!c*qDvwp(HoVx5}S`NIaI9>MUFZtHiQTv<)p zQ^*cOl?0P5I~1kky!A^ow2-XG=_`M$%1_697tJq@!sf& z#F?bBZ!?~DiPR0w(=L1R z<{h5eEL&kbEyXcZ1dUk{^eW-|6bQ%8U&*qb66fw>p! zKV`f#f8m{#?#sU>dYtt3e6qqzlit8fUr1o;AxrsmHsSt8&@5d=g>`jVxXIo zCDDr1*+==%*z5}lBuY+W#S5dNro5fuH_^aZb}?zv@a_0W!jVPXmiaJt^7c85@&VmJ zIj2o!+dEmdUEP%Mi)Pn8gsSZUOC%3iInt4YU3yuBe(+cj0_sRY*FQ@KSaga0KF&qN zdSbg~Z2mON9qJ(6$nszo$qGo3!M4@bS)?GwdW#taIH4!QVw=5vs&hdY$_4Qz!&(?R?tvH`(9wn* zan#y)IskUeXL&wA{W4*0amb<1DS0~9-?jL>T7e$$f`V@hd$XGIw=iZ!q=fNJ`gkG zCUg-MTIX7ALK~dp_Zo;Qdxy-GlE!_}v4g;COfQC={NcccS?Bqz{jjFjGVYn{O@4Qj zn|?$$L_O#<%Wg5BG^@4={jjF{h-H(PPkG(Lao*NeKYPZXwU9pyuj`uUd+aCLvi*C zr`Fca{+1*{DhPa6RxHMyzdZM0UgjA+w_APemgu`kp{FFZkJPY4<){xODkqqdMCF{b zyu6Wq=#VG`Ta*X`q=A00cp5+^Tg_W8W=T2ynZ&KRH~QmUI_5Pcm+FoP(@lf zkz;wg{JHHvXU%TIizIJfBz^lL#1lmOZ<-oi3bsEM3sGF2*c(OCJw86EjUp-@W4R;v zDo%y>waEg(93GwF{SB~B?@xyJCngKb7?CNK-%WWvYf}Z$E283&P<}n+pXEwSa5IUD zi=o}u!}k{^J^!((VpP9B9)5pr(%+w&D#n!mbjV*1?Vp+Qemx!DFNWvy;rU4T?$J>0 zGFJvOj+PYJmm3@`N3h#DJ%YU(DSr8Ty0Nf%E5GrS?>3RDiZvu+2y8o@6+YmGa5FKN zkr>(-388F;snWN?)7sr5aB+DxZ?j0-^44hLdnUkWd5&K};1}SwYHIyHE zKG!dGNu@3gEJZ}VvaZ)u>a~HT9>KLN0Q{`H%(yei#@IHA5dPmk%8Uxzh2c*P`PyKK zXu+^V#>4VqF*SU3Fv~KHg7Cb62V2%MejYa57FGYlu(m{?wtOhx!SLa$e7KJXmBJn@Fk+npcNikDv?JT%YO&GK1Zzhm+w7{i|d_H3^63*e?ob;lqSc0L$0r|YqS zt4&wNvDOdrF(W;Yjfo~K6C-cX2Af?WpQuURrX9v!Lu&N@tIiGz{|cEzf+fhZdyly{ z#R0doScXsyhVJ0}EzsSFDbFHv*QE7v?)s}B>+1-NOPu4wjX1ZZra07OdNkV<`e^?~ zzoyHZYdWKhy=uu;$i;q9yqY#PdRds?FM7IPbZv9d$H>?)(o1RR-=NiXsb3RLE*n3* zjKRKbhl2)oN`etz&u`XYZ>;Eu?T(j#^F{s4#9kOKE_pS*-_j?-Z;uc7?YZ#Vvs-^l zUJSpzfPIEjj9#6|)8V(Lwtkns5`KGm>u<^H;kVa@G11#JhBw1+Z}0@7=^NrDaOAiI z=b-nec#0l;S>?v}Bn^H=*5sEfY?hRNo=K8LUQ^*@~R~l zQO2>qXHEPkUJE%3xWbxCvDGdn_ML=kp=^b3E10*Xe&Y zj@!!q${H8~G29m|_QdYmk3I4hrfNVPh6 z#sEp^G@e{)+ssFir4y2fumPMQnzXxg3}a~Z2Nv?IN`qo$=`QYO_5;;yKxEMsRnf)^ z)iGMbDK{E@ievsQMhaT$ku8i-efOlmbi2)$qpF9%2 zw}v^0@a5@YTT5$=&NhZJCNPZ4y3X6iW+gNY?dTUytZ=2=bgvwe|ErV(bwYi!-feF@ z4vpiuJFsas$)mn;zT89+flVB|u{tqQoI&R2eKqQ4=#D{I@ApRr{C>9b`^5pjKU(?y zi2=Vqh89LgwWGJjN*PCgYdoS}nT!)-Ik~HL#Mi-5;hl3G-Ss%;A3+q!o&`{nK z`+=}lygYHVBlEu3!t=}Fxi;zfIwvBeES|(T1IvNP5r`1^$tu#Ts8~x^EsTY(Ax3;4 zF{3`zGfR&uXiNVEx!!g;4l9o3opzU4IyHIDAGo%B323ZBxQZCl=a~aRBWjWtC6zci)BIcys0#5H9p%f z`1Dl^%E%-PMS@tFakwu}rA8aZ3;lx6l?8_f_3`n3!HZ?Vfqj%=Noe|!D;2b+&-M$R zE(9#1k9LUTCkz-i{JFoZC&UB6-l@rz-Cl%p4Za*O@0V(B;UoJV z3o=QW*l{B+apPiIErT`8mNF?ZhY)zDtDSG>dAjD$7dCu%VC!cb`fL5MUZ@$!fT)Aa z24^9Jvo@rCy#BnB_mpQ}sL+@}CO%&xZULET5}P zngY)EP!c~MJ>>SIrw01>(b?EYWrCv-S_ZC3vCiK}6`AP2!Sh)rUe71P^Ud&lB>YaK zKmXm{vD=-N3&Fgv@|v&>C8My-@}Z-sS(r#ao*sdq~I^B`g~#IcxDV@hX=1DQ4GirQZYNl1RRe5vYmvxHk9|-Sex$I4(-I zCxOsMyNHH@msiyKwfk%D!#->1K{KO8F!4TGe4n>y4=iH4L&z*HLnY?T+~IxYE)vO* zFaI{gcr=0#gO`S^*_>E6is$>um-|~E@i+EfnY6;a zrWk>DlPqF8ZpxVmdNB=yERBi8^OI6PCVV|ru&3{h7FPpzF>S37pZC z_UB+zH{%L6B(tDXHfwK~fzx%qMgq~4oIpVZi!=43#5!s#|AHL)aabYAF|IA=0+Nr` z=|EeP(|dslC2*@m5$&T&6YFToVjW5TMaT*I%U?K4L2e2U6Lesj6i$TJ46zs}gn&u` zM}#&8-wSTEcmbn0F&bS0$LT>VWp#o9TixhA?dFpm;uNVP$8_S-%(X|0iM zyY1x7n62YIaZ4oEi5>Eo-Eo*$e+ULFYSsqN+ zQa-ak$>0#V4cej~TA@e}^C#iC&~0kIJ;hXOP)rw3o9fJtT1{RFFvDCc#*$7E z+cnoqJeZh;FeB8W&sjH%F!S~W%U|y($JmOfqJy$sjWW54euYftqb(g)73uoaAP>r7{ebccN5F7Td=@{Pe`gl`X1=`M{f;comLKl!lRd|GNFz0$?Q`Rr z@04b&QB1EYUsU;Gyoxrn`(0L7HzwyX%b~9CP#*S0%h_`FohWwqTba9{A^WoZTtWLU zR@U?HT+i9Ov+v5j3q$#3UMPbG(;w#^&Tm?$&hJ!bKA_IrB-NR9KjZNMRA=7q2mE0z zY8T$0?LU{_RB7*~(4Ly;wRh9j_F@fwIlC#e2b`9@p+z1Hd5Hl&4!jlpM+48k1M@@~ zw>023lp!Bfy*(M;4%l05mF@JF=zi^6H}dQJYOz2}2~B82s=YDD8mYxqxhO~4bfoJd zr;V*ywu?MlFp>BKa@o+$Jqu(mwFN2p7)c!uNWMT)yBf(iNZNU@ zQCxtR6{$X2`7hrlAxsM8WRLzGZNWsXrc5||P{GB9IBSKkhOeppAc&H1Dc)Oe4Il3Y ziV%ZQ3I9_5b@!wapAfBy5IOCyOk{h$(td*c>c6l2llUvYAuGB_zt;G*`j4Nwd#C_4 z7Ds6_+OT#0KGuu;4@>|X@n7C_+^_ zVUy(}B0kCBLGfWi^NRWQRSryMJm86iolT)of@e|1?3>p|#)P&}#2~&NdCLy=iEeP+ehY?(@meguaBm2}66lI<%p! zLmT=A4Go*UYaW^(s2Q3TX@6*^zLkb1Qq&*X%XM38j*x%VXH5jRKeQ*mm4>Emr9ZSY z-%3N%=Gh6D$-RlqSb(2-vUBY;3{T1KWf^Up- zxEJFvs_V9n>bh^xZe7o=yXM{6{9<&ZL92Ez%+->!(S>RB8pUN3sKYVBcc(6Uj^GN# zFT<=YDP|&8Lt@+DN5!U@!lk$Y?)s8Wfhe8qOc9O~@OIT>MCKx`aOzkP(L$!B1Lru0 zO>-}~w}|=3S~pG}W?jt`u}is}b@#4xc5La0w5N@nYXnq9V^yM2okx(+QwLTpjRMUG z*1{%Dw1^-mO)wH}NH~v}M{~^4@+hJn+zCrpUqmOxnr zPeK$?ZAaB|KS)tKj!W`9>d2!=4UuGc%pdqTJYP8+Nq!Sa@V1R-x2_&|3MmY-seTku z5Frtxz71YKDTYrlDQKN;%VkaJ(jZPBssl4oFJZWIYm9encAfhJBVs}zn+@L@b0AWt zL^3kZq%th+t(Rp`qTN;-dbs&5mWIq9WU z$th+zSCzCl+>Oc>Yrs-1_I~zpC{JGT$hbhl^2hS9H=@;w;h6$54;hezYvkHo2xC;G z0hRIhfT~92=g|kMM7T60kW1`jKsE7G04jZD#2Oq>kWNK!p^>lM%yF94R1(()XJ712 z3{f-h>AuV)9V~MY+eB_}z4g-;_ej{6O>;?uD3AmjNK%O1B6-U$4H=g*i>yuO`4I|% z#1bhez|m2fKOPB8KlUSQbJY_!uT!Ca!xy9-NYrMYSE2|WwrT|G@YY)&^WKO)JV;U& zde8~s9<~8Q=G3Dhs-U->!J$Eu8AguxO+%XwbrpX*YZ`R+&msi zy%V~CnL6JZaPe4xOm=?UU}es@jvvV0|5@7`8YACVM zYsIVCjiu}q*8~$8A`*24llIYW#%_an$95gGJm#5y33cQIDmCP>d$5+J2k{s%k$+LTPx2eYpy~$qnIeX zznbb6DFn-D*RJ(gPvyBrC$(qsd{%oF&qoO*r0198E<-Oa3?I#I=qMuA4G6-m1h!2? zQa{nXKAS_KAQcCpt(iJWzLK6Su&Wqo|Ca|#>lzFs4JczQ4E&v`S%jpNVPvfW5Jjx0c zmQNHfz!%LZZXYcqYN625jlLImA}?^k3P9*j((57BBQMYEe@efqgVOI>t~oLAriwmA zMf#1JC|5)+z%@}^0z9;Z0z@re_BRvjP84VD4QNgjuMnts<|ysq#3yLz=E_QYSpQer z!-rr{dman7mUWJHiAq6lx;Y@<#Xt!pAyt&v#G)liU&wns#k8n)qWBXDVRbuIOZfDA zdILZB?Q-o{@sIT?2Fg(J!ccJ`K34ogGNp%l63H33l3V9ek)`bn1xTX@b;YO63)b!( z0`oB{gotQ>1f({IF{KvZ8y5IbA>*J=7_sTXrCEL>KCJQfI6`zIE76b^T&}YiIiEQC zJrsr+seRvZa>oL%4@R@Ay7e`v9kv4V75dVqTslQEeM`$o?IR*MG+mEZ54>}K>)?jthxO6y0}X@?=h7nEnb zqWfzVk*F+jwnhf}eV6`@mD~FmU6h%E<2@`%asmY{xFLCTdR+Yg2|6H(_<%4E4)B6v zrjPPo}sMs1DqW? zRcglB=zHh*K{U0VUEk_nXQZX20LgIYOmZN5EfwruU(~cH@$~+w7U#0m$Yz1Z!BjDQ ztoUbaG2Je?vJEZ%LKI<|x9jwFUcYg#NOry6KCd@S25=qc?N{_R44SZ6TxuAz$P(N| zO9kj(1U+WO#-h86Pbu6psPI=TlL?I=1s8iD)isS)Xf=~G{X?@{{xxzvb}x6!u_Z(z zIGkk&4P$4S1wYFq*}^eRCZ_;-%DL?c#7t#*sO(T?WrtW${j#{sU2|D{=dP)&aM1_? zY8vh;_BrJ^ORRR5AOu03)L0ms`o}~4BjSg~0+k8<3y2wR3OCdiqk}c?^idAq?62Y@ zD7-3Tr0bb?j6kC9PJ<%|AU)3Yo8c$|vH^~wFf&fQtQv`i* zIF_O(qk=n`Lk|BN;fNsa4l1tSn)lR95GZepzPWn#(c^*Hm`nECefAf`N1UfMIhvlpfKt?m_V# zNs`dLjk6FYla&~dk$G?q)gY*Sg^3B6?ab;XXp1p^*U+xL187^>YlQZd%Wj4C;Ii)+ z+E&fGg0{{c1E8%?HpB@ETYC34xUsj!Z^nuQpBTwhx*5@7mI!3T`FSzx{hsjyk=*8a z;D9pA@0|9g`@gsO0e6)KkG6b(SqGE>qs39@zJ|KRK{g$mrAL@SCO!e_`8bmkK2oz^B7HYXdU8f1sG6BnVb)TG+b zat}?a88w+ynzVGKNqeVe8g*MR;M3;KnhV6on!}(gz%+vn08>S#1*RCt$@3i16(-hQ zdfgQb5w+k#+ph^Yt_>9Cwhp)q5bqxLgBz;?LX2Jye-48fz~T@OV6g&j0E>e>uq^K; zoMlU(F@-`jqM``VbheSZeU|B9dyEJAi;wfjrN+o)mvPLf=&yifv| z^1BY zUzfr?DJjw$!$-Tb8sPi0^hUfGyUSt5DncY(-sj>L$tPzhz>$JESIin6CBx)UzT1T7#_DH*g!Y3Pn)*g|+rDAdPN9tM!>?X188g#gXhRaZdcnD!7=aCTB zl%O^6DW@d~+=8-m$(5Zga@Ib8JHaqN>nfEcw4ARhd=F|3zvm98SJzu*kwukG6QGbEDO*-l}#!znBoqU*FjI( zP@Yf}AtXF5cD3o!<%3Mulfrezi2TWJ)YhJ4eGnvjJs8~^{e2K!3KvW@?nFj8ktw{` zN{IQ3{cD8uoz0TWS&LuK6(U7?+%)6(KKu+6>HATnoI=4nTNw$~5rpdIl95G1dZ_yC z@*CQWRnQYWjr3l3syg{GGF)uq=+VeBLA|oYbt66^t1l_J0>zXo7zjnRM_j58 zz7Z;wbscwYLjvEh9u5?)26EYyiBLEK?Tzl0BoN|%OLQ-&RmCVxA9_OO3{7Fc8qFCH zO&k!K{~y>hSNCO`TmV~uyGOh;BrasDK-Ykc_!=sBf8M!XECV< zj~z~;wE%=QI+U=!v_d&)iHw^v$EmROQ~MU9SVW_z-e-}bK0}UeoN;!OoRpx5l38(Y zm|8|$k5@c&w@0DvB!#F5ktaMYO``(v6|~cP0RU)6&U;(wfUw;^H6=ca3h0WMFfBy( ztZBahMp8Ex^-m!PhB3%=>*`LX1&ZT~pBA{o;`A46i%>7cl!2d=>6|l+jK2>J1X|T> z*l@1IvS)!Nr@p576mzFf&uySpLXvI2yUn4vHtZaP0n~cQZ1WO4#eL5Y>NG3k7AP_Z zCU~JrzH_VvpyqeAK1|=e;b3oKuVvFfc1y!TPp+snj0F%m9ACDm7+achq_KH3|0ph4{Ps$_%x;UUOR!W^A@i>$v z^v3pokak0FWGBF0Ag))FPOD@%5F}h2vAr1Rfbmt3uyOZ}jgtxAb_Ki|(PzPcpj_6o z5O=s+JPPTtkmL!1Ot65f%R@Z}IRmV%=K<}qW{G$p%%`{+nrg+Pun0m`CE}q(U>U#^ zYt8|K<{Wjk`bykgtpRMBT&`wopbzPsvvYto>_oUpYLn4{!e6r4vyc058eB=(Gd5Vv zzCD;7&?jLkk4_jZK*X)s8tXuUlAl7}qN6P{#4jtXMwu?WQ)k-j+q5(D={#P)+5+BZ zoJ;EmxyS=gV7!o0GGW{mgf^9nm~=ffD-Aynqw0H-X$8&&Q#$fSCaNA6tys8hK83Z# z&H4MhKKUq7ALwFI17`&}O<)UHe#xmuA^Ksju)L5Pm8FO=qYcemrhg;miVzR2)~igC z2I46^Mcc$!h5D{@&PQ#!gY8l0E2d=we@o->_8)05+rVq;)=|l4|R}m81#I;<0WtU0%%_^c)v6 z0;vr0!esKB_-dv|(qP93@{8(OVJ(S9BHug~j!t$cs}$A}L=iHv@-<)fz??ISznCS8 z)PIU-%(Tfxoq|@QoW<%un^<;sacs|;ph!-o3ij+y=|Ur{V{jrRE)l^jD8p%>osZz| zMMwcAd*mzECHfN+VU?Jp8JWacCAB~*-jie=1DZSn@#0me&5YEn|2o@?3r%py<|>)3 z;znSOwB!fmYOkxw0F;9NwSIj{Abh|sg?%N$>5iYaqx84C9i)g@bc6@1_An2wZ8*e(i;_57=>m^~JXqVf zV+&9A@c>Vjd4MNsnkil60iNI=EZURtK$Jbt1FY%zqwO?c9I%da{)!Q4%S&+lrnqgy zL~#Hn_YyG54~^S(QkaI}N_4&Uw!P7R#R}(fKh}4=KMEed&2 zhyZ<7R7VODINn?6kUSnZ@z98Y`QZ1!d{BH~eur4x)+|Jea8<-_=9n5No4|};`y_s$ z45ixvV%{;Md%0qX6FP~9*&FhVy0KvADfod65eTtP7?VIOCYy64{2zaj3=xuAsM6rU z>~b!oS_LVb(C74tkSYF!ZjI6rRLEwyj}TRL(U;Va_|L%KUv`~dQ5e%>41dP{V-r#^T~ z^y5$*87Tr8x{XDX9@DbE8T{AEyMpcpetZIeQ4n$6 zdNNtfo-C$9_Ea?+d!mp%UCo|G%;lAAt7dO2wukKP)$HvB+nY5xQ_Y?!h>>C0v(@a` zVn@i{QO({_>Q?dU;c#g zrwoaRPR<6{gGh*CN3>r|lbY12@78#hq{e$|yi@kUCXVH2NVGN@OCh=CvfJVnh0|;O z?npcy4i(1h3Un3jwBc9ZF*k6eHL(|*!H9xHcmHsgcxQrXhy}NMHyVu8rjq;j4#>HQ z-Ai#_QY)~=l^l>x_dC+I$-(hbD4vhDRDg@h;0mU_0xn(oV6}>8 zt5wk9XNfW}!V{fC%e#cgDlW`-3E73;K%1-}mooV-5-`h^{7c5vg`Ie)1_4@|?FsQia4ah!Z5KO0~J993zcC`jsH5 zl4yyF$<+}Z$21+$F+|i69Sxv32`rU$M7K;GjbSe~iK!$LL5ixOxD138V5f1UY#gx@ z@d=zK#V%StdMD7P@*^gA`Vp~!HhvDC$j^s!y3DasQU<=-PmWeFKfPI3YecYca2PKeLAbFvT`Y5O|BMv5i zH0{x{igojZ4@HoNEw2|ZlBI!L0Bwj<&F9_3nz8z8rnCGI5py&VpNpi4MWKWk*pWya ztUeUFsC;I(WjkQ>opp>_^#$q@2Nk9`i|Ug#ZN4!cQWKQo8yWlN>Bn@7sT55@Hw9}H zx}}y%E9jr@7Jv|Z$T`R?o6B7Sl<2F)EF^5+knXgXj*TvY78`+v?-xiS z0hCE-emU+%wR72p&4)>7B&bPBV%m)u60!`qt>Ufd`*cDu(uySMm0fndGDD7$-u5FTsscNr(RB}ichh$Of=VBMEWTkA2&IO@XkY?%L z9Y_bg@Pj0DUc+YoeU@m}!cCG}wrRp3;HAq-)CWzTNwT!8Q|=>^B+F97MP|xfD)x2k z3U}9CwJU#bD6O4LTv)-{u5+k{7|6&Bs-rC)nyTYCE`K_XvnNP8g1MwbBf3aES=2RJ zmNF5Fr6exll&*CYsm7b)X*-$=qU@4Dl)p?NzIe|3poI&w7$7=ag(#eSsz`TNWsY)L)uCa*&AJ+w!%`G$V~ygeI(12&D{Z z%uliQHjCu<16`wMHF^xxB$EQx+P+!ULL0=?LK`$=dlJxJ7Z=*Lo<{T-W~fh6fD=82 zqW6eNTgXNKR8lAfsa#j8W+}U+FY-H{!YaYi$Y?c~9OTbo_1K_k;8nISF8K|rs!@l}q0k!(sNHpi{EShk1&|3X4Gg|%J3LI9yrEK+E z%7mrpD*J2Rhb&#GZEIbPv~Ckp4yaxZsJ>}H+Sm!w*Jgc%ajjJbBzl{N#X>1=<^o%_ zV%T(80la9%kUT&R1uWg+>M7Luz*oOapSMc|4-n1KG4WsaH;N$u!otvOv!pcJpqgfz zy;HM|(l5-mrPx3yzjdye44-QoxWaTYB|c+$tsHo5z`%#-x|ya_?Ayxohc2yiS`$beCROs|Ulsg-G z=;1s;&gpF_#cB`8eKEnEHPQ8I5VNm4dT)WNP*>_2Q7rM-Rk+pzT*(Hmb%U#*3Kaq} zn_vo|+5poEl+ZC5l%P$0C=pC$5R}52`Cq<4Yer&^DhM!2w*xQPfWX;Hp$gjr%w@Pc zFRY`lBU8EfHv8q*u`8ULugc&4=Oj&GE9^qS9)HU&78qNxxz5At1_dMZ#`lW1Ws|G$ zy_8~K>vOtM!Lki|9Or?7GNIndq0i^`$cpp1UCrtOJFgZx@j|sRurTS2-LJA?^A39* z9jWEbn~ghztnh{QJq%)Cb$t!AAqY)ph;?CZ4qMxS$cP3(9gKSD`crYe)8zAW#)G$o zEE4njAtOY2@-QzEKiK1*IWe5!_Xh0__h46(zB&Ww}H%?eQa1=242=7e) zU;LM7Rc}%z0R8{h-x7cO&;FMB+n4>V=5POxzYY1@fAY7wzy06-HtcVg{jK3||Iy!? z{`MdIZN%UH+}~RM_PW2d{q4{EZPefX)ZfPZ?N9t|+}~dFw~oL4vA<3D+aLMcq`&=p zf1C2RSN(0;-~Q0ww)xxtf7|JA|Hj|u{Ox6b zoA#UNe}?hdwg1D{uAYpcQmc)W0w-L%B%c||yoYstdW*$IseTKoYv z2U@~)-{gINYjd6VIXijL8CY$8Om&MlS&Q1@Ip&I5SZYaI{g5xb-`4ra-ZG5(o+#C1EZo8lEW7aTlu2txY0pRaxj+f2ijDQv$tQ}`JZ z(ujOseP;&|CEo2V3zwftrfa)-_k&(Aa`S80L2{?;9lMy%`tt)#;Yg_|=mFo4qP2_}8s?H0W`g2yIxA zVh|BLO3Km9n$94CxEL5BrDoX&qz>mt zldlY+P_~N!@@lG1X$I^yYj@h@x0+1$186 z+_F?Vl^!EuEBlG!m!Sj6VOgPaw6H@wQ9_~qF_xVpo%8G~Fj`1@5>)z#|IAx>$Q5_Y z9x7SsU`*D?jp&-JwuLI~jZy+^TA=C;V}+j7#?qM98757|fJy#W?A5$^x(esney=&# zsh^pk?6$m`+zN*lr2fm~hTWSHYnPS0v>W@3qLWQsfKOLsa0)Gu0+!Pxf_?<@2m;As zx2$(QCqv@CgIg6?Z5%)ZE^c5ff#u#G@`w{qO}!ccfg!0^nY+j~41O94>eW)_p zC{M#LIl4pCtBxT~);<)G!LBllRvM zEp!l%3q%KuXA@=&YW`X-2)`xta(rqR~)E5X_jG>*8gf^TV+@BwU@{=xs*g!8dK1 zrD6nSaJg|ttuI{@6QN#yOx|0}SIJF)e49i)Ce61-`@)7=5R*z`qGR~uhB z&pk})O)B^FNGzajm7luQPHq>qCw(o9oB zzu6KHJO+-}P||jgKrKr~#kZPtMLJZAg4}0Rk_w~3xerD~CT9A;7p>4%Jd;d*v|UjH z)FRA)D#v;f?kM)~rtOMT zrcQ@BKlipPYQ$E{w=1&}8n9idkG5U8{SPe3hV80W@&M%sOG?WS-~o~f50Kb$g$L-t z2f3uNMADPfF<28C6VCy7#1F$B)qaynKwLmQ)(Grc;P}v> zbpDo3BegD8)J&AO%;42pY};JfEm2rQ;I%%gT}3en%ZxzUSZ3CIsU^G`8Du{QIA^Qe z1Oy&nz~~){o)o5s%82F??5u|8#A5;_%!EqNfb+H+GpZh=mJDFj2o_OJQZUazzT)x^ z__bi+*0*#7U$M>CQi4%K;sK-9M7okXqox`Rw}(OJM7(QcyepkB-jz+zgb)DSc~@8x z(1*DdZ=~~0Pi6obGoOrs`K;J&0(-sM?R?^4;=~HUsUrxh^5(EQcJNTH^Y7x$nR?~& zzX1o)VV2GTP|Y*I6NBT5TDoFeY;QzS=WlB2r4t723oLL}Oyu^=e%2eq1+drGo3 zq=RW7>L+@e;I&E^H&}>i4>y{{`Uo9yJr8J9IbsldqYAXs*R=JrV9L#j+BY#FV5=hp ze<7UEoonfba0Jh-yL-d`)VmN;yc+=AnB2jcDPT?7`rJ1T066)414e)3n`ZQ!0|V-R z{pSAmZ<^8D`PfL|UnJN;#(&qGHrKGKHB%^vW!~)k!x+&-;^#lNg@R1)$FPW8^D8)@ zl-YV7l#T0r7t3))P%_K-CB*u}1y^#TCAieagH`$@Zj5ZR{m8lyDM&W5Ke?aB=*{VU zJcjw@K7Kh&x_vWyh_yq6DwgV<{=)fymml?)1NQPa^g<^O4$zDp$Xj2TV?Q~5OLPy# zco9<}uQq$pcBB)yy};w~@?*Sgl+B=QgN0F%+I%Y$X<$!Ls2#Sz#- zf$8phd$1=RU~egWIu7Xf zD(ndwCD_}YG)v4x(_lZ+hdp-$^kI)ff?(gw#sz!iggw|dvoXQm+9_dgFD2~lrGz~% zSA_j&ANEZm7xiFoFD2~lrG&jnFQ;i+tSJwiWz|QVWl!+1nP~b<5Fte~VFFGwUv4TG zP2@~lY;w*KB^~Rh$d}c)=`54+4pJ-Z?jCbrV)A_2X`B z!jFcK;XWRX9)2{mTvyaO4q;(!+DnNa_EO>pFIU8mb{{_)++Etk4|^%`!(K}KK;S94 zDc}`;a4r;Ln76(9XiL<*?LWaA9!!Q94IxJ2E7cLBDP44_j5V~7HoDZMpj`eO)AV%|J|Wl4gGuaplR*SCWz8m?5*NCgrH-ZO<3qK zo6p;eP3OxY$$Y+INt@7DIU9-E65UQSO0ZGQ=qR-vILbw(J!0FgPAJaKaw}`;urZoV z%3zbMK#MP{@~NBC*Ft+fuWN~VWkMNWvBA$Agu=%mbJCB_0-9f$;2ml4b27mcsZR6lL0QvEtEy?(usEV6<6H1$?6U z*+K@1tcjVt04t`>hsw^v}hCv87L^hAJ14{(VJFW6ySjz9d?TPNSb zEp*fpch7a(N}McnPTn)u9jz|Q?zn$JI9zj04@n$eMN z+Z_rCy2fg@BHfVs7Re?fi zo%aMN+%Fx_d*4=|EdLb)3P4+|0&VF%0@`p7Xb0Iv*{DRcH&H3X4c}zjg*j| zu0Ut6jkNLoRjCBOUv)w8`&Ds^->(Wr+bNc-fZo>w^gW_LC zI(@DTCS>-ReAF|}b=xo{`Yt|hTfE>+l$WThP)%h2B_i_Ls$EPoO>T{|mSITFtz@z< z+-2RE*Xk)Jofj^0>CHLi(i7R-(R?lxvU%sl3YF6?R&ea@d~S|@!ol2=&ujVW3Zm(3 zZZ6|(nu-XtE=1V*(e9jh7eyPKL!^x7y?3gX?Lc3vZ{QBpEVh%ZoPOVU>G$n!E@hgo zz>%BPq`Dz!ECYvmobwt?SQHt94jU*w9on(u8E};mLu%HuozFm37_peNa}VXlxbM4L z{Fx5s81i=>&fDvJ1zB3}qMtB!|I#`U9$@y)b;&-%L`2Z1R1? z{4BLuNqB<)wI9f*ur5-ML|(Mii05Mf(1Gr!bHlZK2Z*S}0BUxdzBI=CLFgmm&ewig zqswQ7y5Pdme1=p$9*7ay%nE<<;qx4IMmDXTk>1)FS?3_orhp=HAtc0Id>tw=e4ahy zeAbZTG}bJN!oUrupqC+$uZC^54!pYouomHupYEL5p5X$auzcH*k}M*Zeo6IScXRqeXv(D|$V})^DJA)n z@a9sIKee|jN=bH?Qj-6ma=axbgZ}gI=2DW^!&_v({TV3IO|+VQ)&qGQOIE_E8B`%+vjVS2n%x>7)vy^W)?p5jiB0!TZbcr$zJfxl2nzjhr+u?;WVHO+d7(77#qM_3n_rKPI0h$L?HW9nKN z3}`N|Wc*|1jVNMJePg34J+NO+&jVuy(+(#J7u?ejCmq7aXzK)3E1K}1@*ZUsGkX{z zAEY_lnN&H|An8j#xp*+dAGT*VfJX~%BB0dRFo zlq3MaY*7pV*qp#w;eHg!ydPT&;cFOoP?}f4B1i-)iIWbA<+p*v1~8P+4jQG>Bi!(q38B(X%!q zp2NKSIkwmyxM_tA7-K4rONAYy4H=G*B~3%@(vUI|s4m#SR>|QC3>J1%55B{Mrg$tC zR?5!^T$C2n^`oQ>TnY~?yJ6`W6+MLzdOCza$PBAx{p7Y6n0zcj@MK5^@!(g?+8~0# zqRP_2{094bx`mfLrZr*-dE$Vk>vWg~u1@cKPz-7?2~uafa{5_WRizjT)6hcChMZ5$ znmO#r5f#&=J3~hpkwUesfB?%`OeF+?fn*=ghdOJJlmb&F3vh8K#HTKDPhf=w;u5F# z0zk$azLlksISr;jN1D>peSz%E%O;L6_$ZK zvb$}aRzRTo1E!y0@%L8awZ}VQnpi8Q4WtAmQ(!$zs? ziS$D0Ik?0zN0CvKnXz5Tb8ucm&^eWuZ=%JsMI_ST|6}=f%4pnJEsoLzCYo` zhj>RG+8>9CM_8Oh2qmvWM1SDS1+Cm!OUbog&c%f=_5kZ zU<3gdCt&b>Nt}IaEMALO!bw(2ds=~hh0*&mfg9=B@Vy(8`wdl|cJAWj$Evm7@h(iu3?Tik zvVm*ORt(3!29>@UZ?5#N9!RgoiBR2aansiQF`8TU-r(vRC6(09>#juE^uE#5b>EybBUD!|22MtM2(0^VavI z=R@y7&ug3AJKmF?*WQDkr(c(zr(1jes}m`q03Nj12?Vsr$JmQFaTc@3%4h%`v!~+l zrkM5^VvA!O>dDz)HL*sZcs{}X1L7wLN=)=7#8{n-tch&Wx0zbRv2*54GvS%Yw!dlT zMtvH8@*2K|f!X@?xa4HxNSu&2PM0@<(nFKUgln)#U(A|zT2W&N^f@x<0>Hzh?EuZ) zO*%l2W%Vy+!=a#VVMpR^=98$Hg5?$;hch7AS|IY@9m;IF(`%4n`KR?kP=F|ITV-*jp~rbevUx~`Fcbs|!79i>QeQpq>! znO(X-@-j{~Swq}R?*%(HcuEdA83G5=z0mv=B#0EugWWa|Jc_8xT#7F9R3j}Ffgg+p z&9vC=f%z5;NuQfcnOeL%)}VMx^O(T$kPzNF>RaldCWI5GPLm2ru4sbn@LaYkm2Eet_d_at(1lOU9Mb?KdQcy?&jfYQpWGK>hPVjM$$@_Hs6&IluS7?S zTIT_V$_x^VN;enkga~w^Oul1+88=dxG)}!`BzSJju2wqSF-br0X6b#xhTw8RI!?}g znt$#w+&loA1n2;?nOXg4-tr-|ZQ35yIH6pILud5T8-xObX%L9Ld`fkKWElCl4P?>> z!rd5B9EE`l2^;8(0PMZ8e)jvKX(9gF5Su;V7|5 ztvhf`f~&}eP$gZ>A-qBx^|nMCyZ_2YCS=hvvl)g8rH(Hf@S_%ngfQzq4Q+PoKukPC zq?#UX)CS?kfzbB@*NPi`%;1O|DKL%DN)|GtSOD+TAdD{WgAf7G3}`FpvJ=1By}LJh`vxhgHcOZjge%)<#Z zmKUJK9{`&_iXv*bICxafb)2W|OZJh57p6BES5m`=*TF^Ebxd$X-Q{^P6%JCr-W^=#-iwjDHmKq_Si`8sM z>?^%rafYq4P4XEb%{@XvD;$7Zzt|T=U**7~E+z`&UZIkG zfntj0D!r5N7abE1so7w|G#`e0w6FW%MmD@PH@vwlQ#~bkoZ@D&NNfdwmLYDIoQO8! zw3XJla!UDtg2v&8-<7O{`7GBlk|MglNm%mDian19tXU7wPdlP~+#g5$vCkix2;ioe zIoFOoD*cVbo{UcSu}5!x?9p2vd$MR zho!c9VREGm?TH_Mv6eL7T~N1#lM-roCw`E$LE0**M|VR1yeB>S*9epZvjQe0)#=G; zN`w7Hl#p%CXbP@ubQZ=`hyS=dj_5ud$;)MPg1eh-ioSgj6i!5>jOK^;mZJG#$OJ}e zN7PsGCn?qclxuZu=X<+Pp(aieIikSOIR&9-c0?(A11+rf#z|V)8%UsfBf@08VLNTH zhxcrW21gK-;aWd}AWD^X*Lf3+Csty^wJSf`rU{B#?4!5AufF(a_J7Hvn?~CFWle0^Cb#Qf@EX zvI+URY=awC{vD*o?OMQWOzth)xUWz7he=`aaEJ%jTgsYi#PYdWQ&z327#r7pJaE_H zs!qA`Eb+iRVvz^D30Jl`yM|TM7g5go-KQD3m8(!^kwL(4jT$?+Z87HXHBD=yWoS;_ zaXHez<8s?=2q8?pF)Awq&KfA1O!z?T9S=P(;RCuC`&~+1#={u~gOYwhXZuV_fw9eE za^%Aix-Hhz_Px>J&VQ?GBIk3!qcLz7XW))4Wfw1Yh{MqLIU*lr9n0{NuE_{~g}Ow! zu28~%)eK6cMux!cP;%qL_-%~33?-%SN~_zx?ZG^^^6W|7q1XyKOV zLhQ-qTcZCVBoE#a{R+u}l8k$8h!W?ZozgA}v%;Ulc{C?oGMC=ia_3iNe8P-UfG>Jw zq*~>bUPaqP1uQ_;P>4_^20!jcM&cMHkmCr6Vp+B7U@HT^j<^l2K`!0PM*MJqxtKsc zdy~RRZpT!(FvS?z26-X>Wjhr7+oTskpEwi-9Q* zVysR(7$llB0E1uwz#*9Re?Ju0bm)@tfvrdZcx)hmI|w1$(Wd{57>)%xgosm%b&-c? z)vx~MA3guwHz!ClOVTwj^CF`utamwu9pqu81+OZRmQg1yqfS~zowSVlOxgoas*N)V zjjqh3&z@hOJK7^HT%X&YzgC~LjP^*&c#v=6aA;FBzF{sCWJ0A36)Cv<=1^ys1^aWE zqabEY`D}7u?A~aR38gNP7G%GWmVprLK$xApylkW%NZTxlkc^p;qsRv-E?N>DAjJi* z_qsDEyJ~S5IT0Qkk77Ap%06YoN_<)A5NWeJIPrj zOup~!BL#9U^8h)Qc$k|iKA;LGk8n)E<&lm`h+z+NFzWAns;PpU;R^7SQKZBQjQI{> zCIWEQa34L9dwv!M3Tg^P(s(HbW9I68fMfd~R2(`2HK`CBv3LA#1gH0VA2+GXWFfC2 zr>mIukltY0!@W$}BPaz$=@|f#-cTxo&0!g6LjkP(=qOJI*r7mqdL|mCxIvQ{ zK|s1{Cgg25HTw0sqpg^^S!R4zc)J5;&R6)3Kr!#icJ$GZ`#p^|ejFq3ZCc;6*|Zk? zY~bhaBn3Zr2M_qU+j%%YcZ3xD++iN@bBB01Kes{(e(oR-__+f-pt$>Z7*caxy^tEq zII(ZYeudNoh8I#}$5%yTJmk|7!9GxJf;PQBsu7iA(;YSvLaS;;z81P4U| zf@ia$zozTyWIF+3nKWZ|pK;^>!$>WFVW8ndxlvR1K2dlGtfGcwG%Eu@ z84EY@>Jd1((^bGdq;`w}yx&tWfVw1>ynUj_mO7x|7M6m~`?ROv^VW2$Ed`G#CW)nP zR|Eu7C`4|FF4@oA=59(8%RPKc^xF)a!M-yZ4*=g4jR*LfX*?L(UoD`Y)A%+h0`Q&2 z<79x0QZnF~o-4_0VDf&xvRGzRLW>2ita!<`!`ZM{z<({4FGE(2Pz!DZcF39WRWg%J ziC>=aB{Gvu*w43e_0Wxfr%=jvq@l`V8t7OJk z$&9ZOd^oDBWM<n>C+4_AmD65xw9KgKvAK40?daan;_E&A*>-O7RD@? zxDKoayVx_3Wtxy$o_V<6zx%YkF{ul;qsxl4l zz4zR6&&P9~uk)PeJjYnRS2s{EhUmiH8(oYI>m`bJYL@OW<(b|lz5!aX0dIOov>Uo0 z%WSd9StP+ts>kTU?u;%r1-f|724T}he>#5IG5x?+L?hBQ^klk*-bvTE1y}T;d|oG6 zmG62M`Y8pvxW?bjiQicobaBw%U7*k~mIhti;O};cl1M|fIzM7z(QeN^Kl*&Qoj*VN z9DDKT;Y%Zi4ODnQy*@F3S0yokdIQ8T=81ucdk`F30x|r?fJSbNRK&1pO;rbX31P}y zzV_>Uopf9_1~j4bV6G)ipgV~sSX`CZVEIsDqw5KRow(A4h{+ zHW1d*h3pb>86pqddk2JwCy31uJI8ic1i|UneS$!JE(JxmB8biTra%w?6F~vr86pUK zZv?Sf1cA$ipzbpc5d>p&o);92eAABTYzTs~W{U}jYdJAQRF4sa-5Ehl1cLB!M@Tna zVW;vUD#ZQm4y3-q-))nW=G|Pc&SocIb1T6-7_eJ-4ZyCpieDO@(wUdVv;o9Qrrotg zT$`B_>O`&}I%J$ViU3hD?adxfai|G++6{{Nmj{?Ou|d#Xm^rfDkl=vY8nh)a?W^-G zHW_3t_z=60w)27o|7~1?`h6zGEB5%oldtCv9*)7no2{k-58p!qJp5`dX11CN_<98i z;Oj0f;OkB<<=k@0&n>5fvgeTw%AU=|%vQ{dg}_r90d4(aSVp)E;A+m}0IurTbuzWq z(sVpWpQ>h>>v8lc4Z9h!#zgQZf66DubMALNa4vsGFO&T0s z{GPjA(unL1!nwuYAq^x0=oVc5301)@T>eYp7B2tA*n0iasAmJYJjhvcc?@$Um)BmY zipyiy3S9n_U{Ms6c)Qk5oy?L$wcxPA5m$4Q<**|54OX+^OG{l=qy0KVHn8F7gzFhE zca`_9{wgo6h^bu;GGTT7MV5JAj^FA~SN-Dcz?#Zl4@oS_erAo~jA^DQ76GKw(@wOR zi=u57ZBVo`cuu^RYvU@j1LRL!*?*0Fz}Xk;+Rzp?f3|?6Vi*<++S|iei=`oEn-y4# z;o8ny*J95FOG4vqAQ54NWMu%inEhLHWf%|?gC)~_Bi7m9$iovJNchjL!N&Dild*|D zIHNlz`Z~r*Q<0Z-R?84J#w$H~{~-1DR5mTt5Q;bii5as>E8jqzK*B5*VSK4}^fLEL1BYvTnl}2C)iUEYg;wrWQOYS+3tfe59Io- znAtbiCuF*!URoxPO}5qGXSi#oyV+;5%`n>%GnO5>zf zZ!}w(Z?TaC^O-zWuWRg!quaV|>6WE5{j{+4;omXMRyaM5u*}NiEWth=xL*k@X6P-; z0o)gayWw#bXKx-SwK4ksu)U3Bg}gW3V^?Snu`rUiKc6@!GczsQdBAz85d&0Prf4Dfz z2!~{m?P`jG`jiuk5(&F4Pnmmmq-oi%S($Cmifn3$G|B0ax**zTG9@_K1}^|wV|VJ* zMIJ|3wx`NnoVuuOe!dk}-xWsys`ZiX0SUgd^gYUdjdkQrMzTiL&Bhx}r3rDSV&D8c|F{=Q_v$0KSi#GhjF}GWdAy5fG&RcK4Am zNy08?&<1B20$6446Opt_JKieP$Qi&*OnYMfC zo-8tBf)<;KTv`LB6gJC?36gLogv=L2PBJElRss`Lydt_XFebH=JD_NAK!yi;M|3m> z2m!7Ef#U3))zs%@4KpYsW5Lm?;4oktfS0@0z{~z7)R);>&5egqYXe%UAp65`O|uPX z=@n(60j4LlUG_u>@bD{#01v+c2(Wt%ux`L;=|G5;qxHo>es_YYf`@JltAvdOerJ2m zZ|FsV-?3Kf`Ca7s9m;v%B`NSb7`Ql{@jHBs*g`KiB<9DKh9W|m%?#wggk&YNJ&H9V zFFb23U3e=3K7o1RL|F02T38W+vb=QeFMdFOyl^Sv9k@by2S#MTUZBSDDUWDgJH*~M zU{tSJV^lYQ2Cv!pmMZYZO8nb^mZByO!SvvO1V1Uv7!;2Z6e|QM>NO2VzN2 zDLO)GkS1w%sRz*1ITap#uo|WAEM|VS^gh;WjXz^KBvFhbNXYeIQ<4ddgb74fbhW2 zwuE)X$~K^-Hl%-0Ut5K08-N$L ztpV+S^`pEDxZqB-(0Q^%@FjGLJ?m*{M@jZp)LOlEqgu%jNi|EW(+G zQl&&nr(8Dq-d8H^P#l|ZZ+r5W4Ps& zY3VI1TDtLCrPSdU(JH?n>(AIrvHlF{vJk_7E^F4Fa(c3s(TB!ND*7H49T~5%=wK%0 zX*+f|E;`YP#Mz#=i?frHr)hR_@@}?gcD_ksACS1KX93-I67G$6dCyEq-QFfZYL*?v z2gM#P%MWHG4C(T7WGO`UA|40;Ol*K759q=@Ud=>n9C^M5BfaF=mi}IAF7RUxHWK01tQk8RDv#hc?!Y^cMiMzmBIzm)K+iOSA zI&W8nuPD>N3Z`GK;85niQ8hKAstRbaE@l}DbA)E)1;8^Xw$f6x6#Tt%d*wutl8XMNQS4@T8>1?1^I;MTOnkK>wqErZ7Y=mbu!a@lrEj|%EArF2oGR|&MmJv{iOAAyY%Lv%Ur6o*Mt8~le9-Umz zAa2cPh?|a66Xz^-kKmn4=viXfvY@aDQzKr?4-^weKpi%wJy2{Wc@pfy5k@fur&yF= zd)W~JzUQ;VpypsCiW(=C*!4M|b@I(TjPgZCUySj^{TMp=;sFXg{n6j0HVmHmY20zH z+>r-BUqag|r2FZ1a*_fLV(vW6KY+9MLqT5X544-b;LT-QFnCYm3}>^=|E} zdIR=Csd~3c`t>!vgNkIap_<<3UfE&#T%Y7Heae3LxmClo@mh&&&+yd~*}8t@>G%_` zEa0B#11{kA6RQCCBlqD;uWTQ_bpr3Luvwq@0QufnseLw9s^ixe@g~^9x@KC2ozjM+} znl0f@bGjAWpX|f^Oh$k)DdEm-33rWDfIGJ(+%==Ez$dtCZ(@}o=%X^^z;k{DJgdfE zv>N|Ve!b70M%9q8`=0{}T9p41@ihH0#5=ME0M;&KjbSO_u;Ih`t+*YZ{Hlo$dj!Bm`jwp>qRVy#+`d7ZZ2p^Hq_ z&tX-yqxe3%xy0Hc($Q(gySQ-7^-i`NKUu~Tog=OJ7!g1fo}kFiP0u5N3mb7b?SO!v zWx6sFs^Mxy73J=%0&KHhG=s&)6Ln-}pymG7r2&UN|%*Xf>kd)V0_K1Z_!sk7t-3)e757)7sH63-^M zda9t0@Y{3nN~t~+5s$z`yY|Vm?Im6jtCdJv9F`z=K*pqKcHiNZR+WYT^Rwd2ba+Q72$qm^>s$(N#5>ktAXF2bAKW_!G4kHM+dkkD?g zF(gzBoo5Dw0(-AW;V|zCH96NX=ScEfU~6+OR@jG*Tec8;QTAskHj;pW&qPZ_n<3{A zckPbEbfo2d90Je5jBN+4@8$vqUP0id3IosMVSZ^C2~~z6A#r{Nk&wK3ds}jikdV8x zlBhD%)6!=={_;!>l;C7o_>xFii@9b9U- z3YT#09KfX(xFjU%)l1`&8P^7I=hQy(Gz@v4lNP&{Wc&fElpXLnUwR~OCDNM0oiM$(SpS6?aJs;|0 z0V6Fefb(eh!3iM)I3X+ma3{tBPK6g1aOraCV*v@m0xnnyg$!2&WFX+=Zs=GMf7Ar= zo)Zqg+kOtLa0Q>3&;8gE@mOQ4b!;ws$i`_5XiyqR89bFOQHN^rYC##9{OWksz%Q~*@Z8i<0+E_1%AvNH(EE@oBMBR|x-(};>84QIEAo$|8B z6txI1(pVB_HqZ26BWgog4e6$VA?>0iUz%M~3CT*@=>Q$Nj#}~hxE1ycJid8R;NWxj zS#EbYkYUCv&Jsx3%s{~ssl1j2CV2@}e7ax7C%Jvy$|_#9W)*#O?53*hv#&|B!>Nx! zu4prU&mfF^a!qM;CYu5|=P>7j!Zpi<^8hg=I!*_iDmn_D4!o)z?Q7npQD|CZcdD2%Dq9gPPsR#)+zTU)jH+QR;^R+&8l_E zougW(+_|ci&5BmgW!(8a=h~3yBQspe-MGEIggd{>_G0dQdu%~0bqalY~YRH~3Nb$bf zQo+vKgmPaWbCjDMeAJicjwnZ%i6Pm|9J!WHvh&W%stKJtQ|QRcZ`8DFsLNh^$C(Pn zEZ2McN#7&sG?Jc6LoUJ;5E7bmXLF`+_u3UwJrh!Wns5g0nN-9&VZ^%GJsmGD9CklO z`aOhLiy>D&3)!C}rWzR~yf?ciL&o7O>=slOQRQY1YVu_%%Y+WBpw-2ui$~Elxx;uq z;l3L$VN0KI-%FP;ZtKL;C78(sl`lc;6Ydz-ES+bu#7@jSD~*bLA^Hi7-~=PMU~jL( zJKH4HSP&=ACB0ISZFqpeHiyGAu1NVOQpdbcvtMC@*)q{&zXZO@;xz{|d`AWF_?(4$0hx2|v~)?`e1S=CepW zH)Y4oX6^3*fBxc+Kg}vr9&L)F%s2Ecp`je6%6P-UQxXILMXmD zAz(4^sK#)L*R`4v%nDHB2{ zQT(Z7^25(vt%&?auNn1qLcyA%%fR~6RMLbaq@!9qqkiA>D9u&R#57ok1Ic3*)&k#T zP1&a19565r5j7KkuSdSQMZd;LfQWm0DV%8w;ASVYn*+*f4fJz?=R19jp6x{{xQ2$b zS)t4mPW`G zZP2xX+;_hC4|v^I-wuCq%8uxDQd!F{iSz>P>&x6R0kY6Yq0%zTvd|AD-Isn`<^;7X z(BNBrXMN`4^gvzpi$3N%5)eUN=K@Wmec}Ci)QdX58Li0u z-#`2mc4jJ$4In`IZ62joR1BPE3s^|CidK;$dkH+Vp*3sNdu_koJyeOB!!Hw1cl?eQ zN|F=QC?7!l+|DDA@or_56mb6_D(2L-wW%0xaxNMlyq{8siCFkT2tDnzp>QS_(z(h~ zpI51KYZmIUXdCahsmSHu`(fEgD0N3PUlj?qRii@VbAXes29DrWa;Y_dllOr`%pAwU zn8H-J;%VC3oL#DW17u_9&_V=L4ij zI#gM|BoA*o+xVoura^Q<+|Kr_!ohDVwv0w3lTAlF8nYZnegI?6x*L_*;e}>zi}(Xs z&SZ0@g83;>`W|w|~KZ?tcG{<4w1G$A=Nq_zTSRwkW>T zW~AvelBSO%;BsPFxyRGRd`tH?V~MGeN>Bhf8=;0D@G(7c8q9zY38D^t&W{#=_oF4H zCuVF;VGwMtaEg}}u<#HkRKhFj-Yf*6SVF)K6nowZBtW477_>E#K)~aCT|6`8CNrYjkKlgi$KcuD_!Pl4!2)C ziulfuX*aP>Iylpd_SOo;)ZT>Cd$?amF}t3m9=oizF4NW+0UAXN z=Sk_YHsx8R$J&Ibp)iPvaiX8D-Nsc8VyhbtWoJF`&`}w5?PcTC_&p z#|G+tLUkXrx}Tu#VU15UfHz*_dt5m^PXC>hv&YxNX;W-It=ykpiPNT_RXQI*2hsWN z>z8ZN;l8!Y^@DVeGT&pxuB-R01ky^qF91n!D~D|jAT3i>&r+8> zlk{!{>5Z5XvxPzftJT7qY${%`ltN5_LZr;~8W$Zue)w>&r_1pkYtp?f{-0u=6>M3ETCG>uJrq|0;CCpnw zNT^IG<3TAlAyr>BuS zV;>Xf>vk9#)3#lK-mfomePO8fMTMR)B!GBXp%owaVsjbAwf`sUazIa-cb zA1+nXOa<=6bbyGX=zr=5)_Y$p{WGmN-nZW+`~7=nuXLrW&@z7&*a1sJkyF9^GYQ?sHk zP3oe=ImQmNeD;eqMdwJ|vYnlj_=57}g-#S@Du+SF$=r%R;1NL&0VSedqIqIstCjwq zB={2sAGB!&W1_X#38?@Eo7iY-oYKo|_ME!g_VpqPnj12gdgr?FR##zU=-IUdFG zfu;0NeHYj`uSzz=5{N@jywiD(E1^lgDMv`4_M&96Tu z;Y6zJH-bY6CjStuOOSUltHAS=oao^4$j-xn1Bhd2M?C3Euo8ca4V=Yo>OPRV=qf{_ zw0s&RjtzcW7UGH!bI7*pRncIly+)WbY_)7`$W6=|9m^A6ZUE)W?)`E-LF^+7-Y$Fv zfjPc4SIDN=opGhA15+S5!^aqhH^I9{Q!*ePAMzY2aXDP=AT5XwiBht!Cf;>SeQrQ# zXuYB+R+~{GxUEqNPGM>vug=k__vyvGRx4EG42F9kT`?Ln%R794DMG9=SraAWuKhBfP-qhvt4}(;3}~}1U4b(uYZD&OPtZ9R zslk_4(pc#~Z0V?X|9x82yT1Y-7I-yc^+n)@1o&P|$qcUUp9R*J(wgRIO#B;oUoTc) zxa1rn#~p=h2mmQ8^p|0wjW8Iy7>pHTAQG-w?7T1x5sHO|#;Ew9ng;^qG2YWQ92$vQ z_9kO+WK=}A_y!lTZd(vME|1~Rk_3^`q8Z7l{dSjSAC2S2VaFC0R`Hg>L&;GP(jy-? zg1I~gB8I_?Xu=V4(hK-q@(zS%_udrkOZt|OT3<*w+5-Tz6g?{+K(Kc;2|YmJ4?8lE zlNg@XDN_iV>3cx6iPek$mnN+$k`fq3#H5CV0#fG7NUAM|+;L*Dajc-3Y9pdW|Bv}2 z1qFPR0Z3@oMG^n-))A1w3K}cF%=W<|F)E|sq8Jhr7yCp{GcFdUYZPl*-AVV!4XL(2 z>q(f+mw$YGM=W^hdO4#Hp~e{HFBwgUphV$``_oT+vH@s?In+qBIqCs}h?D`MYI~mQ zrw95z%D6~)Dk4GnP#?ASlTSso2(_VJyc-0gs>GV+d41Il+L>GiZmL66)U^H}Noii5 z^au^sw7wlu-xx-EB}&+GaDVo@y+|G7=?)7AsGbO|qddC5Xd7&Q8YbrNuHgklfp($x zdg?IvAG^QDsuQC`Dv&$QF@?zzZLg_Ech?TMXcw#P4B)*KT+@^COM&1c(;1@d!PN5= zobxx9RlV|E|r}>^PBL6C*uSJ%9VlDVt!GsF<<@% zY8P`yRl9;aKyPYW1d9F4K5*S5j9$-)FQt4Y(a2YlsVRy^ifbTh2FwIDO)E7tBy<6- zBl1`Gm(tPrRAI$%)z(g`7wGDcIwd-5#+jQs=&)7+^@xP&c&HAGJg=-$7r`4co>Up! z#pV0N)eU9TJ97X_1q^`W$0j?h-NU!%(N~Ie;cS-bj*m0E{9anS&8sn>%g!kLyAEwm zb%ccHE_#He%3n=;z)+lIevzZBK`zuJP}BXei5nm7Ul7*t?-!R3-?;#O>fdu0-t|>A zHv4ar$!4!jqUHpVBUPf|&SIgD%c~dJc-CO~r8izuX*v(t%k~uaV9J=5RC@Ek<)hFf z{xrRjGGiVW^&L@Ev?cGAh|7fW7mZtB>aly3XrkV0Q(zlqf@j{gJw*m7Ogf~-*E;Ww zm5Qc&6_ZyaQ09Nllh-j#fBEBUor5{PAyTiRPni6lPt~qH7 zZ7ANf7k4I>Tymw#72|-=>~SyxZbW=cmpgn_->&{3&tWlVyxqFd6nfQr#s)Z^&i%Xp zS<@VW?#KZN%qQYAb?2S^Jv>CIwfsJR2OV#!Dx^>NP`#p>wtEc*KVf>_4DF14J8@S? ztew(21V_z05EQP%VB2I&c&!y+P$I)wg6R^bP8Yy@g)4x( z#Z=Ou9J$v4@?Dvw4*Jfj4eBBAP>-Pmn|p>XHA9yq zc{5BUp*tkW2NKkkG6fL<+E*q7IGLTNV z^Y^09xKE5O3hNoyC84`(Nx=^F35DE@FuZ>8D0?WWUD)aea?CU)X$T`D`XJ@KU&_Z% z*FyhYJW8urD+TeB?#E2SQxT&EW%XAPEvX#VL%TXq=Fjg#Rm166_u`53+V$$iwi*^TdWDJ%HI%%36g>1b)JSDOm|x>M~&x7(d+ z=sBsUQ&XL(cxtL^8Zua0ajpMIRB^y%^e0X4#MUB3jPfDe3h zO6CYUnzvl*${EJL*EkN*sF}rdvDLd6)q%2kA2I$?q_;i_aCStGZ_2a% znrd!c7=I_sYU@Jn9z3?%UU7hBPVXKiS|_vQi@8E+hv@fWZ_*{bF_GY^iom^o4b6RS zE|lJUClYS!LaNg1Rq4K9bQ|{HTC-4^(Ft0@AK(6k=qJIs#saIjA1bCnEqI-9LSz7* zbeX+d${k8^A8zrAbo9cpnGC$)IHT^br9HSWvE?E4eWUCnHcSK0Hi2T`IA;g_1gx~h z|KmN4Xln#c+M?^)vA!hZNZBm#w87yyz4{28@35C8HU=hDP>MUCS#)VYR@%+z!z_OR zKmC~^p*Ootg7xoZ1M8SNf zxnHZicioDvs>`Jm8)mfRSLrE&LqbS8o9ZrWhq-x<;9H?guu%G(Ede4;B=9talZf{) z5^dxHGcM75xHYG=(QzPB4H?c!B8(Asn71@~K!Fie5{g$YpT)e>{q@vF*=zKTeEMye zC7342As?o*k*UVXdEKL_yaRTPDy{%|mn0h7yaJDrwVK1h z&0_lVR@x7IcfrK0eqO77fMK(Ox==@xMK#pR_E44Fv9LSfY-^R}O`8?HeKgL&7ZO<% zt}1ML09O@=i5*ZaoIS|jD0p305Z@9J+tw^P^V)QdJfJN~z{i<#ibpm_I*b(>hWeuM zsgFb%Y$!ry=t7F%yp)zsNu=IMv-Gag&s zK8jri6u?;FhakcMVN7tukISbrEvXsYltRfw(*}srk=j0A__~5GqlWBe428M$E42a7 z%(#d;!^G-vPa+CS6T0k3_Vt)x_@(3l0}^B!3U`r8jRr$a<+dc?t=)Z0k`gmE(P(Dq zEP6}Frb2OiJ?|E70d(Oeh7%(%EV!a5NF3m6Y((Vf@*q9MQWAagCCQ+td3CCjXH{XZ zM%hpqjGO^xESW$@_QtHnlXUa|b0noiMT&fvG9r~r*gVdt(qZN^?wb#N;jbd>JIo_! zxWhTOuJz`67^Szf!l920;$|Cq{ZySiCtYkhd_X1#g%)#?gxZRFeRo;ZnbPo*M9Hm&Ji4N632 z^%b@VH)4(PLT9;17CN_o=+PiKBbyO@B7Ubvw8wc2S!I-+L*@r*A1&9mbVnge2+4xE6UwV; zq=>T?m@DkjG2O`$c5fZ5ZC_h5=%=sEUS-GVnXTKkmdt=fH6!6Jc3Z2AoK~pUlm5c= zNlznW?pB{oR(I3Y-E3JDbCcRWH@&}L=n|K!-1=h+cQ9Y=-VW1dvV>l*vCZ4syJVNH zzi1VML}-B}ik=|F6fI$jW@B4}CGnl4+;Sq0rxDO#9FK#90m3OS#L~Rg>`%6&6Nls5 zQjO~4NPBJTffq9MXDH*ZNC!lVa?BFlN2E>1A1mUo^tOu2Fy=vr>bhU%9ck{qmir}+ zn2;9LogsVoV%)|kX=^znfBrZw$Tl}$UgrK^3YT3D8g-e!;eao*+0XCuY@5$L+x}MD z{gZ@QzOP7U@X&Y1*)28$DB^d|IQ1u#q0dXvDP-dR%V(JI(bx>y z3gR2nZM+e9XB_FD@@C*y-K2&Im&S0XCOw&X>Y~_v^ zxg9caO@9pc-uPpbi;he*C%UM?#2f5r9$!ZWf=Wp(w^X2`W}@H%0cIJ>NqdFjqq2W_ zWVdt`r%e~qeZsfLAK+WhVqebCmQYkDF9T~b+Ry;jtt*QrEIm+QNl|oEqc6>nec}6h zn??9-kRIe`{+f4Yp!D|TB>nv5Vwibjk=gxWce{8d>re=FJ}WlkEBwJ;gVLJ7VuYRw z2ggBs&?1F=8tfYD++x$s2IfOhk`&MaluU0So0bHiH0S~YPl?W&NBqn__unJ$$hR4^ zrBhJ15KIbVgt`w!=N-sH<}@nunc6rDu9r`GxQuW=kOkKLP`?zsd4DlJqxFf7?QdID z6Q}5pT0n!X4RFD!{?x)q2r?3B3Tg^P*LG)ophyouwU6bSnDG~=5wWKP$p8caC*DS6 zC0(nrkRsbUmY@NqHR27JzjnCSs_0!aKIepZZNFMsB=RZINw;cB97Gmra1&O#Y@$+0 zJS~BfT25$!+$#GdVdmQV8A-5>zp=LiQ+c%KEwS^u>=qi>6n}f658OZw95ERNyX4b_ zwt@vqlA@Y{4AStKywP!qSSUceZcNMV`8dT!PA6lUHi6I)Wt+E_YN?t598@ax!?6UF z5gS|3a;S@x0{1<6C>em%M&KlZ5Ee>O3;;tw<9K!%?3tJ69nPInpRh8}_#ki60IsVH zpO}Twmx)EkCQgqCoZ*>I2<$R+bVOsy({OFZ-SQ|wYLg*Juomcv|4Wr;5<)ojAlU8N z`w%DeG<6xH?cn%;H}KNAdHK-g)79f6a7~W{8tAsMQ$bCC=6BTVr+E!VR2Us zfzp^buUe$kI6&V^Qd$9SuP`yU>*B5iG|aRJtVB-)b#&v4LnMWRpC>6Xtc%4wuGoiy zW4fHFO@KIlA%R36-?T_W^AR%{VtV;)uRBH2+&ns%2AScah58io7NVD0S`=NEw-8@c z|2lbi;4gs!Mh62#& zi76y%3a%eu@LgD;QI)1zQF=~0*zX-)A)l7eN&-?q?EZxYejQyGB?UcRD{rIO=j%y> znIl7G>t^K)wp-3%nP6(Fj^~rbz@tsBOQTET>@jR;m zKNy$mN3u}6J5LubxaTe={a8(_|81xUADjjkFtyMuldd*o}mKHLap5f;!!>`%e!{-NloqY-&^K;9w>_GR#Cg&H~gjuH~v&Gol7jxocMe}Zk`B+44j{!2IaNfMzn^AC2 zlQ&vjvpx1;a*re1T};#ov`K$CQpgIIU6#V{>7{C>5usR5qC(@P#~2KtRMaS(336V$ z2Ut=nTe85=Jdq0=<+e0xTv0du&{0AAv7s@$>tzfY1LmQ%-1>#JhcBa5@GM&tF7qz4 zR!h}q-+fn}-1Sw=DcPsvV4k|#!Y=_P7MW`Wra!D(`r-5;NmpXO;(gfws|LP8wwXa7d{txa$2S;M#gO1ix1~{e=NOJw~wdqFdwDC&#?RPYUGf6#y%?xsY-QeC08M`6-hMIDKwLLoKu-4Du# zS@2iWf9VgwgBQRqDkH3e!E0fMw4Sr}`&-sNITz92m{;kcgN3;b8w_v@G0zMyuxXHJ zkgm#&6BglH09LCZ%w)8vYJgF>x$NHpfYl5*+$TLSSVoES>;`6#bo3Jj_L`iU8%tt4 zGF&C|0z$X&s4p|PnF2&aAt#_#L(ZJLrC2n%DuE%Z>YrPR;+#)B&q1oWFkjF*DdzcALAis%h>_PU-2U2vejfxx^cSqg^NR6e zRH`xV0UsN}6yDJvM3CmP9+hb<{V<}Au~elz)LVHGPU7`#Rci4PCCO72&{Jn{y_nW= zojHN~p(d#XhAXwEY)Fjmpb=`)V$1v1#8P5bPu#e9Bp$3Gy#$ntMLBxG?}FkQKJUUs8$??ofHOYQVh@GuP71 zcaDae@wDMY(F* zC9p@9TWFuQA!!*HKbD%BFQu)9wbI4_jDkHcsX56Ks}_{*^P{8CILk~Y=@bj{nZX^9 z(JN;>9o2v@gjc#yhCYV!!X?KUo+pZiBQ)GLX8-j}y$d~5|7<+}$jZbyp3ZYGqLX>K z9Eo&bBzz}Dm~@l%KpDWRF;K@I4;U&4(|{Rq7t(B-hA}z!U22dB)aY8WI|gRsY#F(4 zaO2TbPu~k@mI5`C^^o6_lJoEcqaL{oFyw5#S;y6%LPAb<*l zdV~THCQtgjqUL1Lu@9l@B=q9Zwy4YD!3LBjpv0{MO;Zb2LX+9Gb&O$O zt7Ftscdy0(!iX%o*8>lLsEDbk@Uenu!M5GAM+x1??ip6P)KwNuTkjO5Q*qUOcI*?{=1cEhhqchYh83RpmNKhPj zv4-M6qf^nOclgzwB@_sU+Qs5uGM~(sN(tyJ#niZ|(uAU3^;5;xq67s41T+N$1x5-6 zpa2!TmQ)x_(Bl%LYZM3xaw!a+T+k*gRB2HcB!L_Ie$2O+y*{o5tkWXyVOx?cq$oJE zb=mja4!T2*3%%VmK)|UYUG!2^#NgRZpc=k}EfYyj}yH~16UgR3WLqkT2 zC8v;Wvy`GJdY z_7R#usRyn4NJ!&BB48>MG7OEa1b(xElu@jH?7>3spQj>^rzA*e;$RQM#(=0yg81~1 zp9f(MAVa0~+kzpVO`EM4rXnk-6$5ViTy#dcsD^aZcvQNYX7hMbIWOX4sev`g`(ogM$_Uri< ztX670maYAU|20W=@t2Z&l&nunNj7Iob4$lQJ+ z;8_Zuu_n9*o_Vs6q^hw?WVNIM)BvKCmqIY`H;5>ygucpY^*vozQW=}kpcBc0jsiLea0I*er~ zzbml}rWmk{JE(I2%b4UEmJOSnZ02edk1iBLdtzjOxKe^JYJ0AsffNmAn#&kA`4Jug z)9A4-0=9`qGcnoZSXFA$BT&mrJ8bOw@JQS;4>;>E?pds*qTAVub%H=F6#G~Mg4M-j z4q6$q$&9%e7q#~6CnxB*v_?vycpuy^2SgH^SF_whmuCv zUf5o8|01skOA!kJ@-Pwc`9B8BE8yNbVvK%G&5X&Jfk$}bHT^V<{iUO31v0=Y$!^9= zwM|w4z@QgK4+3*jzfdOx5Od3jIU)Mhx)=d zS&c)01pusxL*=+vZHGN55LHDwj9IBnV+JcHec09me@plLiOB6F)Z|R7qC$D@l?fSv50f z4hThp29m7OvkSQrqhHg#t_sgFygE2N9WE+BNM}*MJut{dFST7wk>%l**u=zA0&r|l zP|zbY2w@bWO)}nL&tmsE=~?zzM&uTW5@Yo+dnrtCz#)!L8dDJEi35XRG*pb|^P(KG zjA*5z)SgUA6}44dUekNe-r(|l#574G8j27F_ea;bJbAFc!{xQsU?v#=WrlPkz636> z-JMwO#pU_j^26>V1^jb>Y*>p}o68#&^|?F%2@DP6JAe}BDO)B1i&hJTQdF2|(Mm$9 zipw+Z(pluG#n_t8LXT+9N-m!aaCs)=fy?{h7gpIF6q*{BN9z!mrygV$McW9A%TqY& zu|dfw)Lw|o+Y3Kj3@aq8VO^W8i-!+RxH0}2Jo-?mhWf;89gA|w)~(C|w!TK0T7V0{ zmAYa0<=J{4k&Gh)`c-Uw2$6=z2Hk+qgjra~2H5%_W|eGRm^HxG!K{+4zjVwpwqEK9 z1F$Rm>DhW8X=vmy(imGmX{2Eab8<*SJ!>Kj>4B{eBaPTWz<%z6L5cZU$=1V|X*Yl- zUPNkLgRQ3y?rCh@B7lh7i$Seq>tcc8;Jo89Le<9Bskdb7aO;Y#$FOz37PykF1663u zTnYR{DXrLgA|fc6x}*)O+|6o2UaHpCjL6q-^Ck8sz(YJ&C$xdMI$#o4H_Q;Bn=+#K z5@dv{>jGD2E(ss3=1YmVdQ-iVT?FPKaxFWnu8X%wc?l3eu@^idKV7mf1Qx*~%l0SQ z@lSW2*(9Ef`%d)OLi4tT=Mj8o+<9B!9el?3Ff7TVMRXzF0tdCs?(gKPfQ`I+XkIHC z77u`x4x-3rD3X1{9wC<~U(KM}tU+T51X7Fs1>)kb8?zBRIVUJfrI<6yYZ%j(a-WS-~ZkN zAJ}{FKos5|@Ts?4cjNv8(c;Zy%Wu)|dwDF2s1n|N^Y#BWfA`*--?LQa4G(3`@c91y z*WGeMnep8p*t_?=2Xnr?@x2G40|&2rKNli6)$n^J}?5yKa^eO&7&i~8)!d>)tAO5@O_m3TqPQURFqx@5U674>G zJaV7GPh4gHUby~ExPD~Br+;Q7dmZojx3XrX>EEh!_xIu3`$v5lkBq*gCjH51cB!v& zYm`M>w{BG}b^Sz9TR)k8^lxj_>F_@7G-*YfpmtHa!OJXa=o@|MJ~x*2#w@et&qkFx z8bwLeR&qPycPw&$JC?;Gk^9H7?3B&QZ9nZ1-A4TK>Qm!c+@-wn?38g5Yx;@WUHwM< z^6JmXF+ow|+3A~-A4TK->KZ4dg`f>d!(C^Glt?s;!<3m5IY_>#zdeN)z0 VzVKrYZDoxA5BGT#-FIL5{{e)Gw+8?K literal 0 HcmV?d00001 diff --git a/packages/forge2d/lib/src/backend/wasm/wasm_runtime.dart b/packages/forge2d/lib/src/backend/wasm/wasm_runtime.dart new file mode 100644 index 00000000..0f846020 --- /dev/null +++ b/packages/forge2d/lib/src/backend/wasm/wasm_runtime.dart @@ -0,0 +1,570 @@ +/// The WebAssembly runtime plumbing of the web backend: module loading, +/// WASI stubs, host callback imports, and memory access. +/// +/// Works under both dart2js and dart2wasm; only `dart:js_interop` is used. +library; + +import 'dart:convert'; +import 'dart:js_interop'; +import 'dart:js_interop_unsafe'; + +@JS('fetch') +external JSPromise<_Response> _fetch(JSString url); + +extension type _Response._(JSObject _) implements JSObject { + external bool get ok; + external JSPromise arrayBuffer(); +} + +@JS('WebAssembly.instantiate') +external JSPromise<_InstantiateResult> _instantiate( + JSArrayBuffer bytes, + JSObject imports, +); + +extension type _InstantiateResult._(JSObject _) implements JSObject { + external _Instance get instance; +} + +extension type _Instance._(JSObject _) implements JSObject { + external JSObject get exports; +} + +extension type _Memory._(JSObject _) implements JSObject { + external JSArrayBuffer get buffer; +} + +@JS('Float32Array') +extension type _F32._(JSObject _) implements JSObject { + external _F32(JSArrayBuffer buffer); + external double operator [](int index); + external void operator []=(int index, double value); +} + +@JS('Float64Array') +extension type _F64._(JSObject _) implements JSObject { + external _F64(JSArrayBuffer buffer); + external double operator [](int index); + external void operator []=(int index, double value); +} + +@JS('Int32Array') +extension type _I32._(JSObject _) implements JSObject { + external _I32(JSArrayBuffer buffer); + external int operator [](int index); + external void operator []=(int index, int value); +} + +@JS('Uint8Array') +extension type _U8._(JSObject _) implements JSObject { + external _U8(JSArrayBuffer buffer); + external int operator [](int index); + external void operator []=(int index, int value); + external int get length; +} + +@JS('performance.now') +external double _performanceNow(); + +/// The host callbacks the shim can invoke synchronously during a call into +/// the module. Dispatch targets are swapped in by the backend around each +/// native call that can call back. +class WasmHostCallbacks { + /// See `f2d_host_cast_ray` in f2d_shim.c. + double Function(int, int, double, double, double, double, double)? castRay; + + /// See `f2d_host_overlap`. + bool Function(int, int)? overlap; + + /// See `f2d_host_custom_filter`, keyed by nothing: the world's callback. + bool Function(int, int, int, int)? customFilter; + + /// See `f2d_host_pre_solve`. + bool Function(int, int, int, int, double, double)? preSolve; + + /// The debug draw dispatch target, non-null only during `worldDraw`. + WasmDrawTarget? draw; +} + +/// The debug draw callbacks, mirroring the seam's RawDebugDraw callbacks +/// but with vertex pointers resolved by the runtime. +class WasmDrawTarget { + /// Creates a draw target. + WasmDrawTarget({ + required this.drawPolygon, + required this.drawSolidPolygon, + required this.drawCircle, + required this.drawSolidCircle, + required this.drawSolidCapsule, + required this.drawSegment, + required this.drawTransform, + required this.drawPoint, + required this.drawString, + }); + + final void Function(List vertices, int color) drawPolygon; + final void Function( + double, + double, + double, + double, + List vertices, + double radius, + int color, + ) + drawSolidPolygon; + final void Function(double, double, double, int) drawCircle; + final void Function(double, double, double, double, double, int) + drawSolidCircle; + final void Function(double, double, double, double, double, int) + drawSolidCapsule; + final void Function(double, double, double, double, int) drawSegment; + final void Function(double, double, double, double) drawTransform; + final void Function(double, double, double, int) drawPoint; + final void Function(double, double, String, int) drawString; +} + +/// A loaded Box2D WebAssembly module with typed memory access and a scratch +/// arena for argument and result passing. +class WasmRuntime { + WasmRuntime._(this._exports, this._memory) { + _refreshViews(); + _scratch = _malloc(_scratchSize); + } + + final JSObject _exports; + final _Memory _memory; + + /// The host callback dispatch table. + final WasmHostCallbacks callbacks = WasmHostCallbacks(); + + static const _scratchSize = 4096; + + late _F32 _f32; + late _F64 _f64; + late _I32 _i32; + late _U8 _u8; + int _bufferLength = 0; + + /// The fixed scratch arena, at least [_scratchSize] bytes. + late final int _scratch; + + /// A growable secondary buffer for bulk data (events, vertex lists). + int _bulk = 0; + int _bulkCapacity = 0; + + final Map _functions = {}; + + /// Fetches and instantiates the module, trying each of [candidates] in + /// order until one loads. + static Future load(List candidates) async { + Object? lastError; + for (final candidate in candidates) { + try { + final response = await _fetch(candidate.toString().toJS).toDart; + if (!response.ok) { + lastError = StateError('HTTP error fetching $candidate'); + continue; + } + final bytes = await response.arrayBuffer().toDart; + return await instantiate(bytes); + } on Object catch (error) { + lastError = error; + } + } + throw StateError( + 'Could not load box2d.wasm from any of: ${candidates.join(', ')}. ' + 'Pass the location explicitly: initializeForge2D(wasmUri: ...). ' + 'Last error: $lastError', + ); + } + + /// Instantiates the module from raw [bytes]. + static Future instantiate(JSArrayBuffer bytes) async { + late WasmRuntime runtime; + + final env = JSObject(); + void set(String name, JSFunction function) => + env.setProperty(name.toJS, function); + + set( + 'f2d_host_cast_ray', + ( + JSNumber i1, + JSNumber wg, + JSNumber px, + JSNumber py, + JSNumber nx, + JSNumber ny, + JSNumber fraction, + ) { + final callback = runtime.callbacks.castRay; + return (callback == null + ? 1.0 + : callback( + i1.toDartInt, + wg.toDartInt, + px.toDartDouble, + py.toDartDouble, + nx.toDartDouble, + ny.toDartDouble, + fraction.toDartDouble, + )) + .toJS; + } + .toJS, + ); + set( + 'f2d_host_overlap', + (JSNumber i1, JSNumber wg) { + final callback = runtime.callbacks.overlap; + final result = callback == null || callback(i1.toDartInt, wg.toDartInt); + return (result ? 1 : 0).toJS; + }.toJS, + ); + set( + 'f2d_host_custom_filter', + (JSNumber ai1, JSNumber awg, JSNumber bi1, JSNumber bwg) { + final callback = runtime.callbacks.customFilter; + final result = + callback == null || + callback( + ai1.toDartInt, + awg.toDartInt, + bi1.toDartInt, + bwg.toDartInt, + ); + return (result ? 1 : 0).toJS; + }.toJS, + ); + set( + 'f2d_host_pre_solve', + ( + JSNumber ai1, + JSNumber awg, + JSNumber bi1, + JSNumber bwg, + JSNumber nx, + JSNumber ny, + ) { + final callback = runtime.callbacks.preSolve; + final result = + callback == null || + callback( + ai1.toDartInt, + awg.toDartInt, + bi1.toDartInt, + bwg.toDartInt, + nx.toDartDouble, + ny.toDartDouble, + ); + return (result ? 1 : 0).toJS; + } + .toJS, + ); + set( + 'f2d_host_draw_polygon', + (JSNumber vertices, JSNumber count, JSNumber color) { + runtime.callbacks.draw?.drawPolygon( + runtime.readF32List(vertices.toDartInt, count.toDartInt * 2), + color.toDartInt, + ); + }.toJS, + ); + set( + 'f2d_host_draw_solid_polygon', + ( + JSNumber px, + JSNumber py, + JSNumber qc, + JSNumber qs, + JSNumber vertices, + JSNumber count, + JSNumber radius, + JSNumber color, + ) { + runtime.callbacks.draw?.drawSolidPolygon( + px.toDartDouble, + py.toDartDouble, + qc.toDartDouble, + qs.toDartDouble, + runtime.readF32List(vertices.toDartInt, count.toDartInt * 2), + radius.toDartDouble, + color.toDartInt, + ); + } + .toJS, + ); + set( + 'f2d_host_draw_circle', + (JSNumber x, JSNumber y, JSNumber radius, JSNumber color) { + runtime.callbacks.draw?.drawCircle( + x.toDartDouble, + y.toDartDouble, + radius.toDartDouble, + color.toDartInt, + ); + }.toJS, + ); + set( + 'f2d_host_draw_solid_circle', + ( + JSNumber px, + JSNumber py, + JSNumber qc, + JSNumber qs, + JSNumber radius, + JSNumber color, + ) { + runtime.callbacks.draw?.drawSolidCircle( + px.toDartDouble, + py.toDartDouble, + qc.toDartDouble, + qs.toDartDouble, + radius.toDartDouble, + color.toDartInt, + ); + } + .toJS, + ); + set( + 'f2d_host_draw_solid_capsule', + ( + JSNumber p1x, + JSNumber p1y, + JSNumber p2x, + JSNumber p2y, + JSNumber radius, + JSNumber color, + ) { + runtime.callbacks.draw?.drawSolidCapsule( + p1x.toDartDouble, + p1y.toDartDouble, + p2x.toDartDouble, + p2y.toDartDouble, + radius.toDartDouble, + color.toDartInt, + ); + } + .toJS, + ); + set( + 'f2d_host_draw_segment', + (JSNumber p1x, JSNumber p1y, JSNumber p2x, JSNumber p2y, JSNumber color) { + runtime.callbacks.draw?.drawSegment( + p1x.toDartDouble, + p1y.toDartDouble, + p2x.toDartDouble, + p2y.toDartDouble, + color.toDartInt, + ); + }.toJS, + ); + set( + 'f2d_host_draw_transform', + (JSNumber px, JSNumber py, JSNumber qc, JSNumber qs) { + runtime.callbacks.draw?.drawTransform( + px.toDartDouble, + py.toDartDouble, + qc.toDartDouble, + qs.toDartDouble, + ); + }.toJS, + ); + set( + 'f2d_host_draw_point', + (JSNumber x, JSNumber y, JSNumber size, JSNumber color) { + runtime.callbacks.draw?.drawPoint( + x.toDartDouble, + y.toDartDouble, + size.toDartDouble, + color.toDartInt, + ); + }.toJS, + ); + set( + 'f2d_host_draw_string', + (JSNumber x, JSNumber y, JSNumber text, JSNumber color) { + runtime.callbacks.draw?.drawString( + x.toDartDouble, + y.toDartDouble, + runtime.readCString(text.toDartInt), + color.toDartInt, + ); + }.toJS, + ); + set( + 'emscripten_notify_memory_growth', + (JSNumber index) { + runtime._refreshViews(); + }.toJS, + ); + + final wasi = JSObject(); + // clock_time_get(clock_id, precision (i64), out_ptr) -> errno + wasi.setProperty( + 'clock_time_get'.toJS, + (JSNumber clockId, JSAny? precision, JSNumber outPointer) { + final nanoseconds = _performanceNow() * 1e6; + final low = (nanoseconds % 4294967296).floor(); + final high = (nanoseconds / 4294967296).floor(); + final index = outPointer.toDartInt ~/ 4; + runtime._i32[index] = low; + runtime._i32[index + 1] = high; + return 0.toJS; + }.toJS, + ); + // fd_write(fd, iovs, iovs_len, nwritten_ptr) -> errno; swallow output. + wasi.setProperty( + 'fd_write'.toJS, + (JSNumber fd, JSNumber iovs, JSNumber iovsLength, JSNumber nwritten) { + runtime._i32[nwritten.toDartInt ~/ 4] = 0; + return 0.toJS; + }.toJS, + ); + // proc_exit(code) -> never; assert failures end up here. + // ignore: prefer_function_declarations_over_variables + final JSNumber Function(JSNumber) procExit = (code) { + throw StateError('box2d.wasm exited with code ${code.toDartInt}'); + }; + wasi.setProperty('proc_exit'.toJS, procExit.toJS); + + final imports = JSObject() + ..setProperty('env'.toJS, env) + ..setProperty('wasi_snapshot_preview1'.toJS, wasi); + + final result = await _instantiate(bytes, imports).toDart; + final exports = result.instance.exports; + final memory = exports.getProperty('memory'.toJS)! as _Memory; + return runtime = WasmRuntime._(exports, memory); + } + + void _refreshViews() { + final buffer = _memory.buffer; + _f32 = _F32(buffer); + _f64 = _F64(buffer); + _i32 = _I32(buffer); + _u8 = _U8(buffer); + _bufferLength = _u8.length; + } + + void _checkViews() { + // Growth notifications cover emscripten-driven growth; this cheap check + // covers anything else. + if (_memory.buffer.byteLength != _bufferLength) { + _refreshViews(); + } + } + + int _malloc(int bytes) => + ((_functions['malloc'] ??= + _exports.getProperty('malloc'.toJS)! as JSFunction) + .callAsFunction(null, bytes.toJS)! + as JSNumber) + .toDartDouble + .toInt(); + + /// Calls an exported function with number arguments (bools as 0/1) and + /// returns its number result, or 0 for void functions. + num call(String name, List arguments) { + final function = _functions[name] ??= + _exports.getProperty(name.toJS)! as JSFunction; + final jsArguments = [for (final a in arguments) a.toJS]; + final result = function.callAsFunctionVarArgs(null, jsArguments); + _checkViews(); + return switch (result) { + null => 0, + final JSNumber number => number.toDartDouble, + _ => 0, + }; + } + + /// The fixed scratch arena pointer (4 KB). + int get scratch => _scratch; + + /// Returns a pointer to at least [bytes] of bulk storage. + int bulk(int bytes) { + if (bytes > _bulkCapacity) { + if (_bulk != 0) { + (_functions['free'] ??= + _exports.getProperty('free'.toJS)! as JSFunction) + .callAsFunction(null, _bulk.toJS); + } + _bulkCapacity = bytes * 2; + _bulk = _malloc(_bulkCapacity); + } + return _bulk; + } + + // Typed reads and writes. Pointers are byte addresses. + + double readF32(int pointer) => _f32[pointer ~/ 4]; + double readF64(int pointer) => _f64[pointer ~/ 8]; + int readI32(int pointer) => _i32[pointer ~/ 4]; + + void writeF32(int pointer, double value) => _f32[pointer ~/ 4] = value; + void writeF64(int pointer, double value) => _f64[pointer ~/ 8] = value; + void writeI32(int pointer, int value) => _i32[pointer ~/ 4] = value; + + List readF32List(int pointer, int count) { + final base = pointer ~/ 4; + return [for (var i = 0; i < count; i++) _f32[base + i]]; + } + + List readF64List(int pointer, int count) { + final base = pointer ~/ 8; + return [for (var i = 0; i < count; i++) _f64[base + i]]; + } + + List readI32List(int pointer, int count) { + final base = pointer ~/ 4; + return [for (var i = 0; i < count; i++) _i32[base + i]]; + } + + void writeF32List(int pointer, List values) { + final base = pointer ~/ 4; + for (var i = 0; i < values.length; i++) { + _f32[base + i] = values[i]; + } + } + + /// Writes [text] as a NUL-terminated UTF-8 string and returns the + /// pointer, or 0 for null. + int writeCString(int pointer, String? text) { + if (text == null) { + return 0; + } + final bytes = utf8.encode(text); + for (var i = 0; i < bytes.length; i++) { + _u8[pointer + i] = bytes[i]; + } + _u8[pointer + bytes.length] = 0; + return pointer; + } + + /// Reads a NUL-terminated UTF-8 string. + String readCString(int pointer) { + if (pointer == 0) { + return ''; + } + final bytes = []; + var index = pointer; + while (true) { + final byte = _u8[index++]; + if (byte == 0) { + break; + } + bytes.add(byte); + } + return utf8.decode(bytes); + } +} + +extension on JSFunction { + JSAny? callAsFunctionVarArgs(JSAny? thisArg, List arguments) => + callMethodVarArgs('apply'.toJS, [thisArg, arguments.toJS]); +} + +extension on JSArrayBuffer { + external int get byteLength; +} diff --git a/packages/forge2d/lib/src/initialize.dart b/packages/forge2d/lib/src/initialize.dart index 1a552d9b..16c949ea 100644 --- a/packages/forge2d/lib/src/initialize.dart +++ b/packages/forge2d/lib/src/initialize.dart @@ -9,12 +9,17 @@ RawBox2D get rawBox2D => _rawBox2D ??= createRawBox2D(); /// Initializes forge2d. /// -/// On native platforms this completes immediately. On the web (in a future -/// release) it loads and instantiates the Box2D WebAssembly module, and -/// calling it before creating any world is mandatory. +/// On native platforms this completes immediately. On the web it fetches +/// and instantiates the Box2D WebAssembly module, and calling it before +/// creating any world is mandatory. +/// +/// On the web the module is looked up at the package asset path served by +/// the Dart web tooling, then at `box2d.wasm` relative to the page (copy +/// the file there in Flutter web apps with `dart run forge2d:setup_web`). +/// [wasmUri] overrides the lookup. /// /// Cross-platform code should always call and await this first. -Future initializeForge2D() async { - await initializeBackend(); +Future initializeForge2D({Uri? wasmUri}) async { + await initializeBackend(wasmUri: wasmUri); _rawBox2D ??= createRawBox2D(); } diff --git a/packages/forge2d/native/wasm/f2d_shim.c b/packages/forge2d/native/wasm/f2d_shim.c new file mode 100644 index 00000000..d18b8f35 --- /dev/null +++ b/packages/forge2d/native/wasm/f2d_shim.c @@ -0,0 +1,1443 @@ +// The forge2d WebAssembly shim. +// +// The wasm C ABI passes structs indirectly, which makes the raw Box2D +// exports impractical to call from JavaScript. This shim wraps every +// function the Dart backend needs with a flat scalar/pointer signature: +// +// - World ids cross as one uint32: index1 | (generation << 16). +// - Body/shape/chain/joint ids cross as (int32 index1, uint32 wg) where +// wg packs world0 | (generation << 16), matching the Dart seam. +// - Vectors and tuples are written into caller-provided buffers. +// - 64-bit filter bits cross as two uint32 halves. +// - Events are copied into caller-provided float64 buffers (float64 +// represents every int32 exactly, which keeps one buffer per event kind). +// - Synchronous callbacks (queries, custom filter, pre-solve, debug draw) +// call fixed host imports provided at instantiation time. +// +// Keep this file in sync with lib/src/backend/raw_box2d_wasm.dart. + +#include +#include + +#include "box2d/box2d.h" + +#define F2D_EXPORT EMSCRIPTEN_KEEPALIVE + +// Host imports, provided by the Dart side at instantiation. +extern float f2d_host_cast_ray(int32_t shape_index1, uint32_t shape_wg, + float px, float py, float nx, float ny, + float fraction); +extern int f2d_host_overlap(int32_t shape_index1, uint32_t shape_wg); +extern int f2d_host_custom_filter(int32_t a_index1, uint32_t a_wg, + int32_t b_index1, uint32_t b_wg); +extern int f2d_host_pre_solve(int32_t a_index1, uint32_t a_wg, + int32_t b_index1, uint32_t b_wg, float nx, + float ny); +extern void f2d_host_draw_polygon(const float* vertices, int count, + uint32_t color); +extern void f2d_host_draw_solid_polygon(float px, float py, float qc, float qs, + const float* vertices, int count, + float radius, uint32_t color); +extern void f2d_host_draw_circle(float x, float y, float radius, + uint32_t color); +extern void f2d_host_draw_solid_circle(float px, float py, float qc, float qs, + float radius, uint32_t color); +extern void f2d_host_draw_solid_capsule(float p1x, float p1y, float p2x, + float p2y, float radius, + uint32_t color); +extern void f2d_host_draw_segment(float p1x, float p1y, float p2x, float p2y, + uint32_t color); +extern void f2d_host_draw_transform(float px, float py, float qc, float qs); +extern void f2d_host_draw_point(float x, float y, float size, uint32_t color); +extern void f2d_host_draw_string(float x, float y, const char* text, + uint32_t color); + +// Id conversions. + +static b2WorldId f2d_world(uint32_t id) { + return (b2WorldId){(uint16_t)(id & 0xFFFF), (uint16_t)(id >> 16)}; +} + +static uint32_t f2d_pack_world(b2WorldId id) { + return (uint32_t)id.index1 | ((uint32_t)id.generation << 16); +} + +static b2BodyId f2d_body(int32_t index1, uint32_t wg) { + return (b2BodyId){index1, (uint16_t)(wg & 0xFFFF), (uint16_t)(wg >> 16)}; +} + +static b2ShapeId f2d_shape(int32_t index1, uint32_t wg) { + return (b2ShapeId){index1, (uint16_t)(wg & 0xFFFF), (uint16_t)(wg >> 16)}; +} + +static b2ChainId f2d_chain(int32_t index1, uint32_t wg) { + return (b2ChainId){index1, (uint16_t)(wg & 0xFFFF), (uint16_t)(wg >> 16)}; +} + +static b2JointId f2d_joint(int32_t index1, uint32_t wg) { + return (b2JointId){index1, (uint16_t)(wg & 0xFFFF), (uint16_t)(wg >> 16)}; +} + +static uint32_t f2d_wg_body(b2BodyId id) { + return (uint32_t)id.world0 | ((uint32_t)id.generation << 16); +} + +static uint32_t f2d_wg_shape(b2ShapeId id) { + return (uint32_t)id.world0 | ((uint32_t)id.generation << 16); +} + +static uint32_t f2d_wg_chain(b2ChainId id) { + return (uint32_t)id.world0 | ((uint32_t)id.generation << 16); +} + +static uint32_t f2d_wg_joint(b2JointId id) { + return (uint32_t)id.world0 | ((uint32_t)id.generation << 16); +} + +static uint64_t f2d_bits(uint32_t lo, uint32_t hi) { + return (uint64_t)lo | ((uint64_t)hi << 32); +} + +static b2Filter f2d_filter(uint32_t category_lo, uint32_t category_hi, + uint32_t mask_lo, uint32_t mask_hi, + int32_t group_index) { + b2Filter filter = b2DefaultFilter(); + filter.categoryBits = f2d_bits(category_lo, category_hi); + filter.maskBits = f2d_bits(mask_lo, mask_hi); + filter.groupIndex = group_index; + return filter; +} + +static b2QueryFilter f2d_query_filter(uint32_t category_lo, + uint32_t category_hi, uint32_t mask_lo, + uint32_t mask_hi) { + b2QueryFilter filter = b2DefaultQueryFilter(); + filter.categoryBits = f2d_bits(category_lo, category_hi); + filter.maskBits = f2d_bits(mask_lo, mask_hi); + return filter; +} + +// World. + +F2D_EXPORT uint32_t f2d_create_world( + float gravity_x, float gravity_y, float restitution_threshold, + float hit_event_threshold, float contact_hertz, + float contact_damping_ratio, float max_contact_push_speed, + float maximum_linear_speed, int enable_sleep, int enable_continuous) { + b2WorldDef def = b2DefaultWorldDef(); + def.gravity = (b2Vec2){gravity_x, gravity_y}; + def.restitutionThreshold = restitution_threshold; + def.hitEventThreshold = hit_event_threshold; + def.contactHertz = contact_hertz; + def.contactDampingRatio = contact_damping_ratio; + def.maxContactPushSpeed = max_contact_push_speed; + def.maximumLinearSpeed = maximum_linear_speed; + def.enableSleep = enable_sleep; + def.enableContinuous = enable_continuous; + return f2d_pack_world(b2CreateWorld(&def)); +} + +F2D_EXPORT void f2d_destroy_world(uint32_t w) { b2DestroyWorld(f2d_world(w)); } + +F2D_EXPORT int f2d_world_is_valid(uint32_t w) { + return b2World_IsValid(f2d_world(w)); +} + +F2D_EXPORT void f2d_world_step(uint32_t w, float time_step, int sub_steps) { + b2World_Step(f2d_world(w), time_step, sub_steps); +} + +F2D_EXPORT void f2d_world_set_gravity(uint32_t w, float x, float y) { + b2World_SetGravity(f2d_world(w), (b2Vec2){x, y}); +} + +F2D_EXPORT void f2d_world_get_gravity(uint32_t w, float* out) { + b2Vec2 gravity = b2World_GetGravity(f2d_world(w)); + out[0] = gravity.x; + out[1] = gravity.y; +} + +F2D_EXPORT void f2d_world_enable_sleeping(uint32_t w, int enabled) { + b2World_EnableSleeping(f2d_world(w), enabled); +} + +F2D_EXPORT int f2d_world_is_sleeping_enabled(uint32_t w) { + return b2World_IsSleepingEnabled(f2d_world(w)); +} + +F2D_EXPORT void f2d_world_enable_continuous(uint32_t w, int enabled) { + b2World_EnableContinuous(f2d_world(w), enabled); +} + +F2D_EXPORT int f2d_world_is_continuous_enabled(uint32_t w) { + return b2World_IsContinuousEnabled(f2d_world(w)); +} + +F2D_EXPORT void f2d_world_explode(uint32_t w, uint32_t mask_lo, + uint32_t mask_hi, float x, float y, + float radius, float falloff, + float impulse_per_length) { + b2ExplosionDef def = b2DefaultExplosionDef(); + def.maskBits = f2d_bits(mask_lo, mask_hi); + def.position = (b2Vec2){x, y}; + def.radius = radius; + def.falloff = falloff; + def.impulsePerLength = impulse_per_length; + b2World_Explode(f2d_world(w), &def); +} + +// Bodies. + +F2D_EXPORT void f2d_create_body( + uint32_t w, int type, float px, float py, float qc, float qs, float lvx, + float lvy, float angular_velocity, float linear_damping, + float angular_damping, float gravity_scale, float sleep_threshold, + const char* name, int enable_sleep, int is_awake, int fixed_rotation, + int is_bullet, int is_enabled, int allow_fast_rotation, int32_t* out) { + b2BodyDef def = b2DefaultBodyDef(); + def.type = (b2BodyType)type; + def.position = (b2Vec2){px, py}; + def.rotation = (b2Rot){qc, qs}; + def.linearVelocity = (b2Vec2){lvx, lvy}; + def.angularVelocity = angular_velocity; + def.linearDamping = linear_damping; + def.angularDamping = angular_damping; + def.gravityScale = gravity_scale; + def.sleepThreshold = sleep_threshold; + def.name = name; + def.enableSleep = enable_sleep; + def.isAwake = is_awake; + def.fixedRotation = fixed_rotation; + def.isBullet = is_bullet; + def.isEnabled = is_enabled; + def.allowFastRotation = allow_fast_rotation; + b2BodyId id = b2CreateBody(f2d_world(w), &def); + out[0] = id.index1; + out[1] = (int32_t)f2d_wg_body(id); +} + +F2D_EXPORT void f2d_destroy_body(int32_t i1, uint32_t wg) { + b2DestroyBody(f2d_body(i1, wg)); +} + +F2D_EXPORT int f2d_body_is_valid(int32_t i1, uint32_t wg) { + return b2Body_IsValid(f2d_body(i1, wg)); +} + +F2D_EXPORT void f2d_body_get_position(int32_t i1, uint32_t wg, float* out) { + b2Vec2 position = b2Body_GetPosition(f2d_body(i1, wg)); + out[0] = position.x; + out[1] = position.y; +} + +F2D_EXPORT void f2d_body_get_rotation(int32_t i1, uint32_t wg, float* out) { + b2Rot rotation = b2Body_GetRotation(f2d_body(i1, wg)); + out[0] = rotation.c; + out[1] = rotation.s; +} + +F2D_EXPORT void f2d_body_set_transform(int32_t i1, uint32_t wg, float px, + float py, float qc, float qs) { + b2Body_SetTransform(f2d_body(i1, wg), (b2Vec2){px, py}, (b2Rot){qc, qs}); +} + +F2D_EXPORT void f2d_body_get_linear_velocity(int32_t i1, uint32_t wg, + float* out) { + b2Vec2 velocity = b2Body_GetLinearVelocity(f2d_body(i1, wg)); + out[0] = velocity.x; + out[1] = velocity.y; +} + +F2D_EXPORT void f2d_body_set_linear_velocity(int32_t i1, uint32_t wg, float x, + float y) { + b2Body_SetLinearVelocity(f2d_body(i1, wg), (b2Vec2){x, y}); +} + +F2D_EXPORT void f2d_body_apply_force(int32_t i1, uint32_t wg, float fx, + float fy, float px, float py, int wake) { + b2Body_ApplyForce(f2d_body(i1, wg), (b2Vec2){fx, fy}, (b2Vec2){px, py}, + wake); +} + +F2D_EXPORT void f2d_body_apply_force_to_center(int32_t i1, uint32_t wg, + float fx, float fy, int wake) { + b2Body_ApplyForceToCenter(f2d_body(i1, wg), (b2Vec2){fx, fy}, wake); +} + +F2D_EXPORT void f2d_body_apply_torque(int32_t i1, uint32_t wg, float torque, + int wake) { + b2Body_ApplyTorque(f2d_body(i1, wg), torque, wake); +} + +F2D_EXPORT void f2d_body_apply_linear_impulse(int32_t i1, uint32_t wg, + float ix, float iy, float px, + float py, int wake) { + b2Body_ApplyLinearImpulse(f2d_body(i1, wg), (b2Vec2){ix, iy}, + (b2Vec2){px, py}, wake); +} + +F2D_EXPORT void f2d_body_apply_linear_impulse_to_center(int32_t i1, + uint32_t wg, float ix, + float iy, int wake) { + b2Body_ApplyLinearImpulseToCenter(f2d_body(i1, wg), (b2Vec2){ix, iy}, wake); +} + +F2D_EXPORT void f2d_body_apply_angular_impulse(int32_t i1, uint32_t wg, + float impulse, int wake) { + b2Body_ApplyAngularImpulse(f2d_body(i1, wg), impulse, wake); +} + +F2D_EXPORT void f2d_body_get_local_center(int32_t i1, uint32_t wg, + float* out) { + b2Vec2 center = b2Body_GetLocalCenterOfMass(f2d_body(i1, wg)); + out[0] = center.x; + out[1] = center.y; +} + +F2D_EXPORT void f2d_body_get_world_center(int32_t i1, uint32_t wg, + float* out) { + b2Vec2 center = b2Body_GetWorldCenterOfMass(f2d_body(i1, wg)); + out[0] = center.x; + out[1] = center.y; +} + +F2D_EXPORT void f2d_body_set_mass_data(int32_t i1, uint32_t wg, float mass, + float rotational_inertia, float cx, + float cy) { + b2MassData mass_data = {mass, {cx, cy}, rotational_inertia}; + b2Body_SetMassData(f2d_body(i1, wg), mass_data); +} + +F2D_EXPORT const char* f2d_body_get_name(int32_t i1, uint32_t wg) { + return b2Body_GetName(f2d_body(i1, wg)); +} + +F2D_EXPORT void f2d_body_set_name(int32_t i1, uint32_t wg, const char* name) { + b2Body_SetName(f2d_body(i1, wg), name); +} + +F2D_EXPORT void f2d_body_get_world_point(int32_t i1, uint32_t wg, float x, + float y, float* out) { + b2Vec2 point = b2Body_GetWorldPoint(f2d_body(i1, wg), (b2Vec2){x, y}); + out[0] = point.x; + out[1] = point.y; +} + +F2D_EXPORT void f2d_body_get_local_point(int32_t i1, uint32_t wg, float x, + float y, float* out) { + b2Vec2 point = b2Body_GetLocalPoint(f2d_body(i1, wg), (b2Vec2){x, y}); + out[0] = point.x; + out[1] = point.y; +} + +F2D_EXPORT int f2d_body_get_shape_count(int32_t i1, uint32_t wg) { + return b2Body_GetShapeCount(f2d_body(i1, wg)); +} + +F2D_EXPORT int f2d_body_get_shapes(int32_t i1, uint32_t wg, int32_t* out, + int capacity) { + b2ShapeId shapes[capacity]; + int count = b2Body_GetShapes(f2d_body(i1, wg), shapes, capacity); + for (int i = 0; i < count; ++i) { + out[2 * i] = shapes[i].index1; + out[2 * i + 1] = (int32_t)f2d_wg_shape(shapes[i]); + } + return count; +} + +F2D_EXPORT int f2d_body_get_joint_count(int32_t i1, uint32_t wg) { + return b2Body_GetJointCount(f2d_body(i1, wg)); +} + +F2D_EXPORT int f2d_body_get_joints(int32_t i1, uint32_t wg, int32_t* out, + int capacity) { + b2JointId joints[capacity]; + int count = b2Body_GetJoints(f2d_body(i1, wg), joints, capacity); + for (int i = 0; i < count; ++i) { + out[2 * i] = joints[i].index1; + out[2 * i + 1] = (int32_t)f2d_wg_joint(joints[i]); + } + return count; +} + +// Scalar body accessors, generated by macro. +#define F2D_BODY_GET_FLOAT(name, fn) \ + F2D_EXPORT float name(int32_t i1, uint32_t wg) { return fn(f2d_body(i1, wg)); } +#define F2D_BODY_SET_FLOAT(name, fn) \ + F2D_EXPORT void name(int32_t i1, uint32_t wg, float v) { fn(f2d_body(i1, wg), v); } +#define F2D_BODY_GET_INT(name, fn) \ + F2D_EXPORT int name(int32_t i1, uint32_t wg) { return (int)fn(f2d_body(i1, wg)); } +#define F2D_BODY_SET_BOOL(name, fn) \ + F2D_EXPORT void name(int32_t i1, uint32_t wg, int v) { fn(f2d_body(i1, wg), v); } +#define F2D_BODY_CALL(name, fn) \ + F2D_EXPORT void name(int32_t i1, uint32_t wg) { fn(f2d_body(i1, wg)); } + +F2D_BODY_GET_FLOAT(f2d_body_get_angular_velocity, b2Body_GetAngularVelocity) +F2D_BODY_SET_FLOAT(f2d_body_set_angular_velocity, b2Body_SetAngularVelocity) +F2D_BODY_GET_FLOAT(f2d_body_get_mass, b2Body_GetMass) +F2D_BODY_GET_FLOAT(f2d_body_get_rotational_inertia, b2Body_GetRotationalInertia) +F2D_BODY_CALL(f2d_body_apply_mass_from_shapes, b2Body_ApplyMassFromShapes) +F2D_BODY_GET_INT(f2d_body_get_type, b2Body_GetType) +F2D_EXPORT void f2d_body_set_type(int32_t i1, uint32_t wg, int type) { + b2Body_SetType(f2d_body(i1, wg), (b2BodyType)type); +} +F2D_BODY_GET_INT(f2d_body_is_awake, b2Body_IsAwake) +F2D_BODY_SET_BOOL(f2d_body_set_awake, b2Body_SetAwake) +F2D_BODY_GET_INT(f2d_body_is_sleep_enabled, b2Body_IsSleepEnabled) +F2D_BODY_SET_BOOL(f2d_body_enable_sleep, b2Body_EnableSleep) +F2D_BODY_GET_FLOAT(f2d_body_get_sleep_threshold, b2Body_GetSleepThreshold) +F2D_BODY_SET_FLOAT(f2d_body_set_sleep_threshold, b2Body_SetSleepThreshold) +F2D_BODY_GET_INT(f2d_body_is_enabled, b2Body_IsEnabled) +F2D_BODY_CALL(f2d_body_disable, b2Body_Disable) +F2D_BODY_CALL(f2d_body_enable, b2Body_Enable) +F2D_BODY_GET_INT(f2d_body_is_fixed_rotation, b2Body_IsFixedRotation) +F2D_BODY_SET_BOOL(f2d_body_set_fixed_rotation, b2Body_SetFixedRotation) +F2D_BODY_GET_INT(f2d_body_is_bullet, b2Body_IsBullet) +F2D_BODY_SET_BOOL(f2d_body_set_bullet, b2Body_SetBullet) +F2D_BODY_GET_FLOAT(f2d_body_get_gravity_scale, b2Body_GetGravityScale) +F2D_BODY_SET_FLOAT(f2d_body_set_gravity_scale, b2Body_SetGravityScale) +F2D_BODY_GET_FLOAT(f2d_body_get_linear_damping, b2Body_GetLinearDamping) +F2D_BODY_SET_FLOAT(f2d_body_set_linear_damping, b2Body_SetLinearDamping) +F2D_BODY_GET_FLOAT(f2d_body_get_angular_damping, b2Body_GetAngularDamping) +F2D_BODY_SET_FLOAT(f2d_body_set_angular_damping, b2Body_SetAngularDamping) + +// Shapes. + +typedef struct f2dShapeDef { + float friction; + float restitution; + float rolling_resistance; + float tangent_speed; + int32_t user_material_id; + uint32_t custom_color; + float density; + uint32_t category_lo; + uint32_t category_hi; + uint32_t mask_lo; + uint32_t mask_hi; + int32_t group_index; + int32_t is_sensor; + int32_t enable_sensor_events; + int32_t enable_contact_events; + int32_t enable_hit_events; + int32_t enable_pre_solve_events; + int32_t invoke_contact_creation; + int32_t update_body_mass; +} f2dShapeDef; + +static b2ShapeDef f2d_shape_def(const f2dShapeDef* in) { + b2ShapeDef def = b2DefaultShapeDef(); + def.material.friction = in->friction; + def.material.restitution = in->restitution; + def.material.rollingResistance = in->rolling_resistance; + def.material.tangentSpeed = in->tangent_speed; + def.material.userMaterialId = in->user_material_id; + def.material.customColor = in->custom_color; + def.density = in->density; + def.filter = f2d_filter(in->category_lo, in->category_hi, in->mask_lo, + in->mask_hi, in->group_index); + def.isSensor = in->is_sensor; + def.enableSensorEvents = in->enable_sensor_events; + def.enableContactEvents = in->enable_contact_events; + def.enableHitEvents = in->enable_hit_events; + def.enablePreSolveEvents = in->enable_pre_solve_events; + def.invokeContactCreation = in->invoke_contact_creation; + def.updateBodyMass = in->update_body_mass; + return def; +} + +static void f2d_out_shape(b2ShapeId id, int32_t* out) { + out[0] = id.index1; + out[1] = (int32_t)f2d_wg_shape(id); +} + +F2D_EXPORT void f2d_create_circle_shape(int32_t i1, uint32_t wg, + const f2dShapeDef* def, float cx, + float cy, float radius, int32_t* out) { + b2ShapeDef shape_def = f2d_shape_def(def); + b2Circle circle = {{cx, cy}, radius}; + f2d_out_shape(b2CreateCircleShape(f2d_body(i1, wg), &shape_def, &circle), + out); +} + +F2D_EXPORT void f2d_create_capsule_shape(int32_t i1, uint32_t wg, + const f2dShapeDef* def, float c1x, + float c1y, float c2x, float c2y, + float radius, int32_t* out) { + b2ShapeDef shape_def = f2d_shape_def(def); + b2Capsule capsule = {{c1x, c1y}, {c2x, c2y}, radius}; + f2d_out_shape(b2CreateCapsuleShape(f2d_body(i1, wg), &shape_def, &capsule), + out); +} + +F2D_EXPORT void f2d_create_segment_shape(int32_t i1, uint32_t wg, + const f2dShapeDef* def, float p1x, + float p1y, float p2x, float p2y, + int32_t* out) { + b2ShapeDef shape_def = f2d_shape_def(def); + b2Segment segment = {{p1x, p1y}, {p2x, p2y}}; + f2d_out_shape(b2CreateSegmentShape(f2d_body(i1, wg), &shape_def, &segment), + out); +} + +F2D_EXPORT void f2d_create_box_shape(int32_t i1, uint32_t wg, + const f2dShapeDef* def, float half_width, + float half_height, float cx, float cy, + float qc, float qs, float radius, + int32_t* out) { + b2ShapeDef shape_def = f2d_shape_def(def); + b2Polygon polygon = b2MakeOffsetRoundedBox(half_width, half_height, + (b2Vec2){cx, cy}, (b2Rot){qc, qs}, + radius); + f2d_out_shape(b2CreatePolygonShape(f2d_body(i1, wg), &shape_def, &polygon), + out); +} + +// Returns false when the hull is degenerate. +F2D_EXPORT int f2d_create_polygon_shape(int32_t i1, uint32_t wg, + const f2dShapeDef* def, + const float* points, int count, + float radius, int32_t* out) { + b2Hull hull = b2ComputeHull((const b2Vec2*)points, count); + if (hull.count == 0) { + return 0; + } + b2ShapeDef shape_def = f2d_shape_def(def); + b2Polygon polygon = b2MakePolygon(&hull, radius); + f2d_out_shape(b2CreatePolygonShape(f2d_body(i1, wg), &shape_def, &polygon), + out); + return 1; +} + +F2D_EXPORT void f2d_destroy_shape(int32_t i1, uint32_t wg, + int update_body_mass) { + b2DestroyShape(f2d_shape(i1, wg), update_body_mass); +} + +F2D_EXPORT void f2d_shape_get_body(int32_t i1, uint32_t wg, int32_t* out) { + b2BodyId id = b2Shape_GetBody(f2d_shape(i1, wg)); + out[0] = id.index1; + out[1] = (int32_t)f2d_wg_body(id); +} + +F2D_EXPORT void f2d_shape_set_density(int32_t i1, uint32_t wg, float density, + int update_body_mass) { + b2Shape_SetDensity(f2d_shape(i1, wg), density, update_body_mass); +} + +F2D_EXPORT void f2d_shape_get_filter(int32_t i1, uint32_t wg, int32_t* out) { + b2Filter filter = b2Shape_GetFilter(f2d_shape(i1, wg)); + out[0] = (int32_t)(filter.categoryBits & 0xFFFFFFFF); + out[1] = (int32_t)(filter.categoryBits >> 32); + out[2] = (int32_t)(filter.maskBits & 0xFFFFFFFF); + out[3] = (int32_t)(filter.maskBits >> 32); + out[4] = filter.groupIndex; +} + +F2D_EXPORT void f2d_shape_set_filter(int32_t i1, uint32_t wg, + uint32_t category_lo, uint32_t category_hi, + uint32_t mask_lo, uint32_t mask_hi, + int32_t group_index) { + b2Shape_SetFilter(f2d_shape(i1, wg), f2d_filter(category_lo, category_hi, + mask_lo, mask_hi, + group_index)); +} + +F2D_EXPORT int f2d_shape_test_point(int32_t i1, uint32_t wg, float x, + float y) { + return b2Shape_TestPoint(f2d_shape(i1, wg), (b2Vec2){x, y}); +} + +F2D_EXPORT void f2d_shape_get_aabb(int32_t i1, uint32_t wg, float* out) { + b2AABB aabb = b2Shape_GetAABB(f2d_shape(i1, wg)); + out[0] = aabb.lowerBound.x; + out[1] = aabb.lowerBound.y; + out[2] = aabb.upperBound.x; + out[3] = aabb.upperBound.y; +} + +#define F2D_SHAPE_GET_FLOAT(name, fn) \ + F2D_EXPORT float name(int32_t i1, uint32_t wg) { return fn(f2d_shape(i1, wg)); } +#define F2D_SHAPE_SET_FLOAT(name, fn) \ + F2D_EXPORT void name(int32_t i1, uint32_t wg, float v) { fn(f2d_shape(i1, wg), v); } +#define F2D_SHAPE_GET_INT(name, fn) \ + F2D_EXPORT int name(int32_t i1, uint32_t wg) { return (int)fn(f2d_shape(i1, wg)); } +#define F2D_SHAPE_SET_BOOL(name, fn) \ + F2D_EXPORT void name(int32_t i1, uint32_t wg, int v) { fn(f2d_shape(i1, wg), v); } + +F2D_SHAPE_GET_INT(f2d_shape_is_valid, b2Shape_IsValid) +F2D_SHAPE_GET_INT(f2d_shape_get_type, b2Shape_GetType) +F2D_SHAPE_GET_INT(f2d_shape_is_sensor, b2Shape_IsSensor) +F2D_SHAPE_GET_FLOAT(f2d_shape_get_density, b2Shape_GetDensity) +F2D_SHAPE_GET_FLOAT(f2d_shape_get_friction, b2Shape_GetFriction) +F2D_SHAPE_SET_FLOAT(f2d_shape_set_friction, b2Shape_SetFriction) +F2D_SHAPE_GET_FLOAT(f2d_shape_get_restitution, b2Shape_GetRestitution) +F2D_SHAPE_SET_FLOAT(f2d_shape_set_restitution, b2Shape_SetRestitution) +F2D_SHAPE_GET_INT(f2d_shape_are_sensor_events_enabled, + b2Shape_AreSensorEventsEnabled) +F2D_SHAPE_SET_BOOL(f2d_shape_enable_sensor_events, b2Shape_EnableSensorEvents) +F2D_SHAPE_GET_INT(f2d_shape_are_contact_events_enabled, + b2Shape_AreContactEventsEnabled) +F2D_SHAPE_SET_BOOL(f2d_shape_enable_contact_events, b2Shape_EnableContactEvents) +F2D_SHAPE_GET_INT(f2d_shape_are_hit_events_enabled, b2Shape_AreHitEventsEnabled) +F2D_SHAPE_SET_BOOL(f2d_shape_enable_hit_events, b2Shape_EnableHitEvents) +F2D_SHAPE_GET_INT(f2d_shape_are_pre_solve_events_enabled, + b2Shape_ArePreSolveEventsEnabled) +F2D_SHAPE_SET_BOOL(f2d_shape_enable_pre_solve_events, + b2Shape_EnablePreSolveEvents) + +// Chains. + +F2D_EXPORT void f2d_create_chain(int32_t i1, uint32_t wg, const float* points, + int point_count, const float* materials, + int material_count, uint32_t category_lo, + uint32_t category_hi, uint32_t mask_lo, + uint32_t mask_hi, int32_t group_index, + int is_loop, int enable_sensor_events, + int32_t* out) { + b2SurfaceMaterial surface_materials[material_count]; + for (int i = 0; i < material_count; ++i) { + const float* m = materials + i * 6; + surface_materials[i] = (b2SurfaceMaterial){ + m[0], m[1], m[2], m[3], (int)m[4], (uint32_t)m[5]}; + } + b2ChainDef def = b2DefaultChainDef(); + def.points = (const b2Vec2*)points; + def.count = point_count; + def.materials = surface_materials; + def.materialCount = material_count; + def.filter = f2d_filter(category_lo, category_hi, mask_lo, mask_hi, + group_index); + def.isLoop = is_loop; + def.enableSensorEvents = enable_sensor_events; + b2ChainId id = b2CreateChain(f2d_body(i1, wg), &def); + out[0] = id.index1; + out[1] = (int32_t)f2d_wg_chain(id); +} + +F2D_EXPORT void f2d_destroy_chain(int32_t i1, uint32_t wg) { + b2DestroyChain(f2d_chain(i1, wg)); +} + +F2D_EXPORT int f2d_chain_is_valid(int32_t i1, uint32_t wg) { + return b2Chain_IsValid(f2d_chain(i1, wg)); +} + +F2D_EXPORT void f2d_chain_set_friction(int32_t i1, uint32_t wg, float v) { + b2Chain_SetFriction(f2d_chain(i1, wg), v); +} + +F2D_EXPORT float f2d_chain_get_friction(int32_t i1, uint32_t wg) { + return b2Chain_GetFriction(f2d_chain(i1, wg)); +} + +F2D_EXPORT void f2d_chain_set_restitution(int32_t i1, uint32_t wg, float v) { + b2Chain_SetRestitution(f2d_chain(i1, wg), v); +} + +F2D_EXPORT float f2d_chain_get_restitution(int32_t i1, uint32_t wg) { + return b2Chain_GetRestitution(f2d_chain(i1, wg)); +} + +F2D_EXPORT int f2d_chain_get_segment_count(int32_t i1, uint32_t wg) { + return b2Chain_GetSegmentCount(f2d_chain(i1, wg)); +} + +F2D_EXPORT int f2d_chain_get_segments(int32_t i1, uint32_t wg, int32_t* out, + int capacity) { + b2ShapeId segments[capacity]; + int count = b2Chain_GetSegments(f2d_chain(i1, wg), segments, capacity); + for (int i = 0; i < count; ++i) { + out[2 * i] = segments[i].index1; + out[2 * i + 1] = (int32_t)f2d_wg_shape(segments[i]); + } + return count; +} + +// Joints. + +static void f2d_out_joint(b2JointId id, int32_t* out) { + out[0] = id.index1; + out[1] = (int32_t)f2d_wg_joint(id); +} + +F2D_EXPORT void f2d_create_distance_joint( + uint32_t w, int32_t ai1, uint32_t awg, int32_t bi1, uint32_t bwg, + float anchor_ax, float anchor_ay, float anchor_bx, float anchor_by, + float length, int enable_spring, float hertz, float damping_ratio, + int enable_limit, float min_length, float max_length, int enable_motor, + float max_motor_force, float motor_speed, int collide_connected, + int32_t* out) { + b2DistanceJointDef def = b2DefaultDistanceJointDef(); + def.bodyIdA = f2d_body(ai1, awg); + def.bodyIdB = f2d_body(bi1, bwg); + def.localAnchorA = (b2Vec2){anchor_ax, anchor_ay}; + def.localAnchorB = (b2Vec2){anchor_bx, anchor_by}; + def.length = length; + def.enableSpring = enable_spring; + def.hertz = hertz; + def.dampingRatio = damping_ratio; + def.enableLimit = enable_limit; + def.minLength = min_length; + def.maxLength = max_length; + def.enableMotor = enable_motor; + def.maxMotorForce = max_motor_force; + def.motorSpeed = motor_speed; + def.collideConnected = collide_connected; + f2d_out_joint(b2CreateDistanceJoint(f2d_world(w), &def), out); +} + +F2D_EXPORT void f2d_create_filter_joint(uint32_t w, int32_t ai1, uint32_t awg, + int32_t bi1, uint32_t bwg, + int32_t* out) { + b2FilterJointDef def = b2DefaultFilterJointDef(); + def.bodyIdA = f2d_body(ai1, awg); + def.bodyIdB = f2d_body(bi1, bwg); + f2d_out_joint(b2CreateFilterJoint(f2d_world(w), &def), out); +} + +F2D_EXPORT void f2d_create_motor_joint(uint32_t w, int32_t ai1, uint32_t awg, + int32_t bi1, uint32_t bwg, + float offset_x, float offset_y, + float angular_offset, float max_force, + float max_torque, + float correction_factor, + int collide_connected, int32_t* out) { + b2MotorJointDef def = b2DefaultMotorJointDef(); + def.bodyIdA = f2d_body(ai1, awg); + def.bodyIdB = f2d_body(bi1, bwg); + def.linearOffset = (b2Vec2){offset_x, offset_y}; + def.angularOffset = angular_offset; + def.maxForce = max_force; + def.maxTorque = max_torque; + def.correctionFactor = correction_factor; + def.collideConnected = collide_connected; + f2d_out_joint(b2CreateMotorJoint(f2d_world(w), &def), out); +} + +F2D_EXPORT void f2d_create_mouse_joint(uint32_t w, int32_t ai1, uint32_t awg, + int32_t bi1, uint32_t bwg, + float target_x, float target_y, + float hertz, float damping_ratio, + float max_force, int collide_connected, + int32_t* out) { + b2MouseJointDef def = b2DefaultMouseJointDef(); + def.bodyIdA = f2d_body(ai1, awg); + def.bodyIdB = f2d_body(bi1, bwg); + def.target = (b2Vec2){target_x, target_y}; + def.hertz = hertz; + def.dampingRatio = damping_ratio; + def.maxForce = max_force; + def.collideConnected = collide_connected; + f2d_out_joint(b2CreateMouseJoint(f2d_world(w), &def), out); +} + +F2D_EXPORT void f2d_create_prismatic_joint( + uint32_t w, int32_t ai1, uint32_t awg, int32_t bi1, uint32_t bwg, + float anchor_ax, float anchor_ay, float anchor_bx, float anchor_by, + float axis_x, float axis_y, float reference_angle, + float target_translation, int enable_spring, float hertz, + float damping_ratio, int enable_limit, float lower_translation, + float upper_translation, int enable_motor, float max_motor_force, + float motor_speed, int collide_connected, int32_t* out) { + b2PrismaticJointDef def = b2DefaultPrismaticJointDef(); + def.bodyIdA = f2d_body(ai1, awg); + def.bodyIdB = f2d_body(bi1, bwg); + def.localAnchorA = (b2Vec2){anchor_ax, anchor_ay}; + def.localAnchorB = (b2Vec2){anchor_bx, anchor_by}; + def.localAxisA = (b2Vec2){axis_x, axis_y}; + def.referenceAngle = reference_angle; + def.targetTranslation = target_translation; + def.enableSpring = enable_spring; + def.hertz = hertz; + def.dampingRatio = damping_ratio; + def.enableLimit = enable_limit; + def.lowerTranslation = lower_translation; + def.upperTranslation = upper_translation; + def.enableMotor = enable_motor; + def.maxMotorForce = max_motor_force; + def.motorSpeed = motor_speed; + def.collideConnected = collide_connected; + f2d_out_joint(b2CreatePrismaticJoint(f2d_world(w), &def), out); +} + +F2D_EXPORT void f2d_create_revolute_joint( + uint32_t w, int32_t ai1, uint32_t awg, int32_t bi1, uint32_t bwg, + float anchor_ax, float anchor_ay, float anchor_bx, float anchor_by, + float reference_angle, float target_angle, int enable_spring, float hertz, + float damping_ratio, int enable_limit, float lower_angle, + float upper_angle, int enable_motor, float max_motor_torque, + float motor_speed, float draw_size, int collide_connected, int32_t* out) { + b2RevoluteJointDef def = b2DefaultRevoluteJointDef(); + def.bodyIdA = f2d_body(ai1, awg); + def.bodyIdB = f2d_body(bi1, bwg); + def.localAnchorA = (b2Vec2){anchor_ax, anchor_ay}; + def.localAnchorB = (b2Vec2){anchor_bx, anchor_by}; + def.referenceAngle = reference_angle; + def.targetAngle = target_angle; + def.enableSpring = enable_spring; + def.hertz = hertz; + def.dampingRatio = damping_ratio; + def.enableLimit = enable_limit; + def.lowerAngle = lower_angle; + def.upperAngle = upper_angle; + def.enableMotor = enable_motor; + def.maxMotorTorque = max_motor_torque; + def.motorSpeed = motor_speed; + def.drawSize = draw_size; + def.collideConnected = collide_connected; + f2d_out_joint(b2CreateRevoluteJoint(f2d_world(w), &def), out); +} + +F2D_EXPORT void f2d_create_weld_joint( + uint32_t w, int32_t ai1, uint32_t awg, int32_t bi1, uint32_t bwg, + float anchor_ax, float anchor_ay, float anchor_bx, float anchor_by, + float reference_angle, float linear_hertz, float angular_hertz, + float linear_damping_ratio, float angular_damping_ratio, + int collide_connected, int32_t* out) { + b2WeldJointDef def = b2DefaultWeldJointDef(); + def.bodyIdA = f2d_body(ai1, awg); + def.bodyIdB = f2d_body(bi1, bwg); + def.localAnchorA = (b2Vec2){anchor_ax, anchor_ay}; + def.localAnchorB = (b2Vec2){anchor_bx, anchor_by}; + def.referenceAngle = reference_angle; + def.linearHertz = linear_hertz; + def.angularHertz = angular_hertz; + def.linearDampingRatio = linear_damping_ratio; + def.angularDampingRatio = angular_damping_ratio; + def.collideConnected = collide_connected; + f2d_out_joint(b2CreateWeldJoint(f2d_world(w), &def), out); +} + +F2D_EXPORT void f2d_create_wheel_joint( + uint32_t w, int32_t ai1, uint32_t awg, int32_t bi1, uint32_t bwg, + float anchor_ax, float anchor_ay, float anchor_bx, float anchor_by, + float axis_x, float axis_y, int enable_spring, float hertz, + float damping_ratio, int enable_limit, float lower_translation, + float upper_translation, int enable_motor, float max_motor_torque, + float motor_speed, int collide_connected, int32_t* out) { + b2WheelJointDef def = b2DefaultWheelJointDef(); + def.bodyIdA = f2d_body(ai1, awg); + def.bodyIdB = f2d_body(bi1, bwg); + def.localAnchorA = (b2Vec2){anchor_ax, anchor_ay}; + def.localAnchorB = (b2Vec2){anchor_bx, anchor_by}; + def.localAxisA = (b2Vec2){axis_x, axis_y}; + def.enableSpring = enable_spring; + def.hertz = hertz; + def.dampingRatio = damping_ratio; + def.enableLimit = enable_limit; + def.lowerTranslation = lower_translation; + def.upperTranslation = upper_translation; + def.enableMotor = enable_motor; + def.maxMotorTorque = max_motor_torque; + def.motorSpeed = motor_speed; + def.collideConnected = collide_connected; + f2d_out_joint(b2CreateWheelJoint(f2d_world(w), &def), out); +} + +F2D_EXPORT void f2d_destroy_joint(int32_t i1, uint32_t wg) { + b2DestroyJoint(f2d_joint(i1, wg)); +} + +F2D_EXPORT int f2d_joint_is_valid(int32_t i1, uint32_t wg) { + return b2Joint_IsValid(f2d_joint(i1, wg)); +} + +F2D_EXPORT int f2d_joint_get_type(int32_t i1, uint32_t wg) { + return (int)b2Joint_GetType(f2d_joint(i1, wg)); +} + +F2D_EXPORT void f2d_joint_get_body_a(int32_t i1, uint32_t wg, int32_t* out) { + b2BodyId id = b2Joint_GetBodyA(f2d_joint(i1, wg)); + out[0] = id.index1; + out[1] = (int32_t)f2d_wg_body(id); +} + +F2D_EXPORT void f2d_joint_get_body_b(int32_t i1, uint32_t wg, int32_t* out) { + b2BodyId id = b2Joint_GetBodyB(f2d_joint(i1, wg)); + out[0] = id.index1; + out[1] = (int32_t)f2d_wg_body(id); +} + +F2D_EXPORT void f2d_joint_get_local_anchor_a(int32_t i1, uint32_t wg, + float* out) { + b2Vec2 anchor = b2Joint_GetLocalAnchorA(f2d_joint(i1, wg)); + out[0] = anchor.x; + out[1] = anchor.y; +} + +F2D_EXPORT void f2d_joint_get_local_anchor_b(int32_t i1, uint32_t wg, + float* out) { + b2Vec2 anchor = b2Joint_GetLocalAnchorB(f2d_joint(i1, wg)); + out[0] = anchor.x; + out[1] = anchor.y; +} + +F2D_EXPORT int f2d_joint_get_collide_connected(int32_t i1, uint32_t wg) { + return b2Joint_GetCollideConnected(f2d_joint(i1, wg)); +} + +F2D_EXPORT void f2d_joint_set_collide_connected(int32_t i1, uint32_t wg, + int v) { + b2Joint_SetCollideConnected(f2d_joint(i1, wg), v); +} + +F2D_EXPORT void f2d_joint_wake_bodies(int32_t i1, uint32_t wg) { + b2Joint_WakeBodies(f2d_joint(i1, wg)); +} + +F2D_EXPORT void f2d_joint_get_constraint_force(int32_t i1, uint32_t wg, + float* out) { + b2Vec2 force = b2Joint_GetConstraintForce(f2d_joint(i1, wg)); + out[0] = force.x; + out[1] = force.y; +} + +F2D_EXPORT float f2d_joint_get_constraint_torque(int32_t i1, uint32_t wg) { + return b2Joint_GetConstraintTorque(f2d_joint(i1, wg)); +} + +#define F2D_JOINT_GET_FLOAT(name, fn) \ + F2D_EXPORT float name(int32_t i1, uint32_t wg) { return fn(f2d_joint(i1, wg)); } +#define F2D_JOINT_SET_FLOAT(name, fn) \ + F2D_EXPORT void name(int32_t i1, uint32_t wg, float v) { fn(f2d_joint(i1, wg), v); } +#define F2D_JOINT_GET_INT(name, fn) \ + F2D_EXPORT int name(int32_t i1, uint32_t wg) { return (int)fn(f2d_joint(i1, wg)); } +#define F2D_JOINT_SET_BOOL(name, fn) \ + F2D_EXPORT void name(int32_t i1, uint32_t wg, int v) { fn(f2d_joint(i1, wg), v); } +#define F2D_JOINT_SET_RANGE(name, fn) \ + F2D_EXPORT void name(int32_t i1, uint32_t wg, float a, float b) { \ + fn(f2d_joint(i1, wg), a, b); \ + } + +F2D_JOINT_GET_FLOAT(f2d_distance_joint_get_length, b2DistanceJoint_GetLength) +F2D_JOINT_SET_FLOAT(f2d_distance_joint_set_length, b2DistanceJoint_SetLength) +F2D_JOINT_GET_FLOAT(f2d_distance_joint_get_current_length, + b2DistanceJoint_GetCurrentLength) +F2D_JOINT_GET_INT(f2d_distance_joint_is_spring_enabled, + b2DistanceJoint_IsSpringEnabled) +F2D_JOINT_SET_BOOL(f2d_distance_joint_enable_spring, + b2DistanceJoint_EnableSpring) +F2D_JOINT_GET_FLOAT(f2d_distance_joint_get_spring_hertz, + b2DistanceJoint_GetSpringHertz) +F2D_JOINT_SET_FLOAT(f2d_distance_joint_set_spring_hertz, + b2DistanceJoint_SetSpringHertz) +F2D_JOINT_GET_FLOAT(f2d_distance_joint_get_spring_damping_ratio, + b2DistanceJoint_GetSpringDampingRatio) +F2D_JOINT_SET_FLOAT(f2d_distance_joint_set_spring_damping_ratio, + b2DistanceJoint_SetSpringDampingRatio) +F2D_JOINT_GET_INT(f2d_distance_joint_is_limit_enabled, + b2DistanceJoint_IsLimitEnabled) +F2D_JOINT_SET_BOOL(f2d_distance_joint_enable_limit, + b2DistanceJoint_EnableLimit) +F2D_JOINT_GET_FLOAT(f2d_distance_joint_get_min_length, + b2DistanceJoint_GetMinLength) +F2D_JOINT_GET_FLOAT(f2d_distance_joint_get_max_length, + b2DistanceJoint_GetMaxLength) +F2D_JOINT_SET_RANGE(f2d_distance_joint_set_length_range, + b2DistanceJoint_SetLengthRange) +F2D_JOINT_GET_INT(f2d_distance_joint_is_motor_enabled, + b2DistanceJoint_IsMotorEnabled) +F2D_JOINT_SET_BOOL(f2d_distance_joint_enable_motor, + b2DistanceJoint_EnableMotor) +F2D_JOINT_GET_FLOAT(f2d_distance_joint_get_motor_speed, + b2DistanceJoint_GetMotorSpeed) +F2D_JOINT_SET_FLOAT(f2d_distance_joint_set_motor_speed, + b2DistanceJoint_SetMotorSpeed) +F2D_JOINT_GET_FLOAT(f2d_distance_joint_get_max_motor_force, + b2DistanceJoint_GetMaxMotorForce) +F2D_JOINT_SET_FLOAT(f2d_distance_joint_set_max_motor_force, + b2DistanceJoint_SetMaxMotorForce) +F2D_JOINT_GET_FLOAT(f2d_distance_joint_get_motor_force, + b2DistanceJoint_GetMotorForce) + +F2D_EXPORT void f2d_motor_joint_get_linear_offset(int32_t i1, uint32_t wg, + float* out) { + b2Vec2 offset = b2MotorJoint_GetLinearOffset(f2d_joint(i1, wg)); + out[0] = offset.x; + out[1] = offset.y; +} + +F2D_EXPORT void f2d_motor_joint_set_linear_offset(int32_t i1, uint32_t wg, + float x, float y) { + b2MotorJoint_SetLinearOffset(f2d_joint(i1, wg), (b2Vec2){x, y}); +} + +F2D_JOINT_GET_FLOAT(f2d_motor_joint_get_angular_offset, + b2MotorJoint_GetAngularOffset) +F2D_JOINT_SET_FLOAT(f2d_motor_joint_set_angular_offset, + b2MotorJoint_SetAngularOffset) +F2D_JOINT_GET_FLOAT(f2d_motor_joint_get_max_force, b2MotorJoint_GetMaxForce) +F2D_JOINT_SET_FLOAT(f2d_motor_joint_set_max_force, b2MotorJoint_SetMaxForce) +F2D_JOINT_GET_FLOAT(f2d_motor_joint_get_max_torque, b2MotorJoint_GetMaxTorque) +F2D_JOINT_SET_FLOAT(f2d_motor_joint_set_max_torque, b2MotorJoint_SetMaxTorque) +F2D_JOINT_GET_FLOAT(f2d_motor_joint_get_correction_factor, + b2MotorJoint_GetCorrectionFactor) +F2D_JOINT_SET_FLOAT(f2d_motor_joint_set_correction_factor, + b2MotorJoint_SetCorrectionFactor) + +F2D_EXPORT void f2d_mouse_joint_get_target(int32_t i1, uint32_t wg, + float* out) { + b2Vec2 target = b2MouseJoint_GetTarget(f2d_joint(i1, wg)); + out[0] = target.x; + out[1] = target.y; +} + +F2D_EXPORT void f2d_mouse_joint_set_target(int32_t i1, uint32_t wg, float x, + float y) { + b2MouseJoint_SetTarget(f2d_joint(i1, wg), (b2Vec2){x, y}); +} + +F2D_JOINT_GET_FLOAT(f2d_mouse_joint_get_spring_hertz, + b2MouseJoint_GetSpringHertz) +F2D_JOINT_SET_FLOAT(f2d_mouse_joint_set_spring_hertz, + b2MouseJoint_SetSpringHertz) +F2D_JOINT_GET_FLOAT(f2d_mouse_joint_get_spring_damping_ratio, + b2MouseJoint_GetSpringDampingRatio) +F2D_JOINT_SET_FLOAT(f2d_mouse_joint_set_spring_damping_ratio, + b2MouseJoint_SetSpringDampingRatio) +F2D_JOINT_GET_FLOAT(f2d_mouse_joint_get_max_force, b2MouseJoint_GetMaxForce) +F2D_JOINT_SET_FLOAT(f2d_mouse_joint_set_max_force, b2MouseJoint_SetMaxForce) + +F2D_JOINT_GET_INT(f2d_prismatic_joint_is_spring_enabled, + b2PrismaticJoint_IsSpringEnabled) +F2D_JOINT_SET_BOOL(f2d_prismatic_joint_enable_spring, + b2PrismaticJoint_EnableSpring) +F2D_JOINT_GET_FLOAT(f2d_prismatic_joint_get_spring_hertz, + b2PrismaticJoint_GetSpringHertz) +F2D_JOINT_SET_FLOAT(f2d_prismatic_joint_set_spring_hertz, + b2PrismaticJoint_SetSpringHertz) +F2D_JOINT_GET_FLOAT(f2d_prismatic_joint_get_spring_damping_ratio, + b2PrismaticJoint_GetSpringDampingRatio) +F2D_JOINT_SET_FLOAT(f2d_prismatic_joint_set_spring_damping_ratio, + b2PrismaticJoint_SetSpringDampingRatio) +F2D_JOINT_GET_FLOAT(f2d_prismatic_joint_get_target_translation, + b2PrismaticJoint_GetTargetTranslation) +F2D_JOINT_SET_FLOAT(f2d_prismatic_joint_set_target_translation, + b2PrismaticJoint_SetTargetTranslation) +F2D_JOINT_GET_INT(f2d_prismatic_joint_is_limit_enabled, + b2PrismaticJoint_IsLimitEnabled) +F2D_JOINT_SET_BOOL(f2d_prismatic_joint_enable_limit, + b2PrismaticJoint_EnableLimit) +F2D_JOINT_GET_FLOAT(f2d_prismatic_joint_get_lower_limit, + b2PrismaticJoint_GetLowerLimit) +F2D_JOINT_GET_FLOAT(f2d_prismatic_joint_get_upper_limit, + b2PrismaticJoint_GetUpperLimit) +F2D_JOINT_SET_RANGE(f2d_prismatic_joint_set_limits, b2PrismaticJoint_SetLimits) +F2D_JOINT_GET_INT(f2d_prismatic_joint_is_motor_enabled, + b2PrismaticJoint_IsMotorEnabled) +F2D_JOINT_SET_BOOL(f2d_prismatic_joint_enable_motor, + b2PrismaticJoint_EnableMotor) +F2D_JOINT_GET_FLOAT(f2d_prismatic_joint_get_motor_speed, + b2PrismaticJoint_GetMotorSpeed) +F2D_JOINT_SET_FLOAT(f2d_prismatic_joint_set_motor_speed, + b2PrismaticJoint_SetMotorSpeed) +F2D_JOINT_GET_FLOAT(f2d_prismatic_joint_get_max_motor_force, + b2PrismaticJoint_GetMaxMotorForce) +F2D_JOINT_SET_FLOAT(f2d_prismatic_joint_set_max_motor_force, + b2PrismaticJoint_SetMaxMotorForce) +F2D_JOINT_GET_FLOAT(f2d_prismatic_joint_get_motor_force, + b2PrismaticJoint_GetMotorForce) +F2D_JOINT_GET_FLOAT(f2d_prismatic_joint_get_translation, + b2PrismaticJoint_GetTranslation) +F2D_JOINT_GET_FLOAT(f2d_prismatic_joint_get_speed, b2PrismaticJoint_GetSpeed) + +F2D_JOINT_GET_INT(f2d_revolute_joint_is_spring_enabled, + b2RevoluteJoint_IsSpringEnabled) +F2D_JOINT_SET_BOOL(f2d_revolute_joint_enable_spring, + b2RevoluteJoint_EnableSpring) +F2D_JOINT_GET_FLOAT(f2d_revolute_joint_get_spring_hertz, + b2RevoluteJoint_GetSpringHertz) +F2D_JOINT_SET_FLOAT(f2d_revolute_joint_set_spring_hertz, + b2RevoluteJoint_SetSpringHertz) +F2D_JOINT_GET_FLOAT(f2d_revolute_joint_get_spring_damping_ratio, + b2RevoluteJoint_GetSpringDampingRatio) +F2D_JOINT_SET_FLOAT(f2d_revolute_joint_set_spring_damping_ratio, + b2RevoluteJoint_SetSpringDampingRatio) +F2D_JOINT_GET_FLOAT(f2d_revolute_joint_get_target_angle, + b2RevoluteJoint_GetTargetAngle) +F2D_JOINT_SET_FLOAT(f2d_revolute_joint_set_target_angle, + b2RevoluteJoint_SetTargetAngle) +F2D_JOINT_GET_FLOAT(f2d_revolute_joint_get_angle, b2RevoluteJoint_GetAngle) +F2D_JOINT_GET_INT(f2d_revolute_joint_is_limit_enabled, + b2RevoluteJoint_IsLimitEnabled) +F2D_JOINT_SET_BOOL(f2d_revolute_joint_enable_limit, + b2RevoluteJoint_EnableLimit) +F2D_JOINT_GET_FLOAT(f2d_revolute_joint_get_lower_limit, + b2RevoluteJoint_GetLowerLimit) +F2D_JOINT_GET_FLOAT(f2d_revolute_joint_get_upper_limit, + b2RevoluteJoint_GetUpperLimit) +F2D_JOINT_SET_RANGE(f2d_revolute_joint_set_limits, b2RevoluteJoint_SetLimits) +F2D_JOINT_GET_INT(f2d_revolute_joint_is_motor_enabled, + b2RevoluteJoint_IsMotorEnabled) +F2D_JOINT_SET_BOOL(f2d_revolute_joint_enable_motor, + b2RevoluteJoint_EnableMotor) +F2D_JOINT_GET_FLOAT(f2d_revolute_joint_get_motor_speed, + b2RevoluteJoint_GetMotorSpeed) +F2D_JOINT_SET_FLOAT(f2d_revolute_joint_set_motor_speed, + b2RevoluteJoint_SetMotorSpeed) +F2D_JOINT_GET_FLOAT(f2d_revolute_joint_get_max_motor_torque, + b2RevoluteJoint_GetMaxMotorTorque) +F2D_JOINT_SET_FLOAT(f2d_revolute_joint_set_max_motor_torque, + b2RevoluteJoint_SetMaxMotorTorque) +F2D_JOINT_GET_FLOAT(f2d_revolute_joint_get_motor_torque, + b2RevoluteJoint_GetMotorTorque) + +F2D_JOINT_GET_FLOAT(f2d_weld_joint_get_linear_hertz, b2WeldJoint_GetLinearHertz) +F2D_JOINT_SET_FLOAT(f2d_weld_joint_set_linear_hertz, b2WeldJoint_SetLinearHertz) +F2D_JOINT_GET_FLOAT(f2d_weld_joint_get_angular_hertz, + b2WeldJoint_GetAngularHertz) +F2D_JOINT_SET_FLOAT(f2d_weld_joint_set_angular_hertz, + b2WeldJoint_SetAngularHertz) +F2D_JOINT_GET_FLOAT(f2d_weld_joint_get_linear_damping_ratio, + b2WeldJoint_GetLinearDampingRatio) +F2D_JOINT_SET_FLOAT(f2d_weld_joint_set_linear_damping_ratio, + b2WeldJoint_SetLinearDampingRatio) +F2D_JOINT_GET_FLOAT(f2d_weld_joint_get_angular_damping_ratio, + b2WeldJoint_GetAngularDampingRatio) +F2D_JOINT_SET_FLOAT(f2d_weld_joint_set_angular_damping_ratio, + b2WeldJoint_SetAngularDampingRatio) + +F2D_JOINT_GET_INT(f2d_wheel_joint_is_spring_enabled, + b2WheelJoint_IsSpringEnabled) +F2D_JOINT_SET_BOOL(f2d_wheel_joint_enable_spring, b2WheelJoint_EnableSpring) +F2D_JOINT_GET_FLOAT(f2d_wheel_joint_get_spring_hertz, + b2WheelJoint_GetSpringHertz) +F2D_JOINT_SET_FLOAT(f2d_wheel_joint_set_spring_hertz, + b2WheelJoint_SetSpringHertz) +F2D_JOINT_GET_FLOAT(f2d_wheel_joint_get_spring_damping_ratio, + b2WheelJoint_GetSpringDampingRatio) +F2D_JOINT_SET_FLOAT(f2d_wheel_joint_set_spring_damping_ratio, + b2WheelJoint_SetSpringDampingRatio) +F2D_JOINT_GET_INT(f2d_wheel_joint_is_limit_enabled, + b2WheelJoint_IsLimitEnabled) +F2D_JOINT_SET_BOOL(f2d_wheel_joint_enable_limit, b2WheelJoint_EnableLimit) +F2D_JOINT_GET_FLOAT(f2d_wheel_joint_get_lower_limit, + b2WheelJoint_GetLowerLimit) +F2D_JOINT_GET_FLOAT(f2d_wheel_joint_get_upper_limit, + b2WheelJoint_GetUpperLimit) +F2D_JOINT_SET_RANGE(f2d_wheel_joint_set_limits, b2WheelJoint_SetLimits) +F2D_JOINT_GET_INT(f2d_wheel_joint_is_motor_enabled, + b2WheelJoint_IsMotorEnabled) +F2D_JOINT_SET_BOOL(f2d_wheel_joint_enable_motor, b2WheelJoint_EnableMotor) +F2D_JOINT_GET_FLOAT(f2d_wheel_joint_get_motor_speed, + b2WheelJoint_GetMotorSpeed) +F2D_JOINT_SET_FLOAT(f2d_wheel_joint_set_motor_speed, + b2WheelJoint_SetMotorSpeed) +F2D_JOINT_GET_FLOAT(f2d_wheel_joint_get_max_motor_torque, + b2WheelJoint_GetMaxMotorTorque) +F2D_JOINT_SET_FLOAT(f2d_wheel_joint_set_max_motor_torque, + b2WheelJoint_SetMaxMotorTorque) +F2D_JOINT_GET_FLOAT(f2d_wheel_joint_get_motor_torque, + b2WheelJoint_GetMotorTorque) + +// Events. Counts are queried first; the fill functions then write float64 +// records into a caller-provided buffer and return the record count. +// +// Strides (in float64 elements): +// - begin contact: 13 [aI1, aWg, bI1, bWg, nx, ny, pointCount, +// p0x, p0y, sep0, p1x, p1y, sep1] +// - end contact: 4 [aI1, aWg, bI1, bWg] +// - hit contact: 9 [aI1, aWg, bI1, bWg, px, py, nx, ny, approachSpeed] +// - sensor: 4 [sensorI1, sensorWg, visitorI1, visitorWg] +// - body move: 7 [i1, wg, x, y, qc, qs, fellAsleep] + +F2D_EXPORT void f2d_world_get_contact_event_counts(uint32_t w, int32_t* out) { + b2ContactEvents events = b2World_GetContactEvents(f2d_world(w)); + out[0] = events.beginCount; + out[1] = events.endCount; + out[2] = events.hitCount; +} + +F2D_EXPORT int f2d_world_get_contact_begin_events(uint32_t w, double* out) { + b2ContactEvents events = b2World_GetContactEvents(f2d_world(w)); + for (int i = 0; i < events.beginCount; ++i) { + const b2ContactBeginTouchEvent* event = events.beginEvents + i; + double* record = out + i * 13; + record[0] = event->shapeIdA.index1; + record[1] = f2d_wg_shape(event->shapeIdA); + record[2] = event->shapeIdB.index1; + record[3] = f2d_wg_shape(event->shapeIdB); + record[4] = event->manifold.normal.x; + record[5] = event->manifold.normal.y; + record[6] = event->manifold.pointCount; + for (int p = 0; p < 2; ++p) { + record[7 + 3 * p] = event->manifold.points[p].point.x; + record[8 + 3 * p] = event->manifold.points[p].point.y; + record[9 + 3 * p] = event->manifold.points[p].separation; + } + } + return events.beginCount; +} + +F2D_EXPORT int f2d_world_get_contact_end_events(uint32_t w, double* out) { + b2ContactEvents events = b2World_GetContactEvents(f2d_world(w)); + for (int i = 0; i < events.endCount; ++i) { + const b2ContactEndTouchEvent* event = events.endEvents + i; + double* record = out + i * 4; + record[0] = event->shapeIdA.index1; + record[1] = f2d_wg_shape(event->shapeIdA); + record[2] = event->shapeIdB.index1; + record[3] = f2d_wg_shape(event->shapeIdB); + } + return events.endCount; +} + +F2D_EXPORT int f2d_world_get_contact_hit_events(uint32_t w, double* out) { + b2ContactEvents events = b2World_GetContactEvents(f2d_world(w)); + for (int i = 0; i < events.hitCount; ++i) { + const b2ContactHitEvent* event = events.hitEvents + i; + double* record = out + i * 9; + record[0] = event->shapeIdA.index1; + record[1] = f2d_wg_shape(event->shapeIdA); + record[2] = event->shapeIdB.index1; + record[3] = f2d_wg_shape(event->shapeIdB); + record[4] = event->point.x; + record[5] = event->point.y; + record[6] = event->normal.x; + record[7] = event->normal.y; + record[8] = event->approachSpeed; + } + return events.hitCount; +} + +F2D_EXPORT void f2d_world_get_sensor_event_counts(uint32_t w, int32_t* out) { + b2SensorEvents events = b2World_GetSensorEvents(f2d_world(w)); + out[0] = events.beginCount; + out[1] = events.endCount; +} + +F2D_EXPORT int f2d_world_get_sensor_begin_events(uint32_t w, double* out) { + b2SensorEvents events = b2World_GetSensorEvents(f2d_world(w)); + for (int i = 0; i < events.beginCount; ++i) { + const b2SensorBeginTouchEvent* event = events.beginEvents + i; + double* record = out + i * 4; + record[0] = event->sensorShapeId.index1; + record[1] = f2d_wg_shape(event->sensorShapeId); + record[2] = event->visitorShapeId.index1; + record[3] = f2d_wg_shape(event->visitorShapeId); + } + return events.beginCount; +} + +F2D_EXPORT int f2d_world_get_sensor_end_events(uint32_t w, double* out) { + b2SensorEvents events = b2World_GetSensorEvents(f2d_world(w)); + for (int i = 0; i < events.endCount; ++i) { + const b2SensorEndTouchEvent* event = events.endEvents + i; + double* record = out + i * 4; + record[0] = event->sensorShapeId.index1; + record[1] = f2d_wg_shape(event->sensorShapeId); + record[2] = event->visitorShapeId.index1; + record[3] = f2d_wg_shape(event->visitorShapeId); + } + return events.endCount; +} + +F2D_EXPORT int f2d_world_get_body_event_count(uint32_t w) { + return b2World_GetBodyEvents(f2d_world(w)).moveCount; +} + +F2D_EXPORT int f2d_world_get_body_events(uint32_t w, double* out) { + b2BodyEvents events = b2World_GetBodyEvents(f2d_world(w)); + for (int i = 0; i < events.moveCount; ++i) { + const b2BodyMoveEvent* event = events.moveEvents + i; + double* record = out + i * 7; + record[0] = event->bodyId.index1; + record[1] = f2d_wg_body(event->bodyId); + record[2] = event->transform.p.x; + record[3] = event->transform.p.y; + record[4] = event->transform.q.c; + record[5] = event->transform.q.s; + record[6] = event->fellAsleep; + } + return events.moveCount; +} + +// Queries. + +// Returns whether something was hit; the hit is written as +// [i1, wg, px, py, nx, ny, fraction] float32s with ids stored via the +// int32 view of the same buffer slots 0 and 1. +F2D_EXPORT int f2d_world_cast_ray_closest(uint32_t w, float ox, float oy, + float tx, float ty, + uint32_t category_lo, + uint32_t category_hi, + uint32_t mask_lo, uint32_t mask_hi, + double* out) { + b2RayResult result = b2World_CastRayClosest( + f2d_world(w), (b2Vec2){ox, oy}, (b2Vec2){tx, ty}, + f2d_query_filter(category_lo, category_hi, mask_lo, mask_hi)); + if (!result.hit) { + return 0; + } + out[0] = result.shapeId.index1; + out[1] = f2d_wg_shape(result.shapeId); + out[2] = result.point.x; + out[3] = result.point.y; + out[4] = result.normal.x; + out[5] = result.normal.y; + out[6] = result.fraction; + return 1; +} + +static float f2d_cast_ray_trampoline(b2ShapeId shape, b2Vec2 point, + b2Vec2 normal, float fraction, + void* context) { + (void)context; + return f2d_host_cast_ray(shape.index1, f2d_wg_shape(shape), point.x, point.y, + normal.x, normal.y, fraction); +} + +F2D_EXPORT void f2d_world_cast_ray(uint32_t w, float ox, float oy, float tx, + float ty, uint32_t category_lo, + uint32_t category_hi, uint32_t mask_lo, + uint32_t mask_hi) { + b2World_CastRay(f2d_world(w), (b2Vec2){ox, oy}, (b2Vec2){tx, ty}, + f2d_query_filter(category_lo, category_hi, mask_lo, mask_hi), + f2d_cast_ray_trampoline, NULL); +} + +static bool f2d_overlap_trampoline(b2ShapeId shape, void* context) { + (void)context; + return f2d_host_overlap(shape.index1, f2d_wg_shape(shape)); +} + +F2D_EXPORT void f2d_world_overlap_aabb(uint32_t w, float lx, float ly, + float ux, float uy, + uint32_t category_lo, + uint32_t category_hi, uint32_t mask_lo, + uint32_t mask_hi) { + b2AABB aabb = {{lx, ly}, {ux, uy}}; + b2World_OverlapAABB( + f2d_world(w), aabb, + f2d_query_filter(category_lo, category_hi, mask_lo, mask_hi), + f2d_overlap_trampoline, NULL); +} + +// Simulation callbacks. + +static bool f2d_custom_filter_trampoline(b2ShapeId a, b2ShapeId b, + void* context) { + (void)context; + return f2d_host_custom_filter(a.index1, f2d_wg_shape(a), b.index1, + f2d_wg_shape(b)); +} + +F2D_EXPORT void f2d_world_set_custom_filter(uint32_t w, int enabled) { + b2World_SetCustomFilterCallback( + f2d_world(w), enabled ? f2d_custom_filter_trampoline : NULL, NULL); +} + +static bool f2d_pre_solve_trampoline(b2ShapeId a, b2ShapeId b, + b2Manifold* manifold, void* context) { + (void)context; + return f2d_host_pre_solve(a.index1, f2d_wg_shape(a), b.index1, + f2d_wg_shape(b), manifold->normal.x, + manifold->normal.y); +} + +F2D_EXPORT void f2d_world_set_pre_solve(uint32_t w, int enabled) { + b2World_SetPreSolveCallback(f2d_world(w), + enabled ? f2d_pre_solve_trampoline : NULL, NULL); +} + +// Debug drawing. + +static void f2d_draw_polygon(const b2Vec2* vertices, int count, + b2HexColor color, void* context) { + (void)context; + f2d_host_draw_polygon((const float*)vertices, count, (uint32_t)color); +} + +static void f2d_draw_solid_polygon(b2Transform transform, + const b2Vec2* vertices, int count, + float radius, b2HexColor color, + void* context) { + (void)context; + f2d_host_draw_solid_polygon(transform.p.x, transform.p.y, transform.q.c, + transform.q.s, (const float*)vertices, count, + radius, (uint32_t)color); +} + +static void f2d_draw_circle(b2Vec2 center, float radius, b2HexColor color, + void* context) { + (void)context; + f2d_host_draw_circle(center.x, center.y, radius, (uint32_t)color); +} + +static void f2d_draw_solid_circle(b2Transform transform, float radius, + b2HexColor color, void* context) { + (void)context; + f2d_host_draw_solid_circle(transform.p.x, transform.p.y, transform.q.c, + transform.q.s, radius, (uint32_t)color); +} + +static void f2d_draw_solid_capsule(b2Vec2 p1, b2Vec2 p2, float radius, + b2HexColor color, void* context) { + (void)context; + f2d_host_draw_solid_capsule(p1.x, p1.y, p2.x, p2.y, radius, + (uint32_t)color); +} + +static void f2d_draw_segment(b2Vec2 p1, b2Vec2 p2, b2HexColor color, + void* context) { + (void)context; + f2d_host_draw_segment(p1.x, p1.y, p2.x, p2.y, (uint32_t)color); +} + +static void f2d_draw_transform(b2Transform transform, void* context) { + (void)context; + f2d_host_draw_transform(transform.p.x, transform.p.y, transform.q.c, + transform.q.s); +} + +static void f2d_draw_point(b2Vec2 point, float size, b2HexColor color, + void* context) { + (void)context; + f2d_host_draw_point(point.x, point.y, size, (uint32_t)color); +} + +static void f2d_draw_string(b2Vec2 point, const char* text, b2HexColor color, + void* context) { + (void)context; + f2d_host_draw_string(point.x, point.y, text, (uint32_t)color); +} + +F2D_EXPORT void f2d_world_draw(uint32_t w, int use_bounds, float bounds_lx, + float bounds_ly, float bounds_ux, + float bounds_uy, int draw_shapes, + int draw_joints, int draw_joint_extras, + int draw_bounds, int draw_mass, + int draw_body_names, int draw_contacts, + int draw_graph_colors, + int draw_contact_normals, + int draw_contact_impulses, + int draw_contact_features, + int draw_friction_impulses, int draw_islands) { + b2DebugDraw draw = b2DefaultDebugDraw(); + draw.DrawPolygonFcn = f2d_draw_polygon; + draw.DrawSolidPolygonFcn = f2d_draw_solid_polygon; + draw.DrawCircleFcn = f2d_draw_circle; + draw.DrawSolidCircleFcn = f2d_draw_solid_circle; + draw.DrawSolidCapsuleFcn = f2d_draw_solid_capsule; + draw.DrawSegmentFcn = f2d_draw_segment; + draw.DrawTransformFcn = f2d_draw_transform; + draw.DrawPointFcn = f2d_draw_point; + draw.DrawStringFcn = f2d_draw_string; + draw.useDrawingBounds = use_bounds; + draw.drawingBounds = (b2AABB){{bounds_lx, bounds_ly}, {bounds_ux, bounds_uy}}; + draw.drawShapes = draw_shapes; + draw.drawJoints = draw_joints; + draw.drawJointExtras = draw_joint_extras; + draw.drawBounds = draw_bounds; + draw.drawMass = draw_mass; + draw.drawBodyNames = draw_body_names; + draw.drawContacts = draw_contacts; + draw.drawGraphColors = draw_graph_colors; + draw.drawContactNormals = draw_contact_normals; + draw.drawContactImpulses = draw_contact_impulses; + draw.drawContactFeatures = draw_contact_features; + draw.drawFrictionImpulses = draw_friction_impulses; + draw.drawIslands = draw_islands; + b2World_Draw(f2d_world(w), &draw); +} diff --git a/packages/forge2d/test/api/body_test.dart b/packages/forge2d/test/api/body_test.dart index b46718d4..9c056f67 100644 --- a/packages/forge2d/test/api/body_test.dart +++ b/packages/forge2d/test/api/body_test.dart @@ -1,6 +1,3 @@ -@TestOn('vm') -library; - import 'dart:math' as math; import 'package:forge2d/forge2d.dart'; diff --git a/packages/forge2d/test/api/debug_draw_test.dart b/packages/forge2d/test/api/debug_draw_test.dart index a72d37db..fc5bfa04 100644 --- a/packages/forge2d/test/api/debug_draw_test.dart +++ b/packages/forge2d/test/api/debug_draw_test.dart @@ -1,6 +1,3 @@ -@TestOn('vm') -library; - import 'package:forge2d/forge2d.dart'; import 'package:test/test.dart'; diff --git a/packages/forge2d/test/api/events_test.dart b/packages/forge2d/test/api/events_test.dart index 385d7d26..49964037 100644 --- a/packages/forge2d/test/api/events_test.dart +++ b/packages/forge2d/test/api/events_test.dart @@ -1,6 +1,3 @@ -@TestOn('vm') -library; - import 'package:forge2d/forge2d.dart'; import 'package:test/test.dart'; diff --git a/packages/forge2d/test/api/joints_test.dart b/packages/forge2d/test/api/joints_test.dart index 5472efee..28d79d69 100644 --- a/packages/forge2d/test/api/joints_test.dart +++ b/packages/forge2d/test/api/joints_test.dart @@ -1,6 +1,3 @@ -@TestOn('vm') -library; - import 'dart:math' as math; import 'package:forge2d/forge2d.dart'; diff --git a/packages/forge2d/test/api/shape_test.dart b/packages/forge2d/test/api/shape_test.dart index d6040f3d..e2718ac0 100644 --- a/packages/forge2d/test/api/shape_test.dart +++ b/packages/forge2d/test/api/shape_test.dart @@ -1,6 +1,3 @@ -@TestOn('vm') -library; - import 'package:forge2d/forge2d.dart'; import 'package:test/test.dart'; @@ -166,7 +163,12 @@ void main() { test('friction and restitution apply to all segments', () { final chain = body.createChain( ChainDef( - points: [Vector2(-5, 0), Vector2(0, 0), Vector2(5, 0)], + points: [ + Vector2(-6, 0), + Vector2(-2, 0), + Vector2(2, 0), + Vector2(6, 0), + ], materials: [SurfaceMaterial(friction: 0.3, restitution: 0.2)], ), )..friction = 0.8; @@ -178,7 +180,14 @@ void main() { test('destroy removes the chain and its segments', () { final chain = body.createChain( - ChainDef(points: [Vector2(-5, 0), Vector2(0, 0), Vector2(5, 0)]), + ChainDef( + points: [ + Vector2(-6, 0), + Vector2(-2, 0), + Vector2(2, 0), + Vector2(6, 0), + ], + ), )..destroy(); expect(chain.isValid, isFalse); }); diff --git a/packages/forge2d/test/api/world_test.dart b/packages/forge2d/test/api/world_test.dart index b1c5cd9e..fe268edd 100644 --- a/packages/forge2d/test/api/world_test.dart +++ b/packages/forge2d/test/api/world_test.dart @@ -1,6 +1,3 @@ -@TestOn('vm') -library; - import 'package:forge2d/forge2d.dart'; import 'package:test/test.dart'; diff --git a/packages/forge2d/test/backend/world_backend_test.dart b/packages/forge2d/test/backend/world_backend_test.dart index b95cafc0..502daf51 100644 --- a/packages/forge2d/test/backend/world_backend_test.dart +++ b/packages/forge2d/test/backend/world_backend_test.dart @@ -1,6 +1,3 @@ -@TestOn('vm') -library; - import 'package:forge2d/forge2d.dart'; import 'package:forge2d/src/initialize.dart'; import 'package:test/test.dart'; diff --git a/packages/forge2d/tool/build_wasm.sh b/packages/forge2d/tool/build_wasm.sh new file mode 100755 index 00000000..da5436cc --- /dev/null +++ b/packages/forge2d/tool/build_wasm.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +# Builds the Box2D WebAssembly module used by the web backend. +# +# Requires an activated emsdk (https://github.com/emscripten-core/emsdk); +# CI pins the version, see .github/workflows/build-wasm.yml. +# +# Usage: tool/build_wasm.sh +set -euo pipefail + +PACKAGE_DIR="$(cd "$(dirname "$0")/.." && pwd)" +BOX2D="$PACKAGE_DIR/third_party/box2d" +OUT="$PACKAGE_DIR/lib/src/backend/wasm/box2d.wasm" + +emcc \ + -O3 \ + -std=gnu17 \ + -DNDEBUG \ + -msimd128 \ + -msse2 \ + --no-entry \ + -sSTANDALONE_WASM=1 \ + -sALLOW_MEMORY_GROWTH=1 \ + -sINITIAL_MEMORY=16MB \ + -sFILESYSTEM=0 \ + -sEXPORTED_FUNCTIONS=_malloc,_free \ + -sERROR_ON_UNDEFINED_SYMBOLS=0 \ + -I"$BOX2D/include" \ + -I"$BOX2D/src" \ + "$BOX2D"/src/*.c \ + "$PACKAGE_DIR/native/wasm/f2d_shim.c" \ + -o "$OUT" + +ls -la "$OUT" From 0a197d455b3c0c97fd33d84ca1d32c12827fffd6 Mon Sep 17 00:00:00 2001 From: Lukas Klingsbo Date: Sun, 19 Jul 2026 23:47:15 +0200 Subject: [PATCH 2/5] fix: Bundle the wasm module into Flutter apps automatically Flutter web apps needed a manual setup step to host box2d.wasm. Declaring the module as a package asset makes Flutter bundle it into every consuming app, and the loader now checks the bundled asset path, so the same zero setup applies to Dart web and Flutter web alike. Verified with a real Flutter web app depending on forge2d: it builds, bundles the module, and runs a simulation with no configuration. Also loosens the build hook dependency minimums (hooks ^2.0.0, native_toolchain_c ^0.19.0): the newest patch releases require meta 1.19 while the Flutter SDK pins meta 1.18, which made forge2d unresolvable in Flutter apps altogether. --- README.md | 9 +++++---- packages/forge2d/lib/src/backend/raw_box2d_wasm.dart | 4 ++++ packages/forge2d/lib/src/initialize.dart | 6 +++--- packages/forge2d/pubspec.yaml | 10 ++++++++-- 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index fddac7ff..0e758389 100644 --- a/README.md +++ b/README.md @@ -34,10 +34,11 @@ You can use it independently in Dart or in your - Supported platforms: Android, iOS, macOS, Windows, Linux, and the web. On the web the same API runs against a bundled WebAssembly build of Box2D -(about 300 KB). Dart web apps need no setup: `initializeForge2D()` finds -the module at the package asset path. Flutter web apps copy it next to the -page once with `dart run forge2d:setup_web`, or pass a custom location via -`initializeForge2D(wasmUri: ...)`. +(about 220 KB). No setup is needed: `initializeForge2D()` finds the module +at the package asset path in Dart web apps, and at the bundled package +asset in Flutter web apps. For custom hosting setups the location can be +overridden with `initializeForge2D(wasmUri: ...)`, and +`dart run forge2d:setup_web` copies the module into a `web/` directory. ## Getting started diff --git a/packages/forge2d/lib/src/backend/raw_box2d_wasm.dart b/packages/forge2d/lib/src/backend/raw_box2d_wasm.dart index 0b5f1204..a08e470e 100644 --- a/packages/forge2d/lib/src/backend/raw_box2d_wasm.dart +++ b/packages/forge2d/lib/src/backend/raw_box2d_wasm.dart @@ -16,8 +16,12 @@ Future initializeBackend({Uri? wasmUri}) async { if (wasmUri != null) wasmUri else ...[ + // Dart web tooling serves package files directly. Uri.parse('packages/forge2d/src/backend/wasm/box2d.wasm'), Uri.parse('/packages/forge2d/src/backend/wasm/box2d.wasm'), + // Flutter web bundles the module as a package asset automatically. + Uri.parse('assets/packages/forge2d/lib/src/backend/wasm/box2d.wasm'), + // Last resort: a manually hosted copy next to the page. Uri.parse('box2d.wasm'), ], ]); diff --git a/packages/forge2d/lib/src/initialize.dart b/packages/forge2d/lib/src/initialize.dart index 16c949ea..19e1a6a6 100644 --- a/packages/forge2d/lib/src/initialize.dart +++ b/packages/forge2d/lib/src/initialize.dart @@ -14,9 +14,9 @@ RawBox2D get rawBox2D => _rawBox2D ??= createRawBox2D(); /// creating any world is mandatory. /// /// On the web the module is looked up at the package asset path served by -/// the Dart web tooling, then at `box2d.wasm` relative to the page (copy -/// the file there in Flutter web apps with `dart run forge2d:setup_web`). -/// [wasmUri] overrides the lookup. +/// the Dart web tooling, at the package asset bundled into Flutter web +/// apps, and finally at `box2d.wasm` relative to the page. [wasmUri] +/// overrides the lookup for custom hosting setups. /// /// Cross-platform code should always call and await this first. Future initializeForge2D({Uri? wasmUri}) async { diff --git a/packages/forge2d/pubspec.yaml b/packages/forge2d/pubspec.yaml index 866b42d3..897973e5 100644 --- a/packages/forge2d/pubspec.yaml +++ b/packages/forge2d/pubspec.yaml @@ -10,13 +10,19 @@ environment: dependencies: code_assets: ^1.2.1 ffi: ^2.1.4 - hooks: ^2.1.0 + hooks: ^2.0.0 logging: ^1.3.0 meta: ^1.16.0 - native_toolchain_c: ^0.19.3 + native_toolchain_c: ^0.19.0 vector_math: ^2.1.4 dev_dependencies: dartdoc: ^8.3.4 flame_lint: ^1.4.1 test: ^1.26.3 + +# Bundles the WebAssembly module as an asset in Flutter apps, so Flutter +# web needs no setup step. Non-Flutter tooling ignores this section. +flutter: + assets: + - lib/src/backend/wasm/box2d.wasm From cf8cd47b78bb85c7812af352f8ebd6705bca2d77 Mon Sep 17 00:00:00 2001 From: Lukas Klingsbo Date: Sun, 19 Jul 2026 23:55:21 +0200 Subject: [PATCH 3/5] feat: Add geometry read-back to Shape Shape.geometry returns the sealed ShapeGeometry read from the simulation: circles, capsules, segments, polygons with their convex hull vertices and rounding radius, and chain segments as the segment they collide with. Renderers like flame_forge2d's BodyComponent need this to draw fixtures. Implemented across both backends (new f2d shim exports for the wasm side) and covered by a round-trip test for every kind on vm, dart2js, and dart2wasm. Also finishes the abbreviation cleanup in the wasm backend and the C shim, which the earlier rename missed because positional parameter names are not checked against the interface. --- packages/forge2d/lib/src/api/shape.dart | 51 + .../forge2d/lib/src/backend/raw_box2d.dart | 32 + .../lib/src/backend/raw_box2d_ffi.dart | 71 + .../lib/src/backend/raw_box2d_wasm.dart | 1576 ++++++++++++----- .../forge2d/lib/src/backend/wasm/box2d.wasm | Bin 226900 -> 227788 bytes packages/forge2d/native/wasm/f2d_shim.c | 402 +++-- packages/forge2d/test/api/shape_test.dart | 59 + 7 files changed, 1573 insertions(+), 618 deletions(-) diff --git a/packages/forge2d/lib/src/api/shape.dart b/packages/forge2d/lib/src/api/shape.dart index 2cf70ba2..8812264d 100644 --- a/packages/forge2d/lib/src/api/shape.dart +++ b/packages/forge2d/lib/src/api/shape.dart @@ -1,6 +1,7 @@ import 'package:forge2d/src/api/body.dart'; import 'package:forge2d/src/api/defs.dart'; import 'package:forge2d/src/api/enums.dart'; +import 'package:forge2d/src/api/geometry.dart'; import 'package:forge2d/src/api/math.dart'; import 'package:forge2d/src/api/world.dart'; import 'package:forge2d/src/initialize.dart'; @@ -149,6 +150,56 @@ class Shape { bool testPoint(Vector2 point) => rawBox2D.shapeTestPoint(index1, worldAndGeneration, point.x, point.y); + /// The geometry of the shape in the body's local coordinates, read back + /// from the simulation. + /// + /// Polygons return their convex hull, so a [Polygon.box] comes back as a + /// four-vertex [Polygon]. Chain segments come back as the [Segment] they + /// collide with. + ShapeGeometry get geometry { + switch (type) { + case ShapeType.circle: + final (centerX, centerY, radius) = rawBox2D.shapeGetCircle( + index1, + worldAndGeneration, + ); + return Circle(center: Vector2(centerX, centerY), radius: radius); + case ShapeType.capsule: + final (center1X, center1Y, center2X, center2Y, radius) = rawBox2D + .shapeGetCapsule(index1, worldAndGeneration); + return Capsule( + center1: Vector2(center1X, center1Y), + center2: Vector2(center2X, center2Y), + radius: radius, + ); + case ShapeType.segment: + final (point1X, point1Y, point2X, point2Y) = rawBox2D.shapeGetSegment( + index1, + worldAndGeneration, + ); + return Segment( + point1: Vector2(point1X, point1Y), + point2: Vector2(point2X, point2Y), + ); + case ShapeType.chainSegment: + final (point1X, point1Y, point2X, point2Y) = rawBox2D + .shapeGetChainSegment(index1, worldAndGeneration); + return Segment( + point1: Vector2(point1X, point1Y), + point2: Vector2(point2X, point2Y), + ); + case ShapeType.polygon: + final polygon = rawBox2D.shapeGetPolygon(index1, worldAndGeneration); + return Polygon( + [ + for (var i = 0; i < polygon.points.length; i += 2) + Vector2(polygon.points[i], polygon.points[i + 1]), + ], + radius: polygon.radius, + ); + } + } + /// The current world axis-aligned bounding box of the shape. Aabb get aabb { final (lowerX, lowerY, upperX, upperY) = rawBox2D.shapeGetAabb( diff --git a/packages/forge2d/lib/src/backend/raw_box2d.dart b/packages/forge2d/lib/src/backend/raw_box2d.dart index 7c79ca81..2798f31d 100644 --- a/packages/forge2d/lib/src/backend/raw_box2d.dart +++ b/packages/forge2d/lib/src/backend/raw_box2d.dart @@ -487,6 +487,38 @@ abstract interface class RawBox2D { /// Whether the given world point is inside the shape. bool shapeTestPoint(int index1, int worldAndGeneration, double x, double y); + /// Returns the geometry of a circle shape as (centerX, centerY, radius). + (double, double, double) shapeGetCircle(int index1, int worldAndGeneration); + + /// Returns the geometry of a capsule shape as + /// (center1X, center1Y, center2X, center2Y, radius). + (double, double, double, double, double) shapeGetCapsule( + int index1, + int worldAndGeneration, + ); + + /// Returns the geometry of a segment shape as + /// (point1X, point1Y, point2X, point2Y). + (double, double, double, double) shapeGetSegment( + int index1, + int worldAndGeneration, + ); + + /// Returns the inner segment of a chain segment shape as + /// (point1X, point1Y, point2X, point2Y). + (double, double, double, double) shapeGetChainSegment( + int index1, + int worldAndGeneration, + ); + + /// Returns the geometry of a polygon shape: the convex hull vertices + /// flattened as `[x0, y0, x1, y1, ...]` in local coordinates, and the + /// rounding radius. + ({List points, double radius}) shapeGetPolygon( + int index1, + int worldAndGeneration, + ); + /// Returns the current world axis-aligned bounding box of the shape as /// (lowerX, lowerY, upperX, upperY). (double, double, double, double) shapeGetAabb( diff --git a/packages/forge2d/lib/src/backend/raw_box2d_ffi.dart b/packages/forge2d/lib/src/backend/raw_box2d_ffi.dart index 5c0ef158..f10c6838 100644 --- a/packages/forge2d/lib/src/backend/raw_box2d_ffi.dart +++ b/packages/forge2d/lib/src/backend/raw_box2d_ffi.dart @@ -871,6 +871,77 @@ final class RawBox2DFfi implements RawBox2D { bool shapeTestPoint(int index1, int worldAndGeneration, double x, double y) => b2.b2Shape_TestPoint(_shape(index1, worldAndGeneration), _vec2(x, y)); + @override + (double, double, double) shapeGetCircle( + int index1, + int worldAndGeneration, + ) { + final circle = b2.b2Shape_GetCircle(_shape(index1, worldAndGeneration)); + return (circle.center.x, circle.center.y, circle.radius); + } + + @override + (double, double, double, double, double) shapeGetCapsule( + int index1, + int worldAndGeneration, + ) { + final capsule = b2.b2Shape_GetCapsule(_shape(index1, worldAndGeneration)); + return ( + capsule.center1.x, + capsule.center1.y, + capsule.center2.x, + capsule.center2.y, + capsule.radius, + ); + } + + @override + (double, double, double, double) shapeGetSegment( + int index1, + int worldAndGeneration, + ) { + final segment = b2.b2Shape_GetSegment(_shape(index1, worldAndGeneration)); + return ( + segment.point1.x, + segment.point1.y, + segment.point2.x, + segment.point2.y, + ); + } + + @override + (double, double, double, double) shapeGetChainSegment( + int index1, + int worldAndGeneration, + ) { + final chainSegment = b2.b2Shape_GetChainSegment( + _shape(index1, worldAndGeneration), + ); + return ( + chainSegment.segment.point1.x, + chainSegment.segment.point1.y, + chainSegment.segment.point2.x, + chainSegment.segment.point2.y, + ); + } + + @override + ({List points, double radius}) shapeGetPolygon( + int index1, + int worldAndGeneration, + ) { + final polygon = b2.b2Shape_GetPolygon(_shape(index1, worldAndGeneration)); + return ( + points: [ + for (var i = 0; i < polygon.count; i++) ...[ + polygon.vertices[i].x, + polygon.vertices[i].y, + ], + ], + radius: polygon.radius, + ); + } + @override (double, double, double, double) shapeGetAabb( int index1, diff --git a/packages/forge2d/lib/src/backend/raw_box2d_wasm.dart b/packages/forge2d/lib/src/backend/raw_box2d_wasm.dart index a08e470e..80b3e8b3 100644 --- a/packages/forge2d/lib/src/backend/raw_box2d_wasm.dart +++ b/packages/forge2d/lib/src/backend/raw_box2d_wasm.dart @@ -211,36 +211,36 @@ final class RawBox2DWasm implements RawBox2D { } @override - void destroyBody(int index1, int wg) => - _call('f2d_destroy_body', [index1, wg]); + void destroyBody(int index1, int worldAndGeneration) => + _call('f2d_destroy_body', [index1, worldAndGeneration]); @override - bool bodyIsValid(int index1, int wg) => - _callB('f2d_body_is_valid', [index1, wg]); + bool bodyIsValid(int index1, int worldAndGeneration) => + _callB('f2d_body_is_valid', [index1, worldAndGeneration]); @override - (double, double) bodyGetPosition(int index1, int wg) { - _call('f2d_body_get_position', [index1, wg, _out]); + (double, double) bodyGetPosition(int index1, int worldAndGeneration) { + _call('f2d_body_get_position', [index1, worldAndGeneration, _out]); return _outVec2(); } @override - (double, double) bodyGetRotation(int index1, int wg) { - _call('f2d_body_get_rotation', [index1, wg, _out]); + (double, double) bodyGetRotation(int index1, int worldAndGeneration) { + _call('f2d_body_get_rotation', [index1, worldAndGeneration, _out]); return _outVec2(); } @override void bodySetTransform( int index1, - int wg, + int worldAndGeneration, double positionX, double positionY, double rotationCos, double rotationSin, ) => _call('f2d_body_set_transform', [ index1, - wg, + worldAndGeneration, positionX, positionY, rotationCos, @@ -248,27 +248,39 @@ final class RawBox2DWasm implements RawBox2D { ]); @override - (double, double) bodyGetLinearVelocity(int index1, int wg) { - _call('f2d_body_get_linear_velocity', [index1, wg, _out]); + (double, double) bodyGetLinearVelocity(int index1, int worldAndGeneration) { + _call('f2d_body_get_linear_velocity', [index1, worldAndGeneration, _out]); return _outVec2(); } @override - void bodySetLinearVelocity(int index1, int wg, double x, double y) => - _call('f2d_body_set_linear_velocity', [index1, wg, x, y]); + void bodySetLinearVelocity( + int index1, + int worldAndGeneration, + double x, + double y, + ) => + _call('f2d_body_set_linear_velocity', [index1, worldAndGeneration, x, y]); @override - double bodyGetAngularVelocity(int index1, int wg) => - _callF('f2d_body_get_angular_velocity', [index1, wg]); + double bodyGetAngularVelocity(int index1, int worldAndGeneration) => + _callF('f2d_body_get_angular_velocity', [index1, worldAndGeneration]); @override - void bodySetAngularVelocity(int index1, int wg, double value) => - _call('f2d_body_set_angular_velocity', [index1, wg, value]); + void bodySetAngularVelocity( + int index1, + int worldAndGeneration, + double value, + ) => _call('f2d_body_set_angular_velocity', [ + index1, + worldAndGeneration, + value, + ]); @override void bodyApplyForce( int index1, - int wg, + int worldAndGeneration, double forceX, double forceY, double pointX, @@ -276,7 +288,7 @@ final class RawBox2DWasm implements RawBox2D { required bool wake, }) => _call('f2d_body_apply_force', [ index1, - wg, + worldAndGeneration, forceX, forceY, pointX, @@ -287,13 +299,13 @@ final class RawBox2DWasm implements RawBox2D { @override void bodyApplyForceToCenter( int index1, - int wg, + int worldAndGeneration, double forceX, double forceY, { required bool wake, }) => _call('f2d_body_apply_force_to_center', [ index1, - wg, + worldAndGeneration, forceX, forceY, _b(wake), @@ -302,15 +314,20 @@ final class RawBox2DWasm implements RawBox2D { @override void bodyApplyTorque( int index1, - int wg, + int worldAndGeneration, double torque, { required bool wake, - }) => _call('f2d_body_apply_torque', [index1, wg, torque, _b(wake)]); + }) => _call('f2d_body_apply_torque', [ + index1, + worldAndGeneration, + torque, + _b(wake), + ]); @override void bodyApplyLinearImpulse( int index1, - int wg, + int worldAndGeneration, double impulseX, double impulseY, double pointX, @@ -318,7 +335,7 @@ final class RawBox2DWasm implements RawBox2D { required bool wake, }) => _call('f2d_body_apply_linear_impulse', [ index1, - wg, + worldAndGeneration, impulseX, impulseY, pointX, @@ -329,13 +346,13 @@ final class RawBox2DWasm implements RawBox2D { @override void bodyApplyLinearImpulseToCenter( int index1, - int wg, + int worldAndGeneration, double impulseX, double impulseY, { required bool wake, }) => _call('f2d_body_apply_linear_impulse_to_center', [ index1, - wg, + worldAndGeneration, impulseX, impulseY, _b(wake), @@ -344,43 +361,53 @@ final class RawBox2DWasm implements RawBox2D { @override void bodyApplyAngularImpulse( int index1, - int wg, + int worldAndGeneration, double impulse, { required bool wake, - }) => - _call('f2d_body_apply_angular_impulse', [index1, wg, impulse, _b(wake)]); + }) => _call('f2d_body_apply_angular_impulse', [ + index1, + worldAndGeneration, + impulse, + _b(wake), + ]); @override - double bodyGetMass(int index1, int wg) => - _callF('f2d_body_get_mass', [index1, wg]); + double bodyGetMass(int index1, int worldAndGeneration) => + _callF('f2d_body_get_mass', [index1, worldAndGeneration]); @override - double bodyGetRotationalInertia(int index1, int wg) => - _callF('f2d_body_get_rotational_inertia', [index1, wg]); + double bodyGetRotationalInertia(int index1, int worldAndGeneration) => + _callF('f2d_body_get_rotational_inertia', [index1, worldAndGeneration]); @override - (double, double) bodyGetLocalCenterOfMass(int index1, int wg) { - _call('f2d_body_get_local_center', [index1, wg, _out]); + (double, double) bodyGetLocalCenterOfMass( + int index1, + int worldAndGeneration, + ) { + _call('f2d_body_get_local_center', [index1, worldAndGeneration, _out]); return _outVec2(); } @override - (double, double) bodyGetWorldCenterOfMass(int index1, int wg) { - _call('f2d_body_get_world_center', [index1, wg, _out]); + (double, double) bodyGetWorldCenterOfMass( + int index1, + int worldAndGeneration, + ) { + _call('f2d_body_get_world_center', [index1, worldAndGeneration, _out]); return _outVec2(); } @override void bodySetMassData( int index1, - int wg, + int worldAndGeneration, double mass, double rotationalInertia, double centerX, double centerY, ) => _call('f2d_body_set_mass_data', [ index1, - wg, + worldAndGeneration, mass, rotationalInertia, centerX, @@ -388,133 +415,198 @@ final class RawBox2DWasm implements RawBox2D { ]); @override - void bodyApplyMassFromShapes(int index1, int wg) => - _call('f2d_body_apply_mass_from_shapes', [index1, wg]); + void bodyApplyMassFromShapes(int index1, int worldAndGeneration) => + _call('f2d_body_apply_mass_from_shapes', [index1, worldAndGeneration]); @override - int bodyGetType(int index1, int wg) => - _callI('f2d_body_get_type', [index1, wg]); + int bodyGetType(int index1, int worldAndGeneration) => + _callI('f2d_body_get_type', [index1, worldAndGeneration]); @override - void bodySetType(int index1, int wg, int type) => - _call('f2d_body_set_type', [index1, wg, type]); + void bodySetType(int index1, int worldAndGeneration, int type) => + _call('f2d_body_set_type', [index1, worldAndGeneration, type]); @override - String bodyGetName(int index1, int wg) => - _runtime.readCString(_callI('f2d_body_get_name', [index1, wg])); + String bodyGetName(int index1, int worldAndGeneration) => _runtime + .readCString(_callI('f2d_body_get_name', [index1, worldAndGeneration])); @override - void bodySetName(int index1, int wg, String? name) => _call( + void bodySetName(int index1, int worldAndGeneration, String? name) => _call( 'f2d_body_set_name', - [index1, wg, _runtime.writeCString(_string, name)], + [index1, worldAndGeneration, _runtime.writeCString(_string, name)], ); @override - bool bodyIsAwake(int index1, int wg) => - _callB('f2d_body_is_awake', [index1, wg]); + bool bodyIsAwake(int index1, int worldAndGeneration) => + _callB('f2d_body_is_awake', [index1, worldAndGeneration]); @override - void bodySetAwake(int index1, int wg, {required bool awake}) => - _call('f2d_body_set_awake', [index1, wg, _b(awake)]); + void bodySetAwake( + int index1, + int worldAndGeneration, { + required bool awake, + }) => _call('f2d_body_set_awake', [index1, worldAndGeneration, _b(awake)]); @override - bool bodyIsSleepEnabled(int index1, int wg) => - _callB('f2d_body_is_sleep_enabled', [index1, wg]); + bool bodyIsSleepEnabled(int index1, int worldAndGeneration) => + _callB('f2d_body_is_sleep_enabled', [index1, worldAndGeneration]); @override - void bodyEnableSleep(int index1, int wg, {required bool enabled}) => - _call('f2d_body_enable_sleep', [index1, wg, _b(enabled)]); + void bodyEnableSleep( + int index1, + int worldAndGeneration, { + required bool enabled, + }) => + _call('f2d_body_enable_sleep', [index1, worldAndGeneration, _b(enabled)]); @override - double bodyGetSleepThreshold(int index1, int wg) => - _callF('f2d_body_get_sleep_threshold', [index1, wg]); + double bodyGetSleepThreshold(int index1, int worldAndGeneration) => + _callF('f2d_body_get_sleep_threshold', [index1, worldAndGeneration]); @override - void bodySetSleepThreshold(int index1, int wg, double value) => - _call('f2d_body_set_sleep_threshold', [index1, wg, value]); + void bodySetSleepThreshold( + int index1, + int worldAndGeneration, + double value, + ) => _call('f2d_body_set_sleep_threshold', [ + index1, + worldAndGeneration, + value, + ]); @override - bool bodyIsEnabled(int index1, int wg) => - _callB('f2d_body_is_enabled', [index1, wg]); + bool bodyIsEnabled(int index1, int worldAndGeneration) => + _callB('f2d_body_is_enabled', [index1, worldAndGeneration]); @override - void bodyDisable(int index1, int wg) => - _call('f2d_body_disable', [index1, wg]); + void bodyDisable(int index1, int worldAndGeneration) => + _call('f2d_body_disable', [index1, worldAndGeneration]); @override - void bodyEnable(int index1, int wg) => _call('f2d_body_enable', [index1, wg]); + void bodyEnable(int index1, int worldAndGeneration) => + _call('f2d_body_enable', [index1, worldAndGeneration]); @override - bool bodyIsFixedRotation(int index1, int wg) => - _callB('f2d_body_is_fixed_rotation', [index1, wg]); + bool bodyIsFixedRotation(int index1, int worldAndGeneration) => + _callB('f2d_body_is_fixed_rotation', [index1, worldAndGeneration]); @override - void bodySetFixedRotation(int index1, int wg, {required bool flag}) => - _call('f2d_body_set_fixed_rotation', [index1, wg, _b(flag)]); + void bodySetFixedRotation( + int index1, + int worldAndGeneration, { + required bool flag, + }) => _call('f2d_body_set_fixed_rotation', [ + index1, + worldAndGeneration, + _b(flag), + ]); @override - bool bodyIsBullet(int index1, int wg) => - _callB('f2d_body_is_bullet', [index1, wg]); + bool bodyIsBullet(int index1, int worldAndGeneration) => + _callB('f2d_body_is_bullet', [index1, worldAndGeneration]); @override - void bodySetBullet(int index1, int wg, {required bool flag}) => - _call('f2d_body_set_bullet', [index1, wg, _b(flag)]); + void bodySetBullet( + int index1, + int worldAndGeneration, { + required bool flag, + }) => _call('f2d_body_set_bullet', [index1, worldAndGeneration, _b(flag)]); @override - double bodyGetGravityScale(int index1, int wg) => - _callF('f2d_body_get_gravity_scale', [index1, wg]); + double bodyGetGravityScale(int index1, int worldAndGeneration) => + _callF('f2d_body_get_gravity_scale', [index1, worldAndGeneration]); @override - void bodySetGravityScale(int index1, int wg, double scale) => - _call('f2d_body_set_gravity_scale', [index1, wg, scale]); + void bodySetGravityScale(int index1, int worldAndGeneration, double scale) => + _call('f2d_body_set_gravity_scale', [index1, worldAndGeneration, scale]); @override - double bodyGetLinearDamping(int index1, int wg) => - _callF('f2d_body_get_linear_damping', [index1, wg]); + double bodyGetLinearDamping(int index1, int worldAndGeneration) => + _callF('f2d_body_get_linear_damping', [index1, worldAndGeneration]); @override - void bodySetLinearDamping(int index1, int wg, double damping) => - _call('f2d_body_set_linear_damping', [index1, wg, damping]); + void bodySetLinearDamping( + int index1, + int worldAndGeneration, + double damping, + ) => _call('f2d_body_set_linear_damping', [ + index1, + worldAndGeneration, + damping, + ]); @override - double bodyGetAngularDamping(int index1, int wg) => - _callF('f2d_body_get_angular_damping', [index1, wg]); + double bodyGetAngularDamping(int index1, int worldAndGeneration) => + _callF('f2d_body_get_angular_damping', [index1, worldAndGeneration]); @override - void bodySetAngularDamping(int index1, int wg, double damping) => - _call('f2d_body_set_angular_damping', [index1, wg, damping]); + void bodySetAngularDamping( + int index1, + int worldAndGeneration, + double damping, + ) => _call('f2d_body_set_angular_damping', [ + index1, + worldAndGeneration, + damping, + ]); @override - (double, double) bodyGetWorldPoint(int index1, int wg, double x, double y) { - _call('f2d_body_get_world_point', [index1, wg, x, y, _out]); + (double, double) bodyGetWorldPoint( + int index1, + int worldAndGeneration, + double x, + double y, + ) { + _call('f2d_body_get_world_point', [index1, worldAndGeneration, x, y, _out]); return _outVec2(); } @override - (double, double) bodyGetLocalPoint(int index1, int wg, double x, double y) { - _call('f2d_body_get_local_point', [index1, wg, x, y, _out]); + (double, double) bodyGetLocalPoint( + int index1, + int worldAndGeneration, + double x, + double y, + ) { + _call('f2d_body_get_local_point', [index1, worldAndGeneration, x, y, _out]); return _outVec2(); } @override - List bodyGetShapes(int index1, int wg) { - final count = _callI('f2d_body_get_shape_count', [index1, wg]); + List bodyGetShapes(int index1, int worldAndGeneration) { + final count = _callI('f2d_body_get_shape_count', [ + index1, + worldAndGeneration, + ]); if (count == 0) { return const []; } final buffer = _runtime.bulk(count * 8); - final written = _callI('f2d_body_get_shapes', [index1, wg, buffer, count]); + final written = _callI('f2d_body_get_shapes', [ + index1, + worldAndGeneration, + buffer, + count, + ]); return _runtime.readI32List(buffer, written * 2); } @override - List bodyGetJoints(int index1, int wg) { - final count = _callI('f2d_body_get_joint_count', [index1, wg]); + List bodyGetJoints(int index1, int worldAndGeneration) { + final count = _callI('f2d_body_get_joint_count', [ + index1, + worldAndGeneration, + ]); if (count == 0) { return const []; } final buffer = _runtime.bulk(count * 8); - final written = _callI('f2d_body_get_joints', [index1, wg, buffer, count]); + final written = _callI('f2d_body_get_joints', [ + index1, + worldAndGeneration, + buffer, + count, + ]); return _runtime.readI32List(buffer, written * 2); } @@ -549,7 +641,7 @@ final class RawBox2DWasm implements RawBox2D { @override (int, int) createCircleShape( int bodyIndex1, - int bodyWg, { + int bodyWorldAndGeneration, { required double centerX, required double centerY, required double radius, @@ -558,7 +650,7 @@ final class RawBox2DWasm implements RawBox2D { _writeShapeDef(definition); _call('f2d_create_circle_shape', [ bodyIndex1, - bodyWg, + bodyWorldAndGeneration, _shapeDefPointer, centerX, centerY, @@ -571,7 +663,7 @@ final class RawBox2DWasm implements RawBox2D { @override (int, int) createCapsuleShape( int bodyIndex1, - int bodyWg, { + int bodyWorldAndGeneration, { required double center1X, required double center1Y, required double center2X, @@ -582,7 +674,7 @@ final class RawBox2DWasm implements RawBox2D { _writeShapeDef(definition); _call('f2d_create_capsule_shape', [ bodyIndex1, - bodyWg, + bodyWorldAndGeneration, _shapeDefPointer, center1X, center1Y, @@ -597,7 +689,7 @@ final class RawBox2DWasm implements RawBox2D { @override (int, int) createSegmentShape( int bodyIndex1, - int bodyWg, { + int bodyWorldAndGeneration, { required double point1X, required double point1Y, required double point2X, @@ -607,7 +699,7 @@ final class RawBox2DWasm implements RawBox2D { _writeShapeDef(definition); _call('f2d_create_segment_shape', [ bodyIndex1, - bodyWg, + bodyWorldAndGeneration, _shapeDefPointer, point1X, point1Y, @@ -621,7 +713,7 @@ final class RawBox2DWasm implements RawBox2D { @override (int, int) createBoxShape( int bodyIndex1, - int bodyWg, { + int bodyWorldAndGeneration, { required double halfWidth, required double halfHeight, required double centerX, @@ -634,7 +726,7 @@ final class RawBox2DWasm implements RawBox2D { _writeShapeDef(definition); _call('f2d_create_box_shape', [ bodyIndex1, - bodyWg, + bodyWorldAndGeneration, _shapeDefPointer, halfWidth, halfHeight, @@ -651,7 +743,7 @@ final class RawBox2DWasm implements RawBox2D { @override (int, int) createPolygonShape( int bodyIndex1, - int bodyWg, { + int bodyWorldAndGeneration, { required List points, required double radius, required RawShapeDef definition, @@ -661,7 +753,7 @@ final class RawBox2DWasm implements RawBox2D { _runtime.writeF32List(buffer, points); final ok = _callB('f2d_create_polygon_shape', [ bodyIndex1, - bodyWg, + bodyWorldAndGeneration, _shapeDefPointer, buffer, points.length ~/ 2, @@ -678,63 +770,77 @@ final class RawBox2DWasm implements RawBox2D { } @override - void destroyShape(int index1, int wg, {required bool updateBodyMass}) => - _call('f2d_destroy_shape', [index1, wg, _b(updateBodyMass)]); + void destroyShape( + int index1, + int worldAndGeneration, { + required bool updateBodyMass, + }) => _call('f2d_destroy_shape', [ + index1, + worldAndGeneration, + _b(updateBodyMass), + ]); @override - bool shapeIsValid(int index1, int wg) => - _callB('f2d_shape_is_valid', [index1, wg]); + bool shapeIsValid(int index1, int worldAndGeneration) => + _callB('f2d_shape_is_valid', [index1, worldAndGeneration]); @override - int shapeGetType(int index1, int wg) => - _callI('f2d_shape_get_type', [index1, wg]); + int shapeGetType(int index1, int worldAndGeneration) => + _callI('f2d_shape_get_type', [index1, worldAndGeneration]); @override - (int, int) shapeGetBody(int index1, int wg) { - _call('f2d_shape_get_body', [index1, wg, _out]); + (int, int) shapeGetBody(int index1, int worldAndGeneration) { + _call('f2d_shape_get_body', [index1, worldAndGeneration, _out]); return _outIdPair(); } @override - bool shapeIsSensor(int index1, int wg) => - _callB('f2d_shape_is_sensor', [index1, wg]); + bool shapeIsSensor(int index1, int worldAndGeneration) => + _callB('f2d_shape_is_sensor', [index1, worldAndGeneration]); @override - double shapeGetDensity(int index1, int wg) => - _callF('f2d_shape_get_density', [index1, wg]); + double shapeGetDensity(int index1, int worldAndGeneration) => + _callF('f2d_shape_get_density', [index1, worldAndGeneration]); @override void shapeSetDensity( int index1, - int wg, + int worldAndGeneration, double density, { required bool updateBodyMass, }) => _call('f2d_shape_set_density', [ index1, - wg, + worldAndGeneration, density, _b(updateBodyMass), ]); @override - double shapeGetFriction(int index1, int wg) => - _callF('f2d_shape_get_friction', [index1, wg]); + double shapeGetFriction(int index1, int worldAndGeneration) => + _callF('f2d_shape_get_friction', [index1, worldAndGeneration]); @override - void shapeSetFriction(int index1, int wg, double friction) => - _call('f2d_shape_set_friction', [index1, wg, friction]); + void shapeSetFriction(int index1, int worldAndGeneration, double friction) => + _call('f2d_shape_set_friction', [index1, worldAndGeneration, friction]); @override - double shapeGetRestitution(int index1, int wg) => - _callF('f2d_shape_get_restitution', [index1, wg]); + double shapeGetRestitution(int index1, int worldAndGeneration) => + _callF('f2d_shape_get_restitution', [index1, worldAndGeneration]); @override - void shapeSetRestitution(int index1, int wg, double restitution) => - _call('f2d_shape_set_restitution', [index1, wg, restitution]); + void shapeSetRestitution( + int index1, + int worldAndGeneration, + double restitution, + ) => _call('f2d_shape_set_restitution', [ + index1, + worldAndGeneration, + restitution, + ]); @override - (int, int, int) shapeGetFilter(int index1, int wg) { - _call('f2d_shape_get_filter', [index1, wg, _out]); + (int, int, int) shapeGetFilter(int index1, int worldAndGeneration) { + _call('f2d_shape_get_filter', [index1, worldAndGeneration, _out]); return ( _joinBits(_runtime.readI32(_out), _runtime.readI32(_out + 4)), _joinBits(_runtime.readI32(_out + 8), _runtime.readI32(_out + 12)), @@ -745,7 +851,7 @@ final class RawBox2DWasm implements RawBox2D { @override void shapeSetFilter( int index1, - int wg, + int worldAndGeneration, int categoryBits, int maskBits, int groupIndex, @@ -754,7 +860,7 @@ final class RawBox2DWasm implements RawBox2D { final (maskLow, maskHigh) = _splitBits(maskBits); _call('f2d_shape_set_filter', [ index1, - wg, + worldAndGeneration, categoryLow, categoryHigh, maskLow, @@ -764,44 +870,153 @@ final class RawBox2DWasm implements RawBox2D { } @override - bool shapeAreSensorEventsEnabled(int index1, int wg) => - _callB('f2d_shape_are_sensor_events_enabled', [index1, wg]); + bool shapeAreSensorEventsEnabled(int index1, int worldAndGeneration) => + _callB('f2d_shape_are_sensor_events_enabled', [ + index1, + worldAndGeneration, + ]); + + @override + void shapeEnableSensorEvents( + int index1, + int worldAndGeneration, { + required bool enabled, + }) => _call('f2d_shape_enable_sensor_events', [ + index1, + worldAndGeneration, + _b(enabled), + ]); + + @override + bool shapeAreContactEventsEnabled(int index1, int worldAndGeneration) => + _callB('f2d_shape_are_contact_events_enabled', [ + index1, + worldAndGeneration, + ]); + + @override + void shapeEnableContactEvents( + int index1, + int worldAndGeneration, { + required bool enabled, + }) => _call('f2d_shape_enable_contact_events', [ + index1, + worldAndGeneration, + _b(enabled), + ]); + + @override + bool shapeAreHitEventsEnabled(int index1, int worldAndGeneration) => + _callB('f2d_shape_are_hit_events_enabled', [index1, worldAndGeneration]); + + @override + void shapeEnableHitEvents( + int index1, + int worldAndGeneration, { + required bool enabled, + }) => _call('f2d_shape_enable_hit_events', [ + index1, + worldAndGeneration, + _b(enabled), + ]); @override - void shapeEnableSensorEvents(int index1, int wg, {required bool enabled}) => - _call('f2d_shape_enable_sensor_events', [index1, wg, _b(enabled)]); + bool shapeArePreSolveEventsEnabled(int index1, int worldAndGeneration) => + _callB('f2d_shape_are_pre_solve_events_enabled', [ + index1, + worldAndGeneration, + ]); @override - bool shapeAreContactEventsEnabled(int index1, int wg) => - _callB('f2d_shape_are_contact_events_enabled', [index1, wg]); + void shapeEnablePreSolveEvents( + int index1, + int worldAndGeneration, { + required bool enabled, + }) => _call('f2d_shape_enable_pre_solve_events', [ + index1, + worldAndGeneration, + _b(enabled), + ]); @override - void shapeEnableContactEvents(int index1, int wg, {required bool enabled}) => - _call('f2d_shape_enable_contact_events', [index1, wg, _b(enabled)]); + bool shapeTestPoint(int index1, int worldAndGeneration, double x, double y) => + _callB('f2d_shape_test_point', [index1, worldAndGeneration, x, y]); @override - bool shapeAreHitEventsEnabled(int index1, int wg) => - _callB('f2d_shape_are_hit_events_enabled', [index1, wg]); + (double, double, double) shapeGetCircle(int index1, int worldAndGeneration) { + _call('f2d_shape_get_circle', [index1, worldAndGeneration, _out]); + return ( + _runtime.readF32(_out), + _runtime.readF32(_out + 4), + _runtime.readF32(_out + 8), + ); + } @override - void shapeEnableHitEvents(int index1, int wg, {required bool enabled}) => - _call('f2d_shape_enable_hit_events', [index1, wg, _b(enabled)]); + (double, double, double, double, double) shapeGetCapsule( + int index1, + int worldAndGeneration, + ) { + _call('f2d_shape_get_capsule', [index1, worldAndGeneration, _out]); + return ( + _runtime.readF32(_out), + _runtime.readF32(_out + 4), + _runtime.readF32(_out + 8), + _runtime.readF32(_out + 12), + _runtime.readF32(_out + 16), + ); + } @override - bool shapeArePreSolveEventsEnabled(int index1, int wg) => - _callB('f2d_shape_are_pre_solve_events_enabled', [index1, wg]); + (double, double, double, double) shapeGetSegment( + int index1, + int worldAndGeneration, + ) { + _call('f2d_shape_get_segment', [index1, worldAndGeneration, _out]); + return ( + _runtime.readF32(_out), + _runtime.readF32(_out + 4), + _runtime.readF32(_out + 8), + _runtime.readF32(_out + 12), + ); + } @override - void shapeEnablePreSolveEvents(int index1, int wg, {required bool enabled}) => - _call('f2d_shape_enable_pre_solve_events', [index1, wg, _b(enabled)]); + (double, double, double, double) shapeGetChainSegment( + int index1, + int worldAndGeneration, + ) { + _call('f2d_shape_get_chain_segment', [index1, worldAndGeneration, _out]); + return ( + _runtime.readF32(_out), + _runtime.readF32(_out + 4), + _runtime.readF32(_out + 8), + _runtime.readF32(_out + 12), + ); + } @override - bool shapeTestPoint(int index1, int wg, double x, double y) => - _callB('f2d_shape_test_point', [index1, wg, x, y]); + ({List points, double radius}) shapeGetPolygon( + int index1, + int worldAndGeneration, + ) { + final count = _callI('f2d_shape_get_polygon', [ + index1, + worldAndGeneration, + _out, + ]); + return ( + points: _runtime.readF32List(_out + 4, count * 2), + radius: _runtime.readF32(_out), + ); + } @override - (double, double, double, double) shapeGetAabb(int index1, int wg) { - _call('f2d_shape_get_aabb', [index1, wg, _out]); + (double, double, double, double) shapeGetAabb( + int index1, + int worldAndGeneration, + ) { + _call('f2d_shape_get_aabb', [index1, worldAndGeneration, _out]); return ( _runtime.readF32(_out), _runtime.readF32(_out + 4), @@ -815,7 +1030,7 @@ final class RawBox2DWasm implements RawBox2D { @override (int, int) createChain( int bodyIndex1, - int bodyWg, { + int bodyWorldAndGeneration, { required List points, required List materials, required int categoryBits, @@ -833,7 +1048,7 @@ final class RawBox2DWasm implements RawBox2D { final (maskLow, maskHigh) = _splitBits(maskBits); _call('f2d_create_chain', [ bodyIndex1, - bodyWg, + bodyWorldAndGeneration, buffer, points.length ~/ 2, buffer + pointBytes, @@ -851,39 +1066,49 @@ final class RawBox2DWasm implements RawBox2D { } @override - void destroyChain(int index1, int wg) => - _call('f2d_destroy_chain', [index1, wg]); + void destroyChain(int index1, int worldAndGeneration) => + _call('f2d_destroy_chain', [index1, worldAndGeneration]); @override - bool chainIsValid(int index1, int wg) => - _callB('f2d_chain_is_valid', [index1, wg]); + bool chainIsValid(int index1, int worldAndGeneration) => + _callB('f2d_chain_is_valid', [index1, worldAndGeneration]); @override - void chainSetFriction(int index1, int wg, double friction) => - _call('f2d_chain_set_friction', [index1, wg, friction]); + void chainSetFriction(int index1, int worldAndGeneration, double friction) => + _call('f2d_chain_set_friction', [index1, worldAndGeneration, friction]); @override - double chainGetFriction(int index1, int wg) => - _callF('f2d_chain_get_friction', [index1, wg]); + double chainGetFriction(int index1, int worldAndGeneration) => + _callF('f2d_chain_get_friction', [index1, worldAndGeneration]); @override - void chainSetRestitution(int index1, int wg, double restitution) => - _call('f2d_chain_set_restitution', [index1, wg, restitution]); + void chainSetRestitution( + int index1, + int worldAndGeneration, + double restitution, + ) => _call('f2d_chain_set_restitution', [ + index1, + worldAndGeneration, + restitution, + ]); @override - double chainGetRestitution(int index1, int wg) => - _callF('f2d_chain_get_restitution', [index1, wg]); + double chainGetRestitution(int index1, int worldAndGeneration) => + _callF('f2d_chain_get_restitution', [index1, worldAndGeneration]); @override - List chainGetSegments(int index1, int wg) { - final count = _callI('f2d_chain_get_segment_count', [index1, wg]); + List chainGetSegments(int index1, int worldAndGeneration) { + final count = _callI('f2d_chain_get_segment_count', [ + index1, + worldAndGeneration, + ]); if (count == 0) { return const []; } final buffer = _runtime.bulk(count * 8); final written = _callI('f2d_chain_get_segments', [ index1, - wg, + worldAndGeneration, buffer, count, ]); @@ -1193,544 +1418,994 @@ final class RawBox2DWasm implements RawBox2D { } @override - void destroyJoint(int index1, int wg) => - _call('f2d_destroy_joint', [index1, wg]); + void destroyJoint(int index1, int worldAndGeneration) => + _call('f2d_destroy_joint', [index1, worldAndGeneration]); @override - bool jointIsValid(int index1, int wg) => - _callB('f2d_joint_is_valid', [index1, wg]); + bool jointIsValid(int index1, int worldAndGeneration) => + _callB('f2d_joint_is_valid', [index1, worldAndGeneration]); @override - int jointGetType(int index1, int wg) => - _callI('f2d_joint_get_type', [index1, wg]); + int jointGetType(int index1, int worldAndGeneration) => + _callI('f2d_joint_get_type', [index1, worldAndGeneration]); @override - (int, int) jointGetBodyA(int index1, int wg) { - _call('f2d_joint_get_body_a', [index1, wg, _out]); + (int, int) jointGetBodyA(int index1, int worldAndGeneration) { + _call('f2d_joint_get_body_a', [index1, worldAndGeneration, _out]); return _outIdPair(); } @override - (int, int) jointGetBodyB(int index1, int wg) { - _call('f2d_joint_get_body_b', [index1, wg, _out]); + (int, int) jointGetBodyB(int index1, int worldAndGeneration) { + _call('f2d_joint_get_body_b', [index1, worldAndGeneration, _out]); return _outIdPair(); } @override - (double, double) jointGetLocalAnchorA(int index1, int wg) { - _call('f2d_joint_get_local_anchor_a', [index1, wg, _out]); + (double, double) jointGetLocalAnchorA(int index1, int worldAndGeneration) { + _call('f2d_joint_get_local_anchor_a', [index1, worldAndGeneration, _out]); return _outVec2(); } @override - (double, double) jointGetLocalAnchorB(int index1, int wg) { - _call('f2d_joint_get_local_anchor_b', [index1, wg, _out]); + (double, double) jointGetLocalAnchorB(int index1, int worldAndGeneration) { + _call('f2d_joint_get_local_anchor_b', [index1, worldAndGeneration, _out]); return _outVec2(); } @override - bool jointGetCollideConnected(int index1, int wg) => - _callB('f2d_joint_get_collide_connected', [index1, wg]); + bool jointGetCollideConnected(int index1, int worldAndGeneration) => + _callB('f2d_joint_get_collide_connected', [index1, worldAndGeneration]); @override - void jointSetCollideConnected(int index1, int wg, {required bool value}) => - _call('f2d_joint_set_collide_connected', [index1, wg, _b(value)]); + void jointSetCollideConnected( + int index1, + int worldAndGeneration, { + required bool value, + }) => _call('f2d_joint_set_collide_connected', [ + index1, + worldAndGeneration, + _b(value), + ]); @override - void jointWakeBodies(int index1, int wg) => - _call('f2d_joint_wake_bodies', [index1, wg]); + void jointWakeBodies(int index1, int worldAndGeneration) => + _call('f2d_joint_wake_bodies', [index1, worldAndGeneration]); @override - (double, double) jointGetConstraintForce(int index1, int wg) { - _call('f2d_joint_get_constraint_force', [index1, wg, _out]); + (double, double) jointGetConstraintForce(int index1, int worldAndGeneration) { + _call('f2d_joint_get_constraint_force', [index1, worldAndGeneration, _out]); return _outVec2(); } @override - double jointGetConstraintTorque(int index1, int wg) => - _callF('f2d_joint_get_constraint_torque', [index1, wg]); + double jointGetConstraintTorque(int index1, int worldAndGeneration) => + _callF('f2d_joint_get_constraint_torque', [index1, worldAndGeneration]); // Distance joint. @override - double distanceJointGetLength(int index1, int wg) => - _callF('f2d_distance_joint_get_length', [index1, wg]); + double distanceJointGetLength(int index1, int worldAndGeneration) => + _callF('f2d_distance_joint_get_length', [index1, worldAndGeneration]); @override - void distanceJointSetLength(int index1, int wg, double length) => - _call('f2d_distance_joint_set_length', [index1, wg, length]); + void distanceJointSetLength( + int index1, + int worldAndGeneration, + double length, + ) => _call('f2d_distance_joint_set_length', [ + index1, + worldAndGeneration, + length, + ]); @override - double distanceJointGetCurrentLength(int index1, int wg) => - _callF('f2d_distance_joint_get_current_length', [index1, wg]); + double distanceJointGetCurrentLength(int index1, int worldAndGeneration) => + _callF('f2d_distance_joint_get_current_length', [ + index1, + worldAndGeneration, + ]); @override - bool distanceJointIsSpringEnabled(int index1, int wg) => - _callB('f2d_distance_joint_is_spring_enabled', [index1, wg]); + bool distanceJointIsSpringEnabled(int index1, int worldAndGeneration) => + _callB('f2d_distance_joint_is_spring_enabled', [ + index1, + worldAndGeneration, + ]); @override - void distanceJointEnableSpring(int index1, int wg, {required bool enabled}) => - _call('f2d_distance_joint_enable_spring', [index1, wg, _b(enabled)]); + void distanceJointEnableSpring( + int index1, + int worldAndGeneration, { + required bool enabled, + }) => _call('f2d_distance_joint_enable_spring', [ + index1, + worldAndGeneration, + _b(enabled), + ]); + + @override + double distanceJointGetSpringHertz(int index1, int worldAndGeneration) => + _callF('f2d_distance_joint_get_spring_hertz', [ + index1, + worldAndGeneration, + ]); @override - double distanceJointGetSpringHertz(int index1, int wg) => - _callF('f2d_distance_joint_get_spring_hertz', [index1, wg]); + void distanceJointSetSpringHertz( + int index1, + int worldAndGeneration, + double hertz, + ) => _call('f2d_distance_joint_set_spring_hertz', [ + index1, + worldAndGeneration, + hertz, + ]); @override - void distanceJointSetSpringHertz(int index1, int wg, double hertz) => - _call('f2d_distance_joint_set_spring_hertz', [index1, wg, hertz]); + double distanceJointGetSpringDampingRatio( + int index1, + int worldAndGeneration, + ) => _callF('f2d_distance_joint_get_spring_damping_ratio', [ + index1, + worldAndGeneration, + ]); @override - double distanceJointGetSpringDampingRatio(int index1, int wg) => - _callF('f2d_distance_joint_get_spring_damping_ratio', [index1, wg]); + void distanceJointSetSpringDampingRatio( + int index1, + int worldAndGeneration, + double ratio, + ) => _call('f2d_distance_joint_set_spring_damping_ratio', [ + index1, + worldAndGeneration, + ratio, + ]); @override - void distanceJointSetSpringDampingRatio(int index1, int wg, double ratio) => - _call('f2d_distance_joint_set_spring_damping_ratio', [ + bool distanceJointIsLimitEnabled(int index1, int worldAndGeneration) => + _callB('f2d_distance_joint_is_limit_enabled', [ index1, - wg, - ratio, + worldAndGeneration, ]); @override - bool distanceJointIsLimitEnabled(int index1, int wg) => - _callB('f2d_distance_joint_is_limit_enabled', [index1, wg]); - - @override - void distanceJointEnableLimit(int index1, int wg, {required bool enabled}) => - _call('f2d_distance_joint_enable_limit', [index1, wg, _b(enabled)]); + void distanceJointEnableLimit( + int index1, + int worldAndGeneration, { + required bool enabled, + }) => _call('f2d_distance_joint_enable_limit', [ + index1, + worldAndGeneration, + _b(enabled), + ]); @override - double distanceJointGetMinLength(int index1, int wg) => - _callF('f2d_distance_joint_get_min_length', [index1, wg]); + double distanceJointGetMinLength(int index1, int worldAndGeneration) => + _callF('f2d_distance_joint_get_min_length', [index1, worldAndGeneration]); @override - double distanceJointGetMaxLength(int index1, int wg) => - _callF('f2d_distance_joint_get_max_length', [index1, wg]); + double distanceJointGetMaxLength(int index1, int worldAndGeneration) => + _callF('f2d_distance_joint_get_max_length', [index1, worldAndGeneration]); @override void distanceJointSetLengthRange( int index1, - int wg, + int worldAndGeneration, double minLength, double maxLength, ) => _call('f2d_distance_joint_set_length_range', [ index1, - wg, + worldAndGeneration, minLength, maxLength, ]); @override - bool distanceJointIsMotorEnabled(int index1, int wg) => - _callB('f2d_distance_joint_is_motor_enabled', [index1, wg]); + bool distanceJointIsMotorEnabled(int index1, int worldAndGeneration) => + _callB('f2d_distance_joint_is_motor_enabled', [ + index1, + worldAndGeneration, + ]); @override - void distanceJointEnableMotor(int index1, int wg, {required bool enabled}) => - _call('f2d_distance_joint_enable_motor', [index1, wg, _b(enabled)]); + void distanceJointEnableMotor( + int index1, + int worldAndGeneration, { + required bool enabled, + }) => _call('f2d_distance_joint_enable_motor', [ + index1, + worldAndGeneration, + _b(enabled), + ]); @override - double distanceJointGetMotorSpeed(int index1, int wg) => - _callF('f2d_distance_joint_get_motor_speed', [index1, wg]); + double distanceJointGetMotorSpeed(int index1, int worldAndGeneration) => + _callF('f2d_distance_joint_get_motor_speed', [ + index1, + worldAndGeneration, + ]); @override - void distanceJointSetMotorSpeed(int index1, int wg, double speed) => - _call('f2d_distance_joint_set_motor_speed', [index1, wg, speed]); + void distanceJointSetMotorSpeed( + int index1, + int worldAndGeneration, + double speed, + ) => _call('f2d_distance_joint_set_motor_speed', [ + index1, + worldAndGeneration, + speed, + ]); @override - double distanceJointGetMaxMotorForce(int index1, int wg) => - _callF('f2d_distance_joint_get_max_motor_force', [index1, wg]); + double distanceJointGetMaxMotorForce(int index1, int worldAndGeneration) => + _callF('f2d_distance_joint_get_max_motor_force', [ + index1, + worldAndGeneration, + ]); @override - void distanceJointSetMaxMotorForce(int index1, int wg, double force) => - _call('f2d_distance_joint_set_max_motor_force', [index1, wg, force]); + void distanceJointSetMaxMotorForce( + int index1, + int worldAndGeneration, + double force, + ) => _call('f2d_distance_joint_set_max_motor_force', [ + index1, + worldAndGeneration, + force, + ]); @override - double distanceJointGetMotorForce(int index1, int wg) => - _callF('f2d_distance_joint_get_motor_force', [index1, wg]); + double distanceJointGetMotorForce(int index1, int worldAndGeneration) => + _callF('f2d_distance_joint_get_motor_force', [ + index1, + worldAndGeneration, + ]); // Motor joint. @override - (double, double) motorJointGetLinearOffset(int index1, int wg) { - _call('f2d_motor_joint_get_linear_offset', [index1, wg, _out]); + (double, double) motorJointGetLinearOffset( + int index1, + int worldAndGeneration, + ) { + _call('f2d_motor_joint_get_linear_offset', [ + index1, + worldAndGeneration, + _out, + ]); return _outVec2(); } @override - void motorJointSetLinearOffset(int index1, int wg, double x, double y) => - _call('f2d_motor_joint_set_linear_offset', [index1, wg, x, y]); + void motorJointSetLinearOffset( + int index1, + int worldAndGeneration, + double x, + double y, + ) => _call('f2d_motor_joint_set_linear_offset', [ + index1, + worldAndGeneration, + x, + y, + ]); @override - double motorJointGetAngularOffset(int index1, int wg) => - _callF('f2d_motor_joint_get_angular_offset', [index1, wg]); + double motorJointGetAngularOffset(int index1, int worldAndGeneration) => + _callF('f2d_motor_joint_get_angular_offset', [ + index1, + worldAndGeneration, + ]); @override - void motorJointSetAngularOffset(int index1, int wg, double offset) => - _call('f2d_motor_joint_set_angular_offset', [index1, wg, offset]); + void motorJointSetAngularOffset( + int index1, + int worldAndGeneration, + double offset, + ) => _call('f2d_motor_joint_set_angular_offset', [ + index1, + worldAndGeneration, + offset, + ]); @override - double motorJointGetMaxForce(int index1, int wg) => - _callF('f2d_motor_joint_get_max_force', [index1, wg]); + double motorJointGetMaxForce(int index1, int worldAndGeneration) => + _callF('f2d_motor_joint_get_max_force', [index1, worldAndGeneration]); @override - void motorJointSetMaxForce(int index1, int wg, double force) => - _call('f2d_motor_joint_set_max_force', [index1, wg, force]); + void motorJointSetMaxForce( + int index1, + int worldAndGeneration, + double force, + ) => _call('f2d_motor_joint_set_max_force', [ + index1, + worldAndGeneration, + force, + ]); @override - double motorJointGetMaxTorque(int index1, int wg) => - _callF('f2d_motor_joint_get_max_torque', [index1, wg]); + double motorJointGetMaxTorque(int index1, int worldAndGeneration) => + _callF('f2d_motor_joint_get_max_torque', [index1, worldAndGeneration]); @override - void motorJointSetMaxTorque(int index1, int wg, double torque) => - _call('f2d_motor_joint_set_max_torque', [index1, wg, torque]); + void motorJointSetMaxTorque( + int index1, + int worldAndGeneration, + double torque, + ) => _call('f2d_motor_joint_set_max_torque', [ + index1, + worldAndGeneration, + torque, + ]); @override - double motorJointGetCorrectionFactor(int index1, int wg) => - _callF('f2d_motor_joint_get_correction_factor', [index1, wg]); + double motorJointGetCorrectionFactor(int index1, int worldAndGeneration) => + _callF('f2d_motor_joint_get_correction_factor', [ + index1, + worldAndGeneration, + ]); @override - void motorJointSetCorrectionFactor(int index1, int wg, double factor) => - _call('f2d_motor_joint_set_correction_factor', [index1, wg, factor]); + void motorJointSetCorrectionFactor( + int index1, + int worldAndGeneration, + double factor, + ) => _call('f2d_motor_joint_set_correction_factor', [ + index1, + worldAndGeneration, + factor, + ]); // Mouse joint. @override - (double, double) mouseJointGetTarget(int index1, int wg) { - _call('f2d_mouse_joint_get_target', [index1, wg, _out]); + (double, double) mouseJointGetTarget(int index1, int worldAndGeneration) { + _call('f2d_mouse_joint_get_target', [index1, worldAndGeneration, _out]); return _outVec2(); } @override - void mouseJointSetTarget(int index1, int wg, double x, double y) => - _call('f2d_mouse_joint_set_target', [index1, wg, x, y]); + void mouseJointSetTarget( + int index1, + int worldAndGeneration, + double x, + double y, + ) => _call('f2d_mouse_joint_set_target', [index1, worldAndGeneration, x, y]); @override - double mouseJointGetSpringHertz(int index1, int wg) => - _callF('f2d_mouse_joint_get_spring_hertz', [index1, wg]); + double mouseJointGetSpringHertz(int index1, int worldAndGeneration) => + _callF('f2d_mouse_joint_get_spring_hertz', [index1, worldAndGeneration]); @override - void mouseJointSetSpringHertz(int index1, int wg, double hertz) => - _call('f2d_mouse_joint_set_spring_hertz', [index1, wg, hertz]); + void mouseJointSetSpringHertz( + int index1, + int worldAndGeneration, + double hertz, + ) => _call('f2d_mouse_joint_set_spring_hertz', [ + index1, + worldAndGeneration, + hertz, + ]); @override - double mouseJointGetSpringDampingRatio(int index1, int wg) => - _callF('f2d_mouse_joint_get_spring_damping_ratio', [index1, wg]); + double mouseJointGetSpringDampingRatio(int index1, int worldAndGeneration) => + _callF('f2d_mouse_joint_get_spring_damping_ratio', [ + index1, + worldAndGeneration, + ]); @override - void mouseJointSetSpringDampingRatio(int index1, int wg, double ratio) => - _call('f2d_mouse_joint_set_spring_damping_ratio', [index1, wg, ratio]); + void mouseJointSetSpringDampingRatio( + int index1, + int worldAndGeneration, + double ratio, + ) => _call('f2d_mouse_joint_set_spring_damping_ratio', [ + index1, + worldAndGeneration, + ratio, + ]); @override - double mouseJointGetMaxForce(int index1, int wg) => - _callF('f2d_mouse_joint_get_max_force', [index1, wg]); + double mouseJointGetMaxForce(int index1, int worldAndGeneration) => + _callF('f2d_mouse_joint_get_max_force', [index1, worldAndGeneration]); @override - void mouseJointSetMaxForce(int index1, int wg, double force) => - _call('f2d_mouse_joint_set_max_force', [index1, wg, force]); + void mouseJointSetMaxForce( + int index1, + int worldAndGeneration, + double force, + ) => _call('f2d_mouse_joint_set_max_force', [ + index1, + worldAndGeneration, + force, + ]); // Prismatic joint. @override - bool prismaticJointIsSpringEnabled(int index1, int wg) => - _callB('f2d_prismatic_joint_is_spring_enabled', [index1, wg]); + bool prismaticJointIsSpringEnabled(int index1, int worldAndGeneration) => + _callB('f2d_prismatic_joint_is_spring_enabled', [ + index1, + worldAndGeneration, + ]); @override void prismaticJointEnableSpring( int index1, - int wg, { + int worldAndGeneration, { required bool enabled, - }) => _call('f2d_prismatic_joint_enable_spring', [index1, wg, _b(enabled)]); + }) => _call('f2d_prismatic_joint_enable_spring', [ + index1, + worldAndGeneration, + _b(enabled), + ]); @override - double prismaticJointGetSpringHertz(int index1, int wg) => - _callF('f2d_prismatic_joint_get_spring_hertz', [index1, wg]); + double prismaticJointGetSpringHertz(int index1, int worldAndGeneration) => + _callF('f2d_prismatic_joint_get_spring_hertz', [ + index1, + worldAndGeneration, + ]); @override - void prismaticJointSetSpringHertz(int index1, int wg, double hertz) => - _call('f2d_prismatic_joint_set_spring_hertz', [index1, wg, hertz]); + void prismaticJointSetSpringHertz( + int index1, + int worldAndGeneration, + double hertz, + ) => _call('f2d_prismatic_joint_set_spring_hertz', [ + index1, + worldAndGeneration, + hertz, + ]); @override - double prismaticJointGetSpringDampingRatio(int index1, int wg) => - _callF('f2d_prismatic_joint_get_spring_damping_ratio', [index1, wg]); + double prismaticJointGetSpringDampingRatio( + int index1, + int worldAndGeneration, + ) => _callF('f2d_prismatic_joint_get_spring_damping_ratio', [ + index1, + worldAndGeneration, + ]); @override - void prismaticJointSetSpringDampingRatio(int index1, int wg, double ratio) => - _call('f2d_prismatic_joint_set_spring_damping_ratio', [ - index1, - wg, - ratio, - ]); + void prismaticJointSetSpringDampingRatio( + int index1, + int worldAndGeneration, + double ratio, + ) => _call('f2d_prismatic_joint_set_spring_damping_ratio', [ + index1, + worldAndGeneration, + ratio, + ]); @override - double prismaticJointGetTargetTranslation(int index1, int wg) => - _callF('f2d_prismatic_joint_get_target_translation', [index1, wg]); + double prismaticJointGetTargetTranslation( + int index1, + int worldAndGeneration, + ) => _callF('f2d_prismatic_joint_get_target_translation', [ + index1, + worldAndGeneration, + ]); @override - void prismaticJointSetTargetTranslation(int index1, int wg, double value) => - _call('f2d_prismatic_joint_set_target_translation', [ - index1, - wg, - value, - ]); + void prismaticJointSetTargetTranslation( + int index1, + int worldAndGeneration, + double value, + ) => _call('f2d_prismatic_joint_set_target_translation', [ + index1, + worldAndGeneration, + value, + ]); @override - bool prismaticJointIsLimitEnabled(int index1, int wg) => - _callB('f2d_prismatic_joint_is_limit_enabled', [index1, wg]); + bool prismaticJointIsLimitEnabled(int index1, int worldAndGeneration) => + _callB('f2d_prismatic_joint_is_limit_enabled', [ + index1, + worldAndGeneration, + ]); @override - void prismaticJointEnableLimit(int index1, int wg, {required bool enabled}) => - _call('f2d_prismatic_joint_enable_limit', [index1, wg, _b(enabled)]); + void prismaticJointEnableLimit( + int index1, + int worldAndGeneration, { + required bool enabled, + }) => _call('f2d_prismatic_joint_enable_limit', [ + index1, + worldAndGeneration, + _b(enabled), + ]); @override - double prismaticJointGetLowerLimit(int index1, int wg) => - _callF('f2d_prismatic_joint_get_lower_limit', [index1, wg]); + double prismaticJointGetLowerLimit(int index1, int worldAndGeneration) => + _callF('f2d_prismatic_joint_get_lower_limit', [ + index1, + worldAndGeneration, + ]); @override - double prismaticJointGetUpperLimit(int index1, int wg) => - _callF('f2d_prismatic_joint_get_upper_limit', [index1, wg]); + double prismaticJointGetUpperLimit(int index1, int worldAndGeneration) => + _callF('f2d_prismatic_joint_get_upper_limit', [ + index1, + worldAndGeneration, + ]); @override void prismaticJointSetLimits( int index1, - int wg, + int worldAndGeneration, double lower, double upper, - ) => _call('f2d_prismatic_joint_set_limits', [index1, wg, lower, upper]); + ) => _call('f2d_prismatic_joint_set_limits', [ + index1, + worldAndGeneration, + lower, + upper, + ]); @override - bool prismaticJointIsMotorEnabled(int index1, int wg) => - _callB('f2d_prismatic_joint_is_motor_enabled', [index1, wg]); + bool prismaticJointIsMotorEnabled(int index1, int worldAndGeneration) => + _callB('f2d_prismatic_joint_is_motor_enabled', [ + index1, + worldAndGeneration, + ]); @override - void prismaticJointEnableMotor(int index1, int wg, {required bool enabled}) => - _call('f2d_prismatic_joint_enable_motor', [index1, wg, _b(enabled)]); + void prismaticJointEnableMotor( + int index1, + int worldAndGeneration, { + required bool enabled, + }) => _call('f2d_prismatic_joint_enable_motor', [ + index1, + worldAndGeneration, + _b(enabled), + ]); @override - double prismaticJointGetMotorSpeed(int index1, int wg) => - _callF('f2d_prismatic_joint_get_motor_speed', [index1, wg]); + double prismaticJointGetMotorSpeed(int index1, int worldAndGeneration) => + _callF('f2d_prismatic_joint_get_motor_speed', [ + index1, + worldAndGeneration, + ]); @override - void prismaticJointSetMotorSpeed(int index1, int wg, double speed) => - _call('f2d_prismatic_joint_set_motor_speed', [index1, wg, speed]); + void prismaticJointSetMotorSpeed( + int index1, + int worldAndGeneration, + double speed, + ) => _call('f2d_prismatic_joint_set_motor_speed', [ + index1, + worldAndGeneration, + speed, + ]); @override - double prismaticJointGetMaxMotorForce(int index1, int wg) => - _callF('f2d_prismatic_joint_get_max_motor_force', [index1, wg]); + double prismaticJointGetMaxMotorForce(int index1, int worldAndGeneration) => + _callF('f2d_prismatic_joint_get_max_motor_force', [ + index1, + worldAndGeneration, + ]); @override - void prismaticJointSetMaxMotorForce(int index1, int wg, double force) => - _call('f2d_prismatic_joint_set_max_motor_force', [index1, wg, force]); + void prismaticJointSetMaxMotorForce( + int index1, + int worldAndGeneration, + double force, + ) => _call('f2d_prismatic_joint_set_max_motor_force', [ + index1, + worldAndGeneration, + force, + ]); @override - double prismaticJointGetMotorForce(int index1, int wg) => - _callF('f2d_prismatic_joint_get_motor_force', [index1, wg]); + double prismaticJointGetMotorForce(int index1, int worldAndGeneration) => + _callF('f2d_prismatic_joint_get_motor_force', [ + index1, + worldAndGeneration, + ]); @override - double prismaticJointGetTranslation(int index1, int wg) => - _callF('f2d_prismatic_joint_get_translation', [index1, wg]); + double prismaticJointGetTranslation(int index1, int worldAndGeneration) => + _callF('f2d_prismatic_joint_get_translation', [ + index1, + worldAndGeneration, + ]); @override - double prismaticJointGetSpeed(int index1, int wg) => - _callF('f2d_prismatic_joint_get_speed', [index1, wg]); + double prismaticJointGetSpeed(int index1, int worldAndGeneration) => + _callF('f2d_prismatic_joint_get_speed', [index1, worldAndGeneration]); // Revolute joint. @override - bool revoluteJointIsSpringEnabled(int index1, int wg) => - _callB('f2d_revolute_joint_is_spring_enabled', [index1, wg]); + bool revoluteJointIsSpringEnabled(int index1, int worldAndGeneration) => + _callB('f2d_revolute_joint_is_spring_enabled', [ + index1, + worldAndGeneration, + ]); + + @override + void revoluteJointEnableSpring( + int index1, + int worldAndGeneration, { + required bool enabled, + }) => _call('f2d_revolute_joint_enable_spring', [ + index1, + worldAndGeneration, + _b(enabled), + ]); @override - void revoluteJointEnableSpring(int index1, int wg, {required bool enabled}) => - _call('f2d_revolute_joint_enable_spring', [index1, wg, _b(enabled)]); + double revoluteJointGetSpringHertz(int index1, int worldAndGeneration) => + _callF('f2d_revolute_joint_get_spring_hertz', [ + index1, + worldAndGeneration, + ]); @override - double revoluteJointGetSpringHertz(int index1, int wg) => - _callF('f2d_revolute_joint_get_spring_hertz', [index1, wg]); + void revoluteJointSetSpringHertz( + int index1, + int worldAndGeneration, + double hertz, + ) => _call('f2d_revolute_joint_set_spring_hertz', [ + index1, + worldAndGeneration, + hertz, + ]); @override - void revoluteJointSetSpringHertz(int index1, int wg, double hertz) => - _call('f2d_revolute_joint_set_spring_hertz', [index1, wg, hertz]); + double revoluteJointGetSpringDampingRatio( + int index1, + int worldAndGeneration, + ) => _callF('f2d_revolute_joint_get_spring_damping_ratio', [ + index1, + worldAndGeneration, + ]); @override - double revoluteJointGetSpringDampingRatio(int index1, int wg) => - _callF('f2d_revolute_joint_get_spring_damping_ratio', [index1, wg]); + void revoluteJointSetSpringDampingRatio( + int index1, + int worldAndGeneration, + double ratio, + ) => _call('f2d_revolute_joint_set_spring_damping_ratio', [ + index1, + worldAndGeneration, + ratio, + ]); @override - void revoluteJointSetSpringDampingRatio(int index1, int wg, double ratio) => - _call('f2d_revolute_joint_set_spring_damping_ratio', [ + double revoluteJointGetTargetAngle(int index1, int worldAndGeneration) => + _callF('f2d_revolute_joint_get_target_angle', [ index1, - wg, - ratio, + worldAndGeneration, ]); @override - double revoluteJointGetTargetAngle(int index1, int wg) => - _callF('f2d_revolute_joint_get_target_angle', [index1, wg]); - - @override - void revoluteJointSetTargetAngle(int index1, int wg, double angle) => - _call('f2d_revolute_joint_set_target_angle', [index1, wg, angle]); + void revoluteJointSetTargetAngle( + int index1, + int worldAndGeneration, + double angle, + ) => _call('f2d_revolute_joint_set_target_angle', [ + index1, + worldAndGeneration, + angle, + ]); @override - double revoluteJointGetAngle(int index1, int wg) => - _callF('f2d_revolute_joint_get_angle', [index1, wg]); + double revoluteJointGetAngle(int index1, int worldAndGeneration) => + _callF('f2d_revolute_joint_get_angle', [index1, worldAndGeneration]); @override - bool revoluteJointIsLimitEnabled(int index1, int wg) => - _callB('f2d_revolute_joint_is_limit_enabled', [index1, wg]); + bool revoluteJointIsLimitEnabled(int index1, int worldAndGeneration) => + _callB('f2d_revolute_joint_is_limit_enabled', [ + index1, + worldAndGeneration, + ]); @override - void revoluteJointEnableLimit(int index1, int wg, {required bool enabled}) => - _call('f2d_revolute_joint_enable_limit', [index1, wg, _b(enabled)]); + void revoluteJointEnableLimit( + int index1, + int worldAndGeneration, { + required bool enabled, + }) => _call('f2d_revolute_joint_enable_limit', [ + index1, + worldAndGeneration, + _b(enabled), + ]); @override - double revoluteJointGetLowerLimit(int index1, int wg) => - _callF('f2d_revolute_joint_get_lower_limit', [index1, wg]); + double revoluteJointGetLowerLimit(int index1, int worldAndGeneration) => + _callF('f2d_revolute_joint_get_lower_limit', [ + index1, + worldAndGeneration, + ]); @override - double revoluteJointGetUpperLimit(int index1, int wg) => - _callF('f2d_revolute_joint_get_upper_limit', [index1, wg]); + double revoluteJointGetUpperLimit(int index1, int worldAndGeneration) => + _callF('f2d_revolute_joint_get_upper_limit', [ + index1, + worldAndGeneration, + ]); @override - void revoluteJointSetLimits(int index1, int wg, double lower, double upper) => - _call('f2d_revolute_joint_set_limits', [index1, wg, lower, upper]); + void revoluteJointSetLimits( + int index1, + int worldAndGeneration, + double lower, + double upper, + ) => _call('f2d_revolute_joint_set_limits', [ + index1, + worldAndGeneration, + lower, + upper, + ]); @override - bool revoluteJointIsMotorEnabled(int index1, int wg) => - _callB('f2d_revolute_joint_is_motor_enabled', [index1, wg]); + bool revoluteJointIsMotorEnabled(int index1, int worldAndGeneration) => + _callB('f2d_revolute_joint_is_motor_enabled', [ + index1, + worldAndGeneration, + ]); @override - void revoluteJointEnableMotor(int index1, int wg, {required bool enabled}) => - _call('f2d_revolute_joint_enable_motor', [index1, wg, _b(enabled)]); + void revoluteJointEnableMotor( + int index1, + int worldAndGeneration, { + required bool enabled, + }) => _call('f2d_revolute_joint_enable_motor', [ + index1, + worldAndGeneration, + _b(enabled), + ]); @override - double revoluteJointGetMotorSpeed(int index1, int wg) => - _callF('f2d_revolute_joint_get_motor_speed', [index1, wg]); + double revoluteJointGetMotorSpeed(int index1, int worldAndGeneration) => + _callF('f2d_revolute_joint_get_motor_speed', [ + index1, + worldAndGeneration, + ]); @override - void revoluteJointSetMotorSpeed(int index1, int wg, double speed) => - _call('f2d_revolute_joint_set_motor_speed', [index1, wg, speed]); + void revoluteJointSetMotorSpeed( + int index1, + int worldAndGeneration, + double speed, + ) => _call('f2d_revolute_joint_set_motor_speed', [ + index1, + worldAndGeneration, + speed, + ]); @override - double revoluteJointGetMaxMotorTorque(int index1, int wg) => - _callF('f2d_revolute_joint_get_max_motor_torque', [index1, wg]); + double revoluteJointGetMaxMotorTorque(int index1, int worldAndGeneration) => + _callF('f2d_revolute_joint_get_max_motor_torque', [ + index1, + worldAndGeneration, + ]); @override - void revoluteJointSetMaxMotorTorque(int index1, int wg, double torque) => - _call('f2d_revolute_joint_set_max_motor_torque', [index1, wg, torque]); + void revoluteJointSetMaxMotorTorque( + int index1, + int worldAndGeneration, + double torque, + ) => _call('f2d_revolute_joint_set_max_motor_torque', [ + index1, + worldAndGeneration, + torque, + ]); @override - double revoluteJointGetMotorTorque(int index1, int wg) => - _callF('f2d_revolute_joint_get_motor_torque', [index1, wg]); + double revoluteJointGetMotorTorque(int index1, int worldAndGeneration) => + _callF('f2d_revolute_joint_get_motor_torque', [ + index1, + worldAndGeneration, + ]); // Weld joint. @override - double weldJointGetLinearHertz(int index1, int wg) => - _callF('f2d_weld_joint_get_linear_hertz', [index1, wg]); + double weldJointGetLinearHertz(int index1, int worldAndGeneration) => + _callF('f2d_weld_joint_get_linear_hertz', [index1, worldAndGeneration]); @override - void weldJointSetLinearHertz(int index1, int wg, double hertz) => - _call('f2d_weld_joint_set_linear_hertz', [index1, wg, hertz]); + void weldJointSetLinearHertz( + int index1, + int worldAndGeneration, + double hertz, + ) => _call('f2d_weld_joint_set_linear_hertz', [ + index1, + worldAndGeneration, + hertz, + ]); @override - double weldJointGetAngularHertz(int index1, int wg) => - _callF('f2d_weld_joint_get_angular_hertz', [index1, wg]); + double weldJointGetAngularHertz(int index1, int worldAndGeneration) => + _callF('f2d_weld_joint_get_angular_hertz', [index1, worldAndGeneration]); @override - void weldJointSetAngularHertz(int index1, int wg, double hertz) => - _call('f2d_weld_joint_set_angular_hertz', [index1, wg, hertz]); + void weldJointSetAngularHertz( + int index1, + int worldAndGeneration, + double hertz, + ) => _call('f2d_weld_joint_set_angular_hertz', [ + index1, + worldAndGeneration, + hertz, + ]); @override - double weldJointGetLinearDampingRatio(int index1, int wg) => - _callF('f2d_weld_joint_get_linear_damping_ratio', [index1, wg]); + double weldJointGetLinearDampingRatio(int index1, int worldAndGeneration) => + _callF('f2d_weld_joint_get_linear_damping_ratio', [ + index1, + worldAndGeneration, + ]); @override - void weldJointSetLinearDampingRatio(int index1, int wg, double ratio) => - _call('f2d_weld_joint_set_linear_damping_ratio', [index1, wg, ratio]); + void weldJointSetLinearDampingRatio( + int index1, + int worldAndGeneration, + double ratio, + ) => _call('f2d_weld_joint_set_linear_damping_ratio', [ + index1, + worldAndGeneration, + ratio, + ]); @override - double weldJointGetAngularDampingRatio(int index1, int wg) => - _callF('f2d_weld_joint_get_angular_damping_ratio', [index1, wg]); + double weldJointGetAngularDampingRatio(int index1, int worldAndGeneration) => + _callF('f2d_weld_joint_get_angular_damping_ratio', [ + index1, + worldAndGeneration, + ]); @override - void weldJointSetAngularDampingRatio(int index1, int wg, double ratio) => - _call('f2d_weld_joint_set_angular_damping_ratio', [index1, wg, ratio]); + void weldJointSetAngularDampingRatio( + int index1, + int worldAndGeneration, + double ratio, + ) => _call('f2d_weld_joint_set_angular_damping_ratio', [ + index1, + worldAndGeneration, + ratio, + ]); // Wheel joint. @override - bool wheelJointIsSpringEnabled(int index1, int wg) => - _callB('f2d_wheel_joint_is_spring_enabled', [index1, wg]); + bool wheelJointIsSpringEnabled(int index1, int worldAndGeneration) => + _callB('f2d_wheel_joint_is_spring_enabled', [index1, worldAndGeneration]); @override - void wheelJointEnableSpring(int index1, int wg, {required bool enabled}) => - _call('f2d_wheel_joint_enable_spring', [index1, wg, _b(enabled)]); + void wheelJointEnableSpring( + int index1, + int worldAndGeneration, { + required bool enabled, + }) => _call('f2d_wheel_joint_enable_spring', [ + index1, + worldAndGeneration, + _b(enabled), + ]); @override - double wheelJointGetSpringHertz(int index1, int wg) => - _callF('f2d_wheel_joint_get_spring_hertz', [index1, wg]); + double wheelJointGetSpringHertz(int index1, int worldAndGeneration) => + _callF('f2d_wheel_joint_get_spring_hertz', [index1, worldAndGeneration]); @override - void wheelJointSetSpringHertz(int index1, int wg, double hertz) => - _call('f2d_wheel_joint_set_spring_hertz', [index1, wg, hertz]); + void wheelJointSetSpringHertz( + int index1, + int worldAndGeneration, + double hertz, + ) => _call('f2d_wheel_joint_set_spring_hertz', [ + index1, + worldAndGeneration, + hertz, + ]); @override - double wheelJointGetSpringDampingRatio(int index1, int wg) => - _callF('f2d_wheel_joint_get_spring_damping_ratio', [index1, wg]); + double wheelJointGetSpringDampingRatio(int index1, int worldAndGeneration) => + _callF('f2d_wheel_joint_get_spring_damping_ratio', [ + index1, + worldAndGeneration, + ]); @override - void wheelJointSetSpringDampingRatio(int index1, int wg, double ratio) => - _call('f2d_wheel_joint_set_spring_damping_ratio', [index1, wg, ratio]); + void wheelJointSetSpringDampingRatio( + int index1, + int worldAndGeneration, + double ratio, + ) => _call('f2d_wheel_joint_set_spring_damping_ratio', [ + index1, + worldAndGeneration, + ratio, + ]); @override - bool wheelJointIsLimitEnabled(int index1, int wg) => - _callB('f2d_wheel_joint_is_limit_enabled', [index1, wg]); + bool wheelJointIsLimitEnabled(int index1, int worldAndGeneration) => + _callB('f2d_wheel_joint_is_limit_enabled', [index1, worldAndGeneration]); @override - void wheelJointEnableLimit(int index1, int wg, {required bool enabled}) => - _call('f2d_wheel_joint_enable_limit', [index1, wg, _b(enabled)]); + void wheelJointEnableLimit( + int index1, + int worldAndGeneration, { + required bool enabled, + }) => _call('f2d_wheel_joint_enable_limit', [ + index1, + worldAndGeneration, + _b(enabled), + ]); @override - double wheelJointGetLowerLimit(int index1, int wg) => - _callF('f2d_wheel_joint_get_lower_limit', [index1, wg]); + double wheelJointGetLowerLimit(int index1, int worldAndGeneration) => + _callF('f2d_wheel_joint_get_lower_limit', [index1, worldAndGeneration]); @override - double wheelJointGetUpperLimit(int index1, int wg) => - _callF('f2d_wheel_joint_get_upper_limit', [index1, wg]); + double wheelJointGetUpperLimit(int index1, int worldAndGeneration) => + _callF('f2d_wheel_joint_get_upper_limit', [index1, worldAndGeneration]); @override - void wheelJointSetLimits(int index1, int wg, double lower, double upper) => - _call('f2d_wheel_joint_set_limits', [index1, wg, lower, upper]); + void wheelJointSetLimits( + int index1, + int worldAndGeneration, + double lower, + double upper, + ) => _call('f2d_wheel_joint_set_limits', [ + index1, + worldAndGeneration, + lower, + upper, + ]); @override - bool wheelJointIsMotorEnabled(int index1, int wg) => - _callB('f2d_wheel_joint_is_motor_enabled', [index1, wg]); + bool wheelJointIsMotorEnabled(int index1, int worldAndGeneration) => + _callB('f2d_wheel_joint_is_motor_enabled', [index1, worldAndGeneration]); @override - void wheelJointEnableMotor(int index1, int wg, {required bool enabled}) => - _call('f2d_wheel_joint_enable_motor', [index1, wg, _b(enabled)]); + void wheelJointEnableMotor( + int index1, + int worldAndGeneration, { + required bool enabled, + }) => _call('f2d_wheel_joint_enable_motor', [ + index1, + worldAndGeneration, + _b(enabled), + ]); @override - double wheelJointGetMotorSpeed(int index1, int wg) => - _callF('f2d_wheel_joint_get_motor_speed', [index1, wg]); + double wheelJointGetMotorSpeed(int index1, int worldAndGeneration) => + _callF('f2d_wheel_joint_get_motor_speed', [index1, worldAndGeneration]); @override - void wheelJointSetMotorSpeed(int index1, int wg, double speed) => - _call('f2d_wheel_joint_set_motor_speed', [index1, wg, speed]); + void wheelJointSetMotorSpeed( + int index1, + int worldAndGeneration, + double speed, + ) => _call('f2d_wheel_joint_set_motor_speed', [ + index1, + worldAndGeneration, + speed, + ]); @override - double wheelJointGetMaxMotorTorque(int index1, int wg) => - _callF('f2d_wheel_joint_get_max_motor_torque', [index1, wg]); + double wheelJointGetMaxMotorTorque(int index1, int worldAndGeneration) => + _callF('f2d_wheel_joint_get_max_motor_torque', [ + index1, + worldAndGeneration, + ]); @override - void wheelJointSetMaxMotorTorque(int index1, int wg, double torque) => - _call('f2d_wheel_joint_set_max_motor_torque', [index1, wg, torque]); + void wheelJointSetMaxMotorTorque( + int index1, + int worldAndGeneration, + double torque, + ) => _call('f2d_wheel_joint_set_max_motor_torque', [ + index1, + worldAndGeneration, + torque, + ]); @override - double wheelJointGetMotorTorque(int index1, int wg) => - _callF('f2d_wheel_joint_get_motor_torque', [index1, wg]); + double wheelJointGetMotorTorque(int index1, int worldAndGeneration) => + _callF('f2d_wheel_joint_get_motor_torque', [index1, worldAndGeneration]); // Events. @@ -1925,9 +2600,17 @@ final class RawBox2DWasm implements RawBox2D { final (maskLow, maskHigh) = _splitBits(maskBits); final previous = _runtime.callbacks.castRay; _runtime.callbacks.castRay = - (index1, wg, pointX, pointY, normalX, normalY, fraction) => callback(( + ( + index1, + worldAndGeneration, + pointX, + pointY, + normalX, + normalY, + fraction, + ) => callback(( shapeIndex1: index1, - shapeWorldAndGeneration: wg, + shapeWorldAndGeneration: worldAndGeneration, pointX: pointX, pointY: pointY, normalX: normalX, @@ -1965,10 +2648,10 @@ final class RawBox2DWasm implements RawBox2D { final (maskLow, maskHigh) = _splitBits(maskBits); final results = []; final previous = _runtime.callbacks.overlap; - _runtime.callbacks.overlap = (index1, wg) { + _runtime.callbacks.overlap = (index1, worldAndGeneration) { results ..add(index1) - ..add(wg); + ..add(worldAndGeneration); return true; }; try { @@ -2039,8 +2722,15 @@ final class RawBox2DWasm implements RawBox2D { _call('f2d_world_set_custom_filter', [worldId, 0]); } else { _filterCallbacks[_world0(worldId)] = callback; - _runtime.callbacks.customFilter = (a1, awg, b1, bwg) => - _filterCallbacks[awg & 0xFFFF]?.call(a1, awg, b1, bwg) ?? true; + _runtime.callbacks.customFilter = + (a1, aWorldAndGeneration, b1, bWorldAndGeneration) => + _filterCallbacks[aWorldAndGeneration & 0xFFFF]?.call( + a1, + aWorldAndGeneration, + b1, + bWorldAndGeneration, + ) ?? + true; _call('f2d_world_set_custom_filter', [worldId, 1]); } } @@ -2063,16 +2753,24 @@ final class RawBox2DWasm implements RawBox2D { _call('f2d_world_set_pre_solve', [worldId, 0]); } else { _preSolveCallbacks[_world0(worldId)] = callback; - _runtime.callbacks.preSolve = (a1, awg, b1, bwg, normalX, normalY) => - _preSolveCallbacks[awg & 0xFFFF]?.call( + _runtime.callbacks.preSolve = + ( a1, - awg, + aWorldAndGeneration, b1, - bwg, + bWorldAndGeneration, normalX, normalY, - ) ?? - true; + ) => + _preSolveCallbacks[aWorldAndGeneration & 0xFFFF]?.call( + a1, + aWorldAndGeneration, + b1, + bWorldAndGeneration, + normalX, + normalY, + ) ?? + true; _call('f2d_world_set_pre_solve', [worldId, 1]); } } diff --git a/packages/forge2d/lib/src/backend/wasm/box2d.wasm b/packages/forge2d/lib/src/backend/wasm/box2d.wasm index 8a1905346b9ed2398a4dc78b0db12944932c1579..6b9e2a948ef61f3aee5d6a9eaa2de06b608fe641 100755 GIT binary patch delta 4941 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 diff --git a/packages/forge2d/native/wasm/f2d_shim.c b/packages/forge2d/native/wasm/f2d_shim.c index d18b8f35..f14de86f 100644 --- a/packages/forge2d/native/wasm/f2d_shim.c +++ b/packages/forge2d/native/wasm/f2d_shim.c @@ -5,8 +5,8 @@ // function the Dart backend needs with a flat scalar/pointer signature: // // - World ids cross as one uint32: index1 | (generation << 16). -// - Body/shape/chain/joint ids cross as (int32 index1, uint32 wg) where -// wg packs world0 | (generation << 16), matching the Dart seam. +// - Body/shape/chain/joint ids cross as (int32 index1, uint32 world_and_generation) where +// world_and_generation packs world0 | (generation << 16), matching the Dart seam. // - Vectors and tuples are written into caller-provided buffers. // - 64-bit filter bits cross as two uint32 halves. // - Events are copied into caller-provided float64 buffers (float64 @@ -62,20 +62,20 @@ static uint32_t f2d_pack_world(b2WorldId id) { return (uint32_t)id.index1 | ((uint32_t)id.generation << 16); } -static b2BodyId f2d_body(int32_t index1, uint32_t wg) { - return (b2BodyId){index1, (uint16_t)(wg & 0xFFFF), (uint16_t)(wg >> 16)}; +static b2BodyId f2d_body(int32_t index1, uint32_t world_and_generation) { + return (b2BodyId){index1, (uint16_t)(world_and_generation & 0xFFFF), (uint16_t)(world_and_generation >> 16)}; } -static b2ShapeId f2d_shape(int32_t index1, uint32_t wg) { - return (b2ShapeId){index1, (uint16_t)(wg & 0xFFFF), (uint16_t)(wg >> 16)}; +static b2ShapeId f2d_shape(int32_t index1, uint32_t world_and_generation) { + return (b2ShapeId){index1, (uint16_t)(world_and_generation & 0xFFFF), (uint16_t)(world_and_generation >> 16)}; } -static b2ChainId f2d_chain(int32_t index1, uint32_t wg) { - return (b2ChainId){index1, (uint16_t)(wg & 0xFFFF), (uint16_t)(wg >> 16)}; +static b2ChainId f2d_chain(int32_t index1, uint32_t world_and_generation) { + return (b2ChainId){index1, (uint16_t)(world_and_generation & 0xFFFF), (uint16_t)(world_and_generation >> 16)}; } -static b2JointId f2d_joint(int32_t index1, uint32_t wg) { - return (b2JointId){index1, (uint16_t)(wg & 0xFFFF), (uint16_t)(wg >> 16)}; +static b2JointId f2d_joint(int32_t index1, uint32_t world_and_generation) { + return (b2JointId){index1, (uint16_t)(world_and_generation & 0xFFFF), (uint16_t)(world_and_generation >> 16)}; } static uint32_t f2d_wg_body(b2BodyId id) { @@ -216,128 +216,128 @@ F2D_EXPORT void f2d_create_body( out[1] = (int32_t)f2d_wg_body(id); } -F2D_EXPORT void f2d_destroy_body(int32_t i1, uint32_t wg) { - b2DestroyBody(f2d_body(i1, wg)); +F2D_EXPORT void f2d_destroy_body(int32_t index1, uint32_t world_and_generation) { + b2DestroyBody(f2d_body(index1, world_and_generation)); } -F2D_EXPORT int f2d_body_is_valid(int32_t i1, uint32_t wg) { - return b2Body_IsValid(f2d_body(i1, wg)); +F2D_EXPORT int f2d_body_is_valid(int32_t index1, uint32_t world_and_generation) { + return b2Body_IsValid(f2d_body(index1, world_and_generation)); } -F2D_EXPORT void f2d_body_get_position(int32_t i1, uint32_t wg, float* out) { - b2Vec2 position = b2Body_GetPosition(f2d_body(i1, wg)); +F2D_EXPORT void f2d_body_get_position(int32_t index1, uint32_t world_and_generation, float* out) { + b2Vec2 position = b2Body_GetPosition(f2d_body(index1, world_and_generation)); out[0] = position.x; out[1] = position.y; } -F2D_EXPORT void f2d_body_get_rotation(int32_t i1, uint32_t wg, float* out) { - b2Rot rotation = b2Body_GetRotation(f2d_body(i1, wg)); +F2D_EXPORT void f2d_body_get_rotation(int32_t index1, uint32_t world_and_generation, float* out) { + b2Rot rotation = b2Body_GetRotation(f2d_body(index1, world_and_generation)); out[0] = rotation.c; out[1] = rotation.s; } -F2D_EXPORT void f2d_body_set_transform(int32_t i1, uint32_t wg, float px, +F2D_EXPORT void f2d_body_set_transform(int32_t index1, uint32_t world_and_generation, float px, float py, float qc, float qs) { - b2Body_SetTransform(f2d_body(i1, wg), (b2Vec2){px, py}, (b2Rot){qc, qs}); + b2Body_SetTransform(f2d_body(index1, world_and_generation), (b2Vec2){px, py}, (b2Rot){qc, qs}); } -F2D_EXPORT void f2d_body_get_linear_velocity(int32_t i1, uint32_t wg, +F2D_EXPORT void f2d_body_get_linear_velocity(int32_t index1, uint32_t world_and_generation, float* out) { - b2Vec2 velocity = b2Body_GetLinearVelocity(f2d_body(i1, wg)); + b2Vec2 velocity = b2Body_GetLinearVelocity(f2d_body(index1, world_and_generation)); out[0] = velocity.x; out[1] = velocity.y; } -F2D_EXPORT void f2d_body_set_linear_velocity(int32_t i1, uint32_t wg, float x, +F2D_EXPORT void f2d_body_set_linear_velocity(int32_t index1, uint32_t world_and_generation, float x, float y) { - b2Body_SetLinearVelocity(f2d_body(i1, wg), (b2Vec2){x, y}); + b2Body_SetLinearVelocity(f2d_body(index1, world_and_generation), (b2Vec2){x, y}); } -F2D_EXPORT void f2d_body_apply_force(int32_t i1, uint32_t wg, float fx, +F2D_EXPORT void f2d_body_apply_force(int32_t index1, uint32_t world_and_generation, float fx, float fy, float px, float py, int wake) { - b2Body_ApplyForce(f2d_body(i1, wg), (b2Vec2){fx, fy}, (b2Vec2){px, py}, + b2Body_ApplyForce(f2d_body(index1, world_and_generation), (b2Vec2){fx, fy}, (b2Vec2){px, py}, wake); } -F2D_EXPORT void f2d_body_apply_force_to_center(int32_t i1, uint32_t wg, +F2D_EXPORT void f2d_body_apply_force_to_center(int32_t index1, uint32_t world_and_generation, float fx, float fy, int wake) { - b2Body_ApplyForceToCenter(f2d_body(i1, wg), (b2Vec2){fx, fy}, wake); + b2Body_ApplyForceToCenter(f2d_body(index1, world_and_generation), (b2Vec2){fx, fy}, wake); } -F2D_EXPORT void f2d_body_apply_torque(int32_t i1, uint32_t wg, float torque, +F2D_EXPORT void f2d_body_apply_torque(int32_t index1, uint32_t world_and_generation, float torque, int wake) { - b2Body_ApplyTorque(f2d_body(i1, wg), torque, wake); + b2Body_ApplyTorque(f2d_body(index1, world_and_generation), torque, wake); } -F2D_EXPORT void f2d_body_apply_linear_impulse(int32_t i1, uint32_t wg, +F2D_EXPORT void f2d_body_apply_linear_impulse(int32_t index1, uint32_t world_and_generation, float ix, float iy, float px, float py, int wake) { - b2Body_ApplyLinearImpulse(f2d_body(i1, wg), (b2Vec2){ix, iy}, + b2Body_ApplyLinearImpulse(f2d_body(index1, world_and_generation), (b2Vec2){ix, iy}, (b2Vec2){px, py}, wake); } -F2D_EXPORT void f2d_body_apply_linear_impulse_to_center(int32_t i1, - uint32_t wg, float ix, +F2D_EXPORT void f2d_body_apply_linear_impulse_to_center(int32_t index1, + uint32_t world_and_generation, float ix, float iy, int wake) { - b2Body_ApplyLinearImpulseToCenter(f2d_body(i1, wg), (b2Vec2){ix, iy}, wake); + b2Body_ApplyLinearImpulseToCenter(f2d_body(index1, world_and_generation), (b2Vec2){ix, iy}, wake); } -F2D_EXPORT void f2d_body_apply_angular_impulse(int32_t i1, uint32_t wg, +F2D_EXPORT void f2d_body_apply_angular_impulse(int32_t index1, uint32_t world_and_generation, float impulse, int wake) { - b2Body_ApplyAngularImpulse(f2d_body(i1, wg), impulse, wake); + b2Body_ApplyAngularImpulse(f2d_body(index1, world_and_generation), impulse, wake); } -F2D_EXPORT void f2d_body_get_local_center(int32_t i1, uint32_t wg, +F2D_EXPORT void f2d_body_get_local_center(int32_t index1, uint32_t world_and_generation, float* out) { - b2Vec2 center = b2Body_GetLocalCenterOfMass(f2d_body(i1, wg)); + b2Vec2 center = b2Body_GetLocalCenterOfMass(f2d_body(index1, world_and_generation)); out[0] = center.x; out[1] = center.y; } -F2D_EXPORT void f2d_body_get_world_center(int32_t i1, uint32_t wg, +F2D_EXPORT void f2d_body_get_world_center(int32_t index1, uint32_t world_and_generation, float* out) { - b2Vec2 center = b2Body_GetWorldCenterOfMass(f2d_body(i1, wg)); + b2Vec2 center = b2Body_GetWorldCenterOfMass(f2d_body(index1, world_and_generation)); out[0] = center.x; out[1] = center.y; } -F2D_EXPORT void f2d_body_set_mass_data(int32_t i1, uint32_t wg, float mass, +F2D_EXPORT void f2d_body_set_mass_data(int32_t index1, uint32_t world_and_generation, float mass, float rotational_inertia, float cx, float cy) { b2MassData mass_data = {mass, {cx, cy}, rotational_inertia}; - b2Body_SetMassData(f2d_body(i1, wg), mass_data); + b2Body_SetMassData(f2d_body(index1, world_and_generation), mass_data); } -F2D_EXPORT const char* f2d_body_get_name(int32_t i1, uint32_t wg) { - return b2Body_GetName(f2d_body(i1, wg)); +F2D_EXPORT const char* f2d_body_get_name(int32_t index1, uint32_t world_and_generation) { + return b2Body_GetName(f2d_body(index1, world_and_generation)); } -F2D_EXPORT void f2d_body_set_name(int32_t i1, uint32_t wg, const char* name) { - b2Body_SetName(f2d_body(i1, wg), name); +F2D_EXPORT void f2d_body_set_name(int32_t index1, uint32_t world_and_generation, const char* name) { + b2Body_SetName(f2d_body(index1, world_and_generation), name); } -F2D_EXPORT void f2d_body_get_world_point(int32_t i1, uint32_t wg, float x, +F2D_EXPORT void f2d_body_get_world_point(int32_t index1, uint32_t world_and_generation, float x, float y, float* out) { - b2Vec2 point = b2Body_GetWorldPoint(f2d_body(i1, wg), (b2Vec2){x, y}); + b2Vec2 point = b2Body_GetWorldPoint(f2d_body(index1, world_and_generation), (b2Vec2){x, y}); out[0] = point.x; out[1] = point.y; } -F2D_EXPORT void f2d_body_get_local_point(int32_t i1, uint32_t wg, float x, +F2D_EXPORT void f2d_body_get_local_point(int32_t index1, uint32_t world_and_generation, float x, float y, float* out) { - b2Vec2 point = b2Body_GetLocalPoint(f2d_body(i1, wg), (b2Vec2){x, y}); + b2Vec2 point = b2Body_GetLocalPoint(f2d_body(index1, world_and_generation), (b2Vec2){x, y}); out[0] = point.x; out[1] = point.y; } -F2D_EXPORT int f2d_body_get_shape_count(int32_t i1, uint32_t wg) { - return b2Body_GetShapeCount(f2d_body(i1, wg)); +F2D_EXPORT int f2d_body_get_shape_count(int32_t index1, uint32_t world_and_generation) { + return b2Body_GetShapeCount(f2d_body(index1, world_and_generation)); } -F2D_EXPORT int f2d_body_get_shapes(int32_t i1, uint32_t wg, int32_t* out, +F2D_EXPORT int f2d_body_get_shapes(int32_t index1, uint32_t world_and_generation, int32_t* out, int capacity) { b2ShapeId shapes[capacity]; - int count = b2Body_GetShapes(f2d_body(i1, wg), shapes, capacity); + int count = b2Body_GetShapes(f2d_body(index1, world_and_generation), shapes, capacity); for (int i = 0; i < count; ++i) { out[2 * i] = shapes[i].index1; out[2 * i + 1] = (int32_t)f2d_wg_shape(shapes[i]); @@ -345,14 +345,14 @@ F2D_EXPORT int f2d_body_get_shapes(int32_t i1, uint32_t wg, int32_t* out, return count; } -F2D_EXPORT int f2d_body_get_joint_count(int32_t i1, uint32_t wg) { - return b2Body_GetJointCount(f2d_body(i1, wg)); +F2D_EXPORT int f2d_body_get_joint_count(int32_t index1, uint32_t world_and_generation) { + return b2Body_GetJointCount(f2d_body(index1, world_and_generation)); } -F2D_EXPORT int f2d_body_get_joints(int32_t i1, uint32_t wg, int32_t* out, +F2D_EXPORT int f2d_body_get_joints(int32_t index1, uint32_t world_and_generation, int32_t* out, int capacity) { b2JointId joints[capacity]; - int count = b2Body_GetJoints(f2d_body(i1, wg), joints, capacity); + int count = b2Body_GetJoints(f2d_body(index1, world_and_generation), joints, capacity); for (int i = 0; i < count; ++i) { out[2 * i] = joints[i].index1; out[2 * i + 1] = (int32_t)f2d_wg_joint(joints[i]); @@ -362,15 +362,15 @@ F2D_EXPORT int f2d_body_get_joints(int32_t i1, uint32_t wg, int32_t* out, // Scalar body accessors, generated by macro. #define F2D_BODY_GET_FLOAT(name, fn) \ - F2D_EXPORT float name(int32_t i1, uint32_t wg) { return fn(f2d_body(i1, wg)); } + F2D_EXPORT float name(int32_t index1, uint32_t world_and_generation) { return fn(f2d_body(index1, world_and_generation)); } #define F2D_BODY_SET_FLOAT(name, fn) \ - F2D_EXPORT void name(int32_t i1, uint32_t wg, float v) { fn(f2d_body(i1, wg), v); } + F2D_EXPORT void name(int32_t index1, uint32_t world_and_generation, float v) { fn(f2d_body(index1, world_and_generation), v); } #define F2D_BODY_GET_INT(name, fn) \ - F2D_EXPORT int name(int32_t i1, uint32_t wg) { return (int)fn(f2d_body(i1, wg)); } + F2D_EXPORT int name(int32_t index1, uint32_t world_and_generation) { return (int)fn(f2d_body(index1, world_and_generation)); } #define F2D_BODY_SET_BOOL(name, fn) \ - F2D_EXPORT void name(int32_t i1, uint32_t wg, int v) { fn(f2d_body(i1, wg), v); } + F2D_EXPORT void name(int32_t index1, uint32_t world_and_generation, int v) { fn(f2d_body(index1, world_and_generation), v); } #define F2D_BODY_CALL(name, fn) \ - F2D_EXPORT void name(int32_t i1, uint32_t wg) { fn(f2d_body(i1, wg)); } + F2D_EXPORT void name(int32_t index1, uint32_t world_and_generation) { fn(f2d_body(index1, world_and_generation)); } F2D_BODY_GET_FLOAT(f2d_body_get_angular_velocity, b2Body_GetAngularVelocity) F2D_BODY_SET_FLOAT(f2d_body_set_angular_velocity, b2Body_SetAngularVelocity) @@ -378,8 +378,8 @@ F2D_BODY_GET_FLOAT(f2d_body_get_mass, b2Body_GetMass) F2D_BODY_GET_FLOAT(f2d_body_get_rotational_inertia, b2Body_GetRotationalInertia) F2D_BODY_CALL(f2d_body_apply_mass_from_shapes, b2Body_ApplyMassFromShapes) F2D_BODY_GET_INT(f2d_body_get_type, b2Body_GetType) -F2D_EXPORT void f2d_body_set_type(int32_t i1, uint32_t wg, int type) { - b2Body_SetType(f2d_body(i1, wg), (b2BodyType)type); +F2D_EXPORT void f2d_body_set_type(int32_t index1, uint32_t world_and_generation, int type) { + b2Body_SetType(f2d_body(index1, world_and_generation), (b2BodyType)type); } F2D_BODY_GET_INT(f2d_body_is_awake, b2Body_IsAwake) F2D_BODY_SET_BOOL(f2d_body_set_awake, b2Body_SetAwake) @@ -451,36 +451,36 @@ static void f2d_out_shape(b2ShapeId id, int32_t* out) { out[1] = (int32_t)f2d_wg_shape(id); } -F2D_EXPORT void f2d_create_circle_shape(int32_t i1, uint32_t wg, +F2D_EXPORT void f2d_create_circle_shape(int32_t index1, uint32_t world_and_generation, const f2dShapeDef* def, float cx, float cy, float radius, int32_t* out) { b2ShapeDef shape_def = f2d_shape_def(def); b2Circle circle = {{cx, cy}, radius}; - f2d_out_shape(b2CreateCircleShape(f2d_body(i1, wg), &shape_def, &circle), + f2d_out_shape(b2CreateCircleShape(f2d_body(index1, world_and_generation), &shape_def, &circle), out); } -F2D_EXPORT void f2d_create_capsule_shape(int32_t i1, uint32_t wg, +F2D_EXPORT void f2d_create_capsule_shape(int32_t index1, uint32_t world_and_generation, const f2dShapeDef* def, float c1x, float c1y, float c2x, float c2y, float radius, int32_t* out) { b2ShapeDef shape_def = f2d_shape_def(def); b2Capsule capsule = {{c1x, c1y}, {c2x, c2y}, radius}; - f2d_out_shape(b2CreateCapsuleShape(f2d_body(i1, wg), &shape_def, &capsule), + f2d_out_shape(b2CreateCapsuleShape(f2d_body(index1, world_and_generation), &shape_def, &capsule), out); } -F2D_EXPORT void f2d_create_segment_shape(int32_t i1, uint32_t wg, +F2D_EXPORT void f2d_create_segment_shape(int32_t index1, uint32_t world_and_generation, const f2dShapeDef* def, float p1x, float p1y, float p2x, float p2y, int32_t* out) { b2ShapeDef shape_def = f2d_shape_def(def); b2Segment segment = {{p1x, p1y}, {p2x, p2y}}; - f2d_out_shape(b2CreateSegmentShape(f2d_body(i1, wg), &shape_def, &segment), + f2d_out_shape(b2CreateSegmentShape(f2d_body(index1, world_and_generation), &shape_def, &segment), out); } -F2D_EXPORT void f2d_create_box_shape(int32_t i1, uint32_t wg, +F2D_EXPORT void f2d_create_box_shape(int32_t index1, uint32_t world_and_generation, const f2dShapeDef* def, float half_width, float half_height, float cx, float cy, float qc, float qs, float radius, @@ -489,12 +489,12 @@ F2D_EXPORT void f2d_create_box_shape(int32_t i1, uint32_t wg, b2Polygon polygon = b2MakeOffsetRoundedBox(half_width, half_height, (b2Vec2){cx, cy}, (b2Rot){qc, qs}, radius); - f2d_out_shape(b2CreatePolygonShape(f2d_body(i1, wg), &shape_def, &polygon), + f2d_out_shape(b2CreatePolygonShape(f2d_body(index1, world_and_generation), &shape_def, &polygon), out); } // Returns false when the hull is degenerate. -F2D_EXPORT int f2d_create_polygon_shape(int32_t i1, uint32_t wg, +F2D_EXPORT int f2d_create_polygon_shape(int32_t index1, uint32_t world_and_generation, const f2dShapeDef* def, const float* points, int count, float radius, int32_t* out) { @@ -504,29 +504,29 @@ F2D_EXPORT int f2d_create_polygon_shape(int32_t i1, uint32_t wg, } b2ShapeDef shape_def = f2d_shape_def(def); b2Polygon polygon = b2MakePolygon(&hull, radius); - f2d_out_shape(b2CreatePolygonShape(f2d_body(i1, wg), &shape_def, &polygon), + f2d_out_shape(b2CreatePolygonShape(f2d_body(index1, world_and_generation), &shape_def, &polygon), out); return 1; } -F2D_EXPORT void f2d_destroy_shape(int32_t i1, uint32_t wg, +F2D_EXPORT void f2d_destroy_shape(int32_t index1, uint32_t world_and_generation, int update_body_mass) { - b2DestroyShape(f2d_shape(i1, wg), update_body_mass); + b2DestroyShape(f2d_shape(index1, world_and_generation), update_body_mass); } -F2D_EXPORT void f2d_shape_get_body(int32_t i1, uint32_t wg, int32_t* out) { - b2BodyId id = b2Shape_GetBody(f2d_shape(i1, wg)); +F2D_EXPORT void f2d_shape_get_body(int32_t index1, uint32_t world_and_generation, int32_t* out) { + b2BodyId id = b2Shape_GetBody(f2d_shape(index1, world_and_generation)); out[0] = id.index1; out[1] = (int32_t)f2d_wg_body(id); } -F2D_EXPORT void f2d_shape_set_density(int32_t i1, uint32_t wg, float density, +F2D_EXPORT void f2d_shape_set_density(int32_t index1, uint32_t world_and_generation, float density, int update_body_mass) { - b2Shape_SetDensity(f2d_shape(i1, wg), density, update_body_mass); + b2Shape_SetDensity(f2d_shape(index1, world_and_generation), density, update_body_mass); } -F2D_EXPORT void f2d_shape_get_filter(int32_t i1, uint32_t wg, int32_t* out) { - b2Filter filter = b2Shape_GetFilter(f2d_shape(i1, wg)); +F2D_EXPORT void f2d_shape_get_filter(int32_t index1, uint32_t world_and_generation, int32_t* out) { + b2Filter filter = b2Shape_GetFilter(f2d_shape(index1, world_and_generation)); out[0] = (int32_t)(filter.categoryBits & 0xFFFFFFFF); out[1] = (int32_t)(filter.categoryBits >> 32); out[2] = (int32_t)(filter.maskBits & 0xFFFFFFFF); @@ -534,22 +534,66 @@ F2D_EXPORT void f2d_shape_get_filter(int32_t i1, uint32_t wg, int32_t* out) { out[4] = filter.groupIndex; } -F2D_EXPORT void f2d_shape_set_filter(int32_t i1, uint32_t wg, +F2D_EXPORT void f2d_shape_set_filter(int32_t index1, uint32_t world_and_generation, uint32_t category_lo, uint32_t category_hi, uint32_t mask_lo, uint32_t mask_hi, int32_t group_index) { - b2Shape_SetFilter(f2d_shape(i1, wg), f2d_filter(category_lo, category_hi, + b2Shape_SetFilter(f2d_shape(index1, world_and_generation), f2d_filter(category_lo, category_hi, mask_lo, mask_hi, group_index)); } -F2D_EXPORT int f2d_shape_test_point(int32_t i1, uint32_t wg, float x, +F2D_EXPORT int f2d_shape_test_point(int32_t index1, uint32_t world_and_generation, float x, float y) { - return b2Shape_TestPoint(f2d_shape(i1, wg), (b2Vec2){x, y}); + return b2Shape_TestPoint(f2d_shape(index1, world_and_generation), (b2Vec2){x, y}); } -F2D_EXPORT void f2d_shape_get_aabb(int32_t i1, uint32_t wg, float* out) { - b2AABB aabb = b2Shape_GetAABB(f2d_shape(i1, wg)); +F2D_EXPORT void f2d_shape_get_circle(int32_t index1, uint32_t world_and_generation, float* out) { + b2Circle circle = b2Shape_GetCircle(f2d_shape(index1, world_and_generation)); + out[0] = circle.center.x; + out[1] = circle.center.y; + out[2] = circle.radius; +} + +F2D_EXPORT void f2d_shape_get_capsule(int32_t index1, uint32_t world_and_generation, float* out) { + b2Capsule capsule = b2Shape_GetCapsule(f2d_shape(index1, world_and_generation)); + out[0] = capsule.center1.x; + out[1] = capsule.center1.y; + out[2] = capsule.center2.x; + out[3] = capsule.center2.y; + out[4] = capsule.radius; +} + +F2D_EXPORT void f2d_shape_get_segment(int32_t index1, uint32_t world_and_generation, float* out) { + b2Segment segment = b2Shape_GetSegment(f2d_shape(index1, world_and_generation)); + out[0] = segment.point1.x; + out[1] = segment.point1.y; + out[2] = segment.point2.x; + out[3] = segment.point2.y; +} + +F2D_EXPORT void f2d_shape_get_chain_segment(int32_t index1, uint32_t world_and_generation, + float* out) { + b2ChainSegment chain_segment = b2Shape_GetChainSegment(f2d_shape(index1, world_and_generation)); + out[0] = chain_segment.segment.point1.x; + out[1] = chain_segment.segment.point1.y; + out[2] = chain_segment.segment.point2.x; + out[3] = chain_segment.segment.point2.y; +} + +// Writes the radius followed by the vertex pairs; returns the vertex count. +F2D_EXPORT int f2d_shape_get_polygon(int32_t index1, uint32_t world_and_generation, float* out) { + b2Polygon polygon = b2Shape_GetPolygon(f2d_shape(index1, world_and_generation)); + out[0] = polygon.radius; + for (int i = 0; i < polygon.count; ++i) { + out[1 + 2 * i] = polygon.vertices[i].x; + out[2 + 2 * i] = polygon.vertices[i].y; + } + return polygon.count; +} + +F2D_EXPORT void f2d_shape_get_aabb(int32_t index1, uint32_t world_and_generation, float* out) { + b2AABB aabb = b2Shape_GetAABB(f2d_shape(index1, world_and_generation)); out[0] = aabb.lowerBound.x; out[1] = aabb.lowerBound.y; out[2] = aabb.upperBound.x; @@ -557,13 +601,13 @@ F2D_EXPORT void f2d_shape_get_aabb(int32_t i1, uint32_t wg, float* out) { } #define F2D_SHAPE_GET_FLOAT(name, fn) \ - F2D_EXPORT float name(int32_t i1, uint32_t wg) { return fn(f2d_shape(i1, wg)); } + F2D_EXPORT float name(int32_t index1, uint32_t world_and_generation) { return fn(f2d_shape(index1, world_and_generation)); } #define F2D_SHAPE_SET_FLOAT(name, fn) \ - F2D_EXPORT void name(int32_t i1, uint32_t wg, float v) { fn(f2d_shape(i1, wg), v); } + F2D_EXPORT void name(int32_t index1, uint32_t world_and_generation, float v) { fn(f2d_shape(index1, world_and_generation), v); } #define F2D_SHAPE_GET_INT(name, fn) \ - F2D_EXPORT int name(int32_t i1, uint32_t wg) { return (int)fn(f2d_shape(i1, wg)); } + F2D_EXPORT int name(int32_t index1, uint32_t world_and_generation) { return (int)fn(f2d_shape(index1, world_and_generation)); } #define F2D_SHAPE_SET_BOOL(name, fn) \ - F2D_EXPORT void name(int32_t i1, uint32_t wg, int v) { fn(f2d_shape(i1, wg), v); } + F2D_EXPORT void name(int32_t index1, uint32_t world_and_generation, int v) { fn(f2d_shape(index1, world_and_generation), v); } F2D_SHAPE_GET_INT(f2d_shape_is_valid, b2Shape_IsValid) F2D_SHAPE_GET_INT(f2d_shape_get_type, b2Shape_GetType) @@ -588,7 +632,7 @@ F2D_SHAPE_SET_BOOL(f2d_shape_enable_pre_solve_events, // Chains. -F2D_EXPORT void f2d_create_chain(int32_t i1, uint32_t wg, const float* points, +F2D_EXPORT void f2d_create_chain(int32_t index1, uint32_t world_and_generation, const float* points, int point_count, const float* materials, int material_count, uint32_t category_lo, uint32_t category_hi, uint32_t mask_lo, @@ -610,43 +654,43 @@ F2D_EXPORT void f2d_create_chain(int32_t i1, uint32_t wg, const float* points, group_index); def.isLoop = is_loop; def.enableSensorEvents = enable_sensor_events; - b2ChainId id = b2CreateChain(f2d_body(i1, wg), &def); + b2ChainId id = b2CreateChain(f2d_body(index1, world_and_generation), &def); out[0] = id.index1; out[1] = (int32_t)f2d_wg_chain(id); } -F2D_EXPORT void f2d_destroy_chain(int32_t i1, uint32_t wg) { - b2DestroyChain(f2d_chain(i1, wg)); +F2D_EXPORT void f2d_destroy_chain(int32_t index1, uint32_t world_and_generation) { + b2DestroyChain(f2d_chain(index1, world_and_generation)); } -F2D_EXPORT int f2d_chain_is_valid(int32_t i1, uint32_t wg) { - return b2Chain_IsValid(f2d_chain(i1, wg)); +F2D_EXPORT int f2d_chain_is_valid(int32_t index1, uint32_t world_and_generation) { + return b2Chain_IsValid(f2d_chain(index1, world_and_generation)); } -F2D_EXPORT void f2d_chain_set_friction(int32_t i1, uint32_t wg, float v) { - b2Chain_SetFriction(f2d_chain(i1, wg), v); +F2D_EXPORT void f2d_chain_set_friction(int32_t index1, uint32_t world_and_generation, float v) { + b2Chain_SetFriction(f2d_chain(index1, world_and_generation), v); } -F2D_EXPORT float f2d_chain_get_friction(int32_t i1, uint32_t wg) { - return b2Chain_GetFriction(f2d_chain(i1, wg)); +F2D_EXPORT float f2d_chain_get_friction(int32_t index1, uint32_t world_and_generation) { + return b2Chain_GetFriction(f2d_chain(index1, world_and_generation)); } -F2D_EXPORT void f2d_chain_set_restitution(int32_t i1, uint32_t wg, float v) { - b2Chain_SetRestitution(f2d_chain(i1, wg), v); +F2D_EXPORT void f2d_chain_set_restitution(int32_t index1, uint32_t world_and_generation, float v) { + b2Chain_SetRestitution(f2d_chain(index1, world_and_generation), v); } -F2D_EXPORT float f2d_chain_get_restitution(int32_t i1, uint32_t wg) { - return b2Chain_GetRestitution(f2d_chain(i1, wg)); +F2D_EXPORT float f2d_chain_get_restitution(int32_t index1, uint32_t world_and_generation) { + return b2Chain_GetRestitution(f2d_chain(index1, world_and_generation)); } -F2D_EXPORT int f2d_chain_get_segment_count(int32_t i1, uint32_t wg) { - return b2Chain_GetSegmentCount(f2d_chain(i1, wg)); +F2D_EXPORT int f2d_chain_get_segment_count(int32_t index1, uint32_t world_and_generation) { + return b2Chain_GetSegmentCount(f2d_chain(index1, world_and_generation)); } -F2D_EXPORT int f2d_chain_get_segments(int32_t i1, uint32_t wg, int32_t* out, +F2D_EXPORT int f2d_chain_get_segments(int32_t index1, uint32_t world_and_generation, int32_t* out, int capacity) { b2ShapeId segments[capacity]; - int count = b2Chain_GetSegments(f2d_chain(i1, wg), segments, capacity); + int count = b2Chain_GetSegments(f2d_chain(index1, world_and_generation), segments, capacity); for (int i = 0; i < count; ++i) { out[2 * i] = segments[i].index1; out[2 * i + 1] = (int32_t)f2d_wg_shape(segments[i]); @@ -662,15 +706,15 @@ static void f2d_out_joint(b2JointId id, int32_t* out) { } F2D_EXPORT void f2d_create_distance_joint( - uint32_t w, int32_t ai1, uint32_t awg, int32_t bi1, uint32_t bwg, + uint32_t w, int32_t a_index1, uint32_t a_world_and_generation, int32_t b_index1, uint32_t b_world_and_generation, float anchor_ax, float anchor_ay, float anchor_bx, float anchor_by, float length, int enable_spring, float hertz, float damping_ratio, int enable_limit, float min_length, float max_length, int enable_motor, float max_motor_force, float motor_speed, int collide_connected, int32_t* out) { b2DistanceJointDef def = b2DefaultDistanceJointDef(); - def.bodyIdA = f2d_body(ai1, awg); - def.bodyIdB = f2d_body(bi1, bwg); + def.bodyIdA = f2d_body(a_index1, a_world_and_generation); + def.bodyIdB = f2d_body(b_index1, b_world_and_generation); def.localAnchorA = (b2Vec2){anchor_ax, anchor_ay}; def.localAnchorB = (b2Vec2){anchor_bx, anchor_by}; def.length = length; @@ -687,25 +731,25 @@ F2D_EXPORT void f2d_create_distance_joint( f2d_out_joint(b2CreateDistanceJoint(f2d_world(w), &def), out); } -F2D_EXPORT void f2d_create_filter_joint(uint32_t w, int32_t ai1, uint32_t awg, - int32_t bi1, uint32_t bwg, +F2D_EXPORT void f2d_create_filter_joint(uint32_t w, int32_t a_index1, uint32_t a_world_and_generation, + int32_t b_index1, uint32_t b_world_and_generation, int32_t* out) { b2FilterJointDef def = b2DefaultFilterJointDef(); - def.bodyIdA = f2d_body(ai1, awg); - def.bodyIdB = f2d_body(bi1, bwg); + def.bodyIdA = f2d_body(a_index1, a_world_and_generation); + def.bodyIdB = f2d_body(b_index1, b_world_and_generation); f2d_out_joint(b2CreateFilterJoint(f2d_world(w), &def), out); } -F2D_EXPORT void f2d_create_motor_joint(uint32_t w, int32_t ai1, uint32_t awg, - int32_t bi1, uint32_t bwg, +F2D_EXPORT void f2d_create_motor_joint(uint32_t w, int32_t a_index1, uint32_t a_world_and_generation, + int32_t b_index1, uint32_t b_world_and_generation, float offset_x, float offset_y, float angular_offset, float max_force, float max_torque, float correction_factor, int collide_connected, int32_t* out) { b2MotorJointDef def = b2DefaultMotorJointDef(); - def.bodyIdA = f2d_body(ai1, awg); - def.bodyIdB = f2d_body(bi1, bwg); + def.bodyIdA = f2d_body(a_index1, a_world_and_generation); + def.bodyIdB = f2d_body(b_index1, b_world_and_generation); def.linearOffset = (b2Vec2){offset_x, offset_y}; def.angularOffset = angular_offset; def.maxForce = max_force; @@ -715,15 +759,15 @@ F2D_EXPORT void f2d_create_motor_joint(uint32_t w, int32_t ai1, uint32_t awg, f2d_out_joint(b2CreateMotorJoint(f2d_world(w), &def), out); } -F2D_EXPORT void f2d_create_mouse_joint(uint32_t w, int32_t ai1, uint32_t awg, - int32_t bi1, uint32_t bwg, +F2D_EXPORT void f2d_create_mouse_joint(uint32_t w, int32_t a_index1, uint32_t a_world_and_generation, + int32_t b_index1, uint32_t b_world_and_generation, float target_x, float target_y, float hertz, float damping_ratio, float max_force, int collide_connected, int32_t* out) { b2MouseJointDef def = b2DefaultMouseJointDef(); - def.bodyIdA = f2d_body(ai1, awg); - def.bodyIdB = f2d_body(bi1, bwg); + def.bodyIdA = f2d_body(a_index1, a_world_and_generation); + def.bodyIdB = f2d_body(b_index1, b_world_and_generation); def.target = (b2Vec2){target_x, target_y}; def.hertz = hertz; def.dampingRatio = damping_ratio; @@ -733,7 +777,7 @@ F2D_EXPORT void f2d_create_mouse_joint(uint32_t w, int32_t ai1, uint32_t awg, } F2D_EXPORT void f2d_create_prismatic_joint( - uint32_t w, int32_t ai1, uint32_t awg, int32_t bi1, uint32_t bwg, + uint32_t w, int32_t a_index1, uint32_t a_world_and_generation, int32_t b_index1, uint32_t b_world_and_generation, float anchor_ax, float anchor_ay, float anchor_bx, float anchor_by, float axis_x, float axis_y, float reference_angle, float target_translation, int enable_spring, float hertz, @@ -741,8 +785,8 @@ F2D_EXPORT void f2d_create_prismatic_joint( float upper_translation, int enable_motor, float max_motor_force, float motor_speed, int collide_connected, int32_t* out) { b2PrismaticJointDef def = b2DefaultPrismaticJointDef(); - def.bodyIdA = f2d_body(ai1, awg); - def.bodyIdB = f2d_body(bi1, bwg); + def.bodyIdA = f2d_body(a_index1, a_world_and_generation); + def.bodyIdB = f2d_body(b_index1, b_world_and_generation); def.localAnchorA = (b2Vec2){anchor_ax, anchor_ay}; def.localAnchorB = (b2Vec2){anchor_bx, anchor_by}; def.localAxisA = (b2Vec2){axis_x, axis_y}; @@ -762,15 +806,15 @@ F2D_EXPORT void f2d_create_prismatic_joint( } F2D_EXPORT void f2d_create_revolute_joint( - uint32_t w, int32_t ai1, uint32_t awg, int32_t bi1, uint32_t bwg, + uint32_t w, int32_t a_index1, uint32_t a_world_and_generation, int32_t b_index1, uint32_t b_world_and_generation, float anchor_ax, float anchor_ay, float anchor_bx, float anchor_by, float reference_angle, float target_angle, int enable_spring, float hertz, float damping_ratio, int enable_limit, float lower_angle, float upper_angle, int enable_motor, float max_motor_torque, float motor_speed, float draw_size, int collide_connected, int32_t* out) { b2RevoluteJointDef def = b2DefaultRevoluteJointDef(); - def.bodyIdA = f2d_body(ai1, awg); - def.bodyIdB = f2d_body(bi1, bwg); + def.bodyIdA = f2d_body(a_index1, a_world_and_generation); + def.bodyIdB = f2d_body(b_index1, b_world_and_generation); def.localAnchorA = (b2Vec2){anchor_ax, anchor_ay}; def.localAnchorB = (b2Vec2){anchor_bx, anchor_by}; def.referenceAngle = reference_angle; @@ -790,14 +834,14 @@ F2D_EXPORT void f2d_create_revolute_joint( } F2D_EXPORT void f2d_create_weld_joint( - uint32_t w, int32_t ai1, uint32_t awg, int32_t bi1, uint32_t bwg, + uint32_t w, int32_t a_index1, uint32_t a_world_and_generation, int32_t b_index1, uint32_t b_world_and_generation, float anchor_ax, float anchor_ay, float anchor_bx, float anchor_by, float reference_angle, float linear_hertz, float angular_hertz, float linear_damping_ratio, float angular_damping_ratio, int collide_connected, int32_t* out) { b2WeldJointDef def = b2DefaultWeldJointDef(); - def.bodyIdA = f2d_body(ai1, awg); - def.bodyIdB = f2d_body(bi1, bwg); + def.bodyIdA = f2d_body(a_index1, a_world_and_generation); + def.bodyIdB = f2d_body(b_index1, b_world_and_generation); def.localAnchorA = (b2Vec2){anchor_ax, anchor_ay}; def.localAnchorB = (b2Vec2){anchor_bx, anchor_by}; def.referenceAngle = reference_angle; @@ -810,15 +854,15 @@ F2D_EXPORT void f2d_create_weld_joint( } F2D_EXPORT void f2d_create_wheel_joint( - uint32_t w, int32_t ai1, uint32_t awg, int32_t bi1, uint32_t bwg, + uint32_t w, int32_t a_index1, uint32_t a_world_and_generation, int32_t b_index1, uint32_t b_world_and_generation, float anchor_ax, float anchor_ay, float anchor_bx, float anchor_by, float axis_x, float axis_y, int enable_spring, float hertz, float damping_ratio, int enable_limit, float lower_translation, float upper_translation, int enable_motor, float max_motor_torque, float motor_speed, int collide_connected, int32_t* out) { b2WheelJointDef def = b2DefaultWheelJointDef(); - def.bodyIdA = f2d_body(ai1, awg); - def.bodyIdB = f2d_body(bi1, bwg); + def.bodyIdA = f2d_body(a_index1, a_world_and_generation); + def.bodyIdB = f2d_body(b_index1, b_world_and_generation); def.localAnchorA = (b2Vec2){anchor_ax, anchor_ay}; def.localAnchorB = (b2Vec2){anchor_bx, anchor_by}; def.localAxisA = (b2Vec2){axis_x, axis_y}; @@ -835,79 +879,79 @@ F2D_EXPORT void f2d_create_wheel_joint( f2d_out_joint(b2CreateWheelJoint(f2d_world(w), &def), out); } -F2D_EXPORT void f2d_destroy_joint(int32_t i1, uint32_t wg) { - b2DestroyJoint(f2d_joint(i1, wg)); +F2D_EXPORT void f2d_destroy_joint(int32_t index1, uint32_t world_and_generation) { + b2DestroyJoint(f2d_joint(index1, world_and_generation)); } -F2D_EXPORT int f2d_joint_is_valid(int32_t i1, uint32_t wg) { - return b2Joint_IsValid(f2d_joint(i1, wg)); +F2D_EXPORT int f2d_joint_is_valid(int32_t index1, uint32_t world_and_generation) { + return b2Joint_IsValid(f2d_joint(index1, world_and_generation)); } -F2D_EXPORT int f2d_joint_get_type(int32_t i1, uint32_t wg) { - return (int)b2Joint_GetType(f2d_joint(i1, wg)); +F2D_EXPORT int f2d_joint_get_type(int32_t index1, uint32_t world_and_generation) { + return (int)b2Joint_GetType(f2d_joint(index1, world_and_generation)); } -F2D_EXPORT void f2d_joint_get_body_a(int32_t i1, uint32_t wg, int32_t* out) { - b2BodyId id = b2Joint_GetBodyA(f2d_joint(i1, wg)); +F2D_EXPORT void f2d_joint_get_body_a(int32_t index1, uint32_t world_and_generation, int32_t* out) { + b2BodyId id = b2Joint_GetBodyA(f2d_joint(index1, world_and_generation)); out[0] = id.index1; out[1] = (int32_t)f2d_wg_body(id); } -F2D_EXPORT void f2d_joint_get_body_b(int32_t i1, uint32_t wg, int32_t* out) { - b2BodyId id = b2Joint_GetBodyB(f2d_joint(i1, wg)); +F2D_EXPORT void f2d_joint_get_body_b(int32_t index1, uint32_t world_and_generation, int32_t* out) { + b2BodyId id = b2Joint_GetBodyB(f2d_joint(index1, world_and_generation)); out[0] = id.index1; out[1] = (int32_t)f2d_wg_body(id); } -F2D_EXPORT void f2d_joint_get_local_anchor_a(int32_t i1, uint32_t wg, +F2D_EXPORT void f2d_joint_get_local_anchor_a(int32_t index1, uint32_t world_and_generation, float* out) { - b2Vec2 anchor = b2Joint_GetLocalAnchorA(f2d_joint(i1, wg)); + b2Vec2 anchor = b2Joint_GetLocalAnchorA(f2d_joint(index1, world_and_generation)); out[0] = anchor.x; out[1] = anchor.y; } -F2D_EXPORT void f2d_joint_get_local_anchor_b(int32_t i1, uint32_t wg, +F2D_EXPORT void f2d_joint_get_local_anchor_b(int32_t index1, uint32_t world_and_generation, float* out) { - b2Vec2 anchor = b2Joint_GetLocalAnchorB(f2d_joint(i1, wg)); + b2Vec2 anchor = b2Joint_GetLocalAnchorB(f2d_joint(index1, world_and_generation)); out[0] = anchor.x; out[1] = anchor.y; } -F2D_EXPORT int f2d_joint_get_collide_connected(int32_t i1, uint32_t wg) { - return b2Joint_GetCollideConnected(f2d_joint(i1, wg)); +F2D_EXPORT int f2d_joint_get_collide_connected(int32_t index1, uint32_t world_and_generation) { + return b2Joint_GetCollideConnected(f2d_joint(index1, world_and_generation)); } -F2D_EXPORT void f2d_joint_set_collide_connected(int32_t i1, uint32_t wg, +F2D_EXPORT void f2d_joint_set_collide_connected(int32_t index1, uint32_t world_and_generation, int v) { - b2Joint_SetCollideConnected(f2d_joint(i1, wg), v); + b2Joint_SetCollideConnected(f2d_joint(index1, world_and_generation), v); } -F2D_EXPORT void f2d_joint_wake_bodies(int32_t i1, uint32_t wg) { - b2Joint_WakeBodies(f2d_joint(i1, wg)); +F2D_EXPORT void f2d_joint_wake_bodies(int32_t index1, uint32_t world_and_generation) { + b2Joint_WakeBodies(f2d_joint(index1, world_and_generation)); } -F2D_EXPORT void f2d_joint_get_constraint_force(int32_t i1, uint32_t wg, +F2D_EXPORT void f2d_joint_get_constraint_force(int32_t index1, uint32_t world_and_generation, float* out) { - b2Vec2 force = b2Joint_GetConstraintForce(f2d_joint(i1, wg)); + b2Vec2 force = b2Joint_GetConstraintForce(f2d_joint(index1, world_and_generation)); out[0] = force.x; out[1] = force.y; } -F2D_EXPORT float f2d_joint_get_constraint_torque(int32_t i1, uint32_t wg) { - return b2Joint_GetConstraintTorque(f2d_joint(i1, wg)); +F2D_EXPORT float f2d_joint_get_constraint_torque(int32_t index1, uint32_t world_and_generation) { + return b2Joint_GetConstraintTorque(f2d_joint(index1, world_and_generation)); } #define F2D_JOINT_GET_FLOAT(name, fn) \ - F2D_EXPORT float name(int32_t i1, uint32_t wg) { return fn(f2d_joint(i1, wg)); } + F2D_EXPORT float name(int32_t index1, uint32_t world_and_generation) { return fn(f2d_joint(index1, world_and_generation)); } #define F2D_JOINT_SET_FLOAT(name, fn) \ - F2D_EXPORT void name(int32_t i1, uint32_t wg, float v) { fn(f2d_joint(i1, wg), v); } + F2D_EXPORT void name(int32_t index1, uint32_t world_and_generation, float v) { fn(f2d_joint(index1, world_and_generation), v); } #define F2D_JOINT_GET_INT(name, fn) \ - F2D_EXPORT int name(int32_t i1, uint32_t wg) { return (int)fn(f2d_joint(i1, wg)); } + F2D_EXPORT int name(int32_t index1, uint32_t world_and_generation) { return (int)fn(f2d_joint(index1, world_and_generation)); } #define F2D_JOINT_SET_BOOL(name, fn) \ - F2D_EXPORT void name(int32_t i1, uint32_t wg, int v) { fn(f2d_joint(i1, wg), v); } + F2D_EXPORT void name(int32_t index1, uint32_t world_and_generation, int v) { fn(f2d_joint(index1, world_and_generation), v); } #define F2D_JOINT_SET_RANGE(name, fn) \ - F2D_EXPORT void name(int32_t i1, uint32_t wg, float a, float b) { \ - fn(f2d_joint(i1, wg), a, b); \ + F2D_EXPORT void name(int32_t index1, uint32_t world_and_generation, float a, float b) { \ + fn(f2d_joint(index1, world_and_generation), a, b); \ } F2D_JOINT_GET_FLOAT(f2d_distance_joint_get_length, b2DistanceJoint_GetLength) @@ -951,16 +995,16 @@ F2D_JOINT_SET_FLOAT(f2d_distance_joint_set_max_motor_force, F2D_JOINT_GET_FLOAT(f2d_distance_joint_get_motor_force, b2DistanceJoint_GetMotorForce) -F2D_EXPORT void f2d_motor_joint_get_linear_offset(int32_t i1, uint32_t wg, +F2D_EXPORT void f2d_motor_joint_get_linear_offset(int32_t index1, uint32_t world_and_generation, float* out) { - b2Vec2 offset = b2MotorJoint_GetLinearOffset(f2d_joint(i1, wg)); + b2Vec2 offset = b2MotorJoint_GetLinearOffset(f2d_joint(index1, world_and_generation)); out[0] = offset.x; out[1] = offset.y; } -F2D_EXPORT void f2d_motor_joint_set_linear_offset(int32_t i1, uint32_t wg, +F2D_EXPORT void f2d_motor_joint_set_linear_offset(int32_t index1, uint32_t world_and_generation, float x, float y) { - b2MotorJoint_SetLinearOffset(f2d_joint(i1, wg), (b2Vec2){x, y}); + b2MotorJoint_SetLinearOffset(f2d_joint(index1, world_and_generation), (b2Vec2){x, y}); } F2D_JOINT_GET_FLOAT(f2d_motor_joint_get_angular_offset, @@ -976,16 +1020,16 @@ F2D_JOINT_GET_FLOAT(f2d_motor_joint_get_correction_factor, F2D_JOINT_SET_FLOAT(f2d_motor_joint_set_correction_factor, b2MotorJoint_SetCorrectionFactor) -F2D_EXPORT void f2d_mouse_joint_get_target(int32_t i1, uint32_t wg, +F2D_EXPORT void f2d_mouse_joint_get_target(int32_t index1, uint32_t world_and_generation, float* out) { - b2Vec2 target = b2MouseJoint_GetTarget(f2d_joint(i1, wg)); + b2Vec2 target = b2MouseJoint_GetTarget(f2d_joint(index1, world_and_generation)); out[0] = target.x; out[1] = target.y; } -F2D_EXPORT void f2d_mouse_joint_set_target(int32_t i1, uint32_t wg, float x, +F2D_EXPORT void f2d_mouse_joint_set_target(int32_t index1, uint32_t world_and_generation, float x, float y) { - b2MouseJoint_SetTarget(f2d_joint(i1, wg), (b2Vec2){x, y}); + b2MouseJoint_SetTarget(f2d_joint(index1, world_and_generation), (b2Vec2){x, y}); } F2D_JOINT_GET_FLOAT(f2d_mouse_joint_get_spring_hertz, @@ -1140,7 +1184,7 @@ F2D_JOINT_GET_FLOAT(f2d_wheel_joint_get_motor_torque, // - end contact: 4 [aI1, aWg, bI1, bWg] // - hit contact: 9 [aI1, aWg, bI1, bWg, px, py, nx, ny, approachSpeed] // - sensor: 4 [sensorI1, sensorWg, visitorI1, visitorWg] -// - body move: 7 [i1, wg, x, y, qc, qs, fellAsleep] +// - body move: 7 [index1, world_and_generation, x, y, qc, qs, fellAsleep] F2D_EXPORT void f2d_world_get_contact_event_counts(uint32_t w, int32_t* out) { b2ContactEvents events = b2World_GetContactEvents(f2d_world(w)); @@ -1256,7 +1300,7 @@ F2D_EXPORT int f2d_world_get_body_events(uint32_t w, double* out) { // Queries. // Returns whether something was hit; the hit is written as -// [i1, wg, px, py, nx, ny, fraction] float32s with ids stored via the +// [index1, world_and_generation, px, py, nx, ny, fraction] float32s with ids stored via the // int32 view of the same buffer slots 0 and 1. F2D_EXPORT int f2d_world_cast_ray_closest(uint32_t w, float ox, float oy, float tx, float ty, diff --git a/packages/forge2d/test/api/shape_test.dart b/packages/forge2d/test/api/shape_test.dart index e2718ac0..5385dcdc 100644 --- a/packages/forge2d/test/api/shape_test.dart +++ b/packages/forge2d/test/api/shape_test.dart @@ -113,6 +113,65 @@ void main() { expect(shape.userData, 'updated'); }); + test('geometry reads back every shape kind', () { + final circle = body + .createShape(Circle(center: Vector2(1, 2), radius: 0.75)) + .geometry; + expect(circle, isA()); + circle as Circle; + expect(circle.center.x, closeTo(1, 1e-6)); + expect(circle.center.y, closeTo(2, 1e-6)); + expect(circle.radius, closeTo(0.75, 1e-6)); + + final capsule = body + .createShape( + Capsule( + center1: Vector2(0, -1), + center2: Vector2(0, 1), + radius: 0.5, + ), + ) + .geometry; + expect(capsule, isA()); + capsule as Capsule; + expect(capsule.center1.y, closeTo(-1, 1e-6)); + expect(capsule.center2.y, closeTo(1, 1e-6)); + expect(capsule.radius, closeTo(0.5, 1e-6)); + + final segment = body + .createShape(Segment(point1: Vector2(-2, 0), point2: Vector2(2, 1))) + .geometry; + expect(segment, isA()); + segment as Segment; + expect(segment.point1.x, closeTo(-2, 1e-6)); + expect(segment.point2.y, closeTo(1, 1e-6)); + + final box = body.createShape(Polygon.box(1.5, 0.5)).geometry; + expect(box, isA()); + box as Polygon; + expect(box.points, hasLength(4)); + expect(box.radius, 0); + final xs = box.points!.map((point) => point.x.abs()); + final ys = box.points!.map((point) => point.y.abs()); + expect(xs.every((x) => (x - 1.5).abs() < 1e-5), isTrue); + expect(ys.every((y) => (y - 0.5).abs() < 1e-5), isTrue); + + final chainSegments = body + .createChain( + ChainDef( + points: [ + Vector2(-6, 0), + Vector2(-2, 0), + Vector2(2, 0), + Vector2(6, 0), + ], + ), + ) + .segments; + final chainSegment = chainSegments.first.geometry; + expect(chainSegment, isA()); + }); + test('testPoint and aabb agree with the geometry', () { final shape = body.createShape(Polygon.square(1)); expect(shape.testPoint(Vector2(0.5, 0.5)), isTrue); From 8daef64e734f17746d9ea90c5f52ff685ba28dca Mon Sep 17 00:00:00 2001 From: Lukas Klingsbo Date: Mon, 20 Jul 2026 00:16:27 +0200 Subject: [PATCH 4/5] fix: Address the code review findings - Mutation safety during step: destroying bodies, shapes, chains, and joints from inside a step (collision callbacks) is deferred until the step returns, restoring the old engine's semantics (compare 0704959); destroy is idempotent, and mid-step creates throw a catchable StateError instead of aborting the process in native code. - World.destroy unregisters the custom filter and pre-solve callbacks, so their closures no longer leak in the backend registries or fire for a recycled world slot. - Body.destroy releases the user data of chains that die with the body, tracked through a chain-to-owner registry. - The wasm backend reads ids as unsigned consistently, keeping handles from creation, queries, and events equal even after 32768 generations of slot reuse. - 64-bit filter masks with the top bit set now split exactly on the wasm seam instead of collapsing to all-ones; -1 remains the all-categories sentinel. - Strings cross into the wasm module through a growable buffer instead of a fixed 4 KB scratch slice; body names are documented as truncated to 31 bytes by Box2D itself. - The C shim heap-allocates its id arrays instead of using caller-sized stack VLAs that could overflow the 64 KB wasm stack. - createBody and createShape validate finiteness, damping, density, and radius in debug mode, restoring the old Dart-side NaN safety net. - The README migration guide documents the (0, -10) default gravity of a bare World() and the new mid-step mutation semantics. --- README.md | 6 + packages/forge2d/lib/src/api/body.dart | 43 +++++- packages/forge2d/lib/src/api/chain.dart | 11 ++ packages/forge2d/lib/src/api/defs.dart | 3 +- .../forge2d/lib/src/api/joints/joint.dart | 10 ++ packages/forge2d/lib/src/api/shape.dart | 10 ++ packages/forge2d/lib/src/api/world.dart | 76 +++++++++- .../lib/src/backend/raw_box2d_wasm.dart | 39 ++++-- .../forge2d/lib/src/backend/wasm/box2d.wasm | Bin 227788 -> 227788 bytes .../lib/src/backend/wasm/wasm_runtime.dart | 26 +++- packages/forge2d/native/wasm/f2d_shim.c | 16 ++- .../forge2d/test/api/locked_world_test.dart | 132 ++++++++++++++++++ 12 files changed, 347 insertions(+), 25 deletions(-) create mode 100644 packages/forge2d/test/api/locked_world_test.dart diff --git a/README.md b/README.md index 0e758389..ff7f3375 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,12 @@ concepts map as follows: removed; stay on forge2d 0.14 if you depend on it. - Worlds default to `subStepCount: 4` in `step` instead of velocity and position iterations. +- A bare `World()` now has the Box2D default gravity of `(0, -10)`; the old + API defaulted to zero gravity. Top-down games should pass + `World(gravity: Vector2.zero())`. +- Destroying bodies, shapes, chains, or joints while the world is stepping + (from a collision callback) is deferred until the step ends; creating + them mid-step throws a `StateError` instead of the old silent queueing. ## Timeline diff --git a/packages/forge2d/lib/src/api/body.dart b/packages/forge2d/lib/src/api/body.dart index f0112ddd..23f6a11e 100644 --- a/packages/forge2d/lib/src/api/body.dart +++ b/packages/forge2d/lib/src/api/body.dart @@ -336,8 +336,23 @@ class Body { } /// Creates a shape with the given [geometry] and attaches it to the body. + /// + /// Cannot be called while the world is stepping. Shape createShape(ShapeGeometry geometry, [ShapeDef? definition]) { + world.checkCanMutate('create a shape'); final shapeDefinition = definition ?? ShapeDef(); + assert( + shapeDefinition.density >= 0, + 'ShapeDef.density must not be negative', + ); + assert( + switch (geometry) { + Circle(:final radius) => radius > 0 && radius.isFinite, + Capsule(:final radius) => radius > 0 && radius.isFinite, + _ => true, + }, + 'The geometry radius must be positive and finite', + ); final rawDefinition = _rawShapeDef(shapeDefinition); final (shapeIndex1, shapeWorldAndGeneration) = switch (geometry) { Circle(:final center, :final radius) => rawBox2D.createCircleShape( @@ -410,7 +425,10 @@ class Body { /// /// Chains need at least four points: open chains use the first and last /// point as invisible ghost anchors that smooth collisions at the ends. + /// + /// Cannot be called while the world is stepping. Chain createChain(ChainDef definition) { + world.checkCanMutate('create a chain'); if (definition.points.length < 4) { throw ArgumentError('A chain needs at least four points'); } @@ -446,6 +464,10 @@ class Body { world.chainUserData[(chainIndex1, chainWorldAndGeneration)] = definition.userData; } + world.chainOwners[(chainIndex1, chainWorldAndGeneration)] = ( + index1, + worldAndGeneration, + ); return Chain.internal(world, chainIndex1, chainWorldAndGeneration); } @@ -467,14 +489,33 @@ class Body { ]; } - /// Destroys this body and all its shapes and joints. + /// Destroys this body and all its shapes, chains, and joints. + /// + /// Safe to call while the world is stepping (from a callback or while + /// processing events): the destruction is deferred until the step ends, + /// and the body stays valid until then. void destroy() { + if (world.locked) { + world.deferredActions.add(destroy); + return; + } + if (!isValid) { + // Also covers a deferred destroy that was requested more than once. + return; + } for (final shape in shapes) { world.shapeUserData.remove((shape.index1, shape.worldAndGeneration)); } for (final joint in joints) { world.jointUserData.remove((joint.index1, joint.worldAndGeneration)); } + world.chainOwners.removeWhere((chainId, ownerId) { + if (ownerId == (index1, worldAndGeneration)) { + world.chainUserData.remove(chainId); + return true; + } + return false; + }); world.bodyUserData.remove((index1, worldAndGeneration)); rawBox2D.destroyBody(index1, worldAndGeneration); } diff --git a/packages/forge2d/lib/src/api/chain.dart b/packages/forge2d/lib/src/api/chain.dart index c6161ff6..8b08db1a 100644 --- a/packages/forge2d/lib/src/api/chain.dart +++ b/packages/forge2d/lib/src/api/chain.dart @@ -63,8 +63,19 @@ class Chain { } /// Destroys this chain and its segment shapes. + /// + /// Safe to call while the world is stepping: the destruction is deferred + /// until the step ends, and the chain stays valid until then. void destroy() { + if (world.locked) { + world.deferredActions.add(destroy); + return; + } + if (!isValid) { + return; + } world.chainUserData.remove((index1, worldAndGeneration)); + world.chainOwners.remove((index1, worldAndGeneration)); rawBox2D.destroyChain(index1, worldAndGeneration); } diff --git a/packages/forge2d/lib/src/api/defs.dart b/packages/forge2d/lib/src/api/defs.dart index 9fe59283..cc087e08 100644 --- a/packages/forge2d/lib/src/api/defs.dart +++ b/packages/forge2d/lib/src/api/defs.dart @@ -180,7 +180,8 @@ class BodyDef { /// The speed below which the body may fall asleep, in meters per second. double sleepThreshold; - /// An optional name for debugging. + /// An optional name for debugging, truncated to 31 bytes by the native + /// library. String? name; /// An arbitrary object associated with the body. diff --git a/packages/forge2d/lib/src/api/joints/joint.dart b/packages/forge2d/lib/src/api/joints/joint.dart index e23ca227..97113c98 100644 --- a/packages/forge2d/lib/src/api/joints/joint.dart +++ b/packages/forge2d/lib/src/api/joints/joint.dart @@ -164,7 +164,17 @@ abstract base class Joint { } /// Destroys this joint. + /// + /// Safe to call while the world is stepping: the destruction is deferred + /// until the step ends, and the joint stays valid until then. void destroy() { + if (world.locked) { + world.deferredActions.add(destroy); + return; + } + if (!isValid) { + return; + } world.jointUserData.remove((index1, worldAndGeneration)); rawBox2D.destroyJoint(index1, worldAndGeneration); } diff --git a/packages/forge2d/lib/src/api/shape.dart b/packages/forge2d/lib/src/api/shape.dart index 8812264d..5fc66b64 100644 --- a/packages/forge2d/lib/src/api/shape.dart +++ b/packages/forge2d/lib/src/api/shape.dart @@ -224,7 +224,17 @@ class Shape { /// /// When [updateBodyMass] is false, call [Body.applyMassFromShapes] /// afterwards. + /// + /// Safe to call while the world is stepping: the destruction is deferred + /// until the step ends, and the shape stays valid until then. void destroy({bool updateBodyMass = true}) { + if (world.locked) { + world.deferredActions.add(() => destroy(updateBodyMass: updateBodyMass)); + return; + } + if (!isValid) { + return; + } world.shapeUserData.remove((index1, worldAndGeneration)); rawBox2D.destroyShape( index1, diff --git a/packages/forge2d/lib/src/api/world.dart b/packages/forge2d/lib/src/api/world.dart index 052e1e32..998f8e58 100644 --- a/packages/forge2d/lib/src/api/world.dart +++ b/packages/forge2d/lib/src/api/world.dart @@ -68,6 +68,33 @@ class World { @internal final Map<(int, int), Object?> jointUserData = {}; + /// The owning body of every chain, keyed by chain id, so that destroying + /// a body can release its chains' user data. + @internal + final Map<(int, int), (int, int)> chainOwners = {}; + + /// Whether the world is currently inside [step]. Mutating operations are + /// deferred or rejected while stepping. + @internal + bool locked = false; + + /// Destroy operations requested during [step], flushed when it returns. + @internal + final List deferredActions = []; + + /// Throws when the world is stepping: [operation] cannot run from inside + /// a callback. Destroy operations are deferred automatically instead. + @internal + void checkCanMutate(String operation) { + if (locked) { + throw StateError( + 'Cannot $operation while the world is stepping. Collision callbacks ' + 'run inside step(); create after step() returns instead. Destroy ' + 'operations are deferred automatically.', + ); + } + } + /// Whether this world has not been destroyed. bool get isValid => rawBox2D.worldIsValid(id); @@ -77,7 +104,19 @@ class World { /// for accuracy; 4 is a good default. void step(double timeStep, {int subStepCount = 4}) { assert(isValid, 'World has been destroyed'); - rawBox2D.worldStep(id, timeStep, subStepCount); + locked = true; + try { + rawBox2D.worldStep(id, timeStep, subStepCount); + } finally { + locked = false; + if (deferredActions.isNotEmpty) { + final actions = List.of(deferredActions); + deferredActions.clear(); + for (final action in actions) { + action(); + } + } + } } /// The gravity vector, in meters per second squared. @@ -103,9 +142,29 @@ class World { rawBox2D.worldEnableContinuous(id, enabled: value); /// Creates a body in this world. + /// + /// Cannot be called while the world is stepping. Body createBody([BodyDef? definition]) { assert(isValid, 'World has been destroyed'); + checkCanMutate('create a body'); final bodyDefinition = definition ?? BodyDef(); + assert( + bodyDefinition.position.x.isFinite && bodyDefinition.position.y.isFinite, + 'BodyDef.position must be finite', + ); + assert( + bodyDefinition.linearVelocity.x.isFinite && + bodyDefinition.linearVelocity.y.isFinite, + 'BodyDef.linearVelocity must be finite', + ); + assert( + bodyDefinition.angularVelocity.isFinite, + 'BodyDef.angularVelocity must be finite', + ); + assert( + bodyDefinition.linearDamping >= 0 && bodyDefinition.angularDamping >= 0, + 'BodyDef damping must not be negative', + ); final (index1, worldAndGeneration) = rawBox2D.createBody( id, type: bodyDefinition.type.index, @@ -136,6 +195,7 @@ class World { /// Creates a distance joint from [definition]. DistanceJoint createDistanceJoint(DistanceJointDef definition) { + checkCanMutate('create a joint'); final (index1, worldAndGeneration) = rawBox2D.createDistanceJoint( id, bodyA: (definition.bodyA.index1, definition.bodyA.worldAndGeneration), @@ -161,6 +221,7 @@ class World { /// Creates a filter joint from [definition], disabling collision between /// the two bodies. FilterJoint createFilterJoint(FilterJointDef definition) { + checkCanMutate('create a joint'); final (index1, worldAndGeneration) = rawBox2D.createFilterJoint( id, bodyA: (definition.bodyA.index1, definition.bodyA.worldAndGeneration), @@ -172,6 +233,7 @@ class World { /// Creates a motor joint from [definition]. MotorJoint createMotorJoint(MotorJointDef definition) { + checkCanMutate('create a joint'); final (index1, worldAndGeneration) = rawBox2D.createMotorJoint( id, bodyA: (definition.bodyA.index1, definition.bodyA.worldAndGeneration), @@ -189,6 +251,7 @@ class World { /// Creates a mouse joint from [definition]. MouseJoint createMouseJoint(MouseJointDef definition) { + checkCanMutate('create a joint'); final (index1, worldAndGeneration) = rawBox2D.createMouseJoint( id, bodyA: (definition.bodyA.index1, definition.bodyA.worldAndGeneration), @@ -205,6 +268,7 @@ class World { /// Creates a prismatic joint from [definition]. PrismaticJoint createPrismaticJoint(PrismaticJointDef definition) { + checkCanMutate('create a joint'); final (index1, worldAndGeneration) = rawBox2D.createPrismaticJoint( id, bodyA: (definition.bodyA.index1, definition.bodyA.worldAndGeneration), @@ -231,6 +295,7 @@ class World { /// Creates a revolute joint from [definition]. RevoluteJoint createRevoluteJoint(RevoluteJointDef definition) { + checkCanMutate('create a joint'); final (index1, worldAndGeneration) = rawBox2D.createRevoluteJoint( id, bodyA: (definition.bodyA.index1, definition.bodyA.worldAndGeneration), @@ -257,6 +322,7 @@ class World { /// Creates a weld joint from [definition]. WeldJoint createWeldJoint(WeldJointDef definition) { + checkCanMutate('create a joint'); final (index1, worldAndGeneration) = rawBox2D.createWeldJoint( id, bodyA: (definition.bodyA.index1, definition.bodyA.worldAndGeneration), @@ -276,6 +342,7 @@ class World { /// Creates a wheel joint from [definition]. WheelJoint createWheelJoint(WheelJointDef definition) { + checkCanMutate('create a joint'); final (index1, worldAndGeneration) = rawBox2D.createWheelJoint( id, bodyA: (definition.bodyA.index1, definition.bodyA.worldAndGeneration), @@ -685,10 +752,17 @@ class World { /// Destroys this world and everything in it. void destroy() { assert(isValid, 'World has been destroyed'); + checkCanMutate('destroy the world'); + // Unregister simulation callbacks so their closures do not outlive the + // world in the backend registries. + rawBox2D.worldSetCustomFilterCallback(id, null); + rawBox2D.worldSetPreSolveCallback(id, null); rawBox2D.destroyWorld(id); bodyUserData.clear(); shapeUserData.clear(); chainUserData.clear(); jointUserData.clear(); + chainOwners.clear(); + deferredActions.clear(); } } diff --git a/packages/forge2d/lib/src/backend/raw_box2d_wasm.dart b/packages/forge2d/lib/src/backend/raw_box2d_wasm.dart index 80b3e8b3..56d2e790 100644 --- a/packages/forge2d/lib/src/backend/raw_box2d_wasm.dart +++ b/packages/forge2d/lib/src/backend/raw_box2d_wasm.dart @@ -50,14 +50,12 @@ final class RawBox2DWasm implements RawBox2D { // Scratch arena layout (see WasmRuntime.scratch, 4096 bytes): // - byte 0: small out-parameters (up to 128 bytes) // - byte 128: the f2dShapeDef struct (19 * 4 bytes) - // - byte 1024: string data + // Strings go through the runtime's growable string buffer instead. static const _outOffset = 0; static const _shapeDefOffset = 128; - static const _stringOffset = 1024; int get _out => _runtime.scratch + _outOffset; int get _shapeDefPointer => _runtime.scratch + _shapeDefOffset; - int get _string => _runtime.scratch + _stringOffset; num _call(String name, List arguments) => _runtime.call(name, arguments); @@ -72,11 +70,16 @@ final class RawBox2DWasm implements RawBox2D { static int _b(bool value) => value ? 1 : 0; /// Splits a Dart int holding a 64-bit bit field into unsigned 32-bit - /// halves. Negative values mean "all bits set" (the seam contract). + /// halves. -1 is the seam's "all bits set" sentinel; other negative + /// values are 64-bit patterns with the top bit set, which only exist + /// where ints are 64-bit (the VM and dart2wasm) and split exactly there. static (int, int) _splitBits(int value) { - if (value < 0) { + if (value == -1) { return (0xFFFFFFFF, 0xFFFFFFFF); } + if (value < 0) { + return (value & 0xFFFFFFFF, (value >> 32) & 0xFFFFFFFF); + } return (value % 0x100000000, value ~/ 0x100000000); } @@ -92,8 +95,20 @@ final class RawBox2DWasm implements RawBox2D { (double, double) _outVec2() => (_runtime.readF32(_out), _runtime.readF32(_out + 4)); - (int, int) _outIdPair() => - (_runtime.readI32(_out), _runtime.readI32(_out + 4)); + /// Reinterprets a value read through a signed 32-bit view as unsigned, + /// keeping ids consistent with the unsigned values in event records even + /// once a slot's generation count sets the top bit. + static int _unsigned32(int value) => value < 0 ? value + 0x100000000 : value; + + /// Normalizes the worldAndGeneration halves of a flat id pair list. + static List _idPairs(List raw) => [ + for (var i = 0; i < raw.length; i++) i.isOdd ? _unsigned32(raw[i]) : raw[i], + ]; + + (int, int) _outIdPair() => ( + _runtime.readI32(_out), + _unsigned32(_runtime.readI32(_out + 4)), + ); // World. @@ -183,7 +198,7 @@ final class RawBox2DWasm implements RawBox2D { required bool isEnabled, required bool allowFastRotation, }) { - final namePointer = _runtime.writeCString(_string, name); + final namePointer = _runtime.writeCString(name); _call('f2d_create_body', [ worldId, type, @@ -433,7 +448,7 @@ final class RawBox2DWasm implements RawBox2D { @override void bodySetName(int index1, int worldAndGeneration, String? name) => _call( 'f2d_body_set_name', - [index1, worldAndGeneration, _runtime.writeCString(_string, name)], + [index1, worldAndGeneration, _runtime.writeCString(name)], ); @override @@ -588,7 +603,7 @@ final class RawBox2DWasm implements RawBox2D { buffer, count, ]); - return _runtime.readI32List(buffer, written * 2); + return _idPairs(_runtime.readI32List(buffer, written * 2)); } @override @@ -607,7 +622,7 @@ final class RawBox2DWasm implements RawBox2D { buffer, count, ]); - return _runtime.readI32List(buffer, written * 2); + return _idPairs(_runtime.readI32List(buffer, written * 2)); } // Shapes. @@ -1112,7 +1127,7 @@ final class RawBox2DWasm implements RawBox2D { buffer, count, ]); - return _runtime.readI32List(buffer, written * 2); + return _idPairs(_runtime.readI32List(buffer, written * 2)); } // Joints. diff --git a/packages/forge2d/lib/src/backend/wasm/box2d.wasm b/packages/forge2d/lib/src/backend/wasm/box2d.wasm index 6b9e2a948ef61f3aee5d6a9eaa2de06b608fe641..037e3e46ac9914ed3c4ef1d4a59108c92dacdcbb 100755 GIT binary patch delta 1316 zcmb_bO=uHA6rOpz+1+fC?rsdJG*;iP4WtFlEfp%7QA&DH+gm~KAl{Pr)6!b8)CwN- z*xQIFFKxkt6g;GrCiYM;Lh4?|686YhfM8yG^K zgD3&ZfkhDN1*>0Ug82;c7|L=zFvSzQEVhmm13d$EDv=|TaJ-*3pA6s9@@OF!PoZ24 zcm|jsMgtk$Pvyw7d=3V-HDY`1E?gbn1~C7p z1RYb*eUT)1*plGqla&~JG6;)xMvB;3p8xgz#87c&gg!%C-kO4e!{mq@BuC`mF>ujSU)3bx zV&_+~Pl8U8U&%n~w;aL8>7L+kx*Y*U-@ybykdU=y=!?-r2lf4lvat;JdAxFG1xB@` zgHgdtzD}TB(eos!KJI&Ra}@^BL&%V=$;TKGypSSBLZ(yEaWDQR1akAXv z%o>>aXF6C18qPc{bC8Ewp%^z)C%xB@lpo(h$7qORB|;P^LnKPH4mfI*0BtB0aKSFc z7#bp~4t%nHGnIzQuQFnRbm8LL-rk-$=0~(V@or_4QY!OxICer;xeTMd?@M*g#{;_= zU{y3tMg6}OG4)RjE%i?pMp|j3X=8)Lr9Hu-=J$2zvMg-2TXMS&y=|+TM5~p2L;Z>$ bZChP!+sTg}Xm#_?$v2zHugtxJvrFFr18MTr delta 1264 zcmbu8Pe>F|9LL}L&D)vXnO*1YwrOb_znR?FJhYcEN-JL?O7sW11RbL25X^3r;-5&& zq(jmrZ?6s=B8rYehh4#4J$Nwi9&|DYBrgWyO|##)NeGrfr|-<0-=F!u-|w@qnpjv( zOy=zmr|jt_+q`?LSiEd^Nq8(Sb4K>ID=7pXY#i zHk!z&B84N*^9g93U`B?B57bH=!SZ5T&OS0BLJbJ;gZgQofj zMrf{!tl-yCOdUqlf#UXHWi!+1GGs@SfuCvt>D41i+G{|Gbd(8XPyslZ{9_6l6$Qa`IehU9$(&XO!upgZjKN*89~cxhx7Hcfmf zAhy>^9ZJBcVv1M_d5P$;aX2{INc^xId^b@L7AitOL{+*#&$QzP?SRqK _functions = {}; /// Fetches and instantiates the module, trying each of [candidates] in @@ -528,18 +533,27 @@ class WasmRuntime { } } - /// Writes [text] as a NUL-terminated UTF-8 string and returns the - /// pointer, or 0 for null. - int writeCString(int pointer, String? text) { + /// Writes [text] as a NUL-terminated UTF-8 string into the growable + /// string buffer and returns the pointer, or 0 for null. + int writeCString(String? text) { if (text == null) { return 0; } final bytes = utf8.encode(text); + if (bytes.length + 1 > _stringCapacity) { + if (_stringBuffer != 0) { + (_functions['free'] ??= + _exports.getProperty('free'.toJS)! as JSFunction) + .callAsFunction(null, _stringBuffer.toJS); + } + _stringCapacity = (bytes.length + 1) * 2; + _stringBuffer = _malloc(_stringCapacity); + } for (var i = 0; i < bytes.length; i++) { - _u8[pointer + i] = bytes[i]; + _u8[_stringBuffer + i] = bytes[i]; } - _u8[pointer + bytes.length] = 0; - return pointer; + _u8[_stringBuffer + bytes.length] = 0; + return _stringBuffer; } /// Reads a NUL-terminated UTF-8 string. diff --git a/packages/forge2d/native/wasm/f2d_shim.c b/packages/forge2d/native/wasm/f2d_shim.c index f14de86f..46c4be1b 100644 --- a/packages/forge2d/native/wasm/f2d_shim.c +++ b/packages/forge2d/native/wasm/f2d_shim.c @@ -17,6 +17,7 @@ // Keep this file in sync with lib/src/backend/raw_box2d_wasm.dart. #include +#include #include #include "box2d/box2d.h" @@ -336,12 +337,15 @@ F2D_EXPORT int f2d_body_get_shape_count(int32_t index1, uint32_t world_and_gener F2D_EXPORT int f2d_body_get_shapes(int32_t index1, uint32_t world_and_generation, int32_t* out, int capacity) { - b2ShapeId shapes[capacity]; + // Heap allocated: capacity is caller controlled and the wasm stack is + // only 64 KB. + b2ShapeId* shapes = malloc(capacity * sizeof(b2ShapeId)); int count = b2Body_GetShapes(f2d_body(index1, world_and_generation), shapes, capacity); for (int i = 0; i < count; ++i) { out[2 * i] = shapes[i].index1; out[2 * i + 1] = (int32_t)f2d_wg_shape(shapes[i]); } + free(shapes); return count; } @@ -351,12 +355,13 @@ F2D_EXPORT int f2d_body_get_joint_count(int32_t index1, uint32_t world_and_gener F2D_EXPORT int f2d_body_get_joints(int32_t index1, uint32_t world_and_generation, int32_t* out, int capacity) { - b2JointId joints[capacity]; + b2JointId* joints = malloc(capacity * sizeof(b2JointId)); int count = b2Body_GetJoints(f2d_body(index1, world_and_generation), joints, capacity); for (int i = 0; i < count; ++i) { out[2 * i] = joints[i].index1; out[2 * i + 1] = (int32_t)f2d_wg_joint(joints[i]); } + free(joints); return count; } @@ -639,7 +644,8 @@ F2D_EXPORT void f2d_create_chain(int32_t index1, uint32_t world_and_generation, uint32_t mask_hi, int32_t group_index, int is_loop, int enable_sensor_events, int32_t* out) { - b2SurfaceMaterial surface_materials[material_count]; + b2SurfaceMaterial* surface_materials = + malloc(material_count * sizeof(b2SurfaceMaterial)); for (int i = 0; i < material_count; ++i) { const float* m = materials + i * 6; surface_materials[i] = (b2SurfaceMaterial){ @@ -655,6 +661,7 @@ F2D_EXPORT void f2d_create_chain(int32_t index1, uint32_t world_and_generation, def.isLoop = is_loop; def.enableSensorEvents = enable_sensor_events; b2ChainId id = b2CreateChain(f2d_body(index1, world_and_generation), &def); + free(surface_materials); out[0] = id.index1; out[1] = (int32_t)f2d_wg_chain(id); } @@ -689,12 +696,13 @@ F2D_EXPORT int f2d_chain_get_segment_count(int32_t index1, uint32_t world_and_ge F2D_EXPORT int f2d_chain_get_segments(int32_t index1, uint32_t world_and_generation, int32_t* out, int capacity) { - b2ShapeId segments[capacity]; + b2ShapeId* segments = malloc(capacity * sizeof(b2ShapeId)); int count = b2Chain_GetSegments(f2d_chain(index1, world_and_generation), segments, capacity); for (int i = 0; i < count; ++i) { out[2 * i] = segments[i].index1; out[2 * i + 1] = (int32_t)f2d_wg_shape(segments[i]); } + free(segments); return count; } diff --git a/packages/forge2d/test/api/locked_world_test.dart b/packages/forge2d/test/api/locked_world_test.dart new file mode 100644 index 00000000..6302835e --- /dev/null +++ b/packages/forge2d/test/api/locked_world_test.dart @@ -0,0 +1,132 @@ +import 'package:forge2d/forge2d.dart'; +import 'package:test/test.dart'; + +void main() { + setUpAll(initializeForge2D); + + late World world; + + setUp(() => world = World()); + tearDown(() { + if (world.isValid) { + world.destroy(); + } + }); + + Body ground() => + world.createBody(BodyDef(position: Vector2(0, -1))) + ..createShape(Polygon.box(50, 1), ShapeDef(enablePreSolveEvents: true)); + + test('destroying a body from a pre-solve callback is deferred', () { + ground(); + final ball = world.createBody( + BodyDef(type: BodyType.dynamic, position: Vector2(0, 2)), + )..createShape(Circle(radius: 0.5), ShapeDef(enablePreSolveEvents: true)); + + var sawContact = false; + world.preSolveCallback = (shapeA, shapeB, normal) { + sawContact = true; + // Destroying mid-step must not abort or corrupt; it is deferred. + ball.destroy(); + expect(ball.isValid, isTrue, reason: 'still valid inside the step'); + return true; + }; + + for (var i = 0; i < 120 && !sawContact; i++) { + world.step(1 / 60); + } + expect(sawContact, isTrue); + expect(ball.isValid, isFalse, reason: 'destroyed after the step ended'); + }); + + test('a deferred destroy runs only once even if requested twice', () { + ground(); + final ball = world.createBody( + BodyDef(type: BodyType.dynamic, position: Vector2(0, 2)), + )..createShape(Circle(radius: 0.5), ShapeDef(enablePreSolveEvents: true)); + + var calls = 0; + world.preSolveCallback = (shapeA, shapeB, normal) { + calls++; + ball + ..destroy() + ..destroy(); + return true; + }; + for (var i = 0; i < 120 && calls == 0; i++) { + world.step(1 / 60); + } + expect(ball.isValid, isFalse); + // A second step must not blow up on the already-destroyed body. + world.step(1 / 60); + }); + + test('creating a body from a pre-solve callback throws a StateError', () { + ground(); + world + .createBody(BodyDef(type: BodyType.dynamic, position: Vector2(0, 2))) + .createShape( + Circle(radius: 0.5), + ShapeDef(enablePreSolveEvents: true), + ); + + Object? caught; + world.preSolveCallback = (shapeA, shapeB, normal) { + try { + world.createBody(); + } on Object catch (error) { + caught = error; + } + return true; + }; + for (var i = 0; i < 120 && caught == null; i++) { + world.step(1 / 60); + } + expect(caught, isA()); + }); + + test('destroying a world clears its simulation callbacks', () { + // A world whose filter callback rejects every collision. + ground(); + world.customFilterCallback = (shapeA, shapeB) => false; + world.destroy(); + + // A new world likely reuses the same slot; its collisions must not be + // filtered by the dead world's callback. + world = World(); + ground(); + final ball = world.createBody( + BodyDef(type: BodyType.dynamic, position: Vector2(0, 2)), + )..createShape(Circle(radius: 0.5)); + for (var i = 0; i < 120; i++) { + world.step(1 / 60); + } + expect( + ball.position.y, + greaterThan(0), + reason: "ball must land, not fall through the dead world's filter", + ); + }); + + test('destroying a body releases the user data of its chains', () { + final carrier = world.createBody(); + carrier.createChain( + ChainDef( + points: [Vector2(-6, 0), Vector2(-2, 0), Vector2(2, 0), Vector2(6, 0)], + userData: 'terrain', + ), + ); + expect(world.chainUserData, hasLength(1)); + carrier.destroy(); + expect(world.chainUserData, isEmpty); + expect(world.chainOwners, isEmpty); + }); + + test('long body names are safe and truncated to 31 bytes', () { + // The native library caps names at 31 bytes; the point of the long + // input is that the web backend must not overflow its string buffer. + final name = 'domino ${'x' * 5000}'; + final body = world.createBody(BodyDef(name: name)); + expect(body.name, name.substring(0, 31)); + }); +} From 430c4debe6c610d6f1922ef002b861aa9d328264 Mon Sep 17 00:00:00 2001 From: Lukas Klingsbo Date: Mon, 20 Jul 2026 00:24:25 +0200 Subject: [PATCH 5/5] test: Add exhaustive accessor round-trips, raising coverage to 96% Every public getter and setter on Body, Shape, Chain, and all eight joint types now round-trips through both backends, together with the DebugDraw default no-ops and the math conveniences. Line coverage of the VM-runnable library code rises from 77% to 96%, and the same suite runs on chrome/dart2js and chrome/dart2wasm. The tests immediately caught a real Box2D behavior worth knowing: setting fixedRotation zeroes the body's angular velocity. --- packages/forge2d/test/api/accessors_test.dart | 320 ++++++++++++++++++ 1 file changed, 320 insertions(+) create mode 100644 packages/forge2d/test/api/accessors_test.dart diff --git a/packages/forge2d/test/api/accessors_test.dart b/packages/forge2d/test/api/accessors_test.dart new file mode 100644 index 00000000..a0a3c9c6 --- /dev/null +++ b/packages/forge2d/test/api/accessors_test.dart @@ -0,0 +1,320 @@ +import 'dart:math' as math; + +import 'package:forge2d/forge2d.dart'; +import 'package:test/test.dart'; + +/// Exhaustive get/set round-trips for every public accessor, so that a +/// broken seam mapping (wrong native function, wrong argument order) in +/// either backend cannot go unnoticed. +void main() { + setUpAll(initializeForge2D); + + late World world; + late Body ground; + + Body dynamicBox(double x, double y) => world.createBody( + BodyDef(type: BodyType.dynamic, position: Vector2(x, y)), + )..createShape(Polygon.square(0.5)); + + setUp(() { + world = World(); + ground = world.createBody(BodyDef(position: Vector2(0, -1))) + ..createShape(Polygon.box(50, 1)); + }); + tearDown(() => world.destroy()); + + test('Body accessors round-trip', () { + final body = dynamicBox(0, 5) + ..angularVelocity = 1.25 + ..linearVelocity = Vector2(2, 3) + ..gravityScale = 0.5 + ..linearDamping = 0.125 + ..angularDamping = 0.25 + ..sleepThreshold = 0.75 + ..sleepEnabled = false + ..isBullet = true + ..name = 'renamed'; + + expect(body.angularVelocity, closeTo(1.25, 1e-6)); + expect(body.linearVelocity.x, closeTo(2, 1e-6)); + expect(body.gravityScale, closeTo(0.5, 1e-6)); + expect(body.linearDamping, closeTo(0.125, 1e-6)); + expect(body.angularDamping, closeTo(0.25, 1e-6)); + expect(body.sleepThreshold, closeTo(0.75, 1e-6)); + expect(body.sleepEnabled, isFalse); + expect(body.isBullet, isTrue); + // Fixing the rotation zeroes the angular velocity, so it is set last. + body.fixedRotation = true; + expect(body.fixedRotation, isTrue); + expect(body.angularVelocity, 0); + expect(body.name, 'renamed'); + expect(body.transform.position.y, closeTo(5, 1e-6)); + expect(body.worldCenterOfMass.y, closeTo(5, 1e-6)); + + body.applyForce(Vector2(10, 0), point: body.worldPoint(Vector2(0, 0.5))); + body.applyTorque(3); + body.applyLinearImpulse( + Vector2(0, 1), + point: body.worldPoint(Vector2(0.1, 0)), + ); + body.applyAngularImpulse(0.5); + world.step(1 / 60); + expect(body.isValid, isTrue); + }); + + test('Shape accessors round-trip', () { + final shape = dynamicBox(0, 5).shapes.single + ..preSolveEventsEnabled = true + ..userData = 'tagged'; + expect(shape.preSolveEventsEnabled, isTrue); + expect(shape.userData, 'tagged'); + shape.userData = null; + expect(shape.userData, isNull); + + shape.setDensity(4, updateBodyMass: false); + expect(shape.density, 4); + shape.body.applyMassFromShapes(); + expect(shape.body.mass, closeTo(4, 1e-5)); + }); + + test('Chain accessors round-trip', () { + final chain = ground.createChain( + ChainDef( + points: [Vector2(-6, 4), Vector2(-2, 4), Vector2(2, 4), Vector2(6, 4)], + ), + )..userData = 'rail'; + expect(chain.userData, 'rail'); + chain.userData = null; + expect(chain.userData, isNull); + expect(chain.segments.first.body, ground); + }); + + test('Joint common accessors round-trip', () { + final body = dynamicBox(0, 3); + final joint = world.createDistanceJoint( + DistanceJointDef( + bodyA: ground, + bodyB: body, + localAnchorA: Vector2(0, 7), + length: 4, + ), + )..collideConnected = true; + expect(joint.collideConnected, isTrue); + joint.wakeBodies(); + expect(body.isAwake, isTrue); + + for (var i = 0; i < 30; i++) { + world.step(1 / 60); + } + // The joint carries the hanging body against gravity. + expect(joint.constraintForce.length, greaterThan(0)); + expect(joint.constraintTorque.isFinite, isTrue); + }); + + test('DistanceJoint accessors round-trip', () { + final joint = + world.createDistanceJoint( + DistanceJointDef(bodyA: ground, bodyB: dynamicBox(0, 4), length: 2), + ) + ..length = 3 + ..springEnabled = true + ..springHertz = 2.5 + ..springDampingRatio = 0.3 + ..limitEnabled = true + ..motorEnabled = true + ..motorSpeed = 0.5 + ..maxMotorForce = 12 + ..setLengthRange(min: 1, max: 5); + expect(joint.length, closeTo(3, 1e-6)); + expect(joint.springEnabled, isTrue); + expect(joint.springHertz, closeTo(2.5, 1e-6)); + expect(joint.springDampingRatio, closeTo(0.3, 1e-6)); + expect(joint.limitEnabled, isTrue); + expect(joint.minLength, closeTo(1, 1e-6)); + expect(joint.maxLength, closeTo(5, 1e-6)); + expect(joint.motorEnabled, isTrue); + expect(joint.motorSpeed, closeTo(0.5, 1e-6)); + expect(joint.maxMotorForce, closeTo(12, 1e-6)); + world.step(1 / 60); + expect(joint.currentLength, greaterThan(0)); + expect(joint.motorForce.isFinite, isTrue); + }); + + test('MotorJoint accessors round-trip', () { + final joint = + world.createMotorJoint( + MotorJointDef(bodyA: ground, bodyB: dynamicBox(0, 3)), + ) + ..linearOffset = Vector2(1, 2) + ..angularOffset = 0.5 + ..maxForce = 40 + ..maxTorque = 20 + ..correctionFactor = 0.7; + expect(joint.linearOffset.x, closeTo(1, 1e-6)); + expect(joint.linearOffset.y, closeTo(2, 1e-6)); + expect(joint.angularOffset, closeTo(0.5, 1e-6)); + expect(joint.maxForce, 40); + expect(joint.maxTorque, 20); + expect(joint.correctionFactor, closeTo(0.7, 1e-6)); + }); + + test('MouseJoint accessors round-trip', () { + final joint = + world.createMouseJoint( + MouseJointDef(bodyA: ground, bodyB: dynamicBox(0, 3)), + ) + ..target = Vector2(3, 4) + ..springHertz = 6 + ..springDampingRatio = 0.9 + ..maxForce = 250; + expect(joint.target, Vector2(3, 4)); + expect(joint.springHertz, closeTo(6, 1e-6)); + expect(joint.springDampingRatio, closeTo(0.9, 1e-6)); + expect(joint.maxForce, 250); + }); + + test('PrismaticJoint accessors round-trip', () { + final joint = + world.createPrismaticJoint( + PrismaticJointDef( + bodyA: ground, + bodyB: dynamicBox(0, 3), + localAnchorA: Vector2(0, 4), + localAxisA: Vector2(0, 1), + ), + ) + ..springEnabled = true + ..springHertz = 3 + ..springDampingRatio = 0.4 + ..targetTranslation = 0.5 + ..limitEnabled = true + ..motorEnabled = true + ..motorSpeed = 1.5 + ..maxMotorForce = 60 + ..setLimits(lower: -2, upper: 2); + expect(joint.springEnabled, isTrue); + expect(joint.springHertz, closeTo(3, 1e-6)); + expect(joint.springDampingRatio, closeTo(0.4, 1e-6)); + expect(joint.targetTranslation, closeTo(0.5, 1e-6)); + expect(joint.limitEnabled, isTrue); + expect(joint.lowerLimit, closeTo(-2, 1e-6)); + expect(joint.upperLimit, closeTo(2, 1e-6)); + expect(joint.motorEnabled, isTrue); + expect(joint.motorSpeed, closeTo(1.5, 1e-6)); + expect(joint.maxMotorForce, closeTo(60, 1e-6)); + world.step(1 / 60); + expect(joint.translation.isFinite, isTrue); + expect(joint.speed.isFinite, isTrue); + expect(joint.motorForce.isFinite, isTrue); + }); + + test('RevoluteJoint accessors round-trip', () { + final joint = + world.createRevoluteJoint( + RevoluteJointDef( + bodyA: ground, + bodyB: dynamicBox(0, 3), + localAnchorA: Vector2(0, 4), + ), + ) + ..springEnabled = true + ..springHertz = 2 + ..springDampingRatio = 0.6 + ..targetAngle = 0.25 + ..limitEnabled = true + ..motorEnabled = true + ..motorSpeed = 2 + ..maxMotorTorque = 15 + ..setLimits(lower: -math.pi / 4, upper: math.pi / 4); + expect(joint.springEnabled, isTrue); + expect(joint.springHertz, closeTo(2, 1e-6)); + expect(joint.springDampingRatio, closeTo(0.6, 1e-6)); + expect(joint.targetAngle, closeTo(0.25, 1e-6)); + expect(joint.limitEnabled, isTrue); + expect(joint.lowerLimit, closeTo(-math.pi / 4, 1e-6)); + expect(joint.upperLimit, closeTo(math.pi / 4, 1e-6)); + expect(joint.motorEnabled, isTrue); + expect(joint.motorSpeed, closeTo(2, 1e-6)); + expect(joint.maxMotorTorque, closeTo(15, 1e-6)); + world.step(1 / 60); + expect(joint.angle.isFinite, isTrue); + expect(joint.motorTorque.isFinite, isTrue); + }); + + test('WeldJoint accessors round-trip', () { + final joint = + world.createWeldJoint( + WeldJointDef(bodyA: ground, bodyB: dynamicBox(0, 3)), + ) + ..linearHertz = 1.5 + ..angularHertz = 2.5 + ..linearDampingRatio = 0.2 + ..angularDampingRatio = 0.8; + expect(joint.linearHertz, closeTo(1.5, 1e-6)); + expect(joint.angularHertz, closeTo(2.5, 1e-6)); + expect(joint.linearDampingRatio, closeTo(0.2, 1e-6)); + expect(joint.angularDampingRatio, closeTo(0.8, 1e-6)); + }); + + test('WheelJoint accessors round-trip', () { + final joint = + world.createWheelJoint( + WheelJointDef(bodyA: ground, bodyB: dynamicBox(0, 3)), + ) + ..springEnabled = false + ..springHertz = 5 + ..springDampingRatio = 0.1 + ..limitEnabled = true + ..motorEnabled = true + ..motorSpeed = 3 + ..maxMotorTorque = 25 + ..setLimits(lower: -1, upper: 1); + expect(joint.springEnabled, isFalse); + expect(joint.springHertz, closeTo(5, 1e-6)); + expect(joint.springDampingRatio, closeTo(0.1, 1e-6)); + expect(joint.limitEnabled, isTrue); + expect(joint.lowerLimit, closeTo(-1, 1e-6)); + expect(joint.upperLimit, closeTo(1, 1e-6)); + expect(joint.motorEnabled, isTrue); + expect(joint.motorSpeed, closeTo(3, 1e-6)); + expect(joint.maxMotorTorque, closeTo(25, 1e-6)); + world.step(1 / 60); + expect(joint.motorTorque.isFinite, isTrue); + }); + + test('the DebugDraw defaults are callable no-ops', () { + dynamicBox(0, 2); + world.createRevoluteJoint( + RevoluteJointDef(bodyA: ground, bodyB: dynamicBox(3, 2)), + ); + // A bare subclass with every toggle on: the default empty draw methods + // must all be safely callable. + final draw = _BareDebugDraw() + ..drawJointExtras = true + ..drawBounds = true + ..drawMass = true + ..drawBodyNames = true + ..drawContacts = true + ..drawGraphColors = true + ..drawContactNormals = true + ..drawContactImpulses = true + ..drawContactFeatures = true + ..drawFrictionImpulses = true + ..drawIslands = true; + world + ..step(1 / 60) + ..draw(draw); + }); + + test('math conveniences', () { + expect(const Rot.identity(), Rot.fromAngle(0)); + expect({const Rot.identity(), Rot.fromAngle(0)}, hasLength(1)); + expect(const Rot.identity().toString(), contains('Rot')); + expect(Transform.identity().toString(), contains('Transform')); + expect(Aabb(Vector2.zero(), Vector2(1, 1)).toString(), contains('Aabb')); + final rotated = Rot.fromAngle(math.pi / 2).inverseRotate(Vector2(0, 1)); + expect(rotated.x, closeTo(1, 1e-9)); + }); +} + +class _BareDebugDraw extends DebugDraw {}